// Copyright (c) 2015 OPEN CASCADE SAS
//
// This file is part of Open CASCADE Technology software library.
//
// This library is free software; you can redistribute it and/or modify it under
// the terms of the GNU Lesser General Public License version 2.1 as published
// by the Free Software Foundation, with special exception defined in the file
// OCCT_LGPL_EXCEPTION.txt. Consult the file LICENSE_LGPL_21.txt included in OCCT
// distribution for complete text of the license and disclaimer of any warranty.
//
// Alternatively, this file may be used under the terms of Open CASCADE
// commercial license or contractual agreement.

#include <StdLDrivers_DocumentRetrievalDriver.hxx>
#include <StdLDrivers.hxx>

#include <StdObjMgt_MapOfInstantiators.hxx>
#include <StdObjMgt_ReadData.hxx>

#include <Storage_HeaderData.hxx>
#include <Storage_TypeData.hxx>
#include <Storage_RootData.hxx>
#include <Storage_BaseDriver.hxx>
#include <Storage_StreamTypeMismatchError.hxx>
#include <Storage_StreamFormatError.hxx>
#include <Storage_StreamReadError.hxx>

#include <PCDM.hxx>
#include <PCDM_ReadWriter.hxx>

#include <Standard_ErrorHandler.hxx>
#include <Standard_NotImplemented.hxx>
#include <NCollection_Array1.hxx>
#include <TDocStd_Document.hxx>
#include <Storage_Schema.hxx>

IMPLEMENT_STANDARD_RTTIEXT(StdLDrivers_DocumentRetrievalDriver, PCDM_RetrievalDriver)

//=======================================================================
// function : Read
// purpose  : Retrieve the content of a file into a new document
//=======================================================================
void StdLDrivers_DocumentRetrievalDriver::Read(const TCollection_ExtendedString& theFileName,
                                               const Handle(CDM_Document)&       theNewDocument,
                                               const Handle(CDM_Application)&,
                                               const Handle(PCDM_ReaderFilter)&,
                                               const Message_ProgressRange& /*theRange*/)
{
  // Read header data and persistent document
  Storage_HeaderData           aHeaderData;
  Handle(StdObjMgt_Persistent) aPDocument = read(theFileName, aHeaderData);
  if (aPDocument.IsNull())
    return;

  // Import transient document from the persistent one
  aPDocument->ImportDocument(Handle(TDocStd_Document)::DownCast(theNewDocument));

  // Copy comments from the header data
  theNewDocument->SetComments(aHeaderData.Comments());
}

//=======================================================================
// function : read
// purpose  : Read persistent document from a file
//=======================================================================
Handle(StdObjMgt_Persistent) StdLDrivers_DocumentRetrievalDriver::read(
  const TCollection_ExtendedString& theFileName,
  Storage_HeaderData&               theHeaderData)
{
  Standard_Integer i;

  // Create a driver appropriate for the given file
  Handle(Storage_BaseDriver) aFileDriver;
  if (PCDM::FileDriverType(TCollection_AsciiString(theFileName), aFileDriver) == PCDM_TOFD_Unknown)
  {
    myReaderStatus = PCDM_RS_UnknownFileDriver;
    return NULL;
  }

  // Try to open the file
  try
  {
    OCC_CATCH_SIGNALS
    PCDM_ReadWriter::Open(aFileDriver, theFileName, Storage_VSRead);
    myReaderStatus = PCDM_RS_OK;
  }
  catch (Standard_Failure const& anException)
  {
    myReaderStatus = PCDM_RS_OpenError;

    Standard_SStream aMsg;
    aMsg << anException << std::endl;
    throw Standard_Failure(aMsg.str().c_str());
  }

  // Read header section
  if (!theHeaderData.Read(aFileDriver))
    raiseOnStorageError(theHeaderData.ErrorStatus());

  // Read type section
  Storage_TypeData aTypeData;
  if (!aTypeData.Read(aFileDriver))
    raiseOnStorageError(aTypeData.ErrorStatus());

  // Read root section
  Storage_RootData aRootData;
  if (!aRootData.Read(aFileDriver))
    raiseOnStorageError(aRootData.ErrorStatus());

  if (aRootData.NumberOfRoots() < 1)
  {
    myReaderStatus = PCDM_RS_NoDocument;

    Standard_SStream aMsg;
    aMsg << "could not find any document in this file" << std::endl;
    throw Standard_Failure(aMsg.str().c_str());
  }

  // Select instantiators for the used types
  NCollection_Array1<StdObjMgt_Persistent::Instantiator> anInstantiators(1,
                                                                         aTypeData.NumberOfTypes());
  {
    StdObjMgt_MapOfInstantiators aMapOfInst;
    bindTypes(aMapOfInst);

    TColStd_SequenceOfAsciiString anUnknownTypes;
    Standard_Integer              aCurTypeNum;
    TCollection_AsciiString       aCurTypeName;

    for (i = 1; i <= aTypeData.NumberOfTypes(); i++)
    {
      aCurTypeName = aTypeData.Type(i);
      aCurTypeNum  = aTypeData.Type(aCurTypeName);

      TCollection_AsciiString newName;
      if (Storage_Schema::CheckTypeMigration(aCurTypeName, newName))
      {
#ifdef OCCT_DEBUG
        std::cout << "CheckTypeMigration:OldType = " << aCurTypeName << " Len = " << aCurTypeNum
                  << std::endl;
        std::cout << "CheckTypeMigration:NewType = " << newName << " Len = " << newName.Length()
                  << std::endl;
#endif
        aCurTypeName = newName;
      }
      StdObjMgt_Persistent::Instantiator anInstantiator;
      if (aMapOfInst.Find(aCurTypeName, anInstantiator))
        anInstantiators(aCurTypeNum) = anInstantiator;
      else
        anUnknownTypes.Append(aCurTypeName);
    }

    if (!anUnknownTypes.IsEmpty())
    {
      myReaderStatus = PCDM_RS_TypeNotFoundInSchema;

      Standard_SStream aMsg;
      aMsg << "cannot read: `" << theFileName
           << "' because it contains the following unknown types: ";
      for (i = 1; i <= anUnknownTypes.Length(); i++)
      {
        aMsg << anUnknownTypes(i);
        if (i < anUnknownTypes.Length())
          aMsg << ",";
        else
          aMsg << std::endl;
      }

      throw Standard_Failure(aMsg.str().c_str());
    }
  }

  // Read and parse reference section
  StdObjMgt_ReadData aReadData(aFileDriver, theHeaderData.NumberOfObjects());

  raiseOnStorageError(aFileDriver->BeginReadRefSection());

  Standard_Integer len = aFileDriver->RefSectionSize();
  for (i = 1; i <= len; i++)
  {
    Standard_Integer aRef = 0, aType = 0;
    Storage_Error    anError;
    try
    {
      OCC_CATCH_SIGNALS
      aFileDriver->ReadReferenceType(aRef, aType);
      anError = Storage_VSOk;
    }
    catch (Storage_StreamTypeMismatchError const&)
    {
      anError = Storage_VSTypeMismatch;
    }

    raiseOnStorageError(anError);

    aReadData.CreatePersistentObject(aRef, anInstantiators(aType));
  }

  raiseOnStorageError(aFileDriver->EndReadRefSection());

  // Read and parse data section
  raiseOnStorageError(aFileDriver->BeginReadDataSection());

  for (i = 1; i <= theHeaderData.NumberOfObjects(); i++)
  {
    Storage_Error anError;
    try
    {
      OCC_CATCH_SIGNALS
      aReadData.ReadPersistentObject(i);
      anError = Storage_VSOk;
    }
    catch (Storage_StreamTypeMismatchError const&)
    {
      anError = Storage_VSTypeMismatch;
    }
    catch (Storage_StreamFormatError const&)
    {
      anError = Storage_VSFormatError;
    }
    catch (Storage_StreamReadError const&)
    {
      anError = Storage_VSFormatError;
    }

    raiseOnStorageError(anError);
  }

  raiseOnStorageError(aFileDriver->EndReadDataSection());

  // Get persistent document from the root object
  return aReadData.PersistentObject(aRootData.Roots()->First()->Reference());
}

//=======================================================================
// function : Read
// purpose  : not implemented
//=======================================================================

void StdLDrivers_DocumentRetrievalDriver::Read(Standard_IStream& /*theIStream*/,
                                               const Handle(Storage_Data)& /*theStorageData*/,
                                               const Handle(CDM_Document)& /*theDoc*/,
                                               const Handle(CDM_Application)& /*theApplication*/,
                                               const Handle(PCDM_ReaderFilter)& /*theFilter*/,
                                               const Message_ProgressRange& /*theRange*/)
{
  throw Standard_NotImplemented(
    "Reading from stream is not supported by StdLDrivers_DocumentRetrievalDriver");
}

//=======================================================================
// function : raiseOnStorageError
// purpose  : Update the reader status and raise an exception
//           appropriate for the given storage error
//=======================================================================
void StdLDrivers_DocumentRetrievalDriver::raiseOnStorageError(Storage_Error theError)
{
  Standard_SStream aMsg;

  switch (theError)
  {
    case Storage_VSOk:
      break;

    case Storage_VSOpenError:
    case Storage_VSNotOpen:
    case Storage_VSAlreadyOpen:
      myReaderStatus = PCDM_RS_OpenError;
      aMsg << "Stream Open Error" << std::endl;
      throw Standard_Failure(aMsg.str().c_str());

    case Storage_VSModeError:
      myReaderStatus = PCDM_RS_WrongStreamMode;
      aMsg << "Stream is opened with a wrong mode for operation" << std::endl;
      throw Standard_Failure(aMsg.str().c_str());

    case Storage_VSSectionNotFound:
      myReaderStatus = PCDM_RS_FormatFailure;
      aMsg << "Section is not found" << std::endl;
      throw Standard_Failure(aMsg.str().c_str());

    case Storage_VSFormatError:
      myReaderStatus = PCDM_RS_FormatFailure;
      aMsg << "Wrong format error" << std::endl;
      throw Standard_Failure(aMsg.str().c_str());

    case Storage_VSUnknownType:
      myReaderStatus = PCDM_RS_TypeFailure;
      aMsg << "Try to read an unknown type" << std::endl;
      throw Standard_Failure(aMsg.str().c_str());

    case Storage_VSTypeMismatch:
      myReaderStatus = PCDM_RS_TypeFailure;
      aMsg << "Try to read a wrong primitive type" << std::endl;
      throw Standard_Failure(aMsg.str().c_str());

    default:
      myReaderStatus = PCDM_RS_DriverFailure;
      aMsg << "Retrieval Driver Failure" << std::endl;
      throw Standard_Failure(aMsg.str().c_str());
  }
}

//=======================================================================
// function : bindTypes
// purpose  : Register types
//=======================================================================
void StdLDrivers_DocumentRetrievalDriver::bindTypes(StdObjMgt_MapOfInstantiators& theMap)
{
  StdLDrivers::BindTypes(theMap);
}
