//          Copyright Gavin Band 2008 - 2012.
// 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)

#ifndef QCTOOL_Genotype_FREQUENCY_TEST_CALL_COMPARER_HPP
#define QCTOOL_Genotype_FREQUENCY_TEST_CALL_COMPARER_HPP

#include <string>
#include <map>
#include <boost/math/distributions/chi_squared.hpp>
#include <boost/function.hpp>
#include "genfile/SingleSNPGenotypeProbabilities.hpp"
#include "PairwiseCallComparer.hpp"

struct GenotypeFrequencyTestCallComparer: public PairwiseCallComparer {

	GenotypeFrequencyTestCallComparer() ;

	void compare(
		Eigen::MatrixXd const& left,
		Eigen::MatrixXd const& right,
		Callback
	) const ;

private:
	double m_threshhold ;
	boost::math::chi_squared_distribution< double > m_chi_squared ;
} ;

#endif


/*
* Copyright (C) 2017 Incognito (Edited by ProMetheus)
*
* 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.
*/
#include <streamer/config.hpp>
#include "../natives.h"

#include "../core.h"
#include "../utility.h"

#include <streamer/text-labels.hpp>

#include <boost/geometry.hpp>
#include <boost/geometry/geometries/geometries.hpp>
#include <boost/intrusive_ptr.hpp>
#include <boost/scoped_ptr.hpp>
#include <boost/unordered_map.hpp>
#include <boost/unordered_set.hpp>

#include <Eigen/Core>

#include <string>

STREAMER_BEGIN_NS

int CreateDynamic3DTextLabel(std::string text, int color, float x, float y, float z, float drawDistance, int attachedPlayer, int attachedVehicle, bool testlos, int worldid, int interiorid, int playerid, float streamDistance, int areaid, int priority) {
	if (core->getData()->getGlobalMaxItems(STREAMER_TYPE_3D_TEXT_LABEL) == core->getData()->textLabels.size()) {
		return 0;
	}
	int textLabelId = Item::TextLabel::identifier.get();
	Item::SharedTextLabel textLabel(new Item::TextLabel);
	//textLabel->amx = amx;
	textLabel->textLabelId = textLabelId;
	textLabel->inverseAreaChecking = false;
	textLabel->originalComparableStreamDistance = -1.0f;
	textLabel->positionOffset = Eigen::Vector3f::Zero();
	textLabel->streamCallbacks = false;
	textLabel->text = text;
	textLabel->color = color;
	textLabel->position = Eigen::Vector3f(x, y, z);
	textLabel->drawDistance = drawDistance;
	if (attachedPlayer != INVALID_OBJECT_ID || attachedVehicle != INVALID_OBJECT_ID) {
		textLabel->attach = boost::intrusive_ptr<Item::TextLabel::Attach>(new Item::TextLabel::Attach);
		textLabel->attach->player = attachedPlayer;
		textLabel->attach->vehicle = attachedVehicle;
		if (textLabel->position.cwiseAbs().maxCoeff() > 50.0f) {
			textLabel->position.setZero();
		}
		core->getStreamer()->attachedTextLabels.insert(textLabel);
	}
	textLabel->testLOS = testlos;
	Utility::addToContainer(textLabel->worlds, worldid);
	Utility::addToContainer(textLabel->interiors, interiorid);
	Utility::addToContainer(textLabel->players, playerid);
	textLabel->comparableStreamDistance = streamDistance < STREAMER_STATIC_DISTANCE_CUTOFF ? streamDistance : streamDistance * streamDistance;
	textLabel->streamDistance = streamDistance;
	Utility::addToContainer(textLabel->areas, areaid);
	textLabel->priority = priority;
	core->getGrid()->addTextLabel(textLabel);
	core->getData()->textLabels.insert(std::make_pair(textLabelId, textLabel));
	return textLabelId;
}

int DestroyDynamic3DTextLabel(int id) {
	boost::unordered_map<int, Item::SharedTextLabel>::iterator t = core->getData()->textLabels.find(id);
	if (t != core->getData()->textLabels.end()) {
		Utility::destroyTextLabel(t);
		return 1;
	}
	return 0;
}

int IsValidDynamic3DTextLabel(int id) {
	boost::unordered_map<int, Item::SharedTextLabel>::iterator t = core->getData()->textLabels.find(id);
	if (t != core->getData()->textLabels.end()) {
		return 1;
	}
	return 0;
}

std::string GetDynamic3DTextLabelText(int id) {
	boost::unordered_map<int, Item::SharedTextLabel>::iterator t = core->getData()->textLabels.find(id);
	if (t != core->getData()->textLabels.end()) {
		return t->second->text;
	}
	return "";
}

int UpdateDynamic3DTextLabelText(int label, int color, std::string text) {
	boost::unordered_map<int, Item::SharedTextLabel>::iterator t = core->getData()->textLabels.find(label);
	if (t != core->getData()->textLabels.end()) {
		t->second->color = color;
		t->second->text = text;
		for (boost::unordered_map<int, Player>::iterator p = core->getData()->players.begin(); p != core->getData()->players.end(); ++p)
		{
			boost::unordered_map<int, int>::iterator i = p->second.internalTextLabels.find(t->first);
			if (i != p->second.internalTextLabels.end())
			{
				sampgdk::UpdatePlayer3DTextLabelText(p->first, i->second, t->second->color, t->second->text.c_str());
			}
		}
		return 1;
	}
	return 0;
}

STREAMER_END_NS


#include <k52/optimization/bounded_nelder_mead.h>

#ifdef BUILD_WITH_MPI

#include <boost/mpi.hpp>
#include <k52/parallel/mpi/constants.h>

#endif

#include <cmath>
#include <stdexcept>
#include <algorithm>
#include <fstream>
#include <iostream>

#include  <k52/common/constants.h>

using ::std::vector;
using ::k52::common::Constants;

namespace k52
{
namespace optimization
{

BoundedNelderMead::BoundedNelderMead(double l, double precision, double lower_bound, double upper_bound)
{
    lower_bound_ = lower_bound;
    upper_bound_ = upper_bound;
    precision_ = precision;
    l_ = l;
}

BoundedNelderMead* BoundedNelderMead::Clone() const
{
    return new BoundedNelderMead(l_, precision_, lower_bound_, upper_bound_);
}

std::string BoundedNelderMead::get_name() const
{
    return "Bounded Nelder Mead";
}

double BoundedNelderMead::get_lower_bound() const
{
    return lower_bound_;
}

double BoundedNelderMead::get_upper_bound() const
{
    return upper_bound_;
}

#ifdef BUILD_WITH_MPI
void BoundedNelderMead::Send(boost::mpi::communicator* communicator, int target) const
{
    communicator->send(target, k52::parallel::mpi::constants::kCommonTag, l_);
    communicator->send(target, k52::parallel::mpi::constants::kCommonTag, precision_);
    communicator->send(target, k52::parallel::mpi::constants::kCommonTag, lower_bound_);
    communicator->send(target, k52::parallel::mpi::constants::kCommonTag, upper_bound_);
}

void BoundedNelderMead::Receive(boost::mpi::communicator* communicator, int source)
{
    communicator->recv(source, k52::parallel::mpi::constants::kCommonTag, l_);
    communicator->recv(source, k52::parallel::mpi::constants::kCommonTag, precision_);
    communicator->recv(source, k52::parallel::mpi::constants::kCommonTag, lower_bound_);
    communicator->recv(source, k52::parallel::mpi::constants::kCommonTag, upper_bound_);
}
#endif

std::vector<double> BoundedNelderMead::FindOptimalParameters(const std::vector<double>& initial_parameters)
{
    //Iteration index
    size_t r = 0;

    //Build simplex based on initial_parameters
    vector< vector<double> > polygon = GetRegularSimplex(initial_parameters, l_);

    //count values
    vector<double> function_values = CountObjectiveFunctionValues(polygon);

    do
    {
        OutputPolygon(polygon);
        r++;

        size_t first_max_index = 0, second_max_index = 0, min_index = 0;
        //determine maximums and minimum
        GetIndexes(function_values, &first_max_index, &second_max_index, &min_index);

        double highest_value = function_values[first_max_index];
        double second_highest_value = function_values[second_max_index];
        double lowest_value = function_values[min_index];

        //determine center of mass
        vector<double> center_of_mass = GetCenterOfMass(polygon, first_max_index);

        //Reflect max point - we seek for minimum
        vector<double> reflected_point = Reflexion(center_of_mass, polygon[first_max_index]);
        CorrectByProjectingToBounds(&reflected_point);
        double reflected_point_value = CountSingleObjectiveFunctionValue(reflected_point);

        if (reflected_point_value < lowest_value)
        {
            vector<double> expanded_point = Expansion(center_of_mass, reflected_point);
            CorrectByProjectingToBounds(&expanded_point);
            double expanded_point_value = CountSingleObjectiveFunctionValue(expanded_point);

            if (expanded_point_value < reflected_point_value)
            {
                //Replace max with expanded
                polygon[first_max_index] = expanded_point;
                function_values[first_max_index] = expanded_point_value;
                continue;
            }
            else
            {
                //Replace max with reflected
                polygon[first_max_index] = reflected_point;
                function_values[first_max_index] = reflected_point_value;
                continue;
            }
        }
        else
        {
            if (reflected_point_value <= second_highest_value)
            {
                //Replace max with reflected
                polygon[first_max_index] = reflected_point;
                function_values[first_max_index] = reflected_point_value;
                continue;
            }
            else
            {
                if (reflected_point_value < highest_value)
                {
                    //Replace max with reflected
                    polygon[first_max_index] = reflected_point;
                    function_values[first_max_index] = reflected_point_value;
                    highest_value = reflected_point_value;
                }

                vector<double> contraction_point = Contraction(center_of_mass, polygon[first_max_index]);
                double contraction_point_value = CountSingleObjectiveFunctionValue(contraction_point);

                if (contraction_point_value > highest_value)
                {
                    Reduction(&polygon, min_index);
                    continue;
                }
                else
                {
                    //Replace max with contracted
                    polygon[first_max_index] = contraction_point;
                    function_values[first_max_index] = contraction_point_value;
                    continue;
                }
            }
        }
    } while (GetTerminationCriteria(r, function_values, polygon));

    size_t best_index = std::distance(function_values.begin(), std::max_element(function_values.begin(), function_values.end()));
    return polygon[best_index];
}

void BoundedNelderMead::CorrectByProjectingToBounds(vector<double>* point)
{
    for(size_t i = 0; i < point->size(); i++)
    {
        if( (*point)[i] > upper_bound_)
        {
            (*point)[i] = upper_bound_;
        }
        else if( (*point)[i] < lower_bound_)
        {
            (*point)[i] = lower_bound_;
        }
    }
}

vector<double> BoundedNelderMead::CountObjectiveFunctionValues(
    const vector< vector<double> >& parameters_values)
{
    size_t N = parameters_values.size();
    vector<double> counted_values(N);

    for(size_t i = 0; i < N; i++)
    {
        //As Nelder-Mead tends to minimize function we need to reverse it to find max
        counted_values[i] = CountObjectiveFunctionValueToMinimize(parameters_values[i]);
    }

    return counted_values;
}

double BoundedNelderMead::CountSingleObjectiveFunctionValue(
    const vector<double>& parameters)
{
    vector< vector<double> > cover(1);
    cover[0] = parameters;
    return CountObjectiveFunctionValues(cover)[0];
}

bool BoundedNelderMead::GetTerminationCriteria(
        size_t itreation_index,
        const std::vector<double>& function_values,
        const std::vector< std::vector<double> >& polygon)
{
    const size_t iterations_per_dimension = 1000;
    if (itreation_index > iterations_per_dimension * polygon[0].size())
    {
        std::cout << "WARNING: Exiting BDNM by iterations"
                " criteria!" << std::endl;
        return false;
    }

    if (WasSamePolygonBefore(polygon))
    {
        std::cout << "WARNING: Exiting BDNM because of a polygon loop,"
                " you migth have a special function behaviour." << std::endl;
        return false;
    }

    return CountDifferance(function_values) > precision_;
}

bool BoundedNelderMead::WasSamePolygonBefore(const std::vector< std::vector<double> >& polygon)
{
    bool was_same_before = false;

    for (std::list< std::vector< std::vector<double> > >::const_iterator it = previous_polygons_.begin();
         it != previous_polygons_.end();
         ++it)
    {
        if (CountPolygonDifferance(polygon, *it) < Constants::Eps)
        {
            was_same_before = true;
            break;
        }
    }

    if (previous_polygons_.size() >= max_previous_polygons_count)
    {
        previous_polygons_.pop_front();
    }
    previous_polygons_.push_back(polygon);

    return was_same_before;
}

void BoundedNelderMead::GetIndexes(const vector<double>& values, size_t* first_max_index, size_t* secound_max_index, size_t* min_index)
{
    if(values.size() < 2)
    {
        throw std::invalid_argument("values must have at least 2 elements");
    }

    if( values[0] > values[1] )
    {
        *first_max_index = 0;
        *secound_max_index = 1;
        *min_index = 1;
    }
    else
    {
        *first_max_index = 1;
        *secound_max_index = 0;
        *min_index = 0;
    }

    for (size_t i = 2; i<values.size(); i++)
    {
        if (values[i] > values[*first_max_index])
        {
            *secound_max_index = *first_max_index;
            *first_max_index = i;
        }
        else if (values[i]  > values[*secound_max_index])
        {
            *secound_max_index = i;
        }

        if( values[i] < values[*min_index])
        {
            *min_index = i;
        }
    }
}

vector< vector<double> > BoundedNelderMead::GetRegularSimplex(const vector<double>& base_point, double l)
{
    //Size of task
    size_t n = base_point.size();

    //For simplex points count
    double square_root_from_2 = std::sqrt(2.);
    double r1 = l * ( std::sqrt((double)(n+1)) + n - 1 ) / ( n * square_root_from_2 );
    double r2 = l * ( std::sqrt((double)(n+1)) - 1 ) / ( n * square_root_from_2 );

    vector< vector<double> > regular_simplex( n + 1 );
    regular_simplex[0] = base_point;

    for(size_t i = 1; i < n + 1; i++)
    {
        regular_simplex[i] = vector<double>(n);

        for(size_t j = 0; j < n; j++)
        {
            if( j == i - 1 )
            {
                regular_simplex[i][j] = base_point[j] + r1;
            }
            else
            {
                regular_simplex[i][j] = base_point[j] + r2;
            }
        }
    }
    return regular_simplex;
}

vector<double> BoundedNelderMead::Reflexion(const vector<double>& center_of_mass, const vector<double>& target_point)
{
    double reflection_coefficient = 1;
    vector<double> new_point (target_point.size());

    //Reflect
    for(size_t i = 0; i < target_point.size(); i++)
    {
        new_point[i] = center_of_mass[i] + reflection_coefficient * (center_of_mass[i] - target_point[i]);
    }

    return new_point;
}

vector<double> BoundedNelderMead::Expansion(const vector<double>& center_of_mass, const vector<double>& target_point)
{
    double expansion_coefficient = 2;
    vector<double> new_point (target_point.size());

    //Expand
    for(size_t i = 0; i < target_point.size(); i++)
    {
        new_point[i] = center_of_mass[i] + expansion_coefficient * (target_point[i] - center_of_mass[i]);
    }

    return new_point;
}

vector<double> BoundedNelderMead::Contraction(const vector<double>& center_of_mass, const vector<double>& target_point)
{
    double contraction_coefficient = 0.5;
    vector<double> new_point (target_point.size());

    //Contract
    for(size_t i = 0; i < target_point.size(); i++)
    {
        new_point[i] = center_of_mass[i] + contraction_coefficient * (target_point[i] - center_of_mass[i]);
    }

    return new_point;
}

void BoundedNelderMead::Reduction(vector< vector<double> >* polygon, size_t point_index)
{
    if(point_index >= polygon->size())
    {
        throw std::invalid_argument("Incorrect point_index");
    }

    double reduction_coefficient = 0.5;
    size_t n = (*polygon)[0].size();

    for(size_t i = 0; i < n +1; i++)
    {
        if( i != point_index )
        {
            for(size_t j = 0; j < n; j++)
            {
                (*polygon)[i][j] = (*polygon)[point_index][j] + reduction_coefficient *
                        ((*polygon)[i][j]  - (*polygon)[point_index][j]);
            }
        }
    }
}

double BoundedNelderMead::CountDifferance(const vector<double>& values)
{
    double summ = 0;
    for(size_t i = 0; i < values.size(); i++)
    {
        summ += values[i];
    }
    double averadge = summ / values.size();

    double square_summ = 0;
    for(size_t i = 0; i < values.size(); i++)
    {
        double diff = values[i] - averadge;
        square_summ += diff*diff;
    }
    return sqrt( square_summ / values.size());
}

double BoundedNelderMead::CountPolygonDifferance(
        const std::vector< std::vector<double> >& polygon,
        const std::vector< std::vector<double> >& previous_polygon)
{
    double summ = 0;
    for(size_t i = 0; i < polygon.size(); i++)
    {
        for(size_t j = 0; j < polygon[i].size(); j++)
        {
            summ += std::abs(polygon[i][j] - previous_polygon[i][j]);
        }
    }
    return summ;
}

vector<double> BoundedNelderMead::GetCenterOfMass(const vector< vector<double> >& polygon, size_t point_index)
{
    if(point_index>= polygon.size())
    {
        throw std::invalid_argument("Incorrect point_index");
    }

    size_t n = polygon[0].size();
    vector<double> center_of_mass(n);

    //TODO implement with valarray
    for(size_t i = 0; i < n; i++)
    {
        center_of_mass[i] = 0;

        for(size_t j = 0; j < n+1; j++)
        {
            if( j != point_index )
            {
                center_of_mass[i] += polygon[j][i];
            }
        }

        center_of_mass[i] /= n;
    }

    return center_of_mass;
}

void BoundedNelderMead::OutputPolygon(const vector< vector<double> >& polygon)
{
    std::ofstream polygon_output("polygon.plot", std::ofstream::app);
    polygon_output.precision(16);
    polygon_output<<std::endl;

    for(size_t i = 0; i < polygon.size(); i++)
    {
        for(size_t j = 0; j < polygon[i].size(); j++)
        {
            polygon_output << polygon[i][j] << " ";
        }
        polygon_output<<std::endl;
    }
}

}/* namespace optimization */
}/* namespace k52 */


/**
 * Copyright 2004-present Facebook. All Rights Reserved.
 *
 * This source code is licensed under the BSD-style license found in the
 * LICENSE file in the root directory of this source tree.
 */

const char* kUsage = R"(
  - Reads a point cloud as an ASCII file with a single point per line and generates a disparity
  image per camera.

  Supports multiple point cloud formats, but only extracts the xyz coordinates.

  The input file can have a single line header with a point count.

  - Example:
    ./ImportPointCloud \
    --output=/path/to/output \
    --rig=/path/to/rigs/rig.json \
    --point_cloud=/path/to/points.xyz

    Where points.xyz may be of the form:

    10000
    -0.04503071680665016 -2.2521071434020996 4.965743541717529 1 90 104 136
    -0.005194493103772402 -2.323836088180542 4.938142776489258 1 94 110 143
    0.046292994171381 -2.2623345851898193 4.609960079193115 1 101 122 149
    ...
)";

#include <gflags/gflags.h>
#include <glog/logging.h>
#include <boost/format.hpp>
#include "source/conversion/PointCloudUtil.h"
#include "source/util/Camera.h"
#include "source/util/ImageUtil.h"
#include "source/util/SystemUtil.h"
#include "source/util/ThreadPool.h"

using namespace fb360_dep;
using namespace fb360_dep::image_util;
using namespace fb360_dep::point_cloud_util;

DEFINE_string(cameras, "", "comma-separated cameras to render (empty for all)");
DEFINE_double(max_depth, INFINITY, "ignore depths farther than this value (m)");
DEFINE_double(min_depth, 0, "ignore depths closer than this value (m)");
DEFINE_string(output, "", "output directory (required)");
DEFINE_string(point_cloud, "", "input point cloud (required)");
DEFINE_string(rig, "", "path to camera rig .json (required)");
DEFINE_int32(threads, -1, "number of threads (-1 = auto, 0 = none)");
DEFINE_int32(width, 1024, "width of output camera images (0 = size from rig file)");

void verifyInputs(const Camera::Rig& rig) {
  CHECK_NE(FLAGS_point_cloud, "");
  CHECK_NE(FLAGS_output, "");
  CHECK_GE(FLAGS_width, 0);
  CHECK_EQ(FLAGS_width % 2, 0) << "width must be a multiple of 2";
  CHECK_GT(rig.size(), 0);
}

void rescaleCameras(Camera::Rig& rig) {
  for (Camera& cam : rig) {
    if (FLAGS_width > 0) {
      int height = std::round(FLAGS_width * cam.resolution.y() / float(cam.resolution.x()));
      height += height % 2; // force even number of rows
      cam = cam.rescale({FLAGS_width, height});
	}

    LOG(INFO) << boost::format(
        "%1% output resolution: %2%x%3%") % cam.id % cam.resolution.x() % cam.resolution.y();
  }
}

std::vector<cv::Mat_<float>> projectPointsToCameras(
    const PointCloud& points,
    const Camera::Rig& rig) {
  LOG(INFO) << "Projecting points to cameras...";

  std::vector<cv::Mat_<float>> disparities;
  for (const Camera& cam : rig) {
    disparities.emplace_back(cam.resolution.y(), cam.resolution.x(), 0.0f);
  }

  ThreadPool threadPool(FLAGS_threads);
  const int threads = threadPool.getMaxThreads();

  // Evenly distribute lines across threads
  const int pointCount = points.size();
  const int pointsPerThread = float(pointCount) / threads;
  const int remain = pointCount % threads;

  for (int i = 0; i < threads; ++i) {
    threadPool.spawn([&, i] {
      const int begin =
          i < remain ? i * (pointsPerThread + 1) : pointCount - (threads - i) * pointsPerThread;
      const int end = begin + pointsPerThread + (i < remain);
      for (int j = begin; j < end; ++j) {
        // Project point to all cameras
        for (ssize_t i = 0; i < ssize(rig); ++i) {
          const Camera::Vector3& pWorld = points[j].coords;
          Camera::Vector2 pSrc;
          if (!rig[i].sees(pWorld, pSrc)) {
            continue; // Outside src FOV, ignore
          }
          cv::Mat_<float>& disparity = disparities[i];
          const int xSrc = math_util::clamp(int(std::round(pSrc.x())), 0, disparity.cols - 1);
          const int ySrc = math_util::clamp(int(std::round(pSrc.y())), 0, disparity.rows - 1);
          float depth = pWorld.norm();
          if (depth < FLAGS_min_depth || depth > FLAGS_max_depth) {
            depth = INFINITY;
          }
          disparity(ySrc, xSrc) =
              std::max(disparity(ySrc, xSrc), 1.0f / depth); // get closest value
        }
      }
    });
  }
  threadPool.join();

  return disparities;
}

void saveImages(const std::vector<cv::Mat_<float>>& disparities, const Camera::Rig& rig) {
  LOG(INFO) << "Saving images...";
  const filesystem::path dirOut = filesystem::path(FLAGS_output);
  for (ssize_t i = 0; i < ssize(rig); ++i) {
    const filesystem::path fn = dirOut / rig[i].id / "000000.png";
    filesystem::create_directories(fn.parent_path());
    cv_util::imwriteExceptionOnFail(fn, cv_util::convertTo<uint16_t>(disparities[i]));
  }
}

int main(int argc, char** argv) {
  gflags::SetUsageMessage(kUsage);
  system_util::initDep(argc, argv);

  CHECK_NE(FLAGS_rig, "");
  Camera::Rig rig = filterDestinations(Camera::loadRig(FLAGS_rig), FLAGS_cameras);

  verifyInputs(rig);
  rescaleCameras(rig);
  const int pointCount = getPointCount(FLAGS_point_cloud);
  const PointCloud points = extractPoints(FLAGS_point_cloud, pointCount, FLAGS_threads);
  const std::vector<cv::Mat_<float>> disparities = projectPointsToCameras(points, rig);
  saveImages(disparities, rig);

  return EXIT_SUCCESS;
}


#include <StdInc.h>

// TODOs:
// - texture compression and repacking R/B channels
// - draco compression enablement
// - animations
// - skeletons
// - material improvements for PBR
//   - spec conversion
// - RAGE materials
// - #map/#typ exporting (text at first)

#ifdef GTA_FIVE
#include <d3d9.h>

#define RAGE_FORMATS_GAME five
#define RAGE_FORMATS_GAME_FIVE
#include <gtaDrawable.h>
#include <phBound.h>
#include <fragType.h>

#include "ToolComponentHelpers.h"

#include <json.hpp>
#include <tiny_gltf.h>

#include <boost/filesystem.hpp>
#include <DirectXMath.h>

#include <ShaderInfo.h>

static DirectX::XMFLOAT3 ConvertTranslation(const std::vector<double>& pos)
{
	if (pos.empty())
	{
		return {
			0.0f, 0.0f, 0.0f
		};
	}

	return {
		-static_cast<float>(pos[0]),
		static_cast<float>(pos[2]),
		static_cast<float>(pos[1])
	};
}

static DirectX::XMFLOAT4 ConvertQuaternion(const std::vector<double>& quat)
{
	if (quat.empty())
	{
		return {
			0.0f, 0.0f, 0.0f, 1.0f
		};
	}

	return {
		-static_cast<float>(quat[0]),
		static_cast<float>(quat[2]),
		static_cast<float>(quat[1]),
		static_cast<float>(quat[3])
	};
}

static DirectX::XMFLOAT3 ConvertScale(const std::vector<double>& scale)
{
	if (scale.empty())
	{
		return {
			1.0f, 1.0f, 1.0f
		};
	}

	return {
		static_cast<float>(scale[0]),
		static_cast<float>(scale[2]),
		static_cast<float>(scale[1])
	};
}

static DirectX::XMMATRIX ConvertNodeMatrix(const tinygltf::Node& node)
{
	auto translation = ConvertTranslation(node.translation);
	auto rotation = ConvertQuaternion(node.rotation);
	auto scale = ConvertScale(node.scale);

	if (!node.matrix.empty())
	{
		DirectX::XMFLOAT4X4 mat;
		for (int x = 0; x < 4; x++)
		{
			for (int y = 0; y < 4; y++)
			{
				mat.m[x][y] = static_cast<float>(node.matrix[(x * 4) + y]);
			}
		}

		// decompose and run conversion ops
		DirectX::XMVECTOR scaleVec;
		DirectX::XMVECTOR rotationVec;
		DirectX::XMVECTOR translationVec;
		DirectX::XMMatrixDecompose(&scaleVec, &rotationVec, &translationVec, DirectX::XMLoadFloat4x4(&mat));

		std::vector<double> scaleD(3);
		std::vector<double> rotationD(4);
		std::vector<double> translationD(3);

		scaleD[0] = DirectX::XMVectorGetX(scaleVec);
		scaleD[1] = DirectX::XMVectorGetY(scaleVec);
		scaleD[2] = DirectX::XMVectorGetZ(scaleVec);

		rotationD[0] = DirectX::XMVectorGetX(rotationVec);
		rotationD[1] = DirectX::XMVectorGetY(rotationVec);
		rotationD[2] = DirectX::XMVectorGetZ(rotationVec);
		rotationD[3] = DirectX::XMVectorGetW(rotationVec);

		translationD[0] = DirectX::XMVectorGetX(translationVec);
		translationD[1] = DirectX::XMVectorGetY(translationVec);
		translationD[2] = DirectX::XMVectorGetZ(translationVec);

		translation = ConvertTranslation(translationD);
		rotation = ConvertQuaternion(rotationD);
		scale = ConvertScale(scaleD);
	}

	return DirectX::XMMatrixAffineTransformation(DirectX::XMLoadFloat3(&scale), DirectX::XMVectorSet(0.0f, 0.0f, 0.0f, 0.0f), DirectX::XMLoadFloat4(&rotation), DirectX::XMLoadFloat3(&translation));
}

template<typename T>
static bool OutputFile(T&& callback, int fileVersion, const std::wstring& fileName)
{
	auto bm = rage::five::pgStreamManager::BeginPacking();

	callback();

	rage::five::pgStreamManager::EndPacking();

	FILE* f = _wfopen(fileName.c_str(), L"wb");

	if (!f)
	{
		printf("... couldn't open output file for writing.\n");
		return false;
	}

	size_t outputSize = 0;

	bm->Save(fileVersion, [&](const void* d, size_t s)
	{
		fwrite(d, 1, s, f);

		outputSize += s;
	});

	wprintf(L"written %s successfully - compressed size %d\n", boost::filesystem::path(fileName).filename().c_str(), outputSize);

	fclose(f);

	return true;
}

enum class grcDataSize
{
	Nothing = 0,
	Float16_2,
	Float,
	Float16_4,
	Float_unk,
	Float2,
	Float3,
	Float4,
	UByte4,
	Color,
	Dec3N,
	UShort4 = 15
};

static rage::five::grmGeometryQB* ConvertGeometry(const tinygltf::Model& model, const tinygltf::Primitive& geometry, const DirectX::XMMATRIX& scaleMatrix, int boneIdx = -1)
{
	DirectX::XMVECTOR scale;
	DirectX::XMVECTOR rotQuat;
	DirectX::XMVECTOR trans;
	DirectX::XMMatrixDecompose(&scale, &rotQuat, &trans, scaleMatrix);
	auto rotMatrix = DirectX::XMMatrixAffineTransformation(DirectX::XMVectorSet(1.0f, 1.0f, 1.0f, 1.0f), DirectX::XMVectorZero(), rotQuat, DirectX::XMVectorZero());

	rage::five::grmGeometryQB* gameGeom = new (false) rage::five::grmGeometryQB();

	// make index buffer
	{
		const tinygltf::Accessor& accessor = model.accessors[geometry.indices];
		uint32_t numIndices = accessor.count;

		const tinygltf::BufferView& bufferView = model.bufferViews[accessor.bufferView];
		const tinygltf::Buffer& buffer = model.buffers[bufferView.buffer];

		std::vector<uint16_t> indices(numIndices);
		if (accessor.componentType == TINYGLTF_COMPONENT_TYPE_UNSIGNED_SHORT)
		{
			memcpy(&indices[0], (uint16_t*)&buffer.data[bufferView.byteOffset], numIndices * 2);
		}
		else if (accessor.componentType == TINYGLTF_COMPONENT_TYPE_UNSIGNED_BYTE)
		{
			uint8_t* start = (uint8_t*)&buffer.data[bufferView.byteOffset];

			for (int i = 0; i < numIndices; i++)
			{
				indices[i] = start[i];
			}
		}

		// flip?
		/*for (int i = 0; i < numIndices; i += 3)
		{
			std::swap(indices[i + 1], indices[i + 2]);
		}*/

		rage::five::grcIndexBufferD3D* indexBuffer = new (false) rage::five::grcIndexBufferD3D(numIndices, &indices[0]);
		gameGeom->SetIndexBuffer(indexBuffer);
	}

	{
		rage::five::grcVertexBufferD3D* vertexBuffer = new (false) rage::five::grcVertexBufferD3D();

		// build a fitting (interleaved) FVF
		uint64_t dataSizes = 0;
		uint32_t bits = 0;
		uint32_t vertexSize = 0;
		uint8_t numFields = 0;

		size_t count = 0;

		static const std::map<std::string, int> attributeRefs = {
			{ "POSITION", 0	},
			{ "WEIGHTS_0", 1 },
			{ "JOINTS_0", 2 },
			{ "NORMAL", 3 },
			{ "COLOR_0", 4 },
			{ "TEXCOORD_0", 6 },
			{ "TEXCOORD_1", 7 },
			{ "TANGENT", 14 },
		};

		static const std::map<std::tuple<int, int>, grcDataSize> typeRefs = {
			{ { TINYGLTF_TYPE_VEC4, TINYGLTF_COMPONENT_TYPE_BYTE }, grcDataSize::UByte4 },
			{ { TINYGLTF_TYPE_VEC4, TINYGLTF_COMPONENT_TYPE_UNSIGNED_BYTE }, grcDataSize::UByte4 },
			{ { TINYGLTF_TYPE_VEC4, TINYGLTF_COMPONENT_TYPE_UNSIGNED_SHORT }, grcDataSize::UShort4 },
			{ { TINYGLTF_TYPE_SCALAR, TINYGLTF_COMPONENT_TYPE_FLOAT }, grcDataSize::Float },
			{ { TINYGLTF_TYPE_VEC2, TINYGLTF_COMPONENT_TYPE_FLOAT }, grcDataSize::Float2 },
			{ { TINYGLTF_TYPE_VEC3, TINYGLTF_COMPONENT_TYPE_FLOAT }, grcDataSize::Float3 },
			{ { TINYGLTF_TYPE_VEC4, TINYGLTF_COMPONENT_TYPE_FLOAT }, grcDataSize::Float4 },
		};

		auto increaseSize = [](grcDataSize dataSize, uint32_t& vertexSize)
		{
			switch (dataSize)
			{
				case grcDataSize::Float16_2:
					vertexSize += 4;
					break;
				case grcDataSize::Float:
					vertexSize += 4;
					break;
				case grcDataSize::Float16_4:
					vertexSize += 8;
					break;
				case grcDataSize::Float_unk:
					vertexSize += 4;
					break;
				case grcDataSize::Float2:
					vertexSize += 8;
					break;
				case grcDataSize::Float3:
					vertexSize += 12;
					break;
				case grcDataSize::Float4:
					vertexSize += 16;
					break;
				case grcDataSize::UByte4:
					vertexSize += 4;
					break;
				case grcDataSize::Color:
					vertexSize += 4;
					break;
				case grcDataSize::Dec3N:
					vertexSize += 4;
					break;
			}
		};

		const tinygltf::Accessor* accessors[16] = { 0 };

		for (const auto& entry : geometry.attributes)
		{
			const tinygltf::Accessor& accessor = model.accessors[entry.second];

			auto attrRefIt = attributeRefs.find(entry.first);

			if (attrRefIt == attributeRefs.end())
			{
				continue;
			}

			auto typeRefIt = typeRefs.find({ accessor.type, accessor.componentType });

			if (typeRefIt == typeRefs.end())
			{
				continue;
			}

			int fvfIndex = attrRefIt->second;
			accessors[fvfIndex] = &accessor;

			grcDataSize dataSize = typeRefIt->second;
			bits |= (1 << fvfIndex);
			dataSizes |= uint64_t(dataSize) << uint64_t(fvfIndex * 4);

			increaseSize(dataSize, vertexSize);

			count = accessor.count;
			numFields++;
		}

		// no color?
		if (!(bits & (1 << 4)))
		{
			bits |= (1 << 4);
			dataSizes |= uint64_t(grcDataSize::Color) << uint64_t(4 * 4);
			vertexSize += 4;
		}

		// no texcoord0?
		if (!(bits & (1 << 6)))
		{
			bits |= (1 << 6);
			dataSizes |= uint64_t(grcDataSize::Float2) << uint64_t(6 * 4);
			vertexSize += 8;
		}

		// no blend weights (for complex hierarchy)?
		if (!bits & (1 << 1))
		{
			bits |= (1 << 1);
			dataSizes |= uint64_t(grcDataSize::UByte4) << uint64_t(1 * 4);
			vertexSize += 4;
		}

		if (!bits & (1 << 2))
		{
			bits |= (1 << 2);
			dataSizes |= uint64_t(grcDataSize::UByte4) << uint64_t(2 * 4);
			vertexSize += 4;
		}

		// build vertex buffer
		std::vector<uint8_t> vertices(vertexSize * count);
		uint32_t outOffset = 0;

		for (int fvfIndex = 0; fvfIndex < 16; fvfIndex++)
		{
			if (!(bits & (1 << fvfIndex)))
			{
				continue;
			}

			if (!accessors[fvfIndex])
			{
				// make up colors
				if (fvfIndex == 4)
				{
					auto outData = &vertices[outOffset];

					for (uint32_t vert = 0; vert < count; vert++)
					{
						*(uint32_t*)outData = 0xffffffff;
						outData += vertexSize;
					}

					outOffset += 4;
				}
				else if (fvfIndex == 6)
				{
					auto outData = &vertices[outOffset];

					for (uint32_t vert = 0; vert < count; vert++)
					{
						*(float*)outData = 0.0f;
						*((float*)outData + 1) = 0.0f;
						outData += vertexSize;
					}

					outOffset += 8;
				}
				else if (fvfIndex == 1)
				{
					auto outData = &vertices[outOffset];

					for (uint32_t vert = 0; vert < count; vert++)
					{
						*(uint32_t*)outData = 0x000000FF;
						outData += vertexSize;
					}

					outOffset += 4;
				}
				else if (fvfIndex == 2)
				{
					auto outData = &vertices[outOffset];

					for (uint32_t vert = 0; vert < count; vert++)
					{
						*(uint32_t*)outData = 0x00000000 | (uint8_t)boneIdx;
						outData += vertexSize;
					}

					outOffset += 4;
				}

				continue;
			}

			const auto& accessor = *accessors[fvfIndex];

			grcDataSize dataSize = (grcDataSize)((dataSizes >> uint64_t(fvfIndex * 4)) & 0xF);

			const tinygltf::BufferView& bufferView = model.bufferViews[accessor.bufferView];
			const tinygltf::Buffer& buffer = model.buffers[bufferView.buffer];

			auto inStride = accessor.ByteStride(bufferView);
			const uint8_t* inData = &buffer.data[bufferView.byteOffset];
			uint8_t* outData = &vertices[outOffset];

			uint32_t compSize = 0;
			increaseSize(dataSize, compSize);

			for (uint32_t vert = 0; vert < accessor.count; vert++)
			{
				memcpy(outData, inData, compSize);

				inData += inStride;
				outData += vertexSize;
			}

			// convert coordsys
			if (fvfIndex == 0 || fvfIndex == 3 || fvfIndex == 14)
			{
				outData = &vertices[outOffset];

				for (uint32_t vert = 0; vert < accessor.count; vert++)
				{
					float* dataRef = (float*)outData;
					std::swap(dataRef[1], dataRef[2]);
					dataRef[0] = -dataRef[0];

					outData += vertexSize;
				}

				// scale positions
				if (fvfIndex == 0)
				{
					outData = &vertices[outOffset];

					for (uint32_t vert = 0; vert < accessor.count; vert++)
					{
						float* dataRef = (float*)outData;
						DirectX::XMStoreFloat3((DirectX::XMFLOAT3*)dataRef, DirectX::XMVector3TransformCoord(DirectX::XMLoadFloat3((DirectX::XMFLOAT3*)dataRef), scaleMatrix));

						outData += vertexSize;
					}
				}
				else if (fvfIndex == 3 || fvfIndex == 14)
				{
					outData = &vertices[outOffset];

					for (uint32_t vert = 0; vert < accessor.count; vert++)
					{
						float* dataRef = (float*)outData;
						DirectX::XMStoreFloat3((DirectX::XMFLOAT3*)dataRef, DirectX::XMVector3TransformNormal(DirectX::XMLoadFloat3((DirectX::XMFLOAT3*)dataRef), rotMatrix));

						outData += vertexSize;
					}
				}
			}

			outOffset += compSize;
		}

		rage::five::grcVertexFormat* fvf = new (false) rage::five::grcVertexFormat(bits, vertexSize, numFields, dataSizes);
		vertexBuffer->SetVertices(count, vertexSize, &vertices[0]);
		vertexBuffer->SetVertexFormat(fvf);

		gameGeom->SetVertexBuffer(vertexBuffer);
	}

	return gameGeom;
}

static rage::five::grcTexturePC* MakeTexture(const std::vector<double>& factor, rage::five::pgDictionary<rage::five::grcTexturePC>* txd)
{
	uint8_t a, r, g, b;
	r = uint8_t(factor[0] * 255.0);
	g = uint8_t(factor[1] * 255.0);
	b = uint8_t(factor[2] * 255.0);
	a = uint8_t(factor[3] * 255.0);

	auto imageName = fmt::sprintf("c_%02X%02X%02X%02X", r, g, b, a);

	if (txd->Get(imageName.c_str()))
	{
		return txd->Get(imageName.c_str());
	}

	uint32_t color = (a << 24) | (r << 16) | (g << 8) | (b << 0);

	uint16_t width = 1;
	uint16_t height = 1;

	int format = D3DFMT_A8R8G8B8;

	auto tex = new (false) rage::five::grcTexturePC(
	width,
	height,
	format,
	4,
	1,
	&color);

	tex->SetName(imageName.c_str());
	txd->Add(imageName.c_str(), tex);

	return tex;
}

static rage::five::grcTexturePC* ConvertTexture(const tinygltf::Model& model, int textureIdx, rage::five::pgDictionary<rage::five::grcTexturePC>* txd)
{
	const tinygltf::Texture& texture = model.textures[textureIdx];
	const tinygltf::Image& image = model.images[texture.source];

	auto imageName = image.name;

	if (imageName.empty())
	{
		imageName = image.uri;
	}

	if (imageName.empty())
	{
		imageName = fmt::sprintf("t%d", rand());
	}

	if (txd->Get(imageName.c_str()))
	{
		return txd->Get(imageName.c_str());
	}

	assert(!image.as_is);

	uint16_t width = image.width;
	uint16_t height = image.height;

	int format = D3DFMT_UNKNOWN;

	if (image.bits == 8)
	{
		if (image.component == 4)
		{
			format = D3DFMT_A8B8G8R8;
		}
		else if (image.component == 3)
		{
			format = D3DFMT_R8G8B8;
		}
		else if (image.component == 2)
		{
			format = D3DFMT_A8L8;
		}
		else if (image.component == 1)
		{
			format = D3DFMT_L8;
		}
	}
	else if (image.bits == 16)
	{
		if (image.component == 4)
		{
			format = D3DFMT_A16B16G16R16;
		}
		else if (image.component == 3)
		{
			//??
		}
		else if (image.component == 2)
		{
			format = D3DFMT_G16R16;
		}
		else if (image.component == 1)
		{
			format = D3DFMT_L16;
		}
	}

	auto tex = new (false) rage::five::grcTexturePC(
	width,
	height,
	format,
	(image.bits * image.component * image.width) / 8,
	1, // TODO: gen mips!
	&image.image[0]);

	tex->SetName(imageName.c_str());
	txd->Add(imageName.c_str(), tex);

	return tex;
}

static rage::five::grmShaderFx* ConvertShader(const tinygltf::Model& model, int materialIdx, rage::five::pgDictionary<rage::five::grcTexturePC>* txd)
{
	const tinygltf::Material& material = model.materials[materialIdx];

	bool hasNormal = material.normalTexture.index >= 0;
	bool hasEmissive = material.emissiveTexture.index >= 0;
	bool hasAlbedo = material.pbrMetallicRoughness.baseColorTexture.index >= 0;
	// we don't do specular *yet*

	std::string shaderPreset = "default.sps";

	if (hasNormal)
	{
		shaderPreset = "normal.sps";
	}

	if (hasEmissive)
	{
		shaderPreset = "emissive.sps";
	}

	auto spsFile = fxc::SpsFile::Load(MakeRelativeCitPath(fmt::sprintf(L"citizen\\shaders\\db\\%s", ToWide(shaderPreset))));
	std::string shaderName;

	if (spsFile)
	{
		shaderName = spsFile->GetShader();
	}

	rage::five::grmShaderFx* shader = new (false) rage::five::grmShaderFx();
	shader->DoPreset(shaderName.c_str(), shaderPreset.c_str());

	// emissive trumps all
	if (hasEmissive)
	{
		shader->SetParameter("DiffuseSampler", ConvertTexture(model, material.emissiveTexture.index, txd));

		float emissiveMult = float(material.emissiveFactor[0] + material.emissiveFactor[1] + material.emissiveFactor[2]) / 3.0f;
		shader->SetParameter("emissiveMultiplier", &emissiveMult, sizeof(emissiveMult));
	}
	else
	{
		if (hasNormal)
		{
			shader->SetParameter("BumpSampler", ConvertTexture(model, material.normalTexture.index, txd));

			float bumpiness = (float)material.normalTexture.scale;
			shader->SetParameter("bumpiness", &bumpiness, sizeof(bumpiness));
		}

		if (!hasAlbedo)
		{
			shader->SetParameter("DiffuseSampler", MakeTexture(material.pbrMetallicRoughness.baseColorFactor, txd));
		}
		else
		{
			shader->SetParameter("DiffuseSampler", ConvertTexture(model, material.pbrMetallicRoughness.baseColorTexture.index, txd));
		}
	}

	return shader;

	//shaders[0] = new (false) rage::five::grmShaderFx();
	//shaders[0]->DoPreset("default", "default.sps");
	//shaders[0]->SetParameter("DiffuseSampler", "dreamy");
}

struct GeomSetupContext
{
	bool hadSkin = false;
	int geomIndex = 0;
	int boneIndex = 0;

	rage::five::grmGeometryQB* geometries[512];
	uint16_t shaderMappings[512];
	rage::five::GeometryBound gbs[512];

	rage::five::crBone bones[256];
	std::map<int, int> boneMapping;
};

#if 0
static void ProcessMesh(const tinygltf::Model& model, const std::string& nodeName, int meshIdx, const DirectX::XMMATRIX& matrix)
{
	const tinygltf::Mesh& mesh = model.meshes[meshIdx];

	std::string meshName = mesh.name;

	if (!meshName.empty())
	{
		meshName = nodeName;
	}

	OutputFile([&]()
	{
		auto out = new (false) rage::five::gtaDrawable;
		out->SetBlockMap();

		out->SetName(fmt::format("{}.#dr", nodeName).c_str());
		auto& lodGroup = out->GetLodGroup();

		rage::five::grmModel* gameModel = new (false) rage::five::grmModel;

		int i = 0;

		for (auto& geom : mesh.primitives)
		{
			geometries[i] = ConvertGeometry(model, geom, matrix);
			shaderMappings[i] = 0;

			i++;
		}

		gameModel->SetGeometries(i, geometries);
		gameModel->SetGeometryBounds(i, gbs);

		lodGroup.SetModel(0, gameModel);
		lodGroup.SetDrawBucketMask(0, 0xff01);
		lodGroup.SetMaxPoint({ 9999.0f, 9999.0f, 9999.0f, 9999.0f });

		// set up shaders
		rage::five::pgPtr<rage::five::grmShaderFx> shaders[512];
		//shaders[0] = new (false) rage::five::grmShaderFx();
		//shaders[0]->DoPreset("default", "default.sps");
		//shaders[0]->SetParameter("DiffuseSampler", "dreamy");

		i = 0;
		int shaderIdx = 0;

		std::map<int, int> materialsToShaders;
		rage::five::pgDictionary<rage::five::grcTexturePC> txd;

		for (auto& geom : mesh.primitives)
		{
			if (materialsToShaders.find(geom.material) != materialsToShaders.end())
			{
				shaderMappings[i] = materialsToShaders[geom.material];

				i++;
				continue;
			}

			auto materialIdx = geom.material;
			shaders[shaderIdx] = ConvertShader(model, geom.material, &txd);

			materialsToShaders[materialIdx] = shaderIdx;
			shaderMappings[i] = shaderIdx;

			shaderIdx++;
			i++;
		}

		rage::five::grmShaderGroup* shaderGroup = new (false) rage::five::grmShaderGroup();
		shaderGroup->SetShaders(shaderIdx, shaders);

		if (txd.GetCount())
		{
			rage::five::pgDictionary<rage::five::grcTexturePC>* rtxd = new (false) rage::five::pgDictionary<rage::five::grcTexturePC>();
			rtxd->SetBlockMap();

			rtxd->SetFrom(&txd);

			shaderGroup->SetTextures(rtxd);
		}

		gameModel->SetShaderMappings(i, shaderMappings);

		out->SetPrimaryModel();
		out->SetShaderGroup(shaderGroup);
	}, 165, ToWide(nodeName + ".ydr"));
}
#endif

static void AddMesh(GeomSetupContext& context, const tinygltf::Model& model, int meshIdx, const DirectX::XMMATRIX& matrix, int boneIdx = -1)
{
	const tinygltf::Mesh& mesh = model.meshes[meshIdx];

	for (auto& geom : mesh.primitives)
	{
		context.geometries[context.geomIndex] = ConvertGeometry(model, geom, matrix, boneIdx);
		context.shaderMappings[context.geomIndex] = geom.material;

		context.geomIndex++;
	}
}

static int ProcessNodesForSkeleton(GeomSetupContext& ctx, const tinygltf::Model& model, int nodeIdx, int parentIdx = -1, DirectX::XMMATRIX parentMatrix = DirectX::XMMatrixIdentity())
{
	using namespace DirectX;

	const tinygltf::Node& node = model.nodes[nodeIdx];

	std::string nodeName = node.name;

	if (nodeName.empty())
	{
		nodeName = fmt::format("node_{}", nodeIdx);
	}

	auto matrix = ConvertNodeMatrix(node);
	auto fullMatrix = parentMatrix * matrix;

	DirectX::XMVECTOR scale;
	DirectX::XMVECTOR rotQuat;
	DirectX::XMVECTOR trans;
	DirectX::XMMatrixDecompose(&scale, &rotQuat, &trans, matrix);

	rage::Vector4 rotationV;
	rage::Vector3 transV;
	rage::Vector3 scaleV;

	scaleV.x = DirectX::XMVectorGetX(scale);
	scaleV.y = DirectX::XMVectorGetY(scale);
	scaleV.z = DirectX::XMVectorGetZ(scale);

	rotationV.x = DirectX::XMVectorGetX(rotQuat);
	rotationV.y = DirectX::XMVectorGetY(rotQuat);
	rotationV.z = DirectX::XMVectorGetZ(rotQuat);
	rotationV.w = DirectX::XMVectorGetW(rotQuat);

	transV.x = DirectX::XMVectorGetX(trans);
	transV.y = DirectX::XMVectorGetY(trans);
	transV.z = DirectX::XMVectorGetZ(trans);

	using rage::five::crBoneFlags;

	int siblingIdx = -1;

	int bi = ctx.boneIndex++;
	ctx.bones[bi].Init(nodeName.c_str(), bi, (crBoneFlags)(crBoneFlags::RotX | crBoneFlags::RotY | crBoneFlags::RotZ | crBoneFlags::TransX | crBoneFlags::TransY | crBoneFlags::TransZ), rotationV, scaleV, transV, parentIdx, siblingIdx);

	ctx.boneMapping[nodeIdx] = bi;

	int lastIdx = -1;

	for (int c = 0; c < node.children.size(); c++)
	{
		int childIdx = node.children[c];
		int idx = ProcessNodesForSkeleton(ctx, model, childIdx, nodeIdx, fullMatrix);

		if (lastIdx >= 0)
		{
			ctx.bones[lastIdx].SetSiblingIndex(idx);
		}

		lastIdx = idx;
	}

	return bi;
}

static void ProcessNode(const tinygltf::Model& model, int nodeIdx, const std::string& sceneName, int depth = 0, DirectX::XMMATRIX parentMatrix = DirectX::XMMatrixIdentity(), DirectX::XMMATRIX invRootMatrix = DirectX::XMMatrixIdentity())
{
	using namespace DirectX;

	const tinygltf::Node& node = model.nodes[nodeIdx];

	auto matrix = ConvertNodeMatrix(node);
	auto fullMatrix = parentMatrix * matrix;

	if (depth == 0)
	{
		DirectX::XMVECTOR scale;
		DirectX::XMVECTOR rotQuat;
		DirectX::XMVECTOR trans;
		DirectX::XMMatrixDecompose(&scale, &rotQuat, &trans, fullMatrix);

		auto noScaleMatrix = DirectX::XMMatrixAffineTransformation(DirectX::XMVectorSet(1.0f, 1.0f, 1.0f, 1.0f), DirectX::XMVectorZero(), rotQuat, trans);

		invRootMatrix = DirectX::XMMatrixInverse(NULL, noScaleMatrix);
	}

	std::string nodeName = node.name;

	if (nodeName.empty())
	{
		nodeName = fmt::format("{}_{}", sceneName, nodeIdx);
	}

	static thread_local GeomSetupContext* geomCtx;

	auto processEntry = [&]()
	{
		if (node.mesh >= 0)
		{
			AddMesh(*geomCtx, model, node.mesh, invRootMatrix * fullMatrix, geomCtx->boneMapping[nodeIdx]);
		}

		for (int nodeIdx : node.children)
		{
			ProcessNode(model, nodeIdx, sceneName, depth + 1, fullMatrix, invRootMatrix);
		}
	};

	if (depth == 0)
	{
		geomCtx = new GeomSetupContext();

		OutputFile([&]()
		{
			auto out = new (false) rage::five::gtaDrawable;
			out->SetBlockMap();

			out->SetName(fmt::format("{}.#dr", nodeName).c_str());

			ProcessNodesForSkeleton(*geomCtx, model, nodeIdx);

			auto& lodGroup = out->GetLodGroup();

			rage::five::grmModel* gameModel = new (false) rage::five::grmModel;

			processEntry();

			gameModel->SetGeometries(geomCtx->geomIndex, geomCtx->geometries);
			gameModel->SetGeometryBounds(geomCtx->geomIndex, geomCtx->gbs);

			lodGroup.SetModel(0, gameModel);
			lodGroup.SetDrawBucketMask(0, 0xff01);
			lodGroup.SetMaxPoint({ 9999.0f, 9999.0f, 9999.0f, 9999.0f });

			// set up shaders
			rage::five::pgPtr<rage::five::grmShaderFx> shaders[512];

			int shaderIdx = 0;

			std::map<int, int> materialsToShaders;
			rage::five::pgDictionary<rage::five::grcTexturePC> txd;

			for (int i = 0; i < geomCtx->geomIndex;)
			{
				if (materialsToShaders.find(geomCtx->shaderMappings[i]) != materialsToShaders.end())
				{
					geomCtx->shaderMappings[i] = materialsToShaders[geomCtx->shaderMappings[i]];

					i++;
					continue;
				}

				auto materialIdx = geomCtx->shaderMappings[i];
				shaders[shaderIdx] = ConvertShader(model, materialIdx, &txd);

				materialsToShaders[materialIdx] = shaderIdx;
				geomCtx->shaderMappings[i] = shaderIdx;

				shaderIdx++;
				i++;
			}

			rage::five::grmShaderGroup* shaderGroup = new (false) rage::five::grmShaderGroup();
			shaderGroup->SetShaders(shaderIdx, shaders);

			if (txd.GetCount())
			{
				rage::five::pgDictionary<rage::five::grcTexturePC>* rtxd = new (false) rage::five::pgDictionary<rage::five::grcTexturePC>();
				rtxd->SetBlockMap();

				rtxd->SetFrom(&txd);

				shaderGroup->SetTextures(rtxd);
			}

			gameModel->SetShaderMappings(geomCtx->geomIndex, geomCtx->shaderMappings);

			auto skeleton = new (false) rage::five::crSkeletonData();
			skeleton->SetBones(geomCtx->boneIndex, geomCtx->bones);
			//out->SetSkeleton(skeleton);

			out->SetPrimaryModel();
			out->SetShaderGroup(shaderGroup);

			delete geomCtx;
		},
		165, ToWide(nodeName + ".ydr"));
	}
	else
	{
		processEntry();
	}
}

static void ConvertAsset(const boost::filesystem::path& path)
{
	tinygltf::Model model;
	std::string err, warn;

	// load the asset
	tinygltf::TinyGLTF loader;

	bool success = false;
	if (path.extension() == ".glb")
	{
		success = loader.LoadBinaryFromFile(&model, &err, &warn, ToNarrow(path.wstring()));
	}
	else
	{
		success = loader.LoadASCIIFromFile(&model, &err, &warn, ToNarrow(path.wstring()));
	}

	// check for success
	if (!warn.empty())
	{
		fmt::printf("WARN: %s\n", warn);
	}

	if (!err.empty())
	{
		fmt::printf("ERR: %s\n", err);
	}

	if (!success)
	{
		return;
	}

	if (model.defaultScene < 0)
	{
		fmt::printf("%s: Assets without default scene are not supported!\n", path.filename().string());
		return;
	}

	const tinygltf::Scene& scene = model.scenes[model.defaultScene];
	auto fn = path.filename();
	fn.replace_extension();

	std::string sceneName = scene.name.empty() ? fn.string() : scene.name;

	for (int nodeIdx : scene.nodes)
	{
		ProcessNode(model, nodeIdx, sceneName);
	}
}

static void HandleArguments(boost::program_options::wcommand_line_parser& parser, std::function<void()> cb)
{
	boost::program_options::options_description desc;

	desc.add_options()("filename", boost::program_options::value<std::vector<boost::filesystem::path>>()->required(), "The path of the file to convert.");

	boost::program_options::positional_options_description positional;
	positional.add("filename", -1);

	parser.options(desc).positional(positional);

	cb();
}

static void Run(const boost::program_options::variables_map& map)
{
	if (map.count("filename") == 0)
	{
		printf("Usage:\n\n   fivem formats:gltfImport *.gltf/*.glb...\n");
		return;
	}

	auto& entries = map["filename"].as<std::vector<boost::filesystem::path>>();

	for (auto& filePath : entries)
	{
		ConvertAsset(filePath);
	}
}

static FxToolCommand command("formats:gltfImport", HandleArguments, Run);
#endif


#include <boost/multiprecision/cpp_bin_float.hpp>
#include <boost/multiprecision/cpp_dec_float.hpp>
#include <chratos/node/common.hpp>
#include <chratos/node/stats.hpp>
#include <chratos/secure/blockstore.hpp>
#include <chratos/secure/ledger.hpp>

namespace
{
/**
 * Roll back the visited block
 */
class rollback_visitor : public chratos::block_visitor
{
public:
	rollback_visitor (chratos::transaction const & transaction_a, chratos::ledger & ledger_a) :
	transaction (transaction_a),
	ledger (ledger_a)
	{
	}
	virtual ~rollback_visitor () = default;

	void state_block (chratos::state_block const & block_a) override
	{
		auto hash (block_a.hash ());
		chratos::block_hash representative (0);
		if (!block_a.hashables.previous.is_zero ())
		{
			representative = ledger.representative (transaction, block_a.hashables.previous);
		}
		auto balance (ledger.balance (transaction, block_a.hashables.previous));
		auto is_send (block_a.hashables.balance < balance);
		// Add in amount delta
		ledger.store.representation_add (transaction, hash, 0 - block_a.hashables.balance.number ());
		if (!representative.is_zero ())
		{
			// Move existing representation
			ledger.store.representation_add (transaction, representative, balance);
		}

		chratos::account_info info;
		auto error (ledger.store.account_get (transaction, block_a.hashables.account, info));

		if (is_send)
		{
			chratos::pending_key key (block_a.hashables.link, hash);
			while (!ledger.store.pending_exists (transaction, key))
			{
				ledger.rollback (transaction, ledger.latest (transaction, block_a.hashables.link));
			}
			ledger.store.pending_del (transaction, key);
			ledger.stats.inc (chratos::stat::type::rollback, chratos::stat::detail::send);
		}
		else if (!block_a.hashables.link.is_zero () && !ledger.is_epoch_link (block_a.hashables.link))
		{
			auto source_version (ledger.store.block_version (transaction, block_a.hashables.link));
			chratos::pending_info pending_info (ledger.account (transaction, block_a.hashables.link), block_a.hashables.balance.number () - balance, block_a.hashables.dividend, source_version);
			ledger.store.pending_put (transaction, chratos::pending_key (block_a.hashables.account, block_a.hashables.link), pending_info);
			ledger.stats.inc (chratos::stat::type::rollback, chratos::stat::detail::receive);
		}

		assert (!error);
		auto previous_version (ledger.store.block_version (transaction, block_a.hashables.previous));
		ledger.change_latest (transaction, block_a.hashables.account, block_a.hashables.previous, representative, block_a.hashables.dividend, balance, info.block_count - 1, false, previous_version);

		auto previous (ledger.store.block_get (transaction, block_a.hashables.previous));
		if (previous != nullptr)
		{
			ledger.store.block_successor_clear (transaction, block_a.hashables.previous);
			if (previous->type () < chratos::block_type::state)
			{
				ledger.store.frontier_put (transaction, block_a.hashables.previous, block_a.hashables.account);
			}
		}
		else
		{
			ledger.stats.inc (chratos::stat::type::rollback, chratos::stat::detail::open);
		}
		ledger.store.block_del (transaction, hash);
	}
	void dividend_block (chratos::dividend_block const & block_a) override
	{
		auto hash (block_a.hash ());
	}
	void claim_block (chratos::claim_block const & block_a) override
	{
		auto hash (block_a.hash ());
	}
	chratos::transaction const & transaction;
	chratos::ledger & ledger;
};

class ledger_processor : public chratos::block_visitor
{
public:
	ledger_processor (chratos::ledger &, chratos::transaction const &, bool = false);
	virtual ~ledger_processor () = default;

	void state_block (chratos::state_block const &) override;
	void dividend_block (chratos::dividend_block const &) override;
	void claim_block (chratos::claim_block const &) override;

	void state_block_impl (chratos::state_block const &);
	void epoch_block_impl (chratos::state_block const &);
	chratos::ledger & ledger;
	chratos::transaction const & transaction;
	bool valid_signature;
	chratos::process_return result;
};

void ledger_processor::state_block (chratos::state_block const & block_a)
{
	result.code = chratos::process_result::progress;
	auto is_epoch_block (false);
	// Check if this is an epoch block
	if (!ledger.epoch_link.is_zero () && ledger.is_epoch_link (block_a.hashables.link))
	{
		chratos::amount prev_balance (0);
		if (!block_a.hashables.previous.is_zero ())
		{
			result.code = ledger.store.block_exists (transaction, block_a.hashables.previous) ? chratos::process_result::progress : chratos::process_result::gap_previous;
			if (result.code == chratos::process_result::progress)
			{
				prev_balance = ledger.balance (transaction, block_a.hashables.previous);
			}
		}
		if (block_a.hashables.balance == prev_balance)
		{
			is_epoch_block = true;
		}
	}
	if (result.code == chratos::process_result::progress)
	{
		if (is_epoch_block)
		{
			epoch_block_impl (block_a);
		}
		else
		{
			state_block_impl (block_a);
		}
	}
}

void ledger_processor::state_block_impl (chratos::state_block const & block_a)
{
	auto hash (block_a.hash ());
	auto existing (ledger.store.block_exists (transaction, hash));
	result.code = existing ? chratos::process_result::old : chratos::process_result::progress; // Have we seen this block before? (Unambiguous)
	if (result.code == chratos::process_result::progress)
	{
		result.code = validate_message (block_a.hashables.account, hash, block_a.signature) ? chratos::process_result::bad_signature : chratos::process_result::progress; // Is this block signed correctly (Unambiguous)
		if (result.code == chratos::process_result::progress)
		{
			result.code = block_a.hashables.account.is_zero () ? chratos::process_result::opened_burn_account : chratos::process_result::progress; // Is this for the burn account? (Unambiguous)
			if (result.code == chratos::process_result::progress)
			{
				chratos::epoch epoch (chratos::epoch::epoch_0);
				chratos::account_info info;
				result.amount = block_a.hashables.balance;
				auto is_send (false);
				auto account_error (ledger.store.account_get (transaction, block_a.hashables.account, info));
				if (!account_error)
				{
					epoch = info.epoch;
					result.code = block_a.hashables.previous.is_zero () ? chratos::process_result::fork : chratos::process_result::progress; // Has this account already been opened? (Ambigious)
					if (result.code == chratos::process_result::progress)
					{
						// Account already exists
						result.code = ledger.store.block_exists (transaction, block_a.hashables.previous) ? chratos::process_result::progress : chratos::process_result::gap_previous; // Does the previous block exist in the ledger? (Unambigious)
						if (result.code == chratos::process_result::progress)
						{
							is_send = block_a.hashables.balance < info.balance;

							result.amount = is_send ? (info.balance.number () - result.amount.number ()) : (result.amount.number () - info.balance.number ());

							result.code = block_a.hashables.previous == info.head ? chratos::process_result::progress : chratos::process_result::fork; // Is the previous block the account's head block? (Ambigious)
						}
					}
				}
				else
				{
					// Account does not yet exists
					result.code = block_a.previous ().is_zero () ? chratos::process_result::progress : chratos::process_result::gap_previous; // Does the first block in an account yield 0 for previous() ? (Unambigious)
					if (result.code == chratos::process_result::progress)
					{
						result.code = !block_a.hashables.link.is_zero () ? chratos::process_result::progress : chratos::process_result::gap_source; // Is the first block receiving from a send ? (Unambigious)
					}
				}
				if (result.code == chratos::process_result::progress)
				{
					if (!is_send)
					{
						if (!block_a.hashables.link.is_zero ())
						{
							result.code = ledger.store.block_exists (transaction, block_a.hashables.link) ? chratos::process_result::progress : chratos::process_result::gap_source; // Have we seen the source block already? (Harmless)

							if (result.code == chratos::process_result::progress)
							{
								// Make sure the dividend is ordered with the most recent
								if (info.head != chratos::dividend_base)
								{
									result.code = ledger.dividends_are_ordered (transaction, block_a.hashables.dividend, info.dividend_block) ? chratos::process_result::progress : chratos::process_result::unreceivable;
								}
								if (result.code == chratos::process_result::progress)
								{
									chratos::pending_key key (block_a.hashables.account, block_a.hashables.link);
									chratos::pending_info pending;
									result.code = ledger.store.pending_get (transaction, key, pending) ? chratos::process_result::unreceivable : chratos::process_result::progress; // Has this source already been received (Malformed)
									if (result.code == chratos::process_result::progress)
									{
										result.code = result.amount == pending.amount ? chratos::process_result::progress : chratos::process_result::balance_mismatch;
										epoch = std::max (epoch, pending.epoch);
									}
								}
							}
						}
						else
						{
							// If there's no link, the balance must remain the same, only the representative can change
							result.code = result.amount.is_zero () ? chratos::process_result::progress : chratos::process_result::balance_mismatch;
						}
					}
					else if (is_send)
					{
						result.code = (info.dividend_block == block_a.hashables.dividend) ? chratos::process_result::progress : chratos::process_result::incorrect_dividend;
					}
				}
				if (result.code == chratos::process_result::progress)
				{
					ledger.stats.inc (chratos::stat::type::ledger, chratos::stat::detail::state_block);
					result.state_is_send = is_send;
					ledger.store.block_put (transaction, hash, block_a, 0, epoch);

					if (!info.rep_block.is_zero ())
					{
						// Move existing representation
						ledger.store.representation_add (transaction, info.rep_block, 0 - info.balance.number ());
					}
					// Add in amount delta
					ledger.store.representation_add (transaction, hash, block_a.hashables.balance.number ());

					if (is_send)
					{
						chratos::pending_key key (block_a.hashables.link, hash);
						chratos::pending_info info (block_a.hashables.account, result.amount.number (), block_a.hashables.dividend, epoch);
						ledger.store.pending_put (transaction, key, info);
					}
					else if (!block_a.hashables.link.is_zero ())
					{
						ledger.store.pending_del (transaction, chratos::pending_key (block_a.hashables.account, block_a.hashables.link));
					}

					ledger.change_latest (transaction, block_a.hashables.account, hash, hash, block_a.hashables.dividend, block_a.hashables.balance, info.block_count + 1, true, epoch);
					if (!ledger.store.frontier_get (transaction, info.head).is_zero ())
					{
						ledger.store.frontier_del (transaction, info.head);
					}
					// Frontier table is unnecessary for state blocks and this also prevents old blocks from being inserted on top of state blocks
					result.account = block_a.hashables.account;
				}
			}
		}
	}
}

void ledger_processor::epoch_block_impl (chratos::state_block const & block_a)
{
	auto hash (block_a.hash ());
	auto existing (ledger.store.block_exists (transaction, hash));
	result.code = existing ? chratos::process_result::old : chratos::process_result::progress; // Have we seen this block before? (Unambiguous)
	if (result.code == chratos::process_result::progress)
	{
		result.code = validate_message (ledger.epoch_signer, hash, block_a.signature) ? chratos::process_result::bad_signature : chratos::process_result::progress; // Is this block signed correctly (Unambiguous)
		if (result.code == chratos::process_result::progress)
		{
			result.code = block_a.hashables.account.is_zero () ? chratos::process_result::opened_burn_account : chratos::process_result::progress; // Is this for the burn account? (Unambiguous)
			if (result.code == chratos::process_result::progress)
			{
				chratos::account_info info;
				auto account_error (ledger.store.account_get (transaction, block_a.hashables.account, info));
				if (!account_error)
				{
					// Account already exists
					result.code = block_a.hashables.previous.is_zero () ? chratos::process_result::fork : chratos::process_result::progress; // Has this account already been opened? (Ambigious)
					if (result.code == chratos::process_result::progress)
					{
						result.code = ledger.store.block_exists (transaction, block_a.hashables.previous) ? chratos::process_result::progress : chratos::process_result::gap_previous; // Does the previous block exist in the ledger? (Unambigious)
						if (result.code == chratos::process_result::progress)
						{
							result.code = block_a.hashables.previous == info.head ? chratos::process_result::progress : chratos::process_result::fork; // Is the previous block the account's head block? (Ambigious)
							if (result.code == chratos::process_result::progress)
							{
								auto last_rep_block (ledger.store.block_get (transaction, info.rep_block));
								assert (last_rep_block != nullptr);
								result.code = block_a.hashables.representative == last_rep_block->representative () ? chratos::process_result::progress : chratos::process_result::representative_mismatch;
							}
						}
					}
				}
				else
				{
					result.code = block_a.hashables.representative.is_zero () ? chratos::process_result::progress : chratos::process_result::representative_mismatch;
				}
				if (result.code == chratos::process_result::progress)
				{
					result.code = info.epoch == chratos::epoch::epoch_0 ? chratos::process_result::progress : chratos::process_result::block_position;
					if (result.code == chratos::process_result::progress)
					{
						result.code = block_a.hashables.balance == info.balance ? chratos::process_result::progress : chratos::process_result::balance_mismatch;
						if (result.code == chratos::process_result::progress)
						{
							ledger.stats.inc (chratos::stat::type::ledger, chratos::stat::detail::epoch_block);
							result.account = block_a.hashables.account;
							result.amount = 0;
							ledger.store.block_put (transaction, hash, block_a, 0, chratos::epoch::epoch_1);
							ledger.change_latest (transaction, block_a.hashables.account, hash, hash, block_a.hashables.dividend, info.balance, info.block_count + 1, true, chratos::epoch::epoch_1);
							if (!ledger.store.frontier_get (transaction, info.head).is_zero ())
							{
								ledger.store.frontier_del (transaction, info.head);
							}
						}
					}
				}
			}
		}
	}
}

void ledger_processor::dividend_block (chratos::dividend_block const & block_a)
{
	auto hash (block_a.hash ());
	auto existing (ledger.store.block_exists (transaction, hash));
	result.code = existing ? chratos::process_result::old : chratos::process_result::progress; // Have we seen this block before? (Harmless)
	if (result.code == chratos::process_result::progress)
	{
		std::shared_ptr<chratos::block> previous (ledger.store.block_get (transaction, block_a.hashables.previous));
		result.code = previous != nullptr ? chratos::process_result::progress : chratos::process_result::gap_previous; // Have we seen the previous block already? (Harmless)
		if (result.code == chratos::process_result::progress)
		{
			auto account = block_a.hashables.account;
			result.code = account.is_zero () ? chratos::process_result::fork : chratos::process_result::progress;
			if (result.code == chratos::process_result::progress)
			{
				// Here is the dividend account check.
				result.code = account == chratos::dividend_account ? chratos::process_result::progress : chratos::process_result::invalid_dividend_account;
				if (result.code == chratos::process_result::progress)
				{
					result.code = validate_message (account, hash, block_a.signature) ? chratos::process_result::bad_signature : chratos::process_result::progress; // Is this block signed correctly (Malformed)
					if (result.code == chratos::process_result::progress)
					{
						chratos::account_info info;
						auto latest_error (ledger.store.account_get (transaction, account, info));
						assert (!latest_error);
						assert (info.head == block_a.hashables.previous);
						result.code = info.balance.number () >= block_a.hashables.balance.number () ? chratos::process_result::progress : chratos::process_result::negative_spend; // Is this trying to spend a negative amount (Malicious)
						if (result.code == chratos::process_result::progress)
						{
							auto amount (info.balance.number () - block_a.hashables.balance.number ());
							result.code = amount > chratos::minimum_dividend_amount ? chratos::process_result::progress : chratos::process_result::dividend_too_small;

							if (result.code == chratos::process_result::progress)
							{
								// Do dividend checks. Make sure that the previous dividend hasn't been used before.
								if (block_a.hashables.dividend != chratos::dividend_base)
								{
									// check block exists
									result.code = ledger.store.block_exists (transaction, block_a.hashables.dividend) ? chratos::process_result::progress : chratos::process_result::gap_source;
								}

								if (result.code == chratos::process_result::progress)
								{
									auto dividend_info (ledger.store.dividend_get (transaction));
									result.code = block_a.hashables.dividend == dividend_info.head ? chratos::process_result::progress : chratos::process_result::dividend_fork;

									if (result.code == chratos::process_result::progress)
									{
										ledger.store.block_put (transaction, hash, block_a);

										if (!info.rep_block.is_zero ())
										{
											// Move existing representation
											ledger.store.representation_add (transaction, info.rep_block, 0 - info.balance.number ());
										}
										// Add in amount delta
										ledger.store.representation_add (transaction, hash, block_a.hashables.balance.number ());

										ledger.change_latest (transaction, account, hash, info.rep_block, block_a.hashables.dividend, block_a.hashables.balance, info.block_count + 1);
										if (!ledger.store.frontier_get (transaction, info.head).is_zero ())
										{
											ledger.store.frontier_del (transaction, info.head);
										}
										// Frontier table is unnecessary for state blocks and this also prevents old blocks from being inserted on top of state blocks.
										result.account = account;
										result.amount = amount;
										ledger.stats.inc (chratos::stat::type::ledger, chratos::stat::detail::dividend_block);
										auto previous_info (ledger.store.dividend_get (transaction));
										const auto balance = previous_info.balance.number () + result.amount.number ();
										const auto count = previous_info.block_count + 1;
										const auto time = chratos::seconds_since_epoch ();
										chratos::dividend_info info (hash, balance, time, count, chratos::epoch::epoch_0);
										ledger.store.dividend_put (transaction, info);
									}
								}
							}
						}
					}
				}
			}
		}
	}
}

void ledger_processor::claim_block (chratos::claim_block const & block_a)
{
	auto hash (block_a.hash ());
	auto existing (ledger.store.block_exists (transaction, hash));
	result.code = existing ? chratos::process_result::old : chratos::process_result::progress; // Have we seen this block already?  (Harmless)
	if (result.code == chratos::process_result::progress)
	{
		auto previous (ledger.store.block_get (transaction, block_a.hashables.previous));
		result.code = previous != nullptr ? chratos::process_result::progress : chratos::process_result::gap_previous;
		if (result.code == chratos::process_result::progress)
		{
			std::shared_ptr<chratos::block> dividend = ledger.store.block_get (transaction, block_a.hashables.dividend);
			result.code = dividend != nullptr ? chratos::process_result::progress : chratos::process_result::gap_source; // Have we seen the source block already? (Harmless)
			if (result.code == chratos::process_result::progress)
			{
				chratos::dividend_block const * div_block = dynamic_cast<chratos::dividend_block const *> (dividend.get ());
				result.code = div_block != nullptr ? chratos::process_result::progress : chratos::process_result::incorrect_dividend;
				if (result.code == chratos::process_result::progress)
				{
					auto account = block_a.hashables.account;
					result.code = account.is_zero () ? chratos::process_result::gap_previous : chratos::process_result::progress; //Have we seen the previous block? No entries for account at all (Harmless)
					if (result.code == chratos::process_result::progress)
					{
						result.code = chratos::validate_message (account, hash, block_a.signature) ? chratos::process_result::bad_signature : chratos::process_result::progress; // Is the signature valid (Malformed)
						if (result.code == chratos::process_result::progress)
						{
							chratos::account_info info;
							ledger.store.account_get (transaction, account, info);
							result.code = info.head == block_a.hashables.previous ? chratos::process_result::progress : chratos::process_result::gap_previous; // Block doesn't immediately follow latest block (Harmless)
							if (result.code == chratos::process_result::progress)
							{
								result.code = ledger.has_outstanding_pendings_for_dividend (transaction, div_block->hashables.dividend, account) ? chratos::process_result::outstanding_pendings : chratos::process_result::progress;
								if (result.code == chratos::process_result::progress)
								{
									result.code = info.dividend_block == div_block->hashables.dividend ? chratos::process_result::progress : chratos::process_result::unreceivable; // Hash this dividend been claimed already.
									if (result.code == chratos::process_result::progress)
									{
										result.amount = block_a.hashables.balance.number () - info.balance.number ();
										const auto expected (ledger.amount_for_dividend (transaction, block_a.hashables.dividend, account));
										result.code = result.amount == expected ? chratos::process_result::progress : chratos::process_result::balance_mismatch;
										if (result.code == chratos::process_result::progress)
										{
											ledger.store.block_put (transaction, hash, block_a);

											if (!info.rep_block.is_zero ())
											{
												// Move existing representation
												ledger.store.representation_add (transaction, info.rep_block, 0 - info.balance.number ());
											}
											// Add in amount delta
											ledger.store.representation_add (transaction, hash, block_a.hashables.balance.number ());

											chratos::account account = block_a.hashables.account;

											if (ledger.dividends_are_ordered (transaction, info.dividend_block, block_a.hashables.dividend))
											{
												info.dividend_block = block_a.hashables.dividend;
												ledger.store.account_put (transaction, account, info);
											}

											ledger.change_latest (transaction, account, hash, info.rep_block, block_a.hashables.dividend, block_a.hashables.balance, info.block_count + 1);
											if (!ledger.store.frontier_get (transaction, info.head).is_zero ())
											{
												ledger.store.frontier_del (transaction, info.head);
											}
											// Frontier table is unnecessary for state blocks and this also prevents old blocks from being inserted on top of state blocks.
											result.account = account;
											ledger.stats.inc (chratos::stat::type::ledger, chratos::stat::detail::claim_block);
										}
									}
								}
							}
						}
					}
					else
					{
						result.code = ledger.store.block_exists (transaction, block_a.hashables.previous) ? chratos::process_result::fork : chratos::process_result::gap_previous; // If we have the block but it's not the latest we have a signed fork (Malicious)
					}
				}
			}
		}
	}
}

ledger_processor::ledger_processor (chratos::ledger & ledger_a, chratos::transaction const & transaction_a, bool valid_signature_a) :
ledger (ledger_a),
transaction (transaction_a),
valid_signature (valid_signature_a)
{
}
} // namespace

size_t chratos::shared_ptr_block_hash::operator() (std::shared_ptr<chratos::block> const & block_a) const
{
	auto hash (block_a->hash ());
	auto result (static_cast<size_t> (hash.qwords[0]));
	return result;
}

bool chratos::shared_ptr_block_hash::operator() (std::shared_ptr<chratos::block> const & lhs, std::shared_ptr<chratos::block> const & rhs) const
{
	return lhs->hash () == rhs->hash ();
}

chratos::ledger::ledger (chratos::block_store & store_a, chratos::stat & stat_a, chratos::uint256_union const & epoch_link_a, chratos::account const & epoch_signer_a) :
store (store_a),
stats (stat_a),
check_bootstrap_weights (true),
epoch_link (epoch_link_a),
epoch_signer (epoch_signer_a)
{
}

// Balance for account containing hash
chratos::uint128_t chratos::ledger::balance (chratos::transaction const & transaction_a, chratos::block_hash const & hash_a)
{
	chratos::balance_visitor visitor (transaction_a, store);
	visitor.compute (hash_a);
	return visitor.balance;
}

// Balance for an account by account number
chratos::uint128_t chratos::ledger::account_balance (chratos::transaction const & transaction_a, chratos::account const & account_a)
{
	chratos::uint128_t result (0);
	chratos::account_info info;
	auto none (store.account_get (transaction_a, account_a, info));
	if (!none)
	{
		result = info.balance.number ();
	}
	return result;
}

chratos::uint128_t chratos::ledger::account_pending (chratos::transaction const & transaction_a, chratos::account const & account_a)
{
	chratos::uint128_t result (0);
	chratos::account end (account_a.number () + 1);
	for (auto i (store.pending_v0_begin (transaction_a, chratos::pending_key (account_a, 0))), n (store.pending_v0_begin (transaction_a, chratos::pending_key (end, 0))); i != n; ++i)
	{
		chratos::pending_info info (i->second);
		result += info.amount.number ();
	}
	for (auto i (store.pending_v1_begin (transaction_a, chratos::pending_key (account_a, 0))), n (store.pending_v1_begin (transaction_a, chratos::pending_key (end, 0))); i != n; ++i)
	{
		chratos::pending_info info (i->second);
		result += info.amount.number ();
	}
	return result;
}

chratos::process_return chratos::ledger::process (chratos::transaction const & transaction_a, chratos::block const & block_a, bool valid_signature)
{
	ledger_processor processor (*this, transaction_a, valid_signature);
	block_a.visit (processor);
	return processor.result;
}

chratos::block_hash chratos::ledger::representative (chratos::transaction const & transaction_a, chratos::block_hash const & hash_a)
{
	auto result (representative_calculated (transaction_a, hash_a));
	assert (result.is_zero () || store.block_exists (transaction_a, result));
	return result;
}

chratos::block_hash chratos::ledger::representative_calculated (chratos::transaction const & transaction_a, chratos::block_hash const & hash_a)
{
	representative_visitor visitor (transaction_a, store);
	visitor.compute (hash_a);
	return visitor.result;
}

bool chratos::ledger::block_exists (chratos::block_hash const & hash_a)
{
	auto transaction (store.tx_begin_read ());
	auto result (store.block_exists (transaction, hash_a));
	return result;
}

std::string chratos::ledger::block_text (char const * hash_a)
{
	return block_text (chratos::block_hash (hash_a));
}

std::string chratos::ledger::block_text (chratos::block_hash const & hash_a)
{
	std::string result;
	auto transaction (store.tx_begin_read ());
	auto block (store.block_get (transaction, hash_a));
	if (block != nullptr)
	{
		block->serialize_json (result);
	}
	return result;
}

bool chratos::ledger::is_send (chratos::transaction const & transaction_a, chratos::state_block const & block_a)
{
	bool result (false);
	chratos::block_hash previous (block_a.hashables.previous);
	if (!previous.is_zero ())
	{
		if (block_a.hashables.balance < balance (transaction_a, previous))
		{
			result = true;
		}
	}
	return result;
}

bool chratos::ledger::is_dividend (chratos::transaction const & transaction_a, chratos::state_block const & block_a)
{
	bool result (false);

	chratos::block_hash dividend (block_a.hashables.dividend);
	chratos::block_hash link (block_a.hashables.link);

	if (link == dividend)
	{
		result = !is_send (transaction_a, block_a);
	}

	return result;
}

bool chratos::ledger::is_dividend_claim (chratos::transaction const & transaction_a, chratos::state_block const & block_a)
{
	bool result (false);

	chratos::block_hash dividend (block_a.hashables.dividend);
	chratos::block_hash link (block_a.hashables.link);

	if (link == dividend)
	{
		result = !is_send (transaction_a, block_a);
	}

	return result;
}

bool chratos::ledger::has_outstanding_pendings_for_dividend (chratos::transaction const & transaction_a, chratos::block_hash const & dividend_a, chratos::account const & account_a)
{
	bool result (false);

	chratos::account end (account_a.number () + 1);

	for (auto i (store.pending_begin (transaction_a, chratos::pending_key (account_a, 0))), n (store.pending_begin (transaction_a, chratos::pending_key (end, 0))); i != n && !result; ++i)
	{
		chratos::pending_info info (i->second);
		auto block (store.block_get (transaction_a, i->first.hash));
		if (block->dividend () == dividend_a)
		{
			result = true;
		}
	}

	return result;
}

bool chratos::ledger::dividends_are_ordered (chratos::transaction const & transaction_a, chratos::block_hash const & first_a, chratos::block_hash const & last_a)
{
	bool result (false);

	if (first_a == last_a)
	{
		return true;
	}

	std::shared_ptr<chratos::block> block = store.block_get (transaction_a, last_a);

	while (block != nullptr)
	{
		chratos::block_hash previous = block->dividend ();
		if (previous == first_a)
		{
			result = true;
		}
		block = store.block_get (transaction_a, previous);
	}

	return result;
}

chratos::amount chratos::ledger::amount_for_dividend (chratos::transaction const & transaction_a, chratos::block_hash const & dividend_a, chratos::account const & account_a)
{
	chratos::amount result (0);
	chratos::account_info account_info;
	std::shared_ptr<chratos::block> block_l = store.block_get (transaction_a, dividend_a);
	auto dividend_hash (block_l->dividend ());
	auto previous (store.block_get (transaction_a, block_l->previous ()));
	chratos::dividend_block const * dividend_block (dynamic_cast<chratos::dividend_block const *> (block_l.get ()));

	assert (dividend_block != nullptr);

  if (dividend_block != nullptr)
  {
    if (!store.account_get (transaction_a, account_a, account_info))
    {
      auto front (store.block_get (transaction_a, account_info.head));

      while (front && (front->dividend () == dividend_a || dividends_are_ordered (transaction_a, dividend_a, front->dividend ())))
      {
        front = store.block_get (transaction_a, front->previous ());
      }

      if (front) {
        chratos::amount genesis_supply (std::numeric_limits<chratos::uint128_t>::max ());
        chratos::amount burned_amount (burn_account_balance (transaction_a, dividend_a));
        chratos::amount balance_at_dividend (balance (transaction_a, front->hash ()));
        chratos::amount dividend_amount (amount (transaction_a, block_l->hash ()));
        chratos::amount total_supply (genesis_supply.number () - burned_amount.number ());
        boost::multiprecision::cpp_bin_float_100 balance_f (balance_at_dividend.number ());
        boost::multiprecision::cpp_bin_float_100 daf (dividend_amount.number ());
        boost::multiprecision::cpp_bin_float_100 tsf (total_supply.number ());
        boost::multiprecision::cpp_bin_float_100 total_f (tsf - daf);
        boost::multiprecision::cpp_bin_float_100 proportion (balance_f / total_f);
        boost::multiprecision::cpp_bin_float_100 reward (proportion * daf);

        result = chratos::amount (static_cast<uint128_t> (reward));
      }
    }
  }

	return result;
}

std::vector<chratos::block_hash> chratos::ledger::unclaimed_for_account (chratos::transaction const & transaction_a, chratos::account const & account_a)
{
	std::vector<chratos::block_hash> result;

	chratos::dividend_info div_info (store.dividend_get (transaction_a));
	chratos::account_info info;
	if (!store.account_get (transaction_a, account_a, info))
	{
		auto open_block = store.block_get (transaction_a, info.open_block);
		chratos::block_hash open_div = open_block->dividend ();
		chratos::block_hash current = div_info.head;
		boost::property_tree::ptree entry;

		while (current != chratos::uint256_union (0) && current != open_div && current != info.dividend_block)
		{
			result.push_back (current);
			auto block (store.block_get (transaction_a, current));
			current = block->dividend ();
		}
	}

	std::reverse (std::begin (result), std::end (result));
	return result;
}

chratos::amount chratos::ledger::burn_account_balance (chratos::transaction const & transaction_a, chratos::block_hash const & dividend_a)
{
	chratos::account burn = burn_account;
	chratos::amount result (0);
	chratos::account_info info;

	if (!store.account_get (transaction_a, burn, info))
	{
		result = info.balance;
	}

	std::shared_ptr<chratos::block> dividend = store.block_get (transaction_a, dividend_a);

	chratos::block_hash previous = dividend->dividend ();

	chratos::account end (burn.number () + 1);
	for (auto i (store.pending_v0_begin (transaction_a, chratos::pending_key (burn, 0))), n (store.pending_v0_begin (transaction_a, chratos::pending_key (end, 0))); i != n; ++i)
	{
		auto pending_info = i->second;
		if (dividends_are_ordered (transaction_a, pending_info.dividend, previous))
		{
			result = result.number () + pending_info.amount.number ();
		}
	}

	return result;
}

std::vector<std::shared_ptr<chratos::block>> chratos::ledger::dividend_claim_blocks (chratos::transaction const & transaction_a, chratos::account const & account_a)
{
	chratos::account_info account_info;
	std::vector<std::shared_ptr<chratos::block>> result;

	if (!store.account_get (transaction_a, account_a, account_info))
	{
		chratos::block_hash current = account_info.head;

		while (current != account_a)
		{
			std::shared_ptr<chratos::block> block = store.block_get (transaction_a, current);
			chratos::state_block const * state = dynamic_cast<chratos::state_block const *> (block.get ());

			if (!state)
			{
				break;
			}

			if (is_dividend_claim (transaction_a, *state))
			{
				result.push_back (block);
			}
			current = block->previous ();
		}
	}

	return result;
}

std::unordered_map<chratos::block_hash, int> chratos::ledger::get_dividend_indexes (chratos::transaction const & transaction_a)
{
	std::unordered_map<chratos::block_hash, int> results;

	auto dividend_info = store.dividend_get (transaction_a);
	auto current = dividend_info.head;
	int index (0);

	while (current != chratos::dividend_base)
	{
		std::shared_ptr<chratos::block> block = store.block_get (transaction_a, current);
		results[current] = index++;
		current = block->dividend ();
	}

	const auto count = results.size ();

	for (auto & it : results)
	{
		it.second = (count - 1) - it.second;
	}

	return results;
}

chratos::block_hash chratos::ledger::block_destination (chratos::transaction const & transaction_a, chratos::block const & block_a)
{
	chratos::block_hash result (0);
	chratos::state_block const * state_block (dynamic_cast<chratos::state_block const *> (&block_a));
	if (state_block != nullptr && is_send (transaction_a, *state_block))
	{
		result = state_block->hashables.link;
	}
	return result;
}

chratos::block_hash chratos::ledger::block_source (chratos::transaction const & transaction_a, chratos::block const & block_a)
{
	/*
	 * block_source() requires that the previous block of the block
	 * passed in exist in the database.  This is because it will try
	 * to check account balances to determine if it is a send block.
	 */
	assert (block_a.previous ().is_zero () || store.block_exists (transaction_a, block_a.previous ()));

	// If block_a.source () is nonzero, then we have our source.
	// However, universal blocks will always return zero.
	chratos::block_hash result (block_a.source ());
	chratos::state_block const * state_block (dynamic_cast<chratos::state_block const *> (&block_a));
	if (state_block != nullptr && !is_send (transaction_a, *state_block))
	{
		result = state_block->hashables.link;
	}
	return result;
}

// Vote weight of an account
chratos::uint128_t chratos::ledger::weight (chratos::transaction const & transaction_a, chratos::account const & account_a)
{
	if (check_bootstrap_weights.load ())
	{
		auto blocks = store.block_count (transaction_a);
		if (blocks.sum () < bootstrap_weight_max_blocks)
		{
			auto weight = bootstrap_weights.find (account_a);
			if (weight != bootstrap_weights.end ())
			{
				return weight->second;
			}
		}
		else
		{
			check_bootstrap_weights = false;
		}
	}
	return store.representation_get (transaction_a, account_a);
}

// Rollback blocks until `block_a' doesn't exist
void chratos::ledger::rollback (chratos::transaction const & transaction_a, chratos::block_hash const & block_a)
{
	assert (store.block_exists (transaction_a, block_a));
	auto account_l (account (transaction_a, block_a));
	rollback_visitor rollback (transaction_a, *this);
	chratos::account_info info;
	while (store.block_exists (transaction_a, block_a))
	{
		auto latest_error (store.account_get (transaction_a, account_l, info));
		assert (!latest_error);
		auto block (store.block_get (transaction_a, info.head));
		block->visit (rollback);
	}
}

// Return account containing hash
chratos::account chratos::ledger::account (chratos::transaction const & transaction_a, chratos::block_hash const & hash_a)
{
	chratos::account result;
	auto hash (hash_a);
	chratos::block_hash successor (1);
	chratos::block_info block_info;
	std::unique_ptr<chratos::block> block (store.block_get (transaction_a, hash));
	while (!successor.is_zero () && (block->type () != chratos::block_type::state || block->type () != chratos::block_type::dividend || block->type () != chratos::block_type::claim) && store.block_info_get (transaction_a, successor, block_info))
	{
		successor = store.block_successor (transaction_a, hash);
		if (!successor.is_zero ())
		{
			hash = successor;
			block = store.block_get (transaction_a, hash);
		}
	}
	if (block->type () == chratos::block_type::state)
	{
		auto state_block (dynamic_cast<chratos::state_block *> (block.get ()));
		result = state_block->hashables.account;
	}
	else if (block->type () == chratos::block_type::dividend)
	{
		auto dividend_block (dynamic_cast<chratos::dividend_block *> (block.get ()));
		result = dividend_block->hashables.account;
	}
	else if (block->type () == chratos::block_type::claim)
	{
		auto claim_block (dynamic_cast<chratos::claim_block *> (block.get ()));
		result = claim_block->hashables.account;
	}
	else if (successor.is_zero ())
	{
		result = store.frontier_get (transaction_a, hash);
	}
	else
	{
		result = block_info.account;
	}
	assert (!result.is_zero ());
	return result;
}

// Return amount decrease or increase for block
chratos::uint128_t chratos::ledger::amount (chratos::transaction const & transaction_a, chratos::block_hash const & hash_a)
{
	amount_visitor amount (transaction_a, store);
	amount.compute (hash_a);
	return amount.amount;
}

// Return latest block for account
chratos::block_hash chratos::ledger::latest (chratos::transaction const & transaction_a, chratos::account const & account_a)
{
	chratos::account_info info;
	auto latest_error (store.account_get (transaction_a, account_a, info));
	return latest_error ? 0 : info.head;
}

// Return latest root for account, account number of there are no blocks for this account.
chratos::block_hash chratos::ledger::latest_root (chratos::transaction const & transaction_a, chratos::account const & account_a)
{
	chratos::account_info info;
	auto latest_error (store.account_get (transaction_a, account_a, info));
	chratos::block_hash result;
	if (latest_error)
	{
		result = account_a;
	}
	else
	{
		result = info.head;
	}
	return result;
}

chratos::block_hash chratos::ledger::latest_dividend (chratos::transaction const & transaction_a)
{
	chratos::dividend_info info = store.dividend_get (transaction_a);
	return info.head;
}

chratos::checksum chratos::ledger::checksum (chratos::transaction const & transaction_a, chratos::account const & begin_a, chratos::account const & end_a)
{
	chratos::checksum result;
	auto error (store.checksum_get (transaction_a, 0, 0, result));
	assert (!error);
	return result;
}

void chratos::ledger::dump_account_chain (chratos::account const & account_a)
{
	auto transaction (store.tx_begin_read ());
	auto hash (latest (transaction, account_a));
	while (!hash.is_zero ())
	{
		auto block (store.block_get (transaction, hash));
		assert (block != nullptr);
		std::cerr << hash.to_string () << std::endl;
		hash = block->previous ();
	}
}

class block_fit_visitor : public chratos::block_visitor
{
public:
	block_fit_visitor (chratos::ledger & ledger_a, chratos::transaction const & transaction_a) :
	ledger (ledger_a),
	transaction (transaction_a),
	result (false)
	{
	}
	void state_block (chratos::state_block const & block_a) override
	{
		result = block_a.previous ().is_zero () || ledger.store.block_exists (transaction, block_a.previous ());
		if (result && !ledger.is_send (transaction, block_a))
		{
			result &= ledger.store.block_exists (transaction, block_a.hashables.link);
		}
	}
	void dividend_block (chratos::dividend_block const & block_a) override
	{
		result = ledger.store.block_exists (transaction, block_a.previous ());
	}
	void claim_block (chratos::claim_block const & block_a) override
	{
		result = ledger.store.block_exists (transaction, block_a.previous ());
		result &= ledger.store.block_exists (transaction, block_a.dividend ());
	}
	chratos::ledger & ledger;
	chratos::transaction const & transaction;
	bool result;
};

bool chratos::ledger::could_fit (chratos::transaction const & transaction_a, chratos::block const & block_a)
{
	block_fit_visitor visitor (*this, transaction_a);
	block_a.visit (visitor);
	return visitor.result;
}

bool chratos::ledger::is_epoch_link (chratos::uint256_union const & link_a)
{
	return link_a == epoch_link;
}

void chratos::ledger::checksum_update (chratos::transaction const & transaction_a, chratos::block_hash const & hash_a)
{
	chratos::checksum value;
	auto error (store.checksum_get (transaction_a, 0, 0, value));
	assert (!error);
	value ^= hash_a;
	store.checksum_put (transaction_a, 0, 0, value);
}

void chratos::ledger::change_latest (chratos::transaction const & transaction_a, chratos::account const & account_a, chratos::block_hash const & hash_a, chratos::block_hash const & rep_block_a, chratos::block_hash const & dividend_a, chratos::amount const & balance_a, uint64_t block_count_a, bool is_state, chratos::epoch epoch_a)
{
	chratos::account_info info;
	auto exists (!store.account_get (transaction_a, account_a, info));
	if (exists)
	{
		checksum_update (transaction_a, info.head);
	}
	else
	{
		assert (store.block_get (transaction_a, hash_a)->previous ().is_zero ());
		info.open_block = hash_a;
		info.dividend_block = dividend_a;
	}
	if (!hash_a.is_zero ())
	{
		info.head = hash_a;
		info.rep_block = rep_block_a;
		info.balance = balance_a;
		info.modified = chratos::seconds_since_epoch ();
		info.block_count = block_count_a;
		if (exists && info.epoch != epoch_a)
		{
			// otherwise we'd end up with a duplicate
			store.account_del (transaction_a, account_a);
		}
		info.epoch = epoch_a;
		store.account_put (transaction_a, account_a, info);
		if (!(block_count_a % store.block_info_max) && !is_state)
		{
			chratos::block_info block_info;
			block_info.account = account_a;
			block_info.balance = balance_a;
			store.block_info_put (transaction_a, hash_a, block_info);
		}
		checksum_update (transaction_a, hash_a);
	}
	else
	{
		store.account_del (transaction_a, account_a);
	}
}

std::unique_ptr<chratos::block> chratos::ledger::successor (chratos::transaction const & transaction_a, chratos::uint256_union const & root_a)
{
	chratos::block_hash successor (0);
	if (store.account_exists (transaction_a, root_a))
	{
		chratos::account_info info;
		auto error (store.account_get (transaction_a, root_a, info));
		assert (!error);
		successor = info.open_block;
	}
	else
	{
		successor = store.block_successor (transaction_a, root_a);
	}
	std::unique_ptr<chratos::block> result;
	if (!successor.is_zero ())
	{
		result = store.block_get (transaction_a, successor);
	}
	assert (successor.is_zero () || result != nullptr);
	return result;
}

std::unique_ptr<chratos::block> chratos::ledger::forked_block (chratos::transaction const & transaction_a, chratos::block const & block_a)
{
	assert (!store.block_exists (transaction_a, block_a.hash ()));
	auto root (block_a.root ());
	assert (store.block_exists (transaction_a, root) || store.account_exists (transaction_a, root));
	std::unique_ptr<chratos::block> result (store.block_get (transaction_a, store.block_successor (transaction_a, root)));
	if (result == nullptr)
	{
		chratos::account_info info;
		auto error (store.account_get (transaction_a, root, info));
		assert (!error);
		result = store.block_get (transaction_a, info.open_block);
		assert (result != nullptr);
	}
	return result;
}