#include <GL/glew.h>
#include <GLFW/glfw3.h>
#include <stdlib.h>
#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include <iostream>
#include <tuple>
#include <string>
#include <fstream>
#include <sstream>
#include <iostream>
#include <array>
#include <vector>
#include <complex>
#include <random>
#include <math.h>
#include <utility>
#include <algorithm>
#include <boost/hana/tuple.hpp>
#include <boost/hana/zip.hpp>
#include <boost/hana/for_each.hpp>
#include <boost/hana/ext/std/tuple.hpp>
#include <boost/property_tree/ptree.hpp>
#include <boost/property_tree/xml_parser.hpp>
#include "program.hpp"

const std::string g_infoPath   = "../src/config.xml";
const std::string g_vertexPath = "../src/vertex.vs";
const std::string g_fragmentPath = "../src/fragment.fs";

void GLAPIENTRY msgCallback(GLenum source, GLenum type, GLuint id, GLenum severity, GLsizei length, const GLchar* message, const void* userParam )
{
  std::cout << message << '\n';
}

struct simInfo
{
  size_t gridlen, simlen, diffuseIterations, pressureIterations;
  float density, viscosity;

  simInfo(const std::string &filename)
  {
    boost::property_tree::ptree settings;
    boost::property_tree::xml_parser::read_xml(filename, settings);
    gridlen = settings.get<unsigned int>("simInfo.gridlen");
    simlen = settings.get<unsigned int>("simInfo.gridlen"); // for now
    density = settings.get<float>("simInfo.density");
    viscosity = settings.get<float>("simInfo.viscosity");
    diffuseIterations = settings.get<unsigned int>("simInfo.diffuseIterations");
    pressureIterations = settings.get<unsigned int>("simInfo.pressureIterations");
  }
};

struct simulationSpace
{
  GLuint vao;
  GLuint vbo;

  simulationSpace(const simInfo& info)
  {
    std::vector<glm::vec4> grid;

    for(auto i = size_t{0}; i < info.gridlen; i++)
    {
      for(auto j = size_t{0}; j < info.gridlen; j++)
      {
        for(auto k = size_t{0}; k < info.gridlen; k++)
        {
          grid.emplace_back(
            info.simlen*float(k)/(info.gridlen),
            info.simlen*float(j)/(info.gridlen),
            info.simlen*float(i)/(info.gridlen),
            1.0f);
        }
      }
    }

    glGenBuffers(1, &vbo);
    glBindBuffer(GL_ARRAY_BUFFER, vbo);
    glBufferData(GL_ARRAY_BUFFER, sizeof(glm::vec4)*grid.size(), grid.data(), GL_STATIC_DRAW);

    glGenVertexArrays(1, &vao);

    glBindVertexArray(vao);
    glVertexAttribPointer(0, 4, GL_FLOAT, GL_FALSE, sizeof(glm::vec4), (void* )0);
    glEnableVertexAttribArray(0);
  }
};

struct screenQuad
{
  GLuint vao, vbo;

  screenQuad()
  {
    static auto verts = std::array<glm::vec4, 6>{
            glm::vec4(1.0f,  1.0f, 0.0f, 0.0f),
            glm::vec4(1.0f, -1.0f, 0.0f, 0.0f),
            glm::vec4(-1.0f,  1.0f, 0.0f, 1.0f),
            glm::vec4(-1.0f, -1.0f, 0.0f, 1.0f),
            glm::vec4(1.0f, -1.0f, 0.0f, 1.0f),
            glm::vec4(-1.0f, 1.0f, 0.0f, 1.0f)
    };

    glGenBuffers(1, &vbo);
    glBindBuffer(GL_ARRAY_BUFFER, vbo);
    glBufferData(GL_ARRAY_BUFFER, sizeof(glm::vec4)*6, verts.data(), GL_STATIC_DRAW);

    glGenVertexArrays(1, &vao);
    glBindVertexArray(vao);

    glVertexAttribPointer(0, 4, GL_FLOAT, GL_FALSE, sizeof(glm::vec4), (void*)0);
    glEnableVertexAttribArray(0);
  }
};

class fluidSim
{
  using jacobiType = decltype(
    GLDSEL::make_program_from_paths(
      boost::hana::make_tuple("", ""),
      glDselUniform("dim", int),
      glDselUniform("beta", float),
      glDselUniform("alpha", float),
      glDselUniform("old", int)
    )
  );

  using advectType = decltype(
    GLDSEL::make_program_from_paths(
      boost::hana::make_tuple("", ""),
      glDselUniform("dim", int),
      glDselUniform("tdelta", float),
      glDselUniform("scale", float),
      glDselUniform("old", int),
      glDselUniform("velocity", int)
    )
  );

  using forceType = decltype(
    GLDSEL::make_program_from_paths(
      boost::hana::make_tuple("", ""),
      glDselUniform("dim", int),
      glDselUniform("tdelta", float),
      glDselUniform("old", int),
      glDselUniform("fluid", int)
    )
  );

  using injectType = decltype(
    GLDSEL::make_program_from_paths(
      boost::hana::make_tuple("", ""),
      glDselUniform("dim", int),
      glDselUniform("tdelta", float),
      glDselUniform("old", int),
      glDselUniform("fluid", int)
    )
  );

  using divergenceType = decltype(
    GLDSEL::make_program_from_paths(
      boost::hana::make_tuple("", ""),
      glDselUniform("dim", int),
      glDselUniform("delta", float)
    )
  );

  using projectType = decltype(
    GLDSEL::make_program_from_paths(
      boost::hana::make_tuple("", ""),
      glDselUniform("dim", int),
      glDselUniform("delta", float)
    )
  );

  using boundaryType = decltype(
    GLDSEL::make_program_from_paths(
      boost::hana::make_tuple("", ""),
      glDselUniform("dim", int),
      glDselUniform("scale", float)
    )
  );

  using texclearType = decltype(
    GLDSEL::make_program_from_paths(
      boost::hana::make_tuple("", ""),
      glDselUniform("dim", int)
    )
  );

  simInfo info_;
  jacobiType jacobiProgram_;
  advectType advectProgram_;
  forceType forceProgram_;
  divergenceType divergenceProgram_;
  projectType projectionProgram_;
  boundaryType boundaryProgram_;
  texclearType texclearProgram_;
  injectType injectFluidProgram_;
  GLuint intermediate_;
  GLuint intermediateDiv_;
  GLuint intermediateFluid_;
  GLuint velocity_;
  GLuint pressure_;
  GLuint fluid_;
  GLuint fbuf_;
  GLuint fbuftex_;
  screenQuad quad_;

  auto cleanup()
  {
    glDeleteTextures(1, &intermediate_);
    glDeleteTextures(1, &velocity_);
    glDeleteTextures(1, &pressure_);
    glDeleteTextures(1, &fluid_);
    glDeleteFramebuffers(1, &fbuf_);
    glDeleteTextures(1, &fbuftex_);
    glDeleteTextures(1, &intermediateFluid_);
  }

  auto advect(float timeDelta, GLuint &quantity, GLuint &intermed)
  {
    glBindImageTexture(0, intermed, 0, GL_TRUE, 0, GL_WRITE_ONLY, GL_RGBA32F);
    glActiveTexture(GL_TEXTURE0);
    glBindTexture(GL_TEXTURE_3D, quantity);
    glActiveTexture(GL_TEXTURE1);
    glBindTexture(GL_TEXTURE_3D, velocity_);

    advectProgram_.setUniforms(
      glDselArgument("dim", info_.gridlen),
      glDselArgument("scale", 1.0f/float{info_.simlen}),
      glDselArgument("tdelta", timeDelta),
      glDselArgument("old", 0),
      glDselArgument("velocity", 1)
    );

    glDispatchCompute(info_.gridlen, info_.gridlen, info_.gridlen);

    std::swap(quantity, intermed);
  }

  auto injectDye(float timeDelta)
  {
    glActiveTexture(GL_TEXTURE0);
    glBindTexture(GL_TEXTURE_3D, fluid_);
    glActiveTexture(GL_TEXTURE1);
    glBindTexture(GL_TEXTURE_3D, fluid_);
    glBindImageTexture(0, intermediateFluid_, 0, GL_TRUE, 0, GL_WRITE_ONLY, GL_RGBA32F);

    injectFluidProgram_.setUniforms(
      glDselArgument("dim", info_.gridlen),
      glDselArgument("tdelta", timeDelta),
      glDselArgument("old", 0),
      glDselArgument("fluid", 1)
    );

    glDispatchCompute(info_.gridlen, info_.gridlen, info_.gridlen);

    std::swap(fluid_, intermediateFluid_); // swap handles
  }

  auto force(float timeDelta)
  {
    glActiveTexture(GL_TEXTURE0);
    glBindTexture(GL_TEXTURE_3D, velocity_);
    glActiveTexture(GL_TEXTURE1);
    glBindTexture(GL_TEXTURE_3D, fluid_);
    glBindImageTexture(0, intermediate_, 0, GL_TRUE, 0, GL_WRITE_ONLY, GL_RGBA32F);

    forceProgram_.setUniforms(
      glDselArgument("dim", info_.gridlen),
      glDselArgument("tdelta", timeDelta),
      glDselArgument("old", 0),
      glDselArgument("fluid", 1)
    );

    glDispatchCompute(info_.gridlen, info_.gridlen, info_.gridlen);

    std::swap(velocity_, intermediate_); // swap handles
  }

  auto fluidBoundary()
  {
    glActiveTexture(GL_TEXTURE0);
    glBindTexture(GL_TEXTURE_3D, fluid_);
    glBindImageTexture(0, intermediateFluid_, 0, GL_TRUE, 0, GL_WRITE_ONLY, GL_RGBA32F);

    boundaryProgram_.setUniforms(
      glDselArgument("dim", info_.gridlen),
      glDselArgument("scale", 1.0005f)
    );

    glDispatchCompute(info_.gridlen, info_.gridlen, info_.gridlen);

    std::swap(fluid_, intermediateFluid_);
  }

  auto velocityBoundary()
  {
    glActiveTexture(GL_TEXTURE0);
    glBindTexture(GL_TEXTURE_3D, velocity_);
    glBindImageTexture(0, intermediate_, 0, GL_TRUE, 0, GL_WRITE_ONLY, GL_RGBA32F);

    boundaryProgram_.setUniforms(
      glDselArgument("dim", info_.gridlen),
      glDselArgument("scale", -1.0f)
    );

    glDispatchCompute(info_.gridlen, info_.gridlen, info_.gridlen);

    std::swap(velocity_, intermediate_);
  }

  auto pressureBoundary()
  {
    boundaryProgram_.setUniforms(
      glDselArgument("dim", info_.gridlen),
      glDselArgument("scale", 1.0f)
    );

    glActiveTexture(GL_TEXTURE0);
    glBindTexture(GL_TEXTURE_3D, pressure_);
    glBindImageTexture(0, intermediate_, 0, GL_TRUE, 0, GL_WRITE_ONLY, GL_RGBA32F);

    glDispatchCompute(info_.gridlen, info_.gridlen, info_.gridlen);

    std::swap(pressure_, intermediate_);
  }

  auto projection()
  {
    velocityBoundary();

    glActiveTexture(GL_TEXTURE0);
    glBindTexture(GL_TEXTURE_3D, velocity_);
    glBindImageTexture(0, intermediate_, 0, GL_TRUE, 0, GL_WRITE_ONLY, GL_RGBA32F);

    auto halfdelta = float{0.5f*float{info_.simlen}/info_.gridlen};

    divergenceProgram_.setUniforms(
      glDselArgument("dim", info_.gridlen),
      glDselArgument("delta", halfdelta)
    );

    glDispatchCompute(info_.gridlen, info_.gridlen, info_.gridlen);

    glMemoryBarrier(GL_ALL_BARRIER_BITS);
    std::swap(intermediateDiv_, intermediate_);

    // solver converges slower if this is uncommented
    // glBindImageTexture(0, intermediate_, 0, GL_TRUE, 0, GL_WRITE_ONLY, GL_RGBA32F);
    //
    // texclearProgram_.setUniforms(
    //   glDselArgument("dim", info_.gridlen)
    // );
    //
    // glDispatchCompute(info_.gridlen, info_.gridlen, info_.gridlen);
    //
    // glMemoryBarrier(GL_ALL_BARRIER_BITS);
    // std::swap(pressure_, intermediate_); // set pressure to 0

    auto delta = float{info_.simlen}/info_.gridlen;  // gridspacing
    float alpha = -delta*delta*delta; // one unit volume
    float beta = 6.0f; // divide by 6 to average

    jacobiProgram_.setUniforms(
      glDselArgument("dim", info_.gridlen),
      glDselArgument("alpha", alpha),
      glDselArgument("beta", beta),
      glDselArgument("old", 0)
    );

    for(auto i = size_t{0}; i < info_.pressureIterations; i++) // perform jacobi iterations
    {
      pressureBoundary();

      jacobiProgram_.activate();
      glActiveTexture(GL_TEXTURE0);
      glBindTexture(GL_TEXTURE_3D, pressure_);
      glBindImageTexture(1, intermediate_, 0, GL_TRUE, 0, GL_WRITE_ONLY, GL_RGBA32F);
      glBindImageTexture(2, intermediateDiv_, 0, GL_TRUE, 0, GL_READ_ONLY, GL_RGBA32F);

      glDispatchCompute(info_.gridlen, info_.gridlen, info_.gridlen);

      glMemoryBarrier(GL_ALL_BARRIER_BITS);
      std::swap(pressure_, intermediate_); // swap handles
    }

    pressureBoundary();

    // a diffprog here is nice for tinkering around / checking the results of the poisson solver
    // glBindImageTexture(0, intermediate_, 0, GL_TRUE, 0, GL_WRITE_ONLY, GL_RGBA32F);
    // glBindImageTexture(1, intermediateDiv_, 0, GL_TRUE, 0, GL_READ_ONLY, GL_RGBA32F);
    // glActiveTexture(GL_TEXTURE0);
    // glBindTexture(GL_TEXTURE_3D, pressure_);
    //
    // diffprog.setUniforms(
    //     glDselArgument("dim", info_.gridlen),
    //     glDselArgument("delta", delta*delta)
    // );
    //
    // glDispatchCompute(info_.gridlen, info_.gridlen, info_.gridlen);
    // std::swap(intermediateDiv_, intermediate_);

    projectionProgram_.setUniforms(
        glDselArgument("dim", info_.gridlen),
        glDselArgument("delta", halfdelta)
    );

    glBindTexture(GL_TEXTURE_3D, pressure_);
    glBindImageTexture(0, intermediate_, 0, GL_TRUE, 0, GL_WRITE_ONLY, GL_RGBA32F);
    glBindImageTexture(1, velocity_, 0, GL_TRUE, 0, GL_READ_ONLY, GL_RGBA32F);

    glDispatchCompute(info_.gridlen, info_.gridlen, info_.gridlen);

    glMemoryBarrier(GL_ALL_BARRIER_BITS);
    std::swap(velocity_, intermediate_);
  }

  auto diffuse(float timeDelta)
  {
    auto delta = float{info_.simlen}/info_.gridlen;  // gridspacing
    auto alpha = float{delta*delta*delta/(info_.viscosity*timeDelta)};
    auto beta = alpha + 6.0f;

    jacobiProgram_.setUniforms(
      glDselArgument("dim", info_.gridlen),
      glDselArgument("alpha", alpha),
      glDselArgument("beta", beta),
      glDselArgument("old", 0)
    );

    jacobiProgram_.activate();
    for(auto i = size_t{0}; i < info_.diffuseIterations; i++)
    {
      glActiveTexture(GL_TEXTURE0);
      glBindTexture(GL_TEXTURE_3D, velocity_);
      glBindImageTexture(1, intermediate_, 0, GL_TRUE, 0, GL_WRITE_ONLY, GL_RGBA32F);
      glBindImageTexture(2, velocity_, 0, GL_TRUE, 0, GL_READ_ONLY, GL_RGBA32F);

      glDispatchCompute(info_.gridlen, info_.gridlen, info_.gridlen);

      std::swap(velocity_, intermediate_); // swap handles
    }
  }

public:
  fluidSim(const simInfo &info) :
   info_(info),
   jacobiProgram_(
     GLDSEL::make_program_from_paths(
       boost::hana::make_tuple(boost::none,boost::none,boost::none,boost::none,boost::none, "../src/jacobiIteration/main.cs"),
       glDselUniform("dim", int),
       glDselUniform("beta", float),
       glDselUniform("alpha", float),
       glDselUniform("old", int)
     )
   ),
   advectProgram_(
     GLDSEL::make_program_from_paths(
       boost::hana::make_tuple(boost::none,boost::none,boost::none,boost::none,boost::none, "../src/advection/main.cs"),
       glDselUniform("dim", int),
       glDselUniform("tdelta", float),
       glDselUniform("scale", float),
       glDselUniform("old", int),
       glDselUniform("velocity", int)
     )
   ),
   forceProgram_(
     GLDSEL::make_program_from_paths(
       boost::hana::make_tuple(boost::none,boost::none,boost::none,boost::none,boost::none, "../src/force/main.cs"),
       glDselUniform("dim", int),
       glDselUniform("tdelta", float),
       glDselUniform("old", int),
       glDselUniform("fluid", int)
     )
   ),
   divergenceProgram_(
     GLDSEL::make_program_from_paths(
       boost::hana::make_tuple(boost::none,boost::none,boost::none,boost::none,boost::none, "../src/divergence/main.cs"),
       glDselUniform("dim", int),
       glDselUniform("delta", float)
     )
   ),
   projectionProgram_(
     GLDSEL::make_program_from_paths(
       boost::hana::make_tuple(boost::none,boost::none,boost::none,boost::none,boost::none, "../src/projection/main.cs"),
       glDselUniform("dim", int),
       glDselUniform("delta", float)
     )
   ),
   boundaryProgram_(
     GLDSEL::make_program_from_paths(
       boost::hana::make_tuple(boost::none,boost::none,boost::none,boost::none,boost::none, "../src/boundary/main.cs"),
       glDselUniform("dim", int),
       glDselUniform("scale", float)
     )
   ),
   texclearProgram_(
     GLDSEL::make_program_from_paths(
       boost::hana::make_tuple(boost::none,boost::none,boost::none,boost::none,boost::none, "../src/texclear/main.cs"),
         glDselUniform("dim", int)
     )
   ),
   injectFluidProgram_(
     GLDSEL::make_program_from_paths(
       boost::hana::make_tuple(boost::none,boost::none,boost::none,boost::none,boost::none, "../src/injectDye/main.cs"),
       glDselUniform("dim", int),
       glDselUniform("tdelta", float),
       glDselUniform("old", int),
       glDselUniform("fluid", int)
     )
   ),
   intermediate_(0),
   intermediateDiv_(0),
   intermediateFluid_(0),
   velocity_(0),
   pressure_(0),
   fluid_(0),
   fbuf_(0),
   fbuftex_(0),
   quad_()
  {
    const auto newVolume = [this](auto&& seed, GLenum border = GL_CLAMP_TO_EDGE)
    {
      std::vector<glm::vec4> data;
      for(auto i = size_t{0}; i < info_.gridlen; i++)
      {
        for(auto j = size_t{0}; j < info_.gridlen; j++)
        {
          for(auto k = size_t{0}; k < info_.gridlen; k++)
          {
            data.emplace_back(seed(k,j,i));
          }
        }
      }

      GLuint newV;
      glGenTextures(1, &newV);
      glBindTexture(GL_TEXTURE_3D, newV);
      glTexImage3D(GL_TEXTURE_3D, 0, GL_RGBA32F, info_.gridlen, info_.gridlen, info_.gridlen, 0, GL_RGBA, GL_FLOAT, data.data());
      glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
      glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
      glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_WRAP_S, border);
      glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_WRAP_T, border);
      glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_WRAP_R, border);

      float color[] = { 0.0f, 0.0f, 0.0f, 0.0f };
      glTexParameterfv(GL_TEXTURE_3D, GL_TEXTURE_BORDER_COLOR, color);

      return newV;
    };

    // i = info.gridlen*info.gridlen*x + info.gridlen*y + z

    intermediate_ = newVolume([](const auto &...){return glm::vec4();});
    intermediateDiv_ = newVolume([](const auto &...){return glm::vec4();});
    pressure_ = newVolume([](const auto &...){return glm::vec4(0,0,0,0);});
    velocity_ = newVolume([](const auto &...){return glm::vec4(0,0,0,0);});
    fluid_ = newVolume([this](const auto x, const auto y, const auto z)
    {
      auto retval = glm::vec4(0.0);
      //if(y > info_.gridlen/2 && ((x != 0) &&( x != info_.gridlen-1) && (y != 0) && (y != info_.gridlen-1) && (y != info_.gridlen-2) && (z != 0) && (z != info_.gridlen-1)))
      //{
        //retval = retval + glm::vec4(1.0); // some at the ceiling, none beyond boundary of course
      //}
      return retval;
    });
    intermediateFluid_ = newVolume([](const auto &...){return glm::vec4();});
  }

  auto bindResources()
  {
    glActiveTexture(GL_TEXTURE0);
    glBindTexture(GL_TEXTURE_3D, fluid_);
    glActiveTexture(GL_TEXTURE1);
    glBindTexture(GL_TEXTURE_3D, velocity_);
    glActiveTexture(GL_TEXTURE2);
    glBindTexture(GL_TEXTURE_3D, intermediateDiv_);
  }

  auto step(float timeDelta)
  {
    injectDye(timeDelta);
    velocityBoundary();
    fluidBoundary();
    glMemoryBarrier(GL_ALL_BARRIER_BITS);
    advect(timeDelta, fluid_, intermediateFluid_);
    fluidBoundary();
    glMemoryBarrier(GL_ALL_BARRIER_BITS);
    advect(timeDelta, velocity_, intermediate_);
    velocityBoundary();
    fluidBoundary();
    glMemoryBarrier(GL_ALL_BARRIER_BITS);
    diffuse(timeDelta);
    glMemoryBarrier(GL_ALL_BARRIER_BITS);
    force(timeDelta);
    glMemoryBarrier(GL_ALL_BARRIER_BITS);
    projection();
    glMemoryBarrier(GL_ALL_BARRIER_BITS);
    glBindFramebuffer(GL_FRAMEBUFFER, 0);
    glMemoryBarrier(GL_ALL_BARRIER_BITS);
  }

  ~fluidSim()
  {
    cleanup();
  }
};

void update()
{

}

int main()
{
  glfwSetErrorCallback([](auto err, const auto* desc){ std::cout << "Error: " << desc << '\n'; });

  // glfw init
  if(!glfwInit())
  {
    std::cout << "glfw failed to initialize\n";
    std::exit(1);
  }

  // context init
  glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 2);
  glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 0);
  auto window = glfwCreateWindow(640, 480, "Navier Stokes", NULL, NULL);
  if (!window)
  {
    std::cout << "window/glcontext failed to initialize\n";
    std::exit(1);
  }

  glfwMakeContextCurrent(window);

  // glew init
  auto err = glewInit();
  if(GLEW_OK != err)
  {
    std::cout << "glew failed to init: " << glewGetErrorString(err) << '\n';
    std::exit(1);
  }

  // gl init
  glEnable(GL_DEBUG_OUTPUT);
  glClearColor(0.1f, 0.1f, 0.1f, 1.0f);
  glPointSize(30.0f);
  glEnable(GL_BLEND);
  glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
  glDebugMessageCallback(msgCallback, 0);
  glEnable(GL_DEPTH_TEST);

  glfwSetKeyCallback(window, [](auto window, auto key, auto scancode, auto action, auto mods){
    if (key == GLFW_KEY_ESCAPE && action == GLFW_PRESS)
      glfwSetWindowShouldClose(window, GLFW_TRUE);
    if(key == GLFW_KEY_F4)
    {
      update();
    }
  });

  // program initialization
  auto siminfo = simInfo{g_infoPath};

  fluidSim fsim{siminfo};
  simulationSpace space{siminfo};

  auto gridDraw = GLDSEL::make_program_from_paths(
      boost::hana::make_tuple("../src/main/vertex.vs", "../src/main/fragment.fs"),
      glDselUniform("time", float),
      glDselUniform("model", glm::mat4),
      glDselUniform("view", glm::mat4),
      glDselUniform("proj", glm::mat4),
      glDselUniform("positions", int),
      glDselUniform("pressure", int),
      glDselUniform("div", int)
  );

  int width, height;
  double time = glfwGetTime();
  glm::mat4 proj;
  double tdelta;
  double temp;

  glfwSwapInterval(1);
  while(!glfwWindowShouldClose(window))
  {
    auto oldT = time;
    time = glfwGetTime();
    fsim.step(1.0f/120);

    glfwGetFramebufferSize(window, &width, &height);
    glViewport(0, 0, width, height);
    proj = glm::perspective(1.57f, float(width)/float(height), 0.1f, 7000.0f);
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

    fsim.bindResources();

    //draw(proj, view, glm::mat4(), glfwGetTime(), plane);
    gridDraw.setUniforms( // set uniforms
      glDselArgument("model", glm::mat4()),
      glDselArgument("view", glm::mat4()),
      glDselArgument("proj", proj),
      glDselArgument("time", float(glfwGetTime())),
      glDselArgument("positions", 0),
      glDselArgument("pressure", 1),
      glDselArgument("div", 2)
    );

    glBindVertexArray(space.vao);

    glDrawArrays(GL_POINTS, 0, siminfo.gridlen*siminfo.gridlen*siminfo.gridlen); // draw our points

    glfwSwapBuffers(window);
    glfwPollEvents();
  }

  glfwTerminate();
  return 0;
}


#include "standard_types.hh"
#include "containers.hh"
#include <boost/lexical_cast.hpp>
#include "typelib/typename.hh"
#include <iostream>
using namespace Typelib;

static void addStandardTypes(Typelib::Registry& r)
{
    BOOST_STATIC_ASSERT((Typelib::NamespaceMark == '/'));

    r.add(new NullType("/nil"));
    r.alias("/nil", "/void");

    // Add standard sized integers
    static const int sizes[] = { 1, 2, 4, 8 };
    for (size_t i = 0; i < sizeof(sizes)/sizeof(sizes[0]); ++i)
    {
        std::string suffix = "int" + boost::lexical_cast<std::string>(sizes[i] * 8) + "_t";
        r.add(new Numeric("/"  + suffix, sizes[i], Numeric::SInt));
        r.add(new Numeric("/u" + suffix, sizes[i], Numeric::UInt));
    }

    std::string normalized_type_name = "/int" + boost::lexical_cast<std::string>(std::numeric_limits<signed char>::digits + 1) + "_t";
    r.alias(normalized_type_name, "/signed char");
    if (std::numeric_limits<char>::is_signed)
        r.alias(normalized_type_name, "/char");
    normalized_type_name = "/uint" + boost::lexical_cast<std::string>(std::numeric_limits<signed char>::digits + 1) + "_t";
    if (!std::numeric_limits<char>::is_signed)
        r.alias(normalized_type_name, "/char");
    r.alias(normalized_type_name, "/unsigned char");

    if(std::numeric_limits<wchar_t>::is_signed)
    {
        normalized_type_name = "/int" + boost::lexical_cast<std::string>(std::numeric_limits<wchar_t>::digits + 1) + "_t";
    } else {
        normalized_type_name = "/uint" + boost::lexical_cast<std::string>(std::numeric_limits<wchar_t>::digits) + "_t";
    }
    r.alias(normalized_type_name, "/wchar_t");

    normalized_type_name = "/int" + boost::lexical_cast<std::string>(std::numeric_limits<short int>::digits + 1) + "_t";
    r.alias(normalized_type_name, "/signed short int");
    r.alias(normalized_type_name, "/signed int short");
    r.alias(normalized_type_name, "/int signed short");
    r.alias(normalized_type_name, "/short signed int");
    r.alias(normalized_type_name, "/signed short int");
    r.alias(normalized_type_name, "/signed short");
    r.alias(normalized_type_name, "/short signed");
    r.alias(normalized_type_name, "/short");
    r.alias(normalized_type_name, "/short int");
    normalized_type_name = "/uint" + boost::lexical_cast<std::string>(std::numeric_limits<short int>::digits + 1) + "_t";
    r.alias(normalized_type_name, "/short unsigned int");
    r.alias(normalized_type_name, "/short int unsigned");
    r.alias(normalized_type_name, "/int short unsigned");
    r.alias(normalized_type_name, "/unsigned short int");
    r.alias(normalized_type_name, "/short unsigned");
    r.alias(normalized_type_name, "/unsigned short");

    normalized_type_name = "/int" + boost::lexical_cast<std::string>(std::numeric_limits<int>::digits + 1) + "_t";
    r.alias(normalized_type_name, "/signed int");
    r.alias(normalized_type_name, "/signed");
    r.alias(normalized_type_name, "/int signed");
    r.alias(normalized_type_name, "/int");
    normalized_type_name = "/uint" + boost::lexical_cast<std::string>(std::numeric_limits<int>::digits + 1) + "_t";
    r.alias(normalized_type_name, "/unsigned int");
    r.alias(normalized_type_name, "/unsigned");
    r.alias(normalized_type_name, "/int unsigned");

    normalized_type_name = "/int" + boost::lexical_cast<std::string>(std::numeric_limits<long>::digits + 1) + "_t";
    r.alias(normalized_type_name, "/signed long int");
    r.alias(normalized_type_name, "/signed int long");
    r.alias(normalized_type_name, "/int signed long");
    r.alias(normalized_type_name, "/long signed int");
    r.alias(normalized_type_name, "/signed long int");
    r.alias(normalized_type_name, "/signed long");
    r.alias(normalized_type_name, "/long signed");
    r.alias(normalized_type_name, "/long");
    r.alias(normalized_type_name, "/long int");
    normalized_type_name = "/uint" + boost::lexical_cast<std::string>(std::numeric_limits<long>::digits + 1) + "_t";
    r.alias(normalized_type_name, "/long unsigned int");
    r.alias(normalized_type_name, "/long int unsigned");
    r.alias(normalized_type_name, "/int long unsigned");
    r.alias(normalized_type_name, "/unsigned long int");
    r.alias(normalized_type_name, "/long unsigned");
    r.alias(normalized_type_name, "/unsigned long");

    normalized_type_name = "/int" + boost::lexical_cast<std::string>(std::numeric_limits<long long>::digits + 1) + "_t";
    r.alias(normalized_type_name, "/signed long long int");
    r.alias(normalized_type_name, "/signed long int long");
    r.alias(normalized_type_name, "/signed int long long");
    r.alias(normalized_type_name, "/int signed long long");
    r.alias(normalized_type_name, "/long signed long int");
    r.alias(normalized_type_name, "/long long signed int");
    r.alias(normalized_type_name, "/long long int signed");
    r.alias(normalized_type_name, "/signed long long");
    r.alias(normalized_type_name, "/long long signed");
    r.alias(normalized_type_name, "/long signed long");
    r.alias(normalized_type_name, "/long long int");
    r.alias(normalized_type_name, "/long int long");
    r.alias(normalized_type_name, "/int long long");
    r.alias(normalized_type_name, "/long long");
    normalized_type_name = "/uint" + boost::lexical_cast<std::string>(std::numeric_limits<long long>::digits + 1) + "_t";
    r.alias(normalized_type_name, "/unsigned long long int");
    r.alias(normalized_type_name, "/unsigned long int long");
    r.alias(normalized_type_name, "/unsigned int long long");
    r.alias(normalized_type_name, "/int unsigned long long");
    r.alias(normalized_type_name, "/long unsigned long int");
    r.alias(normalized_type_name, "/long long unsigned int");
    r.alias(normalized_type_name, "/long long int unsigned");
    r.alias(normalized_type_name, "/unsigned long long");
    r.alias(normalized_type_name, "/long long unsigned");
    r.alias(normalized_type_name, "/long unsigned long");

    BOOST_STATIC_ASSERT(( sizeof(float) == sizeof(int32_t) ));
    BOOST_STATIC_ASSERT(( sizeof(double) == sizeof(int64_t) ));
    r.add(new Numeric("/float",  4, Numeric::Float));
    r.add(new Numeric("/double", 8, Numeric::Float));

    // Finally, add definition for boolean types
    r.add(new Numeric("/bool", sizeof(bool), Numeric::UInt));
}

void Typelib::CXX::addStandardTypes(Typelib::Registry& registry)
{
    if (!registry.has("/bool"))
        ::addStandardTypes(registry);
    if (!registry.has("/std/string"))
        registry.add(new String(registry));
}


// Copyright (c) 2014-2020 The Bitcoin Core developers
// Copyright (c) Flo Developers 2013-2021
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.

#include <chainparams.h>
#include <net.h>
#include <uint256.h>
#include <validation.h>

#include <test/util/setup_common.h>

#include <boost/test/unit_test.hpp>

BOOST_FIXTURE_TEST_SUITE(validation_tests, TestingSetup)

static void TestBlockSubsidyHalvings(const Consensus::Params& consensusParams)
{
    int maxHalvings = 64;
    CAmount nInitialSubsidy = 50 * COIN;

    CAmount nPreviousSubsidy = nInitialSubsidy * 2; // for height == 0
    BOOST_CHECK_EQUAL(nPreviousSubsidy, nInitialSubsidy * 2);
    for (int nHalvings = 0; nHalvings < maxHalvings; nHalvings++) {
        int nHeight = nHalvings * consensusParams.nSubsidyHalvingInterval;
        CAmount nSubsidy = GetBlockSubsidy(nHeight, consensusParams);
        BOOST_CHECK(nSubsidy <= nInitialSubsidy);
        BOOST_CHECK_EQUAL(nSubsidy, nPreviousSubsidy / 2);
        nPreviousSubsidy = nSubsidy;
    }
    BOOST_CHECK_EQUAL(GetBlockSubsidy(maxHalvings * consensusParams.nSubsidyHalvingInterval, consensusParams), 0);
}

static void TestBlockSubsidyHalvings(int nSubsidyHalvingInterval)
{
    Consensus::Params consensusParams;
    consensusParams.nSubsidyHalvingInterval = nSubsidyHalvingInterval;
    TestBlockSubsidyHalvings(consensusParams);
}

BOOST_AUTO_TEST_CASE(block_subsidy_test)
{
    const auto chainParams = CreateChainParams(*m_node.args, CBaseChainParams::MAIN);
    TestBlockSubsidyHalvings(chainParams->GetConsensus()); // As in main
    TestBlockSubsidyHalvings(150); // As in regtest
    TestBlockSubsidyHalvings(1000); // Just another interval
}

BOOST_AUTO_TEST_CASE(subsidy_limit_test)
{
    const auto chainParams = CreateChainParams(*m_node.args, CBaseChainParams::MAIN);
    CAmount nSum = 0;
    for (int nHeight = 0; nHeight < 14000000; nHeight += 1000) {
        CAmount nSubsidy = GetBlockSubsidy(nHeight, chainParams->GetConsensus());
        BOOST_CHECK(nSubsidy <= 50 * COIN);
        nSum += nSubsidy * 1000;
        BOOST_CHECK(MoneyRange(nSum));
    }
    BOOST_CHECK_EQUAL(nSum, CAmount{2099999997690000});
}

//! Test retrieval of valid assumeutxo values.
BOOST_AUTO_TEST_CASE(test_assumeutxo)
{
    const auto params = CreateChainParams(*m_node.args, CBaseChainParams::REGTEST);

    // These heights don't have assumeutxo configurations associated, per the contents
    // of chainparams.cpp.
    std::vector<int> bad_heights{0, 100, 111, 115, 209, 211};

    for (auto empty : bad_heights) {
        const auto out = ExpectedAssumeutxo(empty, *params);
        BOOST_CHECK(!out);
    }

    const auto out110 = *ExpectedAssumeutxo(110, *params);
    BOOST_CHECK_EQUAL(out110.hash_serialized.ToString(), "1ebbf5850204c0bdb15bf030f47c7fe91d45c44c712697e4509ba67adb01c618");
    BOOST_CHECK_EQUAL(out110.nChainTx, 110U);

    const auto out210 = *ExpectedAssumeutxo(200, *params);
    BOOST_CHECK_EQUAL(out210.hash_serialized.ToString(), "51c8d11d8b5c1de51543c579736e786aa2736206d1e11e627568029ce092cf62");
    BOOST_CHECK_EQUAL(out210.nChainTx, 200U);
}

BOOST_AUTO_TEST_SUITE_END()


/*
 * MyIMUAgent.cpp
 *
 *  Created on: 23 dic 2015
 *      Author: andrea
 */

#include <agents/MyIMUAgent.h>
#include <iostream>
#include <boost/shared_ptr.hpp>
#include <boost/make_shared.hpp>
#include <boost/uuid/uuid_io.hpp>
#include <boost/log/trivial.hpp>
#include <boost/math/quaternion.hpp>
#include <events/MyIMUSample.h>
#include <events/MyBaroSample.h>
#include <syslog.h>

MyIMUAgent::MyIMUAgent(boost::shared_ptr<MyEventBus> bus,  vector<MyEvent::EventType> acceptedEventTypes) : MyAgent(bus, acceptedEventTypes), initialized(false), lastTickMicros(0), lastDTimeMicros(0) {
}

MyIMUAgent::~MyIMUAgent() {

}
bool MyIMUAgent::initialize() {
	if(imu.dmpBegin()) {
		initialized = true;
	}
	return initialized;
}
void MyIMUAgent::calcTickTimestamp() {
    uint32_t now = boost::posix_time::microsec_clock::local_time().time_of_day().total_microseconds();
    lastDTimeMicros = uint16_t(now - lastTickMicros);
    lastTickMicros = now;
}
void MyIMUAgent::processEvent(boost::shared_ptr<MyEvent> event) {
	if(!initialized) {
		initialize();
	}
	if(this->getState() == MyAgentState::Active) {
		if(event->getType() == MyEvent::EventType::Tick) {
		    this->calcTickTimestamp();
			if(imu.pulse()) {
				const MPU6050::SensorData& md = imu.getData();
//				syslog(LOG_INFO, "YPR: y(%3.2f), p(%3.2f), r(%3.2f) - Accel: x(%d), y(%d), z(%d)", md.ypr[0], md.ypr[1], md.ypr[2], md.accel.x, md.accel.y, md.accel.z);

				boost::math::quaternion<float> q(md.q.w,md.q.x,md.q.y,md.q.z);
				boost::shared_ptr<MyEvent> evOut(boost::make_shared<MyIMUSample>(this->getUuid(), q, md.ypr[0]*57.324840764f, md.ypr[1]*57.324840764f, md.ypr[2]*57.324840764f, md.gravity, md.accel, md.linearAccel));
				m_signal(evOut);
			}
			if(bmp.pulse()) {
				const BMP085::SensorData& md = bmp.getData();
				boost::shared_ptr<MyBaroSample> evOut(boost::make_shared<MyBaroSample>(this->getUuid(), md.altitude, md.estimatedAltitude, md.pressure, md.seaLevelPressure, md.temperature, md.rawPressure, md.rawTemperature, md.dtimeMillis));
				m_signal(evOut);
			}
		}
	}
}


//==============================================================================
//         Copyright 2003 - 2012   LASMEA UMR 6602 CNRS/Univ. Clermont II
//         Copyright 2009 - 2012   LRI    UMR 8623 CNRS/Univ Paris Sud XI
//
//          Distributed under the Boost Software License, Version 1.0.
//                 See accompanying file LICENSE.txt or copy at
//                     http://www.boost.org/LICENSE_1_0.txt
//==============================================================================
#ifndef NT2_LINALG_DETAILS_BLAS_R_HPP_INCLUDED
#define NT2_LINALG_DETAILS_BLAS_R_HPP_INCLUDED

#include <complex>
#include <boost/preprocessor/cat.hpp>
#include <nt2/linalg/details/blas/blas1.hpp>
#include <nt2/linalg/details/utility/f77_wrapper.hpp>

namespace nt2 { namespace details
{
#define NT2_R(T, PREFIX, SUFFIX)                                            \
inline void BOOST_PP_CAT(ger,SUFFIX)( const long int *m, const long int *n  \
                                    , const T *al                           \
                                    , const T *x, const long int *incx      \
                                    , const T *y, const long int *incy      \
                                    , const T *a, const long int *lda       \
                                    )                                       \
{                                                                           \
  NT2_F77NAME(BOOST_PP_CAT(PREFIX,ger))(ta,m,n,al,x,incx,y,incy,a,lda);     \
}                                                                           \
/**/

  // INTERNAL ONLY
  // gerx A <- {t,c}x*y+A  with x =  u, c or nothing in which case x == y
  // _GER  ( M, N, ALPHA, X, INCX, Y, INCY, A, LDA ) S, D
  // _GERU ( M, N, ALPHA, X, INCX, Y, INCY, A, LDA ) C, Z (TRANSPOSE)
  // _GERC ( M, N, ALPHA, X, INCX, Y, INCY, A, LDA ) C, Z (TRANSCONJUGATE)
  NT2_R(double, d, )
  NT2_R(float,  s, )
  NT2_R(std::complex<double>, z, u)
  NT2_R(std::complex<float>,  c, u)
  NT2_R(std::complex<double>, z, c)
  NT2_R(std::complex<float>,  c, c)

#undef NT2_R

#define NT2_R(T, P1, P2)                                                  \
inline void BOOST_PP_CAT(P2,r)( const char * uplo                         \
                              , const long int *n                         \
                              , const T *al                               \
                              , const T *x, const long int *incx          \
                              , T *a      , const long int *lda           \
                              )                                           \
{                                                                         \
  NT2_F77NAME(BOOST_PP_CAT(P1,BOOST_PP_CAT(P2,r)))(ta,n,al,x,incx,a,lda); \
}                                                                         \
/**/

  // INTERNAL ONLY
  // herx A <- hx*y+hy*x+A
  // syrx A <- tx*y+ty*x+A
  // _HER  ( UPLO, N, ALPHA, X, INCX, A, LDA ) C, Z
  // _SYR  ( UPLO, N, ALPHA, X, INCX, A, LDA ) S, D
  NT2_R(std::complex<double>, z, he)
  NT2_R(std::complex<float>,  c, he)
  NT2_R(std::complex<double>, s, sy)
  NT2_R(std::complex<float>,  d, sy)
#undef NT2_R

#define NT2_R(T, P1, P2)                                                    \
inline void BOOST_PP_CAT(P2,r)( const char * uplo                           \
                              , const long int *n                           \
                              , const T *al                                 \
                              , const T *x, const long int *incx            \
                              , T *ap                                       \
                              )                                             \
{                                                                           \
  BOOST_PP_CAT(BOOST_PP_CAT(P1,BOOST_PP_CAT(P2,r),_))(uplo,n,al,x,incx,ap); \
}                                                                           \
/**/

  // INTERNAL ONLY
  // herx A <- hx*y+hy*x+A
  // syrx A <- tx*y+ty*x+A
  // _HER  ( UPLO, N, ALPHA, X, INCX, A, LDA ) C, Z
  // _SYR  ( UPLO, N, ALPHA, X, INCX, A, LDA ) S, D
  NT2_R(std::complex<double>, z, hp)
  NT2_R(std::complex<float>,  c, hp)
  NT2_R(std::complex<double>, s, sp)
  NT2_R(std::complex<float>,  d, sp)
#undef NT2_R

#define NT2_R(T, P1, P2)                                                          \
inline void BOOST_PP_CAT(P2,r2) ( const char * uplo                               \
                                , const long int *n                               \
                                , const T *al                                     \
                                , const T *x, const long int *ix                  \
                                , const T *y, const long int *iy                  \
                                , T *a, const long int *lda                       \
                                )                                                 \
{                                                                                \
  BOOST_PP_CAT(BOOST_PP_CAT(P1,BOOST_PP_CAT(P2,r2),_)(uplo,n,al,x,ix,y,iy,a,lda); \
}                                                                                \
/**/

  // INTERNAL ONLY
  // _HER2 ( UPLO, N, ALPHA, X, INCX, Y, INCY, A, LDA )
  // _SYR2 ( UPLO, N, ALPHA, X, INCX, Y, INCY, A, LDA )
  NT2_R(std::complex<double>, z, he)
  NT2_R(std::complex<float>,  c, he)
  NT2_R(std::complex<double>, s, sy)
  NT2_R(std::complex<float>,  d, sy)
#undef NT2_R

#define NT2_R(T, P1, P2)                                                      \
inline void BOOST_PP_CAT(P2,r)( const char * uplo                             \
                              , const long int *n                             \
                              , const T *al                                   \
                              , const T *x, const long int *incx              \
                              , T *ap                                         \
                              )                                               \
{                                                                             \
  BOOST_PP_CAT(BOOST_PP_CAT(P1,BOOST_PP_CAT(P2,r2),_))(uplo,n,al,x,incx,ap);  \
}                                                                             \
/**/

  // INTERNAL ONLY
  // _HPR  ( UPLO, N, ALPHA, X, INCX, AP )
  // _SPR  ( UPLO, N, ALPHA, X, INCX, AP )
  NT2_R(std::complex<double>, z, hp)
  NT2_R(std::complex<float>,  c, hp)
  NT2_R(std::complex<double>, s, sp)
  NT2_R(std::complex<float>,  d, sp)
#undef NT2_R
} }

#endif


// mapnik-vector-tile
#include "vector_tile_geometry_clipper.hpp"
#include "vector_tile_geometry_feature.hpp"
#include "vector_tile_geometry_simplifier.hpp"
#include "vector_tile_raster_clipper.hpp"
#include "vector_tile_strategy.hpp"
#include "vector_tile_tile.hpp"
#include "vector_tile_layer.hpp"

// mapnik
#include <mapnik/geometry/box2d.hpp>
#include <mapnik/datasource.hpp>
#include <mapnik/feature.hpp>
#include <mapnik/image_scaling.hpp>
#include <mapnik/layer.hpp>
#include <mapnik/map.hpp>
#include <mapnik/version.hpp>
#include <mapnik/attribute.hpp>

#if MAPNIK_VERSION >= 300100
#include <mapnik/geometry/transform.hpp>
#else
#include <mapnik/geometry_transform.hpp>
#endif

// boost
#include <boost/optional.hpp>

// std
#include <future>

namespace mapnik
{

namespace vector_tile_impl
{

namespace detail
{

inline void create_geom_layer(tile_layer & layer,
                              double simplify_distance,
                              double area_threshold,
                              polygon_fill_type fill_type,
                              bool strictly_simple,
                              bool multi_polygon_union,
                              bool process_all_rings)
{
    layer_builder_pbf builder(layer.name(), layer.layer_extent(), layer.get_data());

    // query for the features
    mapnik::featureset_ptr features = layer.get_features();

    if (!features)
    {
        return;
    }

    mapnik::feature_ptr feature = features->next();

    if (!feature)
    {
        return;
    }

    using encoding_process = mapnik::vector_tile_impl::geometry_to_feature_pbf_visitor;
    using clipping_process = mapnik::vector_tile_impl::geometry_clipper<encoding_process>;

    mapnik::vector_tile_impl::vector_tile_strategy vs(layer.get_view_transform());
    mapnik::box2d<double> const& buffered_extent = layer.get_target_buffered_extent();
    const mapnik::geometry::point<double> p1_min(buffered_extent.minx(), buffered_extent.miny());
    const mapnik::geometry::point<double> p1_max(buffered_extent.maxx(), buffered_extent.maxy());
    const mapnik::geometry::point<std::int64_t> p2_min = mapnik::geometry::transform<std::int64_t>(p1_min, vs);
    const mapnik::geometry::point<std::int64_t> p2_max = mapnik::geometry::transform<std::int64_t>(p1_max, vs);
    const double minx = std::min(p2_min.x, p2_max.x);
    const double maxx = std::max(p2_min.x, p2_max.x);
    const double miny = std::min(p2_min.y, p2_max.y);
    const double maxy = std::max(p2_min.y, p2_max.y);
    const mapnik::box2d<int> tile_clipping_extent(minx, miny, maxx, maxy);

    if (simplify_distance > 0)
    {
        using simplifier_process = mapnik::vector_tile_impl::geometry_simplifier<clipping_process>;
        if (layer.get_proj_transform().equal())
        {
            using strategy_type = mapnik::vector_tile_impl::vector_tile_strategy;
            using transform_type = mapnik::vector_tile_impl::transform_visitor<strategy_type, simplifier_process>;
            while (feature)
            {
                mapnik::geometry::geometry<double> const& geom = feature->get_geometry();
                encoding_process encoder(*feature, builder);
                clipping_process clipper(tile_clipping_extent,
                                         area_threshold,
                                         strictly_simple,
                                         multi_polygon_union,
                                         fill_type,
                                         process_all_rings,
                                         encoder);
                simplifier_process simplifier(simplify_distance, clipper);
                transform_type transformer(vs, buffered_extent, simplifier);
                mapnik::util::apply_visitor(transformer, geom);
                feature = features->next();
            }
        }
        else
        {
            using strategy_type = mapnik::vector_tile_impl::vector_tile_strategy_proj;
            using transform_type = mapnik::vector_tile_impl::transform_visitor<strategy_type, simplifier_process>;
            strategy_type vs2(layer.get_proj_transform(), layer.get_view_transform());
            mapnik::box2d<double> const& trans_buffered_extent = layer.get_source_buffered_extent();
            while (feature)
            {
                mapnik::geometry::geometry<double> const& geom = feature->get_geometry();
                encoding_process encoder(*feature, builder);
                clipping_process clipper(tile_clipping_extent,
                                         area_threshold,
                                         strictly_simple,
                                         multi_polygon_union,
                                         fill_type,
                                         process_all_rings,
                                         encoder);
                simplifier_process simplifier(simplify_distance, clipper);
                transform_type transformer(vs2, trans_buffered_extent, simplifier);
                mapnik::util::apply_visitor(transformer, geom);
                feature = features->next();
            }
        }
    }
    else
    {
        if (layer.get_proj_transform().equal())
        {
            using strategy_type = mapnik::vector_tile_impl::vector_tile_strategy;
            using transform_type = mapnik::vector_tile_impl::transform_visitor<strategy_type, clipping_process>;
            while (feature)
            {
                mapnik::geometry::geometry<double> const& geom = feature->get_geometry();
                encoding_process encoder(*feature, builder);
                clipping_process clipper(tile_clipping_extent,
                                         area_threshold,
                                         strictly_simple,
                                         multi_polygon_union,
                                         fill_type,
                                         process_all_rings,
                                         encoder);
                transform_type transformer(vs, buffered_extent, clipper);
                mapnik::util::apply_visitor(transformer, geom);
                feature = features->next();
            }
        }
        else
        {
            using strategy_type = mapnik::vector_tile_impl::vector_tile_strategy_proj;
            using transform_type = mapnik::vector_tile_impl::transform_visitor<strategy_type, clipping_process>;
            strategy_type vs2(layer.get_proj_transform(), layer.get_view_transform());
            mapnik::box2d<double> const& trans_buffered_extent = layer.get_source_buffered_extent();
            while (feature)
            {
                mapnik::geometry::geometry<double> const& geom = feature->get_geometry();
                encoding_process encoder(*feature, builder);
                clipping_process clipper(tile_clipping_extent,
                                         area_threshold,
                                         strictly_simple,
                                         multi_polygon_union,
                                         fill_type,
                                         process_all_rings,
                                         encoder);
                transform_type transformer(vs2, trans_buffered_extent, clipper);
                mapnik::util::apply_visitor(transformer, geom);
                feature = features->next();
            }
        }
    }
    layer.build(builder);
    return;
}

inline void create_raster_layer(tile_layer & layer,
                                std::string const& image_format,
                                scaling_method_e scaling_method)
{
    layer_builder_pbf builder(layer.name(), layer.layer_extent(), layer.get_data());

    // query for the features
    mapnik::featureset_ptr features = layer.get_features();

    if (!features)
    {
        return;
    }

    mapnik::feature_ptr feature = features->next();

    if (!feature)
    {
        return;
    }

    mapnik::raster_ptr const& source = feature->get_raster();
    if (!source)
    {
        return;
    }

    mapnik::box2d<double> target_ext = box2d<double>(source->ext_);

    layer.get_proj_transform().backward(target_ext, PROJ_ENVELOPE_POINTS);
    mapnik::box2d<double> ext = layer.get_view_transform().forward(target_ext);

    int start_x = static_cast<int>(std::floor(ext.minx()+.5));
    int start_y = static_cast<int>(std::floor(ext.miny()+.5));
    int end_x = static_cast<int>(std::floor(ext.maxx()+.5));
    int end_y = static_cast<int>(std::floor(ext.maxy()+.5));
    int raster_width = end_x - start_x;
    int raster_height = end_y - start_y;
    if (raster_width > 0 && raster_height > 0)
    {
        builder.make_painted();
        raster_clipper visit(*source,
                             target_ext,
                             ext,
                             layer.get_proj_transform(),
                             image_format,
                             scaling_method,
                             layer.layer_extent(),
                             layer.layer_extent(),
                             raster_width,
                             raster_height,
                             start_x,
                             start_y);
        std::string buffer = mapnik::util::apply_visitor(visit, source->data_);
        raster_to_feature(buffer, *feature, builder);
    }
    layer.build(builder);
    return;
}

} // end ns detail

MAPNIK_VECTOR_INLINE void processor::update_tile(tile & t,
                                                 double scale_denom,
                                                 int offset_x,
                                                 int offset_y)
{
    // Futures
    std::vector<tile_layer> tile_layers;

    for (mapnik::layer const& lay : m_.layers())
    {
        if (t.has_layer(lay.name()))
        {
            continue;
        }
        tile_layers.emplace_back(m_,
                             lay,
                             t.extent(),
                             t.tile_size(),
                             t.buffer_size(),
                             scale_factor_,
                             scale_denom,
                             offset_x,
                             offset_y,
                             vars_);
        if (!tile_layers.back().is_valid())
        {
            t.add_empty_layer(lay.name());
            tile_layers.pop_back();
            continue;
        }
    }

    if (threading_mode_ == std::launch::deferred)
    {
        for (auto & layer_ref : tile_layers)
        {
            if (layer_ref.get_ds()->type() == datasource::Vector)
            {
                detail::create_geom_layer(layer_ref,
                                          simplify_distance_,
                                          area_threshold_,
                                          fill_type_,
                                          strictly_simple_,
                                          multi_polygon_union_,
                                          process_all_rings_
                                         );
            }
            else // Raster
            {
                detail::create_raster_layer(layer_ref,
                                            image_format_,
                                            scaling_method_
                                           );
            }
        }
    }
    else
    {
        std::vector<std::future<void> > future_layers;
        future_layers.reserve(tile_layers.size());

        for (auto & layer_ref : tile_layers)
        {
            if (layer_ref.get_ds()->type() == datasource::Vector)
            {
                future_layers.push_back(std::async(
                                        threading_mode_,
                                        detail::create_geom_layer,
                                        std::ref(layer_ref),
                                        simplify_distance_,
                                        area_threshold_,
                                        fill_type_,
                                        strictly_simple_,
                                        multi_polygon_union_,
                                        process_all_rings_
                            ));
            }
            else // Raster
            {
                future_layers.push_back(std::async(
                                        threading_mode_,
                                        detail::create_raster_layer,
                                        std::ref(layer_ref),
                                        image_format_,
                                        scaling_method_
                ));
            }
        }

        for (auto && lay_future : future_layers)
        {
            if (!lay_future.valid())
            {
                throw std::runtime_error("unexpected invalid async return");
            }
            lay_future.get();
        }
    }

    for (auto & layer_ref : tile_layers)
    {
        t.add_layer(layer_ref);
    }
}

} // end ns vector_tile_impl

} // end ns mapnik


// Copyright (c) 2021 fortiss GmbH
//
// Authors: Klemens Esterle and Tobias Kessler
//
// This work is licensed under the terms of the MIT license.
// For a copy, see <https://opensource.org/licenses/MIT>.

#ifndef MIQP_COMMON_PARAMETER_LIMITS_PARAMETERS_HPP_
#define MIQP_COMMON_PARAMETER_LIMITS_PARAMETERS_HPP_

#include <Eigen/Dense>
#include <unsupported/Eigen/CXX11/Tensor>

namespace miqp {
namespace common {
namespace parameter {

/**
 * @brief Structure containing state limits for all regions
 *
 */
struct LimitPerRegionParameters {
  LimitPerRegionParameters() {}
  explicit LimitPerRegionParameters(int NumCars, int nr_regions) {
    min_x.resize(NumCars, nr_regions);
    max_x.resize(NumCars, nr_regions);
    min_y.resize(NumCars, nr_regions);
    max_y.resize(NumCars, nr_regions);
  }
  Eigen::MatrixXd min_x;
  Eigen::MatrixXd max_x;
  Eigen::MatrixXd min_y;
  Eigen::MatrixXd max_y;
};

}  // namespace parameter
}  // namespace common
}  // namespace miqp

#endif  // MIQP_COMMON_PARAMETER_LIMITS_PARAMETERS_HPP_


#pragma once

#include <libdevcore/Address.h>
#include <libdevcore/FixedHash.h>
#include <libdevcrypto/Common.h>

#include <boost/asio.hpp>
#include <boost/multiprecision/cpp_int.hpp>
#include <chrono>
#include <filesystem>
#include <string>
#include <type_traits>

namespace taraxa {

namespace fs = std::filesystem;

using dev::Address;
using dev::AddressSet;
using dev::bytes;
using dev::bytesConstRef;
using dev::h256;
using dev::h256Hash;
using dev::h256s;
using dev::h64;
using dev::Secret;
using dev::u256;

using EthBlockNumber = uint64_t;

// time related
using time_point_t = std::chrono::system_clock::time_point;
using time_stamp_t = unsigned long;

using uint128_t = boost::multiprecision::uint128_t;
using uint256_t = boost::multiprecision::uint256_t;
using uint512_t = boost::multiprecision::uint512_t;
using uint1024_t = boost::multiprecision::uint1024_t;

// newtork related
using end_point_udp_t = boost::asio::ip::udp::endpoint;
using socket_udp_t = boost::asio::ip::udp::socket;
using resolver_udp_t = boost::asio::ip::udp::resolver;

/**
 * for example, use uint256_t as intermediate type,
 * uint_hash_t <------> uint256_t <------> stringstream  <------> string
 *                      number         encode/decode            operator>>
 */

template <std::size_t Bytes>
struct uint_hash_t {
  static_assert((Bytes == 16 || Bytes == 32 || Bytes == 64), "Bytes must be 16, 32 or 64\n");

  using Number = typename std::conditional<Bytes == 16, uint128_t,
                                           typename std::conditional<Bytes == 32, uint256_t, uint512_t>::type>::type;
  uint_hash_t() = default;  // Must be a trivial type for std::is_pod_v<>=true
  explicit uint_hash_t(Number const &number);
  explicit uint_hash_t(std::string const &str);
  explicit uint_hash_t(const char *cstr);
  void encodeHex(std::string &str) const;
  bool decodeHex(std::string const &str);

  Number number() const;
  bool isZero() const;
  void clear();
  void operator=(std::string const &str);
  void operator=(const char *str);
  bool operator==(uint_hash_t const &other) const;
  bool operator<(uint_hash_t const &other) const;
  bool operator>(uint_hash_t const &other) const;

  std::string toString() const;

  struct hash {
    size_t operator()(uint_hash_t<Bytes> const &value) const {
      return boost::hash_range(value.bytes.cbegin(), value.bytes.cend());
    }
  };

  // debugging
  void rawPrint() const;
  std::array<uint8_t, Bytes> bytes;
};

template <std::size_t Byte>
std::ostream &operator<<(std::ostream &strm, uint_hash_t<Byte> const &num);

using uint256_t = boost::multiprecision::uint256_t;
using uint512_t = boost::multiprecision::uint512_t;

using uint256_hash_t = dev::FixedHash<32>;
using uint512_hash_t = dev::FixedHash<64>;
using uint520_hash_t = dev::FixedHash<65>;
using uint160_hash_t = dev::FixedHash<20>;

using secret_t = dev::SecureFixedHash<32>;
using public_t = uint512_hash_t;
using addr_t = uint160_hash_t;
using sig_t = uint520_hash_t;
using sig_hash_t = uint256_hash_t;
using vote_hash_t = uint256_hash_t;
using blk_hash_t = uint256_hash_t;
using trx_hash_t = uint256_hash_t;
using sig_hash_t = uint256_hash_t;

using gas_t = uint64_t;
using key_t = std::string;
using level_t = uint64_t;
using val_t = dev::u256;
using root_t = dev::h256;
using dag_blk_num_t = uint64_t;

using vec_blk_t = std::vector<blk_hash_t>;
using vec_trx_t = std::vector<trx_hash_t>;
using trx_num_t = vec_trx_t::size_type;
using byte = uint8_t;
using bytes = std::vector<byte>;
using node_id_t = uint512_hash_t;
using trx_nonce_t = val_t;

// val_t type related helper functions
inline val_t operator+=(val_t const &val, val_t const &other) { return val + other; }
inline std::string toString(val_t const &val) {
  std::stringstream strm;
  strm << val;
  return strm.str();
}

std::ostream &operator<<(std::ostream &strm, bytes const &bytes);

time_point_t getLong2TimePoint(unsigned long l);
unsigned long getTimePoint2Long(time_point_t tp);

bytes str2bytes(std::string const &str);
std::string bytes2str(bytes const &data);

}  // namespace taraxa


#pragma once

#include "tags_object.hpp"
#include <golos/chain/comment_object.hpp>
#include <golos/chain/account_object.hpp>
#include <boost/algorithm/string.hpp>

namespace golos { namespace plugins { namespace tags {
    using golos::api::discussion_helper;

    comment_metadata get_metadata(const std::string& json, std::size_t tags_number, std::size_t tag_max_length);

    struct comment_date { time_point_sec active; time_point_sec last_update; };

    struct operation_visitor {
        operation_visitor(database& db, std::size_t tags_number, std::size_t tag_max_length);
        using result_type = void;

        database& db_;
        std::size_t tags_number_;
        std::size_t tag_max_length_;

        void remove_stats(const tag_object& tag) const;

        void add_stats(const tag_object& tag) const;

        void remove_tag(const tag_object& tag) const;

        const tag_stats_object& get_stats(const tag_object&) const;

        comment_date get_comment_last_update(const comment_object& comment) const;

        void update_tag(const tag_object&, const comment_object&, double hot, double trending) const;

        void create_tag(const std::string&, const tag_type, const comment_object&, double hot, double trending) const;

        /**
         * https://medium.com/hacking-and-gonzo/how-reddit-ranking-algorithms-work-ef111e33d0d9#.lcbj6auuw
         */
        template<int64_t S, int32_t T>
        double calculate_score(const share_type& score, const time_point_sec& created) const {
            /// new algorithm
            auto mod_score = score.value / S;

            /// reddit algorithm
            double order = log10(std::max<int64_t>(std::abs(mod_score), 1));
            int sign = 0;

            if (mod_score > 0) {
                sign = 1;
            } else if (mod_score < 0) {
                sign = -1;
            }

            return sign * order + double(created.sec_since_epoch()) / double(T);
        }

        double calculate_hot(const share_type& score, const time_point_sec& created) const;

        double calculate_trending(const share_type& score, const time_point_sec& created) const;

        /** finds tags that have been added or removed or updated */
        void create_update_tags(const account_name_type& author, const std::string& permlink) const;
        void update_tags(const account_name_type& author, const std::string& permlink) const;
        void remove_tags(const account_name_type& author, const std::string& permlink) const;

        void operator()(const comment_operation& op) const;

        void operator()(const transfer_operation& op) const;

        void operator()(const donate_operation& op) const;

        void operator()(const vote_operation& op) const;

        void operator()(const delete_comment_operation& op) const;

        void operator()(const comment_reward_operation& op) const;

        void operator()(const comment_payout_update_operation& op) const;

        template<typename Op>
        void operator()(Op&&) const {
        } /// ignore all other ops
    };

} } } // golos::plugins::tags


#include "generate_data/single_triangle/generate_data.h"

#include "generate_data/baryocentric_coords.h"
#include "generate_data/baryocentric_to_ray.h"
#include "generate_data/clip_by_plane.h"
#include "generate_data/get_points_from_subset.h"
#include "generate_data/shadowed.h"
#include "generate_data/single_triangle/amend_config.h"
#include "generate_data/single_triangle/constants.h"
#include "generate_data/single_triangle/generate_scene.h"
#include "generate_data/single_triangle/generate_scene_triangles.h"
#include "generate_data/single_triangle/normalize_scene_triangles.h"
#include "generate_data/single_triangle/region_setter.h"
#include "generate_data/to_tensor.h"
#include "generate_data/torch_utils.h"
#include "generate_data/triangle.h"
#include "generate_data/triangle_subset_intersection.h"
#include "generate_data/value_adder.h"
#include "integrate/sample_triangle.h"
#include "lib/projection.h"
#include "render/renderer.h"
#include "rng/uniform/uniform.h"

#include <ATen/ATen.h>
#include <boost/multi_array.hpp>

#include <iostream>
#include <omp.h>
#include <tuple>
#include <vector>

// TODO: consider breaking up more of this
namespace generate_data {
namespace single_triangle {
static VectorT<render::Renderer> renderers;

template <bool is_image>
using Out = std::conditional_t<is_image, ImageData<NetworkInputs>,
                               StandardData<NetworkInputs>>;

// TODO: consider fixing extra copies (if needed).
// Could really return gpu tensor and output directly to tensor.
template <bool is_image>
Out<is_image> generate_data_impl(int n_scenes, int n_samples_per_scene_or_dim,
                                 int n_samples, unsigned base_seed) {
  debug_assert(boost::geometry::is_valid(full_triangle));
  debug_assert(std::abs(boost::geometry::area(full_triangle) - 0.5) < 1e-12);

  auto vals = [&]() {
    if constexpr (is_image) {
      unsigned dim = n_samples_per_scene_or_dim;
      return baryocentric_coords(dim, dim);
    } else {
      return std::tuple{0, 0};
    }
  }();

  auto baryocentric_indexes = std::get<0>(vals);
  auto baryocentric_grid_values = std::get<1>(vals);

  int n_samples_per_scene = [&]() {
    if constexpr (is_image) {
      return baryocentric_grid_values.size();
    } else {
      return n_samples_per_scene_or_dim;
    }
  }();

  boost::multi_array<float, 2> overall_scene_features{
      std::array{n_scenes, constants.n_scene_values}};
  boost::multi_array<float, 3> triangle_features{
      std::array{n_scenes, constants.n_tris, constants.n_tri_values}};
  boost::multi_array<float, 3> baryocentric_coords{std::array{
      n_scenes, n_samples_per_scene, constants.n_coords_feature_values}};

  constexpr unsigned n_prior_dims = 1;
  std::array<TorchIdxT, n_prior_dims> prior_dims{n_scenes};

  using SinglePolyRegionSetter = RegionSetter<n_prior_dims>;

  auto get_vec = [&](unsigned n) {
    VectorT<SinglePolyRegionSetter> out;
    for (unsigned i = 0; i < n; ++i) {
      out.push_back({prior_dims});
    }
    return out;
  };

  auto clipped_setters = get_vec(constants.n_tris);
  // TODO: do we also want non intersected with clipped variations?
  auto totally_shadowed_setters = get_vec(constants.n_shadowable_tris);
  auto partially_shadowed_setters = get_vec(constants.n_shadowable_tris);
  auto centroid_shadowed_setters = get_vec(constants.n_shadowable_tris);
  VectorT<VectorT<VectorT<PartiallyShadowedInfo::RayItem>>> ray_items(
      constants.n_ray_items);
  for (auto &r : ray_items) {
    r.resize(n_scenes);
  }
  VectorT<boost::multi_array<TorchIdxT, 1>> ray_item_counts(
      constants.n_ray_items);
  for (auto &r : ray_item_counts) {
    r.resize(prior_dims);
  }

  renderers.resize(omp_get_max_threads());

  render::Settings settings;
  amend_config(settings);

  std::array values_dim{n_scenes, n_samples_per_scene, constants.n_rgb_dims};
  boost::multi_array<float, 3> values{values_dim};

  boost::multi_array<TorchIdxT, 2> image_indexes;
  if constexpr (is_image) {
    image_indexes.resize(std::array{unsigned(baryocentric_indexes.size()), 2u});
    for (int i = 0; i < int(baryocentric_indexes.size()); ++i) {
      auto [x, y] = baryocentric_indexes[i];
      image_indexes[i][0] = TorchIdxT(x);
      image_indexes[i][1] = TorchIdxT(y);
    }
  }

  // could use more than cpu cores really - goal is async...
#pragma omp parallel for schedule(dynamic, 8) if (!debug_build)
  for (int i = 0; i < n_scenes; ++i) {
    UniformState rng_state(base_seed + i);
  restart:
    auto tris = generate_scene_triangles(rng_state);
    auto new_tris = normalize_scene_triangles(tris);

    // clip by triangle_onto plane
    auto light_region =
        clip_by_plane(Eigen::Vector3d::UnitZ(), 0., new_tris.triangle_light);
    auto blocking_region_initial =
        clip_by_plane(Eigen::Vector3d::UnitZ(), 0., new_tris.triangle_blocking);

    // clip by triangle_light plane
    auto light_normal = new_tris.triangle_light.normal_raw();
    auto light_point = new_tris.triangle_light.vertices[0];
    auto onto_region =
        clip_by_plane_point(light_normal, light_point, new_tris.triangle_onto);

    auto blocking_region = triangle_subset_intersection(
        blocking_region_initial,
        clip_by_plane_point(light_normal, light_point,
                            new_tris.triangle_blocking));

    if (light_region.type() == TriangleSubsetType::None ||
        blocking_region.type() == TriangleSubsetType::None ||
        onto_region.type() == TriangleSubsetType::None) {
      goto restart;
    }

    std::array tri_pointers{
        &new_tris.triangle_onto,
        &new_tris.triangle_blocking,
        &new_tris.triangle_light,
    };
    std::array region_pointers{
        &onto_region,
        &blocking_region,
        &light_region,
    };
    std::array<Eigen::Vector3d, 3> region_centroids;
    std::array<Eigen::Vector3d, 3> centroids;
    for (int tri_idx = 0; tri_idx < int(tri_pointers.size()); ++tri_idx) {
      const auto &tri = *tri_pointers[tri_idx];
      auto from_points = get_points_from_subset(tri, *region_pointers[tri_idx]);
      region_centroids[tri_idx] = Eigen::Vector3d::Zero();
      for (const auto &p : from_points) {
        region_centroids[tri_idx] += p;
      }
      region_centroids[tri_idx] /= from_points.size();
      centroids[tri_idx] = tri.centroid();
    }

    for (unsigned onto_idx : {0, 2}) {
      std::array<const intersect::TriangleGen<double> *, 2> end_tris{
          &new_tris.triangle_light, &new_tris.triangle_onto};
      std::array<const TriangleSubset *, 2> end_region{&light_region,
                                                       &onto_region};
      unsigned feature_idx = 0;
      bool is_light = onto_idx == 2;
      if (is_light) {
        feature_idx = 1;
        std::swap(end_tris[0], end_tris[1]);
        std::swap(end_region[0], end_region[1]);
      }
      auto shadow_info = totally_shadowed(
          *end_tris[0], *end_region[0], new_tris.triangle_blocking,
          blocking_region, *end_tris[1], *end_region[1]);
      if (shadow_info.totally_shadowed.type() == TriangleSubsetType::All) {
        // assert that we aren't on the second iter (should have already
        // restarted)
        // always_assert(onto_idx == 0);
        // numerical issues cause this to occur (very rarely...)

        // totally blocked
        goto restart;
      }

      // check if clipped region is totally shadowed also
      // (if end_region[1]->type() is All, then this case isn't relavent,
      // the clipped is the whole thing...)
      if (shadow_info.totally_shadowed.type() == TriangleSubsetType::Some &&
          end_region[1]->type() == TriangleSubsetType::Some) {
        const auto &end_region_poly =
            end_region[1]->get(tag_v<TriangleSubsetType::Some>);
        const auto &intersected_poly =
            shadow_info.totally_shadowed.get(tag_v<TriangleSubsetType::Some>);
        double region_area = boost::geometry::area(end_region_poly);
        double intersected_area = boost::geometry::area(intersected_poly);
        if (std::abs(region_area - intersected_area) < 1e-10) {
          // assert that we aren't on the second iter (should have already
          // restarted)
          // always_assert(onto_idx == 0);
          // numerical issues cause this to occur (very rarely...)

          // totally blocked
          goto restart;
        }
      }

      auto partially_shadowed_info = partially_shadowed(
          *end_tris[0], *end_region[0], new_tris.triangle_blocking,
          blocking_region, *end_tris[1], *end_region[1]);
      const auto &partially_shadowed =
          partially_shadowed_info.partially_shadowed;

      if (partially_shadowed.type() == TriangleSubsetType::None) {
        // assert that we aren't on the second iter (should have already
        // restarted)
        // always_assert(onto_idx == 0);
        // numerical issues cause this to occur (very rarely...)

        // not blocked at all
        goto restart;
      }

      std::array<TorchIdxT, n_prior_dims> prior_idxs{i};

      totally_shadowed_setters[feature_idx].set_region(
          prior_idxs, shadow_info.totally_shadowed, *end_tris[1]);

      partially_shadowed_setters[feature_idx].set_region(
          prior_idxs, partially_shadowed, *end_tris[1]);
      ray_items[feature_idx][i] = partially_shadowed_info.ray_items;
      ray_item_counts[feature_idx][i] =
          partially_shadowed_info.ray_items.size();

      auto centroid_shadow = shadowed_from_point(
          region_centroids[onto_idx],
          get_points_from_subset(new_tris.triangle_blocking, blocking_region),
          *end_tris[1], *end_region[1]);
      centroid_shadowed_setters[feature_idx].set_region(
          prior_idxs, centroid_shadow, *end_tris[1]);
    }

    auto scene_adder = make_value_adder(
        [&](float v, int idx) { overall_scene_features[i][idx] = v; });

    // TODO: precompute where possible
    for (int tri_idx = 0; tri_idx < int(tri_pointers.size()); ++tri_idx) {
      const auto &tri = *tri_pointers[tri_idx];

      // add tri scene values

      auto tri_adder = make_value_adder([&](float v, int value_idx) {
        triangle_features[i][tri_idx][value_idx] = v;
      });

      for (const auto &point : tri.vertices) {
        tri_adder.add_remap_all_values(point);
      }
      const auto normal_scaled = tri.normal_scaled_by_area();
      tri_adder.add_remap_all_values(normal_scaled);
      tri_adder.add_remap_all_values(region_centroids[tri_idx]);
      const auto normal = normal_scaled.normalized().eval();
      tri_adder.add_values(normal);
      tri_adder.add_remap_all_value(tri.area());
      double region_area = clipped_setters[tri_idx].set_region(
          {i}, *region_pointers[tri_idx], tri);
      tri_adder.add_remap_all_value(region_area);
      debug_assert(tri_adder.idx == constants.n_tri_values);

      // add tri interaction general scene values
      for (int other_tri_idx = tri_idx + 1;
           other_tri_idx < int(tri_pointers.size()); ++other_tri_idx) {
        const auto &other_tri = *tri_pointers[other_tri_idx];
        const auto other_normal_scaled = other_tri.normal_scaled_by_area();
        scene_adder.add_remap_all_value(other_normal_scaled.dot(normal_scaled));
        const auto other_normal = other_normal_scaled.normalized().eval();
        const double normal_dot = other_normal.dot(normal);
        scene_adder.add_value(normal_dot);
        scene_adder.add_value(std::acos(normal_dot));

        for (const auto &centroid_arr : {centroids, region_centroids}) {
          Eigen::Vector3d centroid_vec_raw =
              centroid_arr[other_tri_idx] - centroid_arr[tri_idx];
          double centroid_dist = centroid_vec_raw.norm();
          scene_adder.add_remap_all_value(centroid_dist);
          Eigen::Vector3d centroid_vec = centroid_vec_raw.normalized();
          scene_adder.add_values(centroid_vec);
          double normal_centroid_dot = normal.dot(centroid_vec);
          scene_adder.add_value(normal_centroid_dot);
          scene_adder.add_value(std::acos(normal_centroid_dot));
          double other_normal_centroid_dot = other_normal.dot(-centroid_vec);
          scene_adder.add_value(other_normal_centroid_dot);
          scene_adder.add_value(std::acos(other_normal_centroid_dot));
        }
      }
    }

    debug_assert(scene_adder.idx == constants.n_scene_values);

    VectorT<render::InitialIdxAndDirSpec> idxs_and_dirs(n_samples_per_scene);

    auto dir_towards = -tris.triangle_onto.template cast<float>().normal();
    for (int j = 0; j < n_samples_per_scene; ++j) {
      auto [s, t] = [&]() {
        if constexpr (is_image) {
          return baryocentric_grid_values[j];
        } else {
          return integrate::uniform_baryocentric(rng_state);
        }
      }();
      idxs_and_dirs[j] = {
          .idx = 0,
          .ray = baryocentric_to_ray(
              s, t, tris.triangle_onto.template cast<float>(), dir_towards),
      };
      auto baryo_adder = make_value_adder(
          [&](float v, int idx) { baryocentric_coords[i][j][idx] = v; });

      baryo_adder.add_value(s);
      baryo_adder.add_value(t);
      const auto point = tris.triangle_onto.baryo_to_point({s, t});
      // surface area of tri is normalized, so not sure how important remap_all
      // is...
      baryo_adder.add_remap_all_values(point);
    }

    VectorT<FloatRGB> values_vec(n_samples_per_scene);

    if (!idxs_and_dirs.empty()) {
      auto scene = generate_scene(tris);
      always_assert(size_t(omp_get_thread_num()) < renderers.size());
      renderers[omp_get_thread_num()].render(
          ExecutionModel::GPU,
          {tag_v<render::SampleSpecType::InitialIdxAndDir>, idxs_and_dirs},
          {tag_v<render::OutputType::FloatRGB>, values_vec}, scene, n_samples,
          settings, false);
      for (int j = 0; j < n_samples_per_scene; ++j) {
        for (int k = 0; k < 3; ++k) {
          values[i][j][k] = values_vec[j][k];
        }
      }
    }
  }

  std::vector<PolygonInputForTri> polygon_inputs;
  for (int tri_idx = 0; tri_idx < constants.n_tris; ++tri_idx) {
    polygon_inputs.push_back(PolygonInputForTri{
        .polygon_feature = clipped_setters[tri_idx].as_poly_input(),
        .tri_idx = tri_idx});
  }
  for (int onto_idx : {0, 2}) {
    unsigned feature_idx = onto_idx == 0 ? 0 : 1;

    auto add = [&](VectorT<SinglePolyRegionSetter> &setters) {
      polygon_inputs.push_back(
          {.polygon_feature = setters[feature_idx].as_poly_input(),
           .tri_idx = onto_idx});
    };

    add(totally_shadowed_setters);
    add(partially_shadowed_setters);
    add(centroid_shadowed_setters);
  }
  debug_assert(int(polygon_inputs.size()) == constants.n_polys);

  std::vector<RayInput> ray_inputs(ray_items.size());
  for (unsigned i = 0; i < ray_items.size(); ++i) {
    auto counts = to_tensor(ray_item_counts[i]);
    TorchIdxT total = counts.sum().item().template to<TorchIdxT>();
    boost::multi_array<float, 2> values{
        std::array{total, TorchIdxT(constants.n_ray_item_values)}};
    boost::multi_array<bool, 1> is_ray{std::array{total}};
    boost::multi_array<bool, 1> is_close_point{std::array{total}};
    unsigned running_idx = 0;
    for (const auto &vec : ray_items[i]) {
      for (const auto &item : vec) {
        auto adder = make_value_adder(
            [&](float v, int idx) { values[running_idx][idx] = v; });
        adder.add_values(baryo_to_eigen(item.baryo_origin));
        adder.add_values(baryo_to_eigen(item.baryo_endpoint));
        adder.add_remap_all_values(item.origin);
        adder.add_remap_all_values(item.endpoint);
        item.result.visit_tagged([&](auto tag, const auto &v) {
          if constexpr (tag == RayItemResultType::Ray) {
            debug_assert(std::abs(v.norm() - 1.f) < 1e-12);
            adder.add_values(v);
            adder.add_value(0.);
            adder.add_remap_all_value(0.);
            adder.add_remap_all_value(0.);
            adder.add_remap_all_value(0.);
          } else if constexpr (tag == RayItemResultType::Intersection) {
            adder.add_values(v.normalized().eval());
            adder.add_value(std::atan2(v.y(), v.x()));
            // values can get VERY large
            const double norm = v.norm();
            adder.add_remap_all_value(norm);
            adder.add_remap_all_value(v.x());
            adder.add_remap_all_value(v.y());
          } else {
            static_assert(tag == RayItemResultType::ClosePoint);
            adder.add_values(Eigen::Vector2d::Zero());
            adder.add_value(0.);
            adder.add_remap_all_value(0.);
            adder.add_remap_all_value(0.);
            adder.add_remap_all_value(0.);
          }
        });
        is_ray[running_idx] = item.result.type() == RayItemResultType::Ray;
        is_close_point[running_idx] =
            item.result.type() == RayItemResultType::ClosePoint;
        debug_assert(adder.idx == constants.n_ray_item_values);
        ++running_idx;
      }
    }
    always_assert(running_idx == total);

    TorchIdxT min_count = counts.min().item().template to<TorchIdxT>();
    always_assert(min_count > 0);

    ray_inputs[i] = {
        .values = to_tensor(values),
        .counts = counts,
        .prefix_sum_counts =
            at::cat({at::tensor({TorchIdxT(0)}), counts.flatten().cumsum(0)}),
        .is_ray = to_tensor(is_ray),
        .is_close_point = to_tensor(is_close_point),
    };
  }

  StandardData<NetworkInputs> out{
      .inputs =
          {
              .overall_scene_features = to_tensor(overall_scene_features),
              .triangle_features = to_tensor(triangle_features),
              .polygon_inputs = polygon_inputs,
              .ray_inputs = ray_inputs,
              .baryocentric_coords = to_tensor(baryocentric_coords),
          },
      .values = to_tensor(values),

  };

  if constexpr (is_image) {
    return {.standard = out, .image_indexes = to_tensor(image_indexes)};
  } else {
    return out;
  }
}

StandardData<NetworkInputs> generate_data(int n_scenes, int n_samples_per_scene,
                                          int n_samples, unsigned base_seed) {
  return generate_data_impl<false>(n_scenes, n_samples_per_scene, n_samples,
                                   base_seed);
}

ImageData<NetworkInputs> generate_data_for_image(int n_scenes, int dim,
                                                 int n_samples,
                                                 unsigned base_seed) {
  return generate_data_impl<true>(n_scenes, dim, n_samples, base_seed);
}

void deinit_renderers() { renderers.clear(); }
} // namespace single_triangle
} // namespace generate_data


// Copyright (c) 2014 The Bitcoin Core developers
// Copyright (c) 2014-2015 The Dash developers
// Copyright (c) 2015-2017 The PIVX developers
// Copyright (c) 2015-2017 The MRI developers
// Distributed under the MIT/X11 software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.

#include "primitives/transaction.h"
#include "main.h"

#include <boost/test/unit_test.hpp>

BOOST_AUTO_TEST_SUITE(main_tests)

CAmount nMoneySupplyPoWEnd = 57879300 * COIN;

BOOST_AUTO_TEST_CASE(subsidy_limit_test)
{
    CAmount nSum = 0;
    for (int nHeight = 0; nHeight < 1; nHeight += 1) {
        /* premine in block 1 */
        CAmount nSubsidy = GetBlockValue(nHeight);
        BOOST_CHECK(nSubsidy <= 100001 * COIN);
        nSum += nSubsidy;
    }

    for (int nHeight = 1; nHeight < 86400; nHeight += 1) {
        CAmount nSubsidy = GetBlockValue(nHeight);
        BOOST_CHECK(nSubsidy <= 200 * COIN);
        nSum += nSubsidy;
    }
    for (int nHeight = 86400; nHeight < 151200; nHeight += 1) {
        CAmount nSubsidy = GetBlockValue(nHeight);
        BOOST_CHECK(nSubsidy <= 150 * COIN);
        nSum += nSubsidy;
    }
    for (int nHeight = 151200; nHeight < 302400; nHeight += 1) {
        CAmount nSubsidy = GetBlockValue(nHeight);
        BOOST_CHECK(nSubsidy <= 125 * COIN);
        nSum += nSubsidy;
    }
    for (int nHeight = 302400; nHeight < 345600; nHeight += 1) {
        CAmount nSubsidy = GetBlockValue(nHeight);
        BOOST_CHECK(nSubsidy <= 100 * COIN);
        nSum += nSubsidy;
    }
    for (int nHeight = 345600; nHeight < 388800; nHeight += 1) {
        CAmount nSubsidy = GetBlockValue(nHeight);
        BOOST_CHECK(nSubsidy <= 75 * COIN);
        nSum += nSubsidy;
    }
    for (int nHeight = 388800; nHeight < 475200; nHeight += 1) {
        CAmount nSubsidy = GetBlockValue(nHeight);
        BOOST_CHECK(nSubsidy <= 50 * COIN);
        nSum += nSubsidy;
    }

	BOOST_CHECK(nSum > 0 && nSum <= nMoneySupplyPoWEnd);
}

BOOST_AUTO_TEST_SUITE_END()


/*
 Copyright (C) 2019 Quaternion Risk Management Ltd

 All rights reserved.

 This file is part of ORE, a free-software/open-source library
 for transparent pricing and risk analysis - http://opensourcerisk.org

 ORE is free software: you can redistribute it and/or modify it
 under the terms of the Modified BSD License.  You should have received a
 copy of the license along with this program.
 The license is also available online at <http://opensourcerisk.org>

 This program is distributed on the basis that it will form a useful
 contribution to risk analytics and model standardisation, but WITHOUT
 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 FITNESS FOR A PARTICULAR PURPOSE. See the license for more details.
*/

#include <ored/portfolio/builders/bond.hpp>
#include <ored/portfolio/builders/forwardbond.hpp>
#include <ored/portfolio/fixingdates.hpp>
#include <ored/portfolio/forwardbond.hpp>
#include <ored/portfolio/legdata.hpp>
#include <ored/portfolio/swap.hpp>
#include <ored/utilities/indexparser.hpp>
#include <ored/utilities/log.hpp>
#include <ored/utilities/parsers.hpp>

#include <qle/instruments/forwardbond.hpp>

#include <ql/cashflows/simplecashflow.hpp>
#include <ql/instruments/bond.hpp>
#include <ql/instruments/bonds/zerocouponbond.hpp>
#include <ql/quotes/simplequote.hpp>
#include <ql/time/calendars/weekendsonly.hpp>
#include <ql/time/daycounters/actual360.hpp>

#include <boost/lexical_cast.hpp>
#include <boost/make_shared.hpp>

using namespace QuantLib;
using namespace QuantExt;

namespace ore {
namespace data {

void ForwardBond::build(const boost::shared_ptr<EngineFactory>& engineFactory) {
    DLOG("ForwardBond::build() called for trade " << id());

    const boost::shared_ptr<Market> market = engineFactory->market();

    boost::shared_ptr<EngineBuilder> builder_fwd = engineFactory->builder("ForwardBond");
    boost::shared_ptr<EngineBuilder> builder_bd = engineFactory->builder("Bond");

    bondData_.populateFromBondReferenceData(engineFactory->referenceData());

    QL_REQUIRE(!bondData_.referenceCurveId().empty(), "reference curve id required");
    QL_REQUIRE(!bondData_.settlementDays().empty(), "settlement days required");

    Date issueDate = parseDate(bondData_.issueDate());
    Calendar calendar = parseCalendar(bondData_.calendar());
    Natural settlementDays = boost::lexical_cast<Natural>(bondData_.settlementDays());

    Date fwdMaturityDate = parseDate(fwdMaturityDate_);
    Date fwdSettlementDate = fwdSettlementDate_.empty() ? fwdMaturityDate : parseDate(fwdSettlementDate_);
    bool isPhysicallySettled;
    if (settlement_ == "Physical" || settlement_.empty())
        isPhysicallySettled = true;
    else if (settlement_ == "Cash")
        isPhysicallySettled = false;
    else {
        QL_FAIL("ForwardBond: invalid settlement '" << settlement_ << "', expected Cash or Physical");
    }
    Real amount = amount_.empty() ? Null<Real>() : parseReal(amount_);
    Real lockRate = lockRate_.empty() ? Null<Real>() : parseReal(lockRate_);
    DayCounter lockRateDayCounter = lockRateDayCounter_.empty() ? Actual360() : parseDayCounter(lockRateDayCounter_);
    bool settlementDirty = settlementDirty_.empty() ? true : parseBool(settlementDirty_);
    Real compensationPayment = parseReal(compensationPayment_);
    Date compensationPaymentDate = parseDate(compensationPaymentDate_);
    bool longInForward = parseBool(longInForward_);

    QL_REQUIRE((amount == Null<Real>() && lockRate != Null<Real>()) ||
                   (amount != Null<Real>() && lockRate == Null<Real>()),
               "ForwardBond: exactly one of Amount of LockRate must be given");
    QL_REQUIRE(compensationPaymentDate <= fwdMaturityDate, "Premium cannot be paid after forward contract maturity");

    if (lockRate != Null<Real>())
        isPhysicallySettled = false;

    QL_REQUIRE(!bondData_.coupons().empty(), "ForwardBond: No LegData given. If you want to represent a zero bond, "
                                             "set it up as a coupon bond with zero fixed rate");

    bool firstLegIsPayer = bondData_.coupons()[0].isPayer();
    QL_REQUIRE(firstLegIsPayer == false, "ForwardBond: The underlying bond must be entered with a receiver leg. Use "
                                         "LongInBond to specify pay direction of forward payoff");
    QL_REQUIRE(compensationPayment > 0.0 || close_enough(compensationPayment, 0.0),
               "ForwardBond: Negative compensation payments ("
                   << compensationPayment
                   << ") are not allowed. Notice that we will ensure that a positive compensation amount will be paid "
                      "by the party being long in the forward contract.");

    boost::shared_ptr<Payoff> payoff;
    if (amount != Null<Real>()) {
        payoff = longInForward ? boost::make_shared<QuantExt::ForwardBondTypePayoff>(Position::Long, amount)
                               : boost::make_shared<QuantExt::ForwardBondTypePayoff>(Position::Short, amount);
    }
    compensationPayment = longInForward ? compensationPayment : -compensationPayment;

    std::vector<Leg> separateLegs;
    for (Size i = 0; i < bondData_.coupons().size(); ++i) {
        Leg leg;
        auto configuration = builder_bd->configuration(MarketContext::pricing);
        auto legBuilder = engineFactory->legBuilder(bondData_.coupons()[i].legType());
        leg = legBuilder->buildLeg(bondData_.coupons()[i], engineFactory, requiredFixings_, configuration);
        separateLegs.push_back(leg);
    }
    Leg leg = joinLegs(separateLegs);
    auto bond = boost::make_shared<QuantLib::Bond>(settlementDays, calendar, issueDate, leg);

    npvCurrency_ = currency_ = bondData_.coupons().front().currency();
    maturity_ = bond->cashflows().back()->date();
    notional_ = currentNotional(bond->cashflows()) * bondData_.bondNotional();
    notionalCurrency_ = currency_;

    // cashflows will be generated as additional results in the pricing engine
    legs_ = {};
    legCurrencies_ = {npvCurrency_};
    legPayers_ = {firstLegIsPayer};
    Currency currency = parseCurrency(currency_);

    // first ctor is for vanilla fwd bonds, second for tlocks with a lock rate specifying the payoff
    boost::shared_ptr<QuantLib::Instrument> fwdBond =
        payoff ? boost::make_shared<QuantExt::ForwardBond>(bond, payoff, fwdMaturityDate, fwdSettlementDate,
                                                           isPhysicallySettled, settlementDirty, compensationPayment,
                                                           compensationPaymentDate, bondData_.bondNotional())
               : boost::make_shared<QuantExt::ForwardBond>(bond, lockRate, lockRateDayCounter, longInForward,
                                                           fwdMaturityDate, fwdSettlementDate, isPhysicallySettled,
                                                           settlementDirty, compensationPayment,
                                                           compensationPaymentDate, bondData_.bondNotional());

    boost::shared_ptr<fwdBondEngineBuilder> fwdBondBuilder =
        boost::dynamic_pointer_cast<fwdBondEngineBuilder>(builder_fwd);
    QL_REQUIRE(fwdBondBuilder, "ForwardBond::build(): could not cast builder: " << id());

    fwdBond->setPricingEngine(fwdBondBuilder->engine(id(), currency, bondData_.creditCurveId(), bondData_.securityId(),
                                                     bondData_.referenceCurveId(), bondData_.incomeCurveId()));
    instrument_.reset(new VanillaInstrument(fwdBond, 1.0));

    additionalData_["currentNotional"] = currentNotional(bond->cashflows()) * bondData_.bondNotional();
    additionalData_["originalNotional"] = originalNotional(bond->cashflows()) * bondData_.bondNotional();
    additionalData_["currency"] = currency_;
}

void ForwardBond::fromXML(XMLNode* node) {
    Trade::fromXML(node);
    XMLNode* fwdBondNode = XMLUtils::getChildNode(node, "ForwardBondData");
    QL_REQUIRE(fwdBondNode, "No ForwardBondData Node");
    bondData_.fromXML(XMLUtils::getChildNode(fwdBondNode, "BondData"));

    XMLNode* fwdSettlementNode = XMLUtils::getChildNode(fwdBondNode, "SettlementData");
    QL_REQUIRE(fwdSettlementNode, "No fwdSettlementNode Node");

    fwdMaturityDate_ = XMLUtils::getChildValue(fwdSettlementNode, "ForwardMaturityDate", true);
    fwdSettlementDate_ = XMLUtils::getChildValue(fwdSettlementNode, "ForwardSettlementDate", false);
    settlement_ = XMLUtils::getChildValue(fwdSettlementNode, "Settlement", false);
    amount_ = XMLUtils::getChildValue(fwdSettlementNode, "Amount", false);
    lockRate_ = XMLUtils::getChildValue(fwdSettlementNode, "LockRate", false);
    lockRateDayCounter_ = XMLUtils::getChildValue(fwdSettlementNode, "LockRateDayCounter", false);
    settlementDirty_ = XMLUtils::getChildValue(fwdSettlementNode, "SettlementDirty", false);

    XMLNode* fwdPremiumNode = XMLUtils::getChildNode(fwdBondNode, "PremiumData");
    if (fwdPremiumNode) {
        compensationPayment_ = XMLUtils::getChildValue(fwdPremiumNode, "Amount", true);
        compensationPaymentDate_ = XMLUtils::getChildValue(fwdPremiumNode, "Date", true);
    } else {
        compensationPayment_ = "0.0";
        compensationPaymentDate_ = fwdMaturityDate_;
    }

    longInForward_ = XMLUtils::getChildValue(fwdBondNode, "LongInForward", true);
}

XMLNode* ForwardBond::toXML(XMLDocument& doc) {
    XMLNode* node = Trade::toXML(doc);
    XMLNode* fwdBondNode = doc.allocNode("ForwardBondData");
    XMLUtils::appendNode(node, fwdBondNode);
    XMLUtils::appendNode(fwdBondNode, bondData_.toXML(doc));

    XMLNode* fwdSettlementNode = doc.allocNode("SettlementData");
    XMLUtils::appendNode(fwdBondNode, fwdSettlementNode);
    XMLUtils::addChild(doc, fwdSettlementNode, "ForwardMaturityDate", fwdMaturityDate_);
    if (!fwdSettlementDate_.empty())
        XMLUtils::addChild(doc, fwdSettlementNode, "ForwardSettlementDate", fwdSettlementDate_);
    if (!settlement_.empty())
        XMLUtils::addChild(doc, fwdSettlementNode, "Settlement", settlement_);
    if (!amount_.empty())
        XMLUtils::addChild(doc, fwdSettlementNode, "Amount", amount_);
    if (!lockRate_.empty())
        XMLUtils::addChild(doc, fwdSettlementNode, "LockRate", lockRate_);
    if (!lockRateDayCounter_.empty())
        XMLUtils::addChild(doc, fwdSettlementNode, "LockRateDayCounter", lockRateDayCounter_);
    if (!settlementDirty_.empty())
        XMLUtils::addChild(doc, fwdSettlementNode, "SettlementDirty", settlementDirty_);

    XMLNode* fwdPremiumNode = doc.allocNode("PremiumData");
    XMLUtils::appendNode(fwdBondNode, fwdPremiumNode);
    XMLUtils::addChild(doc, fwdPremiumNode, "Amount", compensationPayment_);
    XMLUtils::addChild(doc, fwdPremiumNode, "Date", compensationPaymentDate_);

    XMLUtils::addChild(doc, fwdBondNode, "LongInForward", longInForward_);

    return node;
}

std::map<AssetClass, std::set<std::string>>
ForwardBond::underlyingIndices(const boost::shared_ptr<ReferenceDataManager>& referenceDataManager) const {
    std::map<AssetClass, std::set<std::string>> result;
    result[AssetClass::BOND] = {bondData_.securityId()};
    return result;
}

} // namespace data
} // namespace ore


//   Copyright 2015 Patrick Putnam
//
//   Licensed under the Apache License, Version 2.0 (the "License");
//   you may not use this file except in compliance with the License.
//   You may obtain a copy of the License at
//
//       http://www.apache.org/licenses/LICENSE-2.0
//
//   Unless required by applicable law or agreed to in writing, software
//   distributed under the License is distributed on an "AS IS" BASIS,
//   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//   See the License for the specific language governing permissions and
//   limitations under the License.
#ifndef CLOTHO_SELECTION_GENERATOR_FITNES_INLINE_HPP_
#define CLOTHO_SELECTION_GENERATOR_FITNES_INLINE_HPP_

#include <boost/property_tree/ptree.hpp>

#include <boost/random/discrete_distribution.hpp>
#include <boost/random/uniform_int_distribution.hpp>

#include "clotho/data_spaces/selection/selection_generator.hpp"
#include "clotho/data_spaces/fitness/general_fitness.hpp"

#include "clotho/data_spaces/selection/selection_details.hpp"

namespace clotho {
namespace genetics {

template < class RNG >
class UniformPairGenerator : public selection_details< RNG, unsigned int > {
public:
    typedef selection_details< RNG, unsigned int >    base_type;
    typedef typename base_type::individual_id_type individual_id_type;
    typedef typename base_type::parent_pair parent_pair;

    UniformPairGenerator( RNG * rng, unsigned int N ) :
        base_type(rng)
        , m_dist( 0, N)
    {  }

    parent_pair operator()() {
        return std::make_pair( nextID(), nextID() );
    }

    individual_id_type  nextID() {
        return m_dist( *(this->m_rand) );
    }

    virtual ~UniformPairGenerator() {}

protected:
    boost::random::uniform_int_distribution< individual_id_type > m_dist;
};

template < class RNG, class ScoreType >
class DiscretePairGenerator : public selection_details< RNG, unsigned int > {
public:

    typedef selection_details< RNG, unsigned int > base_type;
    typedef typename base_type::individual_id_type  individual_id_type;
    typedef typename base_type::parent_pair         parent_pair;
    typedef ScoreType                               score_type;

    template< class ScoreIterator >
    DiscretePairGenerator( RNG * rng, ScoreIterator first, ScoreIterator last ) :
        base_type( rng )
        , m_dist( first, last )
    {}

    parent_pair operator()() {
        return std::make_pair( nextID(), nextID() );
    }

    individual_id_type nextID() {
        return m_dist( *(this->m_rand) );
    }

    virtual ~DiscretePairGenerator() {}

protected:
    boost::random::discrete_distribution< individual_id_type, score_type > m_dist;
};

template < class RNG >
class ConstantPairGenerator : public selection_details< RNG, unsigned int > {
public:

    typedef selection_details< RNG, unsigned int >  base_type;
    typedef typename base_type::individual_id_type  individual_id_type;
    typedef typename base_type::parent_pair         parent_pair;

    ConstantPairGenerator( RNG * rng, unsigned int id = 0 ) :
        base_type( rng )
        , m_id(id)
    {}

    parent_pair operator()() {
        return std::make_pair( m_id, m_id );
    }

    virtual ~ConstantPairGenerator() {}

protected:
    unsigned int m_id;
};

}   // namepsace_genetics
}   // namespace clotho

#endif  // CLOTHO_SELECTION_GENERATOR_FITNES_INLINE_HPP_