// Copyright (c) 2022 The Foundry Visionmongers Ltd. All Rights Reserved.

#include "DDImage/Knobs.h"
#include "DDImage/SourceGeomOp.h"

#include "usg/geom/MeshPrim.h"
#include "usg/geom/GeomTokens.h"

#include "ndk/geo/utils/MeshUtils.h"

#include <cassert>

using namespace DD::Image;
using namespace fdk;
using namespace usg;

namespace NukeExample {
  static const char* kTriangleClass = "GeoTriangle";
  static const char* kHelp = "Creates a 3D @i;Triangle@n;";

  static const char* kPointAKnobName = "point_a";
  static const char* kPointBKnobName = "point_b";
  static const char* kPointCKnobName = "point_c";

  class GeoTriangle : public SourceGeomOp
  {
    public:
      class Engine : public SourceEngine
      {
      public:
        Engine(Op* parent) : SourceEngine(parent) {}

        void createPrims(usg::GeomSceneContext& context, const usg::Path& path) override
        {
          if (context.doGeometryProcessing()) {
            // Create the mesh prim and the base attributes
            LayerRef defineLayer = editLayer();
            assert(defineLayer);
            ndk::MeshSample sample; // local temp storage

            const auto knobPointA = knob(kPointAKnobName); // can animate!
            const auto knobPointB = knob(kPointBKnobName); // can animate!
            const auto knobPointC = knob(kPointCKnobName); // can animate!

            // Are the XYZ knobs animating? Any other knobs that may affect
            // the triangle's point locations over time should contribute
            // to this bool. For example if a new 'size' knob is added
            // that affects the global point locations (not done via xform)
            // then that knob's isAnimated() value should be OR'd in here:
            const bool pointsAnimating = knobPointA.isAnimated()
              || knobPointB.isAnimated()
              || knobPointC.isAnimated();

            MeshPrim mesh = MeshPrim::defineInLayer(defineLayer, path);

            TransformKnobs::Binding transformKnobBinding(*this);
            transformKnobBinding.apply(mesh, context);

            bool buildTopology = context.doGeometryInitialization();
            Vec3f pointA, pointB, pointC;
            for (const TimeValue& time : context.processTimes()) {
              if (knobPointA) {
                pointA = knobPointA.get<Vec3f>(time);
              }
              if (knobPointB) {
                pointB = knobPointB.get<Vec3f>(time);
              }
              if (knobPointC) {
                pointC = knobPointC.get<Vec3f>(time);
              }

              if (buildTopology) {
                sample.faceVertexCounts.clear();
                sample.faceVertexIndices.clear();
                sample.all_tris  = true;
                sample.all_quads = false;
                sample.addTri(0, 1, 2);

                sample.facevert_uvs = {{0, 0}, {1, 0}, {0, 1}};
              }

              sample.points = {pointA, pointB, pointC};
              sample.facevert_normals.resize(1, { (pointB - pointA).cross(pointC - pointA).normalized() });

              if (buildTopology) {
                sample.setMeshPrimFaceTopology(mesh);
                buildTopology = false;
              }

              sample.setMeshPrimProperties(mesh, true/*setUvs*/, true/*setNormals*/);

              const fdk::TimeValue pointsTime = (pointsAnimating) ? time : fdk::defaultTimeValue();
              mesh.setPoints(sample.points, pointsTime);
              mesh.setBoundsAttr(sample.points, pointsTime);
            }
            assignMaterial(context, {path});
          }
        }
      };

      GeoTriangle(Node* node) :
        SourceGeomOp(node, BuildEngine<Engine>()),
        _aPos(0, 0, 0),
        _bPos(1, 0, 0),
        _cPos(0, 1, 0)
      {}

      virtual const char* Class() const override { return kTriangleClass; }

      const char* node_help() const override { return kHelp; }

      virtual void knobs(Knob_Callback f) override {
        SourceGeomOp::knobs(f); // Set up the common SourceGeo knobs.

        XYZ_knob(f, &_aPos[0], kPointAKnobName, "a");
        KnobModifiesAttribValues(f);
        XYZ_knob(f, &_bPos[0], kPointBKnobName, "b");
        KnobModifiesAttribValues(f);
        XYZ_knob(f, &_cPos[0], kPointCKnobName, "c");
        KnobModifiesAttribValues(f);

        Divider(f);
        makeTransformKnob(f);
      }

      static Op* Build(Node* node) { return new GeoTriangle(node); }
      static const Op::Description description;

    private:
      Vec3f _aPos;
      Vec3f _bPos;
      Vec3f _cPos;
  };

  const Op::Description GeoTriangle::description(kTriangleClass, GeoTriangle::Build);
}