// Copyright (c) 2022 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 _RWPly_CafWriter_HeaderFiler
#define _RWPly_CafWriter_HeaderFiler

#include <TCollection_AsciiString.hxx>
#include <NCollection_IndexedDataMap.hxx>
#include <NCollection_Map.hxx>
#include <TDF_Label.hxx>
#include <NCollection_Sequence.hxx>
#include <TopTools_ShapeMapHasher.hxx>
#include <RWMesh_CoordinateSystemConverter.hxx>
#include <XCAFPrs_Style.hxx>

#include <memory>

class Message_ProgressRange;
class RWMesh_FaceIterator;
class TDocStd_Document;

class Message_LazyProgressScope;
class RWPly_PlyWriterContext;

//! PLY writer context from XCAF document.
class RWPly_CafWriter : public Standard_Transient
{
  DEFINE_STANDARD_RTTIEXT(RWPly_CafWriter, Standard_Transient)
public:
  //! Main constructor.
  //! @param[in] theFile path to output PLY file
  Standard_EXPORT RWPly_CafWriter(const TCollection_AsciiString& theFile);

  //! Destructor.
  Standard_EXPORT ~RWPly_CafWriter() override;

  //! Return transformation from OCCT to PLY coordinate system.
  const RWMesh_CoordinateSystemConverter& CoordinateSystemConverter() const { return myCSTrsf; }

  //! Return transformation from OCCT to PLY coordinate system.
  RWMesh_CoordinateSystemConverter& ChangeCoordinateSystemConverter() { return myCSTrsf; }

  //! Set transformation from OCCT to PLY coordinate system.
  void SetCoordinateSystemConverter(const RWMesh_CoordinateSystemConverter& theConverter)
  {
    myCSTrsf = theConverter;
  }

  //! Return default material definition to be used for nodes with only color defined.
  const XCAFPrs_Style& DefaultStyle() const { return myDefaultStyle; }

  //! Set default material definition to be used for nodes with only color defined.
  void SetDefaultStyle(const XCAFPrs_Style& theStyle) { myDefaultStyle = theStyle; }

public:
  //! Return TRUE if vertex position should be stored with double floating point precision; FALSE by
  //! default.
  bool IsDoublePrecision() const { return myIsDoublePrec; }

  //! Set if vertex position should be stored with double floating point precision.
  void SetDoublePrecision(bool theDoublePrec) { myIsDoublePrec = theDoublePrec; }

  //! Return TRUE if normals should be written; TRUE by default.
  bool HasNormals() const { return myHasNormals; }

  //! Set if normals are defined.
  void SetNormals(const bool theHasNormals) { myHasNormals = theHasNormals; }

  //! Return TRUE if UV / texture coordinates should be written; FALSE by default.
  bool HasTexCoords() const { return myHasTexCoords; }

  //! Set if UV / texture coordinates should be written.
  void SetTexCoords(const bool theHasTexCoords) { myHasTexCoords = theHasTexCoords; }

  //! Return TRUE if point colors should be written; TRUE by default.
  bool HasColors() const { return myHasColors; }

  //! Set if point colors should be written.
  void SetColors(bool theToWrite) { myHasColors = theToWrite; }

  //! Return TRUE if part Id should be written as element attribute; TRUE by default.
  bool HasPartId() const { return myHasPartId; }

  //! Set if part Id should be written as element attribute; FALSE by default.
  //! Cannot be combined with HasFaceId().
  void SetPartId(bool theSurfId)
  {
    myHasPartId = theSurfId;
    myHasFaceId = myHasFaceId && !myHasPartId;
  }

  //! Return TRUE if face Id should be written as element attribute; FALSE by default.
  bool HasFaceId() const { return myHasFaceId; }

  //! Set if face Id should be written as element attribute; FALSE by default.
  //! Cannot be combined with HasPartId().
  void SetFaceId(bool theSurfId)
  {
    myHasFaceId = theSurfId;
    myHasPartId = myHasPartId && !myHasFaceId;
  }

public:
  //! Write PLY file and associated MTL material file.
  //! Triangulation data should be precomputed within shapes!
  //! @param[in] theDocument    input document
  //! @param[in] theRootLabels  list of root shapes to export
  //! @param[in] theLabelFilter optional filter with document nodes to export,
  //!                           with keys defined by XCAFPrs_DocumentExplorer::DefineChildId() and
  //!                           filled recursively (leaves and parent assembly nodes at all levels);
  //!                           when not NULL, all nodes not included into the map will be ignored
  //! @param[in] theFileInfo    map with file metadata to put into PLY header section
  //! @param[in] theProgress    optional progress indicator
  //! @return FALSE on file writing failure
  Standard_EXPORT virtual bool Perform(
    const occ::handle<TDocStd_Document>&            theDocument,
    const NCollection_Sequence<TDF_Label>&          theRootLabels,
    const NCollection_Map<TCollection_AsciiString>* theLabelFilter,
    const NCollection_IndexedDataMap<TCollection_AsciiString, TCollection_AsciiString>& theFileInfo,
    const Message_ProgressRange& theProgress);

  //! Write PLY file and associated MTL material file.
  //! Triangulation data should be precomputed within shapes!
  //! @param[in] theDocument input document
  //! @param[in] theFileInfo map with file metadata to put into PLY header section
  //! @param[in] theProgress optional progress indicator
  //! @return FALSE on file writing failure
  Standard_EXPORT virtual bool Perform(
    const occ::handle<TDocStd_Document>&                                                theDocument,
    const NCollection_IndexedDataMap<TCollection_AsciiString, TCollection_AsciiString>& theFileInfo,
    const Message_ProgressRange& theProgress);

protected:
  //! Return TRUE if face mesh should be skipped (e.g. because it is invalid or empty).
  Standard_EXPORT virtual bool toSkipFaceMesh(const RWMesh_FaceIterator& theFaceIter);

  //! Collect face triangulation info.
  //! @param[in] theFace face to process
  //! @param[in,out] theNbNodes overall number of triangulation nodes (should be appended)
  //! @param[in,out] theNbElems overall number of triangulation elements (should be appended)
  Standard_EXPORT virtual void addFaceInfo(const RWMesh_FaceIterator& theFace,
                                           int&                       theNbNodes,
                                           int&                       theNbElems);

  //! Write the shape.
  //! @param[in] theWriter      PLY writer context
  //! @param[in] thePSentry     progress sentry
  //! @param[in] theWriteStep   export step, 0 for vertex attributes, 1 for elements
  //! @param[in] theLabel       document label to process
  //! @param[in] theParentTrsf  parent node transformation
  //! @param[in] theParentStyle parent node style
  Standard_EXPORT virtual bool writeShape(RWPly_PlyWriterContext&    theWriter,
                                          Message_LazyProgressScope& thePSentry,
                                          const int                  theWriteStep,
                                          const TDF_Label&           theLabel,
                                          const TopLoc_Location&     theParentTrsf,
                                          const XCAFPrs_Style&       theParentStyle);

  //! Write face triangle vertices and attributes.
  //! @param[in] theWriter  PLY writer context
  //! @param[in] thePSentry progress sentry
  //! @param[in] theFace    current face
  //! @return FALSE on writing file error
  Standard_EXPORT virtual bool writeNodes(RWPly_PlyWriterContext&    theWriter,
                                          Message_LazyProgressScope& thePSentry,
                                          const RWMesh_FaceIterator& theFace);

  //! Write face triangles indices.
  //! @param[in] theWriter  PLY writer context
  //! @param[in] thePSentry progress sentry
  //! @param[in] theFace    current face
  //! @return FALSE on writing file error
  Standard_EXPORT virtual bool writeIndices(RWPly_PlyWriterContext&    theWriter,
                                            Message_LazyProgressScope& thePSentry,
                                            const RWMesh_FaceIterator& theFace);

protected:
  TCollection_AsciiString myFile; //!< output PLY file
                                  // clang-format off
  RWMesh_CoordinateSystemConverter myCSTrsf;       //!< transformation from OCCT to PLY coordinate system
  XCAFPrs_Style                    myDefaultStyle; //!< default material definition to be used for nodes with only color defined
                                  // clang-format on
  bool myIsDoublePrec;
  bool myHasNormals;
  bool myHasColors;
  bool myHasTexCoords;
  bool myHasPartId;
  bool myHasFaceId;
};

#endif // _RWPly_CafWriter_HeaderFiler
