/* scene.hpp - path rendering scene graph. */

// Copyright (c) NVIDIA Corporation. All rights reserved.

#ifndef __scene_hpp__
#define __scene_hpp__

#if defined(_MSC_VER) && (_MSC_VER >= 1020)
# pragma once
#endif

#include <vector>
#include <stack>
#include <string>

#include <boost/shared_ptr.hpp>
#include <boost/weak_ptr.hpp>
#include <boost/enable_shared_from_this.hpp>
#include <boost/lexical_cast.hpp>

#include <stdio.h>    /* for printf and NULL */
#include <stdlib.h>   /* for exit */
#include <math.h>     /* for sin and cos */
#include <GL/glew.h>
#if __APPLE__
#include <OpenGL/glext.h>
#endif

#include <Cg/vector/xyzw.hpp>
#include <Cg/vector/rgba.hpp>
#include <Cg/double.hpp>
#include <Cg/vector.hpp>
#include <Cg/matrix.hpp>
#include <Cg/dot.hpp>
#include <Cg/any.hpp>
#include <Cg/all.hpp>
#include <Cg/mul.hpp>
#include <Cg/round.hpp>
#include <Cg/cross.hpp>
#include <Cg/min.hpp>
#include <Cg/max.hpp>
#include <Cg/distance.hpp>
#include <Cg/saturate.hpp>
#include <Cg/inverse.hpp>
#include <Cg/iostream.hpp>

#include "renderer.hpp"

#include "ActiveControlPoint.hpp"
#include "path.hpp"
#include "path_process.hpp"
#include "glmatrix.hpp"

// Grumble, Microsoft (and probably others) define these as macros
#undef min
#undef max

using namespace boost;
using std::string;
using std::vector;
using std::stack;
using namespace Cg;

class Traversal;
typedef shared_ptr<class Visitor> VisitorPtr;

class RectBounds : public float4
{
public:
    RectBounds();
    RectBounds(const float4 &bounds);
    RectBounds(const RectBounds &bounds);
    RectBounds(float2 top_left, float2 bottom_right);
    RectBounds(float left, float top, float right, float bottom);

    inline float width() { return z-x; }
    inline float height() { return w-y; }
    inline bool isValid() { return valid; }
    inline void invalidate() { valid = false; }
    inline RectBounds dilate(float amount) { return dilate(amount, amount); }

    RectBounds& operator =(const float4 &bounds);
    RectBounds& operator =(const RectBounds &bounds);
    RectBounds operator |(const float4 &bounds);
    RectBounds operator |(const RectBounds &bounds);
    RectBounds& operator |=(const float4 &bounds);
    RectBounds& operator |=(const RectBounds &bounds);
    RectBounds operator & (const float4 &bounds);
    RectBounds operator & (const RectBounds &bounds);
    RectBounds& operator &=(const float4 &bounds);
    RectBounds& operator &=(const RectBounds &bounds);
    RectBounds include(float2 point);
    RectBounds transform(const float4x4 &matrix);
    RectBounds transform(const float3x3 &matrix);
    RectBounds dilate(float x_, float y_);

protected:
    bool valid;
};

struct Node {
    virtual ~Node() {};

    virtual void dumpSVGHelper(FILE *file, const float4x4 &transform) { };
    void dumpSVG(FILE *file, const float4x4 &transform);

    virtual float4 getBounds() { return float4(0,0,-1,-1); } // bogus bounds (x>z and y>w);

    // Force every subclass to implement a traverse() method
    virtual void traverse(VisitorPtr visitor, Traversal &traversal) = 0;

    // Make it easier to traverse by automatically using a generic traversal
    void traverse(VisitorPtr visitor);
};

struct Shape;
typedef shared_ptr<struct RendererState<Shape> > ShapeRendererStatePtr;

typedef shared_ptr<struct Text> TextPtr;
struct Text : Node, HasRendererState<Text>, enable_shared_from_this<Text> {
protected:
public:
	std::string font_family;
	std::string text;
	double font_size;
	double3x3 transform;

    Text(const char* font_, const char* text_, double font_size_,
		double3x3 matrix_)
        : HasRendererState<Text>(this),
		font_family(font_),
		text(text_),
		font_size(font_size_),
		transform(matrix_)
    {}
    virtual ~Text() {}

    void invalidate() {
        invalidateRenderStates();
    }

	void traverse(VisitorPtr visitor, Traversal &traversal);
};

enum OpacityTreatment {
    FULLY_OPAQUE,
    VARIABLE_PREMULTIPLIED_ALPHA,
    VARIABLE_INDEPENDENT_ALPHA,
};

// Gradient-related enumerations
enum SpreadMethod {
    PAD,      // clamp to edge
    REFLECT,  // mirror
    REPEAT,   // repeat
    NONE      // clamp to border with (0,0,0,0) border
};
enum GradientUnits {
    USER_SPACE_ON_USE,
    OBJECT_BOUNDING_BOX
};
// http://www.w3.org/TR/SVG/painting.html#ColorInterpolationProperty
enum ColorSpace {
    CORRECTED_SRGB,     // Indicates that sRGB color values should be converted into linearized RGB color space
    UNCORRECTED         // Indicates that color interpolation should be uncorrected
};

struct Paint : HasRendererState<Paint> {
    virtual bool isNonOpaque() = 0;
    virtual bool isOpaque() = 0;
    virtual bool isTransparent() = 0;
    virtual string toSVG(string fillOrStroke) = 0;
    virtual ~Paint() {}

    Paint()
        : HasRendererState<Paint>(this)
    { }
};
typedef shared_ptr<Paint> PaintPtr;
typedef shared_ptr<RendererState<Paint> > PaintRendererStatePtr;

string glColorToSVGColor(const string fillOrStroke, const float4 &color);

struct SolidColorPaint : Paint {
protected:
    float4 color;

public:
    SolidColorPaint(float4 c)
        : color(c)
    {}

    bool isNonOpaque() {
        return color.a != 1;
    }
    bool isOpaque() {\
        return color.a == 1;
    }
    bool isTransparent() {
        return color.a != 1;
    }
    string toSVG(string fillOrStroke) {
        return glColorToSVGColor(fillOrStroke, color);
    }

    inline float4 getColor() const { return color; }
};
typedef shared_ptr<SolidColorPaint> SolidColorPaintPtr;

struct GradientStop {
    float offset;
    float4 color;  // RGB is color, alpha is opacity

    GradientStop()
        : color(0,0,0,1)
    { }  // opaque black is default

    GradientStop operator = (const GradientStop &src) {
        if (&src != this) {
            offset = src.offset;
            color = src.color;
        }
        return *this;
    }
};

struct GradientStops {
    vector<GradientStop> stop_array;
};
typedef shared_ptr<GradientStops> GradientStopsPtr;

struct GradientPaint : Paint {
protected:
    // Generic gradient attributes
    GradientUnits gradient_units;
    float3x3 gradient_transform,
             inverse_gradient_transform;  // could be float4x4
    SpreadMethod spread_method;
    GradientStopsPtr gradient_stops;

public:
    GradientPaint()
        : gradient_units(OBJECT_BOUNDING_BOX)
        , gradient_transform(1,0,0, 0,1,0, 0,0,1)  // 3x3 identity
        , inverse_gradient_transform(1,0,0, 0,1,0, 0,0,1)  // 3x3 identity
        , spread_method(PAD)
    {}

    inline const vector<GradientStop> &getStopArray() const { return gradient_stops->stop_array; }
    inline SpreadMethod getSpreadMethod() const { return spread_method; }
    inline const float3x3 &getGradientTransform() const { return gradient_transform; }
    inline const float3x3 &getInverseGradientTransform() const { return inverse_gradient_transform; }
    inline GradientUnits getGradientUnits() const { return gradient_units; }

    inline void setGradientStops(GradientStopsPtr stops) {
        gradient_stops = stops;
    }
    inline void setGradientTransform(const float3x3 &matrix) {
        gradient_transform = matrix;
        // OpenGL wants to map path-space positions to texture
        // coordinates so needs the reverse transform so take the inverse.
        inverse_gradient_transform = inverse(matrix);
    }
    inline void setSpreadMethod(SpreadMethod v) {
        spread_method = v;
    }
    inline void setGradientUnits(GradientUnits v) {
      gradient_units = v;
    }
};
typedef shared_ptr<GradientPaint> GradientPaintPtr;

struct LinearGradientPaint : GradientPaint {
protected:
    float2 v1, v2;

public:
    LinearGradientPaint(float2 v1_, float2 v2_)
        : v1(v1_)
        , v2(v2_)
    {}

    OpacityTreatment startShading();
    void stopShading();
    bool isNonOpaque() {
        return false;
    }
    bool isOpaque() {
        return false;
    }
    bool isTransparent() {
        return false;
    }
    string toSVG(string fillOrStroke) {
        printf("LinearGradientPaint lacks proper toSVG, outputting red\n");
        return glColorToSVGColor(fillOrStroke, float4(1,0,0,1));
    }

    inline float2 getV1() const { return v1; }
    inline float2 getV2() const { return v2; }
};
typedef shared_ptr<LinearGradientPaint> LinearGradientPaintPtr;

struct RadialGradientPaint : GradientPaint {
protected:
    float2 center, focal_point;
    float radius;

public:
    RadialGradientPaint(float2 c, float2 f, float r)
        : center(c)
        , focal_point(f)
        , radius(r)
    {}

    OpacityTreatment startShading();
    void stopShading();
    bool isNonOpaque() {
        return false;
    }
    bool isOpaque() {
        return false;
    }
    bool isTransparent() {
        return false;
    }
    string toSVG(string fillOrStroke) {
        printf("RadialGradientPaint lacks proper toSVG, outputting cyan\n");
        return glColorToSVGColor(fillOrStroke, float4(0,1,1,1));
    }

    inline float2 getCenter() const { return center; }
    inline float2 getFocalPoint() const { return focal_point; }
    inline float getRadius() const { return radius; }
};
typedef shared_ptr<RadialGradientPaint> RadialGradientPaintPtr;

struct RasterImage {
#pragma pack(1)
    struct Pixel {
        GLubyte r, g, b, a;
    };
#pragma pack()

    RasterImage(Pixel *pixels_, int width_, int height_)
        : pixels(pixels_), width(width_), height(height_) { }
    RasterImage()
        : pixels(NULL)
        , width(0)
        , height(0)
    { }
    ~RasterImage() { if (pixels) free(pixels); }

    Pixel *pixels;
    int width, height;
};
typedef shared_ptr<RasterImage> RasterImagePtr;

struct ImagePaint : Paint {
    RasterImagePtr image;
    bool opaque;

    ImagePaint(RasterImagePtr image_);

    bool isNonOpaque() {
        return !opaque;
    }
    bool isOpaque() {
        return opaque;
    }
    bool isTransparent() {
        return !opaque;
    }
    string toSVG(string fillOrStroke) {
        printf("LinearGradientPaint lacks proper toSVG, outputting green\n");
        return glColorToSVGColor(fillOrStroke, float4(0,1,0,1));
    }
};
typedef shared_ptr<ImagePaint> ImagePaintPtr;

typedef shared_ptr<struct Shape> ShapePtr;
struct Shape : Node, HasRendererState<Shape>, enable_shared_from_this<Shape> {
protected:
    PathPtr path;
    PaintPtr fill_paint, stroke_paint;
public:
	LdpPolyGroupPtr ldpPoly;
public:
    float net_fill_opacity, net_stroke_opacity;

    Shape()
        : HasRendererState<Shape>(this)
    {}
    Shape(PathPtr p)
        : HasRendererState<Shape>(this)
        , path(p)
        , net_fill_opacity(1)
        , net_stroke_opacity(1)
    { }
    Shape(PathPtr p, PaintPtr f, PaintPtr s)
        : HasRendererState<Shape>(this)
        , path(p)
        , fill_paint(f)
        , stroke_paint(s)
        , net_fill_opacity(1)
        , net_stroke_opacity(1)
    { }

    virtual ~Shape() {}

    int countSegments();

    inline PathPtr getPath() const { return path; }
    inline PaintPtr getFillPaint() const { return fill_paint; }
    inline PaintPtr getStrokePaint() const { return stroke_paint; }

    void dumpSVGHelper(FILE *file, const float4x4 &transform);

    void drawControlPoints();
    void drawReferencePoints();

    float4 getBounds() {
        return path ? path->getBounds() : Node::getBounds();
    }

    using Node::traverse; // Lame ... Node::traverse(VisitorPtr visitor) gets "shadowed"
    void traverse(VisitorPtr visitor, Traversal &traversal);

    bool isEmpty() {
        return path->isEmpty();
    }
    bool isFillable() {
        return path->isFillable();
    }
    bool isStrokable() {
        return path->isStrokable();
    }
    void invalidate() {
        invalidateRenderStates();
        path->invalidate();
    }

    bool isOpaqueFill() {
        if (fill_paint) {
            return fill_paint->isOpaque();
        } else {
            return true;
        }
    }
    bool isOpaqueStroke() {
        if (stroke_paint) {
            return stroke_paint->isOpaque();
        } else {
            return true;
        }
    }
    bool isOpaque() {
        return isOpaqueFill() && isOpaqueStroke();
    }
    bool isNonOpaque() {
        return !isOpaque();
    }

    void processSegments(PathSegmentProcessor &processor);
};

struct Transform : Node, enable_shared_from_this<Transform> {
public:
    NodePtr node;
protected:
    float4x4 matrix;
    float4x4 inverse_matrix;

public:
    // Identity transform
    Transform(NodePtr node_);

    // Projective 3D transform (4x4 matrix)
    Transform(NodePtr node_, const float4x4 &matrix_);

    // Projective 2D transform (3x3 matrix)
    Transform(NodePtr node_, const double3x3 &matrix_);

    void dumpSVGHelper(FILE *file, const float4x4 &transform);
    void setMatrix(const float4x4 &transform);
    inline float4x4 getMatrix() const { return matrix; }
    inline float4x4 getInverseMatrix() const { return inverse_matrix; }

    float4 getBounds();

    using Node::traverse; // Lame ... Node::traverse(VisitorPtr visitor) gets "shadowed"
    void traverse(VisitorPtr visitor, Traversal &traversal);
};
typedef shared_ptr<Transform> TransformPtr;

struct WarpTransform : Transform {
protected:
    float2 to[4];
    float2 from[4];  // Points for a quadrilateral that should map to a [-1..+1,-1..+1] square.
    float2 scale;    // Extra scaling term (typically 90%).

    void updateMatrix();

public:
    // Identity transform
    WarpTransform(NodePtr node_, const float2 to_[4], const float2 from_[4], const float2 &scale);

    void drawWarpPoints();

    void setWarpPoint(int ndx, const float2 &xy);

    void findNearerControlPoint(ActiveWarpPoint &hit);

    inline float4x4 scaledTransform(const float4x4 &m) const {
        return mul(m, scale4x4(scale));
    }
};
typedef shared_ptr<WarpTransform> WarpTransformPtr;

enum ClipMerge {
    SUM_WINDING_NUMBERS,
    SUM_WINDING_NUMBERS_MOD_2,
    CLIP_COVERAGE_UNION
};
enum ClipRule {
    NON_ZERO,
    EVEN_ODD
};

typedef shared_ptr<struct Clip> ClipPtr;
struct Clip : Node, enable_shared_from_this<Clip> {
    NodePtr path;
    NodePtr node;
    ClipMerge clip_merge;

    Clip(NodePtr path_, NodePtr node_, ClipMerge clip_merge_)
        : path(path_)
        , node(node_)
        , clip_merge(clip_merge_) {
    }

    float4 getBounds() { return node->getBounds(); }
    RectBounds getClipBounds();
    using Node::traverse; // Lame ... Node::traverse(VisitorPtr visitor) gets "shadowed"
    void traverse(VisitorPtr visitor, Traversal &traversal);
};

struct ViewBox : Clip {
    ViewBox(NodePtr node_, RectBounds &viewbox);
};
typedef shared_ptr<ViewBox> ViewBoxPtr;

struct Group : public Node, enable_shared_from_this<Group> {
    vector<NodePtr> list;

    void push_back(NodePtr node) { list.push_back(node); }

    void dumpSVGHelper(FILE *file, const float4x4 &transform);

    float4 getBounds();

    using Node::traverse; // Lame ... Node::traverse(VisitorPtr visitor) gets "shadowed"
    void traverse(VisitorPtr visitor, Traversal &traversal);

	std::string ldp_layer_name;
};
typedef shared_ptr<Group> GroupPtr;

struct SvgScene : public Group {
    int width, height;
    RectBounds view_box;
    string preserve_aspect_ratio;
	double ldp_pixel2meter;
};
typedef shared_ptr<SvgScene> SvgScenePtr;

///////////////////////////////////////////////////////////////////////////////
// Visitor/Traversal Interfaces

// This class lets us
//   1. Customize how the different types of nodes are treated during traversal
//   2. Confidently maintain state throughout the process
class Visitor
{
public:
    virtual ~Visitor() { } // Make the destructor virtual

    virtual void visit(ShapePtr shape) = 0;

	virtual void visit(TextPtr text) {}

    virtual void apply(TransformPtr transform) { }
    virtual void unapply(TransformPtr transform) { }

    virtual void apply(ClipPtr clip) { }
    virtual void unapply(ClipPtr clip) { }

    virtual void apply(GroupPtr group) { }
    virtual void unapply(GroupPtr group) { }
};

// This class lets us customize
//   1. The order a node gets traversed in
//   2. What nodes get traversed
class Traversal
{
public:
    virtual ~Traversal() { } // Make the destructor virtual

    virtual void traverse(ShapePtr shape, VisitorPtr visitor) = 0;
    virtual void traverse(TransformPtr transform, VisitorPtr visitor) = 0;
    virtual void traverse(ClipPtr clip, VisitorPtr visitor) = 0;
	virtual void traverse(GroupPtr group, VisitorPtr visitor) = 0;
	virtual void traverse(TextPtr group, VisitorPtr visitor) = 0;
};

///////////////////////////////////////////////////////////////////////////////
// Traversal implementations
class GenericTraversal : public Traversal
{
public:
    virtual void traverse(ShapePtr shape, VisitorPtr visitor);
    virtual void traverse(TransformPtr transform, VisitorPtr visitor);
    virtual void traverse(ClipPtr clip, VisitorPtr visitor);
	virtual void traverse(GroupPtr group, VisitorPtr visitor);
	virtual void traverse(TextPtr group, VisitorPtr visitor);
};

class ReverseTraversal : public GenericTraversal
{
public:
    void traverse(GroupPtr group, VisitorPtr visitor);
};

class ForEachShapeTraversal : public GenericTraversal
{
public:
    void traverse(ClipPtr clip, VisitorPtr visitor);
};

class ForEachTransformTraversal : public GenericTraversal
{
public:
    void traverse(ShapePtr shape, VisitorPtr visitor) { }  // ignore shapes
};

///////////////////////////////////////////////////////////////////////////////
// Visitor implementations
class MatrixSaveVisitor : public Visitor
{
protected:
    stack<float4x4> matrix_stack;

public:
    MatrixSaveVisitor();
    virtual ~MatrixSaveVisitor();
    void apply(TransformPtr transform);
    void unapply(TransformPtr transform);
};

// OK to just inherit from MatrixSaveVisitor because the ClipPathSaveVisitor
// requires a matrix anyway
class ClipSaveVisitor : public MatrixSaveVisitor
{
protected:
    struct ClipAndMatrix {
        ClipAndMatrix(ClipPtr c, float4x4 m)
            : clip(c)
            , matrix(m) { }
        ClipPtr clip;
        float4x4 matrix;
    };
    vector<ClipAndMatrix> clip_stack;

public:
    virtual ~ClipSaveVisitor();
    void apply(ClipPtr clip);
    void unapply(ClipPtr clip);
};

///////////////////////////////////////////////////////////////////////////////
// Other visitors
class Invalidate : public Visitor
{
public:
    void visit(ShapePtr shape);
};

class FlushRendererStates : public Visitor
{
public:
    void visit(ShapePtr shape);
};

class FlushRendererState : public Visitor
{
public:
    FlushRendererState(RendererPtr renderer_) : renderer(renderer_) { }
    void visit(ShapePtr shape);

protected:
    RendererPtr renderer;
};

class InvalidateRendererStates : public Visitor
{
public:
    void visit(ShapePtr shape);
};

class InvalidateRendererState : public Visitor
{
public:
    InvalidateRendererState(RendererPtr renderer_) : renderer(renderer_) { }
void visit(ShapePtr shape);

protected:
    RendererPtr renderer;
};

class CountVisitor : public Visitor {
public:
    CountVisitor()
        : count(0)
    { }
    inline int getCount() { return count; }

protected:
    int count;
};

class CountSegments : public CountVisitor  {
public:
    void visit(ShapePtr shape);
};
typedef shared_ptr<CountSegments> CountSegmentsPtr;

class CountNonOpaqueObjects : public CountVisitor  {
public:
    void visit(ShapePtr shape);
};
typedef shared_ptr<CountNonOpaqueObjects> CountNonOpaqueObjectsPtr;

extern float2 clipToSurfaceScales(float w, float h, float scene_ratio);
extern float4x4 surfaceToClip(float w, float h, float scene_ratio);

#endif // __scene_hpp__


#include "WLSEventAction.hh"

#include "G4Event.hh"
#include "G4EventManager.hh"
#include "G4CerenkovNew.hh"
#include "WLSSteppingAction.hh"
#include "WLSStackingAction.hh"
#include "WLSDetectorConstruction.hh"

#include "Randomize.hh"
#include <TMath.h>
#include <TFitResult.h>
#include <TFitResultPtr.h>
#include <TF1.h>
#include <TH1D.h>
#include <TH2D.h>
#include <TH3D.h>
#include <TNtuple.h>
#include <TFile.h>

#include <TStyle.h>
#include <TText.h>
#include <TGraph.h>
#include <TMarker.h>
#include <TGaxis.h>
#include <TCanvas.h>
#include <TPaveStats.h>

#include "MakeCrvPhotons.hh"
#include "MakeCrvSiPMCharges.hh"
#include "MakeCrvWaveforms.hh"
#include "MakeCrvDigis.hh"
#include "MakeCrvRecoPulses.hh"

#include <stdexcept>
#include <boost/shared_ptr.hpp>

#include "CLHEP/Random/Randomize.h"

WLSEventAction* WLSEventAction::_fgInstance = NULL;

WLSEventAction::WLSEventAction(WLSSteppingAction::simulationMode mode, const std::string &singlePEWaveformFilename, const std::string &photonMapFilename,
                               int numberOfPhotons, int simType, unsigned int minBin, bool verbose) :
                                                                                _mode(mode),
                                                                                _numberOfPhotons(numberOfPhotons),
                                                                                _simType(simType),
                                                                                _minBin(minBin),
                                                                                _currentBin(minBin),
                                                                                _singlePEWaveformFilename(singlePEWaveformFilename),
                                                                                _photonMapFilename(photonMapFilename),
                                                                                _verbose(verbose)
                               //numberOfPhotons, simType, minBin, currentBin, verbose is only needed for simulationMode::CreateLookupTables
{
  _fgInstance = this;

  if(_mode==WLSSteppingAction::UseGeantOnly || _mode==WLSSteppingAction::UseGeantAndLookupTables)
  {
    for(int SiPM=0; SiPM<4; SiPM++)
    {
      std::stringstream s0, s1, title;
      s0<<"Photons_Geant_SiPM_"<<SiPM;
      s1<<"Photons_LookupTable_SiPM_"<<SiPM;
      title<<"Fiber: "<<SiPM/2<<",  Side: "<<SiPM%2;
      _histP[0][SiPM] = new TH1D(s0.str().c_str(),title.str().c_str(),1000,0,1000);
      _histP[1][SiPM] = new TH1D(s1.str().c_str(),title.str().c_str(),1000,0,1000);
      _histP[0][SiPM]->GetXaxis()->SetTitle("Photons");
      _histP[0][SiPM]->SetLineColor(1);
      _histP[1][SiPM]->GetXaxis()->SetTitle("Photons");
      _histP[1][SiPM]->SetLineColor(2);
    }

    for(int SiPM=0; SiPM<4; SiPM++)
    {
      std::stringstream s0, s1, title;
      s0<<"ArrivalTimes_Geant_SiPM_"<<SiPM;
      s1<<"ArrivalTimes_LookupTable_SiPM_"<<SiPM;
      title<<"Fiber: "<<SiPM/2<<",  Side: "<<SiPM%2;
      _histT[0][SiPM] = new TH1D(s0.str().c_str(),title.str().c_str(),250,0,250);
      _histT[1][SiPM] = new TH1D(s1.str().c_str(),title.str().c_str(),250,0,250);
      _histT[0][SiPM]->GetXaxis()->SetTitle("t [ns]");
      _histT[0][SiPM]->SetLineColor(1);
      _histT[1][SiPM]->GetXaxis()->SetTitle("t [ns]");
      _histT[1][SiPM]->SetLineColor(2);
    }

    for(int SiPM=0; SiPM<4; SiPM++)
    {
      std::stringstream s0, title;
      s0<<"PEs_SiPM_"<<SiPM;
      title<<"Fiber: "<<SiPM/2<<",  Side: "<<SiPM%2;
      _histPE[SiPM] = new TH1D(s0.str().c_str(),title.str().c_str(),500,0,500);
      _histPE[SiPM]->GetXaxis()->SetTitle("PEs");
      _histPE[SiPM]->SetLineColor(1);
    }

    _ntuple = new TNtuple("CRVNtuple","CRVNtuple","SiPM:photons:PEs:pulseHeight:pulseBeta:recoPEs:pulseTime:LEtime:chi2");
  }
}

WLSEventAction::~WLSEventAction()
{
  if(_mode==WLSSteppingAction::UseGeantOnly || _mode==WLSSteppingAction::UseGeantAndLookupTables)
  {
    for(int SiPM=0; SiPM<4; SiPM++)
    {
      delete _histP[0][SiPM];
      delete _histP[1][SiPM];
      delete _histT[0][SiPM];
      delete _histT[1][SiPM];
      delete _histPE[SiPM];
    }

    _ntuple->SaveAs("CRVNtuple.root");
    delete _ntuple;
  }

}

void WLSEventAction::BeginOfEventAction(const G4Event* evt)
{
  std::cout<<"Event # "<<evt->GetEventID()<<std::endl;

  WLSSteppingAction::Instance()->Reset();
}

void WLSEventAction::EndOfEventAction(const G4Event* evt)
{
  //create entry in lookup tables
  if(_mode==WLSSteppingAction::CreateLookupTables)
  {
    WLSDetectorConstruction *detector = WLSDetectorConstruction::Instance();

    mu2eCrv::LookupBin bin;

    bin.binNumber = _currentBin;  //gets set by WLSPrimaryGenerator

    bin.arrivalProbability=0;
    bin.timeDelays.clear();
    bin.fiberEmissions.clear();

    //prepare histograms
    int histTimeDifference[mu2eCrv::LookupBin::maxTimeDelays]={0}; //in ns
    int histEmissions[mu2eCrv::LookupBin::maxFiberEmissions]={0};
    int maxTimeDifference=0;
    int maxEmissions=0;
    int arrivingPhotons=0;

    //Lookup tables are created only for SiPM# 0 due to symmetry reasons
    const std::vector<WLSSteppingAction::PhotonInfo> &photonInfo = WLSSteppingAction::Instance()->GetPhotonInfo(0);
    for(size_t i=0; i<photonInfo.size(); i++)
    {
      //extract travel times
      int timeDifference=static_cast<int>(photonInfo[i]._arrivalTime+0.5); //rounded to full ns
                                                                  //the WLS decay time has been set to 0 for mode=CreateLookupTables
                                                                  //so that the true travel time can be determined
                                                                  //(in WLSPrimaryGeneratorAction)
      if(timeDifference<0) timeDifference=0;
      if(timeDifference>=mu2eCrv::LookupBin::maxTimeDelays) continue;

      //extract number of fiber emissions
      int nEmissions=photonInfo[i]._fiberEmissions;
      if(nEmissions<0) nEmissions=0;
      if(nEmissions>=mu2eCrv::LookupBin::maxFiberEmissions) continue;

      //fill histogram bins
      histTimeDifference[timeDifference]++;   //fill this histogram bin
      histEmissions[nEmissions]++;

      if(timeDifference>maxTimeDifference) maxTimeDifference=timeDifference;
      if(nEmissions>maxEmissions) maxEmissions=nEmissions;

      arrivingPhotons++; //only count photons for which the arrival time and number of fiber emissions
                         //can be stored in the histograms
    }

    if(arrivingPhotons>0)
    {
      bin.arrivalProbability=static_cast<float>(arrivingPhotons)/static_cast<float>(_generatedPhotons);
                                                              //_generatedPhotons gets set by WLSPrimaryGeneratorAction
                                                              //after sending out all photons

      //store time time delay histogram in bin
      bin.timeDelays.reserve(maxTimeDifference+1);
      for(int i=0; i<=maxTimeDifference; i++)
      {
        float p=static_cast<float>(histTimeDifference[i])/static_cast<float>(arrivingPhotons);
        //multiplying the probability with the probability scale makes it possible to store it as an unsigned char
        unsigned char pChar = static_cast<unsigned char>(mu2eCrv::LookupBin::probabilityScale*p+0.5);
        bin.timeDelays.push_back(pChar);
      }

      //store fiber emission histogram in bin
      bin.fiberEmissions.reserve(maxEmissions+1);
      for(int i=0; i<=maxEmissions; i++)
      {
        float p=static_cast<float>(histEmissions[i])/static_cast<float>(arrivingPhotons);
        //multiplying the probability with the probability scale makes it possible to store it as an unsigned char
        unsigned char pChar = static_cast<unsigned char>(mu2eCrv::LookupBin::probabilityScale*p+0.5);
        bin.fiberEmissions.push_back(pChar);
      }
    }

    if(_verbose)
    {
      std::streamsize origPrecision = std::cout.precision();
      std::cout.precision(10);
      float p=bin.arrivalProbability;
      std::cout<<"Probability: "<<p<<std::endl;
      std::cout.precision(origPrecision);
      std::cout<<"Time Difference Probabilities: ";
      for(size_t i=0; i<bin.timeDelays.size(); i++) std::cout<<i<<"/"<<(int)bin.timeDelays[i]<<" ";
      std::cout<<std::endl;
      std::cout<<"Fiber Emissions Probabilities: ";
      for(size_t i=0; i<bin.fiberEmissions.size(); i++) std::cout<<i<<"/"<<(int)bin.fiberEmissions[i]<<" ";
      std::cout<<std::endl<<std::endl;
    }

    std::stringstream filename;
    filename<<"LookupTable_"<<_simType<<"_";
    filename.fill('0');
    filename.width(6);
    filename<<_minBin;
    if(evt->GetEventID()==0) std::remove(filename.str().c_str());  //remove existing file

//write some constants to the file before the first bin
    if(_simType==0 && _currentBin==0)
    {
      G4Material *scintillator = G4Material::GetMaterial("PolystyreneScint",true); //scintillator
      G4MaterialPropertiesTable *scintillatorPropertiesTable = scintillator->GetMaterialPropertiesTable();
      G4MaterialPropertyVector *rindexScintillator = scintillatorPropertiesTable->GetProperty("RINDEX");

      G4Material *fiber = G4Material::GetMaterial("PolystyreneFiber",true); //fiber
      G4MaterialPropertiesTable* fiberPropertiesTable = fiber->GetMaterialPropertiesTable();
      G4MaterialPropertyVector *rindexFiber = fiberPropertiesTable->GetProperty("RINDEX");

      mu2eCrv::LookupConstants LC;
      LC.version1           = 6;
      LC.version2           = 0;
      LC.reflector          = detector->GetReflectorOption();
      LC.halfThickness      = detector->GetScintillatorHalfThickness(),
      LC.halfWidth          = detector->GetScintillatorHalfWidth(),
      LC.halfLength         = detector->GetScintillatorHalfLength(),
      LC.fiberSeparation    = detector->GetFiberSeparation(),
      LC.holeRadiusX        = detector->GetHoleRadiusX(),
      LC.holeRadiusY        = detector->GetHoleRadiusY(),
      LC.fiberRadius        = detector->GetClad2Radius();
      LC.scintillatorCornerRadius  = detector->GetScintillatorCornerRadius();
      LC.scintillatorBirksConstant = scintillator->GetIonisation()->GetBirksConstant();
      LC.WLSfiberDecayTime         = fiberPropertiesTable->GetConstProperty("WLSTIMECONSTANT");
      LC.Write(filename.str());

      mu2eCrv::LookupCerenkov LCerenkov;
      G4CerenkovNew *cerenkov = G4CerenkovNew::Instance();
      for(double beta=1.0; ; beta/=1.01)
      {
        double photonsScintillator=cerenkov->GetAverageNumberOfPhotons(CLHEP::eplus, beta, scintillator, rindexScintillator);
        double photonsFiber=cerenkov->GetAverageNumberOfPhotons(CLHEP::eplus, beta, fiber, rindexFiber);
        LCerenkov.photonsScintillator[beta]=photonsScintillator;
        LCerenkov.photonsFiber[beta]=photonsFiber;
        if(photonsScintillator==0 && photonsFiber==0) break;
      }
      LCerenkov.Write(filename.str());

      mu2eCrv::LookupBinDefinitions LBD;
      LBD.xBins     = WLSDetectorConstruction::Instance()->GetXBins();
      LBD.yBins     = WLSDetectorConstruction::Instance()->GetYBins();
      LBD.zBins     = WLSDetectorConstruction::Instance()->GetZBins();
      LBD.betaBins  = WLSDetectorConstruction::Instance()->GetBetaBins();
      LBD.thetaBins = WLSDetectorConstruction::Instance()->GetThetaBins();
      LBD.phiBins   = WLSDetectorConstruction::Instance()->GetPhiBins();
      LBD.rBins     = WLSDetectorConstruction::Instance()->GetRBins();
      LBD.Write(filename.str());
    }

//write the data to file
    bin.Write(filename.str());
  }

  //fill histograms if a simulation is run
  if(_mode==WLSSteppingAction::UseGeantOnly || _mode==WLSSteppingAction::UseGeantAndLookupTables)
  {
    for(int SiPM=0; SiPM<4; SiPM++)
    {
      _histP[0][SiPM]->Fill(WLSSteppingAction::Instance()->GetPhotonInfo(SiPM).size());
      _histP[1][SiPM]->Fill(WLSSteppingAction::Instance()->GetArrivalTimesFromLookupTables(SiPM).size());
      const std::vector<WLSSteppingAction::PhotonInfo> &photonInfo = WLSSteppingAction::Instance()->GetPhotonInfo(SiPM);
      const std::vector<double> &arrivalTimesFromLookupTables = WLSSteppingAction::Instance()->GetArrivalTimesFromLookupTables(SiPM);
      for(size_t i=0; i<photonInfo.size(); i++) _histT[0][SiPM]->Fill(photonInfo[i]._arrivalTime);
      for(size_t i=0; i<arrivalTimesFromLookupTables.size(); i++) _histT[1][SiPM]->Fill(arrivalTimesFromLookupTables[i]);
    }

    Draw(evt);

    std::cout<<"Photons: ";
    for(int SiPM=0; SiPM<4; SiPM++) std::cout<<_histP[0][SiPM]->GetMean()<<"/"<<_histP[1][SiPM]->GetMean()<<"  ";
    std::cout<<std::endl;
    std::cout<<"PEs: ";
    for(int SiPM=0; SiPM<4; SiPM++) std::cout<<_histPE[SiPM]->GetMean()<<"       ";
    std::cout<<std::endl;
    std::cout<<"Times: ";
    for(int SiPM=0; SiPM<4; SiPM++) std::cout<<_histT[0][SiPM]->GetMean()<<"/"<<_histT[1][SiPM]->GetMean()<<"  ";
    std::cout<<std::endl;
  }

  WLSStackingAction::Instance()->PrintStatus();
  WLSSteppingAction::Instance()->PrintFiberStats();
}

void WLSEventAction::Draw(const G4Event* evt)
{
  double maxTime=200.0;

  mu2eCrv::MakeCrvSiPMCharges::ProbabilitiesStruct probabilities;
  probabilities._avalancheProbParam1 = 0.607;
  probabilities._avalancheProbParam2 = 2.7;
  probabilities._trapType0Prob = 0.0;
  probabilities._trapType1Prob = 0.0;
  probabilities._trapType0Lifetime = 5;
  probabilities._trapType1Lifetime = 50;
  probabilities._thermalRate = 3.0e-4;
  probabilities._crossTalkProb = 0.09;  //at 2017 test beam

  int nPixelsX=40;
  int nPixelsY=40;
  double overvoltage=3.0;  //at 2017 test beam
  double timeConstant=13.3;
  double capacitance=8.84e-14;
  std::vector<std::pair<int,int> > inactivePixels = { {18,18}, {18,19}, {18,20}, {18,21},
                                                      {19,18}, {19,19}, {19,20}, {19,21},
                                                      {20,18}, {20,19}, {20,20}, {20,21},
                                                      {21,18}, {21,19}, {21,20}, {21,21} };

  static CLHEP::HepJamesRandom engine(1);
  static CLHEP::RandFlat randFlat(engine);
  static CLHEP::RandGaussQ randGaussQ(engine);
  static CLHEP::RandPoissonQ randPoissonQ(engine);
  mu2eCrv::MakeCrvSiPMCharges sim(randFlat,randPoissonQ,_photonMapFilename.c_str());
  sim.SetSiPMConstants(nPixelsX, nPixelsY, overvoltage, timeConstant, capacitance, probabilities, inactivePixels);

  mu2eCrv::MakeCrvWaveforms makeCrvWaveform;
  double singlePEWaveformPrecision=0.5;
  double singlePEWaveformStretchFactor=1.047;
  double singlePEWaveformMaxTime=100.0;
  double singlePEReferenceCharge=2.652e-13;
  double digitizationInterval = 12.55; //ns
  double digitizationInterval2 = 1.0; //ns
  double noise = 4.0e-4;
  double pedestal = 100; //ADC
  double ADCconversionFactor = 2300; //ADC/V
  double calibrationFactor = 391.2; //ADC*ns/PE
  double calibrationFactorPulseHeight = 11.4; //ADC/PE
  makeCrvWaveform.LoadSinglePEWaveform(_singlePEWaveformFilename.c_str(),
                                       singlePEWaveformPrecision, singlePEWaveformStretchFactor,
                                       singlePEWaveformMaxTime, singlePEReferenceCharge);

  mu2eCrv::MakeCrvDigis makeCrvDigis;

  boost::shared_ptr<mu2eCrv::MakeCrvRecoPulses> makeRecoPulses[4];

  double startTimeGlobal=G4UniformRand()*digitizationInterval;  //cannot be negative
  std::vector<double> siPMtimes[4], siPMcharges[4], siPMchargesInPEs[4];
  std::vector<double> waveform[4], waveform2[4];
  std::vector<unsigned int> ADCs[4], ADCs2[4];
  unsigned int TDC, TDC2;
  for(int SiPM=0; SiPM<4; SiPM++)
  {
    std::vector<std::pair<double, size_t> > photonTimes;
    if(_mode==WLSSteppingAction::UseGeantAndLookupTables)
    {
      const std::vector<double> &photonTimesTmp = WLSSteppingAction::Instance()->GetArrivalTimesFromLookupTables(SiPM);
      for(size_t i=0; i<photonTimesTmp.size(); i++) photonTimes.emplace_back(std::pair<double,size_t>(photonTimesTmp[i],0));
    }
    else
    {
      const std::vector<WLSSteppingAction::PhotonInfo> &photonInfo = WLSSteppingAction::Instance()->GetPhotonInfo(SiPM);
      for(size_t i=0; i<photonInfo.size(); i++) photonTimes.emplace_back(std::pair<double,size_t>(photonInfo[i]._arrivalTime,0));
    }
    int photons = photonTimes.size();

    std::vector<mu2eCrv::SiPMresponse> SiPMresponseVector;
    sim.Simulate(photonTimes, SiPMresponseVector, 0.0, maxTime);
    double PEs=0;
    double firstTime=NAN;
    for(size_t i=0; i<SiPMresponseVector.size(); i++)
    {
      siPMtimes[SiPM].push_back(SiPMresponseVector[i]._time);
      siPMcharges[SiPM].push_back(SiPMresponseVector[i]._charge);
      siPMchargesInPEs[SiPM].push_back(SiPMresponseVector[i]._chargeInPEs);
      PEs+=SiPMresponseVector[i]._chargeInPEs;
      if(isnan(firstTime) || firstTime>SiPMresponseVector[i]._time) firstTime=SiPMresponseVector[i]._time;
    }
    _histPE[SiPM]->Fill(PEs);

    double startTime=startTimeGlobal;
    if(firstTime<startTime) startTime=firstTime*G4UniformRand();  //earliest charge cannot be before start time

    makeCrvWaveform.MakeWaveform(siPMtimes[SiPM], siPMcharges[SiPM], waveform[SiPM], startTime, digitizationInterval);
    makeCrvWaveform.MakeWaveform(siPMtimes[SiPM], siPMcharges[SiPM], waveform2[SiPM], startTime, digitizationInterval2);

    makeCrvWaveform.AddElectronicNoise(waveform[SiPM], noise, randGaussQ);
    makeCrvWaveform.AddElectronicNoise(waveform2[SiPM], noise, randGaussQ);

    makeCrvDigis.SetWaveform(waveform[SiPM], ADCconversionFactor, pedestal, startTime, digitizationInterval);
    ADCs[SiPM] = makeCrvDigis.GetADCs();
    TDC = makeCrvDigis.GetTDC();
    makeCrvDigis.SetWaveform(waveform2[SiPM], ADCconversionFactor, pedestal, startTime, digitizationInterval2);
    ADCs2[SiPM] = makeCrvDigis.GetADCs();
    TDC2 = makeCrvDigis.GetTDC();

    float minADCdifference=5;
    float defaultBeta=19.0;
    float minBeta=5.0;
    float maxBeta=40.0;
    float maxTimeDifference=15.0;
    float minPulseHeightRatio=0.7;
    float maxPulseHeightRatio=1.5;
    float LEtimeFactor=0.985;
    bool  allowDoubleGumbel=false;
    float doubleGumbelThreshold=2.0;
    makeRecoPulses[SiPM]=boost::shared_ptr<mu2eCrv::MakeCrvRecoPulses>(new mu2eCrv::MakeCrvRecoPulses(minADCdifference, defaultBeta, minBeta, maxBeta,
                                                                                maxTimeDifference, minPulseHeightRatio,
                                                                                maxPulseHeightRatio, LEtimeFactor,
                                                                                allowDoubleGumbel, doubleGumbelThreshold));
    makeRecoPulses[SiPM]->SetWaveform(ADCs[SiPM], TDC, digitizationInterval, pedestal, calibrationFactor, calibrationFactorPulseHeight);
    if(makeRecoPulses[SiPM]->GetPEs().size()>0)
    {
      double pulseHeight=0;
      double recoPEs= 0;
      double pulseBeta=0;
      double pulseTime=0;
      double LEtime=0;
      double pulseChi2=0;
      for(size_t j=0; j<makeRecoPulses[SiPM]->GetPEs().size(); j++)
      {
         double recoPEsTmp = makeRecoPulses[SiPM]->GetPEs().at(j);
         if(recoPEsTmp>recoPEs)
         {
           recoPEs=recoPEsTmp;
           pulseHeight = makeRecoPulses[SiPM]->GetPulseHeights().at(j);
           pulseBeta = makeRecoPulses[SiPM]->GetPulseBetas().at(j);
           pulseTime = makeRecoPulses[SiPM]->GetPulseTimes().at(j);
           LEtime = makeRecoPulses[SiPM]->GetLEtimes().at(j);
           pulseChi2 = makeRecoPulses[SiPM]->GetPulseFitChi2s().at(j);
         }
      }
      _ntuple->Fill(SiPM,photons,PEs,pulseHeight,pulseBeta,recoPEs,pulseTime,LEtime,pulseChi2);
      _PEs[SiPM].push_back(PEs);
      _recoPEs[SiPM].push_back(recoPEs);
      _pulseTimes[SiPM].push_back(pulseTime);
      _LETimes[SiPM].push_back(LEtime);
      _pulseBetas[SiPM].push_back(pulseBeta);
      double avgPEs=0;
      double avgrecoPEs=0;
      double avgPulseTime=0;
      double avgLETime=0;
      double avgPulseBeta=0;
      for(size_t j=0; j<_PEs[SiPM].size(); j++) {avgPEs+=_PEs[SiPM][j];}
      for(size_t j=0; j<_recoPEs[SiPM].size(); j++) {avgrecoPEs+=_recoPEs[SiPM][j];}
      for(size_t j=0; j<_pulseTimes[SiPM].size(); j++) {avgPulseTime+=_pulseTimes[SiPM][j];}
      for(size_t j=0; j<_LETimes[SiPM].size(); j++) {avgLETime+=_LETimes[SiPM][j];}
      for(size_t j=0; j<_pulseBetas[SiPM].size(); j++) {avgPulseBeta+=_pulseBetas[SiPM][j];}
      avgPEs/=_PEs[SiPM].size();
      avgrecoPEs/=_recoPEs[SiPM].size();
      avgPulseTime/=_pulseTimes[SiPM].size();
      avgLETime/=_LETimes[SiPM].size();
      avgPulseBeta/=_pulseBetas[SiPM].size();
      std::cout<<"SiPM: "<<SiPM<<" PEs: "<<PEs<<"  avg: "<<avgPEs<<"     recoPEs: "<<recoPEs<<"("<<recoPEs/PEs<<") avg: "<<avgrecoPEs<<"("<<avgrecoPEs/avgPEs<<")   ";
      std::cout<<"avgtime:"<<avgPulseTime<<"/"<<avgLETime<<"       width:"<<pulseBeta<<"  avg:"<<avgPulseBeta<<"   height:"<<pulseHeight<<"  ";
      std::cout<<"nPulses:"<<makeRecoPulses[SiPM]->GetPEs().size()<<std::endl;
    }
  }

//Plotting things
  std::ostringstream s1;
  s1<<"waveform_"<<evt->GetEventID();

  gStyle->SetOptStat(0);
  TCanvas c(s1.str().c_str(),s1.str().c_str(),1000,1000);
  c.Divide(2,2);
  TGraph *graph[4]={NULL};
  TGraph *graph2[4]={NULL};
  TH1D *hist[4], *histSiPMResponse[4];

  if(evt->GetEventID()<20)
  {
  std::vector<TGraph*> graphVector;
  std::vector<TMarker*> markerVector;
  std::vector<TGaxis*> axisVector;

  for(int SiPM=0; SiPM<4; SiPM++)
  {
    c.cd(SiPM+1);

//Photon Arrival Times
    std::ostringstream s2, s3;
    s2<<"Photons_"<<evt->GetEventID()<<"__"<<SiPM;
    s3<<"Fiber: "<<SiPM/2<<",  Side: "<<SiPM%2;
    hist[SiPM]=new TH1D(s2.str().c_str(),s3.str().c_str(),100,0,maxTime);
    if(_mode==WLSSteppingAction::UseGeantAndLookupTables)
    {
      const std::vector<double> &photonTimesTmp = WLSSteppingAction::Instance()->GetArrivalTimesFromLookupTables(SiPM);
      for(size_t i=0; i<photonTimesTmp.size(); i++) hist[SiPM]->Fill(photonTimesTmp[i]);
    }
    else
    {
      const std::vector<WLSSteppingAction::PhotonInfo> &photonInfo = WLSSteppingAction::Instance()->GetPhotonInfo(SiPM);
      for(size_t i=0; i<photonInfo.size(); i++) hist[SiPM]->Fill(photonInfo[i]._arrivalTime);
    }

    hist[SiPM]->SetLineColor(kBlue);
    hist[SiPM]->GetXaxis()->SetTitle("t [ns]");
    hist[SiPM]->GetXaxis()->SetTitleOffset(0.8);
    hist[SiPM]->GetXaxis()->SetTitleSize(0.05);
    hist[SiPM]->GetYaxis()->SetTitle("Photons");
    hist[SiPM]->GetYaxis()->SetTitleOffset(0.8);
    hist[SiPM]->GetYaxis()->SetTitleSize(0.05);
    hist[SiPM]->GetYaxis()->SetAxisColor(kBlue);
    hist[SiPM]->GetYaxis()->SetTitleColor(kBlue);
    hist[SiPM]->GetYaxis()->SetLabelColor(kBlue);
    hist[SiPM]->Draw();

//SiPM response
    double scaleSiPMResponse = 1.0;
    double totalPEs=0;
    histSiPMResponse[SiPM]=new TH1D((s2.str()+"SiPMResponse").c_str(),"",100,0,maxTime);
    for(unsigned int j=0; j<siPMtimes[SiPM].size(); j++)
    {
      histSiPMResponse[SiPM]->Fill(siPMtimes[SiPM][j], siPMchargesInPEs[SiPM][j]*scaleSiPMResponse);
      totalPEs+=siPMchargesInPEs[SiPM][j];
    }
    histSiPMResponse[SiPM]->SetLineColor(kOrange-6);
    histSiPMResponse[SiPM]->Draw("histsame");

//waveforms with 1 ns bin width
    unsigned int n2 = ADCs2[SiPM].size();
    if(n2==0) continue;
    double *t2 = new double[n2];
    double *v2 = new double[n2];
    double histMax = hist[SiPM]->GetMaximum();
    double waveformMax = *std::max_element(ADCs2[SiPM].begin(),ADCs2[SiPM].end());
    double scale = histMax/waveformMax;
    for(unsigned int j=0; j<n2; j++)
    {
      t2[j]=(TDC2+j)*digitizationInterval2;
      v2[j]=ADCs2[SiPM][j];
      v2[j]*=scale;
    }
    graph2[SiPM]=new TGraph();
    graph2[SiPM]->SetTitle("");
    graph2[SiPM]->SetLineWidth(1);
    graph2[SiPM]->SetLineColor(kRed);
    graph2[SiPM]->DrawGraph(n2,t2,v2,"same");

    delete[] t2;
    delete[] v2;

//waveforms with 12.55 ns bin width
    unsigned int n = ADCs[SiPM].size();
    if(n==0) continue;
    double *t = new double[n];
    double *v = new double[n];
    for(unsigned int j=0; j<n; j++)
    {
      t[j]=(TDC+j)*digitizationInterval;
      v[j]=ADCs[SiPM][j];
      v[j]*=scale;
    }
    graph[SiPM]=new TGraph(n,t,v);
    graph[SiPM]->SetTitle("");
    graph[SiPM]->SetMarkerStyle(20);
    graph[SiPM]->SetMarkerSize(1.5);
    graph[SiPM]->SetMarkerColor(kRed);
    graph[SiPM]->Draw("sameP");

    delete[] t;
    delete[] v;

//fit
    unsigned int nPulse = makeRecoPulses[SiPM]->GetPEs().size();
    for(unsigned int pulse=0; pulse<nPulse; pulse++)
    {
      if(isnan(makeRecoPulses[SiPM]->GetPulseBetas().at(pulse))) continue;
      double tF1=makeRecoPulses[SiPM]->GetPulseStarts().at(pulse);
      double tF2=makeRecoPulses[SiPM]->GetPulseEnds().at(pulse);
      int nF=(tF2-tF1)/1.0 + 1;
      double *tF = new double[nF];
      double *vF = new double[nF];
      for(int iF=0; iF<nF; iF++)
      {
        double p0 = makeRecoPulses[SiPM]->GetPulseHeights().at(pulse)*TMath::E();
        double p1 = makeRecoPulses[SiPM]->GetPulseTimes().at(pulse);
        double p2 = makeRecoPulses[SiPM]->GetPulseBetas().at(pulse);
        tF[iF] = tF1 + iF*1.0;
        vF[iF] = p0*TMath::Exp(-(tF[iF]-p1)/p2-TMath::Exp(-(tF[iF]-p1)/p2));
        vF[iF]+=pedestal;
        vF[iF]*=scale;
        if(isnan(vF[iF])) nF=0;
      }
      if(nF>0)
      {
        TGraph *graphF=new TGraph();
        graphVector.push_back(graphF);
        graphF->SetTitle("");
        graphF->SetLineWidth(2);
        graphF->SetLineColor(kGreen);
        graphF->DrawGraph(nF,tF,vF,"same");
      }

      delete[] tF;
      delete[] vF;
    }

    TGaxis *axis = new TGaxis(maxTime*0.9,0,maxTime*0.9,histMax,0,histMax/scale,10,"+L");
    axisVector.push_back(axis);
    axis->SetTitle("ADC");
    axis->SetTitleOffset(-0.5);
    axis->SetTitleSize(0.05);
    axis->SetTitleColor(kRed);
    axis->SetLineColor(kRed);
    axis->SetLabelColor(kRed);
    axis->Draw("same");

    TGaxis *axisSiPMResponse = new TGaxis(maxTime,0,maxTime,histMax,0,histMax/scaleSiPMResponse,10,"+L");
    axisVector.push_back(axisSiPMResponse);
    axisSiPMResponse->SetTitle("SiPM charges [PE]");
    axisSiPMResponse->SetTitleOffset(0.85);
    axisSiPMResponse->SetTitleSize(0.05);
    axisSiPMResponse->SetTitleColor(kOrange-6);
    axisSiPMResponse->SetLineColor(kOrange-6);
    axisSiPMResponse->SetLabelColor(kOrange-6);
    axisSiPMResponse->Draw("same");
  }

  c.SaveAs((s1.str()+".C").c_str());

  for(int SiPM=0; SiPM<4; SiPM++)
  {
    delete hist[SiPM];
    delete histSiPMResponse[SiPM];
    if(graph[SiPM]) delete graph[SiPM];
    if(graph2[SiPM]) delete graph2[SiPM];
  }
  for(size_t i=0; i<graphVector.size(); i++) delete graphVector[i];
  for(size_t i=0; i<markerVector.size(); i++) delete markerVector[i];
  for(size_t i=0; i<axisVector.size(); i++) delete axisVector[i];
  }

  gStyle->SetOptStat(1111);
  TCanvas c1("Photons","Photons",1000,1000);
  c1.Divide(2,2);
  for(int SiPM=0; SiPM<4; SiPM++)
  {
    c1.cd(SiPM+1);
    gPad->SetLogy();
    _histP[0][SiPM]->Draw();
    gPad->Update();
    TPaveStats *stats0 = (TPaveStats*)_histP[0][SiPM]->FindObject("stats");
    stats0->SetTextColor(1);
    stats0->SetLineColor(1);
    double X1 = stats0->GetX1NDC();
    double Y1 = stats0->GetY1NDC();
    double X2 = stats0->GetX2NDC();
    double Y2 = stats0->GetY2NDC();
    _histP[1][SiPM]->Draw();
    gPad->Update();
    TPaveStats *stats1 = (TPaveStats*)_histP[1][SiPM]->FindObject("stats");
    stats1->SetTextColor(2);
    stats1->SetLineColor(2);
    stats1->SetX1NDC(X1);
    stats1->SetY1NDC(Y1-(Y2-Y1));
    stats1->SetX2NDC(X2);
    stats1->SetY2NDC(Y1);
    _histP[0][SiPM]->Draw();
    _histP[1][SiPM]->Draw("same");
  }
  c1.SaveAs("Photons.C");

  TCanvas c2("ArrivalTimes","ArrivalTimes",1000,1000);
  c2.Divide(2,2);
  for(int SiPM=0; SiPM<4; SiPM++)
  {
    c2.cd(SiPM+1);
    gPad->SetLogy();
    _histT[0][SiPM]->Draw();
    gPad->Update();
    TPaveStats *stats0 = (TPaveStats*)_histT[0][SiPM]->FindObject("stats");
    stats0->SetTextColor(1);
    stats0->SetLineColor(1);
    double X1 = stats0->GetX1NDC();
    double Y1 = stats0->GetY1NDC();
    double X2 = stats0->GetX2NDC();
    double Y2 = stats0->GetY2NDC();
    _histT[1][SiPM]->Draw();
    gPad->Update();
    TPaveStats *stats1 = (TPaveStats*)_histT[1][SiPM]->FindObject("stats");
    stats1->SetTextColor(2);
    stats1->SetLineColor(2);
    stats1->SetX1NDC(X1);
    stats1->SetY1NDC(Y1-(Y2-Y1));
    stats1->SetX2NDC(X2);
    stats1->SetY2NDC(Y1);
    _histT[0][SiPM]->Draw();
    _histT[1][SiPM]->Draw("same");
  }
  c2.SaveAs("ArrivalTimes.C");

  TCanvas c3("PEs","PEs",1000,1000);
  c3.Divide(2,2);
  for(int SiPM=0; SiPM<4; SiPM++)
  {
    c3.cd(SiPM+1);
    gPad->SetLogy();
    _histPE[SiPM]->Draw();
  }
  c3.SaveAs("PEs.C");
}


//////////////////////////////////////////////////////////////////////////////
//
// (C) Copyright Ion Gaztanaga 2005-2008. Distributed under the Boost
// Software License, Version 1.0. (See accompanying file
// LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
// See http://www.boost.org/libs/interprocess for documentation.
//
//////////////////////////////////////////////////////////////////////////////

#ifndef BOOST_INTERPROCESS_DETAIL_NODE_POOL_HPP
#define BOOST_INTERPROCESS_DETAIL_NODE_POOL_HPP

#if (defined _MSC_VER) && (_MSC_VER >= 1200)
#  pragma once
#endif

#include <boost/interprocess/detail/config_begin.hpp>
#include <boost/interprocess/detail/workaround.hpp>

#include <boost/intrusive/slist.hpp>
#include <boost/math/common_factor_ct.hpp>
#include <boost/pointer_to_other.hpp>

#include <boost/interprocess/sync/interprocess_mutex.hpp>
#include <boost/interprocess/detail/utilities.hpp>
#include <boost/interprocess/exceptions.hpp>
#include <boost/interprocess/detail/math_functions.hpp>
#include <boost/interprocess/detail/type_traits.hpp>
#include <boost/interprocess/allocators/detail/node_tools.hpp>
#include <boost/interprocess/mem_algo/detail/mem_algo_common.hpp>
#include <boost/interprocess/allocators/detail/allocator_common.hpp>
#include <cstddef>
#include <functional>
#include <algorithm>
#include <cassert>

//!\file
//!Describes the real adaptive pool shared by many Interprocess adaptive pool allocators

namespace boost {
namespace interprocess {
namespace detail {

template<class SegmentManagerBase>
class private_node_pool_impl
{
   //Non-copyable
   private_node_pool_impl();
   private_node_pool_impl(const private_node_pool_impl &);
   private_node_pool_impl &operator=(const private_node_pool_impl &);

   //A node object will hold node_t when it's not allocated
   public:
   typedef typename SegmentManagerBase::void_pointer              void_pointer;
   typedef typename node_slist<void_pointer>::slist_hook_t        slist_hook_t;
   typedef typename node_slist<void_pointer>::node_t              node_t;
   typedef typename node_slist<void_pointer>::node_slist_t        free_nodes_t;
   typedef typename SegmentManagerBase::multiallocation_chain     multiallocation_chain;

   private:
   typedef typename bi::make_slist
      < node_t, bi::base_hook<slist_hook_t>
      , bi::linear<true>
      , bi::constant_time_size<false> >::type      blockslist_t;
   public:

   //!Segment manager typedef
   typedef SegmentManagerBase segment_manager_base_type;

   //!Constructor from a segment manager. Never throws
   private_node_pool_impl(segment_manager_base_type *segment_mngr_base, std::size_t node_size, std::size_t nodes_per_block)
   :  m_nodes_per_block(nodes_per_block)
   ,  m_real_node_size(detail::lcm(node_size, std::size_t(alignment_of<node_t>::value)))
      //General purpose allocator
   ,  mp_segment_mngr_base(segment_mngr_base)
   ,  m_blocklist()
   ,  m_freelist()
      //Debug node count
   ,  m_allocated(0)
   {}

   //!Destructor. Deallocates all allocated blocks. Never throws
   ~private_node_pool_impl()
   {  this->purge_blocks();  }

   std::size_t get_real_num_node() const
   {  return m_nodes_per_block; }

   //!Returns the segment manager. Never throws
   segment_manager_base_type* get_segment_manager_base()const
   {  return detail::get_pointer(mp_segment_mngr_base);  }

   //!Allocates array of count elements. Can throw boost::interprocess::bad_alloc
   void *allocate_node()
   {
      //If there are no free nodes we allocate a new block
      if (m_freelist.empty())
         priv_alloc_block();
      //We take the first free node
      node_t *n = &m_freelist.front();
      m_freelist.pop_front();
      ++m_allocated;
      return n;
   }

   //!Deallocates an array pointed by ptr. Never throws
   void deallocate_node(void *ptr)
   {
      //We put the node at the beginning of the free node list
      node_t * to_deallocate = static_cast<node_t*>(ptr);
      m_freelist.push_front(*to_deallocate);
      assert(m_allocated>0);
      --m_allocated;
   }

   //!Allocates a singly linked list of n nodes ending in null pointer
   //!can throw boost::interprocess::bad_alloc
   multiallocation_chain allocate_nodes(const std::size_t n)
   {
      multiallocation_chain nodes;
      std::size_t i = 0;
      try{
         for(; i < n; ++i){
            nodes.push_front(this->allocate_node());
         }
      }
      catch(...){
         this->deallocate_nodes(nodes, i);
         throw;
      }
      return boost::interprocess::move(nodes);
   }

   //!Deallocates the first n nodes of a linked list of nodes. Never throws
   void deallocate_nodes(multiallocation_chain &nodes, std::size_t n)
   {
      for(std::size_t i = 0; i < n; ++i){
         void *p = detail::get_pointer(nodes.front());
         assert(p);
         nodes.pop_front();
         this->deallocate_node(p);
      }
   }

   //!Deallocates the nodes pointed by the multiallocation iterator. Never throws
   void deallocate_nodes(multiallocation_chain chain)
   {
      while(!chain.empty()){
         void *addr = detail::get_pointer(chain.front());
         chain.pop_front();
         deallocate_node(addr);
      }
   }

   //!Deallocates all the free blocks of memory. Never throws
   void deallocate_free_blocks()
   {
      typedef typename free_nodes_t::iterator nodelist_iterator;
      typename blockslist_t::iterator bit(m_blocklist.before_begin()),
                                      it(m_blocklist.begin()),
                                      itend(m_blocklist.end());
      free_nodes_t backup_list;
      nodelist_iterator backup_list_last = backup_list.before_begin();

      //Execute the algorithm and get an iterator to the last value
      std::size_t blocksize = detail::get_rounded_size
         (m_real_node_size*m_nodes_per_block, alignment_of<node_t>::value);

      while(it != itend){
         //Collect all the nodes from the block pointed by it
         //and push them in the list
         free_nodes_t free_nodes;
         nodelist_iterator last_it = free_nodes.before_begin();
         const void *addr = get_block_from_hook(&*it, blocksize);

         m_freelist.remove_and_dispose_if
            (is_between(addr, blocksize), push_in_list(free_nodes, last_it));

         //If the number of nodes is equal to m_nodes_per_block
         //this means that the block can be deallocated
         if(free_nodes.size() == m_nodes_per_block){
            //Unlink the nodes
            free_nodes.clear();
            it = m_blocklist.erase_after(bit);
            mp_segment_mngr_base->deallocate((void*)addr);
         }
         //Otherwise, insert them in the backup list, since the
         //next "remove_if" does not need to check them again.
         else{
            //Assign the iterator to the last value if necessary
            if(backup_list.empty() && !m_freelist.empty()){
               backup_list_last = last_it;
            }
            //Transfer nodes. This is constant time.
            backup_list.splice_after
               ( backup_list.before_begin()
               , free_nodes
               , free_nodes.before_begin()
               , last_it
               , free_nodes.size());
            bit = it;
            ++it;
         }
      }
      //We should have removed all the nodes from the free list
      assert(m_freelist.empty());

      //Now pass all the node to the free list again
      m_freelist.splice_after
         ( m_freelist.before_begin()
         , backup_list
         , backup_list.before_begin()
         , backup_list_last
         , backup_list.size());
   }

   std::size_t num_free_nodes()
   {  return m_freelist.size();  }

   //!Deallocates all used memory. Precondition: all nodes allocated from this pool should
   //!already be deallocated. Otherwise, undefined behaviour. Never throws
   void purge_blocks()
   {
      //check for memory leaks
      assert(m_allocated==0);
      std::size_t blocksize = detail::get_rounded_size
         (m_real_node_size*m_nodes_per_block, alignment_of<node_t>::value);
      typename blockslist_t::iterator
         it(m_blocklist.begin()), itend(m_blocklist.end()), aux;

      //We iterate though the NodeBlock list to free the memory
      while(!m_blocklist.empty()){
         void *addr = get_block_from_hook(&m_blocklist.front(), blocksize);
         m_blocklist.pop_front();
         mp_segment_mngr_base->deallocate((void*)addr);
      }
      //Just clear free node list
      m_freelist.clear();
   }

   void swap(private_node_pool_impl &other)
   {
      std::swap(mp_segment_mngr_base, other.mp_segment_mngr_base);
      m_blocklist.swap(other.m_blocklist);
      m_freelist.swap(other.m_freelist);
      std::swap(m_allocated, other.m_allocated);
   }

   private:

   struct push_in_list
   {
      push_in_list(free_nodes_t &l, typename free_nodes_t::iterator &it)
         :  slist_(l), last_it_(it)
      {}

      void operator()(typename free_nodes_t::pointer p) const
      {
         slist_.push_front(*p);
         if(slist_.size() == 1){ //Cache last element
            ++last_it_ = slist_.begin();
         }
      }

      private:
      free_nodes_t &slist_;
      typename free_nodes_t::iterator &last_it_;
   };

   struct is_between
      :  std::unary_function<typename free_nodes_t::value_type, bool>
   {
      is_between(const void *addr, std::size_t size)
         :  beg_(static_cast<const char *>(addr)), end_(beg_+size)
      {}

      bool operator()(typename free_nodes_t::const_reference v) const
      {
         return (beg_ <= reinterpret_cast<const char *>(&v) &&
                 end_ >  reinterpret_cast<const char *>(&v));
      }
      private:
      const char *      beg_;
      const char *      end_;
   };

   //!Allocates a block of nodes. Can throw boost::interprocess::bad_alloc
   void priv_alloc_block()
   {
      //We allocate a new NodeBlock and put it as first
      //element in the free Node list
      std::size_t blocksize =
         detail::get_rounded_size(m_real_node_size*m_nodes_per_block, alignment_of<node_t>::value);
      char *pNode = reinterpret_cast<char*>
         (mp_segment_mngr_base->allocate(blocksize + sizeof(node_t)));
      if(!pNode)  throw bad_alloc();
      char *pBlock = pNode;
      m_blocklist.push_front(get_block_hook(pBlock, blocksize));

      //We initialize all Nodes in Node Block to insert
      //them in the free Node list
      for(std::size_t i = 0; i < m_nodes_per_block; ++i, pNode += m_real_node_size){
         m_freelist.push_front(*new (pNode) node_t);
      }
   }

   //!Deprecated, use deallocate_free_blocks
   void deallocate_free_chunks()
   {  this->deallocate_free_blocks(); }

   //!Deprecated, use purge_blocks
   void purge_chunks()
   {  this->purge_blocks(); }

   private:
   //!Returns a reference to the block hook placed in the end of the block
   static node_t & get_block_hook (void *block, std::size_t blocksize)
   {
      return *reinterpret_cast<node_t*>(reinterpret_cast<char*>(block) + blocksize);
   }

   //!Returns the starting address of the block reference to the block hook placed in the end of the block
   void *get_block_from_hook (node_t *hook, std::size_t blocksize)
   {
      return (reinterpret_cast<char*>(hook) - blocksize);
   }

   private:
   typedef typename boost::pointer_to_other
      <void_pointer, segment_manager_base_type>::type   segment_mngr_base_ptr_t;

   const std::size_t m_nodes_per_block;
   const std::size_t m_real_node_size;
   segment_mngr_base_ptr_t mp_segment_mngr_base;   //Segment manager
   blockslist_t      m_blocklist;      //Intrusive container of blocks
   free_nodes_t      m_freelist;       //Intrusive container of free nods
   std::size_t       m_allocated;      //Used nodes for debugging
};

//!Pooled shared memory allocator using single segregated storage. Includes
//!a reference count but the class does not delete itself, this is
//!responsibility of user classes. Node size (NodeSize) and the number of
//!nodes allocated per block (NodesPerBlock) are known at compile time
template< class SegmentManager, std::size_t NodeSize, std::size_t NodesPerBlock >
class private_node_pool
   //Inherit from the implementation to avoid template bloat
   :  public private_node_pool_impl<typename SegmentManager::segment_manager_base_type>
{
   typedef private_node_pool_impl<typename SegmentManager::segment_manager_base_type> base_t;
   //Non-copyable
   private_node_pool();
   private_node_pool(const private_node_pool &);
   private_node_pool &operator=(const private_node_pool &);

   public:
   typedef SegmentManager segment_manager;

   static const std::size_t nodes_per_block = NodesPerBlock;
   //Deprecated, use nodes_per_block
   static const std::size_t nodes_per_chunk = NodesPerBlock;

   //!Constructor from a segment manager. Never throws
   private_node_pool(segment_manager *segment_mngr)
      :  base_t(segment_mngr, NodeSize, NodesPerBlock)
   {}

   //!Returns the segment manager. Never throws
   segment_manager* get_segment_manager() const
   {  return static_cast<segment_manager*>(base_t::get_segment_manager_base()); }
};

//!Pooled shared memory allocator using single segregated storage. Includes
//!a reference count but the class does not delete itself, this is
//!responsibility of user classes. Node size (NodeSize) and the number of
//!nodes allocated per block (NodesPerBlock) are known at compile time
//!Pooled shared memory allocator using adaptive pool. Includes
//!a reference count but the class does not delete itself, this is
//!responsibility of user classes. Node size (NodeSize) and the number of
//!nodes allocated per block (NodesPerBlock) are known at compile time
template< class SegmentManager
        , std::size_t NodeSize
        , std::size_t NodesPerBlock
        >
class shared_node_pool
   :  public detail::shared_pool_impl
      < private_node_pool
         <SegmentManager, NodeSize, NodesPerBlock>
      >
{
   typedef detail::shared_pool_impl
      < private_node_pool
         <SegmentManager, NodeSize, NodesPerBlock>
      > base_t;
   public:
   shared_node_pool(SegmentManager *segment_mgnr)
      : base_t(segment_mgnr)
   {}
};

}  //namespace detail {
}  //namespace interprocess {
}  //namespace boost {

#include <boost/interprocess/detail/config_end.hpp>

#endif   //#ifndef BOOST_INTERPROCESS_DETAIL_NODE_POOL_HPP


#include "generate_data/full_scene/generate_data.h"
#include "generate_data/baryocentric_to_ray.h"
#include "generate_data/full_scene/amend_config.h"
#include "generate_data/full_scene/constants.h"
#include "generate_data/full_scene/intersect_for_baryocentric_coords.h"
#include "generate_data/full_scene/scene_generator.h"
#include "generate_data/possibly_shadowed.h"
#include "generate_data/shadowed.h"
#include "generate_data/sort_triangle_points.h"
#include "generate_data/subset_to_multi.h"
#include "generate_data/to_tensor.h"
#include "generate_data/triangle.h"
#include "generate_data/triangle_subset_intersection.h"
#include "generate_data/triangle_subset_union.h"
#include "generate_data/value_adder.h"
#include "integrate/dir_sampler/uniform_direction_sample.h"
#include "lib/vector_type.h"
#include "meta/array_cat.h"
#include "render/renderer.h"
#include "rng/uniform/uniform.h"

#include <boost/multi_array.hpp>

#include "dbg.h"

namespace generate_data {
namespace full_scene {
static VectorT<render::Renderer> renderers;

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

template <bool is_image>
Out<is_image> generate_data_impl(int max_tris,
                                 std::optional<int> forced_n_scenes,
                                 int n_samples_per_tri_or_dim, int n_samples,
                                 int n_steps, unsigned seed) {
  VectorT<IntersectedBaryocentricCoords> intersected_coords;
  unsigned max_n_samples_per_scene = 0;
  VectorT<scene::Scene> scenes;
  VectorT<unsigned> mesh_threshs; // not used ATM
  SceneGenerator generator;
  int tri_count = 0;
  unsigned max_tri_count = 0;
  rng::uniform::Uniform<ExecutionModel::CPU>::Ref::State rng_state(seed);
  std::vector<unsigned> n_total_samples_per;
  while (forced_n_scenes.has_value()
             ? scenes.size() < unsigned(*forced_n_scenes)
             : tri_count < max_tris) {
    auto vals = generator.generate(rng_state.state());
    auto scene = std::get<0>(vals);
    unsigned n_tris = scene.triangles().size();
    int new_tri_count = tri_count + n_tris;

    // TODO: restrict tris somehow?

    tri_count = new_tri_count;
    max_tri_count = std::max(max_tri_count, n_tris);

    unsigned n_total_samples;
    if constexpr (is_image) {
      unsigned dim = n_samples_per_tri_or_dim;
      auto values = intersect_for_baryocentric_coords(scene, dim);
      n_total_samples = unsigned(values.image_indexes.size());
      intersected_coords.push_back(values);
    } else {
      unsigned n_samples_per_tri = n_samples_per_tri_or_dim;
      n_total_samples = n_samples_per_tri * n_tris;
    }
    max_n_samples_per_scene =
        std::max(max_n_samples_per_scene, n_total_samples);
    n_total_samples_per.push_back(n_total_samples);

    scenes.push_back(scene);
    mesh_threshs.push_back(std::get<1>(vals));
  }

  render::Settings settings;
  amend_config(settings);

  std::array<unsigned, 2> base_dims{unsigned(scenes.size()), max_tri_count};

  boost::multi_array<float, 3> triangle_features{
      array_append(base_dims, unsigned(constants.n_tri_values))};
  boost::multi_array<float, 3> bsdf_features{
      array_append(base_dims, unsigned(constants.n_bsdf_values))};
  boost::multi_array<float, 3> emissive_values{
      array_append(base_dims, unsigned(constants.n_rgb_dims))};
  boost::multi_array<bool, 2> mask{base_dims};

  std::array<unsigned, 2> base_sample_dims{unsigned(scenes.size()),
                                           max_n_samples_per_scene};

  boost::multi_array<float, 3> baryocentric_coords{array_append(
      base_sample_dims, unsigned(constants.n_coords_feature_values))};
  boost::multi_array<TorchIdxT, 2> triangle_idxs_for_coords{base_sample_dims};
  boost::multi_array<TorchIdxT, 3> image_indexes;
  if constexpr (is_image) {
    image_indexes.resize(array_append(base_sample_dims, 2u));
  }
  boost::multi_array<float, 4> values{
      std::array{unsigned(scenes.size()), unsigned(n_steps),
                 max_n_samples_per_scene, unsigned(constants.n_rgb_dims)}};
  for (unsigned scene_idx = 0; scene_idx < scenes.size(); ++scene_idx) {
    const auto &scene = scenes[scene_idx];
    VectorT<Triangle> tris(scene.triangles().size());

    Eigen::Vector3d avg = Eigen::Vector3d::Zero();
    Eigen::Vector3d min_pos = max_eigen_vec<double>();
    Eigen::Vector3d max_pos = min_eigen_vec<double>();

    for (unsigned i = 0; i < tris.size(); ++i) {
      tris[i] = scene.triangles()[i].template cast<double>();
      for (const auto &vert : tris[i].vertices) {
        avg += vert;
        min_pos = min_pos.cwiseMin(vert);
        max_pos = max_pos.cwiseMax(vert);
      }
    }

    avg /= tris.size() * 3;

    double scale =
        1. / std::max((avg - min_pos).maxCoeff(), (max_pos - avg).maxCoeff());
    auto scaling = Eigen::Scaling(scale);
    auto translate = Eigen::Translation3d(-avg);
    const Eigen::Affine3d transform = scaling * translate;

    for (unsigned i = 0; i < tris.size(); ++i) {
      auto &tri = tris[i];
      for (auto &vert : tri.vertices) {
        vert = transform * vert;
      }

      sort_triangle_points(tri);

      if (tri.normal_raw().z() < 0.f) {
        std::swap(tri.vertices[1], tri.vertices[2]);
      }

      auto tri_adder = make_value_adder([&](float v, int value_idx) {
        triangle_features[scene_idx][i][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);
      const auto normal = normal_scaled.normalized().eval();
      tri_adder.add_values(normal);
      tri_adder.add_remap_all_value(tri.area());

      debug_assert(tri_adder.idx == constants.n_tri_values);

      const auto &material =
          scene.materials()[scene.triangle_data()[i].material_idx()];

      make_value_adder([&](float v, int value_idx) {
        emissive_values[scene_idx][i][value_idx] = v;
      }).add_values(material.emission);

      FloatRGB diffuse_value = FloatRGB::Zero();
      FloatRGB glossy_value = FloatRGB::Zero();
      FloatRGB mirror_value = FloatRGB::Zero();
      FloatRGB dielectric_value = FloatRGB::Zero();
      float shininess = 40.;
      float ior = 1.6;

      material.bsdf.bsdf.visit_tagged([&](auto tag, const auto &value) {
        if constexpr (tag == bsdf::BSDFType::Diffuse) {
          diffuse_value = value.diffuse;
        } else if constexpr (tag == bsdf::BSDFType::Glossy) {
          glossy_value = value.specular();
          shininess = value.shininess();
        } else if constexpr (tag == bsdf::BSDFType::DiffuseGlossy) {
          float diffuse_weight = value.weight_continuous_inclusive()[0];
          float glossy_weight = 1 - diffuse_weight;
          diffuse_value =
              diffuse_weight * value.items()[boost::hana::int_c<0>].diffuse;
          glossy_value =
              glossy_weight * value.items()[boost::hana::int_c<1>].specular();
          shininess = value.items()[boost::hana::int_c<1>].shininess();
        } else if constexpr (tag == bsdf::BSDFType::DiffuseMirror) {
          unreachable_unchecked();
        } else if constexpr (tag == bsdf::BSDFType::Mirror) {
          mirror_value = value.specular;
        } else {
          static_assert(tag == bsdf::BSDFType::DielectricRefractive);
          dielectric_value = value.specular();
          ior = value.ior();
        }
      });

      auto bsdf_adder = make_value_adder([&](float v, int value_idx) {
        bsdf_features[scene_idx][i][value_idx] = v;
      });

      bsdf_adder.add_values(diffuse_value);
      bsdf_adder.add_values(glossy_value);
      bsdf_adder.add_values(mirror_value);
      bsdf_adder.add_values(dielectric_value);
      bsdf_adder.add_value(shininess);
      bsdf_adder.add_value(ior);

      debug_assert(bsdf_adder.idx == constants.n_bsdf_values);
    }

    for (unsigned i = 0; i < max_tri_count; ++i) {
      mask[scene_idx][i] = i >= tris.size();
    }

    unsigned n_samples_this_scene = [&] {
      if constexpr (is_image) {
        return intersected_coords[scene_idx].coords.size();
      } else {
        unsigned n_samples_per_tri = n_samples_per_tri_or_dim;
        return tris.size() * n_samples_per_tri;
      }
    }();

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

    auto add_value = [&](unsigned i, float s, float t, unsigned tri_idx,
                         const UnitVector &dir) {
      const auto &tri = tris[tri_idx];
      idxs_and_dirs[i] = {
          .idx = tri_idx,
          .ray = baryocentric_to_ray(s, t, tri.template cast<float>(), dir),
      };
      triangle_idxs_for_coords[scene_idx][i] = tri_idx;

      auto baryo_addr = make_value_adder([&](float v, int value_idx) {
        baryocentric_coords[scene_idx][i][value_idx] = v;
      });

      baryo_addr.add_value(s);
      baryo_addr.add_value(t);
      const Eigen::Vector3d vec0 = tri.vertices[1] - tri.vertices[0];
      const Eigen::Vector3d vec1 = tri.vertices[2] - tri.vertices[0];

      const Eigen::Vector3d double_dir = dir->template cast<double>();
      Eigen::Vector3d dir_tri_space{tri.normal()->dot(double_dir),
                                    vec0.dot(double_dir) / vec0.norm(),
                                    vec1.dot(double_dir) / vec1.norm()};
      dir_tri_space.normalize();
      baryo_addr.add_values(dir_tri_space);
    };
    if constexpr (is_image) {
      const auto &item = intersected_coords[scene_idx];
      for (unsigned i = 0; i < item.coords.size(); ++i) {
        auto [s, t] = item.coords[i];
        add_value(i, s, t, item.tri_idxs[i], item.directions[i]);
      }
      for (unsigned i = 0; i < item.image_indexes.size(); ++i) {
        auto [x, y] = item.image_indexes[i];
        image_indexes[scene_idx][i][0] = TorchIdxT(x);
        image_indexes[scene_idx][i][1] = TorchIdxT(y);
      }
    } else {
      unsigned n_samples_per_tri = n_samples_per_tri_or_dim;
      unsigned i = 0;
      for (unsigned tri_idx = 0; tri_idx < tris.size(); ++tri_idx) {
        for (unsigned sample_idx = 0; sample_idx < n_samples_per_tri;
             ++sample_idx, ++i) {
          auto [s, t] = integrate::uniform_baryocentric(rng_state);
          auto direction = integrate::dir_sampler::uniform_direction_sample(
              rng_state, UnitVector::new_unchecked(Eigen::Vector3f::UnitX()),
              true);
          add_value(i, s, t, tri_idx, direction);
        }
      }
    }

    VectorT<VectorT<FloatRGB>> step_outputs(
        n_steps, VectorT<FloatRGB>{n_samples_this_scene});
    VectorT<Span<FloatRGB>> outputs(step_outputs.begin(), step_outputs.end());

    if (n_samples_this_scene > 0) {
      renderers.resize(1);
      renderers[0].render(
          ExecutionModel::GPU,
          {tag_v<render::SampleSpecType::InitialIdxAndDir>, idxs_and_dirs},
          {tag_v<render::OutputType::OutputPerStep>, outputs}, scene, n_samples,
          settings, false);
      for (unsigned j = 0; j < unsigned(n_steps); ++j) {
        for (unsigned k = 0; k < n_samples_this_scene; ++k) {
          for (unsigned l = 0; l < 3; ++l) {
            values[scene_idx][j][k][l] = outputs[j][k][l];
          }
        }
      }
    }
  }

  StandardData<NetworkInputs> out{
      .inputs =
          {
              .triangle_features = to_tensor(triangle_features),
              .mask = to_tensor(mask),
              .bsdf_features = to_tensor(bsdf_features),
              .emissive_values = to_tensor(emissive_values),
              .baryocentric_coords = to_tensor(baryocentric_coords),
              .triangle_idxs_for_coords = to_tensor(triangle_idxs_for_coords),
              .total_tri_count = unsigned(tri_count),
              .n_samples_per = n_total_samples_per,
          },

      .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 max_tris,
                                          std::optional<int> forced_n_scenes,
                                          int n_samples_per_scene,
                                          int n_samples, int n_steps,
                                          unsigned base_seed) {
  return generate_data_impl<false>(max_tris, forced_n_scenes,
                                   n_samples_per_scene, n_samples, n_steps,
                                   base_seed);
}

ImageData<NetworkInputs>
generate_data_for_image(int max_tris, std::optional<int> forced_n_scenes,
                        int dim, int n_samples, int n_steps,
                        unsigned base_seed) {
  return generate_data_impl<true>(max_tris, forced_n_scenes, dim, n_samples,
                                  n_steps, base_seed);
}

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


/*
Copyright (c) 2019 Naomasa Matsubayashi (aka. Fadis)

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/

#include <array>
#include <vector>
#include <utility>
#include <boost/math/common_factor_rt.hpp>
#include <glm/vec4.hpp>
#include <liblnn/layer_def.h>
#include <liblnn/descriptor_set.h>
#include <liblnn/pipeline_layout.h>
#include <liblnn/exceptions.h>
#include <liblnn/pipeline.h>
namespace liblnn {
  layer create_init_pipeline(
    const std::shared_ptr< vk::Device > &device,
    const modules &mods,
    const std::shared_ptr< vk::DescriptorPool > &descriptor_pool,
    const std::shared_ptr< vk::PipelineCache > &pipeline_cache,
    const device_props &props,
    const buffer_view< glm::vec4 > &weight,
    uint32_t input_size
  ) {
    const std::vector< vk::DescriptorSetLayoutBinding > descriptor_set_layout_bindings{
      vk::DescriptorSetLayoutBinding()
        .setDescriptorType( vk::DescriptorType::eStorageBuffer )
        .setDescriptorCount( 1 )
        .setBinding( 2 )
        .setStageFlags( vk::ShaderStageFlagBits::eCompute )
        .setPImmutableSamplers( nullptr )
    };
    const uint32_t size = weight.size();
    uint32_t width = ( size > props.props.limits.maxComputeWorkGroupCount[ 0 ] ) ? boost::math::gcd( size, props.props.limits.maxComputeWorkGroupCount[ 0 ] ) : size;
    uint32_t local_group_size = boost::math::gcd( width, props.subgroup_props.subgroupSize );
    uint32_t height = size / width;
    if( height > props.props.limits.maxComputeWorkGroupCount[ 1 ] ) throw too_large_data();
    auto [descriptor_set,descriptor_set_layout] = get_descriptor_set( device, descriptor_pool, descriptor_set_layout_bindings );
    std::vector< vk::PushConstantRange > push_constant_range{
      vk::PushConstantRange()
       .setStageFlags( vk::ShaderStageFlagBits::eCompute )
       .setOffset( 0 )
       .setSize( 8 )
    };
    auto pipeline_layout = get_pipeline_layout( device, descriptor_set_layout, push_constant_range );
    std::array< uint32_t, 3 > spec_data{ local_group_size, 1, input_size };
    std::array< vk::SpecializationMapEntry, 3 > spec_ent {
      vk::SpecializationMapEntry()
        .setConstantID( 1 )
        .setOffset( 0 )
        .setSize( 4 ),
      vk::SpecializationMapEntry()
        .setConstantID( 2 )
        .setOffset( 4 )
        .setSize( 4 ),
      vk::SpecializationMapEntry()
        .setConstantID( 3 )
        .setOffset( 8 )
        .setSize( 4 )
    };
    auto spec = vk::SpecializationInfo()
      .setMapEntryCount( spec_ent.size() )
      .setPMapEntries( spec_ent.data() )
      .setDataSize( spec_data.size() )
      .setPData( spec_data.data() );
    auto pipelines = device->createComputePipelines(
      *pipeline_cache,
      std::vector< vk::ComputePipelineCreateInfo >{
        vk::ComputePipelineCreateInfo()
    .setStage(
      vk::PipelineShaderStageCreateInfo()
        .setStage( vk::ShaderStageFlagBits::eCompute )
        .setModule( *mods.init )
        .setPName( "main" )
    .setPSpecializationInfo( &spec )
    )
    .setLayout( *pipeline_layout )
      }
    );
    std::shared_ptr< vk::Pipeline > pipeline(
      new vk::Pipeline( std::move( pipelines[ 0 ] ) ),
      [device,pipeline_cache,module=mods.init,pipeline_layout]( vk::Pipeline *p ) {
        if( p ) device->destroyPipeline( *p );
        delete p;
      }
    );

    auto weight_dbi = vk::DescriptorBufferInfo()
      .setBuffer( weight.get() )
      .setOffset( weight.offset() * sizeof( glm::vec4 ) )
      .setRange( weight.size() * sizeof( glm::vec4 ) );
    device->updateDescriptorSets(
      std::vector< vk::WriteDescriptorSet >{
         vk::WriteDescriptorSet()
           .setDstSet( *descriptor_set )
           .setDstBinding( 2 )
           .setDescriptorType( vk::DescriptorType::eStorageBuffer )
           .setDescriptorCount( 1 )
           .setPBufferInfo( &weight_dbi )
      },
      nullptr
    );
    return layer( layer_def()
      .set_weight( weight )
      .set_descriptor_set( descriptor_set )
      .set_pipeline( pipeline )
      .set_descriptor_set_layout( descriptor_set_layout )
      .set_pipeline_layout( pipeline_layout )
      .set_dispatch_size( width / local_group_size, height, 1 ) );
  }
}


/*
 * Copyright (c) 2020, Robobrain.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 *     * Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 *     * Neither the name of the Willow Garage, Inc. nor the names of its
 *       contributors may be used to endorse or promote products derived from
 *       this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

/* Author: Konstantinos Konstantinidis */

#pragma once

#include "kalman-cpp/kalman.hpp"
#include <Eigen/Dense>
#include <ros/console.h>

using namespace Eigen;

typedef std::pair<double, double> Point;
typedef Eigen::Matrix<double, 6, 1> Vector6d;

const double pi = 3.141592653589793238463;

class LshapeTracker {
public:

  LshapeTracker(const double& x_corner, const double& y_corner, const double& L1, const double& L2, const double& theta, const double& dt);
  LshapeTracker();
  void update(const double& thetaL1, const double& x_corner, const double& y_corner, const double& L1, const double& L2, const double& dt, const int cluster_size);
  void BoxModel(double& x, double& y,double& vx, double& vy,double& theta, double& psi, double& omega, double& L1, double& L2, double& length, double& width);

private:
  int current_size;
  double test1, test2, test3;
  double x_old, y_old, L1_old, L2_old, old_thetaL1;

  KalmanFilter shape_kf;
  KalmanFilter dynamic_kf;

  void ClockwisePointSwitch();
  void CounterClockwisePointSwitch();
  double findTurn(const double& new_angle, const double& old_angle);
  void detectCornerPointSwitch(const double& from, const double& to, const double dt);
  void detectCornerPointSwitchMahalanobis(const double& from, const double& to, const double dt);
  void detectCornerPointSwitchMahalanobis(const double& from, const double& to, const double L1, const double L2, const double x_corner, const double y_corner);
  /*! \brief Finds orientations of tracked object
   *
   * Given the orientation of L1 the other three angles of the rectangle are calculated.
   * Then they are compared with the speed of the object, to estimate it's direction.
   *
   * \angle angle of one edge of the box
   * \vx velocity in the x axis
   * \vy velocity in the y axis
   * \orientation orientation of tracked object, based on it's speed
   */
  void findOrientation(double& psi, double& length, double& width);
};


#include <fc/crypto/openssl.hpp>

#include <fc/filesystem.hpp>

#include <boost/filesystem/path.hpp>

#include <cstdlib>
#include <string>
#include <stdlib.h>

#if OPENSSL_VERSION_NUMBER < 0x10100000L
void ECDSA_SIG_get0(const ECDSA_SIG *sig, const BIGNUM **pr, const BIGNUM **ps) {
   if (pr != NULL)
      *pr = sig->r;
   if (ps != NULL)
      *ps = sig->s;
 }

 int ECDSA_SIG_set0(ECDSA_SIG *sig, BIGNUM *r, BIGNUM *s) {
   if (r == NULL || s == NULL)
      return 0;
   BN_clear_free(sig->r);
   BN_clear_free(sig->s);
   sig->r = r;
   sig->s = s;
   return 1;
}
#endif

namespace  fc
{
    struct openssl_scope
    {
       static path& config_path() {
          static path cfg;
          return cfg;
       }
       openssl_scope()
       {
#if OPENSSL_VERSION_NUMBER < 0x10100000L
          ERR_load_crypto_strings();
          OpenSSL_add_all_algorithms();
#endif
          const boost::filesystem::path& boostPath = config_path();
          if(boostPath.empty() == false)
          {
            std::string varSetting("OPENSSL_CONF=");
            varSetting += config_path().to_native_ansi_path();
#if defined(WIN32)
            _putenv((char*)varSetting.c_str());
#else
            putenv((char*)varSetting.c_str());
#endif
          }

#if OPENSSL_VERSION_NUMBER < 0x10100000L
 	       OPENSSL_config(nullptr);
#else
	       OPENSSL_init_crypto(OPENSSL_INIT_LOAD_CONFIG, NULL);
#endif
       }
#if OPENSSL_VERSION_NUMBER < 0x10100000L
       ~openssl_scope()
       {
          EVP_cleanup();
          ERR_free_strings();
       }
#endif
    };

    void store_configuration_path(const path& filePath)
    {
      openssl_scope::config_path() = filePath;
    }

    int init_openssl()
    {
      static openssl_scope ossl;
      return 0;
    }
}


//
// Copyright (c) 2016-2017 Vinnie Falco (vinnie dot falco at gmail dot com)
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
// Official repository: https://github.com/boostorg/beast
//

#ifndef BOOST_BEAST_TEST_FUZZ_HPP
#define BOOST_BEAST_TEST_FUZZ_HPP

#include <boost/beast/core/static_string.hpp>
#include <boost/beast/core/string.hpp>
#include <random>

namespace boost {
namespace beast {
namespace test {

class fuzz_rand
{
    std::mt19937 rng_;

public:
    std::mt19937&
    rng()
    {
        return rng_;
    }

    template<class Unsigned>
    Unsigned
    operator()(Unsigned n)
    {
        return static_cast<Unsigned>(
            std::uniform_int_distribution<
                Unsigned>{0, n-1}(rng_));
    }
};

template<std::size_t N, class Rand, class F>
static
void
fuzz(
    static_string<N> const& input,
    std::size_t repeat,
    std::size_t depth,
    Rand& r,
    F const& f)
{
    static_string<N> mod{input};
    for(auto i = repeat; i; --i)
    {
        switch(r(4))
        {
        case 0: // insert
            if(mod.size() >= mod.max_size())
                continue;
            mod.insert(r(mod.size() + 1), 1,
                static_cast<char>(r(256)));
            break;

        case 1: // erase
            if(mod.size() == 0)
                continue;
            mod.erase(r(mod.size()), 1);
            break;

        case 2: // swap
        {
            if(mod.size() <= 1)
                continue;
            auto off = r(mod.size() - 1);
            auto const temp = mod[off];
            mod[off] = mod[off + 1];
            mod[off + 1] = temp;
            break;
        }
        case 3: // repeat
        {
            if(mod.empty())
                continue;
            auto n = (std::min)(
                std::geometric_distribution<
                    std::size_t>{}(r.rng()),
                mod.max_size() - mod.size());
            if(n == 0)
                continue;
            auto off = r(mod.size());
            mod.insert(off, n, mod[off + 1]);
            break;
        }
        }
        f(string_view{mod.data(), mod.size()});
        if(depth > 0)
            fuzz(mod, repeat, depth - 1, r, f);
    }
}

} // test
} // beast
} // boost

#endif


// Boost.Geometry

// Copyright (c) 2021, Oracle and/or its affiliates.

// Contributed and/or modified by Adam Wulkiewicz, on behalf of Oracle

// Licensed under the Boost Software License version 1.0.
// http://www.boost.org/users/license.html

#ifndef BOOST_GEOMETRY_STRATEGIES_AZIMUTH_SPHERICAL_HPP
#define BOOST_GEOMETRY_STRATEGIES_AZIMUTH_SPHERICAL_HPP

// TODO: move this file to boost/geometry/strategy
#include <boost/geometry/strategies/spherical/azimuth.hpp>

#include <boost/geometry/strategies/azimuth/services.hpp>
#include <boost/geometry/strategies/detail.hpp>

namespace boost { namespace geometry
{

namespace strategies { namespace azimuth
{

template <typename CalculationType = void>
class spherical : strategies::detail::spherical_base<void>
{
    using base_t = strategies::detail::spherical_base<void>;

public:

    static auto azimuth()
    {
        return strategy::azimuth::spherical<CalculationType>();
    }
};

namespace services
{

template <typename Point1, typename Point2>
struct default_strategy<Point1, Point2, spherical_equatorial_tag, spherical_equatorial_tag>
{
    using type = strategies::azimuth::spherical<>;
};

template <typename CT>
struct strategy_converter<strategy::azimuth::spherical<CT> >
{
    static auto get(strategy::azimuth::spherical<CT> const&)
    {
        return strategies::azimuth::spherical<CT>();
    }
};

} // namespace services

}} // namespace strategies::azimuth

}} // namespace boost::geometry

#endif // BOOST_GEOMETRY_STRATEGIES_AZIMUTH_SPHERICAL_HPP


// Copyright © 2017-2019 Trust Wallet.
//
// This file is part of Trust. The full Trust copyright notice, including
// terms governing use, modification, and redistribution, is contained in the
// file LICENSE at the root of the source code distribution tree.

#include "Signer.h"
#include "Serialization.h"

#include "../Hash.h"
#include "../HexCoding.h"
#include "../PrivateKey.h"
#include "../Data.h"
#include <boost/multiprecision/cpp_int.hpp>

#include <google/protobuf/io/coded_stream.h>
#include <google/protobuf/io/zero_copy_stream_impl_lite.h>
#include <string>

using namespace TW;
using namespace TW::Seele;

using json = nlohmann::json;
using uint128_t = boost::multiprecision::uint128_t;

Signer::Signer(Proto::SigningInput&& input) {
    this->input = input;
}

std::vector<uint8_t> Signer::sign() const {
    auto key = PrivateKey(input.private_key());
    auto hash = this->hash(input.sign_transaction());
    auto signature = key.sign(hash, TWCurveSECP256k1);

    return std::vector<uint8_t>(signature.begin(), signature.end());
}

std::string Signer::signaturePreimage() const {
    return signaturePreimageJSON(input).dump();
}

json Signer::buildTransactionJSON(const Data& signature) const {
    auto sig = Seele::Proto::Signature();
    sig.set_sig(signature.data(), signature.size());
    auto privateKey = PrivateKey(input.private_key());

    auto transaction = Seele::Proto::Transaction();
    auto hash = this->hash(input.sign_transaction());

    *transaction.mutable_data() = input.sign_transaction();
    *transaction.mutable_signature() = sig;
    transaction.set_hash("0x"+hex(hash));

    return transactionJSON(transaction);
}

std::string Signer::buildTransaction() const {
    auto signature = sign();
    return buildTransactionJSON(signature).dump();
}

Proto::SigningOutput Signer::build() const {
    auto output = Proto::SigningOutput();

    auto signature = sign();
    auto txJson = buildTransactionJSON(signature);

    output.set_json(txJson.dump());
    output.set_signature(signature.data(), signature.size());

    return output;
}

Data Signer::hash(const Proto::SignTransaction& transaction) const noexcept {
    auto encoded = Data();

    append(encoded, RLP::encodeLong(transaction.type()));
    append(encoded, RLP::encode(parse_hex(transaction.from())));
    append(encoded, RLP::encode(parse_hex(transaction.to())));
    append(encoded, RLP::encode(transaction.amount()));
    append(encoded, RLP::encode(transaction.account_nonce()));
    append(encoded, RLP::encodeLong(transaction.gas_price()));
    append(encoded, RLP::encodeLong(transaction.gas_limit()));
    append(encoded, RLP::encodeLong(transaction.timestamp()));
    append(encoded, RLP::encode(transaction.payload()));

    return Hash::keccak256(RLP::encodeList(encoded));
}


#ifndef BOOST_MPL_NEGATE_HPP_INCLUDED
#define BOOST_MPL_NEGATE_HPP_INCLUDED

// Copyright Aleksey Gurtovoy 2000-2004
//
// Distributed under the Boost Software License, Version 1.0.
// (See accompanying file LICENSE_1_0.txt or copy at
// http://www.boost.org/LICENSE_1_0.txt)
//
// See http://www.boost.org/libs/mpl for documentation.

// $Id: negate.hpp 49267 2008-10-11 06:19:02Z agurtovoy $
// $Date: 2008-10-11 08:19:02 +0200 (sam., 11 oct. 2008) $
// $Revision: 49267 $

#include <boost/mpl/integral_c.hpp>
#include <boost/mpl/aux_/msvc_eti_base.hpp>
#include <boost/mpl/aux_/na_spec.hpp>
#include <boost/mpl/aux_/lambda_support.hpp>
#include <boost/mpl/aux_/config/eti.hpp>
#include <boost/mpl/aux_/config/integral.hpp>
#include <boost/mpl/aux_/config/static_constant.hpp>

namespace boost { namespace mpl {

template< typename Tag > struct negate_impl;

template< typename T > struct negate_tag
{
    typedef typename T::tag type;
};

template<
      typename BOOST_MPL_AUX_NA_PARAM(N)
    >
struct negate
#if !defined(BOOST_MPL_CFG_MSVC_ETI_BUG)
    : negate_impl<
          typename negate_tag<N>::type
        >::template apply<N>::type
#else
    : aux::msvc_eti_base< typename apply_wrap1<
          negate_impl< typename negate_tag<N>::type >
        , N
        >::type >::type
#endif
{
    BOOST_MPL_AUX_LAMBDA_SUPPORT(1, negate, (N))
};

BOOST_MPL_AUX_NA_SPEC(1, negate)

#if defined(BOOST_MPL_CFG_NO_NESTED_VALUE_ARITHMETIC)
namespace aux {
template< typename T, T n > struct negate_wknd
{
    BOOST_STATIC_CONSTANT(T, value = -n);
    typedef integral_c<T,value> type;
};
}
#endif

template<>
struct negate_impl<integral_c_tag>
{
#if defined(BOOST_MPL_CFG_NO_NESTED_VALUE_ARITHMETIC)
    template< typename N > struct apply
        : aux::negate_wknd< typename N::value_type, N::value >
#else
    template< typename N > struct apply
        : integral_c< typename N::value_type, (-N::value) >
#endif
    {
    };
};

}}

#endif // BOOST_MPL_NEGATE_HPP_INCLUDED