// Boost.Geometry (aka GGL, Generic Geometry Library)

// Copyright (c) 2013 Barend Gehrels, Amsterdam, the Netherlands.
// Copyright (c) 2013 Bruno Lalande, Paris, France.
// Copyright (c) 2013 Mateusz Loskot, London, UK.
// Copyright (c) 2013 Adam Wulkiewicz, Lodz, Poland.

// Use, modification and distribution is subject to 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)

#ifndef BOOST_GEOMETRY_ALGORITHMS_DETAIL_RECALCULATE_HPP
#define BOOST_GEOMETRY_ALGORITHMS_DETAIL_RECALCULATE_HPP

#include <cstddef>

#include <boost/concept/requires.hpp>
#include <boost/concept_check.hpp>
#include <boost/mpl/assert.hpp>
#include <boost/mpl/if.hpp>
#include <boost/numeric/conversion/bounds.hpp>
#include <boost/numeric/conversion/cast.hpp>
#include <boost/range/begin.hpp>
#include <boost/range/end.hpp>
#include <boost/range/iterator.hpp>
#include <boost/range/size.hpp>

#include <boost/geometry/arithmetic/arithmetic.hpp>
#include <boost/geometry/algorithms/append.hpp>
#include <boost/geometry/algorithms/clear.hpp>
#include <boost/geometry/core/access.hpp>
#include <boost/geometry/core/interior_rings.hpp>
#include <boost/geometry/core/exterior_ring.hpp>
#include <boost/geometry/core/tags.hpp>

#include <boost/geometry/geometries/concepts/check.hpp>

namespace boost { namespace geometry
{

#ifndef DOXYGEN_NO_DETAIL
namespace detail { namespace recalculate
{

template <std::size_t Dimension>
struct recalculate_point
{
    template <typename Point1, typename Point2, typename Strategy>
    static inline void apply(Point1& point1, Point2 const& point2, Strategy const& strategy)
    {
        std::size_t const dim = Dimension - 1;
        geometry::set<dim>(point1, strategy.template apply<dim>(geometry::get<dim>(point2)));
        recalculate_point<dim>::apply(point1, point2, strategy);
    }
};

template <>
struct recalculate_point<0>
{
    template <typename Point1, typename Point2, typename Strategy>
    static inline void apply(Point1&, Point2 const&, Strategy const&)
    {
    }
};

template <std::size_t Dimension>
struct recalculate_indexed
{
    template <typename Geometry1, typename Geometry2, typename Strategy>
    static inline void apply(Geometry1& geometry1, Geometry2 const& geometry2, Strategy const& strategy)
    {
        // Do it for both indices in one dimension
        static std::size_t const dim = Dimension - 1;
        geometry::set<0, dim>(geometry1, strategy.template apply<dim>(geometry::get<0, dim>(geometry2)));
        geometry::set<1, dim>(geometry1, strategy.template apply<dim>(geometry::get<1, dim>(geometry2)));
        recalculate_indexed<dim>::apply(geometry1, geometry2, strategy);
    }
};

template <>
struct recalculate_indexed<0>
{

    template <typename Geometry1, typename Geometry2, typename Strategy>
    static inline void apply(Geometry1& , Geometry2 const& , Strategy const& )
    {
    }
};

struct range_to_range
{
    template
    <
        typename Range1,
        typename Range2,
        typename Strategy
    >
    static inline void apply(Range1& destination, Range2 const& source,
            Strategy const& strategy)
    {
        typedef typename geometry::point_type<Range2>::type point_type;
        typedef recalculate_point<geometry::dimension<point_type>::value> per_point;
        geometry::clear(destination);

        for (typename boost::range_iterator<Range2 const>::type it
                = boost::begin(source);
            it != boost::end(source);
            ++it)
        {
            point_type p;
            per_point::apply(p, *it, strategy);
            geometry::append(destination, p);
        }
    }
};

struct polygon_to_polygon
{
private:
    template
    <
        typename IteratorIn,
        typename IteratorOut,
        typename Strategy
    >
    static inline void iterate(IteratorIn begin, IteratorIn end,
                    IteratorOut it_out,
                    Strategy const& strategy)
    {
        for (IteratorIn it_in = begin; it_in != end;  ++it_in, ++it_out)
        {
            range_to_range::apply(*it_out, *it_in, strategy);
        }
    }

    template
    <
        typename InteriorRingsOut,
        typename InteriorRingsIn,
        typename Strategy
    >
    static inline void apply_interior_rings(
                    InteriorRingsOut& interior_rings_out,
                    InteriorRingsIn const& interior_rings_in,
                    Strategy const& strategy)
    {
        traits::resize<InteriorRingsOut>::apply(interior_rings_out,
            boost::size(interior_rings_in));

        iterate(
            boost::begin(interior_rings_in), boost::end(interior_rings_in),
            boost::begin(interior_rings_out),
            strategy);
    }

public:
    template
    <
        typename Polygon1,
        typename Polygon2,
        typename Strategy
    >
    static inline void apply(Polygon1& destination, Polygon2 const& source,
            Strategy const& strategy)
    {
        range_to_range::apply(geometry::exterior_ring(destination),
            geometry::exterior_ring(source), strategy);

        apply_interior_rings(geometry::interior_rings(destination),
            geometry::interior_rings(source), strategy);
    }
};

}} // namespace detail::recalculate
#endif // DOXYGEN_NO_DETAIL

#ifndef DOXYGEN_NO_DISPATCH
namespace dispatch
{

template
<
    typename Geometry1,
    typename Geometry2,
    typename Tag1 = typename geometry::tag<Geometry1>::type,
    typename Tag2 = typename geometry::tag<Geometry2>::type
>
struct recalculate : not_implemented<Tag1, Tag2>
{};

template <typename Point1, typename Point2>
struct recalculate<Point1, Point2, point_tag, point_tag>
    : detail::recalculate::recalculate_point<geometry::dimension<Point1>::value>
{};

template <typename Box1, typename Box2>
struct recalculate<Box1, Box2, box_tag, box_tag>
    : detail::recalculate::recalculate_indexed<geometry::dimension<Box1>::value>
{};

template <typename Segment1, typename Segment2>
struct recalculate<Segment1, Segment2, segment_tag, segment_tag>
    : detail::recalculate::recalculate_indexed<geometry::dimension<Segment1>::value>
{};

template <typename Polygon1, typename Polygon2>
struct recalculate<Polygon1, Polygon2, polygon_tag, polygon_tag>
    : detail::recalculate::polygon_to_polygon
{};

} // namespace dispatch
#endif // DOXYGEN_NO_DISPATCH

template <typename Geometry1, typename Geometry2, typename Strategy>
inline void recalculate(Geometry1& geometry1, Geometry2 const& geometry2, Strategy const& strategy)
{
    concepts::check<Geometry1>();
    concepts::check<Geometry2 const>();

    // static assert dimensions (/types) are the same

    dispatch::recalculate<Geometry1, Geometry2>::apply(geometry1, geometry2, strategy);
}

}} // namespace boost::geometry

#endif // BOOST_GEOMETRY_ALGORITHMS_DETAIL_RECALCULATE_HPP


//==================================================================================================
/*!

  Copyright 2015 NumScale SAS

  Distributed under the Boost Software License, Version 1.0.
  (See accompanying file LICENSE.md or copy at http://boost.org/LICENSE_1_0.txt)
*/
//==================================================================================================
#include <boost/simd/constant/maxleftshift.hpp>
#include <boost/simd/as.hpp>
#include <simd_test.hpp>
#include <boost/dispatch/meta/as_integer.hpp>

STF_CASE_TPL( "Check maxleftshift behavior for integral types"
            , (std::uint8_t)(std::uint16_t)(std::uint32_t)(std::uint64_t)
              (std::int8_t)(std::int16_t)(std::int32_t)(std::int64_t)
              (double)(float)
            )
{
  using boost::simd::as;
  using boost::simd::detail::maxleftshift;
  using boost::simd::Maxleftshift;
  using int_t = typename boost::dispatch::as_integer<T>::type;
  STF_TYPE_IS(decltype(Maxleftshift<T>()), int_t);
  STF_EQUAL(Maxleftshift<T>(), (int_t)(sizeof(T)*8-1));
  STF_EQUAL(maxleftshift( as(T{}) ),(int_t)(sizeof(T)*8-1));
}


#include "mview.h"
#include "LoaderEXR.h"
#include <iostream>
#include <fstream>
#include <Eigen/Dense>
#include <opencv2/core.hpp>
#include <opencv2/core/eigen.hpp>
#include <opencv2/imgcodecs.hpp>

static std::pair<GrayImage, RgbImage> ReadImageFromFile(std::string);
static float ColourToGray(Eigen::Vector3f rgb);
static RgbImage convertOpenCVToRgb(cv::Mat rgbMat);

static const std::string data_directory = "data/output640x480/";

std::string dataset_file() {
	return data_directory + "parameter.txt";
}

std::vector<CameraParameter> read_dataset(std::istream& parameter) {
	std::vector<CameraParameter> output;
	std::string line;

	for(int i = 0; std::getline(parameter, line); i++) {
		CameraParameter camera;
		std::ifstream matrix_file(line+".dat");

		camera.extrinsics.setIdentity();
		for(int j = 0; j < 4; j++)
			for(int i = 0; i < 3; i++)
				matrix_file >> camera.extrinsics(i, j);
		for(int j = 0; j < 3; j++)
			camera.extrinsics.col(j).normalize();
		// camera.extrinsics = camera.extrinsics.inverse().eval();
		camera.filename = line + ".jpg";
		camera.ground_truth = line + ".exr";
		output.push_back(camera);
	}

	return output;
}

Image read_image(CameraParameter cameraParameter) {
	Image image;
	std::tie(image.gray_pixels, image.rgb_pixels) = ReadImageFromFile(cameraParameter.filename);
	image.extrinsics = cameraParameter.extrinsics;

	image.ground_truth = GrayImage(image.gray_pixels.rows(), image.gray_pixels.cols());
	read_openexr(cameraParameter.ground_truth, image.ground_truth.data(),
		image.gray_pixels.cols(), image.gray_pixels.rows(), 1);

	/*
	INTRINSIC:

	float fx = IMAGE_WIDTH*4.1/4.54, fy = IMAGE_HEIGHT*4.1/3.42,
	cx = IMAGE_WIDTH/2.0, cy = IMAGE_HEIGHT/2.0;
	*/
	const float image_width = image.gray_pixels.cols();
	const float image_height = image.gray_pixels.rows();

	std::cout << image_width << "x" << image_height << "\n";
	image.intrinsics.setIdentity();
	image.intrinsics(0, 0) = image_width*4.1/4.54;
	image.intrinsics(1, 1) = image_height*4.1/3.42;
	image.intrinsics(0, 2) = image_width/2.;
	image.intrinsics(1, 2) = image_height/2.;
	std::cout << image.intrinsics << "\n";

	return image;
}

std::pair<GrayImage, RgbImage> ReadImageFromFile(std::string filename) {
	std::cout << filename << std::endl;
	cv::Mat rgb_mat = cv::imread(filename, cv::IMREAD_COLOR);
	rgb_mat.assignTo(rgb_mat, CV_32F);
	rgb_mat /= 255.;

	RgbImage rgb_target = convertOpenCVToRgb(rgb_mat);
	GrayImage gray_target = rgb_target.unaryExpr([](auto pixel) { return ColourToGray(pixel); });

	return {gray_target, rgb_target};
}

float ColourToGray(Eigen::Vector3f rgb) {
	static constexpr float GAMMA = 2.2;

	const Eigen::Vector3f gamma_correct = Eigen::Array3f(rgb).pow(GAMMA);
	return gamma_correct.dot(Eigen::Vector3f { .2126, .7152, .0722 });
}

RgbImage convertOpenCVToRgb(const cv::Mat imageMat){
    cv::Mat bgr[3];
    cv::split(imageMat, bgr);

	int width = imageMat.cols;
	int height = imageMat.rows;
    GrayImage r(height, width), g(height, width), b(height, width);

    cv::cv2eigen(bgr[2], r);
    cv::cv2eigen(bgr[1], g);
    cv::cv2eigen(bgr[0], b);
	std::cout << "converted\n";

    RgbImage rgbImage (g.rows(), g.cols());

    for(int row=0;row<g.rows();row++){
        for(int col=0;col<g.cols();col++){
            rgbImage(row,col)<<r(row,col),g(row,col),b(row,col);
        }
    }

    return rgbImage;
}


#include <carma>
#include <armadillo>
#include <pybind11/pybind11.h>
#include <pybind11/numpy.h>

namespace py = pybind11;

void bind_test_is_f_contiguous(py::module& m) {
    m.def(
        "is_f_contiguous",
        [](const py::array_t<double>& arr) { return carma::is_f_contiguous(arr); },
        "Test is F contiguous");
}

py::array_t<double> test_mat_roundtrip(py::array_t<double>& arr) {
    // call function to be tested
    arma::Mat<double> M = carma::arr_to_mat<double>(std::move(arr));
    return carma::mat_to_arr(M);
}

void bind_test_mat_roundtrip(py::module& m) {
    m.def("mat_roundtrip", &test_mat_roundtrip, "Test mat_roundtrip");
}

PYBIND11_MODULE(integration_test_carma, m) {
    bind_test_is_f_contiguous(m);
    bind_test_mat_roundtrip(m);
}


#include "tcenterlinevectP.h"

// tcg includes
#include "tcg/tcg_numeric_ops.h"

// Boost includes
#include <boost/container/flat_map.hpp>
#include <boost/algorithm/minmax_element.hpp>

namespace boost_c = boost::container;

//==========================================================================

//*************************
//*    Colors handling    *
//*************************

//Riassunto: Nel caso di normali raster, i tratti di penna sono colorati con
//l'elemento della palette data maggiormente tendente al nero.
//Per le Toonz colormap abilitiamo una gestione piu' complessa, che tiene
//conto del colore dell'inchiostro specificato direttamente nell'immagine.

//Nello specifico:
//  a) I tratti di penna vengono rilevati in base al valore del campo *tone*
//     di un TPixleCM32, non in base alla luminosita' del colore.
//     (vv. Poligonizzazione)
//  b) Sulle centerline grezze viene costruito un insieme di 'punti di assaggio'
//     dell'immagine; gli id di inchiostro rilevati vengono assegnati
//     direttamente alla stroke: se si verifica un cambio nell'id del colore,
//     il punto di cambio del colore viene identificato e la centerline viene
//     spezzata li'.
//  c) Una volta identificati i colori delle stroke, le si ordina *prima*
//     di inserirle nella vector image di output, in base al colore dell'immagine
//     ai loro estremi (attualmente ordinamento solo parziale).

//--------------------------------------------------------------------------

TPixelCM32 pixel(const TRasterCM32 &ras, int x, int y)
{
	// Seems that raster access was not very much double-checked at the time
	// I wrote this. Too bad. Enforcing it now.

	return ras.pixels(tcrop(y, 0, ras.getLy() - 1))[tcrop(x, 0, ras.getLx() - 1)];
}

//--------------------------------------------------------------------------

T3DPointD firstInkChangePosition(
	const TRasterCM32P &ras, const T3DPointD &start, const T3DPointD &end,
	int threshold)
{
	double dist = norm(end - start);

	int sampleMax = tceil(dist), sampleCount = sampleMax + 1;
	double sampleMaxD = double(sampleMax);

	// Get first ink color
	int s, color = -1;

	for (s = 0; s != sampleCount; ++s) {
		T3DPointD p = tcg::numeric_ops::lerp(start, end, s / sampleMaxD);
		const TPixelCM32 &pix = pixel(*ras, p.x, p.y);

		if (pix.getTone() < threshold) {
			color = pix.getInk();
			break;
		}
	}

	// Get second color
	for (; s != sampleCount; ++s) {
		T3DPointD p = tcg::numeric_ops::lerp(start, end, s / sampleMaxD);
		const TPixelCM32 &pix = pixel(*ras, p.x, p.y);

		if (pix.getTone() < threshold && pix.getInk() != color)
			break;
	}

	// Return middle position between s-1 and s
	if (s < sampleCount)
		return tcg::numeric_ops::lerp(start, end, (s - 0.5) / sampleMaxD);

	return TConsts::nap3d;
}

//------------------------------------------------------------------------

//Find color of input sequence. Will be copied to its equivalent stroke.
//Currently in use only on colormaps

//Riassunto: Per saggiare il colore da assegnare alle strokes e' meglio controllare
//le sequenze *prima* di convertirle in TStroke (visto che si perde parte dell'aderenza originale
//al tratto). Si specifica un numero di 'punti di assaggio' della spezzata equidistanti tra loro,
//su cui viene prelevato il valore dell'ink del pixel corrispondente. Se si identifica un cambio
//di colore, viene lanciata la procedura di spezzamento della sequenza: si identifica il punto
//di spezzamento, e la sequenza s viene bloccata li'; si costruisce una nuova sequenza newSeq e
//viene rilanciata sampleColor(ras,newSeq,sOpposite). Le sequenze tra due punti di spezzamento
//vengono inserite nel vector 'globals->singleSequences'.
//Nel caso di sequenze circolari c'e' una piccola modifica: il primo punto di spezzamento
//*ridefinisce solo* il nodo-raccordo di s, senza introdurre nuove sequenze.
//La sequenza sOpposite, 'inversa' di s, rimane e diventa 'forward-oriented' previo aggiornamento
//della coda.
//Osservare che i nodi di spezzamento vengono inseriti con la signature 'SAMPLECOLOR_SIGN'.
//NOTA: La struttura a grafo J-S 'superiore' non viene alterata qui dentro. Eventualm. da fare fuori.

void sampleColor(const TRasterCM32P &ras, int threshold, Sequence &seq, Sequence &seqOpposite,
				 SequenceList &singleSequences)
{
	SkeletonGraph *currGraph = seq.m_graphHolder;

	// Calculate sequence parametrization
	std::vector<unsigned int> nodes;
	std::vector<double> params;

	// Meanwhile, ensure each point belong to ras. Otherwise, typically an error occured
	// in the thinning process and it's better avoid sampling procedure. Only exception, when
	// a point has x==ras->getLx() || y==ras->getLy(); that is accepted.
	{
		const T3DPointD &headPos = *currGraph->getNode(seq.m_head);

		if (!ras->getBounds().contains(TPoint(headPos.x, headPos.y))) {
			if (headPos.x < 0 || ras->getLx() < headPos.x ||
				headPos.y < 0 || ras->getLy() < headPos.y)
				return;
		}
	}

	unsigned int curr, currLink, next;
	double meanThickness = currGraph->getNode(seq.m_head)->z;

	params.push_back(0);
	nodes.push_back(seq.m_head);

	for (curr = seq.m_head, currLink = seq.m_headLink;
		 curr != seq.m_tail || params.size() == 1;
		 seq.next(curr, currLink)) {
		next = currGraph->getNode(curr).getLink(currLink).getNext();

		const T3DPointD &nextPos = *currGraph->getNode(next);
		if (!ras->getBounds().contains(TPoint(nextPos.x, nextPos.y))) {
			if (nextPos.x < 0 || ras->getLx() < nextPos.x ||
				nextPos.y < 0 || ras->getLy() < nextPos.y)
				return;
		}

		params.push_back(params.back() + tdistance(*currGraph->getNode(next), *currGraph->getNode(curr)));
		nodes.push_back(next);

		meanThickness += currGraph->getNode(next)->z;
	}

	meanThickness /= params.size();

	// Exclude 0-length sequences
	if (params.back() < 0.01) {
		seq.m_color = pixel(*ras, currGraph->getNode(seq.m_head)->x,
							currGraph->getNode(seq.m_head)->y)
						  .getInk();
		return;
	}

	// Prepare sampling procedure
	int paramCount = params.size(),
		paramMax = paramCount - 1;

	int sampleMax = tmax(params.back() / tmax(meanThickness, 1.0), 3.0), // Number of color samples depends on
		sampleCount = sampleMax + 1;									 // the ratio params.back() / meanThickness

	std::vector<double> sampleParams(sampleCount); // Sampling lengths
	std::vector<TPoint> samplePoints(sampleCount); // Image points for color sampling
	std::vector<int> sampleSegments(sampleCount);  // Sequence segment index for the above

	// Sample colors
	for (int s = 0, j = 0; s != sampleCount; ++s) {
		double samplePar = params.back() * (s / double(sampleMax));

		while (j != paramMax && params[j + 1] < samplePar) // params[j] < samplePar <= params[j+1]
			++j;

		double t = (samplePar - params[j]) / (params[j + 1] - params[j]);

		T3DPointD samplePoint(*currGraph->getNode(nodes[j]) * (1 - t) + *currGraph->getNode(nodes[j + 1]) * t);

		sampleParams[s] = samplePar;
		samplePoints[s] = TPoint(tmin(samplePoint.x, double(ras->getLx() - 1)),  // This deals with sample points at
								 tmin(samplePoint.y, double(ras->getLy() - 1))); // the top/right raster border
		sampleSegments[s] = j;
	}

	// NOTE: Extremities of a sequence are considered unreliable: they typically happen
	//       to be junction points shared between possibly different-colored strokes.

	// Find first and last extremity-free sampled points
	T3DPointD first(*currGraph->getNode(seq.m_head));
	T3DPointD last(*currGraph->getNode(seq.m_tail));

	int i, k;

	for (i = 1; params.back() * i / double(sampleMax) <= first.z && i < sampleCount; ++i)
		;
	for (k = sampleMax - 1; params.back() * (sampleMax - k) / double(sampleMax) <= last.z && k >= 0; --k)
		;

	// Give s the first sampled ink color found

	// Initialize with a last-resort reasonable color - not just 0
	seq.m_color = seqOpposite.m_color =
		ras->pixels(samplePoints[0].y)[samplePoints[0].x].getInk();

	int l;

	for (l = i - 1; l >= 0; --l) {
		if (ras->pixels(samplePoints[l].y)[samplePoints[l].x].getTone() < threshold) {
			seq.m_color = seqOpposite.m_color =
				ras->pixels(samplePoints[l].y)[samplePoints[l].x].getInk();

			break;
		}
	}

	// Then, look for the first reliable ink
	for (l = i; l <= k; ++l) {
		if (ras->pixels(samplePoints[l].y)[samplePoints[l].x].getTone() < threshold) {
			seq.m_color = seqOpposite.m_color =
				ras->pixels(samplePoints[l].y)[samplePoints[l].x].getInk();

			break;
		}
	}

	if (i >= k)
		goto _getOut; // No admissible segment found for splitting
					  // check.
	// Find color changes between sampled colors
	for (l = i; l < k; ++l) {
		const TPixelCM32 &nextSample = ras->pixels(samplePoints[l + 1].y)[samplePoints[l + 1].x],
						 &nextSample2 = ras->pixels(samplePoints[l + 2].y)[samplePoints[l + 2].x]; // l < k < sampleMax - so +2 is ok

		if (nextSample.getTone() < threshold && nextSample.getInk() != seq.m_color && nextSample2.getTone() < threshold && nextSample2.getInk() == nextSample.getInk()) // Ignore single-sample color changes
		{
			// Found a color change - apply splitting procedure
			// NOTE: The function RETURNS BEFORE THE FOR IS CONTINUED!

			int nextColor = nextSample.getInk();

			// Identify split segment
			int u;

			for (u = sampleSegments[l]; u < sampleSegments[l + 1]; ++u) {
				const TPixelCM32 &pix = pixel(*ras, currGraph->getNode(nodes[u + 1])->x,
											  currGraph->getNode(nodes[u + 1])->y);
				if (pix.getTone() < threshold && pix.getInk() != seq.m_color)
					break;
			}

			// Now u indicates the splitting segment. Search for splitting point by binary subdivision.
			const T3DPointD &nodeStartPos = *currGraph->getNode(nodes[u]),
							&nodeEndPos = *currGraph->getNode(nodes[u + 1]);

			T3DPointD splitPoint = firstInkChangePosition(ras, nodeStartPos, nodeEndPos, threshold);

			if (splitPoint == TConsts::nap3d)
				splitPoint = 0.5 * (nodeStartPos + nodeEndPos); // A color change was found, but could
																// not be precisely located. Just take
																// a reasonable representant.
			// Insert a corresponding new node in basic graph structure.
			unsigned int splitNode = currGraph->newNode(splitPoint);

			unsigned int nodesLink = currGraph->getNode(nodes[u]).linkOfNode(nodes[u + 1]);
			currGraph->insert(splitNode, nodes[u], nodesLink);
			*currGraph->node(splitNode).link(0) = *currGraph->getNode(nodes[u]).getLink(nodesLink);

			nodesLink = currGraph->getNode(nodes[u + 1]).linkOfNode(nodes[u]);
			currGraph->insert(splitNode, nodes[u + 1], nodesLink);
			*currGraph->node(splitNode).link(1) = *currGraph->getNode(nodes[u + 1]).getLink(nodesLink);

			currGraph->node(splitNode).setAttribute(SAMPLECOLOR_SIGN); // Sign all split-inserted nodes

			if (seq.m_head == seq.m_tail && currGraph->getNode(seq.m_head).getLinksCount() == 2 && !currGraph->getNode(seq.m_head).hasAttribute(SAMPLECOLOR_SIGN)) {
				// Circular case: we update s to splitNode and relaunch this very procedure on it.
				seq.m_head = seq.m_tail = splitNode;
				sampleColor(ras, threshold, seq, seqOpposite, singleSequences);
			} else {
				// Update upper (Joint-Sequence) graph data
				Sequence newSeq;
				newSeq.m_graphHolder = currGraph;
				newSeq.m_head = splitNode;
				newSeq.m_headLink = 0;
				newSeq.m_tail = seq.m_tail;
				newSeq.m_tailLink = seq.m_tailLink;

				seq.m_tail = splitNode;
				seq.m_tailLink = 1; // (link from splitNode to nodes[u] inserted for second by 'insert')

				seqOpposite.m_graphHolder = seq.m_graphHolder; // Inform that a split was found

				// NOTE: access on s terminates at newSeq's push_back, due to possible reallocation of globals->singleSequences

				if ((!(seq.m_head == newSeq.m_tail && currGraph->getNode(seq.m_head).getLinksCount() == 2)) && currGraph->getNode(seq.m_head).hasAttribute(SAMPLECOLOR_SIGN))
					singleSequences.push_back(seq);

				sampleColor(ras, threshold, newSeq, seqOpposite, singleSequences);
			}

			return;
		}
	}

_getOut:

	// Color changes not found (and therefore no newSeq got pushed back); if a split happened, update sOpposite.
	if (currGraph->getNode(seq.m_head).hasAttribute(SAMPLECOLOR_SIGN)) {
		seqOpposite.m_color = seq.m_color;
		seqOpposite.m_head = seq.m_tail;
		seqOpposite.m_headLink = seq.m_tailLink;
		seqOpposite.m_tail = seq.m_head;
		seqOpposite.m_tailLink = seq.m_headLink;
	}
}

//--------------------------------------------------------------------------

//Take samples of image colors to associate each sequence to its corresponding
//palette color. Currently working on colormaps.
//void calculateSequenceColors(const TRasterP &ras)
void calculateSequenceColors(const TRasterP &ras, VectorizerCoreGlobals &g)
{
	int threshold = g.currConfig->m_threshold;
	SequenceList &singleSequences = g.singleSequences;
	JointSequenceGraphList &organizedGraphs = g.organizedGraphs;

	TRasterCM32P cm = ras;
	unsigned int i, j, k;
	int l;

	if (cm && g.currConfig->m_maxThickness > 0.0) {
		//singleSequence is traversed back-to-front because new, possibly splitted sequences
		//are inserted at back - and don't have to be re-sampled.
		for (l = singleSequences.size() - 1; l >= 0; --l) {
			Sequence rear;
			sampleColor(ras, threshold, singleSequences[l], rear, singleSequences);
			//If rear is built, a split occurred and the rear of this
			//single sequence has to be pushed back.
			if (rear.m_graphHolder)
				singleSequences.push_back(rear);
		}

		for (i = 0; i < organizedGraphs.size(); ++i)
			for (j = 0; j < organizedGraphs[i].getNodesCount(); ++j)
				if (!organizedGraphs[i].getNode(j).hasAttribute(JointSequenceGraph::ELIMINATED)) //due to junction recovery
					for (k = 0; k < organizedGraphs[i].getNode(j).getLinksCount(); ++k) {
						Sequence &s = *organizedGraphs[i].node(j).link(k);
						if (s.isForward() && !s.m_graphHolder->getNode(s.m_tail).hasAttribute(SAMPLECOLOR_SIGN)) {
							unsigned int next = organizedGraphs[i].node(j).link(k).getNext();
							unsigned int nextLink = organizedGraphs[i].tailLinkOf(j, k);

							Sequence &sOpposite = *organizedGraphs[i].node(next).link(nextLink);
							sampleColor(cm, threshold, s, sOpposite, singleSequences);
						}
					}
	}
}

//==========================================================================

inline void applyStrokeIndices(VectorizerCoreGlobals *globals)
{
	unsigned int i, j, k, n;
	unsigned int next, nextLink;

	for (i = 0; i < globals->singleSequences.size(); ++i)
		globals->singleSequences[i].m_strokeIndex = i;

	n = i;

	for (i = 0; i < globals->organizedGraphs.size(); ++i) {
		JointSequenceGraph *currJSGraph = &globals->organizedGraphs[i];
		for (j = 0; j < currJSGraph->getNodesCount(); ++j)
			if (!currJSGraph->getNode(j).hasAttribute(JointSequenceGraph::ELIMINATED))
				for (k = 0; k < currJSGraph->getNode(j).getLinksCount(); ++k) {
					Sequence &s = *currJSGraph->node(j).link(k);
					if (s.isForward()) {
						s.m_strokeIndex = n;

						if (!s.m_graphHolder->getNode(s.m_tail).hasAttribute(SAMPLECOLOR_SIGN)) {
							next = currJSGraph->getNode(j).getLink(k).getNext();
							nextLink = currJSGraph->tailLinkOf(j, k);
							currJSGraph->node(next).link(nextLink)->m_strokeIndex = n;
						}

						++n;
					}
				}
	}
}

//==========================================================================

//Riassunto: Dato un grafo superiore, possiamo associare ad ogni nodo il colore
//del pixel associato a quel punto; se una sequenza e' nascosta, ha entrambi
// i nodi agli estremi di colore diverso, viceversa per sequenze esposte.
//Data una sequenza, a partire dai nodi superiori adiacenti possiamo stabilire un
//insieme di sequenze che gli stanno sotto, ed uno di seq. che gli stanno sopra.

//NOTA: Questo problema e' un caso particolare di 'graph labeling', di cui non
//ho ancora trovato soluzione. In rete qualcosa si trova...

//La seguente funzione fa qualcosa di piu' debole: ad ogni joint ed ogni Sequence
//viene assegnata una altezza (intero). Dato un Joint, le sequenze che lo hanno
//per estremo e che hanno lo stesso colore dell'immagine in quella posizione hanno
//un'altezza +1 rispetto al giunto, e viceversa altezza -1. Partendo da
//un giunto iniziale, quest'informazione viene propagata sul grafo; il problema
//sta ritornando ai giunti gia' percorsi...

//--------------------------------------------------------------------------

//Find predominant ink color in a circle of given radius and center
int getInkPredominance(const TRasterCM32P &ras, TPalette *palette, int x, int y, int radius, int threshold)
{
	int i, j;
	int mx, my, Mx, My;
	std::vector<int> inksFound(palette->getStyleCount());

	radius = tmin(radius, 7); //Restrict radius for a minimum significative neighbour

	mx = tmax(x - radius, 0);
	my = tmax(y - radius, 0);
	Mx = tmin(x + radius, ras->getLx() - 1);
	My = tmin(y + radius, ras->getLy() - 1);

	//Check square grid around (x,y)
	for (i = mx; i <= Mx; ++i)
		for (j = my; j <= My; ++j)
			if (sq(i) + sq(j) <= sq(radius) && ras->pixels(j)[i].getTone() < threshold) {
				//Update color table
				inksFound[ras->pixels(j)[i].getInk()] += 255 - ras->pixels(j)[i].getTone();
			}

	//return the most found ink
	int maxCount = 0, mostFound = 0;
	for (i = 0; i < (int)inksFound.size(); ++i)
		if (inksFound[i] > maxCount) {
			maxCount = inksFound[i];
			mostFound = i;
		}

	return mostFound;
}

//--------------------------------------------------------------------------

/*!
  \brief    Find the predominant color in sequences adjacent to the
            input graph node.
  \return   The predominant branch color if found, \p -1 otherwise.
*/
int getBranchPredominance(const TRasterCM32P &ras, TPalette *palette, JointSequenceGraph::Node &node)
{
	struct locals {
		static inline bool valueLess(
			const std::pair<int, int> &a, const std::pair<int, int> &b)
		{
			return (a.second < b.second);
		}
	};

	boost_c::flat_map<int, int> branchInksHistogram;

	UINT l, lCount = node.getLinksCount();

	for (l = 0; l != lCount; ++l) {
		int color = node.getLink(l)->m_color;
		if (color >= 0 && color <= palette->getStyleCount())
			++branchInksHistogram[color];
	}

	// Return the most found ink, or -1 if a predominance color could not be found
	if (branchInksHistogram.empty())
		return -1;

	typedef boost_c::flat_map<int, int>::iterator histo_it;

	const std::pair<histo_it, histo_it> &histoRange =
		boost::minmax_element(branchInksHistogram.begin(), branchInksHistogram.end(),
							  locals::valueLess);

	return (histoRange.first->second == histoRange.second->second) ? -1 : histoRange.second->first;
}

//--------------------------------------------------------------------------

//NOTA: Da implementare una versione in grado di ordinare *pienamente* la vector image.
void sortJS(JointSequenceGraph *js, std::vector<std::pair<int, TStroke *>> &toOrder,
			const TRasterCM32P &ras, TPalette *palette)
{
	enum { SORTED = 0x10 };

	std::vector<std::pair<unsigned int, int>> nodesToDo;
	unsigned int currNodeIdx, nextNodeIdx;
	int currColor, currHeight, nextColor, nextHeight;
	T3DPointD pD;
	TPoint p;

	SkeletonGraph *currGraph = js->getNode(0).getLink(0)->m_graphHolder;

	unsigned int n, nCount = js->getNodesCount();
	for (n = 0; n != nCount; ++n) {
		// Get the first non-ELIMINATED and non-already treated JS node
		if (!js->getNode(n).hasAttribute(JointSequenceGraph::ELIMINATED | SORTED)) {
			nodesToDo.push_back(std::make_pair(n, 0));

			while (!nodesToDo.empty()) {
				currNodeIdx = nodesToDo.back().first;
				currHeight = nodesToDo.back().second;
				nodesToDo.pop_back();

				JointSequenceGraph::Node &currNode = js->node(currNodeIdx);

				// Sign current node
				currNode.setAttribute(SORTED);

				// Initialize this node infos
				pD = *currGraph->getNode(currNode.getLink(0)->m_head);
				p = TPoint(pD.x, pD.y);

				if (!ras->getBounds().contains(p))
					continue;

				//currColor = getInkPredominance(ras, palette, p.x, p.y, (int) pD.z); //ras->pixels(p.y)[p.x].getInk();
				currColor = getBranchPredominance(ras, palette, currNode);
				if (currColor < 0)
					currColor = ras->pixels(p.y)[p.x].getInk();

				int l, lCount = currNode.getLinksCount();
				for (l = 0; l != lCount; ++l) {
					nextNodeIdx = currNode.getLink(l).getNext();
					Sequence &s = *currNode.link(l);

					// Check if outgoing sequence has current color (front) or not (back)
					toOrder[s.m_strokeIndex].first = (s.m_color == currColor) ? currHeight : currHeight - 1;

					if (!(currNode.getLink(l).getAccess() == SORTED)) {
						// Deal with this unchecked branch

						// If sequence was not split (due to color change)
						if (!currGraph->getNode(s.m_tail).hasAttribute(SAMPLECOLOR_SIGN)) {
							JointSequenceGraph::Node &nextNode = js->node(nextNodeIdx);

							// Then check nextNode
							pD = *currGraph->getNode(nextNode.getLink(0)->m_head);
							p = TPoint(pD.x, pD.y);

							if (!ras->getBounds().contains(p))
								continue;

							// If nextNode was not already inserted in ToDo vector, do it now.
							if (!nextNode.hasAttribute(SORTED)) {
								//nextColor = getInkPredominance(ras, palette, p.x, p.y, (int) pD.z);
								nextColor = getBranchPredominance(ras, palette, nextNode);
								if (nextColor < 0)
									nextColor = ras->pixels(p.y)[p.x].getInk();

								nextHeight = (s.m_color == nextColor) ? toOrder[s.m_strokeIndex].first : toOrder[s.m_strokeIndex].first + 1;

								nodesToDo.push_back(std::make_pair(nextNodeIdx, nextHeight));
							}

							// Deny access to its inverse (already processed now)
							nextNode.link(js->tailLinkOf(currNodeIdx, l)).setAccess(SORTED);
						}
					}
				}
			}
		}
	}
}

//--------------------------------------------------------------------------

inline void orderColoredStrokes(JointSequenceGraphList &organizedGraphs, std::vector<TStroke *> &strokes,
								const TRasterCM32P &ras, TPalette *palette)
{
	// Initialize ordering
	std::vector<std::pair<int, TStroke *>> strokesByHeight(
		strokes.size(), std::make_pair(-(std::numeric_limits<int>::max)(), (TStroke *)0));

	size_t s, sCount = strokes.size();
	for (s = 0; s != sCount; ++s)
		strokesByHeight[s].second = strokes[s];

	size_t og, ogCount = organizedGraphs.size();
	for (og = 0; og != ogCount; ++og)
		sortJS(&organizedGraphs[og], strokesByHeight, ras, palette);

	// Now, we have the order vector filled, apply sorting algorithm.
	std::sort(strokesByHeight.begin(), strokesByHeight.end());

	for (s = 0; s != sCount; ++s)
		strokes[s] = strokesByHeight[s].second;
}

//==========================================================================

//Take samples of image colors to associate each stroke to its corresponding
//palette color. Currently working on colormaps, closest-to-black strokes
//otherwise.
void applyStrokeColors(std::vector<TStroke *> &strokes, const TRasterP &ras, TPalette *palette,
					   VectorizerCoreGlobals &g)
{
	JointSequenceGraphList &organizedGraphs = g.organizedGraphs;
	SequenceList &singleSequences = g.singleSequences;

	TRasterCM32P cm = ras;
	unsigned int i, j, k, n;

	if (cm && g.currConfig->m_maxThickness > 0.0) {
		applyStrokeIndices(&g);

		//Treat single sequences before, like conversionToStrokes(..)
		for (i = 0; i < singleSequences.size(); ++i)
			strokes[i]->setStyle(singleSequences[i].m_color);

		//Then, treat remaining graph-strokes
		n = i;

		for (i = 0; i < organizedGraphs.size(); ++i)
			for (j = 0; j < organizedGraphs[i].getNodesCount(); ++j)
				if (!organizedGraphs[i].getNode(j).hasAttribute(JointSequenceGraph::ELIMINATED)) //due to junction recovery
					for (k = 0; k < organizedGraphs[i].getNode(j).getLinksCount(); ++k) {
						Sequence &s = *organizedGraphs[i].node(j).link(k);
						if (s.isForward()) {
							//vi->getStroke(n)->setStyle(s.m_color);
							strokes[n]->setStyle(s.m_color);
							++n;
						}
					}

		//Order vector image according to actual color-coverings at junctions.
		orderColoredStrokes(organizedGraphs, strokes, cm, palette);
	} else {
		//Choose closest-to-black palette color
		int blackStyleId = palette->getClosestStyle(TPixel32::Black);

		unsigned int i;
		for (i = 0; i < strokes.size(); ++i)
			strokes[i]->setStyle(blackStyleId);
	}
}


#include "test_network.hpp"
#include <fstream>
#include <boost/numeric/ublas/io.hpp>
#include "boost/program_options.hpp"
#include <filesystem>

namespace po = boost::program_options;
namespace ub = boost::numeric::ublas;

void stream_test_data(int input_size, std::vector<VectorXd>& test_inputs, std::vector<std::string>& test_outputs, std::istream& stream)
{
    try
    {
        int                      row_size = -1;
        std::vector<std::string> str_test_data;
        do
        {
            str_test_data = input_csv(stream);
            if (!str_test_data.size())
            {
                break;
            }

            // Ensure test data rows are all the same length. Also ensures that the row size is either the same as
            // the network first layer size or one more than it (to fascilitate confusion network generation)
            if ((input_size != str_test_data.size() - 1 && input_size != str_test_data.size()) || str_test_data.size() != row_size && row_size != -1)
            {
                throw std::runtime_error("Test data improperly formatted");
            }
            row_size = str_test_data.size();

            VectorXd tmp_vec(input_size);
            for (int i = 0; i < input_size; i++)
            {
                tmp_vec(i) = std::stod(str_test_data[i]);
            }
            test_inputs.push_back(tmp_vec);

            if (str_test_data.size() > input_size)
            {
                test_outputs.push_back(str_test_data[input_size]);
            }
        }
        while ((str_test_data.size() != 0) && !stream.eof() && !stream.fail());
    }
    catch (const std::invalid_argument&)
    {
        throw std::runtime_error("Test data improperly formatted");
    }
};

void output_results(const std::vector<VectorXd>& test_inputs, const std::vector<std::string>& test_outputs, const ml::neural_network& nn, std::ostream& stream)
{

    if (test_outputs.size() > 0) // labels provided, generate confusion matrix given expected outputs
    {
        std::vector<std::pair<VectorXd, std::string>> tmp_vec;
        for (int i = 0; i < test_outputs.size(); i++)
        {
            tmp_vec.push_back({test_inputs[i], test_outputs[i]});
        }
        auto confusion_matrix = nn.generate_confusion_matrix(tmp_vec);

        // count total predictions and correct predictions (along the diagonal)
        double total_predictions   = 0;
        double correct_predictions = 0;
        for (int i = 0; i < confusion_matrix.second.rows(); i++)
        {
            for (int j = 0; j < confusion_matrix.second.cols(); j++)
            {
                total_predictions += confusion_matrix.second(i, j);
                if (i == j)
                {
                    correct_predictions += confusion_matrix.second(i, j);
                }
            }
        }

        // print model accuracy in top left
        stream << 100 * correct_predictions / total_predictions;
        // print column headers
        for (auto label : confusion_matrix.first)
        {
            stream << ',' << label;
        }

        for (int i = 0; i < confusion_matrix.first.size(); i++)
        {
            stream << std::endl;
            stream << confusion_matrix.first[i];
            for (int j = 0; j < confusion_matrix.second.cols(); j++)
            {
                stream << ',' << confusion_matrix.second(i, j);
            }
        }
    }
    else // labels not provided, output predictions and confidence
    {
        stream << "Input";
        for (int i = 0; i < test_inputs[0].size(); i++)
        {
            stream << ',';
        }
        stream << "Prediction,Confidence(%)";
        for (const auto& input : test_inputs)
        {
            stream << std::endl;
            auto result = nn.classify(input);
            for (int i = 0; i < input.size(); i++)
            {
                stream << input(i) << ',';
            }
            stream << result.first << ',';
            stream << result.second * 100;
        }
    }
    stream << std::endl;
}

int main(int ac, char** av)
{
    po::options_description desc("Allowed options");
    // clang-format off
        desc.add_options()  ("help", "display help message")
                            ("test-data,d", po::value<std::string>(), "String specifying a file to use for test data. If unspecified test data will be read from standard input.")
                            ("trained-network,n", po::value<std::string>(), "String specifying a file to read network weights and biases from. If unspecified, network data will be read from standard input.")
                            ("test-results,r", po::value<std::string>(), "String specifying a file to write test results to. If unspecified, test results will be written to standard output");
    // clang-format on

    try
    {
        po::variables_map vm;
        po::store(po::parse_command_line(ac, av, desc), vm);
        po::notify(vm);

        if (vm.count("help"))
        {
            std::cout << desc << "\n";
            std::cout << "Note: One of either test-data or network-file must be specified.\n";
            return 1;
        }

        if (!vm.count("test-data") && !vm.count("trained-network"))
        {
            throw boost::program_options::error("One of either test-data or network-file must be specified");
        }

        std::vector<MatrixXd> weights;
        std::vector<VectorXd> biases;
        std::set<std::string> labels;

        if (vm.count("trained-network"))
        {
            std::string file_name = vm["trained-network"].as<std::string>();
            if (!std::filesystem::exists(file_name))
            {
                throw std::runtime_error(file_name + ": No such file");
            }
            std::ifstream fstream;
            fstream.open(file_name);
            stream_network_data(weights, biases, labels, fstream);
            fstream.close();
        }
        else // network data is read from standard input
        {
            stream_network_data(weights, biases, labels, std::cin);
        }

        ml::neural_network nn(weights, biases, labels);

        std::vector<VectorXd>    test_inputs;
        std::vector<std::string> test_outputs;

        if (vm.count("test-data"))
        {
            std::string file_name = vm["test-data"].as<std::string>();
            if (!std::filesystem::exists(file_name))
            {
                throw std::runtime_error(file_name + ": No such file");
            }
            std::ifstream fstream;
            fstream.open(vm["test-data"].as<std::string>());
            stream_test_data(weights[0].cols(), test_inputs, test_outputs, fstream);
            fstream.close();
        }
        else // network data is read from standard input
        {
            stream_test_data(weights[0].cols(), test_inputs, test_outputs, std::cin);
        }

        // Check test data labels are in the pretrained network
        for (auto& str : test_outputs)
        {
            if (labels.find(str) == labels.end())
            {
                throw std::runtime_error("Test data incompatible with network");
            }
        }

        if (vm.count("test-results"))
        {
            std::ofstream fstream;
            fstream.open(vm["test-results"].as<std::string>());
            output_results(test_inputs, test_outputs, nn, fstream);
            fstream.close();
        }
        else
        {
            output_results(test_inputs, test_outputs, nn, std::cout);
        }
    }
    catch (const boost::program_options::error_with_option_name& err)
    {
        std::cerr << err.what() << "\n";
        std::cerr << desc << "\n";
    }
    catch (const boost::program_options::error& err)
    {
        std::cerr << err.what() << "\n";
        std::cerr << desc << "\n";
    }
    catch (const std::runtime_error& err)
    {
        std::cerr << err.what() << "\n";
    }

    return 0;
}


/*
    Copyright (C) 2009 Wei Dong <wdong@princeton.edu>. All Rights Reserved.

    This file is part of LSHKIT.

    LSHKIT is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    LSHKIT is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with LSHKIT.  If not, see <http://www.gnu.org/licenses/>.
*/

/**
 * \file embed.cpp
 * \brief Example program of set embedding with random histograms.
 *
 * This program implements the two random histogram embedding methods
 * proposed in the random histogram paper by W. Dong et al.
 *
 * The program reads feature sets from an input text file.  The file
 * is of the following format
 *
 \verbatim
 ID   N                   // ID is the identifier of the set, N is the number of features in the set
 weight   D1  D2  ...     // a weight followed by D dimensions, 1st feature
 weight   D1  D2  ...     // 2nd feature
 ...
 weight   D1  D2  ...     // Nth feature
 ID   N                   // another set
 weight   D1  D2  ...
 \endverbatim
 * ID is string which cannot contain space characters; N is positive integer; weight and the dimension
 * values are floats.
 *
 * The program embedds the input sets into single feature vectors and output
 * them in the following format
 *
 \verbatim
 ID D1  D2 ...              // The input IDs are copied to the output
 ID D1  D2 ...              // Following is the histogram, whose dimensionality is determined by the
 ...                        // input parameters.
 \endverbatim
 *
 * The user is encouraged to modify this program to customize the input and output format.
 *
 * Usage:
 *
 \verbatim
Allowed options:
  -h [ --help ]            produce help message.
  -t [ --type ] arg (=1)   embedding algorithm:
                           1 - stripe embedding [-B, -M, -N, -W],
                           2 - random hyperplane [-B, -M, -N].

  --norm                   normalize the output vector to unit length.
  -I [ --input ] arg (=-)  input file.
  -O [ --output ] arg (=-) output file.
  -D [ --dim ] arg         input dimension.
  -B [ -- ] arg (=8)       #bits per projection.
  -M [ -- ] arg (=1)       take the sum of M.
  -N [ -- ] arg (=10)      repeat N times.
  -W [ -- ] arg (=1)       for type 1 only, LSH window size.
 \endverbatim
 */

#include <boost/program_options.hpp>
#include <boost/foreach.hpp>
#include <lshkit.h>

/*
 The Histogram<> class in LSHKIT doesn't support polymorphism.
 Following we are going to wrap up Histogram<> with Embedder classes
 to support polymorphism, so the user can choose different
 embedder classes in command line.
*/

/// The abstract embedder.
/**
  * An Embedder class only have to implement two virtual functions: dim() and add().
  */
class Embedder {
public:
    /// The dimension of the output histogram.
    virtual unsigned dim () const = 0;
    /// Add a point to the output histogram with weight.
    virtual void add (float *out, const float *in, float weight) const = 0;
    /// Add a point to the output histogram with weight = 1.
    void add (float *out, const float *in) const {
        add(out, in , 1.0);
    }
    /// Initialize the output histogram to zeros.
    void zero (float *out) const {
        std::fill(out, out + dim(), 0);
    }
    /// Scale the output histogram by *s.
    void scale (float *out, float s) const
    {
        for (unsigned i = 0; i < dim(); i++) out[i] *= s;
    }
    /// Normalize the output histogram to a unit vector.
    void norm (float *out) const
    {
        float s = 0.0;
        for (unsigned i = 0; i < dim(); i++) s += out[i] * out[i];
        s = 1.0/sqrtf(s);
        scale(out, s);
    }
};

/// The Stripe LSH. See Section 4.1 of MM08 paper.
typedef lshkit::Repeat<lshkit::LSB<lshkit::GaussianLsh> > StripeLsh;
typedef lshkit::Histogram<StripeLsh> StripeEmbedderBase;

/// Wrapper of the histogram embedder.
class StripeEmbedder: public Embedder, public StripeEmbedderBase {
    typedef StripeEmbedderBase Base;
public:
    struct Parameter: public Base::Parameter {
        unsigned M, N;
        /**
          Inheritec parameters:
          unsigned dim;
          float W;
          unsigned repeat
         */

    };

    StripeEmbedder (const Parameter &param, lshkit::DefaultRng &rng)
        : Base(param.M, param.N, param, rng) {
    }

    virtual unsigned dim () const {
        return Base::dim();
    }

    virtual void add (float *out, const float *in, float weight) const {
        Base::add(out, const_cast<float *>(in), weight);
    }
};

/// Random hyperplane LSH. See Section 4.2 of MM08 paper.
typedef lshkit::Repeat<lshkit::HyperPlaneLsh> HyperPlaneLsh;
typedef lshkit::Histogram<HyperPlaneLsh> HyperPlaneEmbedderBase;

/// Wrapper of the histogram embedder.
class HyperPlaneEmbedder: public Embedder, public HyperPlaneEmbedderBase {
    typedef HyperPlaneEmbedderBase Base;
public:
    struct Parameter: public Base::Parameter {
        unsigned M, N;
        /**
          Inheritec parameters:
          unsigned dim;
          unsigned repeat
         */
    };

    HyperPlaneEmbedder (const Parameter &param, lshkit::DefaultRng &rng)
        : Base(param.M, param.N, param, rng) {
    }

    virtual unsigned dim () const {
        return Base::dim();
    }

    virtual void add (float *out, const float *in, float weight) const {
        Base::add(out, const_cast<float *>(in), weight);
    }
};

using namespace std;
using namespace lshkit;
namespace po = boost::program_options;

#define EMBEDDER_STRIP  1
#define EMBEDDER_RP 2

int main (int argc, char *argv[])
{
    int type;

    string input;
    string output;

    unsigned D, B, M, N;
    float W;
    bool norm = false;

    po::options_description desc("Allowed options");
    desc.add_options()
        ("help,h", "produce help message.")
        ("type,t", po::value(&type)->default_value(1), "embedding algorithm:\n"
            "\t1 - stripe embedding [-B, -M, -N, -W],\n"
            "\t2 - random hyperplane [-B, -M, -N].\n"
         )
        ("norm", "normalize the output vector to unit length.")
        ("input,I", po::value(&input)->default_value("-"), "input file.")
        ("output,O", po::value(&output)->default_value("-"), "output file.")
        ("dim,D", po::value(&D), "input dimension.")
        (",B", po::value(&B)->default_value(8), "#bits per projection.")
        (",M", po::value(&M)->default_value(1), "take the sum of M.")
        (",N", po::value(&N)->default_value(10), "repeat N times.")
        (",W", po::value(&W)->default_value(1.0), "for type 1 only, LSH window size.")
        ;

    po::variables_map vm;
    po::store(po::parse_command_line(argc, argv, desc), vm);
    po::notify(vm);

    if (vm.count("help") || (vm.count("dim") < 1))
    {
        cout << desc;
        return 0;
    }

    if (vm.count("norm")) norm = true;

    DefaultRng rng;
    Embedder *emb;

    switch (type)
    {
        case 1: {
                    StripeEmbedder::Parameter param;
                    param.dim = D;
                    param.W = W;
                    param.repeat = B;
                    param.M = M;
                    param.N = N;
                    emb = new StripeEmbedder(param, rng);
                    break;
                }
        case 2: {

                    HyperPlaneEmbedder::Parameter param;
                    param.dim = D;
                    param.repeat = B;
                    param.M = M;
                    param.N = N;
                    emb = new HyperPlaneEmbedder(param, rng);
                    break;
                }
        default:
                throw invalid_argument("INVALID EMBEDDER TYPE.");
    }

    ifstream is(input.c_str());
    ofstream os(output.c_str());

    float *in = new float[D];
    float *out = new float[emb->dim()];

    for (;;)
    {
/* !!!!!!!!!!!!!!!!! Modify here for different input format !!!!!!!!!!!!!! */
        unsigned n;  /* number of feature vectors for the next object */
        string id;
        is >> id >> n;
        if (!is) break;
        emb->zero(out);
        // process the n vectors
        for (unsigned i = 0; i < n; ++i)
        {
            float weight;
            is >> weight;
            /* read a feature vector */
            for (unsigned j = 0; j < D; ++j) {
                is >> in[j];
            }
            emb->add(out, in, weight);
        }

/* !!!!!!!!!!!!!!!!! Modify here for different output format !!!!!!!!!!!!!! */
        if (norm) emb->norm(out);
        os << id;
        for (unsigned j = 0; j < emb->dim(); ++j) {
            os << '\t' << out[j];
        }
        os << endl;
    }

    delete emb;
    delete []in;
    delete []out;

    return 0;
}


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

#ifndef NETKET_AMSGRAD_HPP
#define NETKET_AMSGRAD_HPP

#include <Eigen/Core>
#include <Eigen/Dense>
#include <cassert>
#include <cmath>
#include <complex>
#include <iostream>
#include "abstract_stepper.hpp"

namespace netket {

class AMSGrad : public AbstractStepper {
  int npar_;

  double eta_;
  double beta1_;
  double beta2_;

  Eigen::VectorXd mt_;
  Eigen::VectorXd vt_;

  double epscut_;

  int mynode_;

  const std::complex<double> I_;

 public:
  // Json constructor
  explicit AMSGrad(const json &pars)
      : eta_(FieldOrDefaultVal(pars["Learning"],"LearningRate",0.001)),
        beta1_(FieldOrDefaultVal(pars["Learning"],"Beta1",0.9)),
        beta2_(FieldOrDefaultVal(pars["Learning"],"Beta2",0.999)),
        epscut_(FieldOrDefaultVal(pars["Learning"],"Epscut",1.0e-7)),
        I_(0, 1) {
    npar_ = -1;

    PrintParameters();
  }

  void PrintParameters() {
    MPI_Comm_rank(MPI_COMM_WORLD, &mynode_);
    if (mynode_ == 0) {
      std::cout << "# AMSGrad stepper initialized with these parameters : "
                << std::endl;
      std::cout << "# Learning Rate = " << eta_ << std::endl;
      std::cout << "# Beta1 = " << beta1_ << std::endl;
      std::cout << "# Beta2 = " << beta2_ << std::endl;
      std::cout << "# Epscut = " << epscut_ << std::endl;
    }
  }

  void Init(const Eigen::VectorXd &pars) override {
    npar_ = pars.size();
    mt_.setZero(npar_);
    vt_.setZero(npar_);

  }

  void Init(const Eigen::VectorXcd &pars) override {
    npar_ = 2 * pars.size();
    mt_.setZero(npar_);
    vt_.setZero(npar_);

  }

  void Update(const Eigen::VectorXd &grad, Eigen::VectorXd &pars) override {
    assert(npar_ > 0);

    mt_=beta1_*mt_+(1.-beta1_)*grad;

    for(int i=0;i<npar_;i++){
      vt_(i)=std::max(vt_(i),beta2_*vt_(i)+(1-beta2_)*std::pow(grad(i),2));
    }

    for(int i=0;i<npar_;i++){
      pars(i)-=eta_*mt_(i)/(std::sqrt(vt_(i))+epscut_);
    }
  }

  void Update(const Eigen::VectorXcd &grad, Eigen::VectorXd &pars) override {
    Update(Eigen::VectorXd(grad.real()), pars);
  }

  void Update(const Eigen::VectorXcd &grad, Eigen::VectorXcd &pars) override {
    assert(npar_ == 2 * pars.size());

    for(int i=0;i<pars.size();i++){
      mt_(2*i)=beta1_*mt_(2*i)+(1.-beta1_)*grad(i).real();
      mt_(2*i+1)=beta1_*mt_(2*i+1)+(1.-beta1_)*grad(i).imag();
    }

    for(int i=0;i<pars.size();i++){
      vt_(2*i)=std::max(vt_(2*i),beta2_*vt_(2*i)+(1-beta2_)*std::pow(grad(i).real(),2));
      vt_(2*i+1)=std::max(vt_(2*i+1),beta2_*vt_(2*i+1)+(1-beta2_)*std::pow(grad(i).imag(),2));
    }

    for(int i=0;i<pars.size();i++){
      pars(i)-=eta_*mt_(2*i)/(std::sqrt(vt_(2*i))+epscut_);
      pars(i)-=eta_*I_*mt_(2*i+1)/(std::sqrt(vt_(2*i+1))+epscut_);
    }
  }

  void Reset() override {
    mt_ = Eigen::VectorXd::Zero(npar_);
    vt_ = Eigen::VectorXd::Zero(npar_);
  }

};

}  // namespace netket

#endif


#include <boost/test/unit_test.hpp>
#include <iostream>
#include <string>
#include <inttypes.h>
#include <vector>
#include "CborUint.h"
#include <boost/timer/timer.hpp>
#include <algorithm>
#include <boost/multiprecision/cpp_int.hpp>
#include <boost/multiprecision/detail/bitscan.hpp>
#include <boost/endian/conversion.hpp>
#ifdef USE_CBOR_FAST
#include <immintrin.h>
#endif

/*
    //https://datatracker.ietf.org/doc/html/rfc8949#appendix-A
   +==============================+====================================+
   |Diagnostic                    | Encoded                            |
   +==============================+====================================+
   |0                             | 0x00                               |
   +------------------------------+------------------------------------+
   |1                             | 0x01                               |
   +------------------------------+------------------------------------+
   |10                            | 0x0a                               |
   +------------------------------+------------------------------------+
   |23                            | 0x17                               |
   +------------------------------+------------------------------------+
   |24                            | 0x1818                             |
   +------------------------------+------------------------------------+
   |25                            | 0x1819                             |
   +------------------------------+------------------------------------+
   |100                           | 0x1864                             |
   +------------------------------+------------------------------------+
   |1000                          | 0x1903e8                           |
   +------------------------------+------------------------------------+
   |1000000                       | 0x1a000f4240                       |
   +------------------------------+------------------------------------+
   |1000000000000                 | 0x1b000000e8d4a51000               |
   +------------------------------+------------------------------------+
   |18446744073709551615          | 0x1bffffffffffffffff               |
   +------------------------------+------------------------------------+
   |18446744073709551616          | 0xc249010000000000000000           |
   +------------------------------+------------------------------------+

*/
//pair = value, expectedEncoding
typedef std::pair<uint64_t, std::vector<uint8_t> > pairVE;
static const std::vector<pairVE> testValuesPlusExpectedEncodings = {
    pairVE(0, {0x00}),
    pairVE(1, {0x01}),
    pairVE(10, {0x0a}),
    pairVE(23, {0x17}),
    pairVE(24, {0x18, 0x18}),
    pairVE(25, {0x18, 0x19}),
    pairVE(100, {0x18, 0x64}),
    pairVE(1000, {0x19, 0x03, 0xe8}),
    pairVE(1000000, {0x1a, 0x00, 0x0f, 0x42, 0x40}),
    pairVE(1000000000000, {0x1b, 0x00, 0x00, 0x00, 0xe8, 0xd4, 0xa5, 0x10, 0x00}),
    pairVE(18446744073709551615U, {0x1b, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff})
    //pairVE(18446744073709551616, {0xc2, 0x49, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}), //too large
};

//pair = value, encodeSize
typedef std::pair<uint64_t, unsigned int> pairVS;
static const std::vector<pairVS> testValuesPlusEncodedSizes = {
    pairVS(0, 1),
    pairVS(1, 1),
    pairVS(2, 1),
    pairVS(3, 1),
    pairVS(4, 1),
    pairVS(5, 1),
    pairVS(6, 1),
    pairVS(7, 1),
    pairVS(8, 1),
    pairVS(9, 1),
    pairVS(10, 1),
    pairVS(11, 1),
    pairVS(12, 1),
    pairVS(13, 1),
    pairVS(14, 1),
    pairVS(15, 1),
    pairVS(16, 1),
    pairVS(17, 1),
    pairVS(18, 1),
    pairVS(19, 1),
    pairVS(20, 1),
    pairVS(21, 1),
    pairVS(22, 1),
    pairVS(23, 1),

    pairVS(24, 2),
    pairVS(25, 2),
    pairVS(26, 2),
    pairVS(27, 2),
    pairVS(28, 2),
    pairVS(29, 2),
    pairVS(30, 2),

    // (1 << 8) = 256
    pairVS(256 - 4, 2),
    pairVS(256 - 3, 2),
    pairVS(256 - 2, 2),
    pairVS(256 - 1, 2),
    pairVS(256 + 0, 3),
    pairVS(256 + 1, 3),
    pairVS(256 + 2, 3),
    pairVS(256 + 3, 3),
    pairVS(256 + 4, 3),

    // (1 << 16) = 65536
    pairVS(65536 - 4, 3),
    pairVS(65536 - 3, 3),
    pairVS(65536 - 2, 3),
    pairVS(65536 - 1, 3),
    pairVS(65536 + 0, 5),
    pairVS(65536 + 1, 5),
    pairVS(65536 + 2, 5),
    pairVS(65536 + 3, 5),
    pairVS(65536 + 4, 5),

    // (1 << 32) = 4294967296
    pairVS(4294967296 - 4, 5),
    pairVS(4294967296 - 3, 5),
    pairVS(4294967296 - 2, 5),
    pairVS(4294967296 - 1, 5),
    pairVS(4294967296 + 0, 9),
    pairVS(4294967296 + 1, 9),
    pairVS(4294967296 + 2, 9),
    pairVS(4294967296 + 3, 9),
    pairVS(4294967296 + 4, 9),

    // (1 << 64) - 1
    pairVS(UINT64_MAX - 4, 9),
    pairVS(UINT64_MAX - 3, 9),
    pairVS(UINT64_MAX - 2, 9),
    pairVS(UINT64_MAX - 1, 9),
    pairVS(UINT64_MAX , 9),

};

BOOST_AUTO_TEST_CASE(CborUint64BitAppendixATestCase)
{
    std::vector<uint8_t> encodedClassic(9);
    std::vector<uint8_t> encodedFast(9);
    for (std::size_t i = 0; i < testValuesPlusExpectedEncodings.size(); ++i) {
        const pairVE & p = testValuesPlusExpectedEncodings[i];
        const uint64_t valueToEncode = p.first;
        const std::vector<uint8_t> & expectedEncoding = p.second;

        //encode classic
        encodedClassic.resize(9);
        encodedClassic.assign(encodedClassic.size(), 0);
        unsigned int encodedSizeClassic = CborEncodeU64Classic(&encodedClassic[0], valueToEncode, 9);
        BOOST_REQUIRE_EQUAL(encodedSizeClassic, expectedEncoding.size());
        BOOST_REQUIRE_EQUAL(encodedSizeClassic, CborGetEncodingSizeU64Classic(valueToEncode));
        encodedClassic.resize(encodedSizeClassic);
        BOOST_REQUIRE(encodedClassic == expectedEncoding);

        //encode classic buf size 9
        encodedClassic.resize(9);
        encodedClassic.assign(encodedClassic.size(), 0);
        encodedSizeClassic = CborEncodeU64ClassicBufSize9(&encodedClassic[0], valueToEncode);
        BOOST_REQUIRE_EQUAL(encodedSizeClassic, expectedEncoding.size());
        encodedClassic.resize(encodedSizeClassic);
        BOOST_REQUIRE(encodedClassic == expectedEncoding);

        //decode classic
        uint8_t numBytesTakenToDecode;
        uint64_t decodedValueClassic = CborDecodeU64Classic(expectedEncoding.data(), &numBytesTakenToDecode, 9);
        BOOST_REQUIRE_EQUAL((unsigned int)numBytesTakenToDecode, expectedEncoding.size());
        BOOST_REQUIRE_EQUAL(decodedValueClassic, valueToEncode);

        //decode classic buf size 9
        decodedValueClassic = CborDecodeU64ClassicBufSize9(expectedEncoding.data(), &numBytesTakenToDecode);
        BOOST_REQUIRE_EQUAL((unsigned int)numBytesTakenToDecode, expectedEncoding.size());
        BOOST_REQUIRE_EQUAL(decodedValueClassic, valueToEncode);

#ifdef USE_CBOR_FAST

        //encode fast
        encodedFast.resize(9);
        encodedFast.assign(encodedFast.size(), 0);
        unsigned int encodedSizeFast = CborEncodeU64Fast(&encodedFast[0], valueToEncode, 9);
        BOOST_REQUIRE_EQUAL(encodedSizeFast, expectedEncoding.size());
        BOOST_REQUIRE_EQUAL(encodedSizeFast, CborGetEncodingSizeU64Fast(valueToEncode));
        encodedFast.resize(encodedSizeFast);
        BOOST_REQUIRE(encodedFast == expectedEncoding);

        //encode fast buf size 9
        encodedFast.resize(9);
        encodedFast.assign(encodedFast.size(), 0);
        encodedSizeFast = CborEncodeU64FastBufSize9(&encodedFast[0], valueToEncode);
        BOOST_REQUIRE_EQUAL(encodedSizeFast, expectedEncoding.size());
        encodedFast.resize(encodedSizeFast);
        BOOST_REQUIRE(encodedFast == expectedEncoding);

        //decode fast
        uint64_t decodedValueFast = CborDecodeU64Fast(expectedEncoding.data(), &numBytesTakenToDecode, 9);
        BOOST_REQUIRE_EQUAL((unsigned int)numBytesTakenToDecode, expectedEncoding.size());
        BOOST_REQUIRE_EQUAL(decodedValueFast, valueToEncode);

        //decode fast buf size 9
        decodedValueFast = CborDecodeU64FastBufSize9(expectedEncoding.data(), &numBytesTakenToDecode);
        BOOST_REQUIRE_EQUAL((unsigned int)numBytesTakenToDecode, expectedEncoding.size());
        BOOST_REQUIRE_EQUAL(decodedValueFast, valueToEncode);

#endif //#ifdef USE_CBOR_FAST

    }

}

#ifdef USE_CBOR_FAST
BOOST_AUTO_TEST_CASE(CborUint64BitNoOverwriteTestCase)
{
    uint64_t s2;
    memcpy(&s2, "bbbbbbbb", 8);
    uint32_t s3;
    memcpy(&s3, "cccc", 4);
    for (unsigned int offset = 0; offset <= 9; ++offset) {
        //sanity check to make sure _mm_stream_si64 won't overwrite data past 8 bytes regardless of alignment
        std::vector<uint64_t> alignedData(4);
        memcpy(alignedData.data(), "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", 32);

        char *s = (char*)alignedData.data();
        char *sOffset = &s[offset];
        _mm_stream_si64((long long int *)sOffset, s2);

        BOOST_REQUIRE_EQUAL(std::string(sOffset, sOffset + 16), std::string("bbbbbbbbaaaaaaaa"));

        _mm_stream_si32((int32_t *)sOffset, s3);
        BOOST_REQUIRE_EQUAL(std::string(sOffset, sOffset + 16), std::string("ccccbbbbaaaaaaaa"));
    }
}
#endif

BOOST_AUTO_TEST_CASE(CborUint64BitEdgeCasesTestCase)
{
    std::vector<uint8_t> encodedClassic(9);
    std::vector<uint8_t> encodedFast(9);
    for (std::size_t i = 0; i < testValuesPlusEncodedSizes.size(); ++i) {
        const pairVS & p = testValuesPlusEncodedSizes[i];
        const uint64_t valueToEncode = p.first;
        const unsigned int expectedEncodingSize = p.second;

        //fail encoding if buffer too small (must return encoding size 0)
        BOOST_REQUIRE_EQUAL(CborEncodeU64Classic(&encodedClassic[0], valueToEncode, expectedEncodingSize - 1), 0);
        //encode classic buf size 9
        encodedClassic.assign(encodedClassic.size(), 0);
        unsigned int encodedSizeClassic = CborEncodeU64ClassicBufSize9(&encodedClassic[0], valueToEncode);
        BOOST_REQUIRE_EQUAL(encodedSizeClassic, expectedEncodingSize);
        //encode classic
        encodedClassic.assign(encodedClassic.size(), 0);
        encodedSizeClassic = CborEncodeU64Classic(&encodedClassic[0], valueToEncode, expectedEncodingSize);
        BOOST_REQUIRE_EQUAL(encodedSizeClassic, expectedEncodingSize);

        //decode classic
        uint8_t numBytesTakenToDecode;
        uint64_t decodedValueClassic = CborDecodeU64Classic(encodedClassic.data(), &numBytesTakenToDecode, expectedEncodingSize);
        BOOST_REQUIRE_EQUAL((unsigned int)numBytesTakenToDecode, expectedEncodingSize);
        BOOST_REQUIRE_EQUAL(decodedValueClassic, valueToEncode);
        //decode classic buf size 9
        decodedValueClassic = CborDecodeU64ClassicBufSize9(encodedClassic.data(), &numBytesTakenToDecode);
        BOOST_REQUIRE_EQUAL((unsigned int)numBytesTakenToDecode, expectedEncodingSize);
        BOOST_REQUIRE_EQUAL(decodedValueClassic, valueToEncode);
        //fail decoding if buffer too small (numBytesTakenToDecode must be 0)
        CborDecodeU64Classic(encodedClassic.data(), &numBytesTakenToDecode, expectedEncodingSize - 1);
        BOOST_REQUIRE_EQUAL((unsigned int)numBytesTakenToDecode, 0);

#ifdef USE_CBOR_FAST

        //fail encoding if buffer too small (must return encoding size 0)
        BOOST_REQUIRE_EQUAL(CborEncodeU64Fast(&encodedFast[0], valueToEncode, expectedEncodingSize - 1), 0);
        //encode fast
        encodedFast.assign(encodedFast.size(), 0);
        unsigned int encodedSizeFast = CborEncodeU64Fast(&encodedFast[0], valueToEncode, expectedEncodingSize);
        BOOST_REQUIRE_EQUAL(encodedSizeFast, expectedEncodingSize);
        //encode fast buf size 9
        encodedFast.assign(encodedFast.size(), 0);
        encodedSizeFast = CborEncodeU64FastBufSize9(&encodedFast[0], valueToEncode);
        BOOST_REQUIRE_EQUAL(encodedSizeFast, expectedEncodingSize);

        //decode fast
        uint64_t decodedValueFast = CborDecodeU64Fast(encodedFast.data(), &numBytesTakenToDecode, expectedEncodingSize);
        BOOST_REQUIRE_EQUAL((unsigned int)numBytesTakenToDecode, expectedEncodingSize);
        BOOST_REQUIRE_EQUAL(decodedValueFast, valueToEncode);
        //decode fast buf size 9
        decodedValueFast = CborDecodeU64FastBufSize9(encodedFast.data(), &numBytesTakenToDecode);
        BOOST_REQUIRE_EQUAL((unsigned int)numBytesTakenToDecode, expectedEncodingSize);
        BOOST_REQUIRE_EQUAL(decodedValueFast, valueToEncode);
        //fail decoding if buffer too small (numBytesTakenToDecode must be 0)
        CborDecodeU64Fast(encodedFast.data(), &numBytesTakenToDecode, expectedEncodingSize - 1);
        BOOST_REQUIRE_EQUAL((unsigned int)numBytesTakenToDecode, 0);

#endif //#ifdef USE_CBOR_FAST

    }

}

BOOST_AUTO_TEST_CASE(CborUint64BitSpeedTestCase, *boost::unit_test::disabled())
{
#define SPEED_TEST_LARGE_ENCODINGS 1
#if SPEED_TEST_LARGE_ENCODINGS
    std::vector<pairVS> testValuesPlusEncodedSizes2(testValuesPlusEncodedSizes.cbegin() + 45, testValuesPlusEncodedSizes.cend());
    for (uint64_t i = 5; i < 70; ++i) {
        testValuesPlusEncodedSizes2.push_back(pairVS(UINT64_MAX - i, 9));
    }

#else
    const std::vector<pairVS> testValuesPlusEncodedSizes2(testValuesPlusEncodedSizes.cbegin() + 21, testValuesPlusEncodedSizes.cend()); //create an even mix of various encoding sizes
#endif

    // shuffle to prevent branch prediction
    std::random_shuffle(testValuesPlusEncodedSizes2.begin(), testValuesPlusEncodedSizes2.end());

    unsigned int totalExpectedEncodingSize = 0;
    std::vector<uint64_t> allExpectedDecodedValues;
    allExpectedDecodedValues.reserve(testValuesPlusEncodedSizes2.size());
    for (std::size_t i = 0; i < testValuesPlusEncodedSizes2.size(); ++i) {
        allExpectedDecodedValues.push_back(testValuesPlusEncodedSizes2[i].first);
        totalExpectedEncodingSize += testValuesPlusEncodedSizes2[i].second;
    }

    std::vector<uint8_t> allEncodedDataClassic(testValuesPlusEncodedSizes2.size() * 9);
    std::vector<uint8_t> allEncodedDataFast(testValuesPlusEncodedSizes2.size() * 9);
    std::size_t totalBytesEncodedClassic = 0;
    std::size_t totalBytesEncodedClassicBufSize9 = 0;
    std::size_t totalBytesEncodedFast = 0;
    std::size_t totalBytesEncodedFastBufSize9 = 0;
    std::cout << "starting speed test\n";
    std::cout << "testValuesPlusEncodedSizes2 size: " << testValuesPlusEncodedSizes2.size() << std::endl;

    static const std::size_t LOOP_COUNT = 5000000;
    //ENCODE ARRAY OF VALS (CLASSIC)
    {
        std::cout << "encode classic\n";
        boost::timer::auto_cpu_timer t;
        for (std::size_t loopI = 0; loopI < LOOP_COUNT; ++loopI) {
            uint8_t * allEncodedDataPtr = allEncodedDataClassic.data();
            for (std::size_t i = 0; i < testValuesPlusEncodedSizes2.size(); ++i) {
                const uint64_t valueToEncode = testValuesPlusEncodedSizes2[i].first;
                //encode
                const unsigned int encodedSize = CborEncodeU64Classic(allEncodedDataPtr, valueToEncode, 9);
                allEncodedDataPtr += encodedSize;
                totalBytesEncodedClassic += encodedSize;
            }
        }
        //std::cout << "totalBytesEncoded " << totalBytesEncoded << std::endl;
        BOOST_REQUIRE_EQUAL(totalBytesEncodedClassic, totalExpectedEncodingSize * LOOP_COUNT);
    }

    //ENCODE ARRAY OF VALS (CLASSIC buf size 9)
    {
        std::cout << "encode classic buf size 9\n";
        boost::timer::auto_cpu_timer t;
        for (std::size_t loopI = 0; loopI < LOOP_COUNT; ++loopI) {
            uint8_t * allEncodedDataPtr = allEncodedDataClassic.data();
            for (std::size_t i = 0; i < testValuesPlusEncodedSizes2.size(); ++i) {
                const uint64_t valueToEncode = testValuesPlusEncodedSizes2[i].first;
                //encode
                const unsigned int encodedSize = CborEncodeU64ClassicBufSize9(allEncodedDataPtr, valueToEncode);
                allEncodedDataPtr += encodedSize;
                totalBytesEncodedClassicBufSize9 += encodedSize;
            }
        }
        //std::cout << "totalBytesEncoded " << totalBytesEncoded << std::endl;
        BOOST_REQUIRE_EQUAL(totalBytesEncodedClassicBufSize9, totalExpectedEncodingSize * LOOP_COUNT);
    }

#ifdef USE_CBOR_FAST
    //ENCODE ARRAY OF VALS (FAST)
    {
        std::cout << "encode fast\n";
        boost::timer::auto_cpu_timer t;
        for (std::size_t loopI = 0; loopI < LOOP_COUNT; ++loopI) {
            uint8_t * allEncodedDataPtr = allEncodedDataFast.data();
            for (std::size_t i = 0; i < testValuesPlusEncodedSizes2.size(); ++i) {
                const uint64_t valueToEncode = testValuesPlusEncodedSizes2[i].first;
                //encode
                const unsigned int encodedSize = CborEncodeU64Fast(allEncodedDataPtr, valueToEncode, 9);
                allEncodedDataPtr += encodedSize;
                totalBytesEncodedFast += encodedSize;
            }
        }
        //std::cout << "totalBytesEncoded " << totalBytesEncoded << std::endl;
        BOOST_REQUIRE_EQUAL(totalBytesEncodedFast, totalExpectedEncodingSize * LOOP_COUNT);
    }

    //ENCODE ARRAY OF VALS (FAST buf size 9)
    {
        std::cout << "encode fast buf size 9\n";
        boost::timer::auto_cpu_timer t;
        for (std::size_t loopI = 0; loopI < LOOP_COUNT; ++loopI) {
            uint8_t * allEncodedDataPtr = allEncodedDataFast.data();
            for (std::size_t i = 0; i < testValuesPlusEncodedSizes2.size(); ++i) {
                const uint64_t valueToEncode = testValuesPlusEncodedSizes2[i].first;
                //encode
                const unsigned int encodedSize = CborEncodeU64FastBufSize9(allEncodedDataPtr, valueToEncode);
                allEncodedDataPtr += encodedSize;
                totalBytesEncodedFastBufSize9 += encodedSize;
            }
        }
        //std::cout << "totalBytesEncoded " << totalBytesEncoded << std::endl;
        BOOST_REQUIRE_EQUAL(totalBytesEncodedFastBufSize9, totalExpectedEncodingSize * LOOP_COUNT);
    }
#endif

    //DECODE ARRAY OF VALS (CLASSIC)
    {
        std::cout << "decode classic\n";
        std::size_t totalBytesDecoded = 0;
        std::vector<uint64_t> allDecodedVals(testValuesPlusEncodedSizes2.size());
        std::size_t j;
        boost::timer::auto_cpu_timer t;
        for (std::size_t loopI = 0; loopI < LOOP_COUNT; ++loopI) {
            const uint8_t * allEncodedDataPtr = allEncodedDataClassic.data();
            j = 0;
            uint64_t * allDecodedDataPtr = allDecodedVals.data();
            while (j < totalExpectedEncodingSize) {
                //return decoded value (0 if failure), also set parameter numBytes taken to decode
                uint8_t numBytesTakenToDecode;
                const uint64_t decodedVal = CborDecodeU64Classic(allEncodedDataPtr, &numBytesTakenToDecode, 9);
                *allDecodedDataPtr++ = decodedVal;
                allEncodedDataPtr += numBytesTakenToDecode;
                totalBytesDecoded += numBytesTakenToDecode;
                j += numBytesTakenToDecode;
            }
        }
        BOOST_REQUIRE_EQUAL(j, totalExpectedEncodingSize);
        BOOST_REQUIRE_EQUAL(totalBytesDecoded, totalExpectedEncodingSize * LOOP_COUNT);
        BOOST_REQUIRE(allDecodedVals == allExpectedDecodedValues);
    }

    //DECODE ARRAY OF VALS (CLASSIC buf size 9)
    {
        std::cout << "decode classic buf size 9\n";
        std::size_t totalBytesDecoded = 0;
        std::vector<uint64_t> allDecodedVals(testValuesPlusEncodedSizes2.size());
        std::size_t j;
        boost::timer::auto_cpu_timer t;
        for (std::size_t loopI = 0; loopI < LOOP_COUNT; ++loopI) {
            const uint8_t * allEncodedDataPtr = allEncodedDataClassic.data();
            j = 0;
            uint64_t * allDecodedDataPtr = allDecodedVals.data();
            while (j < totalExpectedEncodingSize) {
                //return decoded value (0 if failure), also set parameter numBytes taken to decode
                uint8_t numBytesTakenToDecode;
                const uint64_t decodedVal = CborDecodeU64ClassicBufSize9(allEncodedDataPtr, &numBytesTakenToDecode);
                *allDecodedDataPtr++ = decodedVal;
                allEncodedDataPtr += numBytesTakenToDecode;
                totalBytesDecoded += numBytesTakenToDecode;
                j += numBytesTakenToDecode;
            }
        }
        BOOST_REQUIRE_EQUAL(j, totalExpectedEncodingSize);
        BOOST_REQUIRE_EQUAL(totalBytesDecoded, totalExpectedEncodingSize * LOOP_COUNT);
        BOOST_REQUIRE(allDecodedVals == allExpectedDecodedValues);
    }

#ifdef USE_CBOR_FAST
    //DECODE ARRAY OF VALS (FAST)
    {
        std::cout << "decode fast\n";
        std::size_t totalBytesDecoded = 0;
        std::vector<uint64_t> allDecodedVals(testValuesPlusEncodedSizes2.size());
        std::size_t j;
        boost::timer::auto_cpu_timer t;
        for (std::size_t loopI = 0; loopI < LOOP_COUNT; ++loopI) {
            const uint8_t * allEncodedDataPtr = allEncodedDataClassic.data();
            j = 0;
            uint64_t * allDecodedDataPtr = allDecodedVals.data();
            while (j < totalExpectedEncodingSize) {
                //return decoded value (0 if failure), also set parameter numBytes taken to decode
                uint8_t numBytesTakenToDecode;
                const uint64_t decodedVal = CborDecodeU64Fast(allEncodedDataPtr, &numBytesTakenToDecode, 9);
                *allDecodedDataPtr++ = decodedVal;
                allEncodedDataPtr += numBytesTakenToDecode;
                totalBytesDecoded += numBytesTakenToDecode;
                j += numBytesTakenToDecode;
            }
        }
        BOOST_REQUIRE_EQUAL(j, totalExpectedEncodingSize);
        BOOST_REQUIRE_EQUAL(totalBytesDecoded, totalExpectedEncodingSize * LOOP_COUNT);
        BOOST_REQUIRE(allDecodedVals == allExpectedDecodedValues);
    }

    //DECODE ARRAY OF VALS (FAST buf size 9)
    {
        std::cout << "decode fast buf size 9\n";
        std::size_t totalBytesDecoded = 0;
        std::vector<uint64_t> allDecodedVals(testValuesPlusEncodedSizes2.size());
        std::size_t j;
        boost::timer::auto_cpu_timer t;
        for (std::size_t loopI = 0; loopI < LOOP_COUNT; ++loopI) {
            const uint8_t * allEncodedDataPtr = allEncodedDataClassic.data();
            j = 0;
            uint64_t * allDecodedDataPtr = allDecodedVals.data();
            while (j < totalExpectedEncodingSize) {
                //return decoded value (0 if failure), also set parameter numBytes taken to decode
                uint8_t numBytesTakenToDecode;
                const uint64_t decodedVal = CborDecodeU64FastBufSize9(allEncodedDataPtr, &numBytesTakenToDecode);
                *allDecodedDataPtr++ = decodedVal;
                allEncodedDataPtr += numBytesTakenToDecode;
                totalBytesDecoded += numBytesTakenToDecode;
                j += numBytesTakenToDecode;
            }
        }
        BOOST_REQUIRE_EQUAL(j, totalExpectedEncodingSize);
        BOOST_REQUIRE_EQUAL(totalBytesDecoded, totalExpectedEncodingSize * LOOP_COUNT);
        BOOST_REQUIRE(allDecodedVals == allExpectedDecodedValues);
    }
#endif

}


// Copyright (c) 2015-2021 The Dogecoin Core developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.

#include <arith_uint256.h>
#include <chainparams.h>
#include <dogecoin.h>

#include <test/util/setup_common.h>

#include <boost/test/unit_test.hpp>

BOOST_FIXTURE_TEST_SUITE(dogecoin_tests, TestingSetup)

BOOST_AUTO_TEST_CASE(get_next_work_difficulty_limit)
{
    SelectParams(CBaseChainParams::MAIN);
    const Consensus::Params& params = Params().GetConsensus();

    CBlockIndex pindexLast;
    int64_t nLastRetargetTime = 1386474927; // Block # 1

    pindexLast.nHeight = 239;
    pindexLast.nTime = 1386475638; // Block #239
    pindexLast.nBits = 0x1e0ffff0;
    BOOST_CHECK_EQUAL(CalculateDogecoinNextWorkRequired(&pindexLast, nLastRetargetTime, params), 0x1e00ffff);
}

BOOST_AUTO_TEST_CASE(get_next_work_pre_digishield)
{
    SelectParams(CBaseChainParams::MAIN);
    const Consensus::Params& params = Params().GetConsensus();

    CBlockIndex pindexLast;
    int64_t nLastRetargetTime = 1386942008; // Block 9359

    pindexLast.nHeight = 9599;
    pindexLast.nTime = 1386954113;
    pindexLast.nBits = 0x1c1a1206;
    BOOST_CHECK_EQUAL(CalculateDogecoinNextWorkRequired(&pindexLast, nLastRetargetTime, params), 0x1c15ea59);
}

BOOST_AUTO_TEST_CASE(get_next_work_digishield)
{
    SelectParams(CBaseChainParams::MAIN);
    const Consensus::Params& params = Params().GetConsensus();

    CBlockIndex pindexLast;
    int64_t nLastRetargetTime = 1395094427;

    // First hard-fork at 145,000, which applies to block 145,001 onwards
    pindexLast.nHeight = 145000;
    pindexLast.nTime = 1395094679;
    pindexLast.nBits = 0x1b499dfd;
    BOOST_CHECK_EQUAL(CalculateDogecoinNextWorkRequired(&pindexLast, nLastRetargetTime, params), 0x1b671062);
}

BOOST_AUTO_TEST_CASE(get_next_work_digishield_modulated_upper)
{
    SelectParams(CBaseChainParams::MAIN);
    const Consensus::Params& params = Params().GetConsensus();

    CBlockIndex pindexLast;
    int64_t nLastRetargetTime = 1395100835;

    // Test the upper bound on modulated time using mainnet block #145,107
    pindexLast.nHeight = 145107;
    pindexLast.nTime = 1395101360;
    pindexLast.nBits = 0x1b3439cd;
    BOOST_CHECK_EQUAL(CalculateDogecoinNextWorkRequired(&pindexLast, nLastRetargetTime, params), 0x1b4e56b3);
}

BOOST_AUTO_TEST_CASE(get_next_work_digishield_modulated_lower)
{
    SelectParams(CBaseChainParams::MAIN);
    const Consensus::Params& params = Params().GetConsensus();

    CBlockIndex pindexLast;
    int64_t nLastRetargetTime = 1395380517;

    // Test the lower bound on modulated time using mainnet block #149,423
    pindexLast.nHeight = 149423;
    pindexLast.nTime = 1395380447;
    pindexLast.nBits = 0x1b446f21;
    BOOST_CHECK_EQUAL(CalculateDogecoinNextWorkRequired(&pindexLast, nLastRetargetTime, params), 0x1b335358);
}

BOOST_AUTO_TEST_CASE(get_next_work_digishield_rounding)
{
    SelectParams(CBaseChainParams::MAIN);
    const Consensus::Params& params = Params().GetConsensus();

    CBlockIndex pindexLast;
    int64_t nLastRetargetTime = 1395094679;

    // Test case for correct rounding of modulated time - this depends on
    // handling of integer division, and is not obvious from the code
    pindexLast.nHeight = 145001;
    pindexLast.nTime = 1395094727;
    pindexLast.nBits = 0x1b671062;
    BOOST_CHECK_EQUAL(CalculateDogecoinNextWorkRequired(&pindexLast, nLastRetargetTime, params), 0x1b6558a4);
}

BOOST_AUTO_TEST_SUITE_END()