#include "chainparams.h"
#include "libzerocoin/ArithmeticCircuit.h"
#include "libzerocoin/PolynomialCommitment.h"
#include "libzerocoin/Bulletproofs.h"
#include "libzerocoin/SerialNumberSoK_small.h"
#include "libzerocoin/SerialNumberSignatureOfKnowledge.h"
#include <boost/test/unit_test.hpp>
#include <iostream>
#include <util/time.h>
#include <random.h>
#include <veil/zerocoin/zchain.h>

using namespace libzerocoin;

#define COLOR_STR_NORMAL  "\033[0m"
#define COLOR_BOLD        "\033[1m"
#define COLOR_STR_GREEN   "\033[32m"
#define COLOR_STR_RED     "\033[31m"
#define COLOR_CYAN        "\033[0;36m"
#define COLOR_MAGENTA     "\u001b[35m"

std::string colorNormal(COLOR_STR_NORMAL);
std::string colorBold(COLOR_BOLD);
std::string colorGreen(COLOR_STR_GREEN);
std::string colorRed(COLOR_STR_RED);
std::string colorCyan(COLOR_CYAN);
std::string colorMagenta(COLOR_MAGENTA);

// Global test counters
uint32_t    zNumTests        = 0;
uint32_t    zSuccessfulTests = 0;

std::string Pass(bool fReverseTest = false)
{
    return fReverseTest ? "[FAIL (good)]" : "[PASS]";
}

std::string Fail(bool fReverseTest = false)
{
    return fReverseTest ? "[PASS (when it shouldn't!)]" : "[FAIL]";
}

// Parameters ----------------------------------------------------------------------------------------

bool Test_generators(IntegerGroupParams SoKGroup)
{
    zNumTests++;
    std::cout << "- Testing generators...";
    for(unsigned int i=0; i<512; i++) {
        if ( SoKGroup.gis[i].pow_mod(SoKGroup.groupOrder,SoKGroup.modulus) != CBigNum(1)) {
            std::cout << colorRed << Fail() << std::endl;
            std::cout << "gis[" << i << "] ** q != 1" << colorNormal << std::endl;
            return false;
        }
    }
    std::cout << colorGreen << Pass() << colorNormal << std::endl;
    zSuccessfulTests++;
    return true;
}

bool parameters_tests()
{
    std::cout << colorBold << "*** parameters_tests ***" << std::endl;
    std::cout << "------------------------" << colorNormal << std::endl;

    bool finalResult = true;

    SelectParams(CBaseChainParams::MAIN);
    ZerocoinParams *ZCParams = Params().Zerocoin_Params();
    (void)ZCParams;

    finalResult = finalResult & Test_generators(ZCParams->serialNumberSoKCommitmentGroup);

    std::cout << std::endl;

    return finalResult;
}

// ---------------------------------------------------------------------------------------------------
// Arithmetic Circuit --------------------------------------------------------------------------------
bool Test_multGates(ArithmeticCircuit ac, CBigNum q)
{
    zNumTests++;
    // If multiplication gates hold this should be true
    std::cout << "- Testing A times B equals C...";
    for(unsigned int i=0; i<ZKP_M; i++) for(unsigned int j=0; j<ZKP_N; j++) {
            if(ac.A[i][j].mul_mod(ac.B[i][j], q) != ac.C[i][j]) {
                std::cout << colorRed << Fail() << std::endl;
                std::cout << "Hadamard Test failed at i=" << i << ", j=" << j << colorNormal << std::endl;
                return false;
            }
        }

    std::cout << colorGreen << Pass()  << colorNormal << std::endl;
    zSuccessfulTests++;
    return true;
}

bool Test_cfinalLog(ArithmeticCircuit ac, CBigNum q, CBigNum a, CBigNum b, bool fReverseTest = false)
{
    zNumTests++;
    // If circuit correctly evaluates (a^serial)*(b^randomness) this should be true
    std::cout << "- Testing C_final equals Logarithm";
    if (fReverseTest)
        std::cout << colorMagenta << " with wrong assignment" << colorNormal;
    std::cout << "...";
    CBigNum logarithm =
            a.pow_mod(ac.getSerialNumber(),q).mul_mod(
                    b.pow_mod(ac.getRandomness(),q),q);
    CBigNum Cfinal = ac.C[ZKP_M-1][0];
    bool test = (logarithm == Cfinal);
    if (test == fReverseTest) {
        std::cout << colorRed << Fail(fReverseTest) << colorNormal << std::endl;
        return false;
    }

    std::cout << colorGreen << Pass(fReverseTest) << colorNormal << std::endl;
    zSuccessfulTests++;
    return true;
}

bool Test_arithConstraints(ArithmeticCircuit ac, CBigNum q, bool fReverseTest = false)
{
    zNumTests++;
    // Checking that the expressions in Equation (2) of the paper hold
    std::cout << "- Testing the Arithmetic Constraints (eq. 2)";
    if (fReverseTest)
        std::cout << colorMagenta << " with wrong assignment" << colorNormal;
    std::cout << "...";
    bool test = true;
    unsigned int last_index = 0;
    for(unsigned int i=0; i<4*ZKP_SERIALSIZE-2; i++) {
        if (ac.sumWiresDotWs(i) != ac.K[i] % q) {
            test = false;
            last_index = i;
            break;
        }
    }

    if (test == fReverseTest) {
        std::cout << colorRed << Fail(fReverseTest) << std::endl;
        std::cout << "Arithmetic Constraints Test failed at i=" << last_index << colorNormal << std::endl;
        return false;
    }

    std::cout << colorGreen << Pass(fReverseTest)  << colorNormal << std::endl;
    zSuccessfulTests++;
    return true;
}

bool arithmetic_circuit_tests()
{
    std::cout << colorBold << "*** arithmetic_circuit_tests ***" << std::endl;
    std::cout << "--------------------------------" << colorNormal << std::endl;

    bool finalResult = true;

    SelectParams(CBaseChainParams::MAIN);
    ZerocoinParams *ZCParams = Params().Zerocoin_Params();
    (void)ZCParams;

    CBigNum a = ZCParams->coinCommitmentGroup.g;
    CBigNum b = ZCParams->coinCommitmentGroup.h;
    CBigNum q = ZCParams->serialNumberSoKCommitmentGroup.groupOrder;

    // mint a coin
    PrivateCoin coin(ZCParams, CoinDenomination::ZQ_TEN, true);
    // get random Y
    CBigNum Y = CBigNum::randBignum(q);
    ArithmeticCircuit circuit(ZCParams);
    circuit.setWireValues(coin);
    circuit.setYPoly(Y);

    finalResult = finalResult & Test_multGates(circuit, q);
    finalResult = finalResult & Test_cfinalLog(circuit, q, a, b);
    finalResult = finalResult & Test_arithConstraints(circuit, q);

    // !TODO: rewrite this test case.
    // Checking that the expressions in Equation (3) of the paper hold
    // (circuit.sumWiresDotWPoly() == circuit.Kconst)

    // New circuit with random assignment
    ArithmeticCircuit newCircuit(circuit);
    for(unsigned int i=0; i<ZKP_M; i++) {
        random_vector_mod(newCircuit.A[i], q);
        random_vector_mod(newCircuit.B[i], q);
        for(unsigned int j=0; j<ZKP_N; j++)
            newCircuit.C[i][j] = newCircuit.A[i][j].mul_mod(newCircuit.B[i][j],q);
    }

    // If circuit correctly evaluates (a^serial)*(b^randomness) we have a problem
    finalResult = finalResult & Test_cfinalLog(newCircuit, q, a, b, true);

    // Checking that the expressions in Equation (2) of the paper does not hold
    finalResult = finalResult & Test_arithConstraints(newCircuit, q, true);

    // !TODO: rewrite this test case.
    // Checking that the expressions in Equation (3) of the does not paper hold

    std::cout << std::endl;

    return finalResult;
}

// ---------------------------------------------------------------------------------------------------
// Polynomial Commitment -----------------------------------------------------------------------------

// Evaluate tpolynomial at x
CBigNum eval_tpoly(CBN_vector tpoly, CBN_vector xPowersPos, CBN_vector xPowersNeg, CBigNum q)
{
    CBigNum sum = CBigNum(0);
    for(unsigned int i=0; i<=ZKP_NDASH*ZKP_M1DASH; i++)
        sum = ( sum + tpoly[i].mul_mod(xPowersNeg[ZKP_NDASH*ZKP_M1DASH-i],q) ) % q;
    for(unsigned int i=ZKP_NDASH*ZKP_M1DASH+1; i<=ZKP_NDASH*(ZKP_M1DASH+ZKP_M2DASH); i++)
        sum = ( sum + tpoly[i].mul_mod(xPowersPos[i-ZKP_NDASH*ZKP_M1DASH],q) ) % q;
    return sum;
}

bool Test_polyVerify1(PolynomialCommitment pc, CBigNum &val, bool fReverseTest = false)
{
    zNumTests++;
    // Poly-Verify: For honest prover, verifier should be satisfied
    std::cout << "- Testing PolyVerify";
    if (fReverseTest)
        std::cout << colorMagenta << " for dishonest prover" << colorNormal;
    std::cout << "...";
    // val = t(x) if proofs checks out
    bool test = (pc.Verify(val));
    if (test == fReverseTest) {
        std::cout << colorRed << Fail(fReverseTest) << colorNormal << std::endl;
        return false;
    }

    std::cout << colorGreen << Pass(fReverseTest)  << colorNormal << std::endl;
    zSuccessfulTests++;
    return true;
}

bool Test_polyVerify2(CBigNum val, CBN_vector tpoly,
                      CBN_vector xpos, CBN_vector xneg, CBigNum q)
{
    zNumTests++;
    // Poly-Verify: For honest prover, verifier is able to compute t(x)
    std::cout << "- Testing t(x) == dotProduct(tbar,xPowersPos)...";
    CBigNum tx = eval_tpoly(tpoly, xpos, xneg, q);
    if (val != tx) {
        std::cout << colorRed << Fail() << colorNormal << std::endl;
        return false;
    }

    std::cout << colorGreen << Pass()  << colorNormal << std::endl;
    zSuccessfulTests++;
    return true;
}

bool polynomial_commitment_tests()
{
    std::cout << colorBold << "*** polynomial_commitment_tests ***" << std::endl;
    std::cout << "-----------------------------------" << colorNormal << std::endl;

    bool finalResult = true;
    SelectParams(CBaseChainParams::MAIN);
    ZerocoinParams *ZCParams = Params().Zerocoin_Params();
    (void)ZCParams;

    CBigNum q = ZCParams->serialNumberSoKCommitmentGroup.groupOrder;
    CBigNum p = ZCParams->serialNumberSoKCommitmentGroup.modulus;

    // generate a random tpolynomial with 0 constant term
    CBN_vector tpoly(ZKP_NDASH*(ZKP_M1DASH+ZKP_M2DASH)+1);
    for(unsigned int i=0; i<tpoly.size(); i++)
        tpoly[i] = CBigNum::randBignum(q);
    tpoly[ZKP_M1DASH*ZKP_NDASH] = CBigNum(0);

    // generate a random evaluation point x in R and compute powers
    CBigNum x = CBigNum::randBignum(q);
    CBN_vector xPowersPositive(ZKP_M2DASH*ZKP_NDASH+1);
    CBN_vector xPowersNegative(ZKP_M1DASH*ZKP_NDASH+1);
    xPowersPositive[0] = xPowersNegative[0] = CBigNum(1);
    xPowersPositive[1] = x;
    xPowersNegative[1] = x.pow_mod(-1,q);
    for(unsigned int i=2; i<ZKP_M2DASH*ZKP_NDASH+1; i++)
        xPowersPositive[i] = x.pow_mod(i,q);
    for(unsigned int i=2; i<ZKP_M1DASH*ZKP_NDASH+1; i++)
        xPowersNegative[i] = x.pow_mod(-(int)i,q);

    // Poly-Commit and Poly-Evaluate
    PolynomialCommitment polyCommitment(ZCParams);
    polyCommitment.Commit(tpoly);
    polyCommitment.Eval(xPowersPositive, xPowersNegative);

    // Polynomial  evaluation
    CBigNum val;

    finalResult = finalResult & Test_polyVerify1(polyCommitment, val);
    finalResult = finalResult & Test_polyVerify2(val, tpoly, xPowersPositive, xPowersNegative, q);

    // Create copies of the polynomial commitment and mess things up
    PolynomialCommitment newPolyComm1(polyCommitment);
    PolynomialCommitment newPolyComm2(polyCommitment);
    PolynomialCommitment newPolyComm3(polyCommitment);
    random_vector_mod(newPolyComm1.tbar, q);
    random_vector_mod(newPolyComm2.Tf, q);
    random_vector_mod(newPolyComm3.Trho, q);

    // Poly-Verify: For dishonest prover, verifier should fail the test
    finalResult = finalResult & Test_polyVerify1(newPolyComm1, val, true);
    finalResult = finalResult & Test_polyVerify1(newPolyComm2, val, true);
    finalResult = finalResult & Test_polyVerify1(newPolyComm3, val, true);

    std::cout << std::endl;

    return finalResult;
}

// ---------------------------------------------------------------------------------------------------
// Inner Product Argument ----------------------------------------------------------------------------

// !TODO: Adapt to bulletproofs class
/*
BOOST_AUTO_TEST_CASE(inner_product_argument_tests)
{
    std::cout << "*** inner_product_argument_tests ***" << std::endl;
    std::cout << "------------------------------------" << std::endl;
    SelectParams(CBaseChainParams::MAIN);
    ZerocoinParams *ZCParams = Params().Zerocoin_Params(false);
    (void)ZCParams;
    CBigNum q = ZCParams->serialNumberSoKCommitmentGroup.groupOrder;
    CBigNum p = ZCParams->serialNumberSoKCommitmentGroup.modulus;
    // Get random y in Z_q and (N+PADS)-vectors
    CBigNum y = CBigNum::randBignum(q);
    CBN_vector a_sets(ZKP_N+ZKP_PADS);
    CBN_vector b_sets(ZKP_N+ZKP_PADS);
    random_vector_mod(a_sets, q);
    random_vector_mod(b_sets, q);
    // Inner-product PROVE
    InnerProductArgument innerProduct(ZCParams);
    innerProduct.Prove(y, a_sets, b_sets);
    // compute ck_inner sets
    pair<CBN_vector, CBN_vector> resultSets = innerProduct.ck_inner_gen(ZCParams, y);
    CBN_vector ck_inner_g = resultSets.first;
    CBN_vector ck_inner_h = resultSets.second;
    // Compute commitment A to a_sets under ck_inner_g
    CBigNum A = CBigNum(1);
    for(unsigned int i=0; i<a_sets.size(); i++)
        A = A.mul_mod(ck_inner_g[i].pow_mod(a_sets[i], p), p);
    // Compute commitment B to b_sets under ck_inner_h
    CBigNum B = CBigNum(1);
    for(unsigned int i=0; i<b_sets.size(); i++)
        B = B.mul_mod(ck_inner_h[i].pow_mod(b_sets[i], p), p);
    // Inner product z = <a,b>
    CBigNum z = dotProduct(a_sets, b_sets, q);
    // random_z != z
    CBigNum random_z = CBigNum::randBignum(q);
    while( random_z == dotProduct(a_sets, b_sets, q) ) random_z = CBigNum::randBignum(q);
    // innerProductVerify
    std::cout << "- Testing innerProductVerify..." << std::endl;
    bool res = innerProduct.Verify(ZCParams, y, A, B, z);
    BOOST_CHECK_MESSAGE(res,"InnerProduct:: Verification failed\n");
    // Inner product z != <a, b>  (z = 0, z = 1, z = random)
    std::cout << "- Testing innerProductVerify for dishonest prover..." << std::endl;
    BOOST_CHECK_MESSAGE( !innerProduct.Verify(ZCParams, y, A, B, CBigNum(0)),
            "InnerProduct:: Verification returned TRUE[1] for dishonest prover\n");
    BOOST_CHECK_MESSAGE( !innerProduct.Verify(ZCParams, y, A, B, CBigNum(1)),
            "InnerProduct:: Verification returned TRUE[2] for dishonest prover\n");
    BOOST_CHECK_MESSAGE( !innerProduct.Verify(ZCParams, y, A, B, random_z),
            "InnerProduct:: Verification returned TRUE[3] for dishonest prover\n");
    std::cout << std::endl;
}
*/

// ---------------------------------------------------------------------------------------------------
// Signature Of Knowledge ----------------------------------------------------------------------------

void printTime(int64_t start_time, int nProofs)
{
    int64_t total_time = GetTimeMillis() - start_time;
    int64_t nTimePer = nProofs ? total_time/nProofs : 0;
    std::string strPerProof = std::to_string(nTimePer) + " msec per proof";
    std::cout << colorCyan << "\t(" << total_time << " msec " << (nProofs ? strPerProof.c_str() : "") << ")"  << colorNormal << std::endl;
}

bool Test_batchVerify(std::vector<const SerialNumberSoKProof*> proofs, bool fReverseTest = false)
{
    zNumTests++;
    // verify the signature of the received SoKs
    std::cout << "- Verifying the Signatures of Knowledge";
    if (fReverseTest)
        std::cout << colorMagenta << " for dishonest prover" << colorNormal;
    std::cout << "...";

    if (SerialNumberSoKProof::BatchVerify(proofs) == fReverseTest) {
        std::cout << colorRed << Fail(fReverseTest) << colorNormal;
        return false;
    }

    std::cout << colorGreen << Pass(fReverseTest) << colorNormal;
    zSuccessfulTests++;
    return true;
}

bool Test_threadedBatchVerify(std::vector<SerialNumberSoKProof>* proofs, int nThreads, bool fReverseTest = false)
{
    zNumTests++;
    // verify the signature of the received SoKs
    std::cout << "- Threaded verification of the Signatures of Knowledge";
    if (fReverseTest)
        std::cout << colorMagenta << " for dishonest prover" << colorNormal;
    std::cout << "...";

    if (ThreadedBatchVerify(proofs, nThreads) == fReverseTest) {
        std::cout << colorRed << Fail(fReverseTest) << colorNormal;
        return false;
    }

    std::cout << colorGreen << Pass(fReverseTest) << colorNormal;
    zSuccessfulTests++;
    return true;
}

bool batch_signature_of_knowledge_tests(unsigned int start, unsigned int end, unsigned int step)
{
    if (end < start || step < 1) {
        std::cout << "wrong range for batch_signature_of_knowledge_tests";
        return false;
    }

    std::cout << colorBold <<  "*** batch_signature_of_knowledge_tests ***" << std::endl;
    std::cout << "------------------------------------------" << colorNormal <<  std::endl;
    std::cout << "starting size of the list: " << start << std::endl;
    std::cout << "ending size of the list: " << end << std::endl;
    std::cout << "step increment: " << step << std::endl;

    bool finalResult = true;
    SelectParams(CBaseChainParams::MAIN);
    ZerocoinParams *ZCParams = Params().Zerocoin_Params();
    (void)ZCParams;

    std::vector<uint256> msghashList;
    std::vector<PrivateCoin> coinList;
    std::vector<Commitment> commitmentList;
    std::vector<uint256> msghashList2;
    std::vector<PrivateCoin> coinList2;
    std::vector<SerialNumberSoK_small> sigList;
    std::vector<libzerocoin::SerialNumberSoKProof> vProofs_threaded;
    std::vector<Commitment> commitmentList2;

    for(unsigned int k=start; k<=end; k=k+step) {
        // create k random message hashes
        for(unsigned int i=0; i<step; i++) {
            CBigNum rbn = CBigNum::randBignum(256);
            msghashList.push_back(rbn.getuint256());
        }

        // mint k coins
        for(unsigned int i=0; i<step; i++) {
            PrivateCoin newCoin(ZCParams, CoinDenomination::ZQ_TEN, true);
            coinList.push_back(newCoin);
        }

        // commit to these coins
        for(unsigned int i=0; i<step; i++) {
            const CBigNum newCoin_value = coinList[i].getPublicCoin().getValue();
            Commitment commitment(&(ZCParams->serialNumberSoKCommitmentGroup), newCoin_value);
            commitmentList.push_back(commitment);
        }

        // WRONG (random) assignments
        // random messages
        CBigNum rbn = CBigNum::randBignum(256);
        msghashList2.push_back(rbn.getuint256());

        // random coins
        PrivateCoin newCoin(ZCParams, CoinDenomination::ZQ_TEN, true);
        coinList2.push_back(newCoin);

        // commit to these coins
        const CBigNum newCoin_value = coinList2[0].getPublicCoin().getValue();
        Commitment commitment(&(ZCParams->serialNumberSoKCommitmentGroup), newCoin_value);
        commitmentList2.push_back(commitment);

        std::cout << "- Creating array of " << k << " Signatures of Knowledge...\n";

        // create k signatures of knowledge

        int64_t start_time = GetTimeMillis();
        for(unsigned int i=0; i<step; i++) {
            SerialNumberSoK_small sigOfKnowledge(ZCParams, coinList[i], commitmentList[i], msghashList[i]);
            sigList.push_back(sigOfKnowledge);
        }

        printTime(start_time, 0);
        std::cout << "- Packing and serializing the Signatures..." << std::endl;

        // pack the signatures of knowledge (honest prover)
        for(unsigned int i=0; i<step; i++) {
            libzerocoin::SerialNumberSoKProof* p = new SerialNumberSoKProof(sigList[i], coinList[i].getSerialNumber(), commitmentList[i].getCommitmentValue(), msghashList[i]);
            vProofs_threaded.emplace_back(*p);
        }

        // pack the signatures of knowledge (wrong msghash)
        auto vProofs_threaded2 = vProofs_threaded;
        libzerocoin::SerialNumberSoKProof* p_badmsg = new SerialNumberSoKProof(sigList[0], coinList[0].getSerialNumber(), commitmentList[0].getCommitmentValue(), msghashList2[0]);
        vProofs_threaded2.emplace_back(*p_badmsg);

        // pack the signatures of knowledge (wrong commitment)
        //   Test with only one bad element inserted at the front (should be single thread failure)
        auto vProofs_threaded3 = vProofs_threaded;
        libzerocoin::SerialNumberSoKProof* p_badsig = new SerialNumberSoKProof(sigList[0], coinList[0].getSerialNumber(), commitmentList2[0].getCommitmentValue(), msghashList[0]);
        vProofs_threaded3[0] = *p_badsig;

        // pack the signatures of knowledge (wrong coin and commitment)
        //   Test with only one bad element inserted at the end (should be single thread failure)
        auto vProofs_threaded4 = vProofs_threaded;
        libzerocoin::SerialNumberSoKProof* p = new SerialNumberSoKProof(sigList[0], coinList[0].getSerialNumber(), commitmentList2[0].getCommitmentValue(), msghashList2[0]);
        vProofs_threaded4.emplace_back(*p);

        //Add a bad proof into a random spot
        auto vProofs_threaded5 = vProofs_threaded;
        vProofs_threaded5[k - 1] = *p_badmsg;

        start_time = GetTimeMillis();
        finalResult = finalResult & Test_threadedBatchVerify(&vProofs_threaded, 3);
        printTime(start_time, vProofs_threaded.size());

        start_time = GetTimeMillis();
        finalResult = finalResult & Test_threadedBatchVerify(&vProofs_threaded2, 3, true);
        printTime(start_time, vProofs_threaded2.size());

        start_time = GetTimeMillis();
        finalResult = finalResult & Test_threadedBatchVerify(&vProofs_threaded3, 3, true);
        printTime(start_time, vProofs_threaded3.size());

        start_time = GetTimeMillis();
        finalResult = finalResult & Test_threadedBatchVerify(&vProofs_threaded4, 3, true);
        printTime(start_time, vProofs_threaded4.size());

        start_time = GetTimeMillis();
        finalResult = finalResult & Test_threadedBatchVerify(&vProofs_threaded5, 3, true);
        printTime(start_time, vProofs_threaded5.size());
    }
    std::cout << std::endl;
    return finalResult;
}

BOOST_AUTO_TEST_SUITE(zerocoin_zkp_tests)

BOOST_AUTO_TEST_CASE(bulletproofs_tests)
{
    std::cout << std::endl;
    RandomInit();
    ECC_Start();
    BOOST_CHECK(parameters_tests());
    BOOST_CHECK(arithmetic_circuit_tests());
    BOOST_CHECK(polynomial_commitment_tests());
    BOOST_CHECK(batch_signature_of_knowledge_tests(8, 24, 8));
    std::cout << std::endl << zSuccessfulTests << " out of " << zNumTests << " tests passed." << std::endl << std::endl;
}
BOOST_AUTO_TEST_SUITE_END()


#include "Classes.h"
#include <Eigen/Dense>
#include <fstream>
#include <cstdlib>
using namespace Eigen;

PlaneProjection* Object3D::project3D(double projectionPlane[4]) {
    ///
    /// General Function to project the current 3D object onto the projection plane passed as parameter "projectionPlane"
    ///
    // cout << "Check"<<endl; // --------Remove
    // vector<Point> projectedVertices(len); --------Remove
    bool checkinput=check3dobject();
    vector<bool> isHidden;
    vector<int> isHiddenEdge;
    int len = vertices.size();
    // Before anything else, make all faces cyclic
    for(int i = 0; i < this->faces.size(); i++) {
        // cout << "Operating over " << faces[i];
        faces[i] = getCyclicFace(faces[i]);
        // cout << " Got this face: " << faces[i] << endl;
    }
    // cout << "Cyclic Faces: ";
    // for(int i = 0; i < this->faces.size(); i++) {
    //     cout << faces[i];
    // }
    // cout << endl;
    // Iteration to find all the projected vertices
    for (int i = 0; i < len; i++) {
        projectedVertices.push_back(vertices[i].projectPoint(projectionPlane));
    }
    cout << "vertices projected "<<endl;
    // Now, we check if any of the vertices is hidden or not
    for (int i = 0; i < len; i++) {
        // cout << vertices[i].x << vertices[i].y << vertices[i].z << endl; -------Remove
        int flag = 0;
        for (int j = 0; j < this->faces.size(); j++) {
            if(checkHiddenVertice(vertices[i],faces[j],projectionPlane,0))
            {
                flag = 1;
                break;
            }
        }
        if(flag==1)
            isHidden.push_back(true);
        else
            isHidden.push_back(false);
    }
    cout << "Vertices Done" << endl;
    // Next, we check for each edge, if it is hidden or not
    /* Note: If a part of an edge is hidden then the edge is replaced by the first part
       and the remaining part is added in the edges vector of the Object3D object */
    for (auto i = 0; i < this->edges.size(); i++) {
        int flag = 0;
        for (int j = 0; j < this->faces.size(); j++) {
            // cout << "Calling CHE " << j <<  endl;
            if(checkHiddenEdge(this->edges[i],faces[j],projectionPlane,i))
            {
                flag = 1;
                break;
            }
        }
        isHiddenEdge.push_back(flag);
        // cout << i << " " << edges[i] << ":" << flag << isHiddenEdge[i] << isHiddenEdge.size() << endl;
    }
    cout << "Edges Done" << endl;
    // The vectors isHidden and isHiddenEdge store whether or not a point/edge is hidden.
    // This can be used to generate an OrthoProjection object that can be returned from this function.
    PlaneProjection* projection = new PlaneProjection;
    for (int i = 0; i < 4; i++) {
        projection->normal[i] = projectionPlane[i];
    }
    for (auto i = 0; i < projectedVertices.size(); i++) {
        projection->vertices.push_back(projectedVertices[i]);
    }
    for (auto i = 0; i < this->edges.size(); i++) {
        Edge edge;
        edge.p1 = this->edges[i].p1.projectPoint(projectionPlane);
        edge.p2 = this->edges[i].p2.projectPoint(projectionPlane);
        if(isHiddenEdge[i])
            projection->hiddenEdges.push_back(edge);
        else
            projection->visibleEdges.push_back(edge);
    }
    cout << "project3D done" << endl;
    return projection;
}

int countIntersections(Vector3d avertice,Vector3d bvertice,Vector3d dvertice,Vector3d evertice,Vector3d linevector){
    ///
    /// Function to count intersections of the line originating from avertice in the direction of linevector,
    /// with the edges of a polygon, formed using bvertice, dvertice, evertice
    ///
    int retValue = 0;
    Vector3d zerovector(0,0,0);
    Vector3d abvector = bvertice - avertice;
    Vector3d acvector = linevector;
    Vector3d advector = dvertice - avertice;
    Vector3d aevector = evertice - avertice;
    Vector3d t1 = abvector.cross(acvector);
    Vector3d t2 = advector.cross(acvector);
    Vector3d t3 = abvector.cross(advector);
    Vector3d t6 = aevector.cross(acvector);
    double t4 = t1.dot(t2);
    double t5 = t1.dot(t3);
    double t7 = t6.dot(t2);
    double abdotac = abvector.dot(acvector);
    if(t1==zerovector || t2==zerovector){
        // Do Nothing
    }else if((t4<=0) && (t5>=0)){
        retValue = 1;
    }
    if(t1==zerovector && abdotac>0){
        if(t7<=0)
            retValue = 2;
        else
            retValue = 3;
    }
    return retValue;
}

Vector3d intersectLines(Point a,Point b,Vector3d c,Vector3d d) {
    Vector3d retVal;
    Vector3d av(a.x,a.y,a.z);
    Vector3d bv(b.x,b.y,b.z);
    Vector3d f = bv-av;
    Vector3d e = d-c;
    Vector3d g = c-av;
    Vector3d fcrossg = f.cross(g);
    Vector3d fcrosse = f.cross(e);
    double h = abs(fcrossg.norm());
    double k = abs(fcrosse.norm());
    if(fcrossg.dot(fcrosse)>0){
        retVal = c + (h/k)*e;
    }else{
        retVal = c - (h/k)*e;
    }
    return retVal;
}

bool Object3D::rayCasting(Point point, vector<Point> polygon) {
    ///
    /// Function that returns if the Point "point" lies within the polygon formed by the Face "polygon", passed as parameter.
    ///
    int numverticesinpolygon = polygon.size();
    // cout<< "num of points in polygon is "<< numverticesinpolygon<<endl;
    int flag = 0;
    for(int i = 0; i < numverticesinpolygon; i++) {
        if(point.x==polygon[i].x && point.y==polygon[i].y  && point.z==polygon[i].z ){
            flag = 1;
            break;
        }
    }
    if(flag==1)
        return true;
    Vector3d avertice;
    avertice << point.x, point.y, point.z;
    int numintersections = 0;
    Vector3d zerovector(0,0,0);
    Vector3d icap(1,0,0);
    Vector3d jcap(0,1,0);
    Vector3d startone;
    startone << polygon[0].x, polygon[0].y, polygon[0].z;
    Vector3d secondone;
    secondone << polygon[1].x, polygon[1].y, polygon[1].z;
    Vector3d thirdone;
    thirdone << polygon[2].x, polygon[2].y, polygon[2].z;
    Vector3d firstvector = secondone-startone;
    Vector3d secondvector = thirdone-secondone;
    Vector3d perpendicular = firstvector.cross(secondvector);
    Vector3d linevector = perpendicular.cross(icap);

    double checkzero = linevector.dot(linevector);
    if(checkzero==0){
        linevector = perpendicular.cross(jcap);
    }
    // Vector3d linevector(1,1,0);
    int i=0;
    for(auto it= polygon.begin();it!=polygon.end();it++){
        Point thisone = *it;
        Point nextone,prevone;
        if(i==numverticesinpolygon-1){
            prevone = *(it-1);
            nextone = polygon.front();
        }else if(i==0){
            prevone = polygon.back();
            nextone = *(it+1);
        }else{
            prevone = *(it-1);
            nextone = *(it+1);
        }
        //Point nextone = *(it + 1);
        Vector3d bvertice;
        bvertice << thisone.x, thisone.y, thisone.z;
        Vector3d dvertice;
        dvertice << nextone.x,nextone.y, nextone.z;
        Vector3d evertice;
        evertice << prevone.x, prevone.y, prevone.z;
        int ci = countIntersections(avertice,bvertice,dvertice,evertice,linevector);
        if(ci==0){
            // Do Nothing
        }else if(ci==1 || ci==2){
            numintersections = numintersections +1;
        }else if(ci==3){
            numintersections = numintersections + 2;
        }
        i++;
    }
    // cout<< "numintersections is "<< numintersections<<endl;
    if(numintersections%2==1){
        return true;
    }else{
        return false;
    }
}

bool Object3D::checkHiddenVertice(Point vertex, Face face, double plane[4], int predicate) {
    ///
    /// Function to check if the Point passed as parameter "vertex" is hidden by the face,
    /// which is passed in the parameter "face", the projection being taken on the plane "plane"
    ///
    bool retValue = false;
    Vector3d point1(vertices[face.vertices[0]].x,vertices[face.vertices[0]].y,vertices[face.vertices[0]].z);
    Vector3d point2(vertices[face.vertices[1]].x,vertices[face.vertices[1]].y,vertices[face.vertices[1]].z);
    Vector3d point3(vertices[face.vertices[2]].x,vertices[face.vertices[2]].y,vertices[face.vertices[2]].z);
    Vector3d dir1 = (point3-point1);
    Vector3d dir2 = (point2-point1);
    Vector3d normal = dir1.cross(dir2);
    double facePlane[4] = {normal.x(),normal.y(),normal.z(),normal.dot(point1)};
    vector<Point> faceProject;
    for (int i = 0; i < face.vertices.size(); i++) {
        faceProject.push_back(projectedVertices[face.vertices[i]]);
    }
    // cout << "Face Projected" << endl;
    Point projectedVertex = vertex.projectPoint(plane);
    if(rayCasting(projectedVertex,faceProject)) {
        if(predicate==0) {
            if(vertex.relativePosition(facePlane)*projectedVertex.relativePosition(facePlane) >= 0)
                retValue = false;
            else
                retValue = true;
        }else{
            if(vertex.relativePosition(facePlane)*projectedVertex.relativePosition(facePlane) > 0)
                retValue = false;
            else
                retValue = true;
        }
    }
    // cout << "retvalue is " << retValue << endl;
    return retValue;
}

bool Object3D::checkHiddenEdge(Edge edge, Face face, double plane[4], int index) {
    ///
    /// Function to evaluate if the Edge "edge", passed as parameter is hidden by the face,
    /// whose projection on the plane "plane" is passed as argument "face"
    ///
    cout << "In CHE" << endl;
    bool retValue = false;
    Point p1 = edge.p1;
    Point p2 = edge.p2;
    auto it1 = find_if(this->vertices.begin(), this->vertices.end(),
        [p1](Point p) -> bool {return (p.x==p1.x && p.y==p1.y && p.z==p1.z);});
    auto it2 = find_if(this->vertices.begin(), this->vertices.end(),
        [p2](Point p) -> bool {return (p.x==p2.x && p.y==p2.y && p.z==p2.z);});
    int index1,index2;
    if(it1 != vertices.end())
        index1 = distance(vertices.begin(),it1);
    if(it2 != vertices.end())
        index2 = distance(vertices.begin(),it2);
    auto itin1 = find(face.vertices.begin(),face.vertices.end(),index1);
    auto itin2 = find(face.vertices.begin(),face.vertices.end(),index2);
    if (itin1 != face.vertices.end() && itin2 != face.vertices.end())
        return false;
    // If both endpoints are on the face, it isn't hidden.
    Point projectp1 = edge.p1.projectPoint(plane);
    Point projectp2 = edge.p2.projectPoint(plane);
    vector<Point> faceProject;
    for (int i = 0; i < face.vertices.size(); i++) {
        faceProject.push_back(projectedVertices[face.vertices[i]]);
    }
    if(rayCasting(projectp1,faceProject)) {
        if(checkHiddenVertice(edge.p1,face,plane,1)) {
            // Evaluate the point of intersection of edge with the polygon faceProject
            // cout << "First point is hidden."; // -------Remove
            Vector3d avertice(projectp1.x,projectp1.y,projectp1.z);
            Vector3d endvertice(projectp2.x,projectp2.y,projectp2.z);
            Vector3d linevector(projectp2.x-projectp1.x,projectp2.y-projectp1.y,projectp2.z-projectp1.z);
            vector<Vector3d> intersections;
            vector<Point> polygon;
            for(int i = 0; i < face.vertices.size(); i++) {
                polygon.push_back(this->projectedVertices[face.vertices[i]]);
                // pointIndex.push_back(face.vertices[i]);
            }
            int i=0, numverticesinpolygon=polygon.size();
            for(auto it= polygon.begin();it!=polygon.end();it++){
                Point thisone = *it;
                Point nextone,prevone;
                if(i==numverticesinpolygon-1){
                    prevone = *(it-1);
                    nextone = polygon.front();
                }else if(i==0){
                    prevone = polygon.back();
                    nextone = *(it+1);
                }else{
                    prevone = *(it-1);
                    nextone = *(it+1);
                }
                //Point nextone = *(it + 1);
                Vector3d bvertice;
                bvertice << thisone.x, thisone.y, thisone.z;
                Vector3d dvertice;
                dvertice << nextone.x,nextone.y, nextone.z;
                Vector3d evertice;
                evertice << prevone.x, prevone.y, prevone.z;
                int ci = countIntersections(avertice,bvertice,dvertice,evertice,linevector);
                if(ci==2 || ci==3){
                    intersections.push_back(bvertice);
                }else if(ci==1){
                    // Intersects (bvertice,dvertice) in the middle somewhere
                    Vector3d intersection = intersectLines(edge.p1,edge.p2,bvertice,dvertice);
                    cout << "Between " << edge << " and " << bvertice << ", " << dvertice << endl; // ---------Remove
                    intersections.push_back(intersection);
                }
                i++;
            }
            // All intersections of the edge have been taken with the face, and are stored in intersections
            Vector3d closestIntersection;
            if(intersections.size()==1){
                closestIntersection = intersections[0];
            }else if(intersections.size()!=0){
                closestIntersection = *min_element(intersections.begin(),intersections.end(),
                [avertice] (Vector3d v1, Vector3d v2) -> bool {return (avertice-v1).norm()<(avertice-v2).norm();});
            }
            if(intersections.size()!=0) {
                // cout << "Intersection found."; -------Remove
                if(closestIntersection.x()==projectp2.x && closestIntersection.y()==projectp2.y && closestIntersection.z()==projectp2.z){
                    if(checkHiddenVertice(edge.p2,face,plane,1))
                        retValue = true;
                    else
                        retValue = false;
                }else{
                    // The vector closestIntersection holds the closest point of intersection, from p1
                    // This point is on the projected plane, and we must find the corresponding point in 3D
                    Vector3d thirdPoint;
                    double d1 = (closestIntersection-avertice).norm();
                    double d2 = (endvertice-closestIntersection).norm();
                    Vector3d p1(edge.p1.x,edge.p1.y,edge.p1.z);
                    Vector3d p2(edge.p2.x,edge.p2.y,edge.p2.z);
                    thirdPoint = (d1*p2 + d2*p1)/(d1+d2);
                    // cout << "Inserting new element " << thirdPoint << endl; --------Remove
                    Point newPoint,newPointProjected;
                    newPoint.setCoordinates(thirdPoint.x(),thirdPoint.y(),thirdPoint.z());
                    newPointProjected.setCoordinates(closestIntersection.x(),closestIntersection.y(),closestIntersection.z());
                    Edge segment;
                    segment.p1.setCoordinates(thirdPoint.x(),thirdPoint.y(),thirdPoint.z());
                    segment.p2.setCoordinates(edge.p2.x,edge.p2.y,edge.p2.z);
                    edge.p2.setCoordinates(thirdPoint.x(),thirdPoint.y(),thirdPoint.z());
                    this->edges.insert(this->edges.begin()+index+1,segment);
                    this->vertices.push_back(newPoint);
                    this->projectedVertices.push_back(newPointProjected);
                    retValue = true;
                }
            }else{
                retValue = checkHiddenVertice(edge.p2,face,plane,1);
            }
        }else{
            if(checkHiddenVertice(edge.p2,face,plane,0)) {
                retValue = true;
            }else{
                retValue = false;
            }
        }
    }
    // if(retValue)
    //     cout << edge << " is hidden by the face formed by: " << face << endl;
    // else
    //     cout << edge << " is not hidden by the face formed by: " << face << endl; // -------Remove
    return retValue;
}

Face Object3D::getCyclicFace(Face face) {
    ///
    /// Function that reeturns the face with the vertices, re-arranged so that a cyclic order is maintained
    ///
    vector<int> pointIndex;
    Face newFace;
    for(int i = 0; i < face.vertices.size(); i++) {
        // polygon.push_back(this->projectedVertices[face.vertices[i]]);
        pointIndex.push_back(face.vertices[i]);
    }
    int i=0, numverticesinpolygon=face.vertices.size();
    bool done = false;
    Vector3d edge1(vertices[face.vertices[1]].x-vertices[face.vertices[0]].x,vertices[face.vertices[1]].y-vertices[face.vertices[0]].y,vertices[face.vertices[1]].z-vertices[face.vertices[0]].z);
    Vector3d edge2(vertices[face.vertices[2]].x-vertices[face.vertices[1]].x,vertices[face.vertices[2]].y-vertices[face.vertices[1]].y,vertices[face.vertices[2]].z-vertices[face.vertices[1]].z);
    Vector3d normalToFace = edge1.cross(edge2);
    for(int i = 0; i < numverticesinpolygon; i++) {
        if(i==0){
            newFace.vertices.push_back(pointIndex[0]);
        }else if(i==1){
            int index = newFace.vertices[0];
            Point p = this->vertices[index];
            auto it = find_if(this->edges.begin(), this->edges.end(),
                [p,normalToFace](Edge e) -> bool {
                    bool retVal = false;
                    if ((p.x==e.p1.x && p.y==e.p1.y && p.z==e.p1.z) || (p.x==e.p2.x && p.y==e.p2.y && p.z==e.p2.z)){
                        Vector3d edge(e.p1.x-e.p2.x,e.p1.y-e.p2.y,e.p1.z-e.p2.z);
                        if(edge.dot(normalToFace)==0){
                            retVal = true;
                        }
                    }
                    return retVal;});
            Point neighbor;
            Edge e = *it;
            if(p.x==e.p1.x && p.y==e.p1.y && p.z==e.p1.z)
                neighbor = e.p2;
            else
                neighbor = e.p1;
            auto it1 = find_if(this->vertices.begin(), this->vertices.end(),
                [neighbor](Point p) -> bool {return (p.x==neighbor.x && p.y==neighbor.y && p.z==neighbor.z);});
            int index1;
            if(it1 != vertices.end())
                index1 = distance(vertices.begin(),it1);
            newFace.vertices.push_back(index1);
        }else{
            int index = newFace.vertices[i-1];
            int prevIndex = newFace.vertices[i-2];
            Point p = this->vertices[index];
            Point prevp = this->vertices[prevIndex];
            auto it = find_if(this->edges.begin(), this->edges.end(),
                [p,prevp,normalToFace](Edge e) -> bool {
                    bool retVal = false;
                    if ((p.x==e.p1.x && p.y==e.p1.y && p.z==e.p1.z) || (p.x==e.p2.x && p.y==e.p2.y && p.z==e.p2.z)){
                        Vector3d edge(e.p1.x-e.p2.x,e.p1.y-e.p2.y,e.p1.z-e.p2.z);
                        if(edge.dot(normalToFace)==0){
                            if((p.x==e.p1.x && p.y==e.p1.y && p.z==e.p1.z) && !(prevp.x==e.p2.x && prevp.y==e.p2.y && prevp.z==e.p2.z)){
                                retVal = true;
                            }else if((p.x==e.p2.x && p.y==e.p2.y && p.z==e.p2.z) && !(prevp.x==e.p1.x && prevp.y==e.p1.y && prevp.z==e.p1.z)){
                                retVal = true;
                            }
                        }
                    }
                    return retVal;});
            Point neighbor;
            Edge e = *it;
            if(p.x==e.p1.x && p.y==e.p1.y && p.z==e.p1.z)
                neighbor = e.p2;
            else
                neighbor = e.p1;
            auto it1 = find_if(this->vertices.begin(), this->vertices.end(),
                [neighbor](Point p) -> bool {return (p.x==neighbor.x && p.y==neighbor.y && p.z==neighbor.z);});
            int index1;
            if(it1 != vertices.end())
                index1 = distance(vertices.begin(),it1);
            newFace.vertices.push_back(index1);
        }
    }
    return newFace;
}

bool Object3D::check3dobject(){
    ///
    /// Function to check the validity of input file, by rendering the Object in OpenScad
    ///
    try{
        ofstream scadfile ("object.scad");
        if(scadfile.is_open()){
            // scadfile<< "cube([2,3,9]);";
            scadfile <<"ObjectPoints = [\n";
            int numpoints = vertices.size();
            for(int i=0;i<numpoints;i++){
                Point temp = vertices[i];
                scadfile<<"    [ "<<temp.x<<", "<<temp.y<<", "<<temp.z<<" ]";
                if(i!=numpoints-1){
                    scadfile<<",";
                }else{
                    scadfile<<"];";
                }
                scadfile<<  "  //"<<i<<"\n";
            }
            scadfile <<"\n";
            scadfile << "ObjectFaces = [\n";
            int numfaces = faces.size();
            for(int i=0;i<numfaces;i++){
                scadfile<<"[";
                for(int j=0;j<faces[i].vertices.size();j++){
                    scadfile<<faces[i].vertices[j];
                    if(j!=faces[i].vertices.size()-1){
                        scadfile<<",";
                    }
                }
                scadfile<< "]";
                if(i!=numfaces-1){
                    scadfile<<",\n";
                }else{
                    scadfile<<"];\n";
                }
            }
            scadfile<< "polyhedron( ObjectPoints, ObjectFaces );";
            scadfile.close();
            system("openscad -o object.stl object.scad");
            ofstream renderfile ("render.scad");
            if(renderfile.is_open()){
                renderfile<< "render(){import(\"object.stl\");}";
                renderfile.close();
                system("openscad render.scad");
            }else{
                cout<< "unable to render the stl";
            }
        }else{
            cout<< "unable to open file"<<endl;
        }
        return true;
    }catch(...){
        return false;
    }
}


#include <three-dim-util/camera.hpp>
#include <Eigen/Geometry>

namespace threedimutil
{
    using namespace Eigen;

    Camera::Camera() :
    m_position(- 2.0, 0.0, 3.0),
    m_target(0.0, 0.0, 0.0),
    m_up(0.0, 1.0, 0.0),
    m_mode(Mode::None)
    {
    }

    void Camera::RotateAroundTarget(double theta_in_radian)
    {
        // Horizontal rotation
        const Matrix3d rot_h = AngleAxisd(theta_in_radian, m_up).matrix();

        // Update the camera position
        m_position = m_target + rot_h * (m_position - m_target);
    }

    void Camera::BeginTrackball(int x, int y, Mode mode)
    {
        m_mode = mode;
        m_prev_position = Vector2i(x, y);
    }

    void Camera::MoveTrackball(int x, int y)
    {
        constexpr double scale_x = 2000.0;
        constexpr double scale_y = 2000.0;

        switch (m_mode)
        {
            case Mode::None:
                break;
            case Mode::Pan:
            {
                const Vector3d eye      = m_position - m_target;
                const Vector3d left_dir = eye.cross(m_up);
                const double   len      = eye.norm();
                const double   diff_x   = static_cast<double>(x - m_prev_position(0)) / scale_x;
                const double   diff_y   = static_cast<double>(y - m_prev_position(1)) / scale_y;
                const Vector3d trans_x  = len * diff_x * left_dir.normalized();
                const Vector3d trans_y  = len * diff_y * m_up.normalized();
                m_position += trans_x;
                m_position += trans_y;
                m_target   += trans_x;
                m_target   += trans_y;
                break;
            }
            case Mode::Rotate:
            {
                const double theta_x = (2.0 * M_PI * static_cast<double>(x - m_prev_position(0))) / scale_x;
                const double theta_y = (2.0 * M_PI * static_cast<double>(y - m_prev_position(1))) / scale_y;

                // Horizontal rotation
                const Matrix3d rot_h = AngleAxisd(- theta_x, m_up).matrix();
                Vector3d eye = m_position - m_target;

                // Vertical rotation
                const Vector3d left_dir = eye.cross(m_up).normalized();
                const Matrix3d rot_v    = AngleAxisd(theta_y, left_dir).matrix();

                const double test = left_dir.dot((rot_v * eye).cross(m_up));
                if (test > 0.0) {
                    eye = rot_v * rot_h * eye;
                }

                m_position = m_target + eye;
                break;
            }
            case Mode::Zoom:
            {
                constexpr double scale = 0.5;

                const double   speed   = static_cast<double>(y - m_prev_position(1)) / scale_y;
                const Vector3d eye_ray = speed * (m_target - m_position);
                m_position += scale * eye_ray;
                break;
            }
            default:
                break;
        }

        m_prev_position = Vector2i(x, y);
    }

    void Camera::EndTrackball()
    {
        m_mode = Mode::None;
    }
}

Eigen::Matrix4d threedimutil::Camera::GetLookAtMatrix() const
{
    Eigen::Matrix4d mat = Eigen::Matrix4d::Identity();

    const Eigen::Vector3d forward = (m_target - m_position).normalized();
    const Eigen::Vector3d side    = forward.cross(m_up).normalized();
    const Eigen::Vector3d up      = side.cross(forward);

    mat.block<1, 3>(0, 0) = side.transpose();
    mat.block<1, 3>(1, 0) = up.transpose();
    mat.block<1, 3>(2, 0) = - forward.transpose();
    mat(0, 3) = - side.dot(m_position);
    mat(1, 3) = - up.dot(m_position);
    mat(2, 3) = forward.dot(m_position);

    return mat;
}


// copyright (c) 2012-2014 the dzcoin core developers
// distributed under the mit software license, see the accompanying
// file copying or http://www.opensource.org/licenses/mit-license.php.

#include "bignum.h"
#include "script/script.h"
#include "test/test_dzcoin.h"

#include <boost/test/unit_test.hpp>
#include <limits.h>
#include <stdint.h>

boost_fixture_test_suite(scriptnum_tests, basictestingsetup)

static const int64_t values[] = \
{ 0, 1, char_min, char_max, uchar_max, shrt_min, ushrt_max, int_min, int_max, uint_max, long_min, long_max };
static const int64_t offsets[] = { 1, 0x79, 0x80, 0x81, 0xff, 0x7fff, 0x8000, 0xffff, 0x10000};

static bool verify(const cbignum& bignum, const cscriptnum& scriptnum)
{
    return bignum.getvch() == scriptnum.getvch() && bignum.getint() == scriptnum.getint();
}

static void checkcreatevch(const int64_t& num)
{
    cbignum bignum(num);
    cscriptnum scriptnum(num);
    boost_check(verify(bignum, scriptnum));

    cbignum bignum2(bignum.getvch());
    cscriptnum scriptnum2(scriptnum.getvch(), false);
    boost_check(verify(bignum2, scriptnum2));

    cbignum bignum3(scriptnum2.getvch());
    cscriptnum scriptnum3(bignum2.getvch(), false);
    boost_check(verify(bignum3, scriptnum3));
}

static void checkcreateint(const int64_t& num)
{
    cbignum bignum(num);
    cscriptnum scriptnum(num);
    boost_check(verify(bignum, scriptnum));
    boost_check(verify(bignum.getint(), cscriptnum(scriptnum.getint())));
    boost_check(verify(scriptnum.getint(), cscriptnum(bignum.getint())));
    boost_check(verify(cbignum(scriptnum.getint()).getint(), cscriptnum(cscriptnum(bignum.getint()).getint())));
}

static void checkadd(const int64_t& num1, const int64_t& num2)
{
    const cbignum bignum1(num1);
    const cbignum bignum2(num2);
    const cscriptnum scriptnum1(num1);
    const cscriptnum scriptnum2(num2);
    cbignum bignum3(num1);
    cbignum bignum4(num1);
    cscriptnum scriptnum3(num1);
    cscriptnum scriptnum4(num1);

    // int64_t overflow is undefined.
    bool invalid = (((num2 > 0) && (num1 > (std::numeric_limits<int64_t>::max() - num2))) ||
                    ((num2 < 0) && (num1 < (std::numeric_limits<int64_t>::min() - num2))));
    if (!invalid)
    {
        boost_check(verify(bignum1 + bignum2, scriptnum1 + scriptnum2));
        boost_check(verify(bignum1 + bignum2, scriptnum1 + num2));
        boost_check(verify(bignum1 + bignum2, scriptnum2 + num1));
    }
}

static void checknegate(const int64_t& num)
{
    const cbignum bignum(num);
    const cscriptnum scriptnum(num);

    // -int64_min is undefined
    if (num != std::numeric_limits<int64_t>::min())
        boost_check(verify(-bignum, -scriptnum));
}

static void checksubtract(const int64_t& num1, const int64_t& num2)
{
    const cbignum bignum1(num1);
    const cbignum bignum2(num2);
    const cscriptnum scriptnum1(num1);
    const cscriptnum scriptnum2(num2);
    bool invalid = false;

    // int64_t overflow is undefined.
    invalid = ((num2 > 0 && num1 < std::numeric_limits<int64_t>::min() + num2) ||
               (num2 < 0 && num1 > std::numeric_limits<int64_t>::max() + num2));
    if (!invalid)
    {
        boost_check(verify(bignum1 - bignum2, scriptnum1 - scriptnum2));
        boost_check(verify(bignum1 - bignum2, scriptnum1 - num2));
    }

    invalid = ((num1 > 0 && num2 < std::numeric_limits<int64_t>::min() + num1) ||
               (num1 < 0 && num2 > std::numeric_limits<int64_t>::max() + num1));
    if (!invalid)
    {
        boost_check(verify(bignum2 - bignum1, scriptnum2 - scriptnum1));
        boost_check(verify(bignum2 - bignum1, scriptnum2 - num1));
    }
}

static void checkcompare(const int64_t& num1, const int64_t& num2)
{
    const cbignum bignum1(num1);
    const cbignum bignum2(num2);
    const cscriptnum scriptnum1(num1);
    const cscriptnum scriptnum2(num2);

    boost_check((bignum1 == bignum1) == (scriptnum1 == scriptnum1));
    boost_check((bignum1 != bignum1) ==  (scriptnum1 != scriptnum1));
    boost_check((bignum1 < bignum1) ==  (scriptnum1 < scriptnum1));
    boost_check((bignum1 > bignum1) ==  (scriptnum1 > scriptnum1));
    boost_check((bignum1 >= bignum1) ==  (scriptnum1 >= scriptnum1));
    boost_check((bignum1 <= bignum1) ==  (scriptnum1 <= scriptnum1));

    boost_check((bignum1 == bignum1) == (scriptnum1 == num1));
    boost_check((bignum1 != bignum1) ==  (scriptnum1 != num1));
    boost_check((bignum1 < bignum1) ==  (scriptnum1 < num1));
    boost_check((bignum1 > bignum1) ==  (scriptnum1 > num1));
    boost_check((bignum1 >= bignum1) ==  (scriptnum1 >= num1));
    boost_check((bignum1 <= bignum1) ==  (scriptnum1 <= num1));

    boost_check((bignum1 == bignum2) ==  (scriptnum1 == scriptnum2));
    boost_check((bignum1 != bignum2) ==  (scriptnum1 != scriptnum2));
    boost_check((bignum1 < bignum2) ==  (scriptnum1 < scriptnum2));
    boost_check((bignum1 > bignum2) ==  (scriptnum1 > scriptnum2));
    boost_check((bignum1 >= bignum2) ==  (scriptnum1 >= scriptnum2));
    boost_check((bignum1 <= bignum2) ==  (scriptnum1 <= scriptnum2));

    boost_check((bignum1 == bignum2) ==  (scriptnum1 == num2));
    boost_check((bignum1 != bignum2) ==  (scriptnum1 != num2));
    boost_check((bignum1 < bignum2) ==  (scriptnum1 < num2));
    boost_check((bignum1 > bignum2) ==  (scriptnum1 > num2));
    boost_check((bignum1 >= bignum2) ==  (scriptnum1 >= num2));
    boost_check((bignum1 <= bignum2) ==  (scriptnum1 <= num2));
}

static void runcreate(const int64_t& num)
{
    checkcreateint(num);
    cscriptnum scriptnum(num);
    if (scriptnum.getvch().size() <= cscriptnum::nmaxnumsize)
        checkcreatevch(num);
    else
    {
        boost_check_throw (checkcreatevch(num), scriptnum_error);
    }
}

static void runoperators(const int64_t& num1, const int64_t& num2)
{
    checkadd(num1, num2);
    checksubtract(num1, num2);
    checknegate(num1);
    checkcompare(num1, num2);
}

boost_auto_test_case(creation)
{
    for(size_t i = 0; i < sizeof(values) / sizeof(values[0]); ++i)
    {
        for(size_t j = 0; j < sizeof(offsets) / sizeof(offsets[0]); ++j)
        {
            runcreate(values[i]);
            runcreate(values[i] + offsets[j]);
            runcreate(values[i] - offsets[j]);
        }
    }
}

boost_auto_test_case(operators)
{
    for(size_t i = 0; i < sizeof(values) / sizeof(values[0]); ++i)
    {
        for(size_t j = 0; j < sizeof(offsets) / sizeof(offsets[0]); ++j)
        {
            runoperators(values[i], values[i]);
            runoperators(values[i], -values[i]);
            runoperators(values[i], values[j]);
            runoperators(values[i], -values[j]);
            runoperators(values[i] + values[j], values[j]);
            runoperators(values[i] + values[j], -values[j]);
            runoperators(values[i] - values[j], values[j]);
            runoperators(values[i] - values[j], -values[j]);
            runoperators(values[i] + values[j], values[i] + values[j]);
            runoperators(values[i] + values[j], values[i] - values[j]);
            runoperators(values[i] - values[j], values[i] + values[j]);
            runoperators(values[i] - values[j], values[i] - values[j]);
        }
    }
}

boost_auto_test_suite_end()


//==================================================================================================
/*!
  @file

  @copyright 2016 NumScale SAS

  Distributed under the Boost Software License, Version 1.0.
  (See accompanying file LICENSE.md or copy at http://boost.org/LICENSE_1_0.txt)
*/
//==================================================================================================
#ifndef BOOST_SIMD_CONSTANT_MLOGTWO2NMB_HPP_INCLUDED
#define BOOST_SIMD_CONSTANT_MLOGTWO2NMB_HPP_INCLUDED

#if defined(DOXYGEN_ONLY)
namespace boost { namespace simd
{
  /*!

    @ingroup group-constant

    Generates constant Mlogtwo2nmb.

    @par Header <boost/simd/constant/mlogtwo2nmb.hpp>

    @par Semantic:

    @code
    T r = Mlogtwo2nmb<T>();
    @endcode

    is similar to:

    @code
      r =  -log(exp2(T(Nbmantissabits<T>())));
    @endcode

**/
  template<typename T> T Mlogtwo2nmb();

  namespace functional
  {
    /*!
      @ingroup group-callable-constant

      Generates constant Mlogtwo2nmb.

      Generate the  constant mlogtwo2nmb.

      @return The Mlogtwo2nmb constant for the proper type
    **/
    const boost::dispatch::functor<tag::mlogtwo2nmb_> mlogtwo2nmb = {};
  }
} }
#endif

#include <boost/simd/constant/scalar/mlogtwo2nmb.hpp>
#include <boost/simd/constant/simd/mlogtwo2nmb.hpp>

#endif