/*
gruneisen.cpp

Copyright (c) 2014, 2015, 2016 Terumasa Tadano

This file is distributed under the terms of the MIT license.
Please see the file 'LICENCE.txt' in the root directory
or http://opensource.org/licenses/mit-license.php for information.
*/

#include "mpi_common.h"
#include "gruneisen.h"
#include "constants.h"
#include "dynamical.h"
#include "error.h"
#include "fcs_phonon.h"
#include "kpoint.h"
#include "mathfunctions.h"
#include "memory.h"
#include "parsephon.h"
#include "pointers.h"
#include "system.h"
#include "anharmonic_core.h"
#include "version.h"
#include <iostream>
#include <iomanip>
#include <boost/lexical_cast.hpp>
#include <boost/property_tree/xml_parser.hpp>
#include <boost/property_tree/ptree.hpp>
#include <boost/version.hpp>
#include <cmath>

using namespace PHON_NS;

Gruneisen::Gruneisen(PHON *phon): Pointers(phon)
{
    set_default_variables();
};

Gruneisen::~Gruneisen()
{
    deallocate_variables();
};

void Gruneisen::set_default_variables()
{
    delta_a = 0.01;
    print_gruneisen = false;
    print_newfcs = false;
    gruneisen = nullptr;
    xshift_s = nullptr;
}

void Gruneisen::deallocate_variables()
{
    if (gruneisen) {
        memory->deallocate(gruneisen);
    }
    if (xshift_s) {
        memory->deallocate(xshift_s);
    }
    delta_fc2.clear();
    delta_fc3.clear();
}

void Gruneisen::setup()
{
    MPI_Bcast(&delta_a, 1, MPI_DOUBLE, 0, MPI_COMM_WORLD);
    MPI_Bcast(&print_newfcs, 1, MPI_LOGICAL, 0, MPI_COMM_WORLD);

    memory->allocate(xshift_s, 27, 3);

    for (int i = 0; i < 3; ++i) xshift_s[0][i] = 0.0;

    int icell = 0;

    for (int ix = -1; ix <= 1; ++ix) {
        for (int iy = -1; iy <= 1; ++iy) {
            for (int iz = -1; iz <= 1; ++iz) {
                if (ix == 0 && iy == 0 && iz == 0) continue;

                ++icell;

                xshift_s[icell][0] = static_cast<double>(ix);
                xshift_s[icell][1] = static_cast<double>(iy);
                xshift_s[icell][2] = static_cast<double>(iz);
            }
        }
    }

    if (print_gruneisen || print_newfcs) {
        prepare_delta_fcs(fcs_phonon->force_constant_with_cell[1], delta_fc2);
    }

    if (print_newfcs && anharmonic_core->quartic_mode > 0) {
        prepare_delta_fcs(fcs_phonon->force_constant_with_cell[2], delta_fc3);
    }
    if (print_gruneisen) {
        memory->allocate(gruneisen, kpoint->nk, dynamical->neval);
    }

    if (mympi->my_rank == 0) {
        if (print_newfcs) {
            std::cout << std::endl;
            if (anharmonic_core->quartic_mode > 0) {
                std::cout << " NEWFCS = 1 : Harmonic and cubic force constants of " << std::endl;
            } else {
                std::cout << " NEWFCS = 1 : Harmonic force constants of " << std::endl;
            }
            std::cout << "              expanded/compressed systems will be estimated" << std::endl;
            std::cout << "              with DELTA_A = " << std::setw(5) << delta_a << std::endl;
        }
    }
    //   print_stress_energy();
}

void Gruneisen::calc_gruneisen()
{
    auto ns = dynamical->neval;
    auto nk = kpoint->nk;
    std::complex<double> **dfc2_reciprocal;

    memory->allocate(dfc2_reciprocal, ns, ns);

    if (mympi->my_rank == 0) {
        std::cout << std::endl;
        std::cout << " GRUNEISEN = 1 : Calculating Gruneisen parameters ... ";
    }

    for (auto ik = 0; ik < nk; ++ik) {

        calc_dfc2_reciprocal(dfc2_reciprocal, kpoint->xk[ik]);

        for (auto is = 0; is < ns; ++is) {

            gruneisen[ik][is] = std::complex<double>(0.0, 0.0);

            for (unsigned int i = 0; i < ns; ++i) {
                for (unsigned int j = 0; j < ns; ++j) {
                    gruneisen[ik][is] += std::conj(dynamical->evec_phonon[ik][is][i])
                        * dfc2_reciprocal[i][j]
                        * dynamical->evec_phonon[ik][is][j];
                }
            }

            double gamma_imag = gruneisen[ik][is].imag();
            if (std::abs(gamma_imag) > eps10) {
                error->warn("calc_gruneisen", "Gruneisen parameter is not real");
            }

            if (std::abs(dynamical->eval_phonon[ik][is]) < eps8) {
                gruneisen[ik][is] = 0.0;
            } else {
                gruneisen[ik][is] /= - 6.0 * std::pow(dynamical->eval_phonon[ik][is], 2);
            }
        }
    }
    memory->deallocate(dfc2_reciprocal);

    if (mympi->my_rank == 0) {
        std::cout << "done!" << std::endl;
    }
}

void Gruneisen::calc_dfc2_reciprocal(std::complex<double> **dphi2,
                                     const double *xk_in)
{
    unsigned int i;
    unsigned int ns = dynamical->neval;

    double vec[3];

    std::complex<double> im(0.0, 1.0);

    for (i = 0; i < ns; ++i) {
        for (unsigned int j = 0; j < ns; ++j) {
            dphi2[i][j] = std::complex<double>(0.0, 0.0);
        }
    }

    for (const auto &it : delta_fc2) {

        unsigned int atm1 = it.pairs[0].index / 3;
        unsigned int xyz1 = it.pairs[0].index % 3;
        unsigned int atm2 = it.pairs[1].index / 3;
        unsigned int xyz2 = it.pairs[1].index % 3;

        unsigned int tran = it.pairs[1].tran;
        unsigned int cell_s = it.pairs[1].cell_s;

        unsigned int atm1_s = system->map_p2s_anharm[atm1][0];
        unsigned int atm2_s = system->map_p2s_anharm[atm2][tran];

        for (i = 0; i < 3; ++i) {
            vec[i] = system->xr_s_anharm[atm2_s][i] + xshift_s[cell_s][i]
                - system->xr_s_anharm[system->map_p2s_anharm[atm2][0]][i];
        }

        rotvec(vec, vec, system->lavec_s_anharm);
        rotvec(vec, vec, system->rlavec_p);

        double phase = vec[0] * xk_in[0] + vec[1] * xk_in[1] + vec[2] * xk_in[2];

        dphi2[3 * atm1 + xyz1][3 * atm2 + xyz2]
            += it.fcs_val * std::exp(im * phase)
            / std::sqrt(system->mass_anharm[atm1_s] * system->mass_anharm[atm2_s]);

    }
}

void Gruneisen::prepare_delta_fcs(const std::vector<FcsArrayWithCell> &fcs_in,
                                  std::vector<FcsArrayWithCell> &delta_fcs) const
{
    unsigned int i;
    double vec[3];
    double fcs_tmp = 0.0;

    std::vector<FcsAlignedForGruneisen> fcs_aligned;
    std::vector<AtomCellSuper> pairs_vec;
    std::vector<int> index_old, index_now;
    std::vector<int> index_with_cell;
    std::set<std::vector<int>> set_index_uniq;
    AtomCellSuper pairs_tmp;

    unsigned int norder = fcs_in[0].pairs.size();
    unsigned int nmulti;

    delta_fcs.clear();
    fcs_aligned.clear();

    for (auto it = fcs_in.cbegin(); it != fcs_in.cend(); ++it) {
        fcs_aligned.emplace_back((*it).fcs_val, (*it).pairs);
    }
    std::sort(fcs_aligned.begin(), fcs_aligned.end());

    index_old.clear();
    unsigned int nelems = 2 * (norder - 2) + 1;
    for (i = 0; i < nelems; ++i) index_old.push_back(-1);

    index_with_cell.clear();
    set_index_uniq.clear();

    for (auto it = fcs_aligned.cbegin(); it != fcs_aligned.cend(); ++it) {

        index_now.clear();
        index_with_cell.clear();

        index_now.push_back((*it).pairs[0].index);
        index_with_cell.push_back((*it).pairs[0].index);

        for (i = 1; i < norder - 1; ++i) {
            index_now.push_back((*it).pairs[i].index);
            index_now.push_back((*it).pairs[i].tran);

            index_with_cell.push_back((*it).pairs[i].index);
            index_with_cell.push_back((*it).pairs[i].tran);
            index_with_cell.push_back((*it).pairs[i].cell_s);
        }

        if (index_now != index_old) {

            if (index_old[0] != -1) {

                nmulti = set_index_uniq.size();
                fcs_tmp /= static_cast<double>(nmulti);

                if (std::abs(fcs_tmp) > eps15) {
                    for (const auto &it2 : set_index_uniq) {

                        pairs_vec.clear();

                        pairs_tmp.index = it2[0];
                        pairs_tmp.tran = 0;
                        pairs_tmp.cell_s = 0;
                        pairs_vec.push_back(pairs_tmp);
                        for (i = 1; i < norder - 1; ++i) {
                            pairs_tmp.index = it2[3 * i - 2];
                            pairs_tmp.tran = it2[3 * i - 1];
                            pairs_tmp.cell_s = it2[3 * i];
                            pairs_vec.push_back(pairs_tmp);
                        }
                        delta_fcs.emplace_back(fcs_tmp, pairs_vec);
                    }
                }
                set_index_uniq.clear();
            }

            fcs_tmp = 0.0;
            index_old.clear();
            index_old.reserve(index_now.size());
            std::copy(index_now.begin(), index_now.end(), std::back_inserter(index_old));
        }

        set_index_uniq.insert(index_with_cell);

        for (i = 0; i < 3; ++i) {
            vec[i] = system->xr_s_anharm[system->map_p2s_anharm[(*it).pairs[norder - 1].index / 3][(*it).pairs[norder -
                    1].tran]]
                [i]
                - system->xr_s_anharm[system->map_p2s_anharm[(*it).pairs[0].index / 3][0]][i]
                + xshift_s[(*it).pairs[norder - 1].cell_s][i];
        }

        rotvec(vec, vec, system->lavec_s_anharm);

        fcs_tmp += (*it).fcs_val * vec[(*it).pairs[norder - 1].index % 3];
    }

    nmulti = set_index_uniq.size();
    fcs_tmp /= static_cast<double>(nmulti);

    if (std::abs(fcs_tmp) > eps15) {
        for (const auto &it2 : set_index_uniq) {

            pairs_vec.clear();

            pairs_tmp.index = it2[0];
            pairs_tmp.tran = 0;
            pairs_tmp.cell_s = 0;
            pairs_vec.push_back(pairs_tmp);
            for (i = 1; i < norder - 1; ++i) {
                pairs_tmp.index = it2[3 * i - 2];
                pairs_tmp.tran = it2[3 * i - 1];
                pairs_tmp.cell_s = it2[3 * i];
                pairs_vec.push_back(pairs_tmp);
            }
            delta_fcs.emplace_back(fcs_tmp, pairs_vec);
        }
    }

    fcs_aligned.clear();
    set_index_uniq.clear();
}

void Gruneisen::write_new_fcsxml_all()
{
    std::cout << std::endl;

    if (fcs_phonon->update_fc2) {
        error->warn("write_new_fcsxml_all",
                    "NEWFCS = 1 cannot be combined with the FC2XML.");
    } else {
        std::cout << " NEWFCS = 1 : Following XML files are created. " << std::endl;

        std::string file_xml = input->job_title + "_+.xml";
        write_new_fcsxml(file_xml, delta_a);

        std::cout << "  " << std::setw(input->job_title.length() + 12) << std::left << file_xml;
        std::cout << " : Force constants of the system expanded by "
            << std::fixed << std::setprecision(3) << delta_a * 100 << " %" << std::endl;

        file_xml = input->job_title + "_-.xml";
        write_new_fcsxml(file_xml, -delta_a);

        std::cout << "  " << std::setw(input->job_title.length() + 12) << std::left << file_xml;
        std::cout << " : Force constants of the system compressed by "
            << std::fixed << std::setprecision(3) << delta_a * 100 << " %" << std::endl;
    }
}

void Gruneisen::write_new_fcsxml(const std::string filename_xml,
                                 const double change_ratio_of_a)
{
    int i, j;
    double lattice_vector[3][3];

    for (i = 0; i < 3; ++i) {
        for (j = 0; j < 3; ++j) {
            lattice_vector[i][j] = (1.0 + change_ratio_of_a) * system->lavec_s[i][j];
        }
    }

    using boost::property_tree::ptree;

    ptree pt;
    std::string str_pos[3];

    pt.put("Data.ANPHON_version", ALAMODE_VERSION);
    pt.put("Data.Description.OriginalXML", fcs_phonon->file_fcs);
    pt.put("Data.Description.Delta_A", double2string(change_ratio_of_a));

    pt.put("Data.Structure.NumberOfAtoms", system->nat);
    pt.put("Data.Structure.NumberOfElements", system->nkd);

    for (i = 0; i < system->nkd; ++i) {
        ptree &child = pt.add("Data.Structure.AtomicElements.element",
                              system->symbol_kd[i]);
        child.put("<xmlattr>.number", i + 1);
    }

    for (i = 0; i < 3; ++i) {
        str_pos[i].clear();
        for (j = 0; j < 3; ++j) {
            str_pos[i] += " " + double2string(lattice_vector[j][i]);
        }
    }
    pt.put("Data.Structure.LatticeVector", "");
    pt.put("Data.Structure.LatticeVector.a1", str_pos[0]);
    pt.put("Data.Structure.LatticeVector.a2", str_pos[1]);
    pt.put("Data.Structure.LatticeVector.a3", str_pos[2]);

    pt.put("Data.Structure.Position", "");
    std::string str_tmp;

    for (i = 0; i < system->nat; ++i) {
        str_tmp.clear();
        for (j = 0; j < 3; ++j) str_tmp += " " + double2string(system->xr_s[i][j]);
        ptree &child = pt.add("Data.Structure.Position.pos", str_tmp);
        child.put("<xmlattr>.index", i + 1);
        child.put("<xmlattr>.element", system->symbol_kd[system->kd[i]]);
    }

    pt.put("Data.Symmetry.NumberOfTranslations", system->ntran);
    for (i = 0; i < system->ntran; ++i) {
        for (j = 0; j < system->natmin; ++j) {
            ptree &child = pt.add("Data.Symmetry.Translations.map",
                                  system->map_p2s[j][i] + 1);
            child.put("<xmlattr>.tran", i + 1);
            child.put("<xmlattr>.atom", j + 1);
        }
    }

    pt.put("Data.ForceConstants", "");
    str_tmp.clear();

    for (const auto &it : fcs_phonon->force_constant_with_cell[0]) {

        ptree &child = pt.add("Data.ForceConstants.HARMONIC.FC2", double2string(it.fcs_val));

        child.put("<xmlattr>.pair1",
                  std::to_string(it.pairs[0].index / 3 + 1)
                  + " " + std::to_string(it.pairs[0].index % 3 + 1));
        child.put("<xmlattr>.pair2",
                  std::to_string(system->map_p2s[it.pairs[1].index / 3][it.pairs[1].tran] + 1)
                  + " " + std::to_string(it.pairs[1].index % 3 + 1)
                  + " " + std::to_string(it.pairs[1].cell_s + 1));
    }

    for (const auto &it : delta_fc2) {

        if (std::abs(it.fcs_val) < eps12) continue;

        ptree &child = pt.add("Data.ForceConstants.HARMONIC.FC2",
                              double2string(change_ratio_of_a * it.fcs_val));

        child.put("<xmlattr>.pair1",
                  std::to_string(it.pairs[0].index / 3 + 1)
                  + " " + std::to_string(it.pairs[0].index % 3 + 1));
        child.put("<xmlattr>.pair2",
                  std::to_string(system->map_p2s[it.pairs[1].index / 3][it.pairs[1].tran] + 1)
                  + " " + std::to_string(it.pairs[1].index % 3 + 1)
                  + " " + std::to_string(it.pairs[1].cell_s + 1));
    }

    if (anharmonic_core->quartic_mode) {
        for (const auto &it : fcs_phonon->force_constant_with_cell[1]) {

            if (it.pairs[1].index > it.pairs[2].index) continue;

            ptree &child = pt.add("Data.ForceConstants.ANHARM3.FC3",
                                  double2string(it.fcs_val));

            child.put("<xmlattr>.pair1",
                      std::to_string(it.pairs[0].index / 3 + 1)
                      + " " + std::to_string(it.pairs[0].index % 3 + 1));
            child.put("<xmlattr>.pair2",
                      std::to_string(system->map_p2s[it.pairs[1].index / 3][it.pairs[1].tran] + 1)
                      + " " + std::to_string(it.pairs[1].index % 3 + 1)
                      + " " + std::to_string(it.pairs[1].cell_s + 1));
            child.put("<xmlattr>.pair3",
                      std::to_string(system->map_p2s[it.pairs[2].index / 3][it.pairs[2].tran] + 1)
                      + " " + std::to_string(it.pairs[2].index % 3 + 1)
                      + " " + std::to_string(it.pairs[2].cell_s + 1));
        }

        for (const auto &it : delta_fc3) {

            if (std::abs(it.fcs_val) < eps12) continue;

            if (it.pairs[1].index > it.pairs[2].index) continue;

            ptree &child = pt.add("Data.ForceConstants.ANHARM3.FC3",
                                  double2string(change_ratio_of_a * it.fcs_val));

            child.put("<xmlattr>.pair1",
                      std::to_string(it.pairs[0].index / 3 + 1)
                      + " " + std::to_string(it.pairs[0].index % 3 + 1));
            child.put("<xmlattr>.pair2",
                      std::to_string(system->map_p2s[it.pairs[1].index / 3][it.pairs[1].tran] + 1)
                      + " " + std::to_string(it.pairs[1].index % 3 + 1)
                      + " " + std::to_string(it.pairs[1].cell_s + 1));
            child.put("<xmlattr>.pair3",
                      std::to_string(system->map_p2s[it.pairs[2].index / 3][it.pairs[2].tran] + 1)
                      + " " + std::to_string(it.pairs[2].index % 3 + 1)
                      + " " + std::to_string(it.pairs[2].cell_s + 1));
        }
    }

    using namespace boost::property_tree::xml_parser;
    const int indent = 2;

#if BOOST_VERSION >= 105600
    write_xml(filename_xml, pt, std::locale(),
              xml_writer_make_settings<ptree::key_type>(' ', indent, widen<std::string>("utf-8")));
#else
    write_xml(filename_xml, pt, std::locale(),
        xml_writer_make_settings(' ', indent, widen<char>("utf-8")));
#endif
}

std::string Gruneisen::double2string(const double d) const
{
    std::string rt;
    std::stringstream ss;

    ss << std::scientific << std::setprecision(15) << d;
    ss >> rt;
    return rt;
}

// double Gruneisen::calc_stress_energy2(const std::vector<FcsArrayWithCell> fcs_in)
// {
//     unsigned int i, j;
//     double ret = 0.0;
//     double **vec, **pos;
//     double tmp, tmp2;
//     double xshift[3];
//     unsigned int itran;
//     unsigned int norder = fcs_in[0].pairs.size();
//
//     memory->allocate(vec, norder, 3);
//     memory->allocate(pos, norder, 3);
//
//     for (std::vector<FcsArrayWithCell>::const_iterator it = fcs_in.begin(); it != fcs_in.end(); ++it) {
//
//         for (i = 0; i < norder; ++i) {
//             for (j = 0; j < 3; ++j) {
//                 vec[i][j] = system->xr_s[system->map_p2s[(*it).pairs[i].index / 3][(*it).pairs[i].tran]][j]
//                 + xshift_s[(*it).pairs[i].cell_s][j];
//
//                 pos[i][j] = system->xr_s[system->map_p2s[(*it).pairs[i].index / 3][0]][j];
//             //    vec[i][j] = system->xr_s[system->map_p2s[0][(*it).pairs[i].tran]][j] + xshift_s[(*it).pairs[i].cell_s][j];
//             }
//             rotvec(vec[i], vec[i], system->lavec_s);
//             rotvec(pos[i], pos[i], system->lavec_s);
//         }
//
//
//         ret += (*it).fcs_val
//             * (vec[1][(*it).pairs[0].index % 3] - pos[0][(*it).pairs[0].index % 3])
//             * (vec[1][(*it).pairs[1].index % 3] - pos[0][(*it).pairs[1].index % 3]);
//     }
//
//     memory->deallocate(vec);
//     memory->deallocate(pos);
//     return ret;
// }
//
// void Gruneisen::calc_stress_energy3(const std::vector<FcsArrayWithCell> fcs_in, double ****ret)
// {
//     unsigned int i, j, k, l;
//     double **vec, **pos;
//     double tmp, tmp2;
//     double xshift[3];
//     unsigned int itran;
//     unsigned int norder = fcs_in[0].pairs.size();
//     unsigned int crd[4];
//
//     memory->allocate(vec, norder, 3);
//     memory->allocate(pos, norder, 3);
//
//     for (i = 0; i < 3; ++i) {
//         for (j = 0; j < 3; ++j) {
//             for (k = 0; k < 3; ++k) {
//                 for (l = 0; l < 3; ++l) {
//                     ret[i][j][k][l] = 0.0;
//                 }
//             }
//         }
//     }
//
//     for (std::vector<FcsArrayWithCell>::const_iterator it = fcs_in.begin(); it != fcs_in.end(); ++it) {
//
//         for (i = 0; i < norder; ++i) {
//             for (j = 0; j < 3; ++j) {
//                 vec[i][j] = system->xr_s[system->map_p2s[(*it).pairs[i].index / 3][(*it).pairs[i].tran]][j]
//                 + xshift_s[(*it).pairs[i].cell_s][j];
//
//                 pos[i][j] = system->xr_s[system->map_p2s[(*it).pairs[i].index / 3][0]][j];
//             }
//             rotvec(vec[i], vec[i], system->lavec_s);
//             rotvec(pos[i], pos[i], system->lavec_s);
//         }
//
//         crd[0] = (*it).pairs[0].index % 3;
//         crd[1] = (*it).pairs[1].index % 3;
//
//         for (k = 0; k < 3; ++k) {
//
//             crd[2] = k;
//
//             for (l = 0; l < 3; ++l) {
//
//                 crd[3] = l;
//
//                 ret[crd[0]][crd[1]][k][l] += (*it).fcs_val * (vec[1][k] - pos[0][k]) * (vec[1][l] - pos[0][l]);
//             }
//         }
//     }
//
//     memory->deallocate(vec);
//     memory->deallocate(pos);
//
//     for (i = 0; i < 3; ++i) {
//         for (j = 0; j < 3; ++j) {
//             for (k = 0; k < 3; ++k) {
//                 for (l = 0; l < 3; ++l) {
//                     ret[i][j][k][l] *= -0.5;
//                 }
//             }
//         }
//     }
// }
//
//
// void Gruneisen::print_stress_energy()
// {
//
//     double volume = system->volume_p * std::pow(Bohr_in_Angstrom, 3) * 1.0e-30;
//
//
//     double ****A, ****C;
//
//     memory->allocate(A, 3, 3, 3, 3);
//     memory->allocate(C, 3, 3, 3, 3);
//
//     calc_stress_energy3(fcs_phonon->force_constant_with_cell[0], A);
//
//     unsigned int i, j, k, l;
//
//     std::cout << "# A [Ryd]" << std::endl;
//
//     for (i = 0; i < 3; ++i) {
//         for (j = 0; j < 3; ++j) {
//             for (k = 0; k < 3; ++k) {
//                 for (l = 0; l < 3; ++l) {
//                     std::cout << std::setw(3) << i + 1;
//                     std::cout << std::setw(3) << j + 1;
//                     std::cout << std::setw(3) << k + 1;
//                     std::cout << std::setw(3) << l + 1;
//                     std::cout << std::setw(15) << std::fixed << A[i][j][k][l];
//                     std::cout << std::endl;
//                 }
//             }
//         }
//     }
//
//     std::cout << std::endl;
//     std::cout << "# C [GPa]" << std::endl;
//
//     for (i = 0; i < 3; ++i) {
//         for (j = 0; j < 3; ++j) {
//             for (k = 0; k < 3; ++k) {
//                 for (l = 0; l < 3; ++l) {
//                     C[i][j][k][l] = A[i][k][j][l] + A[j][k][i][l] - A[i][j][k][l];
//                     C[i][j][k][l] *= 1.0e-9 * Ryd / volume;
//                     std::cout << std::setw(3) << i + 1;
//                     std::cout << std::setw(3) << j + 1;
//                     std::cout << std::setw(3) << k + 1;
//                     std::cout << std::setw(3) << l + 1;
//                     std::cout << std::setw(15) << std::fixed << C[i][j][k][l];
//                     std::cout << std::endl;
//
//                 }
//             }
//         }
//     }
//
//     std::cout << "Bulk Modulus [GPa] = " << (C[0][0][0][0] + 2.0 * C[0][0][1][1]) / 3.0 << std::endl;
//
//     memory->deallocate(A);
//     memory->deallocate(C);
// }


#include <iostream>
#include <algorithm>
#include <tuple>
#include <complex>
#include <array>
#include <vector>
#include <memory>
#include <numeric>
#include <string>
#include <boost/format.hpp>

using namespace std;
using myfloat_t = double;
using c_t = complex<myfloat_t>;

template<typename T>
struct Rect {
  Rect(const T& xmin = T(), const T& ymin = T(), const T& xmax = T(), const T& ymax = T())
  : xmin_{xmin}, ymin_{ymin}, xmax_{xmax}, ymax_{ymax}
  {
    normalize();
  }
  Rect(const Rect&) = default;
  Rect& operator=(const Rect&) = default;
  friend bool operator==(const Rect<T>& a, const Rect<T>& b) {
    return std::tie(a.xmin_, a.ymin_, a.xmax_, a.ymax_) == std::tie(b.xmin_, b.ymin_, b.xmax_, b.ymax_);
  }
  friend bool operator<(const Rect<T>& a, const Rect<T>& b) {
    return std::tie(a.xmin_, a.ymin_, a.xmax_, a.ymax_) < std::tie(b.xmin_, b.ymin_, b.xmax_, b.ymax_);
  }
  friend bool operator<=(const Rect<T>& a, const Rect<T>& b) {
    return (a < b) || (a == b);
  }
  friend bool operator>(const Rect<T>& a, const Rect<T>& b) {
    return (b < a);
  }
  friend bool operator>=(const Rect<T>& a, const Rect<T>& b) {
    return (b < a) || (a == b);
  }
  friend bool operator!=(const Rect<T>& a, const Rect<T>& b) {
    return !(a == b);
  }
  friend std::ostream& operator<<(std::ostream& o, const Rect<T>& r) {
    std::string fmts = "[%6.5f, %6.5f, %6.5f, %6.5f]";
    if (std::is_integral<T>::value) {
      fmts = "[%d, %d, %d, %d]";
    }
    boost::format fmt{fmts};
    o << fmt % r.xmin_ % r.ymin_ % r.xmax_ % r.ymax_;
    return o;
  }
  void normalize() {
    if (xmax_ < xmin_)
      swap(xmax_, xmin_);
    if (ymax_ < ymin_)
      swap(ymax_, ymin_);
  }
  T width() const {
    if (std::is_integral<T>::value)
      return xmax_ - xmin_ + 1;
    else
      return xmax_ - xmin_;
  }
  T height() const {
    if (std::is_integral<T>::value)
      return ymax_ - ymin_ + 1;
    else
      return ymax_ - ymin_;
  }
  void zoom(myfloat_t f) {
    auto w = width();
    auto h = height();
    auto centerx = xmin_ + w/2.0;
    auto centery = ymin_ + h/2.0;
    auto nw = w * f;
    auto nh = h * f;
    xmin_ = centerx - nw/2.0;
    ymin_ = centery - nh/2.0;
    xmax_ = centerx + nw/2.0;
    ymax_ = centery + nh/2.0;
  }
  void move(const T& x, const T& y) {
    xmin_ += x;
    ymin_ += y;
    xmax_ += x;
    ymax_ += y;
  }
  T xmin_, ymin_, xmax_, ymax_;
};

using FRect = Rect<myfloat_t>;
using URect = Rect<unsigned>;

struct RenderParams {
  unsigned  maxit_  { 2000 };
  myfloat_t test_   { 4.0  };
  unsigned  width_  { 61   };
  unsigned  height_ { 23   };
  myfloat_t gamma_  { 0.6  };
  std::vector<char> alpha_ // { ' ', '.', ':', '-', '+', 'i', 'o', 'x', 'I', 'X', 'N', 'M', 'W', '%'};
  { ' ', '.', '\'', '`', '^', '"', ',', ':', ';', 'I', 'l', '!', 'i', '>', '<', '~', '+', '_', '-', '?'
  , ']', '[', '}', '{', '1', ')', '(', '|', '\\', '/', 't', 'f', 'j', 'r', 'x', 'n', 'u', 'v', 'c', 'z'
  , 'X', 'Y', 'U', 'J', 'C', 'L', 'Q', '0', 'O', 'Z', 'm', 'w', 'q', 'p', 'd', 'b', 'k', 'h', 'a', 'o'
  , '*', '#', 'M', 'W', '&', '8', '%', 'B', '@', '$'};
  friend std::ostream& operator<<(std::ostream& o, const RenderParams& rp) {
    boost::format fmt{"Iterations: %d  Test:%4.2f  Gamma: %4.3f"};
    o << fmt % rp.maxit_ % rp.test_ % rp.gamma_;
    return o;
  }
};

class MImage {
public:
  explicit MImage(unsigned width, unsigned height)
  : width_{width}, height_{height}, rpp_{nullptr}
  , px_(width_ * height_, 0)
  {}
  unsigned& operator()(unsigned x, unsigned y) {
    return px_[y * width_ + x];
  }
  const unsigned& operator()(unsigned x, unsigned y) const {
    return px_[y * width_ + x];
  }
  unsigned width() const { return width_; }
  unsigned height() const { return height_; }
  MImage render(const RenderParams& param = RenderParams()) {
    MImage out(width(), height());
    out.rpp_ = std::unique_ptr<RenderParams>(new RenderParams(param));
    myfloat_t maxa = static_cast<myfloat_t>(param.alpha_.size() - 1);
    myfloat_t maxi = static_cast<myfloat_t>(param.maxit_);
    std::transform(cbegin(), cend(), out.begin()
      , [maxa, maxi, param](const decltype(*cbegin())& i) { return static_cast<unsigned>(pow(static_cast<myfloat_t>(i)/maxi, param.gamma_) * maxa); }
      );
    return out;
  }
  myfloat_t average() {
    auto acc = std::accumulate(cbegin(), cend(), 0);
    return static_cast<myfloat_t>(acc)/px_.size();
  }
  myfloat_t standard_derivation() {
    auto avg = average();
    myfloat_t res = 0;
    for(const auto& e : px_) {
      res += std::pow(static_cast<myfloat_t>(e) - avg, 2.0);
    }
    return std::sqrt(res);
  }
  friend std::ostream& operator<<(std::ostream& o, MImage& img) {
    if (img.rpp_) {
      unsigned n = 0;
      for(const auto& e : img) {
        o << img.rpp_->alpha_[e];
        ++n;
        if (n % img.width() == 0)
          o << std::endl;
      }
    } else {
      unsigned n = 0;
      for(const auto& e : img) {
        o << e << ' ';
        ++n;
        if (n % img.width() == 0)
          o << std::endl;
      }
    }
    return o;
  }
private:
  unsigned width_, height_;
  std::vector<unsigned> px_;
  std::unique_ptr<RenderParams> rpp_;
public:
  decltype(px_.begin()) begin() { return px_.begin(); }
  decltype(px_.end())   end()   { return px_.end(); }
  decltype(px_.cbegin()) cbegin() { return px_.cbegin(); }
  decltype(px_.cend())   cend()   { return px_.cend(); }
};

struct Mandelbrot {
  // Mandelbrot(const FRect& r)
  FRect r_ { -2.5, -1.5, 1.0, 1.5};
  MImage compute(const RenderParams& param = RenderParams()) const {
    MImage img(param.width_, param.height_);
    double fcols = r_.width()  / param.width_;
    double frows = r_.height() / param.height_;
    auto iit = img.begin();
    for(unsigned iy = 0; iy < param.height_; ++iy) {
      double y = r_.ymax_ - frows/2.0 - iy * frows;
      double x = r_.xmin_ + fcols/2.0;
      for(unsigned ix = 0; ix < param.width_; ++ix) {
        x += fcols;
        c_t c(x, y), cc(c);
        unsigned i;
        for(i = 0; norm(c) < param.test_ && i < param.maxit_ - 1; ++i) {
          c = c * c + cc;
          //c = pow(c, 2.6) + cc;
        }
        //img(ix, iy) = i;
        *iit = i;
        ++iit;
      }
    }
    return img;
  }

};

int main(int argn, char* argv[]) {
  ios_base::sync_with_stdio(false);
  boost::format fmt{"gamma: %6.3f standard_derivation: %8.4f"};
  Mandelbrot m;
  RenderParams rp;
  bool ok = true;
  std::string s;
  do {
    auto img = m.compute(rp);
    auto img2 = img.render(rp);
    std::cout << img2 << std::endl << m.r_ << rp << std::endl;
    std::cout << "Zoom + -  Move a w s d  Params t T i I g G r R c C Quit q:";
    std::cin  >> s;
    for (auto c : s) {
      switch(c) {
        case '+':
          m.r_.zoom(0.5);
          break;
        case '-':
          m.r_.zoom(2.0);
          break;
        case 'a':
          m.r_.move(- m.r_.width() * 0.25, 0);
          break;
        case 'd':
          m.r_.move(m.r_.width() * 0.25, 0);
          break;
        case 'w':
          m.r_.move(0, m.r_.height() * 0.25);
          break;
        case 's':
          m.r_.move(0, -m.r_.height() * 0.25);
          break;
        case 't':
          rp.test_ *= 2.0;
          break;
        case 'T':
          rp.test_ *= 0.5;
          break;
        case 'i':
          rp.maxit_ *= 2.0;
          break;
        case 'I':
          rp.maxit_ *= 0.5;
          break;
        case 'g':
          if (rp.gamma_ < 2.0)
            rp.gamma_ += 0.1;
          break;
        case 'G':
          if (rp.gamma_ > 0.1)
            rp.gamma_ -= 0.1;
          break;
        case 'c':
          rp.width_ += 4;
          break;
        case 'C':
          rp.width_ -= 4;
          break;
        case 'r':
          rp.height_ += 2;
          break;
        case 'R':
          rp.height_ -= 2;
          break;
        case 'q':
          ok = false;
          break;
      }
    }
    if(!ok)
      break;
  } while(1);
  // for(myfloat_t g = 1.2; g > 0.0; g-=0.1) {
  //   rp.gamma_ = g;
  //   auto img2 = img.render(rp);
  //   std::cout << fmt % g % img2.standard_derivation() << std::endl;
  //   std::cout << img2 << std::endl;
  // }
  return 0;
}


/* CirKit: A circuit toolkit
 * Copyright (C) 2009-2015  University of Bremen
 * Copyright (C) 2015-2017  EPFL
 *
 * Permission is hereby granted, free of charge, to any person
 * obtaining a copy of this software and associated documentation
 * files (the "Software"), to deal in the Software without
 * restriction, including without limitation the rights to use,
 * copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following
 * conditions:
 *
 * The above copyright notice and this permission notice shall be
 * included in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
 * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
 * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
 * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
 * OTHER DEALINGS IN THE SOFTWARE.
 */

/**
 * @file simulate_aig.hpp
 *
 * @brief AIG simuation
 *
 * @author Mathias Soeken
 * @since  2.0
 */

#ifndef SIMULATE_AIG_HPP
#define SIMULATE_AIG_HPP

#include <functional>
#include <map>
#include <unordered_map>

#include <boost/graph/depth_first_search.hpp>
#include <boost/property_map/property_map.hpp>
#include <boost/range/algorithm.hpp>
#include <boost/range/algorithm_ext/push_back.hpp>

#include <core/properties.hpp>
#include <core/utils/timer.hpp>
#include <classical/aig.hpp>
#include <classical/utils/aig_dfs.hpp>
#include <classical/utils/aig_utils.hpp>
#include <classical/utils/truth_table_utils.hpp>

#include <cuddObj.hh>

namespace cirkit
{

/******************************************************************************
 * Abstract class for simulators                                              *
 ******************************************************************************/

template<typename T>
class aig_simulator
{
public:
  /**
   * @brief Simulator routine when input is encountered
   *
   * @param node AIG node reference in the `aig' graph
   * @param name Name of that node
   * @param pos  Position of that node (usually wrt. to `aig' inputs vector)
   * @param aig  AIG graph
   */
  virtual T get_input( const aig_node& node, const std::string& name, unsigned pos, const aig_graph& aig ) const = 0;
  virtual T get_constant() const = 0;
  virtual T invert( const T& v ) const = 0;
  virtual T and_op( const aig_node& node, const T& v1, const T& v2 ) const = 0;

  virtual bool terminate( const aig_node& node, const aig_graph& aig ) const
  {
    return false;
  }
};

/******************************************************************************
 * Several simulator implementations                                          *
 ******************************************************************************/

class pattern_simulator : public aig_simulator<bool>
{
public:
  pattern_simulator( const boost::dynamic_bitset<>& pattern );

  bool get_input( const aig_node& node, const std::string& name, unsigned pos, const aig_graph& aig ) const;
  bool get_constant() const;
  bool invert( const bool& v ) const;
  bool and_op( const aig_node& node, const bool& v1, const bool& v2 ) const;

private:
  boost::dynamic_bitset<> pattern;
};

class simple_assignment_simulator : public aig_simulator<bool>
{
public:
  using aig_name_value_map = std::unordered_map<std::string, bool>;

  simple_assignment_simulator( const aig_name_value_map& assignment );

  bool get_input( const aig_node& node, const std::string& name, unsigned pos, const aig_graph& aig ) const;
  bool get_constant() const;
  bool invert( const bool& v ) const;
  bool and_op( const aig_node& node, const bool& v1, const bool& v2 ) const;

private:
  const aig_name_value_map& assignment;
};

class simple_node_assignment_simulator : public aig_simulator<bool>
{
public:
  using aig_node_value_map = std::unordered_map<aig_node, bool>;

  simple_node_assignment_simulator( const aig_node_value_map& assignment );

  bool get_input( const aig_node& node, const std::string& name, unsigned pos, const aig_graph& aig ) const;
  bool get_constant() const;
  bool invert( const bool& v ) const;
  bool and_op( const aig_node& node, const bool& v1, const bool& v2 ) const;
  bool terminate( const aig_node& node, const aig_graph& aig ) const;

private:
  const aig_node_value_map& assignment;
};

class word_assignment_simulator : public aig_simulator<boost::dynamic_bitset<>>
{
public:
  using aig_name_value_map = std::unordered_map<std::string, boost::dynamic_bitset<>>;

  word_assignment_simulator( const aig_name_value_map& assignment );

  boost::dynamic_bitset<> get_input( const aig_node& node, const std::string& name, unsigned pos, const aig_graph& aig ) const;
  boost::dynamic_bitset<> get_constant() const;
  boost::dynamic_bitset<> invert( const boost::dynamic_bitset<>& v ) const;
  boost::dynamic_bitset<> and_op( const aig_node& node, const boost::dynamic_bitset<>& v1, const boost::dynamic_bitset<>& v2 ) const;

private:
  const aig_name_value_map& assignment;
};

class word_node_assignment_simulator : public aig_simulator<boost::dynamic_bitset<>>
{
public:
  using aig_node_value_map = std::unordered_map<aig_node, boost::dynamic_bitset<>>;

  word_node_assignment_simulator( const aig_node_value_map& assignment );

  boost::dynamic_bitset<> get_input( const aig_node& node, const std::string& name, unsigned pos, const aig_graph& aig ) const;
  boost::dynamic_bitset<> get_constant() const;
  boost::dynamic_bitset<> invert( const boost::dynamic_bitset<>& v ) const;
  boost::dynamic_bitset<> and_op( const aig_node& node, const boost::dynamic_bitset<>& v1, const boost::dynamic_bitset<>& v2 ) const;
  bool terminate( const aig_node& node, const aig_graph& aig ) const;

private:
  const aig_node_value_map& assignment;
};

class tt_simulator : public aig_simulator<tt>
{
public:
  tt get_input( const aig_node& node, const std::string& name, unsigned pos, const aig_graph& aig ) const;
  tt get_constant() const;
  tt invert( const tt& v ) const;
  tt and_op( const aig_node& node, const tt& v1, const tt& v2 ) const;
};

class bdd_simulator : public aig_simulator<BDD>
{
public:
  bdd_simulator() : mgr( Cudd() ) {}
  bdd_simulator( const Cudd& mgr ) : mgr( mgr ) {}

  BDD get_input( const aig_node& node, const std::string& name, unsigned pos, const aig_graph& aig ) const;
  BDD get_constant() const;
  BDD invert( const BDD& v ) const;
  BDD and_op( const aig_node& node, const BDD& v1, const BDD& v2 ) const;

protected:
  Cudd mgr;
};

class depth_simulator : public aig_simulator<unsigned>
{
public:
  unsigned get_input( const aig_node& node, const std::string& name, unsigned pos, const aig_graph& aig ) const;
  unsigned get_constant() const;
  unsigned invert( const unsigned& v ) const;
  unsigned and_op( const aig_node& node, const unsigned& v1, const unsigned& v2 ) const;
};

template<typename T>
class partial_simulator : public aig_simulator<T>
{
public:
  partial_simulator( const aig_simulator<T>& total_simulator,
                     const std::map<aig_node, T>& assignment,
                     const aig_graph& aig )
    : total_simulator( total_simulator ),
      assignment( assignment )
  {
    for ( const auto& n : aig_info( aig ).inputs )
    {
      if ( assignment.find( n ) == assignment.end() )
      {
        real_inputs.push_back( n );
      }
    }
  }

  T get_input( const aig_node& node, const std::string& name, unsigned pos, const aig_graph& aig ) const
  {
    /* Is in assignment? */
    auto it = assignment.find( node );
    if ( it != assignment.end() )
    {
      return it->second;
    }
    else
    {
      unsigned real_pos = std::distance( real_inputs.begin(), boost::find( real_inputs, node ) );
      return total_simulator.get_input( node, name, real_pos, aig );
    }
  }

  T get_constant() const { return total_simulator.get_constant(); }
  T invert( const T& v ) const { return total_simulator.invert( v ); }
  T and_op( const aig_node& node, const T& v1, const T& v2 ) const { return total_simulator.and_op( node, v1, v2 ); }

private:
  const aig_simulator<T>& total_simulator;
  const std::map<aig_node, T>& assignment;

  std::vector<aig_node> real_inputs;
};

template<typename T>
class aig_partial_node_assignment_simulator : public aig_simulator<T>
{
public:
  aig_partial_node_assignment_simulator( const aig_simulator<T>& total_simulator,
                                         const std::map<aig_node, T>& assignment,
                                         const T& default_value )
    : total_simulator( total_simulator ),
      assignment( assignment ),
      default_value( default_value ) {}

  T get_input( const aig_node& node, const std::string& name, unsigned pos, const aig_graph& aig ) const
  {
    auto it = assignment.find( node );
    if ( it == assignment.end() )
    {
      std::cout << "[w] no assignment given for '" << node << "', assume default" << std::endl;
      return default_value;
    }
    else
    {
      return it->second;
    }
  }

  T get_constant() const { return total_simulator.get_constant(); }
  T invert( const T& v ) const { return total_simulator.invert( v ); }
  T and_op( const aig_node& node, const T& v1, const T& v2 ) const
  {
    auto it = assignment.find( node );
    if ( it == assignment.end() )
    {
      return total_simulator.and_op( node, v1, v2 );
    }
    else
    {
      return it->second;
    }
  }

  bool terminate( const aig_node& node, const aig_graph& aig ) const
  {
    return assignment.find( node ) != assignment.end();
  }

private:
  const aig_simulator<T>& total_simulator;
  const std::map<aig_node, T>& assignment;
  T default_value;
};

template<typename T>
class aig_lambda_simulator : public aig_simulator<T>
{
public:
  aig_lambda_simulator( const std::function<T(const aig_node&, const std::string&, unsigned, const aig_graph&)>& get_input_func,
                        const std::function<T()>& get_constant_func,
                        const std::function<T(const T&)>& invert_func,
                        const std::function<T(const aig_node&, const T&, const T&)>& and_op_func )
    : get_input_func( get_input_func ),
      get_constant_func( get_constant_func ),
      invert_func( invert_func ),
      and_op_func( and_op_func )
  {
  }

  T get_input( const aig_node& node, const std::string& name, unsigned pos, const aig_graph& aig ) const
  {
    return get_input_func( node, name, pos, aig );
  }

  T get_constant() const
  {
    return get_constant_func();
  }

  T invert( const T& v ) const
  {
    return invert_func( v );
  }

  T and_op( const aig_node& node, const T& v1, const T& v2 ) const
  {
    return and_op_func( node, v1, v2 );
  }

private:
  std::function<T(const aig_node&, const std::string&, unsigned, const aig_graph&)> get_input_func;
  std::function<T()> get_constant_func;
  std::function<T(const T&)> invert_func;
  std::function<T(const aig_node&, const T&, const T&)> and_op_func;
};

/******************************************************************************
 * DFS visitor for actual simulation                                          *
 ******************************************************************************/

using aig_node_color_map = circuit_traits<aig_graph>::node_color_map;

template<typename T>
struct simulate_aig_node_visitor : public aig_dfs_visitor
{
public:
  simulate_aig_node_visitor( const aig_graph& aig, const aig_simulator<T>& simulator, std::map<aig_node, T>& node_values )
    : aig_dfs_visitor( aig ),
      simulator( simulator ),
      node_values( node_values ) {}

  void finish_input( const aig_node& node, const std::string& name, const aig_graph& aig )
  {
    unsigned pos = std::distance( graph_info.inputs.begin(), boost::find( graph_info.inputs, node ) );
    node_values[node] = simulator.get_input( node, name, pos, aig );
  }

  void finish_constant( const aig_node& node, const aig_graph& aig )
  {
    node_values[node] = simulator.get_constant();
  }

  void finish_aig_node( const aig_node& node, const aig_function& left, const aig_function& right, const aig_graph& aig )
  {
    T tleft, tright;

    const auto itleft  = node_values.find( left.node );
    const auto itright = node_values.find( right.node );

    if ( itleft != node_values.end() )
    {
      tleft = left.complemented ? simulator.invert( itleft->second ) : itleft->second;
    }
    if ( itright != node_values.end() )
    {
      tright = right.complemented ? simulator.invert( itright->second ) : itright->second;
    }

    node_values[node] = simulator.and_op( node, tleft, tright );
  }

private:
  const aig_simulator<T>& simulator;
  std::map<aig_node, T>& node_values;
};

/******************************************************************************
 * Methods to trigger simulation                                              *
 ******************************************************************************/

template<typename T>
T simulate_aig_node( const aig_graph& aig, const aig_node& node,
                     const aig_simulator<T>& simulator,
                     aig_node_color_map& colors,
                     std::map<aig_node, T>& node_values )
{
  boost::depth_first_visit( aig, node,
                            simulate_aig_node_visitor<T>( aig, simulator, node_values ),
                            boost::make_assoc_property_map( colors ),
                            [&simulator]( const aig_node& node, const aig_graph& aig ) { return simulator.terminate( node, aig ); } );

  return node_values[node];
}

template<typename T>
T simulate_aig_node( const aig_graph& aig, const aig_node& node,
                     const aig_simulator<T>& simulator )
{
  aig_node_color_map colors;
  std::map<aig_node, T> node_values;

  return simulate_aig_node<T>( aig, node, simulator, colors, node_values );
}

template<typename T>
T simulate_aig_function( const aig_graph& aig, const aig_function& f,
                         const aig_simulator<T>& simulator,
                         aig_node_color_map& colors,
                         std::map<aig_node, T>& node_values )
{
  T value = simulate_aig_node<T>( aig, f.node, simulator, colors, node_values );
  return f.complemented ? simulator.invert( value ) : value;
}

template<typename T>
T simulate_aig_function( const aig_graph& aig, const aig_function& f,
                         const aig_simulator<T>& simulator )
{
  T value = simulate_aig_node<T>( aig, f.node, simulator );
  return f.complemented ? simulator.invert( value ) : value;
}

template<typename T>
std::map<aig_function, T> simulate_aig( const aig_graph& aig, const aig_simulator<T>& simulator,
                                        const std::vector< aig_function >& fs,
                                        const properties::ptr& settings = properties::ptr(),
                                        const properties::ptr& statistics = properties::ptr() )
{
  /* settings */
  auto verbose = get( settings, "verbose", false );

  /* timer */
  properties_timer t( statistics );

  aig_node_color_map colors;
  std::map<aig_node, T> node_values;

  std::map<aig_function, T> results;

  for ( const auto& f : fs )
  {
    if ( verbose )
    {
      std::cout << "[i] simulate '" << f.node << "'" << std::endl;
    }
    T value = simulate_aig_node<T>( aig, f.node, simulator, colors, node_values );

    /* value may need to be inverted */
    results[f] = f.complemented ? simulator.invert( value ) : value;
  }

  set( statistics, "node_values", node_values );

  return results;
}

template<typename T>
std::map<aig_function, T> simulate_aig_full( const aig_graph& aig, const aig_simulator<T>& simulator,
                                             const properties::ptr& settings = properties::ptr(),
                                             const properties::ptr& statistics = properties::ptr() )
{
  auto in_degrees = precompute_in_degrees( aig );

  std::vector<aig_function> fs;

  std::set<aig_node> ignore_nodes;
  ignore_nodes.insert( 0 );
  for ( const auto& i : aig_info( aig ).inputs )
  {
    ignore_nodes.insert( i );
  }
  for ( const auto& o : aig_info( aig ).outputs )
  {
    ignore_nodes.insert( o.first.node );
    fs.push_back( o.first );
  }

  for ( const auto& node : boost::make_iterator_range( vertices( aig ) ) )
  {
    if ( ignore_nodes.find( node ) != ignore_nodes.end() )
    {
      continue;
    }

    if ( in_degrees[ node ] == 0u )
    {
      fs.push_back( aig_function( { node, false } ) );
    }
  }

  return simulate_aig( aig, simulator, fs, settings, statistics );
}

template<typename T>
std::map<aig_function, T> simulate_aig( const aig_graph& aig, const aig_simulator<T>& simulator,
                                        const properties::ptr& settings = properties::ptr(),
                                        const properties::ptr& statistics = properties::ptr() )
{
  /* settings */
  auto verbose = get( settings, "verbose", false );

  /* timer */
  properties_timer t( statistics );

  aig_node_color_map colors;
  std::map<aig_node, T> node_values;

  std::map<aig_function, T> results;

  for ( const auto& o : aig_info( aig ).outputs )
  {
    if ( verbose )
    {
      std::cout << "[i] simulate '" << o.second << "'" << std::endl;
    }
    T value = simulate_aig_node<T>( aig, o.first.node, simulator, colors, node_values );

    /* value may need to be inverted */
    results[o.first] = o.first.complemented ? simulator.invert( value ) : value;
  }

  set( statistics, "node_values", node_values );

  return results;
}

}

#endif

// Local Variables:
// c-basic-offset: 2
// eval: (c-set-offset 'substatement-open 0)
// eval: (c-set-offset 'innamespace 0)
// End:


//
// Created by jiangkangkang on 2020/3/9.
//

#ifndef R_BUILD
#include <Eigen/Eigen>
#include <unsupported/Eigen/MatrixFunctions>

#else

#include <RcppEigen.h>

#endif

#include <string.h>

#include <algorithm>
#include <iostream>

using namespace std;
using namespace Eigen;

#ifndef R_BUILD
Eigen::MatrixXd Pointer2MatrixXd(double *x, int x_row, int x_col) {
    Eigen::MatrixXd x_matrix(x_row, x_col);
    int i, j;
    for (i = 0; i < x_row; i++) {
        for (j = 0; j < x_col; j++) {
            x_matrix(i, j) = x[i * x_col + j];
        }
    }
    return x_matrix;
}

Eigen::MatrixXi Pointer2MatrixXi(int *x, int x_row, int x_col) {
    Eigen::MatrixXi x_matrix(x_row, x_col);
    int i, j;
    for (i = 0; i < x_row; i++) {
        for (j = 0; j < x_col; j++) {
            x_matrix(i, j) = x[i * x_col + j];
        }
    }
    return x_matrix;
}

Eigen::VectorXd Pointer2VectorXd(double *x, int x_len) {
    Eigen::VectorXd x_vector(x_len);
    int i;
    for (i = 0; i < x_len; i++) {
        x_vector[i] = x[i];
    }
    return x_vector;
}

Eigen::VectorXi Pointer2VectorXi(int *x, int x_len) {
    Eigen::VectorXi x_vector(x_len);
    int i;
    for (i = 0; i < x_len; i++) {
        x_vector[i] = x[i];
    }
    return x_vector;
}

void MatrixXd2Pointer(Eigen::MatrixXd x_matrix, double *x) {
    int x_matrix_row, x_matrix_col, i, j;
    x_matrix_row = x_matrix.rows();
    x_matrix_col = x_matrix.cols();
    for (i = 0; i < x_matrix_row; i++) {
        for (j = 0; j < x_matrix_col; j++) {
            x[i * x_matrix_col + j] = x_matrix(i, j);
        }
    }
    return;
}

// void MatrixXi2Pointer(Eigen::MatrixXi x_matrix, int *x)
// {
//     int x_matrix_row, x_matrix_col, i, j;
//     x_matrix_row = x_matrix.rows();
//     x_matrix_col = x_matrix.cols();
//     for (i = 0; i < x_matrix_row; i++)
//     {
//         for (j = 0; j < x_matrix_col; j++)
//         {
//             x[i * x_matrix_col + j] = x_matrix(i, j);
//         }
//     }
// }

void VectorXd2Pointer(Eigen::VectorXd x_vector, double *x) {
    int x_matrix_len, i;
    x_matrix_len = x_vector.size();

    for (i = 0; i < x_matrix_len; i++) {
        x[i] = x_vector[i];
    }
    return;
}

// void VectorXi2Pointer(Eigen::VectorXi x_vector, int *x)
// {
//     int x_matrix_len, i;
//     x_matrix_len = x_vector.size();

//     for (i = 0; i < x_matrix_len; i++)
//     {
//         x[i] = x_vector[i];
//     }
// }
#endif

Eigen::VectorXi find_ind(Eigen::VectorXi &L, Eigen::VectorXi &index, Eigen::VectorXi &gsize, int beta_size, int N) {
    if (L.size() == N) {
        return Eigen::VectorXi::LinSpaced(beta_size, 0, beta_size - 1);
    } else {
        int mark = 0;
        Eigen::VectorXi ind = Eigen::VectorXi::Zero(beta_size);

        for (int i = 0; i < L.size(); i++) {
            ind.segment(mark, gsize(L(i))) =
                Eigen::VectorXi::LinSpaced(gsize(L(i)), index(L(i)), index(L(i)) + gsize(L(i)) - 1);
            mark = mark + gsize(L(i));
        }
        return ind.head(mark).eval();
    }
}

Eigen::Matrix<Eigen::MatrixXd, -1, -1> invPhi(Eigen::Matrix<Eigen::MatrixXd, -1, -1> &Phi, int N) {
    Eigen::Matrix<Eigen::MatrixXd, -1, -1> invPhi(N, 1);
    int row;
    for (int i = 0; i < N; i++) {
        row = (Phi(i, 0)).rows();
        invPhi(i, 0) = (Phi(i, 0)).ldlt().solve(Eigen::MatrixXd::Identity(row, row));
    }
    return invPhi;
}

void slice_assignment(Eigen::VectorXd &nums, Eigen::VectorXi &ind, double value) {
    if (ind.size() != 0) {
        for (int i = 0; i < ind.size(); i++) {
            nums(ind(i)) = value;
        }
    }
    return;
}

// Eigen::VectorXd vector_slice(Eigen::VectorXd &nums, Eigen::VectorXi &ind)
// {
//     Eigen::VectorXd sub_nums(ind.size());
//     if (ind.size() != 0)
//     {
//         for (int i = 0; i < ind.size(); i++)
//         {
//             sub_nums(i) = nums(ind(i));
//         }
//     }
//     return sub_nums;
// }

Eigen::VectorXi vector_slice(Eigen::VectorXi &nums, Eigen::VectorXi &ind) {
    Eigen::VectorXi sub_nums(ind.size());
    if (ind.size() != 0) {
        for (int i = 0; i < ind.size(); i++) {
            sub_nums(i) = nums(ind(i));
        }
    }
    return sub_nums;
}

// Eigen::MatrixXd matrix_slice(Eigen::MatrixXd &nums, Eigen::VectorXi &ind, int axis)
// {
//     if (axis == 0)
//     {
//         Eigen::MatrixXd sub_nums(ind.size(), nums.cols());
//         if (ind.size() != 0)
//         {
//             for (int i = 0; i < ind.size(); i++)
//             {
//                 sub_nums.row(i) = nums.row(ind(i));
//             }
//         }
//         return sub_nums;
//     }
//     else
//     {
//         Eigen::MatrixXd sub_nums(nums.rows(), ind.size());
//         if (ind.size() != 0)
//         {
//             for (int i = 0; i < ind.size(); i++)
//             {
//                 sub_nums.col(i) = nums.col(ind(i));
//             }
//         }
//         return sub_nums;
//     }
// }

// Eigen::MatrixXd row_slice(Eigen::MatrixXd &nums, Eigen::VectorXi &ind)
// {
//     Eigen::MatrixXd sub_nums(ind.size(), nums.cols());
//     if (ind.size() != 0)
//     {
//         for (int i = 0; i < ind.size(); i++)
//         {
//             sub_nums.row(i) = nums.row(ind(i));
//         }
//     }
//     return sub_nums;
// }

// Eigen::VectorXi get_value_index(Eigen::VectorXd &nums, double value)
// {
//     Eigen::VectorXi ind(nums.size());
//     int cur_index = 0;
//     for (int i = 0; i < nums.size(); i++)
//     {
//         if (nums(i) == value)
//         {
//             ind(cur_index) = i;
//             cur_index += 1;
//         }
//     }
//     return ind.head(cur_index).eval();
// }

// replace B by C in A
// to do : binary search
Eigen::VectorXi diff_union(Eigen::VectorXi A, Eigen::VectorXi &B, Eigen::VectorXi &C) {
    unsigned int k;
    for (unsigned int i = 0; i < B.size(); i++) {
        for (k = 0; k < A.size(); k++) {
            if (B(i) == A(k)) {
                A(k) = C(i);
                break;
            }
        }
    }
    sort(A.data(), A.data() + A.size());
    return A;
}

Eigen::VectorXi min_k(Eigen::VectorXd &vec, int k, bool sort_by_value) {
    Eigen::VectorXi ind = Eigen::VectorXi::LinSpaced(vec.size(), 0, vec.size() - 1);  // [0 1 2 3 ... N-1]
    auto rule = [vec](int i, int j) -> bool { return vec(i) < vec(j); };              // sort rule
    std::nth_element(ind.data(), ind.data() + k, ind.data() + ind.size(), rule);
    if (sort_by_value) {
        std::sort(ind.data(), ind.data() + k, rule);
    } else {
        std::sort(ind.data(), ind.data() + k);
    }

    return ind.head(k).eval();
}

Eigen::VectorXi max_k(Eigen::VectorXd &vec, int k, bool sort_by_value) {
    Eigen::VectorXi ind = Eigen::VectorXi::LinSpaced(vec.size(), 0, vec.size() - 1);  // [0 1 2 3 ... N-1]
    auto rule = [vec](int i, int j) -> bool { return vec(i) > vec(j); };              // sort rule
    std::nth_element(ind.data(), ind.data() + k, ind.data() + ind.size(), rule);
    if (sort_by_value) {
        std::sort(ind.data(), ind.data() + k, rule);
    } else {
        std::sort(ind.data(), ind.data() + k);
    }
    return ind.head(k).eval();
}

// Eigen::VectorXi max_k_2(Eigen::VectorXd &vec, int k)
// {
//     Eigen::VectorXi ind = Eigen::VectorXi::LinSpaced(vec.size(), 0, vec.size() - 1); //[0 1 2 3 ... N-1]
//     auto rule = [vec](int i, int j) -> bool
//     {
//         return vec(i) > vec(j);
//     }; // sort rule
//     std::nth_element(ind.data(), ind.data() + k, ind.data() + ind.size(), rule);
//     std::sort(ind.data(), ind.data() + k);
//     return ind.head(k).eval();
// }

// Ac
Eigen::VectorXi Ac(Eigen::VectorXi &A, int N) {
    int A_size = A.size();
    if (A_size == 0) {
        return Eigen::VectorXi::LinSpaced(N, 0, N - 1);
    } else if (A_size == N) {
        Eigen::VectorXi I(0);
        return I;
    } else {
        Eigen::VectorXi I(N - A_size);
        int cur_index = 0;
        int A_index = 0;
        for (int i = 0; i < N; i++) {
            if (A_index >= A_size) {
                I(cur_index) = i;
                cur_index += 1;
                continue;
            }
            if (i != A(A_index)) {
                I(cur_index) = i;
                cur_index += 1;
            } else {
                A_index += 1;
            }
        }
        return I;
    }
}

// // Ac
// Eigen::VectorXi Ac(Eigen::VectorXi &A, Eigen::VectorXi &U)
// {
//     int A_size = A.size();
//     int N = U.size();
//     if (A_size == 0)
//     {
//         return U;
//     }
//     else if (A_size == N)
//     {
//         Eigen::VectorXi I(0);
//         return I;
//     }
//     else
//     {
//         Eigen::VectorXi I(N - A_size);
//         int cur_index = 0;
//         int A_index = 0;
//         for (int i = 0; i < N; i++)
//         {
//             if (A_index < A.size() && U(i) == A(A_index))
//             {
//                 A_index += 1;
//                 continue;
//             }
//             else
//             {
//                 I(cur_index) = U(i);
//                 cur_index += 1;
//             }
//         }
//         return I;
//     }
// }

void slice(Eigen::VectorXd &nums, Eigen::VectorXi &ind, Eigen::VectorXd &A, int axis) {
    if (ind.size() == 0) {
        A = Eigen::VectorXd::Zero(0);
    } else {
        A = Eigen::VectorXd::Zero(ind.size());
        for (int i = 0; i < ind.size(); i++) {
            A(i) = nums(ind(i));
        }
    }
    return;
}

void slice(Eigen::MatrixXd &nums, Eigen::VectorXi &ind, Eigen::MatrixXd &A, int axis) {
    if (axis == 0) {
        A = Eigen::MatrixXd::Zero(ind.size(), nums.cols());
        if (ind.size() != 0) {
            for (int i = 0; i < ind.size(); i++) {
                A.row(i) = nums.row(ind(i));
            }
        }
    } else {
        A = Eigen::MatrixXd::Zero(nums.rows(), ind.size());
        if (ind.size() != 0) {
            for (int i = 0; i < ind.size(); i++) {
                A.col(i) = nums.col(ind(i));
            }
        }
    }
    return;
}

void slice(Eigen::SparseMatrix<double> &nums, Eigen::VectorXi &ind, Eigen::SparseMatrix<double> &A, int axis) {
    if (axis == 0) {
        Eigen::SparseMatrix<double, Eigen::RowMajor> nums_row(nums);
        Eigen::SparseMatrix<double, Eigen::RowMajor> A_row(ind.size(), nums.cols());
        A_row.reserve(nums.nonZeros());

        if (ind.size() != 0) {
            for (int i = 0; i < ind.size(); i++) {
                A_row.row(i) = nums_row.row(ind(i));
            }
        }

        A = A_row;
    } else {
        A.resize(nums.rows(), ind.size());
        A.reserve(nums.nonZeros());
        if (ind.size() != 0) {
            for (int i = 0; i < ind.size(); i++) {
                A.col(i) = nums.col(ind(i));
            }
        }
    }
    return;
}

void slice_restore(Eigen::VectorXd &A, Eigen::VectorXi &ind, Eigen::VectorXd &nums, int axis) {
    if (ind.size() == 0) {
        nums = Eigen::VectorXd::Zero(nums.size());
    } else {
        nums = Eigen::VectorXd::Zero(nums.size());
        for (int i = 0; i < ind.size(); i++) {
            nums(ind(i)) = A(i);
        }
    }
    return;
}

void slice_restore(Eigen::MatrixXd &A, Eigen::VectorXi &ind, Eigen::MatrixXd &nums, int axis) {
    if (axis == 0) {
        nums = Eigen::MatrixXd::Zero(nums.rows(), nums.cols());
        if (ind.size() != 0) {
            for (int i = 0; i < ind.size(); i++) {
                nums.row(ind(i)) = A.row(i);
            }
        }
    } else {
        nums = Eigen::MatrixXd::Zero(nums.rows(), nums.cols());
        if (ind.size() != 0) {
            for (int i = 0; i < ind.size(); i++) {
                nums.col(ind(i)) = A.col(i);
            }
        }
    }
    return;
}

void coef_set_zero(int p, int M, Eigen::VectorXd &beta, double &coef0) {
    beta = Eigen::VectorXd::Zero(p);
    coef0 = 0.;
    return;
}

void coef_set_zero(int p, int M, Eigen::MatrixXd &beta, Eigen::VectorXd &coef0) {
    beta = Eigen::MatrixXd::Zero(p, M);
    coef0 = Eigen::VectorXd::Zero(M);
    return;
}

Eigen::VectorXd array_product(Eigen::VectorXd &A, Eigen::VectorXd &B, int axis) {
    A = A.array() * B.array();
    return A;
}

Eigen::MatrixXd array_product(Eigen::MatrixXd &A, Eigen::VectorXd &B, int axis) {
    if (axis == 0) {
        for (int i = 0; i < A.rows(); i++) {
            A.row(i) = A.row(i).array() * B.array();
        }
    } else {
        for (int i = 0; i < A.cols(); i++) {
            A.col(i) = A.col(i).array() * B.array();
        }
    }
    return A;
}

// Eigen::SparseMatrix<double> array_product(Eigen::SparseMatrix<double> &A, Eigen::VectorXd &B, int axis)
// {
//     for (int i = 0; i < A.cols(); i++)
//     {
//         A.col(i) = A.col(i) * B;
//     }
//     return A;
// }

void array_quotient(Eigen::VectorXd &A, Eigen::VectorXd &B, int axis) {
    A = A.array() / B.array();
    return;
}
void array_quotient(Eigen::MatrixXd &A, Eigen::VectorXd &B, int axis) {
    if (axis == 0) {
        for (int i = 0; i < A.rows(); i++) {
            A.row(i) = A.row(i).array() / B.array();
        }
    } else {
        for (int i = 0; i < A.cols(); i++) {
            A.col(i) = A.col(i).array() / B.array();
        }
    }
    return;
}

double matrix_dot(Eigen::VectorXd &A, Eigen::VectorXd &B) { return A.dot(B); }

Eigen::VectorXd matrix_dot(Eigen::MatrixXd &A, Eigen::VectorXd &B) { return A.transpose() * B; }

// void matrix_sqrt(Eigen::MatrixXd &A, Eigen::MatrixXd &B)
// {
//     A.sqrt().evalTo(B);
// }

// void matrix_sqrt(Eigen::SparseMatrix<double> &A, Eigen::MatrixXd &B)
// {
//     if (A.rows() == 1)
//     {
//         B = Eigen::MatrixXd::Ones(1, 1) * A.cwiseSqrt();
//     }
//     else
//     {
//         Eigen::SelfAdjointEigenSolver<Eigen::SparseMatrix<double>>
//             adjoint_eigen_solver(A);
//         // const auto &eigenvalues = adjoint_eigen_solver.eigenvalues();
//         // CHECK_GT(eigenvalues.minCoeff(), -1e-5) //R.minCoeff() 意思是 min(R(:))最小值
//         //     << "MatrixSqrt failed with negative eigenvalues: "
//         //     << eigenvalues.transpose();

//         B = adjoint_eigen_solver.eigenvectors() * (adjoint_eigen_solver.eigenvalues().cwiseSqrt().asDiagonal()) *
//         adjoint_eigen_solver.eigenvectors().transpose();
//         //    .cwiseMax(Eigen::Matrix<FloatType, N, 1>::Zero()) //R.cwiseMax(P)
//         //    .cwiseSqrt()  // R.cwiseSqrt()
//         //    .asDiagonal() * // x.asDiagonal()
//         //    adjoint_eigen_solver.eigenvectors().transpose();
//     }
// }

void add_constant_column(Eigen::MatrixXd &X) {
    X.col(0) = Eigen::MatrixXd::Ones(X.rows(), 1);
    return;
}

void add_constant_column(Eigen::SparseMatrix<double> &X) {
    for (int i = 0; i < X.rows(); i++) {
        X.insert(i, 0) = 1.0;
    }
    return;
}

// void set_nonzeros(Eigen::MatrixXd &X, Eigen::MatrixXd &x)
// {
//     return;
// }

// void set_nonzeros(Eigen::SparseMatrix<double> &X, Eigen::SparseMatrix<double> &x)
// {
//     X.reserve(x.nonZeros() + x.rows());
// }

// void overload_ldlt(Eigen::SparseMatrix<double> &X_new, Eigen::SparseMatrix<double> &X, Eigen::VectorXd &Z,
// Eigen::VectorXd &beta)
// {
//     // Eigen::SparseMatrix<double> XTX = X_new.transpose() * X;

//     // Eigen::SimplicialLDLT<Eigen::SparseMatrix<double>> solver;
//     // solver.compute(X_new.transpose() * X);
//     // beta = solver.solve(X_new.transpose() * Z);
//     Eigen::MatrixXd XTX = X_new.transpose() * X;
//     beta = (XTX).ldlt().solve(X_new.transpose() * Z);
//     return;
// }

// void overload_ldlt(Eigen::SparseMatrix<double> &X_new, Eigen::SparseMatrix<double> &X, Eigen::MatrixXd &Z,
// Eigen::MatrixXd &beta)
// {
//     // Eigen::SparseMatrix<double> XTX = X_new.transpose() * X;

//     // Eigen::SimplicialLDLT<Eigen::SparseMatrix<double>> solver;
//     // solver.compute(X_new.transpose() * X);
//     // beta = solver.solve(X_new.transpose() * Z);

//     Eigen::MatrixXd XTX = X_new.transpose() * X;

//     beta = (XTX).ldlt().solve(X_new.transpose() * Z);

//     return;
// }

// void overload_ldlt(Eigen::MatrixXd &X_new, Eigen::MatrixXd &X, Eigen::VectorXd &Z, Eigen::VectorXd &beta)
// {
//     beta = (X_new.transpose() * X).ldlt().solve(X_new.transpose() * Z);
//     return;
// }

// void overload_ldlt(Eigen::MatrixXd &X_new, Eigen::MatrixXd &X, Eigen::MatrixXd &Z, Eigen::MatrixXd &beta)
// {
//     beta = (X_new.transpose() * X).ldlt().solve(X_new.transpose() * Z);
//     return;
// }

// bool check_ill_condition(Eigen::MatrixXd &M){
//     Eigen::JacobiSVD<Eigen::MatrixXd> svd(M);
//     double l1 = svd.singularValues()(0);
//     double l2 = svd.singularValues()(svd.singularValues().size()-1);
//     return ((l2 == 0 || l1 / l2 > 1e+10) ? true : false);
// }

// to do
void add_weight(Eigen::MatrixXd &x, Eigen::VectorXd &y, Eigen::VectorXd weights) {
    Eigen::VectorXd sqrt_weight = weights.array().sqrt();
    int n = x.rows();
    for (int i = 0; i < n; i++) {
        x.row(i) = x.row(i) * sqrt_weight(i);
    }
    array_product(y, sqrt_weight, 1);
};

void add_weight(Eigen::MatrixXd &x, Eigen::MatrixXd &y, Eigen::VectorXd weights) {
    Eigen::VectorXd sqrt_weight = weights.array().sqrt();
    int n = x.rows();
    for (int i = 0; i < n; i++) {
        x.row(i) = x.row(i) * sqrt_weight(i);
    }
    array_product(y, sqrt_weight, 1);
};

void add_weight(Eigen::SparseMatrix<double> &x, Eigen::VectorXd &y, Eigen::VectorXd weights) {
    for (int k = 0; k < x.outerSize(); ++k) {
        for (SparseMatrix<double>::InnerIterator it(x, k); it; ++it) {
            x.coeffRef(int(it.row()), int(it.col())) = x.coeffRef(int(it.row()), int(it.col())) * weights(it.row());
        }
    }
    Eigen::VectorXd sqrt_weight = weights.array().sqrt();
    array_product(y, sqrt_weight, 1);
};

void add_weight(Eigen::SparseMatrix<double> &x, Eigen::MatrixXd &y, Eigen::VectorXd weights) {
    for (int k = 0; k < x.outerSize(); ++k) {
        for (SparseMatrix<double>::InnerIterator it(x, k); it; ++it) {
            x.coeffRef(int(it.row()), int(it.col())) = x.coeffRef(int(it.row()), int(it.col())) * weights(it.row());
        }
    }
    Eigen::VectorXd sqrt_weight = weights.array().sqrt();
    array_product(y, sqrt_weight, 1);
};


// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements.  See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership.  The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License.  You may obtain a copy of the License at
//
//   http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied.  See the License for the
// specific language governing permissions and limitations
// under the License.

// Operations that can deal with very large values (256-bit).
//
// The intermediate results with decimal can be larger than what can fit into 128-bit,
// but the final results can fit in 128-bit after scaling down. These functions deal
// with operations on the intermediate values.
//

#include "gandiva/decimal_xlarge.h"

#include <boost/multiprecision/cpp_int.hpp>
#include <limits>
#include <vector>

#include "arrow/util/basic_decimal.h"
#include "arrow/util/logging.h"
#include "gandiva/decimal_type_util.h"

#ifndef GANDIVA_UNIT_TEST
#include "gandiva/engine.h"
#include "gandiva/exported_funcs.h"

namespace gandiva {

void ExportedDecimalFunctions::AddMappings(Engine* engine) const {
  std::vector<llvm::Type*> args;
  auto types = engine->types();

  // gdv_multiply_and_scale_down
  args = {types->i64_type(),      // int64_t x_high
          types->i64_type(),      // uint64_t x_low
          types->i64_type(),      // int64_t y_high
          types->i64_type(),      // uint64_t x_low
          types->i32_type(),      // int32_t reduce_scale_by
          types->i64_ptr_type(),  // int64_t* out_high
          types->i64_ptr_type(),  // uint64_t* out_low
          types->i8_ptr_type()};  // bool* overflow

  engine->AddGlobalMappingForFunc(
      "gdv_xlarge_multiply_and_scale_down", types->void_type() /*return_type*/, args,
      reinterpret_cast<void*>(gdv_xlarge_multiply_and_scale_down));

  // gdv_xlarge_scale_up_and_divide
  args = {types->i64_type(),      // int64_t x_high
          types->i64_type(),      // uint64_t x_low
          types->i64_type(),      // int64_t y_high
          types->i64_type(),      // uint64_t y_low
          types->i32_type(),      // int32_t increase_scale_by
          types->i64_ptr_type(),  // int64_t* out_high
          types->i64_ptr_type(),  // uint64_t* out_low
          types->i8_ptr_type()};  // bool* overflow

  engine->AddGlobalMappingForFunc(
      "gdv_xlarge_scale_up_and_divide", types->void_type() /*return_type*/, args,
      reinterpret_cast<void*>(gdv_xlarge_scale_up_and_divide));

  // gdv_xlarge_mod
  args = {types->i64_type(),       // int64_t x_high
          types->i64_type(),       // uint64_t x_low
          types->i32_type(),       // int32_t x_scale
          types->i64_type(),       // int64_t y_high
          types->i64_type(),       // uint64_t y_low
          types->i32_type(),       // int32_t y_scale
          types->i64_ptr_type(),   // int64_t* out_high
          types->i64_ptr_type()};  // uint64_t* out_low

  engine->AddGlobalMappingForFunc("gdv_xlarge_mod", types->void_type() /*return_type*/,
                                  args, reinterpret_cast<void*>(gdv_xlarge_mod));

  // gdv_xlarge_compare
  args = {types->i64_type(),   // int64_t x_high
          types->i64_type(),   // uint64_t x_low
          types->i32_type(),   // int32_t x_scale
          types->i64_type(),   // int64_t y_high
          types->i64_type(),   // uint64_t y_low
          types->i32_type()};  // int32_t y_scale

  engine->AddGlobalMappingForFunc("gdv_xlarge_compare", types->i32_type() /*return_type*/,
                                  args, reinterpret_cast<void*>(gdv_xlarge_compare));
}

}  // namespace gandiva

#endif  // !GANDIVA_UNIT_TEST

using arrow::BasicDecimal128;
using boost::multiprecision::int256_t;

namespace gandiva {
namespace internal {

// Convert to 256-bit integer from 128-bit decimal.
static int256_t ConvertToInt256(BasicDecimal128 in) {
  int256_t v = in.high_bits();
  v <<= 64;
  v |= in.low_bits();
  return v;
}

// Convert to 128-bit decimal from 256-bit integer.
// If there is an overflow, the output is undefined.
static BasicDecimal128 ConvertToDecimal128(int256_t in, bool* overflow) {
  BasicDecimal128 result;
  constexpr int256_t UINT64_MASK = std::numeric_limits<uint64_t>::max();

  int256_t in_abs = abs(in);
  bool is_negative = in < 0;

  uint64_t low = (in_abs & UINT64_MASK).convert_to<uint64_t>();
  in_abs >>= 64;
  uint64_t high = (in_abs & UINT64_MASK).convert_to<uint64_t>();
  in_abs >>= 64;

  if (in_abs > 0) {
    // we've shifted in by 128-bit, so nothing should be left.
    *overflow = true;
  } else if (high > INT64_MAX) {
    // the high-bit must not be set (signed 128-bit).
    *overflow = true;
  } else {
    result = BasicDecimal128(static_cast<int64_t>(high), low);
    if (result > BasicDecimal128::GetMaxValue()) {
      *overflow = true;
    }
  }
  return is_negative ? -result : result;
}

static constexpr int32_t kMaxLargeScale = 2 * DecimalTypeUtil::kMaxPrecision;

// Compute the scale multipliers once.
static std::array<int256_t, kMaxLargeScale + 1> kLargeScaleMultipliers =
    ([]() -> std::array<int256_t, kMaxLargeScale + 1> {
      std::array<int256_t, kMaxLargeScale + 1> values;
      values[0] = 1;
      for (int32_t idx = 1; idx <= kMaxLargeScale; idx++) {
        values[idx] = values[idx - 1] * 10;
      }
      return values;
    })();

static int256_t GetScaleMultiplier(int scale) {
  DCHECK_GE(scale, 0);
  DCHECK_LE(scale, kMaxLargeScale);

  return kLargeScaleMultipliers[scale];
}

// divide input by 10^reduce_by, and round up the fractional part.
static int256_t ReduceScaleBy(int256_t in, int32_t reduce_by) {
  if (reduce_by == 0) {
    // nothing to do.
    return in;
  }

  int256_t divisor = GetScaleMultiplier(reduce_by);
  DCHECK_GT(divisor, 0);
  DCHECK_EQ(divisor % 2, 0);  // multiple of 10.
  auto result = in / divisor;
  auto remainder = in % divisor;
  // round up (same as BasicDecimal128::ReduceScaleBy)
  if (abs(remainder) >= (divisor >> 1)) {
    result += (in > 0 ? 1 : -1);
  }
  return result;
}

// multiply input by 10^increase_by.
static int256_t IncreaseScaleBy(int256_t in, int32_t increase_by) {
  DCHECK_GE(increase_by, 0);
  DCHECK_LE(increase_by, 2 * DecimalTypeUtil::kMaxPrecision);

  return in * GetScaleMultiplier(increase_by);
}

}  // namespace internal
}  // namespace gandiva

extern "C" {

void gdv_xlarge_multiply_and_scale_down(int64_t x_high, uint64_t x_low, int64_t y_high,
                                        uint64_t y_low, int32_t reduce_scale_by,
                                        int64_t* out_high, uint64_t* out_low,
                                        bool* overflow) {
  BasicDecimal128 x{x_high, x_low};
  BasicDecimal128 y{y_high, y_low};
  auto intermediate_result =
      gandiva::internal::ConvertToInt256(x) * gandiva::internal::ConvertToInt256(y);
  intermediate_result =
      gandiva::internal::ReduceScaleBy(intermediate_result, reduce_scale_by);
  auto result = gandiva::internal::ConvertToDecimal128(intermediate_result, overflow);
  *out_high = result.high_bits();
  *out_low = result.low_bits();
}

void gdv_xlarge_scale_up_and_divide(int64_t x_high, uint64_t x_low, int64_t y_high,
                                    uint64_t y_low, int32_t increase_scale_by,
                                    int64_t* out_high, uint64_t* out_low,
                                    bool* overflow) {
  BasicDecimal128 x{x_high, x_low};
  BasicDecimal128 y{y_high, y_low};

  int256_t x_large = gandiva::internal::ConvertToInt256(x);
  int256_t x_large_scaled_up =
      gandiva::internal::IncreaseScaleBy(x_large, increase_scale_by);
  int256_t y_large = gandiva::internal::ConvertToInt256(y);
  int256_t result_large = x_large_scaled_up / y_large;
  int256_t remainder_large = x_large_scaled_up % y_large;

  // Since we are scaling up and then, scaling down, round-up the result (+1 for +ve,
  // -1 for -ve), if the remainder is >= 2 * divisor.
  if (abs(2 * remainder_large) >= abs(y_large)) {
    // x +ve and y +ve, result is +ve =>   (1 ^ 1)  + 1 =  0 + 1 = +1
    // x +ve and y -ve, result is -ve =>  (-1 ^ 1)  + 1 = -2 + 1 = -1
    // x +ve and y -ve, result is -ve =>   (1 ^ -1) + 1 = -2 + 1 = -1
    // x -ve and y -ve, result is +ve =>  (-1 ^ -1) + 1 =  0 + 1 = +1
    result_large += (x.Sign() ^ y.Sign()) + 1;
  }
  auto result = gandiva::internal::ConvertToDecimal128(result_large, overflow);
  *out_high = result.high_bits();
  *out_low = result.low_bits();
}

void gdv_xlarge_mod(int64_t x_high, uint64_t x_low, int32_t x_scale, int64_t y_high,
                    uint64_t y_low, int32_t y_scale, int64_t* out_high,
                    uint64_t* out_low) {
  BasicDecimal128 x{x_high, x_low};
  BasicDecimal128 y{y_high, y_low};

  int256_t x_large = gandiva::internal::ConvertToInt256(x);
  int256_t y_large = gandiva::internal::ConvertToInt256(y);
  if (x_scale < y_scale) {
    x_large = gandiva::internal::IncreaseScaleBy(x_large, y_scale - x_scale);
  } else {
    y_large = gandiva::internal::IncreaseScaleBy(y_large, x_scale - y_scale);
  }
  auto intermediate_result = x_large % y_large;
  bool overflow = false;
  auto result = gandiva::internal::ConvertToDecimal128(intermediate_result, &overflow);
  DCHECK_EQ(overflow, false);

  *out_high = result.high_bits();
  *out_low = result.low_bits();
}

int32_t gdv_xlarge_compare(int64_t x_high, uint64_t x_low, int32_t x_scale,
                           int64_t y_high, uint64_t y_low, int32_t y_scale) {
  BasicDecimal128 x{x_high, x_low};
  BasicDecimal128 y{y_high, y_low};

  int256_t x_large = gandiva::internal::ConvertToInt256(x);
  int256_t y_large = gandiva::internal::ConvertToInt256(y);
  if (x_scale < y_scale) {
    x_large = gandiva::internal::IncreaseScaleBy(x_large, y_scale - x_scale);
  } else {
    y_large = gandiva::internal::IncreaseScaleBy(y_large, x_scale - y_scale);
  }

  if (x_large == y_large) {
    return 0;
  } else if (x_large < y_large) {
    return -1;
  } else {
    return 1;
  }
}

}  // extern "C"