// Copyright (c) 2017 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 <NCollection_Handle.hxx>
#include <NCollection_Array1.hxx>
#include <PCDM.hxx>
#include <PCDM_ReadWriter.hxx>
#include <Standard_ErrorHandler.hxx>
#include <Standard_NullObject.hxx>
#include <StdObjMgt_Persistent.hxx>
#include <StdObjMgt_ReadData.hxx>
#include <StdObjMgt_WriteData.hxx>
#include <StdStorage.hxx>
#include <StdStorage_Data.hxx>
#include <StdStorage_HeaderData.hxx>
#include <StdStorage_TypeData.hxx>
#include <StdStorage_RootData.hxx>
#include <StdStorage_Root.hxx>
#include <NCollection_Sequence.hxx>
#include <NCollection_HSequence.hxx>
#include <StdStorage_BacketOfPersistent.hxx>
#include <Storage.hxx>
#include <Storage_BaseDriver.hxx>
#include <Storage_StreamTypeMismatchError.hxx>
#include <Storage_StreamFormatError.hxx>
#include <Storage_StreamWriteError.hxx>

#include <cstdio>

//=======================================================================
// StdStorage::Version
//=======================================================================
TCollection_AsciiString StdStorage::Version()
{
  TCollection_AsciiString v("1.3");
  return v;
}

//=======================================================================
// StdStorage::Read
// Reads data from a file
//=======================================================================
Storage_Error StdStorage::Read(const TCollection_AsciiString& theFileName,
                               occ::handle<StdStorage_Data>&  theData)
{
  // Create a driver appropriate for the given file
  occ::handle<Storage_BaseDriver> aDriver;
  if (PCDM::FileDriverType(theFileName, aDriver) == PCDM_TOFD_Unknown)
  {
    return Storage_VSWrongFileDriver;
  }

  // Try to open the file
  try
  {
    OCC_CATCH_SIGNALS
    PCDM_ReadWriter::Open(aDriver, theFileName, Storage_VSRead);
  }
  catch (Standard_Failure const&)
  {
    return Storage_VSOpenError;
  }

  return Read(aDriver, theData);
}

//=======================================================================
// StdStorage::Read
// Reads data from a pre-opened for reading driver
//=======================================================================
Storage_Error StdStorage::Read(const occ::handle<Storage_BaseDriver>& theDriver,
                               occ::handle<StdStorage_Data>&          theData)
{
  if (theData.IsNull())
  {
    theData = new StdStorage_Data;
  }
  else
  {
    theData->Clear();
  }

  occ::handle<StdStorage_HeaderData> aHeaderData = theData->HeaderData();
  occ::handle<StdStorage_TypeData>   aTypeData   = theData->TypeData();
  occ::handle<StdStorage_RootData>   aRootData   = theData->RootData();

  // Read header section
  if (!aHeaderData->Read(theDriver))
  {
    return aHeaderData->ErrorStatus();
  }

  // Read types section
  if (!aTypeData->Read(theDriver))
  {
    return aTypeData->ErrorStatus();
  }

  // Select instantiators for the used types
  NCollection_Array1<StdObjMgt_Persistent::Instantiator> anInstantiators(
    1,
    aTypeData->NumberOfTypes());
  for (int i = 1; i <= aTypeData->NumberOfTypes(); i++)
  {
    StdObjMgt_Persistent::Instantiator anInstantiator = aTypeData->Instantiator(i);
    if (anInstantiator)
    {
      anInstantiators(i) = anInstantiator;
    }
    else
    {
      return Storage_VSUnknownType;
    }
  }

  // Read root section
  if (!aRootData->Read(theDriver))
  {
    return aRootData->ErrorStatus();
  }

  Storage_Error anError;

  // Read and parse reference section
  StdObjMgt_ReadData aReadData(theDriver, aHeaderData->NumberOfObjects());

  anError = theDriver->BeginReadRefSection();
  if (anError != Storage_VSOk)
  {
    return anError;
  }

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

    if (anError != Storage_VSOk)
    {
      return anError;
    }

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

  anError = theDriver->EndReadRefSection();
  if (anError != Storage_VSOk)
  {
    return anError;
  }

  // Read and parse data section
  anError = theDriver->BeginReadDataSection();
  if (anError != Storage_VSOk)
  {
    return anError;
  }

  for (int i = 1; i <= aHeaderData->NumberOfObjects(); i++)
  {
    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;
    }

    if (anError != Storage_VSOk)
    {
      return anError;
    }
  }

  anError = theDriver->EndReadDataSection();
  if (anError != Storage_VSOk)
  {
    return anError;
  }

  occ::handle<NCollection_HSequence<occ::handle<StdStorage_Root>>> aRoots = aRootData->Roots();
  if (!aRoots.IsNull())
  {
    for (NCollection_HSequence<occ::handle<StdStorage_Root>>::Iterator anIt(*aRoots); anIt.More();
         anIt.Next())
    {
      occ::handle<StdStorage_Root>& aRoot = anIt.ChangeValue();
      aRoot->SetObject(aReadData.PersistentObject(aRoot->Reference()));
    }
  }

  return Storage_VSOk;
}

//=======================================================================
// StdStorage::currentDate
//=======================================================================
static TCollection_AsciiString currentDate()
{
#define SLENGTH 80

  char       nowstr[SLENGTH];
  time_t     nowbin;
  struct tm* nowstruct;
  if (time(&nowbin) != (time_t)-1)
  {
    nowstruct = localtime(&nowbin);
    strftime(nowstr, SLENGTH, "%m/%d/%Y", nowstruct);
  }
  TCollection_AsciiString t(nowstr);
  return t;

#undef SLENGTH
}

//=======================================================================
// StdStorage::Write
//=======================================================================
Storage_Error StdStorage::Write(const occ::handle<Storage_BaseDriver>& theDriver,
                                const occ::handle<StdStorage_Data>&    theData)
{
  Standard_NullObject_Raise_if(theData.IsNull(), "Null storage data");

  occ::handle<StdStorage_HeaderData> aHeaderData = theData->HeaderData();
  occ::handle<StdStorage_TypeData>   aTypeData   = theData->TypeData();
  occ::handle<StdStorage_RootData>   aRootData   = theData->RootData();

  aHeaderData->SetCreationDate(currentDate());

  occ::handle<NCollection_HSequence<occ::handle<StdStorage_Root>>> aRoots = aRootData->Roots();

  StdStorage_BucketOfPersistent aPObjs;

  if (!aRoots.IsNull())
  {
    StdObjMgt_Persistent::SequenceOfPersistent aPQueue;
    for (NCollection_HSequence<occ::handle<StdStorage_Root>>::Iterator anIt(*aRoots); anIt.More();
         anIt.Next())
    {
      occ::handle<StdStorage_Root>      aRoot = anIt.ChangeValue();
      occ::handle<StdObjMgt_Persistent> aPObj = aRoot->Object();
      if (!aPObj.IsNull())
      {
        aPQueue.Append(aPObj);
      }
    }
    while (!aPQueue.IsEmpty())
    {
      StdObjMgt_Persistent::SequenceOfPersistent aPQueue1;
      for (StdObjMgt_Persistent::SequenceOfPersistent::Iterator anIt(aPQueue); anIt.More();
           anIt.Next())
      {
        occ::handle<StdObjMgt_Persistent>& aPObj = anIt.ChangeValue();
        if (!aPObj.IsNull())
        {
          if (aPObj->TypeNum() == 0 && aPObj->RefNum() == 0)
          {
            aPObj->TypeNum(aTypeData->AddType(aPObj));
            int anPObjId = aPObjs.Length() + 1;
            aPObj->RefNum(anPObjId);
            aPObjs.Append(aPObj);
            aPObj->PChildren(aPQueue1);
          }
        }
      }
      aPQueue.Assign(aPQueue1);
    }
  }

  aHeaderData->SetStorageVersion(StdStorage::Version());
  aHeaderData->SetNumberOfObjects(aPObjs.Length());

  try
  {
    // Write header section
    if (!aHeaderData->Write(theDriver))
    {
      return aHeaderData->ErrorStatus();
    }

    // Write types section
    if (!aTypeData->Write(theDriver))
    {
      return aTypeData->ErrorStatus();
    }

    // Write root section
    if (!aRootData->Write(theDriver))
    {
      return aRootData->ErrorStatus();
    }

    Storage_Error anError;

    // Write reference section
    anError = theDriver->BeginWriteRefSection();
    if (anError != Storage_VSOk)
    {
      return anError;
    }

    theDriver->SetRefSectionSize(aPObjs.Length());
    for (StdStorage_BucketIterator anIt(&aPObjs); anIt.More(); anIt.Next())
    {
      occ::handle<StdObjMgt_Persistent> aPObj = anIt.Value();
      if (!aPObj.IsNull())
      {
        theDriver->WriteReferenceType(aPObj->RefNum(), aPObj->TypeNum());
      }
    }

    anError = theDriver->EndWriteRefSection();
    if (anError != Storage_VSOk)
    {
      return anError;
    }

    // Write data section
    anError = theDriver->BeginWriteDataSection();
    if (anError != Storage_VSOk)
    {
      return anError;
    }

    StdObjMgt_WriteData aWriteData(theDriver);
    for (StdStorage_BucketIterator anIt(&aPObjs); anIt.More(); anIt.Next())
    {
      occ::handle<StdObjMgt_Persistent> aPObj = anIt.Value();
      if (!aPObj.IsNull())
      {
        aWriteData.WritePersistentObject(aPObj);
      }
    }

    anError = theDriver->EndWriteDataSection();
    if (anError != Storage_VSOk)
    {
      return anError;
    }
  }
  catch (Storage_StreamWriteError const&)
  {
    return Storage_VSWriteError;
  }

  return Storage_VSOk;
}
