/******************************************************************************
** Copyright(c) 2025 MAK Technologies, Inc.
** All rights reserved.
******************************************************************************/

//! \file DtCDBVectorGsPoint.h
//! \brief GS point class 
//! \ingroup osgdb_osgearth_model_cdb
#pragma once

#include <osgdb_osgearth_model_cdb/DtCDBVectorModel.h>
#include <osgdb_osgearth_model_cdb/DtCDBGeoSpecificIndirectGeo.h>
#include <osgdb_osgearth_model_cdb/DtCDBKeyToIndirectGeoMap.h>
#include <osgdb_osgearth_model_cdb/DtCDBPager.h>

#include <vrvOsg/DtModelMergerCell.h>

#include <osgearth_cdb_utils/DtCDBTile.h>

#include <vrvUtil/DtSignalConnectionManager.h>

#include <tbb/concurrent_priority_queue.h>

#include <string>

#define  DTCDBVECTOR_GS_POINT_DEBUG 0

namespace makVrv
{
   class DtTerrainPatchObject;

   namespace oe
   {
      namespace CDB
      {
         class DtGsDbfClassRow;
         class DtGsDbfInstanceRow;
         class DtCDBClassIndex;
         class DtCDBGeoSpecificIndirectGeo;

         //! \brief CDBKey GS entry map typedef
         using DtCDBKeyToGsIndirectGeoMap = DtCDBKeyToEntryTypeMapWithArgs<
            DtCDBGeoSpecificIndirectGeo, 
            bool, // uses intersections
            DtIndirectGeometryRegistrar*, // the registrar shared per tile
            std::unordered_map<std::string, DtModelInfo*>*, // model infos per tile
            unsigned int*> ; // next model ID

         //! \brief GS points vector handler class. Support all GS points( GS Trees, GS man made, etc.)
         //! \ingroup osgdb_osgearth_model_cdb
         class DtCDBVectorGsPoint : public DtCDBVectorModel
         {
         public:
            const static std::string theCDBVectorGsPointOsgEarthArenaName;

         public:

            //! \brief CTOR
            DtCDBVectorGsPoint(const DatasetInfo& CurrentDataset, osg::ref_ptr<DtCDBDatabase> cdbDb, const osgDB::Options* dbOptions, const CDBModelOptions* myCdbModelOptions, DtDe& de);

            //! \brief DTOR
            ~DtCDBVectorGsPoint();

            //! not allowed
            DtCDBVectorGsPoint() = delete;
            //! not allowed
            DtCDBVectorGsPoint(const DtCDBVectorGsPoint& rhs) = delete;
            //! not allowed
            DtCDBVectorGsPoint& operator=(const DtCDBVectorGsPoint& rhs) = delete;

         public:

            //! \brief Read the index of the needed fields and set myIsInitialized flag
            virtual void initialize(const std::string& shpInstanceFile, DtCDBInstanceIndex& instanceAttrIndexes,
               const std::string& dbfClassFile, DtCDBClassIndex& classAttrIndexes,
               const std::string& dbfExtendedFile);

            //! \brief Read instance dbf indexes
            virtual void readInstance(const std::string& dbfClassFile, DtCDBInstanceIndex& instanceAttrIndexes, const CDBTileKey& key, 
               const std::string& shpInstanceFile, VectFile* shpVector, VecInstancesDbfRow& vecInstanceRow, const MapStringToClassDbfRow& classMap ) override;

            //! \brief Read class dbf indexes
            virtual void readClass(const std::string& dbfClassFile, DtCDBClassIndex& classAttrIndexes, 
               const CDBTileKey& key,MapStringToClassDbfRow& classMap) override;

            //! \brief Process each GS vector point feature 
            virtual void processFeature(const std::string& dbfClassFile, const CDBTileKey& key, CdbGroupNode* groupNode,
               VecInstancesDbfRow& vecInstanceRow, DtCdbGroupNodeProgress* progress, bool& wasModelFound) override;

            //! \brief Finish loading the tile
            //! Runs runMergeDuplicateTextureVisitor on the group node and calls
            //! finishLoadingIndirect
            virtual void finishTileLoad(CdbGroupNode* groupNode) override;

            //! wrapper around the data needed to load node asynchronously
            struct ModelLoadInfo
            {
               std::string myModelName;
               std::string myFileName;
               osg::ref_ptr<osg::Node> myNode;
               osg::Matrixd myMatrix;
            };

            //! \brief Whether or not feature initialization should be performed with priority based on LOD
            //! This function always returns true.
            virtual bool performFeatureInitializationWithPriority() const override;
         protected:

            //! A container used in priority queues
            struct PriorityInitializedFeature
            {
               PriorityInitializedFeature()
                  : myKey( 0, 0, 0, 0, 0 )
                  , myGroupNode( nullptr )
                  , myProgress( nullptr )
                  , myDiskReadsFinished( false )
                  , myPriorityAdjustment( 0 )
                  , myPriorityAdjustedCount( 0 )
               {
                  // intentional no-op
               }

               PriorityInitializedFeature(const std::string& dbfClassFile, const CDBTileKey& key,
                  osg::ref_ptr <CdbGroupNode> groupNode, VecInstancesDbfRow& vecInstanceRow,
                  DtCdbGroupNodeProgress* progress, bool diskReadsFinished)
                  : myDbfClassFile(dbfClassFile)
                  , myKey(key)
                  , myGroupNode(groupNode)
                  , myProgress(progress)
                  , myDiskReadsFinished( diskReadsFinished )
                  , myPriorityAdjustment(0)
                  , myPriorityAdjustedCount(0)
               {
                  myVecInstanceRow.resize(vecInstanceRow.size());
                  myVecInstanceRow.swap(vecInstanceRow);

                  int lat = myKey.getLat();
                  int lon = myKey.getLon();

                  std::stringstream latStream;
                  latStream << ((lat < 0) ? "S" : "N") << std::setfill( '0' ) << std::setw( 2 ) << abs( lat );
                  myLatString = latStream.str();
                  std::stringstream lonStream;
                  lonStream << ((lon < 0) ? "W" : "E") << std::setfill( '0' ) << std::setw( 3 ) << abs( lon );
                  myLonString = lonStream.str();

                  std::stringstream lodStream;
                  int lod = myKey.getLOD();
                  if( lod < 0 )
                  {
                     lodStream << "LC" << std::setfill( '0' ) << std::setw( 2 ) << abs( lod );
                  }
                  else
                  {
                     // 0 -> 23
                     lodStream << "L" << std::setfill( '0' ) << std::setw( 2 ) << lod;
                  }
                  myLodString = lodStream.str();
               }

               bool operator<(const PriorityInitializedFeature& other) const
               {
                  return (myKey.getLOD() + myPriorityAdjustment) < (other.myKey.getLOD() + myPriorityAdjustment);
               }

               CDBTileKey myKey;
               int myPriorityAdjustment;
               int myPriorityAdjustedCount;
               std::string myLatString;
               std::string myLonString;
               std::string myLodString;
               std::string myDbfClassFile;
               osg::ref_ptr<CdbGroupNode> myGroupNode;
               osg::ref_ptr<DtCdbGroupNodeProgress> myProgress;
               VecInstancesDbfRow myVecInstanceRow;

               //! Map of files to load/that have been loaded with a valid node
               tbb::concurrent_hash_map<const DtGsDbfInstanceRow*, ModelLoadInfo> myFileMap;

               bool myDiskReadsFinished;
            };

         protected:
            //! Process the features on myPriorityInitializedFeaturesToLoadModelsFor
            //! Calls getFileMap & readModelNodesFromDisk
            virtual void processPriorityInitializedFeatures();

            //! Read node files from disk in parallel for
            virtual void readModelNodesFromDisk( PriorityInitializedFeature& feature );

            //! Process the feature on myPriorityInitializedFeaturesToAddToScene
            //! Calls processPriorityFeature & updateCdbTileKeyGroupNode
            virtual void processFeaturesToAddIntoScene();

            //! Locks the map node and calls updateCdbTileKeyGroupNode
            virtual void processPriorityFeature(PriorityInitializedFeature& feature);

            //! Calls processGeoSpecificPointFeature on each feature in the vector
            virtual void updateCdbTileKeyGroupNode(PriorityInitializedFeature& feature);

            //! Calls terminateThreads on terrain close
            virtual void slot_terrainPatchAboutToBeDeleted(DtTerrainPatchObject&);

            //! Connected to signal_cdbParallelForThreadCount
            //! Sets myParallelForThreadCount for the parallel for used for reading nodes from disk
            virtual void slot_parallelForThreadCount( int threads );

            //! Sets myTerminateWorkerThread and joins all threads, waiting for them to finish
            virtual void terminateThreads();

            //! Used for non-indirect pathway
            virtual void addPointToGroupNode( osg::Group* groupNode, osg::Node* modelNode, const osg::Matrixd& _instanceMatrix );

            //! Check if any features that have been cancelled should now be loaded
            //! Called via myPostponedFeaturesThreads
            virtual void checkPostponedFeatues( void );

            //! Check if a feature is visible based on its related paged LOD range data, and current frame number
            //! If the frame number has not been updated, it is not within the visible 
            virtual bool shouldDelayNonVisibleFeature( PriorityInitializedFeature& feature );
         protected:
            std::atomic<unsigned int> myDiskReadsThisRun;

            //! Read mappings files generated from the cdbTool
            virtual void readCachedMappingsForFeature( PriorityInitializedFeature& feature );
            virtual void readMappingsFile( const std::string& filepath, tbb::concurrent_unordered_map<std::string, std::string>* readPaths ) const;
            virtual void readInfosFile( const std::string& filepath );

            //! Tile plus LOD  e.g. N31W87L06 to map of currently loaded nodes 
            tbb::concurrent_hash_map < std::string, tbb::concurrent_unordered_map<std::string, osg::ref_ptr<osg::Node>>> myLoadedNodesForTile;

            //! Tile plus LOD e.g. N31W87L06 to mappings loaded from cache
            tbb::concurrent_hash_map < std::string, tbb::concurrent_unordered_map<std::string, std::string>*> myCachedMappingsForTile;

            //! Grouped model file paths to their infos loaded from disk
            tbb::concurrent_hash_map < std::string, DtModelMergerCell::GroupedModelInfo> myGroupedModelInfosMap;

            //! Tile e.g. N31W87 to indirect structures
            struct DtGsTileIndirectStruct
            {
               DtGsTileIndirectStruct()
                  : myGeometryRegistrar( nullptr )
                  , myMapUniqueModels()
                  , myNextModelId( 0 )
               {
               };
               DtGsTileIndirectStruct( const DtGsTileIndirectStruct& rhs )
                  : myGeometryRegistrar( rhs.myGeometryRegistrar )
                  , myMapUniqueModels( rhs.myMapUniqueModels )
                  , myNextModelId( rhs.myNextModelId )
               {
               };
               ~DtGsTileIndirectStruct()
               {
                  for( auto modelInfoIter : myMapUniqueModels )
                  {
                     delete modelInfoIter.second;
                  }
               }
               osg::ref_ptr<DtIndirectGeometryRegistrar> myGeometryRegistrar;
               std::unordered_map<std::string, DtModelInfo*> myMapUniqueModels;
               unsigned int myNextModelId;
            };
            std::unordered_map<std::string, DtGsTileIndirectStruct> myGsIndirectRegistrarsForTile;
         protected:
            DtSignalConnectionManager myConnections;

            //! \brief GS indirect rendering map
            DtCDBKeyToGsIndirectGeoMap myKeyToIndirectGeometryMap;

            tbb::concurrent_priority_queue<PriorityInitializedFeature> myPriorityInitializedFeaturesToLoadModelsFor; // features to load from disk cache or from database

            tbb::concurrent_priority_queue<PriorityInitializedFeature> myPriorityInitializedFeaturesToAddToScene;    // features to add into the scene       

            tbb::concurrent_hash_map<size_t, PriorityInitializedFeature> myPostponedFeatures;

            std::thread myLoadModelsFromDatabaseWorkerThread;  // worker thread which loads from database
            std::thread myAddModelsIntoSceneWorkerThread;      // worker thread which adds models into the scene
            std::thread myPostponedFeaturesThreads;      // ticks every 5 secs, checks if cancelled features should be readded
            std::condition_variable myAddToSceneDataConditionVar;
            std::condition_variable myProcessPriorityQueueConditionVar;
            std::atomic<bool> myTerminateWorkerThread;

            //! Default 4
            int myParallelForThreadCount;
         };
      }
   }
} // namespace makVrv::oe::CDB