// Copyright 2009-2021 Intel Corporation
// SPDX-License-Identifier: Apache-2.0

#pragma once

#include <type_traits>

#include "bvh_builder_twolevel_internal.h"
#include "bvh.h"
#include "../common/primref.h"
#include "../builders/priminfo.h"
#include "../builders/primrefgen.h"

/* new open/merge builder */
#define ENABLE_DIRECT_SAH_MERGE_BUILDER 1
#define ENABLE_OPEN_SEQUENTIAL 0
#define SPLIT_MEMORY_RESERVE_FACTOR 1000
#define SPLIT_MEMORY_RESERVE_SCALE 2
#define SPLIT_MIN_EXT_SPACE 1000

namespace embree
{
  namespace isa
  {
    template<int N, typename Mesh, typename Primitive>
    class BVHNBuilderTwoLevel : public Builder
    {
      typedef BVHN<N> BVH;
      typedef typename BVH::AABBNode AABBNode;
      typedef typename BVH::NodeRef NodeRef;

      __forceinline static bool isSmallGeometry(Mesh* mesh) {
        return mesh->size() <= 4;
      }

    public:

      typedef void (*createMeshAccelTy)(Scene* scene, unsigned int geomID, AccelData*& accel, Builder*& builder);

      struct BuildRef : public PrimRef
      {
      public:
        __forceinline BuildRef () {}

        __forceinline BuildRef (const BBox3fa& bounds, NodeRef node)
          : PrimRef(bounds,(size_t)node), node(node)
        {
          if (node.isLeaf())
            bounds_area = 0.0f;
          else
            bounds_area = area(this->bounds());
        }

        /* used by the open/merge bvh builder */
        __forceinline BuildRef (const BBox3fa& bounds, NodeRef node, const unsigned int geomID, const unsigned int numPrimitives)
          : PrimRef(bounds,geomID,numPrimitives), node(node)
        {
          /* important for relative buildref ordering */
          if (node.isLeaf())
            bounds_area = 0.0f;
          else
            bounds_area = area(this->bounds());
        }

        __forceinline size_t size() const {
          return primID();
        }

        friend bool operator< (const BuildRef& a, const BuildRef& b) {
          return a.bounds_area < b.bounds_area;
        }

        friend __forceinline embree_ostream operator<<(embree_ostream cout, const BuildRef& ref) {
          return cout << "{ lower = " << ref.lower << ", upper = " << ref.upper << ", center2 = " << ref.center2() << ", geomID = " << ref.geomID() << ", numPrimitives = " << ref.numPrimitives() << ", bounds_area = " << ref.bounds_area << " }";
        }

        __forceinline unsigned int numPrimitives() const { return primID(); }

      public:
        NodeRef node;
        float bounds_area;
      };


      __forceinline size_t openBuildRef(BuildRef &bref, BuildRef *const refs) {
        if (bref.node.isLeaf())
        {
          refs[0] = bref;
          return 1;
        }
        NodeRef ref = bref.node;
        unsigned int geomID   = bref.geomID();
        unsigned int numPrims = max((unsigned int)bref.numPrimitives() / N,(unsigned int)1);
        AABBNode* node = ref.getAABBNode();
        size_t n = 0;
        for (size_t i=0; i<N; i++) {
          if (node->child(i) == BVH::emptyNode) continue;
          refs[i] = BuildRef(node->bounds(i),node->child(i),geomID,numPrims);
          n++;
        }
        assert(n > 1);
        return n;        
      }
      
      /*! Constructor. */
      BVHNBuilderTwoLevel (BVH* bvh, Scene* scene, Geometry::GTypeMask gtype = Mesh::geom_type, bool useMortonBuilder = false, const size_t singleThreadThreshold = DEFAULT_SINGLE_THREAD_THRESHOLD);
      
      /*! Destructor */
      ~BVHNBuilderTwoLevel ();
      
      /*! builder entry point */
      void build();
      void deleteGeometry(size_t geomID);
      void clear();

      void open_sequential(const size_t extSize);
      
    private:

      class RefBuilderBase {
      public:
        virtual ~RefBuilderBase () {}
        virtual void attachBuildRefs (BVHNBuilderTwoLevel* builder) = 0;
        virtual bool meshQualityChanged (RTCBuildQuality currQuality) = 0;
      };

      class RefBuilderSmall : public RefBuilderBase {
      public:

        RefBuilderSmall (size_t objectID)
          : objectID_ (objectID) {}

        void attachBuildRefs (BVHNBuilderTwoLevel* topBuilder) {

          Mesh* mesh = topBuilder->scene->template getSafe<Mesh>(objectID_);
          size_t meshSize = mesh->size();
          assert(isSmallGeometry(mesh));
          
          mvector<PrimRef> prefs(topBuilder->scene->device, meshSize);
          auto pinfo = createPrimRefArray(mesh,objectID_,meshSize,prefs,topBuilder->bvh->scene->progressInterface);

          size_t begin=0;
          while (begin < pinfo.size())
          {
            Primitive* accel = (Primitive*) topBuilder->bvh->alloc.getCachedAllocator().malloc1(sizeof(Primitive),BVH::byteAlignment);
            typename BVH::NodeRef node = BVH::encodeLeaf((char*)accel,1);
            accel->fill(prefs.data(),begin,pinfo.size(),topBuilder->bvh->scene);
            
            /* create build primitive */
#if ENABLE_DIRECT_SAH_MERGE_BUILDER
            topBuilder->refs[topBuilder->nextRef++] = BVHNBuilderTwoLevel::BuildRef(pinfo.geomBounds,node,(unsigned int)objectID_,1);
#else
            topBuilder->refs[topBuilder->nextRef++] = BVHNBuilderTwoLevel::BuildRef(pinfo.geomBounds,node);
#endif
          }
          assert(begin == pinfo.size());
        }

        bool meshQualityChanged (RTCBuildQuality /*currQuality*/) {
          return false;
        }
        
        size_t  objectID_;
      };

      class RefBuilderLarge : public RefBuilderBase {
      public:
        
        RefBuilderLarge (size_t objectID, const Ref<Builder>& builder, RTCBuildQuality quality)
        : objectID_ (objectID), builder_ (builder), quality_ (quality) {}

        void attachBuildRefs (BVHNBuilderTwoLevel* topBuilder)
        {
          BVH* object  = topBuilder->getBVH(objectID_); assert(object);
          
          /* build object if it got modified */
          if (topBuilder->isGeometryModified(objectID_))
            builder_->build();

          /* create build primitive */
          if (!object->getBounds().empty())
          {
#if ENABLE_DIRECT_SAH_MERGE_BUILDER
            Mesh* mesh = topBuilder->getMesh(objectID_);
            topBuilder->refs[topBuilder->nextRef++] = BVHNBuilderTwoLevel::BuildRef(object->getBounds(),object->root,(unsigned int)objectID_,(unsigned int)mesh->size());
#else
            topBuilder->refs[topBuilder->nextRef++] = BVHNBuilderTwoLevel::BuildRef(object->getBounds(),object->root);
#endif
          }
        }

        bool meshQualityChanged (RTCBuildQuality currQuality) {
          return currQuality != quality_;
        }

      private:
        size_t          objectID_;
        Ref<Builder>    builder_;
        RTCBuildQuality quality_;
      };

      void setupLargeBuildRefBuilder (size_t objectID, Mesh const * const mesh);
      void setupSmallBuildRefBuilder (size_t objectID, Mesh const * const mesh);

      BVH*  getBVH (size_t objectID) {
        return this->bvh->objects[objectID];
      }
      Mesh* getMesh (size_t objectID) {
        return this->scene->template getSafe<Mesh>(objectID);
      }
      bool  isGeometryModified (size_t objectID) {
        return this->scene->isGeometryModified(objectID);
      }

      void resizeRefsList ()
      {
        size_t num = parallel_reduce (size_t(0), scene->size(), size_t(0), 
          [this](const range<size_t>& r)->size_t {
            size_t c = 0;
            for (auto i=r.begin(); i<r.end(); ++i) {
              Mesh* mesh = scene->getSafe<Mesh>(i);
              if (mesh == nullptr || mesh->numTimeSteps != 1)
                continue;
              size_t meshSize = mesh->size();
              c += isSmallGeometry(mesh) ? Primitive::blocks(meshSize) : 1;
            }
            return c;
          },
          std::plus<size_t>()
        );

        if (refs.size() < num) {
          refs.resize(num);
        }
      }

      void createMeshAccel (size_t geomID, Builder*& builder)
      {
        bvh->objects[geomID] = new BVH(Primitive::type,scene);
        BVH* accel = bvh->objects[geomID];
        auto mesh = scene->getSafe<Mesh>(geomID);
        if (nullptr == mesh) {
          throw_RTCError(RTC_ERROR_INVALID_ARGUMENT,"geomID does not return correct type");
          return;
        }

        __internal_two_level_builder__::MeshBuilder<N,Mesh,Primitive>()(accel, mesh, geomID, this->gtype, this->useMortonBuilder_, builder);
      }      

      using BuilderList = std::vector<std::unique_ptr<RefBuilderBase>>;

      BuilderList         builders;
      BVH*                bvh;
      Scene*              scene;      
      mvector<BuildRef>   refs;
      mvector<PrimRef>    prims;
      std::atomic<int>    nextRef;
      const size_t        singleThreadThreshold;
      Geometry::GTypeMask gtype;
      bool                useMortonBuilder_ = false;
    };
  }
}
