// Author: Kirill Gavrilov
// Copyright (c) 2016-2019 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.

#ifndef _RWMesh_CafReader_HeaderFile
#define _RWMesh_CafReader_HeaderFile

#include <Message_ProgressRange.hxx>
#include <OSD_OpenFile.hxx>
#include <RWMesh_CoordinateSystemConverter.hxx>
#include <RWMesh_NodeAttributes.hxx>
#include <TColStd_IndexedDataMapOfStringString.hxx>
#include <TDF_Label.hxx>
#include <TopTools_SequenceOfShape.hxx>

class TDocStd_Document;
class XCAFDoc_ShapeTool;
class XCAFDoc_ColorTool;
class XCAFDoc_VisMaterialTool;

//! Extended status bits.
enum RWMesh_CafReaderStatusEx
{
  RWMesh_CafReaderStatusEx_NONE = 0, //!< empty status
  RWMesh_CafReaderStatusEx_Partial =
    0x01, //!< partial read (due to unexpected EOF, syntax error, memory limit)
};

//! The general interface for importing mesh data into XDE document.
//!
//! The tool implements auxiliary structures for creating an XDE document in two steps:
//! 1) Creating TopoDS_Shape hierarchy (myRootShapes)
//!    and Shape attributes (myAttribMap) separately within performMesh().
//!    Attributes include names and styles.
//! 2) Filling XDE document from these auxiliary structures.
//!    Named elements are expanded within document structure, while Compounds having no named
//!    children will remain collapsed. In addition, unnamed nodes can be filled with generated names
//!    like "Face", "Compound" via generateNames() method, and the very root unnamed node can be
//!    filled from file name like "MyModel.obj".
class RWMesh_CafReader : public Standard_Transient
{
  DEFINE_STANDARD_RTTIEXT(RWMesh_CafReader, Standard_Transient)
public:
  //! Structure holding tools for filling the document.
  struct CafDocumentTools
  {
    Handle(XCAFDoc_ShapeTool)                                             ShapeTool;
    Handle(XCAFDoc_ColorTool)                                             ColorTool;
    Handle(XCAFDoc_VisMaterialTool)                                       VisMaterialTool;
    NCollection_DataMap<TopoDS_Shape, TDF_Label, TopTools_ShapeMapHasher> ComponentMap;
    NCollection_DataMap<TopoDS_Shape, TDF_Label, TopTools_ShapeMapHasher> OriginalShapeMap;
  };

public:
  //! Empty constructor.
  Standard_EXPORT RWMesh_CafReader();

  //! Destructor.
  Standard_EXPORT virtual ~RWMesh_CafReader();

  //! Return target document.
  const Handle(TDocStd_Document)& Document() const { return myXdeDoc; }

  //! Set target document.
  //! Set system length unit according to the units of the document
  Standard_EXPORT void SetDocument(const Handle(TDocStd_Document)& theDoc);

  //! Return prefix for generating root labels names.
  const TCollection_AsciiString& RootPrefix() const { return myRootPrefix; }

  //! Set prefix for generating root labels names
  void SetRootPrefix(const TCollection_AsciiString& theRootPrefix) { myRootPrefix = theRootPrefix; }

  //! Flag indicating if partially read file content should be put into the XDE document, TRUE by
  //! default.
  //!
  //! Partial read means unexpected end of file, critical parsing syntax errors in the middle of
  //! file, or reached memory limit indicated by performMesh() returning FALSE. Partial read allows
  //! importing a model even in case of formal reading failure, so that it will be up to user to
  //! decide if processed data has any value.
  //!
  //! In case of partial read (performMesh() returns FALSE, but there are some data that could be
  //! put into document), Perform() will return TRUE and result flag will have failure bit set.
  //! @sa MemoryLimitMiB(), ExtraStatus().
  Standard_Boolean ToFillIncompleteDocument() const { return myToFillIncomplete; }

  //! Set flag allowing partially read file content to be put into the XDE document.
  void SetFillIncompleteDocument(Standard_Boolean theToFillIncomplete)
  {
    myToFillIncomplete = theToFillIncomplete;
  }

  //! Return memory usage limit in MiB, -1 by default which means no limit.
  Standard_Integer MemoryLimitMiB() const { return myMemoryLimitMiB; }

  //! Set memory usage limit in MiB; can be ignored by reader implementation!
  void SetMemoryLimitMiB(Standard_Integer theLimitMiB) { myMemoryLimitMiB = theLimitMiB; }

public:
  //! Return coordinate system converter.
  const RWMesh_CoordinateSystemConverter& CoordinateSystemConverter() const
  {
    return myCoordSysConverter;
  }

  //! Set coordinate system converter.
  void SetCoordinateSystemConverter(const RWMesh_CoordinateSystemConverter& theConverter)
  {
    myCoordSysConverter = theConverter;
  }

  //! Return the length unit to convert into while reading the file, defined as scale factor for m
  //! (meters); -1.0 by default, which means that NO conversion will be applied.
  Standard_Real SystemLengthUnit() const { return myCoordSysConverter.OutputLengthUnit(); }

  //! Set system length units to convert into while reading the file, defined as scale factor for m
  //! (meters).
  void SetSystemLengthUnit(Standard_Real theUnits)
  {
    myCoordSysConverter.SetOutputLengthUnit(theUnits);
  }

  //! Return TRUE if system coordinate system has been defined; FALSE by default.
  Standard_Boolean HasSystemCoordinateSystem() const
  {
    return myCoordSysConverter.HasOutputCoordinateSystem();
  }

  //! Return system coordinate system; UNDEFINED by default, which means that no conversion will be
  //! done.
  const gp_Ax3& SystemCoordinateSystem() const
  {
    return myCoordSysConverter.OutputCoordinateSystem();
  }

  //! Set system origin coordinate system to perform conversion into during read.
  void SetSystemCoordinateSystem(const gp_Ax3& theCS)
  {
    myCoordSysConverter.SetOutputCoordinateSystem(theCS);
  }

  //! Set system origin coordinate system to perform conversion into during read.
  void SetSystemCoordinateSystem(RWMesh_CoordinateSystem theCS)
  {
    myCoordSysConverter.SetOutputCoordinateSystem(theCS);
  }

  //! Return the length unit to convert from while reading the file, defined as scale factor for m
  //! (meters). Can be undefined (-1.0) if file format is unitless.
  Standard_Real FileLengthUnit() const { return myCoordSysConverter.InputLengthUnit(); }

  //! Set (override) file length units to convert from while reading the file, defined as scale
  //! factor for m (meters).
  void SetFileLengthUnit(Standard_Real theUnits)
  {
    myCoordSysConverter.SetInputLengthUnit(theUnits);
  }

  //! Return TRUE if file origin coordinate system has been defined.
  Standard_Boolean HasFileCoordinateSystem() const
  {
    return myCoordSysConverter.HasInputCoordinateSystem();
  }

  //! Return file origin coordinate system; can be UNDEFINED, which means no conversion will be
  //! done.
  const gp_Ax3& FileCoordinateSystem() const { return myCoordSysConverter.InputCoordinateSystem(); }

  //! Set (override) file origin coordinate system to perform conversion during read.
  void SetFileCoordinateSystem(const gp_Ax3& theCS)
  {
    myCoordSysConverter.SetInputCoordinateSystem(theCS);
  }

  //! Set (override) file origin coordinate system to perform conversion during read.
  void SetFileCoordinateSystem(RWMesh_CoordinateSystem theCS)
  {
    myCoordSysConverter.SetInputCoordinateSystem(theCS);
  }

public:
  //! Open stream and pass it to Perform method.
  //! The Document instance should be set beforehand.
  bool Perform(const TCollection_AsciiString& theFile, const Message_ProgressRange& theProgress)
  {
    std::ifstream aStream;
    OSD_OpenStream(aStream, theFile, std::ios_base::in | std::ios_base::binary);
    return Perform(aStream, theProgress, theFile);
  }

  //! Read the data from specified file.
  bool Perform(std::istream&                  theStream,
               const Message_ProgressRange&   theProgress,
               const TCollection_AsciiString& theFile = "")
  {
    return perform(theStream, theFile, theProgress, Standard_False);
  }

  //! Return extended status flags.
  //! @sa RWMesh_CafReaderStatusEx enumeration.
  Standard_Integer ExtraStatus() const { return myExtraStatus; }

public:
  //! Return result as a single shape.
  Standard_EXPORT TopoDS_Shape SingleShape() const;

  //! Return the list of complementary files - external references (textures, data, etc.).
  const NCollection_IndexedMap<TCollection_AsciiString>& ExternalFiles() const
  {
    return myExternalFiles;
  }

  //! Return metadata map.
  const TColStd_IndexedDataMapOfStringString& Metadata() const { return myMetadata; }

  //! Open stream and pass it to ProbeHeader method.
  Standard_Boolean ProbeHeader(const TCollection_AsciiString& theFile,
                               const Message_ProgressRange&   theProgress = Message_ProgressRange())
  {
    std::ifstream aStream;
    OSD_OpenStream(aStream, theFile, std::ios_base::in | std::ios_base::binary);
    return ProbeHeader(aStream, theFile, theProgress);
  }

  //! Read the header data from specified file without reading entire model.
  //! The main purpose is collecting metadata and external references - for copying model into a new
  //! location, for example. Can be NOT implemented (unsupported by format / reader).
  Standard_Boolean ProbeHeader(std::istream&                  theStream,
                               const TCollection_AsciiString& theFile     = "",
                               const Message_ProgressRange&   theProgress = Message_ProgressRange())
  {
    return perform(theStream, theFile, theProgress, Standard_True);
  }

protected:
  //! Open stream and pass it to Perform method.
  //! @param theFile    file to read
  //! @param optional   progress indicator
  //! @param theToProbe flag indicating that mesh data should be skipped and only basing information
  //! to be read
  Standard_EXPORT virtual Standard_Boolean perform(const TCollection_AsciiString& theFile,
                                                   const Message_ProgressRange&   theProgress,
                                                   const Standard_Boolean         theToProbe);

  //! Read the data from specified file.
  //! Default implementation calls performMesh() and fills XDE document from collected shapes.
  //! @param theStream  input stream
  //! @param theFile    path of additional files
  //! @param optional   progress indicator
  //! @param theToProbe flag indicating that mesh data should be skipped and only basing information
  //! to be read
  Standard_EXPORT virtual Standard_Boolean perform(std::istream&                  theStream,
                                                   const TCollection_AsciiString& theFile,
                                                   const Message_ProgressRange&   theProgress,
                                                   const Standard_Boolean         theToProbe);

  //! Read the mesh from specified file
  Standard_EXPORT virtual Standard_Boolean performMesh(const TCollection_AsciiString& theFile,
                                                       const Message_ProgressRange&   theProgress,
                                                       const Standard_Boolean         theToProbe)
  {
    std::ifstream aStream;
    OSD_OpenStream(aStream, theFile, std::ios_base::in | std::ios_base::binary);
    return performMesh(aStream, theFile, theProgress, theToProbe);
  }

  //! Read the mesh from specified file - interface to be implemented by sub-classes.
  Standard_EXPORT virtual Standard_Boolean performMesh(std::istream&                  theStream,
                                                       const TCollection_AsciiString& theFile,
                                                       const Message_ProgressRange&   theProgress,
                                                       const Standard_Boolean theToProbe) = 0;

  //! @name tools for filling XDE document
protected:
  //! Fill document with new root shapes.
  Standard_EXPORT virtual void fillDocument();

  //! Append new shape into the document (recursively).
  Standard_EXPORT Standard_Boolean addShapeIntoDoc(CafDocumentTools&              theTools,
                                                   const TopoDS_Shape&            theShape,
                                                   const TDF_Label&               theLabel,
                                                   const TCollection_AsciiString& theParentName);

  //! Append new sub-shape into the document (recursively).
  Standard_EXPORT Standard_Boolean addSubShapeIntoDoc(CafDocumentTools&   theTools,
                                                      const TopoDS_Shape& theShape,
                                                      const TDF_Label&    theParentLabel);

  //! Put name attribute onto the label.
  Standard_EXPORT void setShapeName(const TDF_Label&               theLabel,
                                    const TopAbs_ShapeEnum         theShapeType,
                                    const TCollection_AsciiString& theName,
                                    const TDF_Label&               theParentLabel,
                                    const TCollection_AsciiString& theParentName);

  //! Put color and material attributes onto the label.
  Standard_EXPORT void setShapeStyle(const CafDocumentTools& theTools,
                                     const TDF_Label&        theLabel,
                                     const XCAFPrs_Style&    theStyle);

  //! Put name data (metadata) attribute onto the label.
  Standard_EXPORT void setShapeNamedData(const CafDocumentTools&           theTools,
                                         const TDF_Label&                  theLabel,
                                         const Handle(TDataStd_NamedData)& theNameData);

  //! Generate names for root labels starting from specified index.
  Standard_EXPORT void generateNames(const TCollection_AsciiString& theFile,
                                     const Standard_Integer         theRootLower,
                                     const Standard_Boolean         theWithSubLabels);

  //! Return shape type as string.
  //! @sa TopAbs::ShapeTypeToString()
  static TCollection_AsciiString shapeTypeToString(TopAbs_ShapeEnum theType)
  {
    TCollection_AsciiString aString = TopAbs::ShapeTypeToString(theType);
    aString.Capitalize();
    return aString;
  }

protected:
  Handle(TDocStd_Document) myXdeDoc; //!< target document

  TColStd_IndexedDataMapOfStringString myMetadata; //!< metadata map
  NCollection_IndexedMap<TCollection_AsciiString>
    // clang-format off
                            myExternalFiles;     //!< the list of complementary files - external references (textures, data, etc.)
  TCollection_AsciiString   myRootPrefix;        //!< root folder for generating root labels names
  TopTools_SequenceOfShape  myRootShapes;        //!< sequence of result root shapes
  RWMesh_NodeAttributeMap   myAttribMap;         //!< map of per-shape attributes

  RWMesh_CoordinateSystemConverter
                            myCoordSysConverter; //!< coordinate system converter
  Standard_Boolean          myToFillDoc;         //!< fill document from shape sequence
  Standard_Boolean          myToFillIncomplete;  //!< fill the document with partially retrieved data even if reader has failed with error
  // clang-format on
  Standard_Integer myMemoryLimitMiB; //!< memory usage limit
  Standard_Integer myExtraStatus;    //!< extra status bitmask
};

#endif // _RWMesh_CafReader_HeaderFile
