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

#include <data_tools/navi_data.h>
#include <data_tools/colormap.h>
#include <data_tools/transforms.h>

#include <fstream>
#include <sstream>
#include <iostream>
#include <tuple>

#define BOOST_NO_CXX11_SCOPED_ENUMS
#include <boost/date_time.hpp>
#undef BOOST_NO_CXX11_SCOPED_ENUMS

#include <cereal/archives/json.hpp>

using namespace std;
using namespace Eigen;

namespace navi_data {

using namespace std_data;

void match_timestamps(mbes_ping::PingsT& pings, nav_entry::EntriesT& entries)
{

    std::stable_sort(pings.begin(), pings.end(), [](const mbes_ping& ping1, const mbes_ping& ping2) {
        return ping1.time_stamp_ < ping2.time_stamp_;
    });
    std::sort(entries.begin(), entries.end(), [](const nav_entry& entry1, const nav_entry& entry2) {
        return entry1.time_stamp_ < entry2.time_stamp_;
    });

    // NOTE: this works, but is terribly slow
    /*
    for (mbes_ping& ping : pings) {
        auto entry = std::min_element(entries.begin(), entries.end(), [&](const nav_entry& entry1, const nav_entry& entry2) {
            return abs(entry1.time_stamp_ - ping.time_stamp_) < abs(entry2.time_stamp_ - ping.time_stamp_);
        });
        ping.pos_ = entry->pos_;
        //cout << "Ping " << ping.time_string_ << " closest to Entry " << entry->time_string_ << endl;
    }
    */

    auto pos = entries.begin();
    for (mbes_ping& ping : pings) {
        pos = std::find_if(pos, entries.end(), [&](const nav_entry& entry) {
            return entry.time_stamp_ > ping.time_stamp_;
        });
        if (pos == entries.end()) {
            //cout << "Ping " << ping.time_string_ << " closest to LAST Entry " << entries.back().time_string_ << endl;
            ping.pos_ = entries.back().pos_;
        }
        else {
            ping.pos_ = pos->pos_;
            //cout << "Ping " << ping.time_string_ << " closest to Entry " << pos->time_string_ << endl;
        }
    }
}

void save_submaps_files(const std_data::mbes_ping::PingsT& pings, const boost::filesystem::path& folder){

    // Save submaps in external files
    string file_name;
    int sm_cnt = 0;
    std::ofstream myfile;
    for (const std_data::mbes_ping& ping_i: pings){
        // If new submap, open new file
        if(ping_i.first_in_file_){
            myfile.close();
            file_name = "/submap_" + std::to_string(sm_cnt) + ".xyz";
            myfile.open(folder.string() + file_name, std::ofstream::out);
            sm_cnt += 1;
        }
        if(myfile.is_open()){
            for(unsigned int i=0; i<ping_i.beams.size(); i++){
                myfile << ping_i.beams.at(i)(0) - 669000.0 << " " << ping_i.beams.at(i)(1) - 6383000.0 << " " << ping_i.beams.at(i)(2) << "\n";
            }
        }
    }
}

double compute_info_in_submap(std_data::mbes_ping::PingsT& submap_pings){

    // Beams z centroid
    double mean_beam = 0;
    int beam_cnt = 0;
    for (const std_data::mbes_ping& ping: submap_pings){
        for(const Eigen::Vector3d& beam_i: ping.beams){
            mean_beam += beam_i[2];
            ++beam_cnt;
        }
    }
    mean_beam = mean_beam / beam_cnt;

    // Condition number of z
    double cond_num = 0;
    for (const std_data::mbes_ping& ping: submap_pings){
        for(const Eigen::Vector3d& beam_i: ping.beams){
            cond_num += std::abs(beam_i[2] - mean_beam);
        }
    }
    return cond_num = cond_num / beam_cnt;
}

void divide_tracks_adaptive(mbes_ping::PingsT& pings)
{
    double info_thres = 0.3;
    // For every line (one line per file)
    for (auto pos = pings.begin(); pos != pings.end(); ) {
        auto next = std::find_if(pos, pings.end(), [&](const mbes_ping& ping) {
            return ping.first_in_file_ && (&ping != &(*pos));
        });

        mbes_ping::PingsT::iterator first_pos_it = pos;
        Vector3d last_pos;
        double mean_width = 0.; double count = 0.;
        for (auto it = pos; it != next; ++it) {
            last_pos = it->pos_;
            mean_width += 1.7*(it->beams.front() - it->beams.back()).norm();
            count += 1;
        }
        mean_width /= count;
        double length = (last_pos - first_pos_it->pos_).norm();

        int nbr_submaps = int(length/mean_width+2.5);
        double min_submap_length = length / double(nbr_submaps);
        double ext_step = min_submap_length/4;
        double max_submap_length = 3*min_submap_length;

        cout << "Min submap length: " << min_submap_length << ", Growing step: "
             << ext_step << ", Max submap length: " << max_submap_length << endl;

        // For every ping in a line
        mbes_ping::PingsT::iterator latest_pos_it = first_pos_it;
        int steps = 0;
        int counter = 0; // TODO: remove!
        int last_submap_cnt = 0;
        for (auto it = pos; it != next; ++it) {
            // If submap too close to end of line
            if ((last_pos - it->pos_).norm() < min_submap_length) {
                cout << "Too close to end, breaking at " << counter << endl;
                break;
            }
            // If submap reaches max length
            if((latest_pos_it->pos_ - it->pos_).norm() > max_submap_length){
                cout << "Breaking up submap with max length at " << counter
                     << " out of " << std::distance(pos, next) << endl;
                it->first_in_file_ = true;
                latest_pos_it = it;
                steps = 0;
                last_submap_cnt = counter;
            }
            // If submap bigger than min length or min_length + extension * steps
            else if (((latest_pos_it->pos_ - it->pos_).norm() >= min_submap_length && steps == 0) ||
                    (latest_pos_it->pos_ - it->pos_).norm() >= min_submap_length + ext_step*steps) {

                // Compute information quantity on submap
                mbes_ping::PingsT pings_submap(pos+last_submap_cnt, it);
                double info_in_submap = compute_info_in_submap(pings_submap);
                cout << "Info in submap " << info_in_submap << endl;

                // If big, break here
                if(info_in_submap > info_thres){
                    cout << "Breaking up submap with enough info at " << counter
                         << " out of " << std::distance(pos, next) << endl;
                    it->first_in_file_ = true;
                    latest_pos_it = it;
                    steps = 0;
                    last_submap_cnt = counter;
                }
                // If small, keep extending submap
                else{
                    steps++;
                }

            }
            ++counter;
        }
        pos = next;
    }
}

void divide_tracks(mbes_ping::PingsT& pings)
{
    for (auto pos = pings.begin(); pos != pings.end(); ) {
        auto next = std::find_if(pos, pings.end(), [&](const mbes_ping& ping) {
            return ping.first_in_file_ && (&ping != &(*pos));
        });

        Vector3d first_pos = pos->pos_;
        Vector3d last_pos;
        double mean_width = 0.; double count = 0.;
        for (auto it = pos; it != next; ++it) {
            last_pos = it->pos_;
            mean_width += 1.7*(it->beams.front() - it->beams.back()).norm();
            count += 1;
        }
        mean_width /= count;
        double length = (last_pos - first_pos).norm();

        int nbr_submaps = int(length/mean_width+0.5);
        double submap_length = length / double(nbr_submaps);

        cout << "Mean width: " << mean_width << ", Length: " << length << ",  Nbr submaps: " << nbr_submaps << ", Submap length: " << submap_length << endl;

        Vector3d recent_pos = first_pos;
        int last_submap_cnt = 0;
        int counter = 0; // TODO: remove!
        for (auto it = pos; it != next; ++it) {
            if ((last_pos - it->pos_).norm() < submap_length/2.) {
                cout << "Too close to end, breaking at " << counter << endl;
                break;
            }
            if ((it->pos_ - recent_pos).norm() > submap_length) {
                cout << "Breaking up submap at " << counter << " out of " << std::distance(pos, next) << endl;
                it->first_in_file_ = true;
                recent_pos = it->pos_;
//                mbes_ping::PingsT pings_submap(pos+last_submap_cnt, it);
//                double info_in_submap = computeInfoInSubmap(pings_submap);
//                cout << "Info in submap " << info_in_submap << endl;
                last_submap_cnt = counter;
            }
            ++counter;
        }

        pos = next;
    }
}

void divide_tracks_equal(mbes_ping::PingsT& pings)
{
    Vector2d point1, point2, dir; // first and last point on line
    vector<bool> line_positive_directions;
    double first_line_pos = -1000000;
    double last_line_pos = 1000000;

    cout << "Really First first in file?: " << pings[0].first_in_file_ << endl;

    double mean_width = 0.; double count = 0.;
    for (auto pos = pings.begin(); pos != pings.end(); ) {
        auto next = std::find_if(pos, pings.end(), [&](const mbes_ping& ping) {
            return ping.first_in_file_ && (&ping != &(*pos));
        });

        Vector2d first_pos = pos->pos_.head<2>();
        Vector2d last_pos;
        for (auto it = pos; it != next; ++it) {
            last_pos = it->pos_.head<2>();
            mean_width += 1.7*(it->beams.front() - it->beams.back()).norm();
            count += 1;
        }
        if (pos == pings.begin()) {
            point1 = first_pos;
            point2 = last_pos;
            dir = point2-point1;
            dir.normalize();
        }

        bool positive_direction = dir.dot(last_pos - first_pos) > 0;
        line_positive_directions.push_back(positive_direction);

        double line_pos1 = dir.dot(first_pos - point1);
        double line_pos2 = dir.dot(last_pos - point1);
        cout << "Number pings: " << std::distance(pos, next) << endl;
        cout << "First == last? " << (pos == next) << endl;
        cout << "First beams: " << pos->beams.size() << " Next beams: " << next->beams.size() << endl;
        cout << "First firs in file?: " << pos->first_in_file_ << ", Next first in file?: " << next->first_in_file_ << endl;
        cout << "First time: " << pos->time_stamp_ << ", Next time: " << next->time_stamp_ << endl;
        cout << "First pos: " << first_pos.transpose() << endl;
        cout << "Last pos: " << last_pos.transpose() << ", Point 1: " << point1.transpose() << "Dir: " << dir.transpose() << endl;
        cout << "Line pos 1: " << line_pos1 << ", Line pos 2: " << line_pos2 << endl;
        if (!positive_direction) {
            std::swap(line_pos1, line_pos2);
        }

        first_line_pos = std::max(first_line_pos, line_pos1);
        last_line_pos = std::min(last_line_pos, line_pos2);

        pos = next;
    }

    mean_width /= count;
    //double length = (point2 - point1).norm();
    double line_pos_length = last_line_pos - first_line_pos;
    int nbr_submaps = int(line_pos_length/mean_width+0.5);
    double submap_length = line_pos_length / double(nbr_submaps);

    cout << "First line pos: " << first_line_pos << ", last line pos: " << last_line_pos << endl;
    cout << "Mean width: " << mean_width << ", Length: " << line_pos_length << ",  Nbr submaps: " << nbr_submaps << ", Submap length: " << submap_length << endl;
    cout << "------------" << endl;
    int track_counter = 0;
    int min_since_last = 5;
    for (auto pos = pings.begin(); pos != pings.end(); ) {
        auto next = std::find_if(pos, pings.end(), [&](const mbes_ping& ping) {
            return ping.first_in_file_ && (&ping != &(*pos));
        });

        bool positive_direction = line_positive_directions[track_counter];
        //Vector3d recent_pos = pos->pos_;
        //int counter;
        double recent_line_pos = positive_direction?
                    dir.dot(pos->pos_.head<2>() - point1) - first_line_pos:
                    last_line_pos - dir.dot(pos->pos_.head<2>() - point1);
        int time_since_last = 0;
        for (auto it = pos; it != next; ++it) {
            if (std::distance(it, next) < min_since_last) {
                break;
            }
            double line_pos = positive_direction?
                        dir.dot(it->pos_.head<2>() - point1) - first_line_pos :
                        last_line_pos - dir.dot(it->pos_.head<2>() - point1);
            if (line_pos > 0 && line_pos < line_pos_length) {
                if ((recent_line_pos < 0 || recent_line_pos > line_pos_length) &&
                        time_since_last > min_since_last) {
                    it->first_in_file_ = true;
                    time_since_last = 0;
                }
                else if ((int(recent_line_pos/submap_length) < int(line_pos/submap_length)) &&
                        time_since_last > min_since_last) {
                    it->first_in_file_ = true;
                    time_since_last = 0;
                }
            }
            else if ((recent_line_pos > 0 && recent_line_pos < line_pos_length) &&
                    time_since_last > min_since_last) {
                it->first_in_file_ = true;
                time_since_last = 0;
            }
            //++counter;
            recent_line_pos = line_pos;
            ++time_since_last;
        }

        pos = next;
        ++track_counter;
    }
}

tuple<ObsT, TransT, AngsT, MatchesT, BBsT, ObsT> create_submaps(const mbes_ping::PingsT& pings)
{
    ObsT submaps;
    TransT trans;
    AngsT angs;
    MatchesT matches;
    BBsT bounds;
    ObsT tracks;
    for (auto pos = pings.begin(); pos != pings.end(); ) {
        auto next = std::find_if(pos, pings.end(), [&](const mbes_ping& ping) {
            return ping.first_in_file_ && (&ping != &(*pos));
        });
        mbes_ping::PingsT track_pings;
        track_pings.insert(track_pings.end(), pos, next);
        cout << "found 1 pos!" << endl;

        if (pos == next) {
            break;
        }

        MatrixXd points(track_pings.size()*track_pings[0].beams.size(), 3);
        MatrixXd track(track_pings.size(), 3);

        // get the direction of the submap as the mean direction
        Vector3d dir = track_pings.back().pos_ - track_pings.front().pos_;
        Vector3d ang; ang << 0., 0., std::atan2(dir(1), dir(0));
        Eigen::Matrix3d RM = data_transforms::euler_to_matrix(ang(0), ang(1), ang(2));

        int counter = 0;
        int ping_counter = 0;
        for (const mbes_ping& ping : track_pings) {
            //cout << "Counter : " << counter << " and size: " << points.rows() << " and new points: " << ping.beams.size() << endl;
            if (counter + ping.beams.size() > points.rows()) {
                points.conservativeResize(counter + ping.beams.size(), 3);
            }
            for (const Vector3d& p : ping.beams) {
                points.row(counter) = p;
                ++counter;
            }
            track.row(ping_counter) = ping.pos_;
            ++ping_counter;
        }
        if (counter == 0) {
            pos = next;
            continue;
        }
        points.conservativeResize(counter, 3);
        trans.push_back(points.colwise().mean().transpose());
        angs.push_back(ang);
        points.array().rowwise() -= trans.back().transpose().array();
        points = points*RM;
        track.array().rowwise() -= trans.back().transpose().array();
        track = track*RM;
        Matrix2d bb;
        bb(0, 0) = points.col(0).minCoeff();
        bb(0, 1) = points.col(1).minCoeff();
        bb(1, 0) = points.col(0).maxCoeff();
        bb(1, 1) = points.col(1).maxCoeff();
        submaps.push_back(points);
        bounds.push_back(bb);
        tracks.push_back(track);

        pos = next;
    }

    // homogenize angles
    /*
    for (int i = 0; i < submaps.size(); ++i) {
        if (fabs(angs[i](2)) > M_PI/2.) {
            submaps[i].leftCols(2).array() *= -1.; // rotate 180 degrees
            tracks[i].leftCols(2).array() *= -1.; // rotate 180 degrees
            Matrix2d bb = bounds[i];
            bounds[i].row(0) = -bb.row(1);
            bounds[i].row(1) = -bb.row(0);
            if (angs[i](2) < -M_PI/2.) {
                angs[i](2) += M_PI;
            }
            else {
                angs[i](2) -= M_PI;
            }
        }
    }
    */

    return make_tuple(submaps, trans, angs, matches, bounds, tracks);
}

} // namespace navi_data

namespace std_data {

// Extract space-separated numbers: Nav files
// 0: Year
// 1: Time (day, hour)
// 2: Second
// 3: Ping num
// 4: Beam num
// 5: X
// 6: Y
// 7: Z
// 8: Tide
// 9: Heading
// 10: Heave
// 11: Pitch
// 12: Roll
template <>
mbes_ping::PingsT parse_file(const boost::filesystem::path& file)
{
    mbes_ping::PingsT pings;

    string line;
    std::ifstream infile(file.string());

    mbes_ping ping;
    string time;
    int beam_id;
    double tide, x, y, z;
    string year_string, date_string, second_string;
    const std::locale loc = std::locale(std::locale::classic(), new boost::posix_time::time_input_facet("%Y %m%d%H%M %S%f"));
    const boost::posix_time::ptime epoch = boost::posix_time::time_from_string("1970-01-01 00:00:00.000");

    std::getline(infile, line); // throw away first line as it contains description
    int counter = 0;
    while (std::getline(infile, line))  // this does the checking!
    {
        istringstream iss(line);

        iss >> year_string >> date_string >> second_string >> ping.id_ >> beam_id >> x >> y >> z >> tide >> ping.heading_ >> ping.heave_ >> ping.pitch_ >> ping.roll_;

        /*
        if (beam_id != 255 && counter % 10 != 0) {
            ++counter;
            continue;
        }
        */

        ping.beams.push_back(Vector3d(x, y, -z));

        if (beam_id == 255) {
            ping.first_in_file_ = pings.empty();
            std::istringstream is(year_string + " " + date_string + " " + second_string);
            is.imbue(loc);
            boost::posix_time::ptime t;
            is >> t;
            boost::posix_time::time_duration const diff = t - epoch;
            ping.time_stamp_ = diff.total_milliseconds();
            stringstream time_ss;
            time_ss << t;
            ping.time_string_ = time_ss.str();
            //cout << year_string << " " << date_string << " " << second_string << endl;
            //cout << t << endl;

            pings.push_back(ping);
            ping.beams.resize(0);
        }

        ++counter;
    }

    return pings;
}

// Extract space-separated numbers: Nav files
// 0: Day
// 1: Time
// 2: Easting
// 3: Northing
// 4: Depth (given in positive values!)
// 5: Zeros
template <>
nav_entry::EntriesT parse_file(const boost::filesystem::path& file)
{
    nav_entry::EntriesT entries;

    nav_entry entry;
    double x, y, z;
    string date_string, time_string;
    const std::locale loc = std::locale(std::locale::classic(), new boost::posix_time::time_input_facet("%Y.%m.%d %H:%M:%S%f"));
    const boost::posix_time::ptime epoch = boost::posix_time::time_from_string("1970-01-01 00:00:00.000");

    string line;
    std::ifstream infile(file.string());
    while (std::getline(infile, line))  // this does the checking!
    {
        istringstream iss(line);

        //iss >> entry.year_ >> entry.time_stamp_ >> x >> y >> z;
        iss >> date_string >> time_string >> x >> y >> z;
        entry.pos_ = Vector3d(x, y, -z);

        std::istringstream is(date_string + " " + time_string);
        is.imbue(loc);
        boost::posix_time::ptime t;
        is >> t;
        boost::posix_time::time_duration const diff = t - epoch;
        entry.time_stamp_ = diff.total_milliseconds();
        stringstream time_ss;
        time_ss << t;
        entry.time_string_ = time_ss.str();

        //cout << date_string << " " << time_string << endl;
        //cout << t << endl;
        //cout << ms << endl;
        entry.first_in_file_ = entries.empty();

        entries.push_back(entry);
    }

    return entries;
}
} // namespace std_data


#ifndef PRIMITIVES
#define PRIMITIVES

#include <iostream>
#include <fstream>
#include <cstdlib>
#include <functional>
#include <string>
#include <iomanip>      // std::put_time
#include <boost/unordered_set.hpp> // hash_combine
#include <chrono>

#include "../include/rapidjson/document.h"

typedef long long int i64;

/**
 * @brief The parameters_t struct
 * Parameters of the conflict optimizer
 */
struct Parameters
{
    std::string instance_name = "";
    std::string solution_name = "";
    std::string info_name = "";
    std::string algorithm = "greedy";
    double power = 1.2;
    double noise_mean = 1;
    double noise_var = 0.15;
    long max_queue = -1;
    long max_run_time = 3600;
    bool dfs = true;
    bool easy = true;
    bool loop = false;
    int loop_time = 3600;
    std::vector<double> power_loop = {1.1, 1.2, 1.3, 1.5, 2.0};
    long loop_index = 0;

    void read(const std::string &filename)
    {
        std::ifstream in(filename, std::ifstream::in | std::ifstream::binary);
        if (!in.is_open())
        {
            std::cerr << "Error reading " << filename << std::endl;
            exit(EXIT_FAILURE);
        }

        rapidjson::IStreamWrapper isw {in};
        rapidjson::Document doc {};
        doc.ParseStream(isw);
        if (doc.HasParseError())
        {
            std::cerr << "Error  : " << doc.GetParseError()  << std::endl;
            std::cerr << "Offset : " << doc.GetErrorOffset() << std::endl;
            exit(EXIT_FAILURE);
        }

        if (doc.HasMember("instance"))
            instance_name = doc["instance"].GetString();
        else
        {
            std::clog << "Missing mandatory argument from parameters file: instance" << std::endl;
            exit(EXIT_FAILURE);
        }
        if (doc.HasMember("solution"))
            solution_name = doc["solution"].GetString();
        if (doc.HasMember("info"))
            info_name = doc["info"].GetString();
        if (doc.HasMember("algorithm"))
            algorithm = doc["algorithm"].GetString();
        if (doc.HasMember("power"))
            power = doc["power"].GetDouble();
        if (doc.HasMember("noise_mean"))
            noise_mean = doc["noise_mean"].GetDouble();
        if (doc.HasMember("noise_var"))
            noise_var = doc["noise_var"].GetDouble();
        if (doc.HasMember("max_queue"))
            max_queue = doc["max_queue"].GetInt();
        if (doc.HasMember("max_run_time"))
            max_run_time =  doc["max_run_time"].GetInt();
        if (doc.HasMember("dfs"))
            dfs = doc["dfs"].GetBool();
        if (doc.HasMember("easy"))
            easy = doc["easy"].GetBool();
        if (doc.HasMember("loop"))
        {
            loop = doc["loop"].GetBool();
            if (loop)
                power = 1.1;
        }
        if (doc.HasMember("loop_time"))
            loop_time = doc["loop_time"].GetInt();

        std::clog << "{ instance: " << instance_name << ", "
                  << "solution: " << solution_name << ", "
                  << "info: " << info_name << ", "
                  << "power: " << power << ", "
                  << "noise_mean: " << noise_mean << ", "
                  << "noise_var: " << noise_var << ", "
                  << "max_queue: " << max_queue << ", "
                  << "max_run_time: " << max_run_time << ", "
                  << "dfs: " << dfs << ", "
                  << "easy: " << easy << ", "
                  << "loop: " << loop << ", "
                  << "loop_time: " << loop_time << " }" << std::endl;
    }
};

class Point {
public:
    i64 x,y;

    Point() {
    }

    Point(i64 _x, i64 _y) : x(_x), y(_y) {
    }

    i64 l1(const Point &p = Point(0,0)) const {
        return abs(x-p.x) + abs(y-p.y);
    }

    i64 linf(const Point &p = Point(0,0)) const {
        return std::max(abs(x-p.x), abs(y-p.y));
    }

    i64 l2sq(const Point &p = Point(0,0)) const {
        return (x-p.x)*(x-p.x) + (y-p.y)*(x-p.x);
    }

    friend bool operator==(const Point &p, const Point &q) {
        return p.x == q.x && p.y == q.y;
    }

    friend bool operator!=(const Point &p, const Point &q) {
        return !(p == q);
    }

    friend bool operator<(const Point &p, const Point &q) {
        return p.x < q.x || (p.x == q.x && p.y < q.y);
    }

    //  auto operator<=>(const Point& p) const = default;

    Point operator-(const Point &p) const {
        return Point(x-p.x, y-p.y);
    }

    Point operator+(const Point &p) const {
        return Point(x+p.x, y+p.y);
    }

    // Dot product
    i64 operator*(const Point &p) const {
        return x*p.x + y*p.y;
    }

    std::string toString() const {
        std::string s = "(" + std::to_string(x) + "," + std::to_string(y) + ")";
        return s;
    }

    bool inside(const Point &p, const Point &q) const {
        i64 minx = std::min(p.x, q.x);
        i64 miny = std::min(p.y, q.y);
        i64 maxx = std::max(p.x, q.x);
        i64 maxy = std::max(p.y, q.y);
        return x >= minx && x <= maxx && y >= miny && y <= maxy;
    }

    friend std::ostream& operator<<(std::ostream& os, const Point& p) {
        os << p.toString();
        return os;
    }
};

class Segment {
    Point p,q;
public:
    Segment() {
    }

    Segment(const Point  &_p, const Point  &_q) {
        if (_p < _q) {
            p = _p;
            q = _q;
        }
        else {
            p = _q;
            q = _p;
        }
    }

    const Point &get_p() const {
        return p;
    }

    const Point &get_q() const{
        return q;
    }

    i64 l1() const {
        return p.l1(q);
    }

    i64 linf() const {
        return p.linf(q);
    }

    i64 l2sq() const {
        return p.l2sq(q);
    }

    double slope() const {
        Point d = q - p;
        return (double) d.y / d.x;
    }

    friend bool operator==(const Segment &s, const Segment &t) {
        return s.p == t.p && s.q == t.q;
    }

    int orientation(const Point &r) const {
        i64 d1 = (q.y - p.y);
        i64 d2 = (r.x - q.x);
        i64 d3 = (q.x - p.x);
        i64 d4 = (r.y - q.y);
        i64 val = d1*d2 - d3*d4;

        return (val > 0) - (val < 0);
    }

    bool cross(const Segment &s) const {
        int o1 = orientation(s.p);
        int o2 = orientation(s.q);
        int o3 = s.orientation(p);
        int o4 = s.orientation(q);

        // No 3 colinear points
        if (o1 != 0 && o2 != 0 && o3 != 0 && o4 != 0) {
            return o1 != o2 && o3 != o4;
        }

        // Colinear but 4 distinct points
        if (s.p != p && s.q != q && s.p != q && s.q != p)
            return s.p.inside(p, q) || s.q.inside(p, q) || p.inside(s.p, s.q) || q.inside(s.p, s.q);

        // Same segment twice, return false for convinience
        if (*this == s)
            return false;

        // 3 points among 4 vertices, not all colinear
        if (o1 != 0 || o2 != 0 || o3 != 0 || o4 != 0)
            return false;

        // 3 points among 4 vertices, all colinear
        if (s.p == p)
            return q.inside(s.p, s.q) || s.q.inside(p, q);
        if (s.q == q)
            return p.inside(s.p, s.q) || s.p.inside(p, q);
        if (s.p == q)
            return p.inside(s.p, s.q) || s.q.inside(p, q);
        //      if (s.q == p)
        return q.inside(s.p, s.q) || s.p.inside(p, q);
    }

    std::string toString() const {
        std::string s = p.toString() + "-" + q.toString();
        return s;
    }

    friend std::ostream& operator<<(std::ostream& os, const Segment& s) {
        os << s.toString();
        return os;
    }
};

namespace std {

template <> struct hash<Point> {
    size_t operator()(const Point& p) const {
        size_t seed = 0;
        boost::hash_combine(seed, p.x);
        boost::hash_combine(seed, p.y);
        return seed;
    }
};

template <> struct hash<Segment> {
    size_t operator()(const Segment& s) const {
        size_t seed = 0;
        boost::hash_combine(seed, s.get_p().x);
        boost::hash_combine(seed, s.get_p().y);
        boost::hash_combine(seed, s.get_q().x);
        boost::hash_combine(seed, s.get_q().y);
        return seed;
    }
};

}

#endif // PRIMITIVES


// Copyright (c) 2012-2017, The CryptoNote developers, The Bytecoin developers
// Copyright (c) 2016-2018  zawy12
// Copyright (c) 2016-2018, The Karbowanec developers
// Copyright (c) 2018-2022, The Deocoin Group.
//
// This file is part of Deocoin.
//
// Deocoin is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Deocoin is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with Deocoin.  If not, see <http://www.gnu.org/licenses/>.

#include <algorithm>
#include <cctype>
#include <cmath>
#include <boost/algorithm/string/trim.hpp>
#include <boost/lexical_cast.hpp>
#include <boost/math/special_functions/round.hpp>
#include <Common/Base58.h>
#include <Common/int-util.h>
#include <Common/Math.h>
#include <Common/StringTools.h>
#include <CryptoNoteCore/Account.h>
#include <CryptoNoteCore/CryptoNoteBasicImpl.h>
#include <CryptoNoteCore/CryptoNoteFormatUtils.h>
#include <CryptoNoteCore/CryptoNoteTools.h>
#include <CryptoNoteCore/Currency.h>
#include <CryptoNoteCore/TransactionExtra.h>
#include <CryptoNoteCore/UpgradeDetector.h>
#include <Global/Constants.h>
#include <Global/CryptoNoteConfig.h>

#undef ERROR

using namespace Logging;
using namespace Common;
using namespace Deocoin;

namespace CryptoNote {

bool Currency::init()
{
    if (!generateGenesisBlock()) {
        logger(ERROR, BRIGHT_RED) << "Failed to generate genesis block";
        return false;
    }

    if (!get_block_hash(m_genesisBlock, m_genesisBlockHash)) {
        logger(ERROR, BRIGHT_RED) << "Failed to get genesis block hash";
        return false;
    }

    if (isTestnet()) {
        m_upgradeHeightV2 = 10;
        m_upgradeHeightV3 = 60;
        m_upgradeHeightV4 = 70;
        m_upgradeHeightV5 = 80;
        m_upgradeHeightV6 = 100;
        m_governancePercent = 10;
        m_governanceHeightStart = 1;
        m_governanceHeightEnd = 100;
        m_blocksFileName = "testnet_" + m_blocksFileName;
        m_blocksCacheFileName = "testnet_" + m_blocksCacheFileName;
        m_blockIndexesFileName = "testnet_" + m_blockIndexesFileName;
        m_txPoolFileName = "testnet_" + m_txPoolFileName;
        m_blockchainIndicesFileName = "testnet_" + m_blockchainIndicesFileName;
    }

    return true;
}

bool Currency::generateGenesisBlock()
{
    m_genesisBlock = boost::value_initialized<Block>();

    // Hard code coinbase tx in genesis block, because "tru" generating tx use random,
    // but genesis should be always the same
    std::string genesisCoinbaseTxHex = GENESIS_COINBASE_TX_HEX;
    BinaryArray minerTxBlob;

    bool r = fromHex(genesisCoinbaseTxHex, minerTxBlob)
             && fromBinaryArray(m_genesisBlock.baseTransaction, minerTxBlob);

    if (!r) {
        logger(ERROR, BRIGHT_RED) << "failed to parse coinbase tx from hard coded blob";
        return false;
    }

    m_genesisBlock.majorVersion = BLOCK_MAJOR_VERSION_1;
    m_genesisBlock.minorVersion = BLOCK_MINOR_VERSION_0;
    m_genesisBlock.timestamp = 0;
    m_genesisBlock.nonce = 70;
    if (m_testnet) {
        ++m_genesisBlock.nonce;
    }

    //miner::find_nonce_for_given_block(bl, 1, 0);

    return true;
}

size_t Currency::blockGrantedFullRewardZoneByBlockVersion(uint8_t blockMajorVersion) const
{
    if (blockMajorVersion >= BLOCK_MAJOR_VERSION_4) {
        return CryptoNote::parameters::CRYPTONOTE_BLOCK_GRANTED_FULL_REWARD_ZONE_V2;
    } else if (blockMajorVersion == BLOCK_MAJOR_VERSION_3) {
        return m_blockGrantedFullRewardZone;
    } else if (blockMajorVersion == BLOCK_MAJOR_VERSION_2) {
        return CryptoNote::parameters::CRYPTONOTE_BLOCK_GRANTED_FULL_REWARD_ZONE_V2;
    } else {
        return CryptoNote::parameters::CRYPTONOTE_BLOCK_GRANTED_FULL_REWARD_ZONE_V1;
    }
}

uint32_t Currency::upgradeHeight(uint8_t majorVersion) const
{
    if (majorVersion == BLOCK_MAJOR_VERSION_6) {
        return m_upgradeHeightV6;
    } else if (majorVersion == BLOCK_MAJOR_VERSION_5) {
        return m_upgradeHeightV5;
    } else if (majorVersion == BLOCK_MAJOR_VERSION_4) {
        return m_upgradeHeightV4;
    } else if (majorVersion == BLOCK_MAJOR_VERSION_2) {
        return m_upgradeHeightV2;
    } else if (majorVersion == BLOCK_MAJOR_VERSION_3) {
        return m_upgradeHeightV3;
    } else {
        return static_cast<uint32_t>(-1);
    }
}

bool Currency::getBlockReward(
    uint8_t blockMajorVersion,
    size_t medianSize,
    size_t currentBlockSize,
    uint64_t alreadyGeneratedCoins,
    uint64_t fee,
    uint64_t &reward,
    int64_t &emissionChange,
    uint32_t height,
    uint64_t blockTarget) const
{
    //assert(alreadyGeneratedCoins <= m_moneySupply);
    assert(m_emissionSpeedFactor > 0 && m_emissionSpeedFactor <= 8 * sizeof(uint64_t));

    // Consistency
    double consistency = 1.0;
    double exponent = 0.25;
    if (height >= CryptoNote::parameters::UPGRADE_HEIGHT_V6 && difficultyTarget() != 0) {
        // blockTarget is (Timestamp of New Block - Timestamp of Previous Block)
        consistency = (double) blockTarget / (double) difficultyTarget();

        // consistency range is 0..2
        if (consistency < 1.0) {
            consistency = std::max<double>(consistency, 0.0);
        }
        else if (consistency > 1.0) {
            consistency = pow(consistency, exponent);
            consistency = std::min<double>(consistency, 2.0);
        }
        else {
            consistency = 1.0;
        }
    }

    // Tail emission

    uint64_t baseReward = ((m_moneySupply - alreadyGeneratedCoins) >> m_emissionSpeedFactor) * consistency;
    if (alreadyGeneratedCoins + CryptoNote::parameters::TAIL_EMISSION_REWARD >= m_moneySupply
        || baseReward < CryptoNote::parameters::TAIL_EMISSION_REWARD) {
        baseReward = CryptoNote::parameters::TAIL_EMISSION_REWARD;
    }

    size_t blockGrantedFullRewardZone = blockGrantedFullRewardZoneByBlockVersion(blockMajorVersion);
    medianSize = std::max(medianSize, blockGrantedFullRewardZone);
    if (currentBlockSize > medianSize * UINT64_C(2)) {
        logger(TRACE)
            << "Block cumulative size is too big: "
            << currentBlockSize
            << ", expected less than "
            << medianSize * UINT64_C(2);
        return false;
    }

    uint64_t penalizedBaseReward = getPenalizedAmount(baseReward, medianSize, currentBlockSize);
    uint64_t penalizedFee = fee;
    if (blockMajorVersion >= BLOCK_MAJOR_VERSION_2 || cryptonoteCoinVersion() == 1) {
        penalizedFee = getPenalizedAmount(fee, medianSize, currentBlockSize);
    }

    emissionChange = penalizedBaseReward - (fee - penalizedFee);
    reward = penalizedBaseReward + penalizedFee;

    return true;
}

size_t Currency::maxBlockCumulativeSize(uint64_t height) const
{
    assert(height <= std::numeric_limits<uint64_t>::max() / m_maxBlockSizeGrowthSpeedNumerator);

    size_t maxSize =
        static_cast<size_t>(m_maxBlockSizeInitial
        + (height * m_maxBlockSizeGrowthSpeedNumerator) / m_maxBlockSizeGrowthSpeedDenominator);

    assert(maxSize >= m_maxBlockSizeInitial);

    return maxSize;
}

bool Currency::isGovernanceEnabled(uint32_t height) const
{
    if (height >= m_governanceHeightStart && height <= m_governanceHeightEnd) {
        return true;
    }
    else {
        return false;
    }
}

uint64_t Currency::getGovernanceReward(uint64_t base_reward) const
{
    // minimum is 1% to avoid a zero amount and maximum is 50%
    uint16_t percent = (m_governancePercent < 1) ? 1 : (m_governancePercent > 50) ? 50 : m_governancePercent;
    return (uint64_t)(base_reward * (percent * 0.01));
}

bool Currency::validate_government_fee(const Transaction &baseTx) const
{
    AccountKeys governanceKeys;
    getGovernanceAddressAndKey(governanceKeys);

    Crypto::PublicKey txPublicKey = getTransactionPublicKeyFromExtra(baseTx.extra);

    Crypto::KeyDerivation derivation;
    if (!Crypto::generate_key_derivation( txPublicKey,
                                  governanceKeys.viewSecretKey,
                                  derivation)) {
        return false;
    }

    uint64_t minerReward = 0;
    uint64_t governmentFee = 0;
    for (size_t idx = 0; idx < baseTx.outputs.size(); ++idx) {
        minerReward += baseTx.outputs[idx].amount;
        if (baseTx.outputs[idx].target.type() != typeid(CryptoNote::KeyOutput))
            continue;
        Crypto::PublicKey outEphemeralKey;
        Crypto::derive_public_key(derivation, idx,
            governanceKeys.address.spendPublicKey, outEphemeralKey);
        if (outEphemeralKey == boost::get<KeyOutput>(baseTx.outputs[idx].target).key)
            governmentFee += baseTx.outputs[idx].amount;
    }

    return (governmentFee == getGovernanceReward(minerReward));
}

bool Currency::getGovernanceAddressAndKey(AccountKeys& governanceKeys) const
{
    std::string address       = GOVERNANCE_WALLET_ADDRESS;
    std::string viewSecretkey = GOVERNANCE_VIEW_SECRET_KEY;

    AccountPublicAddress governanceAddress = boost::value_initialized<AccountPublicAddress>();
    if (!parseAccountAddressString(address, governanceAddress)) {
        logger(Logging::ERROR)
            << "Failed to parse governance wallet address ("
            << address
            << "), "
            << "Check /lib/Global/CryptoNoteConfig.h";
        return false;
    }

    Crypto::SecretKey governanceViewSecretKey;
    if (!Common::podFromHex(viewSecretkey, governanceViewSecretKey)) {
        logger(Logging::ERROR)
            << "Failed to parse governance view secret key"
            << "Check /lib/Global/CryptoNoteConfig.h";
        return false;
    }

    governanceKeys.address = governanceAddress;
    governanceKeys.viewSecretKey = governanceViewSecretKey;

    return true;
}

bool Currency::constructMinerTx(
    uint8_t blockMajorVersion,
    uint32_t height,
    size_t medianSize,
    uint64_t alreadyGeneratedCoins,
    size_t currentBlockSize,
    uint64_t fee,
    const AccountPublicAddress &minerAddress,
    Transaction &tx,
    const BinaryArray &extraNonce /* = BinaryArray()*/,
    size_t maxOuts /* = 1*/,
    uint64_t blockTarget /* = 0xffffffffffffffff*/) const
{
    if (blockTarget == 0xffffffffffffffff)
        blockTarget = difficultyTarget();
    tx.inputs.clear();
    tx.outputs.clear();
    tx.extra.clear();

    KeyPair txkey = generateKeyPair();
    addTransactionPublicKeyToExtra(tx.extra, txkey.publicKey);
    if (!extraNonce.empty()) {
        if (!addExtraNonceToTransactionExtra(tx.extra, extraNonce)) {
            return false;
        }
    }

    BaseInput in;
    in.blockIndex = height;

    uint64_t governanceReward = 0;
    uint64_t totalRewardWGR = 0; //totalRewardWithoutGovernanceReward

    uint64_t blockReward;
    int64_t emissionChange;

    if (!getBlockReward(
            blockMajorVersion,
            medianSize,
            currentBlockSize,
            alreadyGeneratedCoins,
            fee,
            blockReward,
            emissionChange,
            height,
            blockTarget)
        ) {
        logger(INFO) << "Block is too big";
        return false;
    }

    totalRewardWGR = blockReward;

    // If Governance Fee blockReward is decreased by GOVERNANCE_PERCENT_FEE
    bool enableGovernance = isGovernanceEnabled(height);

    if (enableGovernance) {
        governanceReward = getGovernanceReward(blockReward);

        if (alreadyGeneratedCoins != 0) {
            blockReward -= governanceReward;
            totalRewardWGR  = blockReward + governanceReward;
        }
    }

    std::vector<uint64_t> outAmounts;
    decompose_amount_into_digits(
        blockReward,
        UINT64_C(0),
        [&outAmounts](uint64_t a_chunk) { outAmounts.push_back(a_chunk); },
        [&outAmounts](uint64_t a_dust) { outAmounts.push_back(a_dust); }
    );

    if (maxOuts < 1) {
        logger(ERROR, BRIGHT_RED) << "max_out must be non-zero";
        return false;
    }
    while (maxOuts < outAmounts.size()) {
        outAmounts[outAmounts.size() - 2] += outAmounts.back();
        outAmounts.resize(outAmounts.size() - 1);
    }

    uint64_t summaryAmounts = 0;
    for (size_t no = 0; no < outAmounts.size(); no++) {
        Crypto::KeyDerivation derivation = boost::value_initialized<Crypto::KeyDerivation>();
        Crypto::PublicKey outEphemeralPubKey = boost::value_initialized<Crypto::PublicKey>();

        bool r = Crypto::generate_key_derivation(
            minerAddress.viewPublicKey,
            txkey.secretKey,
            derivation
        );

        if (!(r)) {
            logger(ERROR, BRIGHT_RED)
                << "while creating outs: failed to generate_key_derivation("
                << minerAddress.viewPublicKey
                << ", "
                << txkey.secretKey
                << ")";
            return false;
        }

        r = Crypto::derive_public_key(derivation,no,minerAddress.spendPublicKey,outEphemeralPubKey);

        if (!(r)) {
            logger(ERROR, BRIGHT_RED)
                << "while creating outs: failed to derive_public_key("
                << derivation << ", "
                << no << ", "
                << minerAddress.spendPublicKey
                << ")";
            return false;
        }

        KeyOutput tk;
        tk.key = outEphemeralPubKey;

        TransactionOutput out;
        summaryAmounts += out.amount = outAmounts[no];
        out.target = tk;
        tx.outputs.push_back(out);
    }

    if (enableGovernance) {
        AccountKeys governanceKeys;
        getGovernanceAddressAndKey(governanceKeys);

        Crypto::KeyDerivation derivation = boost::value_initialized<Crypto::KeyDerivation>();
        Crypto::PublicKey outEphemeralPubKey = boost::value_initialized<Crypto::PublicKey>();

        bool r = Crypto::generate_key_derivation(governanceKeys.address.viewPublicKey, txkey.secretKey, derivation);
        if (!(r)) {
            logger(ERROR, BRIGHT_RED)
                << "while creating outs: failed to generate_key_derivation("
                << governanceKeys.address.viewPublicKey << ", " << txkey.secretKey << ")";
            return false;
        }
        size_t pos = tx.outputs.size();
        r = Crypto::derive_public_key(derivation, pos++, governanceKeys.address.spendPublicKey, outEphemeralPubKey);
        if (!(r)) {
            logger(ERROR, BRIGHT_RED)
                << "while creating outs: failed to derive_public_key("
                << derivation << ", " << 0 << ", "
                << governanceKeys.address.spendPublicKey << ")";
            return false;
        }
        KeyOutput tk;
        tk.key = outEphemeralPubKey;

        TransactionOutput out;
        summaryAmounts += out.amount = governanceReward;
        out.target = tk;
        tx.outputs.push_back(out);
    }

    if (summaryAmounts != totalRewardWGR) {
        logger(ERROR, BRIGHT_RED)
            << "Failed to construct miner tx, summaryAmounts = "
            << summaryAmounts
            << " not equal blockReward = "
            << blockReward;
        return false;
    }

    tx.version = CURRENT_TRANSACTION_VERSION;
    //lock
    tx.unlockTime = height + m_minedMoneyUnlockWindow;
    tx.inputs.push_back(in);

    return true;
}

bool Currency::isFusionTransaction(
    const std::vector<uint64_t> &inputsAmounts,
    const std::vector<uint64_t> &outputsAmounts,
    size_t size,
    uint32_t height) const
{
    // TODO: Simplify if (...) content.
    if (height <= CryptoNote::parameters::UPGRADE_HEIGHT_V3 ? size > CryptoNote::parameters::CRYPTONOTE_BLOCK_GRANTED_FULL_REWARD_ZONE_CURRENT * 30 / 100 : size > fusionTxMaxSize()) {
        logger(ERROR) << "Fusion transaction verification failed: size exceeded max allowed size.";
        return false;
    }

    if (inputsAmounts.size() < fusionTxMinInputCount()) {
        logger(ERROR)
            << "Fusion transaction verification failed: "
            << "inputs count is less than minimum.";
        return false;
    }

    if (inputsAmounts.size() < outputsAmounts.size() * fusionTxMinInOutCountRatio()) {
        logger(ERROR)
            << "Fusion transaction verification failed: "
            << "inputs to outputs count ratio is less than minimum.";
        return false;
    }

    uint64_t inputAmount = 0;
    for (auto amount : inputsAmounts) {
        if (height < CryptoNote::parameters::UPGRADE_HEIGHT_V4) {
            if (amount < defaultDustThreshold()) {
                logger(ERROR)
                    << "Fusion transaction verification failed: amount "
                    << amount
                    << " is less than dust threshold.";
                return false;
            }
        }
        inputAmount += amount;
    }

    std::vector<uint64_t> expectedOutputsAmounts;
    expectedOutputsAmounts.reserve(outputsAmounts.size());
    decomposeAmount(
        inputAmount,
        height < CryptoNote::parameters::UPGRADE_HEIGHT_V4
        ? defaultDustThreshold()
        : UINT64_C(0), expectedOutputsAmounts);
    std::sort(expectedOutputsAmounts.begin(), expectedOutputsAmounts.end());

    bool decompose = expectedOutputsAmounts == outputsAmounts;
    if (!decompose) {
        logger(ERROR)
            << "Fusion transaction verification failed: "
            << "decomposed output amounts do not match expected.";
        return false;
    }

    return true;
}

bool Currency::isFusionTransaction(const Transaction &transaction,size_t size,uint32_t height) const
{
    assert(getObjectBinarySize(transaction) == size);

    std::vector<uint64_t> outputsAmounts;
    outputsAmounts.reserve(transaction.outputs.size());
    for (const TransactionOutput &output : transaction.outputs) {
        outputsAmounts.push_back(output.amount);
    }

    return isFusionTransaction(getInputsAmounts(transaction), outputsAmounts, size, height);
}

bool Currency::isFusionTransaction(const Transaction &transaction, uint32_t height) const
{
    return isFusionTransaction(transaction, getObjectBinarySize(transaction), height);
}

bool Currency::isAmountApplicableInFusionTransactionInput(
    uint64_t amount,
    uint64_t threshold,
    uint32_t height) const
{
    uint8_t ignore;
    return isAmountApplicableInFusionTransactionInput(amount, threshold, ignore, height);
}

bool Currency::isAmountApplicableInFusionTransactionInput(
    uint64_t amount,
    uint64_t threshold,
    uint8_t &amountPowerOfTen,
    uint32_t height) const
{
    if (amount >= threshold) {
        return false;
    }

    if (height < CryptoNote::parameters::UPGRADE_HEIGHT_V4 && amount < defaultDustThreshold()) {
        return false;
    }

    auto it = std::lower_bound(
        Constants::PRETTY_AMOUNTS.begin(),
        Constants::PRETTY_AMOUNTS.end(),
        amount
    );
    if (it == Constants::PRETTY_AMOUNTS.end() || amount != *it) {
        return false;
    }

    amountPowerOfTen = static_cast<uint8_t>(std::distance(Constants::PRETTY_AMOUNTS.begin(), it)/9);

    return true;
}

std::string Currency::accountAddressAsString(const AccountBase &account) const
{
    return getAccountAddressAsStr(m_publicAddressBase58Prefix, account.getAccountKeys().address);
}

std::string Currency::accountAddressAsString(const AccountPublicAddress &accountPublicAddress) const
{
    return getAccountAddressAsStr(m_publicAddressBase58Prefix, accountPublicAddress);
}

bool Currency::parseAccountAddressString(const std::string &str, AccountPublicAddress &addr) const
{
    uint64_t prefix;
    if (!CryptoNote::parseAccountAddressString(prefix, addr, str)) {
        return false;
    }

    if (prefix != m_publicAddressBase58Prefix) {
        logger(DEBUGGING)
        << "Wrong address prefix: " << prefix
        << ", expected " << m_publicAddressBase58Prefix;
        return false;
    }

    return true;
}

std::string Currency::formatAmount(uint64_t amount) const
{
    std::string s = std::to_string(amount);
    if (s.size() < m_numberOfDecimalPlaces + 1) {
        s.insert(0, m_numberOfDecimalPlaces + 1 - s.size(), '0');
    }
    s.insert(s.size() - m_numberOfDecimalPlaces, ".");

    return s;
}

std::string Currency::formatAmount(int64_t amount) const
{
    std::string s = formatAmount(static_cast<uint64_t>(std::abs(amount)));

    if (amount < 0) {
        s.insert(0, "-");
    }

    return s;
}

bool Currency::parseAmount(const std::string &str, uint64_t &amount) const
{
    std::string strAmount = str;
    boost::algorithm::trim(strAmount);

    size_t pointIndex = strAmount.find_first_of('.');
    size_t fractionSize;
    if (std::string::npos != pointIndex) {
        fractionSize = strAmount.size() - pointIndex - 1;
        while (m_numberOfDecimalPlaces < fractionSize && '0' == strAmount.back()) {
            strAmount.erase(strAmount.size() - 1, 1);
            --fractionSize;
        }
        if (m_numberOfDecimalPlaces < fractionSize) {
            return false;
        }
        strAmount.erase(pointIndex, 1);
    } else {
        fractionSize = 0;
    }

    if (strAmount.empty()) {
        return false;
    }

    if (!std::all_of(strAmount.begin(), strAmount.end(), ::isdigit)) {
        return false;
    }

    if (fractionSize < m_numberOfDecimalPlaces) {
        strAmount.append(m_numberOfDecimalPlaces - fractionSize, '0');
    }

    return Common::fromString(strAmount, amount);
}

// Copyright (c) 2017-2018 Zawy
// http://zawy1.blogspot.com/2017/12/using-difficulty-to-get-constant-value.html
// Moore's law application by Sergey Kozlov
uint64_t Currency::getMinimalFee(
    uint64_t dailyDifficulty,
    uint64_t reward,
    uint64_t avgHistoricalDifficulty,
    uint64_t medianHistoricalReward,
    uint32_t height) const
{
    const uint64_t blocksInTwoYears = parameters::EXPECTED_NUMBER_OF_BLOCKS_PER_DAY * 365 * 2;
    const double gauge = double(0.25);
    uint64_t minimumFee(0);
    double dailyDifficultyMoore = dailyDifficulty
                                  / pow(2, static_cast<double>(height)
                                  / static_cast<double>(blocksInTwoYears));
    double minFee = gauge * parameters::COIN * static_cast<double>(avgHistoricalDifficulty)
                    / dailyDifficultyMoore * static_cast<double>(reward)
                    / static_cast<double>(medianHistoricalReward);
    if (minFee == 0 || !std::isfinite(minFee)) {
        return CryptoNote::parameters::MAXIMUM_FEE; // zero test
    }
    minimumFee = static_cast<uint64_t>(minFee);

    // TODO: return std::min<uint64_t>(CryptoNote::parameters::MAXIMUM_FEE, minimumFee);
    return CryptoNote::parameters::MINIMUM_FEE_V1;
}

uint64_t Currency::roundUpMinFee(uint64_t minimalFee, int digits) const
{
    uint64_t ret(0);
    std::string minFeeString = formatAmount(minimalFee);
    double minFee = boost::lexical_cast<double>(minFeeString);
    double scale = pow(10., floor(log10(fabs(minFee))) + (1 - digits));
    double roundedFee = ceil(minFee / scale) * scale;
    std::stringstream ss;
    ss << std::fixed << std::setprecision(12) << roundedFee;
    std::string roundedFeeString = ss.str();
    parseAmount(roundedFeeString, ret);

    return ret;
}

difficulty_type Currency::nextDifficulty(uint32_t height,
    uint8_t blockMajorVersion,
    std::vector<uint64_t> timestamps,
    std::vector<difficulty_type> cumulativeDifficulties,
    uint64_t nextBlockTime, lazy_stat_callback_type &lazy_stat_cb) const
{
    // check if we use special scenario with some fixed diff
    if (CryptoNote::parameters::FIXED_DIFFICULTY > 0)
    {
        logger (WARNING) << "Fixed difficulty is used: " <<
                            CryptoNote::parameters::FIXED_DIFFICULTY;
        return CryptoNote::parameters::FIXED_DIFFICULTY;
    }
    if (m_fixedDifficulty > 0)
    {
        logger (WARNING) << "Fixed difficulty is used: " <<
                            m_fixedDifficulty;
        return m_fixedDifficulty;
    }

    uint64_t last_timestamp = 0;
    if (!timestamps.empty()) {
        last_timestamp = timestamps.back();
    }
    if ((blockMajorVersion >= BLOCK_MAJOR_VERSION_6) &&
            (nextBlockTime > last_timestamp + CryptoNote::parameters::CRYPTONOTE_CLIF_THRESHOLD)) {
        size_t array_size = cumulativeDifficulties.size();
        difficulty_type last_difficulty = 1;
        if (array_size >= 2) {
            last_difficulty = cumulativeDifficulties[array_size - 1] - cumulativeDifficulties[array_size - 2];
        }
        uint64_t currentSolveTime = nextBlockTime - last_timestamp;
        return getClifDifficulty(height, blockMajorVersion,
                               last_difficulty, last_timestamp,
                               currentSolveTime, lazy_stat_cb);
    }

    if (blockMajorVersion >= BLOCK_MAJOR_VERSION_6) {
        return nextDifficultyV6(blockMajorVersion, timestamps, cumulativeDifficulties, height);
    } else if (blockMajorVersion >= BLOCK_MAJOR_VERSION_5) {
        return nextDifficultyV5(blockMajorVersion, timestamps, cumulativeDifficulties);
    } else if (blockMajorVersion == BLOCK_MAJOR_VERSION_3
               || blockMajorVersion == BLOCK_MAJOR_VERSION_4) {
        return nextDifficultyV3(timestamps, cumulativeDifficulties);
    } else if (blockMajorVersion == BLOCK_MAJOR_VERSION_2) {
        return nextDifficultyV2(timestamps, cumulativeDifficulties);
    } else {
        return nextDifficultyV1(timestamps, cumulativeDifficulties);
    }
}

difficulty_type Currency::nextDifficultyV1(
    std::vector<uint64_t> timestamps,
    std::vector<difficulty_type> cumulativeDifficulties) const
{
    assert(m_difficultyWindow >= 2);

    if (timestamps.size() > m_difficultyWindow) {
        timestamps.resize(m_difficultyWindow);
        cumulativeDifficulties.resize(m_difficultyWindow);
    }

    size_t length = timestamps.size();
    assert(length == cumulativeDifficulties.size());
    assert(length <= m_difficultyWindow);
    if (length <= 1) {
        return 1;
    }

    sort(timestamps.begin(), timestamps.end());

    size_t cutBegin, cutEnd;
    assert(2 * m_difficultyCut <= m_difficultyWindow - 2);
    if (length <= m_difficultyWindow - 2 * m_difficultyCut) {
        cutBegin = 0;
        cutEnd = length;
    } else {
        cutBegin = (length - (m_difficultyWindow - 2 * m_difficultyCut) + 1) / 2;
        cutEnd = cutBegin + (m_difficultyWindow - 2 * m_difficultyCut);
    }
    assert(/*cut_begin >= 0 &&*/ cutBegin + 2 <= cutEnd && cutEnd <= length);
    uint64_t timeSpan = timestamps[cutEnd - 1] - timestamps[cutBegin];
    if (timeSpan == 0) {
        timeSpan = 1;
    }

    difficulty_type totalWork = cumulativeDifficulties[cutEnd - 1]-cumulativeDifficulties[cutBegin];
    assert(totalWork > 0);

    uint64_t low, high;
    low = mul128(totalWork, m_difficultyTarget, &high);
    if (high != 0 || low + timeSpan - 1 < low) {
        return 0;
    }

    return (low + timeSpan - 1) / timeSpan;
}

difficulty_type Currency::nextDifficultyV2(
    std::vector<uint64_t> timestamps,
    std::vector<difficulty_type> cumulativeDifficulties) const
{
    // Difficulty calculation v. 2
    // based on Zawy difficulty algorithm v1.0
    // next Diff = Avg past N Diff * TargetInterval / Avg past N solve times
    // as described at https://github.com/monero-project/research-lab/issues/3
    // Window time span and total difficulty is taken instead of average
    // as suggested by Nuclear_chaos

    size_t m_difficultyWindow_2 = CryptoNote::parameters::DIFFICULTY_WINDOW_V2;
    assert(m_difficultyWindow_2 >= 2);

    if (timestamps.size() > m_difficultyWindow_2) {
        timestamps.resize(m_difficultyWindow_2);
        cumulativeDifficulties.resize(m_difficultyWindow_2);
    }

    size_t length = timestamps.size();
    assert(length == cumulativeDifficulties.size());
    assert(length <= m_difficultyWindow_2);
    if (length <= 1) {
        return 1;
    }

    sort(timestamps.begin(), timestamps.end());

    uint64_t timeSpan = timestamps.back() - timestamps.front();
    if (timeSpan == 0) {
        timeSpan = 1;
    }

    difficulty_type totalWork = cumulativeDifficulties.back() - cumulativeDifficulties.front();
    assert(totalWork > 0);

    // uint64_t nextDiffZ = totalWork * m_difficultyTarget / timeSpan;

    uint64_t low, high;
    low = mul128(totalWork, m_difficultyTarget, &high);
    // blockchain error "Difficulty overhead" if this function returns zero
    if (high != 0) {
        return 0;
    }

    uint64_t nextDiffZ = low / timeSpan;

    // minimum limit
    if (!isTestnet() && nextDiffZ < 100000) {
        nextDiffZ = 100000;
    }

    return nextDiffZ;
}

difficulty_type Currency::nextDifficultyV3(
    std::vector<uint64_t> timestamps,
    std::vector<difficulty_type> cumulativeDifficulties) const
{
    // LWMA difficulty algorithm
    // Copyright (c) 2017-2018 Zawy
    // MIT license http://www.opensource.org/licenses/mit-license.php.
    // This is an improved version of Tom Harding's (Deger8) "WT-144"
    // Karbowanec, Masari, Bitcoin Gold, and Bitcoin Cash have contributed.
    // See https://github.com/zawy12/difficulty-algorithms/issues/1 for other algos.
    // Do not use "if solvetime < 0 then solvetime = 1" which allows a catastrophic exploit.
    // T= target_solvetime;
    // N = int(45 * (600 / T) ^ 0.3));

    const int64_t T = static_cast<int64_t>(m_difficultyTarget);
    size_t N = CryptoNote::parameters::DIFFICULTY_WINDOW_V3;

    // return a difficulty of 1 for first 3 blocks if it's the start of the chain
    if (timestamps.size() < 4) {
        return 1;
    } else if (timestamps.size() < N + 1) {
        // otherwise, use a smaller N if the start of the chain is less than N+1
        N = timestamps.size() - 1;
    } else if (timestamps.size() > N + 1) {
        timestamps.erase(timestamps.begin(), timestamps.end() - N - 1);
        cumulativeDifficulties.erase(
            cumulativeDifficulties.begin(),
            cumulativeDifficulties.end() - N - 1
        );
    }

    // To get an average solvetime to within +/- ~0.1%, use an adjustment factor.
    const double adjust = 0.998;
    // The divisor k normalizes LWMA.
    const double k = N * (N + 1) / 2;

    double LWMA(0), sum_inverse_D(0), harmonic_mean_D(0), nextDifficulty(0);
    int64_t solveTime(0);
    uint64_t difficulty(0), next_difficulty(0);

    // Loop through N most recent blocks.
    for (size_t i = 1; i <= N; i++) {
        solveTime = static_cast<int64_t>(timestamps[i]) - static_cast<int64_t>(timestamps[i - 1]);
        solveTime = std::min<int64_t>((T * 7), std::max<int64_t>(solveTime, (-6 * T)));
        difficulty = cumulativeDifficulties[i] - cumulativeDifficulties[i - 1];
        LWMA += (int64_t)(solveTime * i) / k;
        sum_inverse_D += 1 / static_cast<double>(difficulty);
    }

    // Keep LWMA sane in case something unforeseen occurs.
    if (static_cast<int64_t>(boost::math::round(LWMA)) < T / 20)
    LWMA = static_cast<double>(T) / 20;

    harmonic_mean_D = N / sum_inverse_D * adjust;
    nextDifficulty = harmonic_mean_D * T / LWMA;
    next_difficulty = static_cast<uint64_t>(nextDifficulty);

    // minimum limit
    if (!isTestnet() && next_difficulty < 100000) {
        next_difficulty = 100000;
    }

    return next_difficulty;
}

template <typename T>
inline T clamp(T lo, T v, T hi)
{
    return v < lo ? lo : v > hi ? hi : v;
}

// difficulty for block version 5.0
difficulty_type Currency::nextDifficultyV5(
    uint8_t blockMajorVersion,
    std::vector<std::uint64_t> timestamps,
    std::vector<difficulty_type> cumulativeDifficulties) const
{
    // LWMA-2 difficulty algorithm
    // Copyright (c) 2017-2018 Zawy, MIT License
    // https://github.com/zawy12/difficulty-algorithms/issues/3
    // with modifications by Ryo Currency developers
    // courtesy to aivve from Karbo

    const int64_t  T = static_cast<int64_t>(m_difficultyTarget);
    int64_t  N = difficultyBlocksCount3();
    int64_t  L(0), ST, sum_3_ST(0);
    uint64_t nextDiffV5, prev_D;

    assert(timestamps.size() == cumulativeDifficulties.size()
           && timestamps.size() <= static_cast<uint64_t>(N + 1));

    int64_t max_TS, prev_max_TS;
    prev_max_TS = timestamps[0];
    for (int64_t i = 1; i <= N; i++) {
        if (static_cast<int64_t>(timestamps[i]) > prev_max_TS) {
            max_TS = timestamps[i];
        } else {
            max_TS = prev_max_TS + 1;
        }
        ST = std::min(6 * T, max_TS - prev_max_TS);
        prev_max_TS = max_TS;
        L += ST * i;
        if (i > N - 3) {
            sum_3_ST += ST;
        }
    }

    // It is a potential error,  N should be less than cumulativeDifficulties.size()
    nextDiffV5 = uint64_t((cumulativeDifficulties[N] - cumulativeDifficulties[0]) * T * (N + 1))
                 / uint64_t(2 * L);
    nextDiffV5 = (nextDiffV5 * 99ull) / 100ull;

    // It is a potential error,  N should be less than cumulativeDifficulties.size()
    prev_D = cumulativeDifficulties[N] - cumulativeDifficulties[N - 1];
    nextDiffV5 = clamp(
        (uint64_t)(prev_D * 67ull / 100ull),
        nextDiffV5,
        (uint64_t)(prev_D * 150ull / 100ull)
    );
    if (sum_3_ST < (8 * T) / 10) {
        nextDiffV5 = (prev_D * 110ull) / 100ull;
    }

    // minimum limit
    if (nextDiffV5 < 10000000) {
        nextDiffV5 = 10000000;
    }
    if(isTestnet()){
        nextDiffV5 = 10000;
    }

    return nextDiffV5;
}

// difficulty for block version 6.0
difficulty_type Currency::nextDifficultyV6(uint8_t blockMajorVersion,
    std::vector<uint64_t> timestamps,
    std::vector<difficulty_type> cumulativeDifficulties,
    uint32_t height) const
{
    if(isTestnet()){
        return CryptoNote::parameters::DEFAULT_DIFFICULTY;
    }

    if (timestamps.empty()) {
        return CryptoNote::parameters::DEFAULT_DIFFICULTY;
    }
    // Dynamic difficulty calculation window
    uint32_t diffWindow = timestamps.size() - 1;

    difficulty_type nextDiffV6 = CryptoNote::parameters::DEFAULT_DIFFICULTY;
    difficulty_type min_difficulty = CryptoNote::parameters::DEFAULT_DIFFICULTY;
    // Condition #1 When starting a chain or a working testnet requiring
    // block sample gathering until enough blocks are available (Kick-off Scenario)
    // We shall set a baseline for the hashrate that is specific to mining
    // algo and I propose doing so in the config file.
    // *** During this initial sampling period, a best guess is the best strategy.
    // Consider this as a service or trial period.
    // With EPoW reward algo in place, we don't really need to worry about attackers
    // or large miners taking advanatage of our system.
    if (height < CryptoNote::parameters::UPGRADE_HEIGHT_V6 + diffWindow) {
        return nextDiffV6;
    }

    // check all values in input vectors greater than previous value to calc adjacent differences later
    if (std::adjacent_find(timestamps.begin(), timestamps.end(), std::greater<uint64_t>()) != timestamps.end()) {
        logger (ERROR) << "Invalid timestamps for difficulty calculation";
        return nextDiffV6;
    }
    if (std::adjacent_find(cumulativeDifficulties.begin(), cumulativeDifficulties.end(), std::greater_equal<difficulty_type>()) != cumulativeDifficulties.end()) {
        logger (ERROR) << "Invalid cumulativeDifficulties for difficulty calculation";
        return nextDiffV6;
    }

    uint64_t difficulty_target = CryptoNote::parameters::DIFFICULTY_TARGET;
    uint64_t window_target = difficulty_target * diffWindow;
    uint64_t window_time = timestamps.back() - timestamps.front(); // now subtraction is safe,  we know it is not negative

    // get solvetimes from timestamps
    std::vector<uint64_t> solveTimes;
    solveTimes.resize(timestamps.size());
    std::adjacent_difference(timestamps.begin(), timestamps.end(), solveTimes.begin()); // we check it before and all diffs are positive
    solveTimes.erase(solveTimes.begin());

    // get difficulties from the cumulative difficulties
    std::vector<difficulty_type> difficulties;
    difficulties.resize(cumulativeDifficulties.size());
    std::adjacent_difference(cumulativeDifficulties.begin(),
                             cumulativeDifficulties.end(),
                             difficulties.begin()); // we check it before and all diffs are positive
    difficulties.erase(difficulties.begin());
    difficulty_type prev_difficulty = difficulties.back();

    // calc stat values to detect outliers
    double avg_solvetime = Common::meanValue(solveTimes);
    double stddev_solvetime = Common::stddevValue(solveTimes);
    double solvetime_lowborder = 1.0;
    if(avg_solvetime > stddev_solvetime)
        solvetime_lowborder = avg_solvetime - stddev_solvetime;
    double solvetime_highborder = avg_solvetime + stddev_solvetime;
    size_t valid_solvetime_number = 0;
    uint64_t valid_solvetime_sum = 0;
    size_t invalid_solvetime_number = 0;
    uint64_t invalid_solvetime_sum = 0;
    std::for_each(solveTimes.begin(), solveTimes.end(),
                  [solvetime_lowborder, solvetime_highborder,
                   &valid_solvetime_number, &valid_solvetime_sum,
                   &invalid_solvetime_number, &invalid_solvetime_sum] (uint64_t st) {
        if ((st >= solvetime_lowborder) && (st <= solvetime_highborder)) {
            valid_solvetime_number++;
            valid_solvetime_sum += st;
        } else {
            invalid_solvetime_number++;
            invalid_solvetime_sum += st;
        }
    });

    // if there is no "invalid" solvetimes we can use previous difficulty value
    if (invalid_solvetime_number == 0) {
        return std::max(prev_difficulty, min_difficulty);
    }

    // process data with "invalid" solvetimes
    double valid_solvetime_mean = double(valid_solvetime_sum) / valid_solvetime_number;
    double invalid_solvetime_mean = double(invalid_solvetime_sum) / invalid_solvetime_number;

    if ( (window_time >= window_target * 0.97) &&
         (window_time <= window_target * 1.03) ) {
        if (valid_solvetime_mean >= invalid_solvetime_mean) {
            double coef = double(difficulty_target) / double(valid_solvetime_mean);
            if (valid_solvetime_mean < difficulty_target) {
                nextDiffV6 = prev_difficulty * std::min(1.01, coef) + 0.5;
            } else {
                nextDiffV6 = prev_difficulty * std::max(0.99, coef) + 0.5;
            }
        } else {
            double coef = double(difficulty_target) / double(invalid_solvetime_mean);
            if (invalid_solvetime_mean < difficulty_target) {
                nextDiffV6 = prev_difficulty * std::min(1.01, coef) + 0.5;
            } else {
                nextDiffV6 = prev_difficulty * std::max(0.99, coef) + 0.5;
            }
        }
    } else if (window_time < window_target * 0.97) {
        nextDiffV6 = prev_difficulty * 1.02 + 0.5;
    } else {
        nextDiffV6 = prev_difficulty * 0.98 + 0.5;
    }

    return std::max(nextDiffV6, min_difficulty);
}

difficulty_type Currency::getClifDifficulty(uint32_t height,
                                          uint8_t blockMajorVersion,
                                          difficulty_type last_difficulty,
                                          uint64_t last_timestamp,
                                          uint64_t currentSolveTime,
                                          lazy_stat_callback_type &lazy_stat_cb) const
{
    logger (INFO) << "CLIF difficulty inputs: height " << height <<
                     ", block version " << (int)blockMajorVersion <<
                     ", last difficulty " << last_difficulty <<
                     ", current solve time " << currentSolveTime;

    difficulty_type new_diff = last_difficulty;

    if (new_diff > CryptoNote::parameters::DEFAULT_DIFFICULTY) {
        uint64_t correction_interval = currentSolveTime -
                CryptoNote::parameters::CRYPTONOTE_CLIF_THRESHOLD;
        //below equation shall return quotient of the division.
        int decrease_counter = ((int)correction_interval / (int)CryptoNote::parameters::DIFFICULTY_TARGET) + 1;
        int round_counter = 1;

        new_diff = new_diff / 2;
        logger (INFO) << "CLIF decreased difficulty " << round_counter <<
            " times, intermediate difficulty is " << new_diff;
        difficulty_type mean_diff = lazy_stat_cb(IMinerHandler::stat_period::hour, last_timestamp);
        logger (INFO) << "Last hour average difficulty is " << mean_diff;
        if (mean_diff > 0)
            new_diff = std::min(mean_diff, new_diff);
        mean_diff = lazy_stat_cb(IMinerHandler::stat_period::day, last_timestamp);
        logger (INFO) << "Last day average difficulty is " << mean_diff;
        if (mean_diff > 0)
            new_diff = std::min(mean_diff, new_diff);
        mean_diff = lazy_stat_cb(IMinerHandler::stat_period::week, last_timestamp);
        logger (INFO) << "Last week average difficulty is " << mean_diff;
        if (mean_diff > 0)
            new_diff = std::min(mean_diff, new_diff);
        mean_diff = lazy_stat_cb(IMinerHandler::stat_period::month, last_timestamp);
        logger (INFO) << "Last month average difficulty is " << mean_diff;
        if (mean_diff > 0)
            new_diff = std::min(mean_diff, new_diff);
        mean_diff = lazy_stat_cb(IMinerHandler::stat_period::halfyear, last_timestamp);
        logger (INFO) << "Last halfyear average difficulty is " << mean_diff;
        if (mean_diff > 0)
            new_diff = std::min(mean_diff, new_diff);
        mean_diff = lazy_stat_cb(IMinerHandler::stat_period::year, last_timestamp);
        logger (INFO) << "Last year average difficulty is " << mean_diff;
        if (mean_diff > 0)
            new_diff = std::min(mean_diff, new_diff);

        if (decrease_counter > 1) {
            while (round_counter < decrease_counter) {
                new_diff = new_diff / 2;
                round_counter++;
                if (new_diff <= CryptoNote::parameters::DEFAULT_DIFFICULTY)
                    break;
            }
            logger (INFO) << "CLIF decreased difficulty " << round_counter <<
                " times, intermediate difficulty is " << new_diff;
        }

        new_diff = std::max(new_diff, difficulty_type(CryptoNote::parameters::DEFAULT_DIFFICULTY));
    }

    logger (INFO) << "CLIF difficulty result: " << new_diff;
    return new_diff;
}

bool Currency::checkProofOfWorkV1(
    Crypto::cn_context &context,
    const Block &block,
    difficulty_type currentDiffic,
    Crypto::Hash &proofOfWork) const
{
    if (BLOCK_MAJOR_VERSION_2 == block.majorVersion||BLOCK_MAJOR_VERSION_3 == block.majorVersion) {
        return false;
    }

    if (!get_block_longhash(context, block, proofOfWork)) {
        return false;
    }

    return check_hash(proofOfWork, currentDiffic);
}

bool Currency::checkProofOfWorkV2(
    Crypto::cn_context &context,
    const Block &block,
    difficulty_type currentDiffic,
    Crypto::Hash &proofOfWork) const
{
    if (block.majorVersion < BLOCK_MAJOR_VERSION_2) {
        return false;
    }

    if (!get_block_longhash(context, block, proofOfWork)) {
        return false;
    }

    if (!check_hash(proofOfWork, currentDiffic)) {
        return false;
    }

    TransactionExtraMergeMiningTag mmTag;
    if (!getMergeMiningTagFromExtra(block.parentBlock.baseTransaction.extra, mmTag)) {
        logger(ERROR)
            << "merge mining tag wasn't found in extra of the parent block miner transaction";
        return false;
    }

    if (8 * sizeof(m_genesisBlockHash) < block.parentBlock.blockchainBranch.size()) {
        return false;
    }

    Crypto::Hash auxBlockHeaderHash;
    if (!get_aux_block_header_hash(block, auxBlockHeaderHash)) {
        return false;
    }

    Crypto::Hash auxBlocksMerkleRoot;
    Crypto::tree_hash_from_branch(
        block.parentBlock.blockchainBranch.data(),
        block.parentBlock.blockchainBranch.size(),
        auxBlockHeaderHash,
        &m_genesisBlockHash,
        auxBlocksMerkleRoot
    );

    if (auxBlocksMerkleRoot != mmTag.merkleRoot) {
        logger(ERROR, BRIGHT_YELLOW) << "Aux block hash wasn't found in merkle tree";
        return false;
    }

    return true;
}

bool Currency::checkProofOfWork(
    Crypto::cn_context &context,
    const Block &block,
    difficulty_type currentDiffic,
    Crypto::Hash &proofOfWork) const
{
    switch (block.majorVersion) {
    case BLOCK_MAJOR_VERSION_1:
        // fall through
    case BLOCK_MAJOR_VERSION_4:
        // fall through
    case BLOCK_MAJOR_VERSION_5:
        // fall through
    case BLOCK_MAJOR_VERSION_6:
        return checkProofOfWorkV1(context, block, currentDiffic, proofOfWork);
    case BLOCK_MAJOR_VERSION_2:
        // fall through
    case BLOCK_MAJOR_VERSION_3:
        return checkProofOfWorkV2(context, block, currentDiffic, proofOfWork);
    }

    logger(ERROR, BRIGHT_RED)
        << "Unknown block major version: "
        << block.majorVersion
        << "."
        << block.minorVersion;

    return false;
}

size_t Currency::getApproximateMaximumInputCount(
    size_t transactionSize,
    size_t outputCount,
    size_t mixinCount) const
{
    const size_t KEY_IMAGE_SIZE = sizeof(Crypto::KeyImage);
    const size_t OUTPUT_KEY_SIZE = sizeof(decltype(KeyOutput::key));
    const size_t AMOUNT_SIZE = sizeof(uint64_t) + 2; // varint
    const size_t GLOBAL_INDEXES_VECTOR_SIZE_SIZE = sizeof(uint8_t); // varint
    const size_t GLOBAL_INDEXES_INITIAL_VALUE_SIZE = sizeof(uint32_t); // varint
    const size_t GLOBAL_INDEXES_DIFFERENCE_SIZE = sizeof(uint32_t); // varint
    const size_t SIGNATURE_SIZE = sizeof(Crypto::Signature);
    const size_t EXTRA_TAG_SIZE = sizeof(uint8_t);
    const size_t INPUT_TAG_SIZE = sizeof(uint8_t);
    const size_t OUTPUT_TAG_SIZE = sizeof(uint8_t);
    const size_t PUBLIC_KEY_SIZE = sizeof(Crypto::PublicKey);
    const size_t TRANSACTION_VERSION_SIZE = sizeof(uint8_t);
    const size_t TRANSACTION_UNLOCK_TIME_SIZE = sizeof(uint64_t);

    const size_t outputsSize = outputCount * (OUTPUT_TAG_SIZE + OUTPUT_KEY_SIZE + AMOUNT_SIZE);
    const size_t headerSize = TRANSACTION_VERSION_SIZE
                              + TRANSACTION_UNLOCK_TIME_SIZE
                              + EXTRA_TAG_SIZE
                              + PUBLIC_KEY_SIZE;
    const size_t inputSize = INPUT_TAG_SIZE
                             + AMOUNT_SIZE
                             + KEY_IMAGE_SIZE
                             + SIGNATURE_SIZE
                             + GLOBAL_INDEXES_VECTOR_SIZE_SIZE
                             + GLOBAL_INDEXES_INITIAL_VALUE_SIZE
                             + mixinCount * (GLOBAL_INDEXES_DIFFERENCE_SIZE + SIGNATURE_SIZE);

    return (transactionSize - headerSize - outputsSize) / inputSize;
}

CurrencyBuilder::CurrencyBuilder(Logging::ILogger &log)
    : m_currency(log)
{
    maxBlockNumber(parameters::CRYPTONOTE_MAX_BLOCK_NUMBER);
    maxBlockBlobSize(parameters::CRYPTONOTE_MAX_BLOCK_BLOB_SIZE);
    maxTxSize(parameters::CRYPTONOTE_MAX_TX_SIZE);
    publicAddressBase58Prefix(parameters::CRYPTONOTE_PUBLIC_ADDRESS_BASE58_PREFIX);
    minedMoneyUnlockWindow(parameters::CRYPTONOTE_MINED_MONEY_UNLOCK_WINDOW);
    transactionSpendableAge(parameters::CRYPTONOTE_TX_SPENDABLE_AGE);
    safeTransactionSpendableAge(parameters::CRYPTONOTE_SAFE_TX_SPENDABLE_AGE);
    expectedNumberOfBlocksPerDay(parameters::EXPECTED_NUMBER_OF_BLOCKS_PER_DAY);

    timestampCheckWindow(parameters::BLOCKCHAIN_TIMESTAMP_CHECK_WINDOW);
    timestampCheckWindow_v1(parameters::BLOCKCHAIN_TIMESTAMP_CHECK_WINDOW_V1);
    blockFutureTimeLimit(parameters::CRYPTONOTE_BLOCK_FUTURE_TIME_LIMIT);
    blockFutureTimeLimit_v1(parameters::CRYPTONOTE_BLOCK_FUTURE_TIME_LIMIT_V1);

    moneySupply(parameters::MONEY_SUPPLY);
    emissionSpeedFactor(parameters::EMISSION_SPEED_FACTOR);
    cryptonoteCoinVersion(parameters::CRYPTONOTE_COIN_VERSION);

    rewardBlocksWindow(parameters::CRYPTONOTE_REWARD_BLOCKS_WINDOW);
    blockGrantedFullRewardZone(parameters::CRYPTONOTE_BLOCK_GRANTED_FULL_REWARD_ZONE);
    minerTxBlobReservedSize(parameters::CRYPTONOTE_COINBASE_BLOB_RESERVED_SIZE);
    maxTransactionSizeLimit(parameters::MAX_TRANSACTION_SIZE_LIMIT);

    governancePercent(parameters::GOVERNANCE_PERCENT_FEE);
    governanceHeightStart(parameters::GOVERNANCE_HEIGHT_START);
    governanceHeightEnd(parameters::GOVERNANCE_HEIGHT_END);

    minMixin(parameters::MIN_TX_MIXIN_SIZE);
    maxMixin(parameters::MAX_TX_MIXIN_SIZE);

    numberOfDecimalPlaces(parameters::CRYPTONOTE_DISPLAY_DECIMAL_POINT);

    minimumFee(parameters::MINIMUM_FEE);
    defaultDustThreshold(parameters::DEFAULT_DUST_THRESHOLD);

    difficultyTarget(parameters::DIFFICULTY_TARGET);
    difficultyWindow(parameters::DIFFICULTY_WINDOW);
    difficultyLag(parameters::DIFFICULTY_LAG);
    difficultyCut(parameters::DIFFICULTY_CUT);

    maxBlockSizeInitial(parameters::MAX_BLOCK_SIZE_INITIAL);
    maxBlockSizeGrowthSpeedNumerator(parameters::MAX_BLOCK_SIZE_GROWTH_SPEED_NUMERATOR);
    maxBlockSizeGrowthSpeedDenominator(parameters::MAX_BLOCK_SIZE_GROWTH_SPEED_DENOMINATOR);

    lockedTxAllowedDeltaSeconds(parameters::CRYPTONOTE_LOCKED_TX_ALLOWED_DELTA_SECONDS);
    lockedTxAllowedDeltaBlocks(parameters::CRYPTONOTE_LOCKED_TX_ALLOWED_DELTA_BLOCKS);

    mempoolTxLiveTime(parameters::CRYPTONOTE_MEMPOOL_TX_LIVETIME);
    mempoolTxFromAltBlockLiveTime(parameters::CRYPTONOTE_MEMPOOL_TX_FROM_ALT_BLOCK_LIVETIME);
    numberOfPeriodsToForgetTxDeletedFromPool(
        parameters::CRYPTONOTE_NUMBER_OF_PERIODS_TO_FORGET_TX_DELETED_FROM_POOL
    );

    fusionTxMaxSize(parameters::FUSION_TX_MAX_SIZE);
    fusionTxMinInputCount(parameters::FUSION_TX_MIN_INPUT_COUNT);
    fusionTxMinInOutCountRatio(parameters::FUSION_TX_MIN_IN_OUT_COUNT_RATIO);

    upgradeHeightV2(parameters::UPGRADE_HEIGHT_V2);
    upgradeHeightV3(parameters::UPGRADE_HEIGHT_V3);
    upgradeHeightV4(parameters::UPGRADE_HEIGHT_V4);
    upgradeHeightV5(parameters::UPGRADE_HEIGHT_V5);
    upgradeHeightV6(parameters::UPGRADE_HEIGHT_V6);
    upgradeVotingThreshold(parameters::UPGRADE_VOTING_THRESHOLD);
    upgradeVotingWindow(parameters::UPGRADE_VOTING_WINDOW);
    upgradeWindow(parameters::UPGRADE_WINDOW);

    blocksFileName(parameters::CRYPTONOTE_BLOCKS_FILENAME);
    blocksCacheFileName(parameters::CRYPTONOTE_BLOCKSCACHE_FILENAME);
    blockIndexesFileName(parameters::CRYPTONOTE_BLOCKINDEXES_FILENAME);
    txPoolFileName(parameters::CRYPTONOTE_POOLDATA_FILENAME);
    blockchainIndicesFileName(parameters::CRYPTONOTE_BLOCKCHAIN_INDICES_FILENAME);

    testnet(false);
    fix_difficulty(0);
}

Transaction CurrencyBuilder::generateGenesisTransaction()
{
    CryptoNote::Transaction tx;
    auto ac = boost::value_initialized<CryptoNote::AccountPublicAddress>();

    m_currency.constructMinerTx(1, 0, 0, 0, 0, 0, ac, tx); // zero fee in genesis

    return tx;
}

CurrencyBuilder& CurrencyBuilder::emissionSpeedFactor(unsigned int val)
{
    if (val <= 0 || val > 8 * sizeof(uint64_t)) {
        throw std::invalid_argument("val at emissionSpeedFactor()");
    }

    m_currency.m_emissionSpeedFactor = val;

    return *this;
}

CurrencyBuilder &CurrencyBuilder::numberOfDecimalPlaces(size_t val)
{
    m_currency.m_numberOfDecimalPlaces = val;
    m_currency.m_coin = 1;
    for (size_t i = 0; i < m_currency.m_numberOfDecimalPlaces; ++i) {
        m_currency.m_coin *= 10;
    }

    return *this;
}

CurrencyBuilder &CurrencyBuilder::difficultyWindow(size_t val)
{
    if (val < 2) {
        throw std::invalid_argument("val at difficultyWindow()");
    }

    m_currency.m_difficultyWindow = val;

    return *this;
}

CurrencyBuilder& CurrencyBuilder::upgradeVotingThreshold(unsigned int val)
{
    if (val <= 0 || val > 100) {
        throw std::invalid_argument("val at upgradeVotingThreshold()");
    }

    m_currency.m_upgradeVotingThreshold = val;

    return *this;
}

CurrencyBuilder &CurrencyBuilder::upgradeWindow(size_t val)
{
    if (val <= 0) {
        throw std::invalid_argument("val at upgradeWindow()");
    }

    m_currency.m_upgradeWindow = static_cast<uint32_t>(val);

    return *this;
}

} // namespace CryptoNote


/*
 *  Copyright 2010-2011 Anders Wallin (anders.e.e.wallin "at" gmail.com)
 *
 *  This file is part of OpenCAMlib.
 *
 *  OpenCAMlib is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  OpenCAMlib is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with OpenCAMlib.  If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef HALFEDGEDIAGRAM_HPP
#define HALFEDGEDIAGRAM_HPP

#include <vector>
#include <list>

#include <boost/graph/adjacency_list.hpp>
#include <boost/foreach.hpp>

// bundled BGL properties, see: http://www.boost.org/doc/libs/1_44_0/libs/graph/doc/bundles.html

// dcel notes from http://www.holmes3d.net/graphics/dcel/

// vertex (boost::out_edges)
//  -leaving pointer to HalfEdge that has this vertex as origin
//   if many HalfEdges have this vertex as origin, choose one arbitrarily

// HalfEdge
//  - origin pointer to vertex (boost::source)
//  - face to the left of halfedge
//  - twin pointer to HalfEdge (on the right of this edge)
//  - next pointer to HalfEdge
//     this edge starts from h->twin->origin and ends at next vertex in h->face
//     traveling ccw around boundary
//     (allows face traverse, follow h->next until we arrive back at h)

// Face
//  - edge pointer to HalfEdge
//    this edge has this Face object as face
//    half-edge can be any one on the boundary of face
// special "infinite face", face on "outside" of boundary
// may or may not store edge pointer

/// HEDIGraph is a A half-edge diagram class.
/// Templated on Vertex/Edge/Face property classes which allow
/// attaching information to vertices/edges/faces that is
/// required for a particular algorithm.
///
/// Inherits from boost::adjacency_list
/// minor additions allow storing face-properties.
///
/// the hedi namespace contains functions for manipulating HEDIGraphs
///
/// For a general description of the half-edge data structure see e.g.:
///  - http://www.holmes3d.net/graphics/dcel/
///  - http://openmesh.org/index.php?id=228

namespace ocl {

namespace hedi  {

template <class TOutEdgeList,
          class TVertexList,
          class TDirected,
          class TVertexProperties,
          class TEdgeProperties,
          class TFaceProperties,
          class TGraphProperties,
          class TEdgeList
          >
class HEDIGraph {
    public:

        typedef typename boost::adjacency_list< TOutEdgeList,
                                                TVertexList,
                                                TDirected,
                                                TVertexProperties,
                                                TEdgeProperties,
                                                TGraphProperties,
                                                TEdgeList
                                                > BGLGraph;
        typedef unsigned int Face;
        typedef typename boost::graph_traits< BGLGraph >::edge_descriptor Edge;
        typedef typename boost::graph_traits< BGLGraph >::vertex_descriptor Vertex;
        typedef typename boost::graph_traits< BGLGraph >::vertex_iterator    VertexItr;
        typedef typename boost::graph_traits< BGLGraph >::edge_iterator      EdgeItr;
        typedef typename boost::graph_traits< BGLGraph >::out_edge_iterator  OutEdgeItr;
        typedef typename boost::graph_traits< BGLGraph >::adjacency_iterator AdjacencyItr;

        typedef std::vector<Vertex> VertexVector;
        typedef std::vector<Face> FaceVector;
        typedef std::vector<Edge> EdgeVector;

        inline TFaceProperties& operator[](Face f)  { return faces[f];  }
        inline const TFaceProperties& operator[](Face f) const  { return faces[f]; }

        inline TEdgeProperties& operator[](Edge e)  { return g[e];  }
        inline const TEdgeProperties& operator[](Edge e) const  { return g[e];  }

        inline TVertexProperties& operator[](Vertex v)  { return g[v];  }
        inline const TVertexProperties& operator[](Vertex v) const  { return g[v];  }

//DATA
        std::vector< TFaceProperties > faces;
        BGLGraph g;

Vertex null_vertex() {
    return boost::graph_traits<BGLGraph>::null_vertex();
}

/// add a blank vertex and return its descriptor
Vertex add_vertex() {
    return boost::add_vertex( g );
}

/*
/// add a vertex with given properties, return vertex descriptor
template < class FVertexProperty>
typename boost::graph_traits< BGLGraph >::vertex_descriptor add_vertex(typedef const FVertexProperty& prop) {
    return boost::add_vertex( prop, g );
}*/

/// add an edge between vertices v1-v2
Edge add_edge(Vertex v1, Vertex v2) {
    Edge e;
    bool b;
    boost::tie( e , b ) = boost::add_edge( v1, v2, g);
    return e;
}

/// add an edge with given properties between vertices v1-v2
//template < class EdgeProperty>
/*
typename boost::graph_traits< BGLGraph >::edge_descriptor add_edge(
                                                       typename boost::graph_traits< BGLGraph >::vertex_descriptor v1,
                                                       typename boost::graph_traits< BGLGraph >::vertex_descriptor v2,
                                                       typename  TEdgeProperties prop
                                                       ) {
    typename boost::graph_traits< BGLGraph >::edge_descriptor e;
    bool b;
    boost::tie( e , b ) = boost::add_edge( v1, v2, prop, g);
    return e;
}*/

/// make e1 the twin of e2 (and vice versa)
void twin_edges( Edge e1, Edge e2 ) {
    g[e1].twin = e2;
    g[e2].twin = e1;
}

/// add a face
Face add_face() {
    TFaceProperties f_prop;
    faces.push_back( f_prop);
    Face index = faces.size()-1;
    faces[index].idx = index;
    return index;
}

/// return the target vertex of the given edge
Vertex target( Edge e ) {
    return boost::target( e, g);
}

/// return the source vertex of the given edge
Vertex source( Edge e )  {
    return boost::source( e, g);
}

/// return all vertices in a vector of vertex descriptors
VertexVector vertices()  {
    typedef typename boost::graph_traits< BGLGraph >::vertex_descriptor  HEVertex;
    typedef std::vector<HEVertex> VertexVector;
    typedef typename boost::graph_traits< BGLGraph >::vertex_iterator    HEVertexItr;
    VertexVector vv;
    HEVertexItr it_begin, it_end, itr;
    boost::tie( it_begin, it_end ) = boost::vertices( g );
    for ( itr=it_begin ; itr != it_end ; ++itr ) {
        vv.push_back( *itr );
    }
    return vv;
}

/// return all vertices adjecent to given vertex
VertexVector adjacent_vertices(  Vertex v) {
    VertexVector vv;
    BOOST_FOREACH( Edge edge, out_edges( v ) ) {
        vv.push_back( target( edge ) );
    }
    return vv;
}

/// return all vertices of given face

VertexVector face_vertices(Face face_idx) {
    VertexVector verts;
    Edge startedge = faces[face_idx].edge; // the edge where we start
    Vertex start_target = boost::target( startedge, g);
    verts.push_back(start_target);
    Edge current = g[startedge].next;
    do {
        Vertex current_target = boost::target( current, g);
        verts.push_back(current_target);
        current = g[current].next;
    } while ( current != startedge );
    return verts;
}

/// return edges of face f
EdgeVector face_edges( Face f ) {
    //Edge start_edge = g[ f ].edge; // was cast: (Face)f
    Edge start_edge = faces[f].edge;
    Edge current_edge = start_edge;
    EdgeVector out;
    do {
        out.push_back(current_edge);
        current_edge = g[current_edge].next;
    } while( current_edge != start_edge );
    return out;
}

/// return degree of given vertex
unsigned int degree( Vertex v)  {
    return boost::degree( v, g);
}

/// return number of vertices in graph
unsigned int num_vertices() const {
    return boost::num_vertices( g );
}

/// return out_edges of given vertex
EdgeVector out_edges( Vertex v) {
    typedef typename boost::graph_traits< BGLGraph >::out_edge_iterator  HEOutEdgeItr;
    EdgeVector ev;
    HEOutEdgeItr it, it_end;
    boost::tie( it, it_end ) = boost::out_edges( v, g );
    for ( ; it != it_end ; ++it ) {
        ev.push_back(*it);
    }
    return ev;
}

/// return all edges
EdgeVector  edges() {
    typedef typename boost::graph_traits< BGLGraph >::edge_iterator      HEEdgeItr;
    EdgeVector ev;
    HEEdgeItr it, it_end;
    boost::tie( it, it_end ) = boost::edges( g );
    for ( ; it != it_end ; ++it ) {
        ev.push_back(*it);
    }
    return ev;
}

/// return v1-v2 edge descriptor
Edge edge( Vertex v1, Vertex v2 ) {
    typedef typename std::pair<Edge, bool> EdgeBool;
    EdgeBool result = boost::edge(v1, v2, g );
    return result.first;
}

/// return the previous edge. traverses all edges in face until previous found.
Edge previous_edge( Edge e ) {
    Edge previous = g[e].next;
    while ( g[previous].next != e ) {
        previous = g[previous].next;
    }
    return previous;
}

/// return true if v1-v2 edge exists
bool has_edge( Vertex v1, Vertex v2) {
    typedef typename std::pair<Edge, bool> EdgeBool;
    EdgeBool result = boost::edge(v1, v2, g );
    return result.second;
}

/// return adjacent faces to the given vertex
FaceVector adjacent_faces( Vertex q ) {
    typedef typename boost::graph_traits< BGLGraph >::out_edge_iterator  HEOutEdgeItr;
    std::set<unsigned int> face_set;
    HEOutEdgeItr itr, itr_end;
    boost::tie( itr, itr_end) = boost::out_edges(q, g);
    for ( ; itr!=itr_end ; ++itr ) {
        face_set.insert( g[*itr].face );
    }
    FaceVector fv;
    BOOST_FOREACH(unsigned int m, face_set) {
        fv.push_back(m);
    }
    return fv;
}

/// return number of faces in graph
unsigned int num_faces() const {
    return faces.size();
}

/// return number of edges in graph
unsigned int num_edges() const {
    return boost::num_edges( g );
}

/// inserts given vertex into edge e, and into the twin edge e_twin
void insert_vertex_in_edge(Vertex v, Edge e ) {
    // the vertex v is in the middle of edge e
    //                    face
    //                    e1   e2
    // previous-> source  -> v -> target -> next
    //            tw_trg  <- v <- tw_src <- tw_previous
    //                    te2  te1
    //                    twin_face

    Edge twin = g[e].twin;
    Vertex source = boost::source( e , g );
    Vertex target = boost::target( e , g);
    Vertex twin_source = boost::source( twin , g);
    Vertex twin_target = boost::target( twin , g );
    assert( source == twin_target );
    assert( target == twin_source );

    Face face = g[e].face;
    Face twin_face = g[twin].face;
    Edge previous = previous_edge(e);
    assert( g[previous].face == g[e].face );
    Edge twin_previous = previous_edge(twin);
    assert( g[twin_previous].face == g[twin].face );

    Edge e1 = add_edge( source, v );
    Edge e2 = add_edge( v, target );

    // preserve the left/right face link
    g[e1].face = face;
    g[e2].face = face;
    // next-pointers
    g[previous].next = e1;
    g[e1].next = e2;
    g[e2].next = g[e].next;

    Edge te1 = add_edge( twin_source, v  );
    Edge te2 = add_edge( v, twin_target  );

    g[te1].face = twin_face;
    g[te2].face = twin_face;

    g[twin_previous].next = te1;
    g[te1].next = te2;
    g[te2].next = g[twin].next;

    // TWINNING (note indices 'cross', see ASCII art above)
    g[e1].twin = te2;
    g[te2].twin = e1;
    g[e2].twin = te1;
    g[te1].twin = e2;

    // update the faces (required here?)
    faces[face].edge = e1;
    faces[twin_face].edge = te1;

    // finally, remove the old edge
    boost::remove_edge( e   , g);
    boost::remove_edge( twin, g);
}

/// inserts given vertex into edge e
/*
template <class BGLGraph>
void insert_vertex_in_half_edge(typename boost::graph_traits< BGLGraph >::vertex_descriptor  v,
                           typename boost::graph_traits< BGLGraph >::edge_descriptor e,
                           BGLGraph& g) {
    typedef typename boost::graph_traits< BGLGraph >::edge_descriptor    HEEdge;
    typedef typename boost::graph_traits< BGLGraph >::vertex_descriptor  HEVertex;
    // the vertex v is in the middle of edge e
    //                    face
    //                    e1   e2
    // previous-> source  -> v -> target -> next

    HEVertex source = boost::source( e , g );
    HEVertex target = boost::target( e , g);
    unsigned int face = g[e].face;
    HEEdge previous = previous_edge(e, g);
    assert( g[previous].face == g[e].face );
    HEEdge e1 = add_edge( source, v , g);
    HEEdge e2 = add_edge( v, target , g);
    // preserve the left/right face link
    g[e1].face = face;
    g[e2].face = face;
    // next-pointers
    g[previous].next = e1;
    g[e1].next = e2;
    g[e2].next = g[e].next;
    // update the faces (required here?)
    g.faces[face].edge = e1;
    // finally, remove the old edge
    boost::remove_edge( e   , g);
    // NOTE: twinning is not done here, since the twin edge is not split...
}

*/
        /// check that all edges belong to the correct face, TODO: template this to make it a useful check
        /*
        bool checkFaces() {
            BOOST_FOREACH(FaceProps f, faces) {
                BOOST_FOREACH( HEEdge e, face_edges(f.idx)) {
                    if ( g[e].face != f.idx )
                        return false;
                }
            }
            return true;
        }*/

/// delete a vertex
void delete_vertex(Vertex v) {
    clear_vertex(v);
    remove_vertex(v);
}

/// clear given vertex. this removes all edges connecting to the vertex.
void clear_vertex( Vertex v) {
    boost::clear_vertex( v, g );
}
/// remove given vertex
void remove_vertex( Vertex v) {
    boost::remove_vertex( v , g );
}

void remove_edge( Vertex v1, Vertex v2) {
    boost::remove_edge( v1, v2  , g);
}

void remove_edge( Edge e) {
    boost::remove_edge( e   , g);
}

}; // end class definition

} // end hedi namespace

} // end ocl namespace
#endif
// end halfedgediagram.hpp


/* Copyright Singapore-MIT Alliance for Research and Technology */
#pragma once

#include <boost/shared_ptr.hpp>
#include <map>
#include <vector>
#include <set>
#include <string>
#include "geospatial/network/Link.hpp"
#include "geospatial/network/Node.hpp"
#include "geospatial/network/WayPoint.hpp"

namespace sim_mob
{

/**
 * Class encapsulating K-shortest path algorithm as documented by Dr. Huang He
 *
 * \author Zhang Huai Peng
 * \author Vahid Saber Hamishagi
 * \author Harish Loganathan
 */
class K_ShortestPathImpl
{
public:
    virtual ~K_ShortestPathImpl();

    static boost::shared_ptr<K_ShortestPathImpl> getInstance();

    /**
     * primary operation provided by this class: Find K-Shortest Paths
     * @param from Origin
     * @param to Destination
     * @param res generated paths (output param)
     * @return number of paths found
     *
     * Note: naming conventions follows the Huang HE's documented algorithm.
     */
    int getKShortestPaths(const sim_mob::Node *from, const sim_mob::Node *to, std::vector<std::vector<sim_mob::WayPoint> > &res);

    void setK(int value)
    {
        k = value;
    }

    int getK()
    {
        return k;
    }

private:
    K_ShortestPathImpl();

    /**
     * Obtain the end segments of a given node, as per requirement of Yen's shortest path (mentioned in He's Algorithm)
     * @param spurNode input node
     * @param upLinks output
     */
    std::set<const Link*> getUpstreamLinks(const Node *spurNode) const;

    /**
     * Validates the intermediary results
     * @param RootPath root path of the k-shortest path
     * @param spurPath spur path of the k-shortest path
     * @return true if all the validations are valid, false otherwise
     */
    bool validatePath(const std::vector<sim_mob::WayPoint> &rootPath, const std::vector<sim_mob::WayPoint> &spurPath);

    /**
     * number of shortest paths to generate when getKShortestPaths() function is called
     */
    int k;

    /**
     * store all segments in A, key=id, value=road segment
     */
    std::map<std::string, const sim_mob::RoadSegment*> A_Segments;

    /**
     * store all upstream links for each node
     */
    std::map<const Node *, std::set<const Link *> > upstreamLinksLookup;

    /**
     * static singleton instance
     */
    static boost::shared_ptr<K_ShortestPathImpl> instance;
};

} //end namespace sim_mob