#include <iostream>
#include <string>
#include <math.h>
#include <cstdlib>
#include <stdbool.h>
#include <vector>
//#include <boost/thread/thread.hpp>
#include "RtMidi.h"
#include <unistd.h>
#include <thread>
#include <time.h>
//#include <boost/system/error_code.hpp>
////////////////////////////////////////////////////////////////////////////////
// MAC: Comment in srandomdev(); comment out rand() (one occurrence)
// LINUX: Comment out srandomdev(); comment in ran()
///////////////////////////////////////
// TO RUN WITH C++11
// -Comment out all boost:: lines, commenting in the std:: version next to them.
// -Comment out boost in #include statements, and comment in #include <thread>

// TO RUN WITHOUT C++11
// -Install boost thread and system libraries
// -Comment in all places using boost::, while commenting out the adjacent std:: versions.
// -Comment in the two boost #include lines, and comment out the #include <thread> line.
///////////////////////////////////////////////////////////////////////////////

#define NOTEON 144
#define PGMCH 192
#define CH1 0
#define CH2 1
#define CH3 2
#define CH4 3
#define CH5 4
#define CH6 5
#define CH7 6
#define CH8 7
#define CH9 8
#define CHDr 9
#define CH11 10
#define CH12 11
#define CH13 12
#define CH14 13
#define CH15 14
#define CH16 15

// Platform-dependent sleep routines.
#if defined(__WINDOWS_MM__)
#include <windows.h>
#define SLEEP( milliseconds ) Sleep( (DWORD) milliseconds )
#else // Unix variants
#include <unistd.h>
#define SLEEP( milliseconds ) usleep( (unsigned long) (milliseconds * 1000.0) )
#endif
bool chooseMidiPort( RtMidiOut *rtmidi );
void one();
void two();

std::vector<unsigned int> factors;
std::vector<unsigned int> sorted_factors;
std::vector<unsigned int> dupes_out;
std::vector<unsigned char> low;
std::vector<unsigned char> med;
std::vector<unsigned char> high;
std::vector<unsigned char> low_off;
std::vector<unsigned char> med_off;
std::vector<unsigned char> high_off;

unsigned int chords[3][7];

RtMidiOut *midiout = 0;

//int * keys = [0, 7];

unsigned int this_key = 21;
unsigned int this_chord = 0;

void randomize(){
    //srandomdev();
    srand (time(NULL));
    int number = random() % 100;
    //std::cout<<"Random number: "<<number<<"\n";
    int tenth = number/10;
    if(tenth<2){
        this_chord = 0;
    }
    else if(tenth==2 || tenth==3){
        this_chord = 1;
    }
    else if(tenth==4){
        this_chord = 2;
    }
    else if(tenth==5){
        this_chord = 3;
    }
    else if(tenth==6){
        //this_chord = 4;
        this_chord = 6;
    }
    else if(tenth==7 || tenth==8){
        this_chord = 5;
    }
    else{
        this_chord = 6;
    }
    //std::cout<<"This chord: "<<this_chord<<"\n";
    low[1] = this_key+chords[0][this_chord];
    med[1] = this_key+chords[1][this_chord];
    high[1] = this_key+chords[2][this_chord];
    low_off[1] = this_key+chords[0][this_chord];
    med_off[1] = this_key+chords[1][this_chord];
    high_off[1] = this_key+chords[2][this_chord];

}
void one(){//channel 0 in 0-index, 1 in 1-index
    //for A: use 33 or 45
    int iterations = 0;
    std::vector<unsigned char> pgm_ch;
    pgm_ch.push_back(192);
    pgm_ch.push_back(35);
    midiout->sendMessage( &pgm_ch );
    pgm_ch.clear();
    //std::cout<<"in one\n";

    //SLEEP(2400);
    while(iterations<23){
        //std::cout<<"bass iterations: "<<iterations<<"\n";
        randomize();
        midiout->sendMessage( &low );
        SLEEP(380);
        midiout->sendMessage( &low_off );
        midiout->sendMessage( &low );
        SLEEP(200);
        midiout->sendMessage( &low_off );
        midiout->sendMessage( &low );
        SLEEP(200);
        midiout->sendMessage( &low_off );
        midiout->sendMessage( &high );
        SLEEP(380);
        midiout->sendMessage( &high_off );
        midiout->sendMessage( &low );
        midiout->sendMessage( &med );
        SLEEP(380);
        midiout->sendMessage( &low_off );
        midiout->sendMessage( &med_off );
        midiout->sendMessage( &high );
        SLEEP(200);
        midiout->sendMessage( &high_off );
        midiout->sendMessage( &med );
        SLEEP(200);
        midiout->sendMessage( &med_off );
        midiout->sendMessage( &high );
        SLEEP(380);
        midiout->sendMessage( &high_off );

        midiout->sendMessage( &high );
        SLEEP(380);
        midiout->sendMessage( &high_off );

        midiout->sendMessage( &high );
        SLEEP(200);
        midiout->sendMessage( &high_off );

        midiout->sendMessage( &med );
        SLEEP(200);
        midiout->sendMessage( &med_off );

        midiout->sendMessage( &low );
        SLEEP(200);
        midiout->sendMessage( &low_off );

        midiout->sendMessage( &low );
        SLEEP(200);
        midiout->sendMessage( &low_off );

        midiout->sendMessage( &high );
        SLEEP(380);
        midiout->sendMessage( &high_off );

        iterations++;
    }
    low[1] = this_key+12;
    midiout->sendMessage( &low );
    SLEEP(400);
    midiout->sendMessage( &low_off );
    midiout->sendMessage( &low );
    SLEEP(400);
    midiout->sendMessage( &low_off );
    midiout->sendMessage( &low );
    SLEEP(400);
    midiout->sendMessage( &low_off );
    midiout->sendMessage( &low );
    SLEEP(400);
    midiout->sendMessage( &low_off );
    midiout->sendMessage( &low );
    SLEEP(1600);
    midiout->sendMessage( &low_off );
    exit(1);

}
void two(){//channel 1 in 0-index, 2 in 1-index
    std::vector<unsigned char> pgm_ch;
    pgm_ch.push_back(193);
    pgm_ch.push_back(11);
    midiout->sendMessage( &pgm_ch );
    pgm_ch.clear();
    int iterations = 0;
    std::vector<unsigned char> send;
    send.push_back(145);
    send.push_back(36+this_key);
    send.push_back(100);
    SLEEP(2*4800);
    while(iterations<6){
        //std::cout<<"iterations for two: "<<iterations<<"\n";

        midiout->sendMessage( &send );
        SLEEP(2000);
        send[2] = 0;
        midiout->sendMessage( &send );
        send[2] = 100;
        send[1] = 43+this_key;
        midiout->sendMessage( &send );
        SLEEP(400);
        send[2] = 0;
        midiout->sendMessage( &send );
        send[2] = 100;
        send[1] = 34+this_key;
        midiout->sendMessage( &send );
        send[1] = 41+this_key;
        SLEEP(50);
        midiout->sendMessage( &send );
        send[1] = 38+this_key;
        SLEEP(50);
        midiout->sendMessage( &send );
        SLEEP(7100);//2300+4800
        send[2] = 0;
        midiout->sendMessage( &send );
        send[1] = 41+this_key;
        midiout->sendMessage( &send );
        send[1] = 38+this_key;
        midiout->sendMessage( &send );
        send[2] = 100;
        send[1] = 36+this_key;
        iterations++;
    }
}
void three(){
    int iterations = 0;
    std::vector<unsigned char> pgm_ch;
    pgm_ch.push_back(PGMCH+CH3);
    pgm_ch.push_back(50);
    midiout->sendMessage( &pgm_ch );
    pgm_ch.clear();

    std::vector<unsigned char> send;
    send.push_back(CH3+NOTEON);
    send.push_back(24+this_key);
    send.push_back(80);
    SLEEP(3*4800);
    while(iterations<8){
        //std::cout<<"iterations for three: "<<iterations<<"\n";

        midiout->sendMessage( &send );
        //send[1] = 45+chords[1][this_chord];
        //midiout->sendMessage( &send );
        //send[1] = 45+chords[1][this_chord];
        //midiout->sendMessage( &send );
        SLEEP(2400);
        send[2] = 0;
        midiout->sendMessage( &send );
        send[2] = 80;
        send[1] = 12+this_key+chords[2][this_chord];
        //SLEEP(100);
        midiout->sendMessage( &send );
        //send[1] = 45+chords[1][this_chord];
        //SLEEP(300);
        //midiout->sendMessage( &send );
        //send[2] = 60;

        SLEEP(7200);//2400+4800
        send[2] = 0;
        midiout->sendMessage( &send );
        send[2] = 80;
        send[1] = 12+this_key+chords[2][this_chord];
        iterations++;
    }
}
void four(){
    int iterations = 0;
    std::vector<unsigned char> pgm_ch;
    pgm_ch.push_back(PGMCH+CH4);
    pgm_ch.push_back(26);
    midiout->sendMessage( &pgm_ch );
    pgm_ch.clear();
    SLEEP(4*4800);
    std::vector<unsigned char> send;
    send.push_back(CH4+NOTEON);
    send.push_back(59);
    send.push_back(60);
    //SLEEP(12000);//4800+2400
    while(iterations<8){
        for(int a = 0; a<4; a++){
            midiout->sendMessage( &send );
            SLEEP(300);
            send[2] = 0;
            midiout->sendMessage( &send );
            send[2] = 60;
        }
        send[1] = 55;
        midiout->sendMessage( &send );
        SLEEP(300);
        send[1] = 59;
        midiout->sendMessage( &send );
        SLEEP(300);
        send[2] = 0;
        midiout->sendMessage( &send );
        send[1] = 59;
        midiout->sendMessage( &send );

        send[2] = 60;
        send[1] = 62;
        midiout->sendMessage( &send );
        SLEEP(600);
        send[1] = 55;
        for(int b=0; b<8; b++){
            midiout->sendMessage( &send );
            SLEEP(300);
            send[2] = 0;
            midiout->sendMessage( &send );
            send[2] = 55;
        }

        SLEEP(300);
        send[2] = 0;
        midiout->sendMessage( &send );
        send[1] = 59;
        iterations++;
    }
}
void five(){
    std::vector<unsigned char> pgm_ch;
    pgm_ch.push_back(PGMCH+CH5);
    pgm_ch.push_back(77);
    midiout->sendMessage( &pgm_ch );
    pgm_ch.clear();
    int iterations = 0;
    std::vector<unsigned char> send;
    send.push_back(NOTEON+CH5);
    send.push_back(24+this_key);
    send.push_back(80);
    SLEEP(5*4800);
    //SLEEP(14400);
    while(iterations < 8){
        unsigned int value = random() % 3;
        send[1] = this_key+chords[value][this_chord]+12;
        midiout->sendMessage( &send );
        unsigned int new_value = value+1;
        if(new_value==4){
            new_value = 0;
        }
        send[1] = this_key+chords[new_value][this_chord]+12;
        midiout->sendMessage( &send );
        int interval = random () % 3;
        interval = (interval+1)*400;
        SLEEP(interval);
        send[2] = 0;
        midiout->sendMessage( &send );
        send[1] = this_key+chords[value][this_chord]+12;
        midiout->sendMessage( &send );
        send[2] = 80;
        SLEEP(3600);
        iterations++;
    }
}
void six(){
    std::vector<unsigned char> pgm_ch;
    pgm_ch.push_back(PGMCH+CH6);
    pgm_ch.push_back(54);
    midiout->sendMessage( &pgm_ch );
    pgm_ch.clear();
    int iterations = 0;
    std::vector<unsigned char> send;
    send.push_back(NOTEON+CH6);
    send.push_back(40+this_key);
    send.push_back(100);
    SLEEP(6*4800);
    //SLEEP(14400);
    while(iterations < 3){
        //std::cout<<"iterations for six: "<<iterations<<"\n";
        midiout->sendMessage( &send );
        SLEEP(200);
        send[2] = 0;
        midiout->sendMessage( &send );
        send[2] = 100;
        midiout->sendMessage( &send );
        SLEEP(200);
        send[2] = 0;
        midiout->sendMessage( &send );
        send[2] = 100;
        midiout->sendMessage( &send );
        SLEEP(200);
        send[2] = 0;
        midiout->sendMessage( &send );
        send[2] = 100;
        midiout->sendMessage( &send );
        SLEEP(200);
        send[2] = 0;
        midiout->sendMessage( &send );
        send[2] = 100;
        midiout->sendMessage( &send );
        SLEEP(200);
        send[1] = 36+this_key;
        midiout->sendMessage( &send );
        SLEEP(200);
        send[2] = 0;
        midiout->sendMessage( &send );
        send[1] = 40+this_key;
        midiout->sendMessage( &send );

        send[2] = 100;
        midiout->sendMessage( &send );
        SLEEP(200);
        send[1] = 43+this_key;
        midiout->sendMessage( &send );
        SLEEP(200);
        send[2] = 0;
        midiout->sendMessage( &send );
        send[1] = 40+this_key;
        midiout->sendMessage( &send );
        send[2] = 100;

        midiout->sendMessage( &send );
        SLEEP(200);
        send[2] = 0;
        midiout->sendMessage( &send );
        send[2] = 100;
        midiout->sendMessage( &send );
        SLEEP(200);
        send[2] = 0;
        midiout->sendMessage( &send );
        send[2] = 100;
        midiout->sendMessage( &send );
        SLEEP(200);
        send[2] = 0;
        midiout->sendMessage( &send );
        send[2] = 100;
        midiout->sendMessage( &send );
        SLEEP(200);
        send[2] = 0;
        midiout->sendMessage( &send );
        send[2] = 100;
        send[1] = 43+this_key;
        midiout->sendMessage( &send );
        SLEEP(400);
        send[2] = 0;
        midiout->sendMessage( &send );
        send[2] = 100;
        send[1] = 36+this_key;
        midiout->sendMessage( &send );
        SLEEP(400);
        send[2] = 0;
        midiout->sendMessage( &send );
        send[2] = 100;
        send[1] = 40+this_key;
        midiout->sendMessage( &send );
        SLEEP(400);
        send[2] = 0;
        midiout->sendMessage( &send );
        send[2] = 100;
        SLEEP(14400);//1200 + 2400
        iterations++;
    }
}
void seven(){
    std::vector<unsigned char> pgm_ch;
    pgm_ch.push_back(PGMCH+CH7);
    pgm_ch.push_back(116);
    midiout->sendMessage( &pgm_ch );
    pgm_ch.clear();
    SLEEP(3*4800);
    int iterations = 0;
    std::vector<unsigned char> send;
    send.push_back(NOTEON+CH7);
    send.push_back(24+this_key);
    send.push_back(80);
    //SLEEP(14400);
    while(iterations < 18){
        unsigned int value = random() % 3;
        send[1] = this_key+chords[value][this_chord]+12;
        midiout->sendMessage( &send );
        unsigned int new_value = value+1;
        if(new_value==4){
            new_value = 0;
        }
        send[1] = this_key+chords[new_value][this_chord]+12;
        midiout->sendMessage( &send );
        SLEEP(400);
        iterations++;
    }
}

void mandatory(){
    //std::cout<<"in mandatory thread\n";
    //shifts the key
    SLEEP(2400*26);
    std::cout<<"key changed\n";
    this_key += 7;
    SLEEP(2400*8);
    this_key -= 7;
    std::cout<<"key changed again\n";
    SLEEP(2400);
    std::vector<unsigned char> pgm_ch;
    pgm_ch.push_back(PGMCH+CH8);
    pgm_ch.push_back(119);
    midiout->sendMessage( &pgm_ch );
    pgm_ch.clear();

    std::vector<unsigned char> send;
    send.push_back(CH8+NOTEON);
    send.push_back(59);
    send.push_back(30);
    midiout->sendMessage( &send );
    SLEEP(200);
    send[2] = 40;
    midiout->sendMessage( &send );
    SLEEP(150);
    send[2] = 50;
    midiout->sendMessage( &send );
    SLEEP(120);
    send[2] = 60;
    midiout->sendMessage( &send );
    SLEEP(90);
    send[2] = 70;
    midiout->sendMessage( &send );
    SLEEP(90);
    send[2] = 80;
    midiout->sendMessage( &send );
    SLEEP(90);
    send[2] = 90;
    midiout->sendMessage( &send );
    SLEEP(90);
    send[2] = 100;
    for(unsigned int b=100; b>19; b-=20){
        send[2] = b;
        for(int a=0; a<4; a++){
            midiout->sendMessage( &send );
            SLEEP(100);
        }
    }
    send[2] = 80;
    send[1] = 46;
    midiout->sendMessage( &send );
    SLEEP(100);
    send[2] = 0;
    midiout->sendMessage( &send );
    send[2] = 80;
    send[1] = 51;
    midiout->sendMessage( &send );
    SLEEP(100);
    send[2] = 0;
    midiout->sendMessage( &send );
}
void percussion(int n){
    //std::cout<<"drums playing\n";
    //if(n != 6){}else{
    int iterations = 0;
    std::vector<unsigned char> send;
    send.push_back(CHDr+NOTEON);
    send.push_back(36);
    send.push_back(60);
    SLEEP(2400);
    while(iterations<30){
        midiout->sendMessage( &send );
        SLEEP(400);
        midiout->sendMessage( &send );
        SLEEP(400);
        send[1] = 38;
        midiout->sendMessage( &send );
        send[1] = 36;
        SLEEP(400);
        midiout->sendMessage( &send );
        SLEEP(400);
        send[1] = 38;
        midiout->sendMessage( &send );
        SLEEP(200);
        midiout->sendMessage( &send );
        SLEEP(200);
        midiout->sendMessage( &send );
        SLEEP(400);
        send[1] = 36;
        iterations++;
    }
    //}
}
void chord_init(){//based from the octave below the desired one, for the -1s.
    //chords[3][7]
    chords[0][0] =12;
    chords[1][0] =16;
    chords[2][0] =19;

    chords[0][1] =12;
    chords[1][1] =17;
    chords[2][1] =21;

    chords[0][2] =11;
    chords[1][2] =17;
    chords[2][2] =19;

    chords[0][3] =11;
    chords[1][3] =14;
    chords[2][3] =19;

    chords[0][4] =12;
    chords[1][4] =15;
    chords[2][4] =19;

    chords[0][5] =12;
    chords[1][5] =16;
    chords[2][5] =22;

    chords[0][6] =14;
    chords[1][6] =17;
    chords[2][6] =21;

    low.push_back(144);
    low.push_back(45);
    low.push_back(80);
    med = low;
    high = low;
    low_off = low;
    low_off[2] = 0;
    med_off = low_off;
    high_off = low_off;

}
void sort(){
    for(unsigned int y = 0; y<100; y++){
        for(unsigned int x=0; x<factors.size(); x++){
            if(factors[x] == y){
                sorted_factors.push_back(y);
            }
        }
    }
}
void remove_dupes(){
    dupes_out.push_back(sorted_factors[0]);
    for(unsigned int x=1; x<sorted_factors.size(); x++){
        if(sorted_factors[x]!=sorted_factors[x-1]){
            dupes_out.push_back(sorted_factors[x]);
        }
    }
}
int main(){
    chord_init();
    bool input_done = false;
    unsigned int input;
    while(!input_done){
        std::cout<<"Input a number of 6 digits or less\n";
        std::string in;
        std::getline(std::cin, in);
        //int input = stoi(in, NULL, 10);
        input = atoi(in.c_str());
        if(input/1000000 == 0){
            input_done = true;
        }
    }
    // RtMidiOut constructor
    try {
        midiout = new RtMidiOut();
    }
    catch ( RtMidiError &error ) {
        error.printMessage();
        exit( EXIT_FAILURE );
    }
    // Call function to select port.
    try {
        if ( chooseMidiPort( midiout ) == false ) delete midiout;
    }
    catch ( RtMidiError &error ) {
        error.printMessage();
        delete midiout;
    }
    std::cout<<input<<"\nFactors:\n";
    bool factoring_done = false;
    unsigned int divisor = 1;
    unsigned int count_factors = 0;
    while(!factoring_done){
        if (input % divisor == 0) {
            std::cout<<divisor<<"\n";
            factors.push_back(divisor);
            divisor ++;
            count_factors++;
        }
        else{
            std::cout<<"\t"<<divisor<<" is not a factor.\n";
            divisor++;
        }
        if (divisor > sqrt(input)) {
        //if(divisor>input/2){
            factoring_done = true;
        }
    }
    std::cout<<"Number of factors for "<<input<<": "<<count_factors<<"\n";
    std::cout<<"Factors: ";
    for (unsigned int i = 0; i < factors.size(); i++){
        //std::cout<<factors[i]<<", ";
        factors[i] = factors[i] % 100;
        std::cout<<factors[i]<<", ";
    }
    std::cout<<"\n";
    sort();
    std::cout<<"Sorted: ";

    for (unsigned int j = 0; j < sorted_factors.size(); j++){
        std::cout<<sorted_factors[j]<<", ";
    }
    std::cout<<"\n";
    remove_dupes();
    std::cout<<"Duplicates removed.\n ";
    fflush(stdout);
    bool percussion_started = false;

    std::vector<unsigned char> send;
    send.push_back(CH8+NOTEON);
    send.push_back(59);
    send.push_back(100);
    midiout->sendMessage( &send );
    SLEEP(600);
    send[2] = 0;
    midiout->sendMessage( &send );

    //boost::thread t_mandatory(mandatory);
    //std::thread t_mandatory(mandatory);
    //std::cout<<"thread initiated\n";

    for (unsigned int k = 0; k < dupes_out.size(); k++){
        std::cout<<dupes_out[k]<<", ";
        if(dupes_out[k]==1){
            //boost::thread t_one(one);
            std::thread t_one(one);
        }
        else if(dupes_out[k]==2){
            //SLEEP(2*4800);
            //boost::thread t_two(two);
            std::thread t_two(two);

        }
        else if(dupes_out[k]==3){
            //SLEEP(3*4800);
            //boost::thread t_three(three);
            std::thread t_three(three);
        }
        else if(dupes_out[k]==4){
            //SLEEP(4*4800);
            //boost::thread t_four(four);
            std::thread t_four(four);
        }
        else if(dupes_out[k]==5){
            //SLEEP(5*4800);
            //boost::thread t_five(five);
            std::thread t_five(five);
        }
        else if(dupes_out[k]==6){
            //SLEEP(6*4800);
            //boost::thread t_six(six);
            std::thread t_six(six);
        }
        else if(dupes_out[k]==7){
            //SLEEP(7*4800);
            //boost::thread t_seven(seven);
            std::thread t_seven(seven);
        }
        else{
            if(!percussion_started){
                //std::cout<<"percussion started\n";
                //boost::thread t_else(percussion, dupes_out[k]);
                std::thread t_else(percussion, dupes_out[k]);
                percussion_started = true;
            }
        }
    }
    SLEEP(2400);
    std::cout<<"\n";
    std::cout<<"Number of unique factors after mod 100: "<<dupes_out.size()<<"\n";

    return 0;
}
bool chooseMidiPort( RtMidiOut *rtmidi )
{
    std::cout << "\nWould you like to open a virtual output port? [y/N] ";

    std::string keyHit;
    std::getline( std::cin, keyHit );
    if ( keyHit == "y" ) {
        rtmidi->openVirtualPort();
        return true;
    }

    std::string portName;
    unsigned int i = 0, nPorts = rtmidi->getPortCount();
    if ( nPorts == 0 ) {
        std::cout << "No output ports available!" << std::endl;
        return false;
    }

    if ( nPorts == 1 ) {
        std::cout << "\nOpening " << rtmidi->getPortName() << std::endl;
    }
    else {
        for ( i=0; i<nPorts; i++ ) {
            portName = rtmidi->getPortName(i);
            std::cout << "  Output port #" << i << ": " << portName << '\n';
        }

        do {
            std::cout << "\nChoose a port number: ";
            std::cin >> i;
        } while ( i >= nPorts );
    }

    std::cout << "\n";
    rtmidi->openPort( i );

    return true;
}


// Subscribes to Point Cloud Data, updates the occupancy grid, then publishes the data.
#include <math.h>
#include <signal.h>
#include <stdlib.h>
#include <unordered_set>

#include <Eigen/Core>
#include <opencv2/core/eigen.hpp>
#include <opencv2/imgproc.hpp>
#include <opencv2/opencv.hpp>

#include <pcl/point_cloud.h>
#include <pcl_ros/point_cloud.h>
#include <pcl_ros/transforms.h>

#include <ros/ros.h>

#include <igvc_msgs/map.h>
#include <nav_msgs/Odometry.h>
#include <sensor_msgs/CameraInfo.h>
#include <sensor_msgs/Image.h>

#include <tf/transform_datatypes.h>
#include <tf_conversions/tf_eigen.h>

#include <igvc_utils/robot_state.h>
#include <visualization_msgs/Marker.h>

#include <cv_bridge/cv_bridge.h>

#include <image_transport/image_transport.h>
#include <parameter_assertions/assertions.h>

#include "map_utils.h"
#include "ros_mapper.h"

ROSMapper::ROSMapper() : tf_listener_{ std::unique_ptr<tf::TransformListener>(new tf::TransformListener()) }
{
  ros::NodeHandle nh;
  ros::NodeHandle pNh("~");

  assertions::getParam(pNh, "map/length", length_x_);
  assertions::getParam(pNh, "map/width", width_y_);
  assertions::getParam(pNh, "map/start_x", start_x_);
  assertions::getParam(pNh, "map/start_y", start_y_);
  assertions::getParam(pNh, "octree/resolution", resolution_);

  assertions::getParam(pNh, "topics/lidar", lidar_topic_);

  assertions::getParam(pNh, "topics/line_segmentation/left", line_topic_left_);
  assertions::getParam(pNh, "topics/line_segmentation/center", line_topic_center_);
  assertions::getParam(pNh, "topics/line_segmentation/right", line_topic_right_);

  assertions::getParam(pNh, "topics/projected_line_pc/left", projected_line_topic_left_);
  assertions::getParam(pNh, "topics/projected_line_pc/center", projected_line_topic_center_);
  assertions::getParam(pNh, "topics/projected_line_pc/right", projected_line_topic_right_);

  assertions::getParam(pNh, "topics/camera_info/left", camera_info_topic_left_);
  assertions::getParam(pNh, "topics/camera_info/center", camera_info_topic_center_);
  assertions::getParam(pNh, "topics/camera_info/right", camera_info_topic_right_);

  assertions::getParam(pNh, "frames/camera/left", camera_frame_left_);
  assertions::getParam(pNh, "frames/camera/center", camera_frame_center_);
  assertions::getParam(pNh, "frames/camera/right", camera_frame_right_);

  assertions::getParam(pNh, "node/camera/left/enable", enable_left_cam_);
  assertions::getParam(pNh, "node/camera/center/enable", enable_center_cam_);
  assertions::getParam(pNh, "node/camera/right/enable", enable_right_cam_);

  assertions::getParam(pNh, "node/camera/use_passed_in_pointcloud", use_passed_in_pointcloud_);

  assertions::getParam(pNh, "cameras/resize_width", resize_width_);
  assertions::getParam(pNh, "cameras/resize_height", resize_height_);

  assertions::getParam(pNh, "topics/camera_center", center_camera_topic_);

  assertions::getParam(pNh, "node/debug/publish/map_debug_pcl", debug_pub_map_pcl);
  assertions::getParam(pNh, "node/use_lines", use_lines_);
  assertions::param(pNh, "node/transform_max_wait_time", transform_max_wait_time_, 3.0);

  mapper_ = std::make_unique<Mapper>(pNh);

  pcl_sub_ = nh.subscribe<pcl::PointCloud<pcl::PointXYZ>>(lidar_topic_, 1, &ROSMapper::pcCallback, this);

  if (use_lines_)
  {
    if (enable_left_cam_)
    {
      line_map_subs_.emplace(std::make_pair(
          Camera::left,
          nh.subscribe<sensor_msgs::Image>(line_topic_left_, 1,
                                           boost::bind(&ROSMapper::segmentedImageCallback, this, _1, Camera::left))));
      camera_infos_.emplace(std::make_pair(
          Camera::left,
          nh.subscribe<sensor_msgs::CameraInfo>(camera_info_topic_left_, 1,
                                                boost::bind(&ROSMapper::cameraInfoCallback, this, _1, Camera::left))));
      if (use_passed_in_pointcloud_)
      {
        projected_line_subs_.emplace(
            std::make_pair(Camera::left, nh.subscribe<pcl::PointCloud<pcl::PointXYZ>>(
                                             projected_line_topic_left_, 1,
                                             boost::bind(&ROSMapper::projectedLineCallback, this, _1, Camera::left))));
      }
    }
    if (enable_center_cam_)
    {
      line_map_subs_.emplace(std::make_pair(
          Camera::center,
          nh.subscribe<sensor_msgs::Image>(line_topic_center_, 1,
                                           boost::bind(&ROSMapper::segmentedImageCallback, this, _1, Camera::center))));
      camera_infos_.emplace(
          std::make_pair(Camera::center, nh.subscribe<sensor_msgs::CameraInfo>(
                                             camera_info_topic_center_, 1,
                                             boost::bind(&ROSMapper::cameraInfoCallback, this, _1, Camera::center))));
      if (use_passed_in_pointcloud_)
      {
        projected_line_subs_.emplace(std::make_pair(
            Camera::center, nh.subscribe<pcl::PointCloud<pcl::PointXYZ>>(
                                projected_line_topic_center_, 1,
                                boost::bind(&ROSMapper::projectedLineCallback, this, _1, Camera::center))));
      }
    }
    if (enable_right_cam_)
    {
      line_map_subs_.emplace(std::make_pair(
          Camera::right,
          nh.subscribe<sensor_msgs::Image>(line_topic_right_, 1,
                                           boost::bind(&ROSMapper::segmentedImageCallback, this, _1, Camera::right))));
      camera_infos_.emplace(std::make_pair(
          Camera::right,
          nh.subscribe<sensor_msgs::CameraInfo>(camera_info_topic_right_, 1,
                                                boost::bind(&ROSMapper::cameraInfoCallback, this, _1, Camera::right))));
      if (use_passed_in_pointcloud_)
      {
        projected_line_subs_.emplace(std::make_pair(
            Camera::right, nh.subscribe<pcl::PointCloud<pcl::PointXYZ>>(
                               projected_line_topic_right_, 1,
                               boost::bind(&ROSMapper::projectedLineCallback, this, _1, Camera::right))));
      }
    }
    center_cam_sub_ = nh.subscribe(center_camera_topic_, 1, &ROSMapper::centerCamCallback, this);
  }

  back_circle_sub_ = nh.subscribe("/back_circle", 1, &ROSMapper::backCircleCallback, this);

  map_pub_ = nh.advertise<igvc_msgs::map>("/map", 1);

  if (debug_pub_map_pcl)
  {
    debug_pcl_pub_ = nh.advertise<pcl::PointCloud<pcl::PointXYZRGB>>("/mapper/debug/pcl", 1);
  }

  ROS_INFO("Mapper started!");
}

void ROSMapper::centerCamCallback(const sensor_msgs::CompressedImageConstPtr &image)
{
  auto cv_img = cv_bridge::toCvCopy(image, "bgr8")->image;
  cv::resize(cv_img, cv_img, cv::Size(resize_width_, resize_height_));
  mapper_->setCenterImage(cv_img);
}

void ROSMapper::backCircleCallback(const pcl::PointCloud<pcl::PointXYZ>::Ptr msg)
{
  // do the tf tree
  // getOdomTransform(msg->header.stamp);
  mapper_->insertBackCircle(msg, state_.transform);
}

template <>
bool ROSMapper::getOdomTransform(const ros::Time message_timestamp)
{
  tf::StampedTransform transform;
  static ros::Duration wait_time = ros::Duration(transform_max_wait_time_);
  try
  {
    if (tf_listener_->waitForTransform("/odom", "/base_footprint", message_timestamp, wait_time))
    {
      tf_listener_->lookupTransform("/odom", "/base_footprint", message_timestamp, transform);
      state_.setState(transform);
      return true;
    }
    else
    {
      ROS_DEBUG("Failed to get transform from /base_footprint to /odom in time, using newest transforms");
      tf_listener_->lookupTransform("/odom", "/base_footprint", ros::Time(0), transform);
      state_.setState(transform);
      return true;
    }
  }
  catch (const tf::TransformException &ex)
  {
    ROS_ERROR("tf Transform error");
    ROS_ERROR("%s", ex.what());
    return false;
  }
  catch (std::runtime_error &ex)
  {
    ROS_ERROR("runtime error");
    ROS_ERROR("Runtime Exception at getOdomTransform: [%s]", ex.what());
    return false;
  }
}

template <>
bool ROSMapper::getOdomTransform(const uint64 timestamp)
{
  ros::Time message_timestamp;
  pcl_conversions::fromPCL(timestamp, message_timestamp);
  return getOdomTransform(message_timestamp);
}

void ROSMapper::setMessageMetadata(igvc_msgs::map &message, sensor_msgs::Image &image, uint64_t pcl_stamp)
{
  pcl_conversions::fromPCL(pcl_stamp, image.header.stamp);
  pcl_conversions::fromPCL(pcl_stamp, message.header.stamp);
  message.header.frame_id = "/odom";
  message.image = image;
  message.length = static_cast<unsigned int>(length_x_ / resolution_);
  message.width = static_cast<unsigned int>(width_y_ / resolution_);
  message.resolution = static_cast<float>(resolution_);
  message.orientation = static_cast<float>(state_.yaw());
  message.x = static_cast<unsigned int>(std::round(state_.x() / resolution_) + start_x_ / resolution_);
  message.y = static_cast<unsigned int>(std::round(state_.y() / resolution_) + start_y_ / resolution_);
  message.x_initial = static_cast<unsigned int>(start_x_ / resolution_);
  message.y_initial = static_cast<unsigned int>(start_y_ / resolution_);
}

template <>
bool ROSMapper::checkExistsStaticTransform(const std::string &frame_id, ros::Time message_timestamp,
                                           const std::string &topic)
{
  if (transforms_.find(topic) == transforms_.end())
  {
    // Wait for transform between frame_id (ex. /scan/pointcloud) and base_footprint.
    ROS_INFO_STREAM("Getting transform for " << topic << " from " << frame_id << " to /base_footprint \n");
    if (tf_listener_->waitForTransform("/base_footprint", frame_id, message_timestamp,
                                       ros::Duration(transform_max_wait_time_)))
    {
      tf::StampedTransform transform;
      tf_listener_->lookupTransform("/base_footprint", frame_id, message_timestamp, transform);
      transforms_.insert(std::pair<std::string, tf::StampedTransform>(topic, transform));
      ROS_INFO_STREAM("Found static transform from " << frame_id << " to /base_footprint");
    }
    else
    {
      ROS_ERROR_STREAM("Failed to find transform for " << topic << " from " << frame_id
                                                       << " to /base_footprint using empty transform");
      return false;
    }
  }
  return true;
}

template <>
bool ROSMapper::checkExistsStaticTransform(const std::string &frame_id, uint64 timestamp, const std::string &topic)
{
  if (transforms_.find(topic) == transforms_.end())
  {
    // Wait for transform between frame_id (ex. /scan/pointcloud) and base_footprint.
    ros::Time message_timestamp;
    pcl_conversions::fromPCL(timestamp, message_timestamp);
    return checkExistsStaticTransform(frame_id, message_timestamp, topic);
  }
  return true;
}

void ROSMapper::publish(uint64_t stamp)
{
  std::optional<cv::Mat> map = mapper_->getMap();
  if (!map)
  {
    ROS_WARN_STREAM_THROTTLE(1, "Couldn't get a map");
    return;
  }
  igvc_msgs::map message;
  sensor_msgs::Image image;

  img_bridge_ = cv_bridge::CvImage(message.header, sensor_msgs::image_encodings::MONO8, *map);
  img_bridge_.toImageMsg(image);

  setMessageMetadata(message, image, stamp);
  map_pub_.publish(message);

  if (debug_pub_map_pcl)
  {
    publishMapDebugPC(debug_pcl_pub_, *map, "/odom", stamp);
  }
}

void ROSMapper::publishMapDebugPC(const ros::Publisher &pub, const cv::Mat &mat, const std::string &frame_id,
                                  uint64_t stamp)
{
  pcl::PointCloud<pcl::PointXYZRGB>::Ptr pointcloud = boost::make_shared<pcl::PointCloud<pcl::PointXYZRGB>>();
  for (int i = 0; i < width_y_ / resolution_; i++)
  {
    for (int j = 0; j < length_x_ / resolution_; j++)
    {
      pcl::PointXYZRGB p{};
      p.x = static_cast<float>((i * resolution_) - (width_y_ / 2.0));
      p.y = static_cast<float>((j * resolution_) - (length_x_ / 2.0));
      uchar prob = mat.at<uchar>(i, j);
      if (prob > 127)
      {
        p.r = 0;
        p.g = static_cast<uint8_t>((prob - 127) * 2);
        p.b = 0;
        pointcloud->points.push_back(p);
      }
      else if (prob < 127)
      {
        p.r = 0;
        p.g = 0;
        p.b = static_cast<uint8_t>((127 - prob) * 2);
        pointcloud->points.push_back(p);
      }
    }
  }
  pointcloud->header.frame_id = frame_id;
  pointcloud->header.stamp = stamp;
  pub.publish(pointcloud);
}

void ROSMapper::pcCallback(const pcl::PointCloud<pcl::PointXYZ>::ConstPtr &pc)
{
  if (!checkExistsStaticTransform(pc->header.frame_id, pc->header.stamp, lidar_topic_))
  {
    ROS_ERROR("Couldn't find static transform for pointcloud. Sleeping 2 seconds then trying again...");
    ros::Duration(2).sleep();
    return;
  }

  if (!getOdomTransform(pc->header.stamp))
  {
    ROS_ERROR("Couldn't find odometry transform in pcCallback. Sleeping 2 seconds then trying again...");
    ros::Duration(2).sleep();
    return;
  }

  mapper_->insertLidarScan(pc, state_.transform * transforms_.at(lidar_topic_));

  publish(pc->header.stamp);
}

void ROSMapper::segmentedImageCallback(const sensor_msgs::ImageConstPtr &segmented, Camera camera)
{
  if (camera_infos_.find(camera) != camera_infos_.end())
  {
    ROS_INFO_STREAM_THROTTLE(1, "camera info for " << static_cast<int>(camera) << " still not found...");
    return;
  }
  if (segmented->width != resize_width_ || segmented->height != resize_height_)
  {
    ROS_ERROR_STREAM_THROTTLE(5, "Segmented image dimensions ("
                                     << segmented->width << ", " << segmented->height << ") differ from params: ("
                                     << resize_width_ << ", " << resize_height_ << "). Projection will be incorrect.");
  }
  std::string frame;
  switch (camera)
  {
    case Camera::left:
      frame = camera_frame_left_;
      break;
    case Camera::center:
      frame = camera_frame_center_;
      break;
    case Camera::right:
      frame = camera_frame_right_;
      break;
  }
  std::string topic;
  switch (camera)
  {
    case Camera::left:
      topic = line_topic_left_;
      break;
    case Camera::center:
      topic = line_topic_center_;
      break;
    case Camera::right:
      topic = line_topic_right_;
      break;
  }
  if (!checkExistsStaticTransform(frame, segmented->header.stamp, topic))
  {
    ROS_ERROR_STREAM_THROTTLE(2, "Finding static transform to " << frame << "failed. Trying again...");
    return;
  }

  if (!getOdomTransform(segmented->header.stamp))
  {
    ROS_ERROR_STREAM_THROTTLE(2, "Finding odometry transform failed. Trying again...");
    return;
  }
  // Convert to OpenCV
  cv_bridge::CvImagePtr segmented_ptr = cv_bridge::toCvCopy(segmented, "mono8");
  cv::Mat image = segmented_ptr->image;

  mapper_->insertSegmentedImage(std::move(image), state_.transform, transforms_.at(topic), segmented->header.stamp,
                                camera, use_passed_in_pointcloud_);

  publish(pcl_conversions::toPCL(segmented->header.stamp));
}

void ROSMapper::projectedLineCallback(const pcl::PointCloud<pcl::PointXYZ>::ConstPtr &pc, Camera camera)
{
  if (!getOdomTransform(pc->header.stamp))
  {
    ROS_ERROR_STREAM_THROTTLE(2, "Finding odometry transform failed. Trying again...");
    return;
  }
  mapper_->insertCameraProjection(pc, state_.transform);
  publish(pc->header.stamp);
}

void ROSMapper::cameraInfoCallback(const sensor_msgs::CameraInfoConstPtr &camera_info, Camera camera)
{
  image_geometry::PinholeCameraModel camera_model;
  sensor_msgs::CameraInfoConstPtr changed_camera_info =
      MapUtils::scaleCameraInfo(camera_info, resize_width_, resize_height_);
  camera_model.fromCameraInfo(changed_camera_info);
  mapper_->setProjectionModel(camera_model, camera);

  camera_infos_.erase(camera);
}


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

#define BOOST_TEST_MODULE transition - quasiStatic
#include <boost/test/included/unit_test.hpp>
#include <hpp/bezier-com-traj/solve.hh>
#include <hpp/bezier-com-traj/common_solve_methods.hh>
#include <hpp/centroidal-dynamics/centroidal_dynamics.hh>
#include "test_helper.hh"

BOOST_AUTO_TEST_SUITE(quasiStatic)

BOOST_AUTO_TEST_CASE(single_support) {
  // check if state valid with only one contact :
  MatrixX3 normal(1, 3);
  normal << 0, 0, 1;
  MatrixX3 position(1, 3);
  position << 0, 0, 0;
  std::pair<MatrixX3, VectorX> Ab = generateConstraints(normal, position, Matrix3::Identity(), Vector3::Zero());
  std::pair<Matrix3, Vector3> Hg = computeCost();
  Vector3 init = Vector3::Zero();
  bezier_com_traj::ResultData res = bezier_com_traj::solve(Ab, Hg, init);
  BOOST_CHECK(res.success_);

  // sample positions for second foot inside kinematics constraints and check for feasibility :
}

BOOST_AUTO_TEST_CASE(quasiStatic_exist) {
  // sample positions for second foot inside kinematics constraints and check for feasibility :
  // Vector3 firstLeg_n(0,0,1);
  // Vector3 firstLeg_p(0,0,0);

  for (size_t i = 0; i < 500; ++i) {
    MatrixX3 normal(1, 3);
    MatrixX3 position(1, 3);
    // normal.block<1,3>(0,0) = firstLeg_n;
    // position.block<1,3>(0,0) = firstLeg_p;
    double x = fRandom(KIN_X_MIN, KIN_X_MAX);
    double y = fRandom(KIN_Y_MIN, KIN_Y_MAX);
    normal.block<1, 3>(0, 0) = Vector3(0, 0, 1);
    position.block<1, 3>(0, 0) = Vector3(x, y, 0.);

    std::pair<MatrixX3, VectorX> Ab = generateConstraints(normal, position, Matrix3::Identity(), Vector3::Zero());
    std::pair<Matrix3, Vector3> Hg = computeCost();
    Vector3 init = Vector3::Zero();
    bezier_com_traj::ResultData res = bezier_com_traj::solve(Ab, Hg, init);
    BOOST_CHECK(res.success_);
  }
}

BOOST_AUTO_TEST_CASE(quasiStatic_empty_upX) {
  for (size_t i = 0; i < 500; ++i) {
    MatrixX3 normal(1, 3);
    MatrixX3 position(1, 3);
    // normal.block<1,3>(0,0) = firstLeg_n;
    // position.block<1,3>(0,0) = firstLeg_p;
    double x = fRandom(KIN_X_MAX + (LX / 2.) + 0.001, 10);
    double y = fRandom(KIN_Y_MIN, KIN_Y_MAX);
    normal.block<1, 3>(0, 0) = Vector3(0, 0, 1);
    position.block<1, 3>(0, 0) = Vector3(x, y, 0.);

    std::pair<MatrixX3, VectorX> Ab = generateConstraints(normal, position, Matrix3::Identity(), Vector3::Zero());
    std::pair<Matrix3, Vector3> Hg = computeCost();
    Vector3 init = Vector3::Zero();
    bezier_com_traj::ResultData res = bezier_com_traj::solve(Ab, Hg, init);
    BOOST_CHECK(!res.success_);
  }
}

BOOST_AUTO_TEST_CASE(quasiStatic_empty_downX) {
  for (size_t i = 0; i < 500; ++i) {
    MatrixX3 normal(1, 3);
    MatrixX3 position(1, 3);
    // normal.block<1,3>(0,0) = firstLeg_n;
    // position.block<1,3>(0,0) = firstLeg_p;
    double x = fRandom(-10, KIN_X_MIN - (LX / 2.) - 0.001);
    double y = fRandom(KIN_Y_MIN, KIN_Y_MAX);
    normal.block<1, 3>(0, 0) = Vector3(0, 0, 1);
    position.block<1, 3>(0, 0) = Vector3(x, y, 0.);

    std::pair<MatrixX3, VectorX> Ab = generateConstraints(normal, position, Matrix3::Identity(), Vector3::Zero());
    std::pair<Matrix3, Vector3> Hg = computeCost();
    Vector3 init = Vector3::Zero();
    bezier_com_traj::ResultData res = bezier_com_traj::solve(Ab, Hg, init);
    BOOST_CHECK(!res.success_);
  }
}

BOOST_AUTO_TEST_CASE(quasiStatic_empty_downY) {
  for (size_t i = 0; i < 500; ++i) {
    MatrixX3 normal(1, 3);
    MatrixX3 position(1, 3);
    // normal.block<1,3>(0,0) = firstLeg_n;
    // position.block<1,3>(0,0) = firstLeg_p;
    double x = fRandom(KIN_X_MIN, KIN_X_MAX);
    double y = fRandom(-10, KIN_Y_MIN - (LY / 2.) - 0.001);
    normal.block<1, 3>(0, 0) = Vector3(0, 0, 1);
    position.block<1, 3>(0, 0) = Vector3(x, y, 0.);

    std::pair<MatrixX3, VectorX> Ab = generateConstraints(normal, position, Matrix3::Identity(), Vector3::Zero());
    std::pair<Matrix3, Vector3> Hg = computeCost();
    Vector3 init = Vector3::Zero();
    bezier_com_traj::ResultData res = bezier_com_traj::solve(Ab, Hg, init);
    BOOST_CHECK(!res.success_);
  }
}

BOOST_AUTO_TEST_CASE(quasiStatic_empty_upY) {
  for (size_t i = 0; i < 500; ++i) {
    MatrixX3 normal(1, 3);
    MatrixX3 position(1, 3);
    // normal.block<1,3>(0,0) = firstLeg_n;
    // position.block<1,3>(0,0) = firstLeg_p;
    double x = fRandom(KIN_X_MIN, KIN_X_MAX);
    double y = fRandom(KIN_Y_MAX + (LY / 2.) + 0.001, 10);
    normal.block<1, 3>(0, 0) = Vector3(0, 0, 1);
    position.block<1, 3>(0, 0) = Vector3(x, y, 0.);

    std::pair<MatrixX3, VectorX> Ab = generateConstraints(normal, position, Matrix3::Identity(), Vector3::Zero());
    std::pair<Matrix3, Vector3> Hg = computeCost();
    Vector3 init = Vector3::Zero();
    bezier_com_traj::ResultData res = bezier_com_traj::solve(Ab, Hg, init);
    BOOST_CHECK(!res.success_);
  }
}

BOOST_AUTO_TEST_SUITE_END()


//          Copyright Gavin Band 2008 - 2012.
// Distributed under the Boost Software License, Version 1.0.
//    (See accompanying file LICENSE_1_0.txt or copy at
//          http://www.boost.org/LICENSE_1_0.txt)

#ifndef SNPTEST_REGRESSION_COMBINED_LOGLIKELIHOOD_PRIOR_HPP
#define SNPTEST_REGRESSION_COMBINED_LOGLIKELIHOOD_PRIOR_HPP

#include <boost/noncopyable.hpp>
#include "Eigen/Core"
#include "metro/regression/Design.hpp"
#include "metro/regression/LogLikelihood.hpp"
#include "metro/SmoothFunction.hpp"

namespace metro {
	namespace regression {
		/*
		* Base class for classes implementing regression log posterior densities
		* These are prior / log-likelihood combinations that can unpack the contribution from likelihood and prior
		*/
		struct LogUnnormalisedPosterior: public SmoothFunction
		{
		public:
			typedef std::auto_ptr< LogUnnormalisedPosterior > UniquePtr ;
			LogUnnormalisedPosterior( LogLikelihood& loglikelihood, SmoothFunction& posterior ) ;

		public:
			LogLikelihood const& ll() const ;
			SmoothFunction const& prior() const ;
			std::string get_summary() const ;

		public:
			int number_of_parameters() const { return m_ll.number_of_parameters() ; }

			void evaluate_at( Vector const& parameters, int const numberOfDerivatives = 2 ) ;
			void evaluate( int const numberOfDerivatives = 2 ) ;

			Scalar get_value_of_function() const {
				return m_ll.get_value_of_function() + m_prior.get_value_of_function() ;
			}

			Vector get_value_of_first_derivative() const {
				return m_ll.get_value_of_first_derivative() + m_prior.get_value_of_first_derivative() ;
			}

			Matrix get_value_of_second_derivative() const {
				return m_ll.get_value_of_second_derivative() + m_prior.get_value_of_second_derivative() ;
			}

			Vector const& parameters() const {
				return m_parameters ;
			}

		private:
			LogLikelihood& m_ll ;
			SmoothFunction& m_prior ;
			Vector m_parameters ;
		} ;
	}
}

#endif


// Author(s): Wieger Wesselink
// Copyright: see the accompanying file COPYING or copy at
// https://github.com/mCRL2org/mCRL2/blob/master/COPYING
//
// Distributed under the Boost Software License, Version 1.0.
// (See accompanying file LICENSE_1_0.txt or copy at
// http://www.boost.org/LICENSE_1_0.txt)
//
/// \file pbesrewr_test.cpp
/// \brief Test for the pbes rewriters.

#define BOOST_TEST_MODULE pbesrewr_test
#include <boost/test/included/unit_test_framework.hpp>
#include "mcrl2/data/enumerator.h"
#include "mcrl2/lps/detail/test_input.h"
#include "mcrl2/lps/linearise.h"
#include "mcrl2/modal_formula/detail/test_input.h"
#include "mcrl2/modal_formula/parse.h"
#include "mcrl2/pbes/lps2pbes.h"
#include "mcrl2/pbes/rewrite.h"
#include "mcrl2/pbes/rewriter.h"
#include "mcrl2/pbes/txt2pbes.h"

using namespace mcrl2;
using namespace mcrl2::pbes_system;

BOOST_AUTO_TEST_CASE(test_pbesrewr1)
{
  std::string pbes_text =
    "sort Enum = struct e1 | e2;                           \n"
    "pbes mu X(n:Enum)=exists m1,m2:Enum.(X(m1) || X(m2)); \n"
    "init X(e1);                                           \n"
    ;
  pbes p = txt2pbes(pbes_text);
  data::rewriter datar(p.data(), data::jitty);
  bool enumerate_infinite_sorts = true;
  enumerate_quantifiers_rewriter pbesr(datar, p.data(), enumerate_infinite_sorts);
  pbes_rewrite(p, pbesr);
  BOOST_CHECK(p.is_well_typed());
}

BOOST_AUTO_TEST_CASE(test_pbesrewr2)
{
  lps::specification spec = remove_stochastic_operators(lps::linearise(lps::detail::ABP_SPECIFICATION()));
  state_formulas::state_formula formula = state_formulas::parse_state_formula(lps::detail::NO_DEADLOCK(), spec);
  bool timed = false;
  pbes p = lps2pbes(spec, formula, timed);
  BOOST_CHECK(p.is_well_typed());

  data::rewriter datar(p.data(), data::jitty);
  bool enumerate_infinite_sorts = true;
  enumerate_quantifiers_rewriter pbesr(datar, p.data(), enumerate_infinite_sorts);
  pbes_rewrite(p, pbesr);
  BOOST_CHECK(p.is_well_typed());
}


#include <iostream>
#include <fstream>
#include <iterator>
#include <gflags/gflags.h>
#include <glog/logging.h>
#include <boost/algorithm/string.hpp>

#include <render_views_tesselated_sphere_mod.h>
#include <patch_generator.h>
#include <train_patch_generator.h>

using namespace std;

DEFINE_bool(render, false, "Render Viewpoints (input=PLY file, output=Output Folder");
DEFINE_bool(genpatches, false, "Generate Patches, input=Render Output Folders of each object, output=Output folder");
DEFINE_bool(gentrainpatches, false, "Generate Training Patches after training a network. input=folder containing patches. output=forest-patches-filename");
DEFINE_bool(lmdb, false, "Save patches in lmdb database appropriate for caffe input");
DEFINE_bool(binfile, false, "Save patches in binary format");
DEFINE_string(input, "", "Input argument");
DEFINE_string(output, "", "Output argument");
DEFINE_int32(tessel_level, 1, "Tessellation Level");
DEFINE_int32(inPlaceCamRot, 24, "In Place Camera Rotations");
DEFINE_int32(lightings, 3, "Number of times the light will increase (max 10)");
DEFINE_int32(numHeights, 4, "Number of different heights of the camera. Starting point is 0.6cm");
DEFINE_double(heightStep, 0.25, "Step of heights of the camera in m.");
DEFINE_double(startHeight, 0.3, "Starting distance of the camera in m from the center of the object.");
DEFINE_int32(patch_size, 20, "Pixel size in voxels");
DEFINE_double(voxel_size, 0.001, "Voxel size in meters");
DEFINE_int32(stride, 10, "Stride for extracting patches");
DEFINE_string(caffe_definition, "", "Caffe definition model filename");
DEFINE_string(caffe_weights, "", "Caffe weights model filename");
DEFINE_int32(gpu, -1, "Gpu device number (0). if not set, default is CPU - used only in --gentrainpatches");
DEFINE_bool(no_random_values, false, "Specify when depth=0 if a random value will be generated");
DEFINE_double(distance_threshold, 3.0f, "Do not consider patches being after this threshold away from the camera. In (m)");
DEFINE_int32(batch_size, 1, "Batch size for creating train patches");

DEFINE_bool(above_z, false, "set if camera should go only above Z axis");
DEFINE_bool(below_z, false, "set if camera should go only below Z axis");
// always set to false
DEFINE_bool(use_surface_normals, false, "Wheather extract surface normals patches of simple rgbd");
DEFINE_double(max_depth_range_in_m, 0.25f, "Max distance to truncate depth in patches");

DEFINE_double(percent, 1, "Keep percent of patches");

// these are put in order to be able
// to render a table below an object
DEFINE_bool(render_around_0, false, "Render around center of axes and not around center of object");
DEFINE_double(object_radius, -1.0, "Define another radius rather than the one defined by object extend (helps if a table is below)");

int main(int argc, char** argv)
{
    google::InitGoogleLogging(argv[0]);

    #ifndef GFLAGS_GFLAGS_H_
      namespace gflags = google;
    #endif

    gflags::ParseCommandLineFlags(&argc, &argv, true);

    if(FLAGS_render){

        ifstream f(FLAGS_input.c_str());
        CHECK(f) << "Input file " << FLAGS_input.c_str() << " not found";
        RenderViewsTesselatedSphere view_renderer;
        view_renderer.setPlyFileName(FLAGS_input);
        view_renderer.setOutFolder(FLAGS_output);
        CHECK_GT(FLAGS_tessel_level, 0);
        view_renderer.setTesselationLevel(FLAGS_tessel_level);
        CHECK_GT(FLAGS_inPlaceCamRot, 0);
        view_renderer.setInPlaceCamRotations(FLAGS_inPlaceCamRot);
        view_renderer.setLightings(FLAGS_lightings);
        view_renderer.setHeight(FLAGS_numHeights, (float)FLAGS_heightStep);
        view_renderer.setStartHeight(FLAGS_startHeight);
        view_renderer.setAboveZ(FLAGS_above_z);
        view_renderer.setBelowZ(FLAGS_below_z);
        view_renderer.setRenderAround0(FLAGS_render_around_0);
        view_renderer.setObjectRadius(FLAGS_object_radius);
        view_renderer.generateViews();

    }
    else if(FLAGS_genpatches){

        patch_generator pgen;
        std::vector<std::string> input_objects_folders;
        boost::split(input_objects_folders, FLAGS_input, boost::is_any_of(", "));
        if(!FLAGS_lmdb && !FLAGS_binfile){
            std::cout << "No output method specified, using lmdb by default" << std::endl;
            FLAGS_lmdb = true;
        }
        if(FLAGS_lmdb){
            pgen.setOutputType(patch_generator::OUTPUT_LMDB);
        } else {
            pgen.setOutputType(patch_generator::OUTPUT_BIN);
        }
        pgen.setInputObjectFolders(input_objects_folders);
        pgen.setOutputFolder(FLAGS_output);
        pgen.setVoxelSizeInM(FLAGS_voxel_size);
        pgen.setPatchSizeInVoxels(FLAGS_patch_size);
        pgen.setStride(FLAGS_stride);
        pgen.setGenerateRandomValues(!FLAGS_no_random_values);
        pgen.setDistanceThreshold(FLAGS_distance_threshold);
        pgen.setPercent(FLAGS_percent);
        if(FLAGS_use_surface_normals)
            pgen.generatePatches();
        else {
            pgen.setMaxDepthRangeInM(FLAGS_max_depth_range_in_m);
            pgen.generatePatches_rgbd();
        }

    }

    else if(FLAGS_gentrainpatches){

        CHECK_GT(FLAGS_caffe_definition.size(), 0) << "No caffe definition model defined.";
        CHECK_GT(FLAGS_caffe_weights.size(), 0) << "No caffe weights model defined.";
        CHECK_GT(FLAGS_input.size(), 0) << "No input lmdb specified.";
        CHECK_GT(FLAGS_output.size(), 0) << "No output file specified.";
        train_patch_generator trainpg;
        trainpg.setCaffeModel(FLAGS_caffe_definition, FLAGS_caffe_weights);

        if(FLAGS_gpu >= 0)
            trainpg.useGPU(FLAGS_gpu);
        else
            trainpg.useCPU();

        trainpg.setInputLmdb(FLAGS_input);
        trainpg.setOutputFile(FLAGS_output);
        trainpg.setPatchSize(FLAGS_patch_size);
        trainpg.setBatchSize(FLAGS_batch_size);
        trainpg.generate_train_patches();
    }

    return 0;
}


#include "main.h"
#include "sigma.h"
#include "zerocoin.h" // Mostly for reusing class libzerocoin::SpendMetaData
#include "timedata.h"
#include "chainparams.h"
#include "util.h"
#include "base58.h"
#include "definition.h"
#include "wallet/wallet.h"
#include "wallet/walletdb.h"
#include "crypto/sha256.h"
#include "sigma/coinspend.h"
#include "sigma/coin.h"
#include "noirnode-payments.h"
#include "noirnode-sync.h"

#include "sigma_params.h"

#include <atomic>
#include <sstream>
#include <chrono>

#include <boost/foreach.hpp>

#include <ios>

namespace sigma {

// Set up the Sigma Params object
sigma::Params* SigmaParams = sigma::Params::get_default();

static CSigmaState sigmaState;

static bool CheckSigmaSpendSerial(
        CValidationState &state,
        CSigmaTxInfo *sigmaTxInfo,
        const Scalar &serial,
        int nHeight,
        bool fConnectTip) {
    // check for zerocoin transaction in this block as well
    if (sigmaTxInfo &&
            !sigmaTxInfo->fInfoIsComplete &&
            sigmaTxInfo->spentSerials.find(serial) != sigmaTxInfo->spentSerials.end())
        return state.DoS(0, error("CTransaction::CheckTransaction() : two or more spends with same serial in the same block"));

    // check for used serials in sigmaState
    if (sigmaState.IsUsedCoinSerial(serial)) {
        // Proceed with checks ONLY if we're accepting tx into the memory pool or connecting block to the existing blockchain
        if (nHeight == INT_MAX || fConnectTip) {
            return state.DoS(0, error("CTransaction::CheckTransaction() : The sigma CoinSpend serial has been used"));
        }
    }
    return true;
}

bool IsSigmaAllowed()
{
    LOCK(cs_main);
    return IsSigmaAllowed(chainActive.Height());
}

bool IsSigmaAllowed(int height)
{
	return height >= ::Params().GetConsensus().nSigmaStartBlock;
}

secp_primitives::GroupElement ParseSigmaMintScript(const CScript& script)
{
    if (script.size() < 1) {
        throw std::invalid_argument("Script is not a valid Sigma mint");
    }

    std::vector<unsigned char> serialized(script.begin() + 1, script.end());

    secp_primitives::GroupElement pub;
    pub.deserialize(serialized.data());

    return pub;
}

std::pair<std::unique_ptr<sigma::CoinSpend>, uint32_t> ParseSigmaSpend(const CTxIn& in)
{
    uint32_t groupId = in.prevout.n;

    if (groupId < 1 || groupId >= INT_MAX || in.scriptSig.size() < 1) {
        throw CBadTxIn();
    }

    CDataStream serialized(
        std::vector<unsigned char>(in.scriptSig.begin() + 1, in.scriptSig.end()),
        SER_NETWORK,
        PROTOCOL_VERSION
    );

	std::unique_ptr<sigma::CoinSpend> spend(new sigma::CoinSpend(SigmaParams, serialized));

    return std::make_pair(std::move(spend), groupId);
}

// This function will not report an error only if the transaction is sigma spend.
CAmount GetSpendAmount(const CTxIn& in) {
    if (in.IsSigmaSpend()) {
        std::unique_ptr<sigma::CoinSpend> spend;

        try {
            std::tie(spend, std::ignore) = ParseSigmaSpend(in);
        } catch (const std::ios_base::failure& e) {
            LogPrintf("GetSpendAmount: io error %s\n", e.what());
            return 0;
        } catch (const CBadTxIn& e) {
            LogPrintf("GetSpendAmount: %s\n", e.what());
            return 0;
        }

        return spend->getIntDenomination();
    }
    return 0;
}

CAmount GetSpendAmount(const CTransaction& tx) {
    CAmount sum(0);
    for (const auto& vin : tx.vin) {
        sum += GetSpendAmount(vin);
    }
    return sum;
}

bool CheckSigmaBlock(CValidationState &state, const CBlock& block) {
    auto& consensus = ::Params().GetConsensus();

    size_t spendsAmount = 0;
    CAmount spendsValue(0);

    for (const auto& tx : block.vtx) {
        for (const auto& in : tx.vin) {
            if (in.IsSigmaSpend()) {
                spendsAmount++;
            }
        }
        spendsValue += GetSpendAmount(tx);
    }

    if (spendsAmount > consensus.nMaxSigmaInputPerBlock) {
        return state.DoS(100, false, REJECT_INVALID,
            "bad-txns-spend-invalid");
    }

    if (spendsValue > consensus.nMaxValueSigmaSpendPerBlock) {
        return state.DoS(100, false, REJECT_INVALID,
            "bad-txns-spend-invalid");
    }
    return true;
}

// Will return false for V1, V1.5 and V2 spends.
// Mixing V2 and sigma spends into the same transaction will fail.
bool CheckSigmaSpendTransaction(
        const CTransaction &tx,
        const vector<sigma::CoinDenomination>& targetDenominations,
        CValidationState &state,
        uint256 hashTx,
        bool isVerifyDB,
        int nHeight,
        bool isCheckWallet,
        CSigmaTxInfo *sigmaTxInfo) {
    bool hasSigmaSpendInputs = false, hasNonSigmaInputs = false;
    int vinIndex = -1;
    std::unordered_set<Scalar, sigma::CScalarHash> txSerials;

    for (const CTxIn &txin : tx.vin)
    {
        std::unique_ptr<sigma::CoinSpend> spend;
        uint32_t pubcoinId;

        vinIndex++;
        if (txin.scriptSig.IsSigmaSpend())
            hasSigmaSpendInputs = true;
        else
            hasNonSigmaInputs = true;

        try {
            std::tie(spend, pubcoinId) = ParseSigmaSpend(txin);
        } catch (CBadTxIn&) {
            return state.DoS(100,
                false,
                REJECT_MALFORMED,
                "CheckSigmaSpendTransaction: invalid spend transaction");
        }

        if (spend->getVersion() != ZEROCOIN_TX_VERSION_3 && spend->getVersion() != ZEROCOIN_TX_VERSION_3_1) {
            return state.DoS(100,
                             false,
                             NSEQUENCE_INCORRECT,
                             "CTransaction::CheckTransaction() : Error: incorrect spend transaction verion");
        }

        uint256 txHashForMetadata;

        // Obtain the hash of the transaction sans the zerocoin part
        CMutableTransaction txTemp = tx;
        BOOST_FOREACH(CTxIn &txTempIn, txTemp.vin) {
            if (txTempIn.scriptSig.IsSigmaSpend()) {
                txTempIn.scriptSig.clear();
            }
        }
        txHashForMetadata = txTemp.GetHash();

        LogPrintf("CheckSigmaSpendTransaction: tx version=%d, tx metadata hash=%s, serial=%s\n",
                spend->getVersion(), txHashForMetadata.ToString(),
                spend->getCoinSerialNumber().tostring());

        CSigmaState::SigmaCoinGroupInfo coinGroup;
        if (!sigmaState.GetCoinGroupInfo(targetDenominations[vinIndex], pubcoinId, coinGroup))
            return state.DoS(100, false, NO_MINT_ZEROCOIN,
                    "CheckSigmaSpendTransaction: Error: no coins were minted with such parameters");

        bool passVerify = false;
        CBlockIndex *index = coinGroup.lastBlock;
        pair<sigma::CoinDenomination, int> denominationAndId = std::make_pair(
            targetDenominations[vinIndex], pubcoinId);

        uint256 accumulatorBlockHash = spend->getAccumulatorBlockHash();

        // We use incomplete transaction hash as metadata.
        sigma::SpendMetaData newMetaData(
            pubcoinId,
            accumulatorBlockHash,
            txHashForMetadata);

        // find index for block with hash of accumulatorBlockHash or set index to the coinGroup.firstBlock if not found
        while (index != coinGroup.firstBlock && index->GetBlockHash() != accumulatorBlockHash)
            index = index->pprev;

        // Build a vector with all the public coins with given denomination and accumulator id before
        // the block on which the spend occured.
        // This list of public coins is required by function "Verify" of CoinSpend.
        std::vector<sigma::PublicCoin> anonymity_set;
        while(true) {
            BOOST_FOREACH(const sigma::PublicCoin& pubCoinValue,
                    index->sigmaMintedPubCoins[denominationAndId]) {
                anonymity_set.push_back(pubCoinValue);
            }
            if (index == coinGroup.firstBlock)
                break;
            index = index->pprev;
        }

        bool fPadding = spend->getVersion() >= ZEROCOIN_TX_VERSION_3_1;
        if (!isVerifyDB) {
            auto params = ::Params().GetConsensus();
            bool fShouldPad = (nHeight != INT_MAX && nHeight >= params.nSigmaPaddingBlock) ||
                        (nHeight == INT_MAX && chainActive.Height() >= params.nSigmaPaddingBlock);
            //Accept both padded and not padded spends at HF block
            if(nHeight == params.nSigmaPaddingBlock)
                fShouldPad = fPadding;
            if (fPadding != fShouldPad)
                return state.DoS(1, error("Incorrect sigma spend transaction version"));
        }

        passVerify = spend->Verify(anonymity_set, newMetaData, fPadding);
        if (passVerify) {
            Scalar serial = spend->getCoinSerialNumber();
            // do not check for duplicates in case we've seen exact copy of this tx in this block before
            if (!(sigmaTxInfo && sigmaTxInfo->zcTransactions.count(hashTx) > 0)) {
                if (!CheckSigmaSpendSerial(
                            state, sigmaTxInfo, serial, nHeight, false)) {
                    LogPrintf("CheckSigmaSpendTransaction: serial check failed, serial=%s\n", serial);
                    return false;
                }
            }

            // check duplicated serials in same transaction.
            if (!txSerials.insert(serial).second) {
                return state.DoS(100,
                    error("CheckSigmaSpendTransaction: two or more spends with same serial in the same transaction"));
            }

            if(!isVerifyDB && !isCheckWallet) {
                if (sigmaTxInfo && !sigmaTxInfo->fInfoIsComplete) {
                    // add spend information to the index
                    sigmaTxInfo->spentSerials.insert(std::make_pair(
                                serial, (int)spend->getDenomination()));
                }
            }
        }
        else {
            LogPrintf("CheckSigmaSpendTransaction: verification failed at block %d\n", nHeight);
            return false;
        }
    }

    if(!isVerifyDB && !isCheckWallet) {
        if (sigmaTxInfo && !sigmaTxInfo->fInfoIsComplete && hasSigmaSpendInputs) {
            sigmaTxInfo->zcTransactions.insert(hashTx);
        }
    }

    if (hasSigmaSpendInputs) {
        if (hasNonSigmaInputs) {
            // mixing zerocoin spend input with non-zerocoin inputs is prohibited
            return state.DoS(100, false,
                             REJECT_MALFORMED,
                             "CheckSigmaSpendTransaction: can't mix zerocoin spend input with regular ones");
        }
    }

    return true;
}

bool CheckSigmaMintTransaction(
        const CTxOut &txout,
        CValidationState &state,
        uint256 hashTx,
        CSigmaTxInfo *sigmaTxInfo) {
    secp_primitives::GroupElement pubCoinValue;

    LogPrintf("CheckSigmaMintTransaction txHash = %s\n", txout.GetHash().ToString());
    LogPrintf("nValue = %d\n", txout.nValue);

    try {
        pubCoinValue = ParseSigmaMintScript(txout.scriptPubKey);
    } catch (std::invalid_argument&) {
        return state.DoS(100,
            false,
            PUBCOIN_NOT_VALIDATE,
            "CTransaction::CheckTransaction() : PubCoin validation failed");
    }

    sigma::CoinDenomination denomination;
    if (!IntegerToDenomination(txout.nValue, denomination, state)) {
        return state.DoS(100,
                false,
                PUBCOIN_NOT_VALIDATE,
                "CTransaction::CheckSigmaTransaction() : "
                "PubCoin validation failed, unknown denomination");
    }
    sigma::PublicCoin pubCoin(pubCoinValue, denomination);
    bool hasCoin = sigmaState.HasCoin(pubCoin);

    if (!hasCoin && sigmaTxInfo && !sigmaTxInfo->fInfoIsComplete) {
        BOOST_FOREACH(const sigma::PublicCoin& mint, sigmaTxInfo->mints) {
            if (mint == pubCoin) {
                hasCoin = true;
                break;
            }
        }
    }

    if (hasCoin) {
       LogPrintf("CheckSigmaMintTransaction: double mint, tx=%s\n",
                txout.GetHash().ToString());
    }

    if (!pubCoin.validate())
        return state.DoS(100,
                false,
                PUBCOIN_NOT_VALIDATE,
                "CheckSigmaTransaction : PubCoin validation failed");

    if (sigmaTxInfo != NULL && !sigmaTxInfo->fInfoIsComplete) {
        // Update public coin list in the info
        sigmaTxInfo->mints.push_back(pubCoin);
        sigmaTxInfo->zcTransactions.insert(hashTx);
    }

    return true;
}

bool CheckSigmaTransaction(
        const CTransaction &tx,
        CValidationState &state,
        uint256 hashTx,
        bool isVerifyDB,
        int nHeight,
        bool isCheckWallet,
        CSigmaTxInfo *sigmaTxInfo)
{
    auto& consensus = ::Params().GetConsensus();

    // nHeight have special mode which value is INT_MAX so we need this.
    int realHeight;

    {
        LOCK(cs_main);
        realHeight = chainActive.Height();
    }

    if(tx.IsSigmaSpend() && realHeight >= consensus.nDisableUnpaddedSigmaBlock && realHeight < consensus.nSigmaPaddingBlock)
        return state.DoS(100, error("Sigma is disabled at this period."));

    bool allowSigma = (realHeight >= consensus.nSigmaStartBlock);

    // Check Mint Sigma Transaction
    if (allowSigma) {
        for (const CTxOut &txout : tx.vout) {
            if (!txout.scriptPubKey.empty() && txout.scriptPubKey.IsSigmaMint()) {
                if (!CheckSigmaMintTransaction(txout, state, hashTx, sigmaTxInfo))
                    return false;
            }
        }
    }

    // Check Sigma Spend Transaction
    if(tx.IsSigmaSpend()) {
        // First check number of inputs does not exceed transaction limit
        if (tx.vin.size() > consensus.nMaxSigmaInputPerBlock) {
            return state.DoS(100, false,
                REJECT_INVALID,
                "bad-txns-spend-invalid");
        }

        if (GetSpendAmount(tx) > consensus.nMaxValueSigmaSpendPerBlock) {
            return state.DoS(100, false,
                REJECT_INVALID,
                "bad-txns-spend-invalid");
        }

        vector<sigma::CoinDenomination> denominations;
        uint64_t totalValue = 0;
        BOOST_FOREACH(const CTxIn &txin, tx.vin){
            if(!txin.scriptSig.IsSigmaSpend()) {
                return state.DoS(100, false,
                                 REJECT_MALFORMED,
                                 "CheckSigmaSpendTransaction: can't mix zerocoin spend input with regular ones");
            }
            // Get the CoinDenomination value of each vin for the CheckSigmaSpendTransaction function
            uint32_t pubcoinId = txin.prevout.n;
            if (pubcoinId < 1 || pubcoinId >= INT_MAX) {
                // coin id should be positive integer
                return false;
            }

            CDataStream serializedCoinSpend((const char *)&*(txin.scriptSig.begin() + 1),
                                            (const char *)&*txin.scriptSig.end(),
                                            SER_NETWORK, PROTOCOL_VERSION);
            sigma::CoinSpend newSpend(SigmaParams, serializedCoinSpend);
            uint64_t denom = newSpend.getIntDenomination();
            totalValue += denom;
            sigma::CoinDenomination denomination;
            if (!IntegerToDenomination(denom, denomination, state))
                return false;
            denominations.push_back(denomination);
        }

        // Check vOut
        // Only one loop, we checked on the format before entering this case
        if (!isVerifyDB) {
            if (!CheckSigmaSpendTransaction(
                tx, denominations, state, hashTx, isVerifyDB, nHeight,
                isCheckWallet, sigmaTxInfo)) {
                    return false;
            }
        }
    }

    return true;
}

void DisconnectTipSigma(CBlock & /*block*/, CBlockIndex *pindexDelete) {
    sigmaState.RemoveBlock(pindexDelete);
}

Scalar GetSigmaSpendSerialNumber(const CTransaction &tx, const CTxIn &txin) {
    if (!tx.IsSigmaSpend())
        return Scalar(uint64_t(0));

    try {
        // NOTE(martun): +1 on the next line stands for 1 byte in which the opcode of
        // OP_SIGMASPEND is written. In zerocoin you will see +4 instead,
        // because the size of serialized spend is also written, probably in 3 bytes.
        CDataStream serializedCoinSpend(
                (const char *)&*(txin.scriptSig.begin() + 1),
                (const char *)&*txin.scriptSig.end(),
                SER_NETWORK, PROTOCOL_VERSION);
        sigma::CoinSpend spend(SigmaParams, serializedCoinSpend);
        return spend.getCoinSerialNumber();
    }
    catch (const std::ios_base::failure &) {
        return Scalar(uint64_t(0));
    }
}

CAmount GetSigmaSpendInput(const CTransaction &tx) {
    if (!tx.IsSigmaSpend())
        return CAmount(0);

    try {
        CAmount sum(0);
        BOOST_FOREACH(const CTxIn& txin, tx.vin){
            // NOTE(martun): +1 on the next line stands for 1 byte in which the opcode of
            // OP_ZEROCOINSPENDV3 is written. In zerocoin you will see +4 instead,
            // because the size of serialized spend is also written, probably in 3 bytes.
            CDataStream serializedCoinSpend(
                    (const char *)&*(txin.scriptSig.begin() + 1),
                    (const char *)&*txin.scriptSig.end(),
                    SER_NETWORK, PROTOCOL_VERSION);
            sigma::CoinSpend spend(SigmaParams, serializedCoinSpend);
            sum += spend.getIntDenomination();
        }
        return sum;
    }
    catch (const std::runtime_error &) {
        return CAmount(0);
    }
}

/**
 * Connect a new ZCblock to chainActive. pblock is either NULL or a pointer to a CBlock
 * corresponding to pindexNew, to bypass loading it again from disk.
 */
bool ConnectBlockSigma(
        CValidationState &state,
        const CChainParams &chainparams,
        CBlockIndex *pindexNew,
        const CBlock *pblock,
        bool fJustCheck) {
    // Add zerocoin transaction information to index
    if (pblock && pblock->sigmaTxInfo) {
        if (!fJustCheck) {
            pindexNew->sigmaMintedPubCoins.clear();
            pindexNew->sigmaSpentSerials.clear();
        }

        if (!CheckSigmaBlock(state, *pblock)) {
            return false;
        }

        BOOST_FOREACH(auto& serial, pblock->sigmaTxInfo->spentSerials) {
            if (!CheckSigmaSpendSerial(
                    state,
                    pblock->sigmaTxInfo.get(),
                    serial.first,
                    pindexNew->nHeight,
                    true /* fConnectTip */
                    )) {
                return false;
            }

            if (!fJustCheck) {
                pindexNew->sigmaSpentSerials.insert(serial.first);
                sigmaState.AddSpend(serial.first);
            }
        }

        if (fJustCheck)
            return true;

        // Update pindexNew.sigmaMintedPubCoins
        BOOST_FOREACH(const sigma::PublicCoin& mint, pblock->sigmaTxInfo->mints) {
            sigma::CoinDenomination denomination = mint.getDenomination();
            int mintId = sigmaState.AddMint(pindexNew,	mint);

            LogPrintf("ConnectTipZC: mint added denomination=%d, id=%d\n", denomination, mintId);
            pair<sigma::CoinDenomination, int> denomAndId = make_pair(denomination, mintId);
            pindexNew->sigmaMintedPubCoins[denomAndId].push_back(mint);
        }
    }
    else if (!fJustCheck) { // TODO(martun): not sure if this else is necessary here. Check again later.
        sigmaState.AddBlock(pindexNew);
    }
    return true;
}

bool BuildSigmaStateFromIndex(CChain *chain) {
    sigmaState.Reset();
    for (CBlockIndex *blockIndex = chain->Genesis(); blockIndex; blockIndex=chain->Next(blockIndex))
    {
        sigmaState.AddBlock(blockIndex);
    }
    // DEBUG
    LogPrintf(
        "Latest IDs for sigma coin groups are %d, %d, %d, %d, %d\n",
        sigmaState.GetLatestCoinID(CoinDenomination::SIGMA_DENOM_0_1),
        sigmaState.GetLatestCoinID(CoinDenomination::SIGMA_DENOM_0_5),
        sigmaState.GetLatestCoinID(CoinDenomination::SIGMA_DENOM_1),
        sigmaState.GetLatestCoinID(CoinDenomination::SIGMA_DENOM_10),
        sigmaState.GetLatestCoinID(CoinDenomination::SIGMA_DENOM_100));
    return true;
}

void CSigmaTxInfo::Complete() {
    // We need to sort mints lexicographically by serialized value of pubCoin. That's the way old code
    // works, we need to stick to it. Denomination doesn't matter but we will sort by it as well
    sort(mints.begin(), mints.end(),
            [](decltype(mints)::const_reference m1, decltype(mints)::const_reference m2)->bool {
            CDataStream ds1(SER_DISK, CLIENT_VERSION), ds2(SER_DISK, CLIENT_VERSION);
            ds1 << m1;
            ds2 << m2;
            return ds1.str() < ds2.str();
            });

    // Mark this info as complete
    fInfoIsComplete = true;
}

// CSigmaState

CSigmaState::CSigmaState() {
}

int CSigmaState::AddMint(
        CBlockIndex *index,
        const sigma::PublicCoin &pubCoin) {
    sigma::CoinDenomination denomination = pubCoin.getDenomination();

    if (latestCoinIds[denomination] < 1)
        latestCoinIds[denomination] = 1;
    int	mintCoinGroupId = latestCoinIds[denomination];

    // ZC_SPEND_V3_COINSPERID = 15.000, yet the actual limit of coins per accumlator is 16.000.
    // We need to cut at 15.000, such that we always have enough space for new mints. Mints for
    // each block will end up in the same accumulator.
    SigmaCoinGroupInfo &coinGroup = coinGroups[make_pair(denomination, mintCoinGroupId)];
    int coinsPerId = ZC_SPEND_V3_COINSPERID;
    if (coinGroup.nCoins < coinsPerId // there's still space in the accumulator
        || coinGroup.lastBlock == index // or we have already placed some coins from current block.
        ) {
        if (coinGroup.nCoins++ == 0) {
            // first group of coins for given denomination
            assert(coinGroup.firstBlock == nullptr);
            assert(coinGroup.lastBlock == nullptr);

            coinGroup.firstBlock = coinGroup.lastBlock = index;
        } else {
            assert(coinGroup.firstBlock != nullptr);
            assert(coinGroup.lastBlock != nullptr);
            if (coinGroup.lastBlock->nHeight > index->nHeight) {
                assert(coinGroup.lastBlock->nHeight <= index->nHeight);
            }

            coinGroup.lastBlock = index;
        }
    }
    else {
        latestCoinIds[denomination] = ++mintCoinGroupId;
        SigmaCoinGroupInfo& newCoinGroup = coinGroups[std::make_pair(denomination, mintCoinGroupId)];
        newCoinGroup.firstBlock = newCoinGroup.lastBlock = index;
        newCoinGroup.nCoins = 1;
    }
    CMintedCoinInfo coinInfo;
    coinInfo.denomination = denomination;
    coinInfo.id = mintCoinGroupId;
    coinInfo.nHeight = index->nHeight;
    mintedPubCoins.insert(std::make_pair(pubCoin, coinInfo));
    return mintCoinGroupId;
}

void CSigmaState::AddSpend(const Scalar &serial) {
    usedCoinSerials.insert(serial);
}

void CSigmaState::AddBlock(CBlockIndex *index) {
    BOOST_FOREACH(
        const PAIRTYPE(PAIRTYPE(sigma::CoinDenomination, int), vector<sigma::PublicCoin>) &pubCoins,
            index->sigmaMintedPubCoins) {
        if (!pubCoins.second.empty()) {
            SigmaCoinGroupInfo& coinGroup = coinGroups[pubCoins.first];

            if (coinGroup.firstBlock == NULL)
                coinGroup.firstBlock = index;
            coinGroup.lastBlock = index;
            coinGroup.nCoins += pubCoins.second.size();
        }

        latestCoinIds[pubCoins.first.first] = pubCoins.first.second;
        BOOST_FOREACH(const sigma::PublicCoin &coin, pubCoins.second) {
            CMintedCoinInfo coinInfo;
            coinInfo.denomination = pubCoins.first.first;
            coinInfo.id = pubCoins.first.second;
            coinInfo.nHeight = index->nHeight;
            mintedPubCoins.insert(pair<sigma::PublicCoin, CMintedCoinInfo>(coin, coinInfo));
        }
    }

    BOOST_FOREACH(const Scalar &serial, index->sigmaSpentSerials) {
        usedCoinSerials.insert(serial);
    }
}

void CSigmaState::RemoveBlock(CBlockIndex *index) {
    // roll back accumulator updates
    BOOST_FOREACH(
        const PAIRTYPE(PAIRTYPE(sigma::CoinDenomination, int),vector<sigma::PublicCoin>) &coin,
        index->sigmaMintedPubCoins)
    {
        SigmaCoinGroupInfo   &coinGroup = coinGroups[coin.first];
        int  nMintsToForget = coin.second.size();

        assert(coinGroup.nCoins >= nMintsToForget);

        if ((coinGroup.nCoins -= nMintsToForget) == 0) {
            // all the coins of this group have been erased, remove the group altogether
            coinGroups.erase(coin.first);
            // decrease pubcoin id for this denomination
            latestCoinIds[coin.first.first]--;
        }
        else {
            // roll back lastBlock to previous position
            assert(coinGroup.lastBlock == index);

            do {
                assert(coinGroup.lastBlock != coinGroup.firstBlock);
                coinGroup.lastBlock = coinGroup.lastBlock->pprev;
            } while (coinGroup.lastBlock->sigmaMintedPubCoins.count(coin.first) == 0);
        }
    }

    // roll back mints
    BOOST_FOREACH(const PAIRTYPE(PAIRTYPE(sigma::CoinDenomination, int),vector<sigma::PublicCoin>) &pubCoins,
                  index->sigmaMintedPubCoins) {
        BOOST_FOREACH(const sigma::PublicCoin &coin, pubCoins.second) {
            auto coins = mintedPubCoins.equal_range(coin);
            auto coinIt = find_if(
                coins.first, coins.second,
                [=](const decltype(mintedPubCoins)::value_type &v) {
                    return v.second.denomination == pubCoins.first.first &&
                        v.second.id == pubCoins.first.second;
                });
            assert(coinIt != coins.second);
            mintedPubCoins.erase(coinIt);
        }
    }

    index->sigmaMintedPubCoins.clear();

    // roll back spends
    BOOST_FOREACH(const Scalar &serial, index->sigmaSpentSerials) {
        usedCoinSerials.erase(serial);
    }

    index->sigmaSpentSerials.clear();
}

bool CSigmaState::GetCoinGroupInfo(
        sigma::CoinDenomination denomination,
        int group_id,
        SigmaCoinGroupInfo& result) {
    std::pair<sigma::CoinDenomination, int> key =
        std::make_pair(denomination, group_id);
    if (coinGroups.count(key) == 0)
        return false;

    result = coinGroups[key];
    return true;
}

bool CSigmaState::IsUsedCoinSerial(const Scalar &coinSerial) {
    return usedCoinSerials.count(coinSerial) != 0;
}

bool CSigmaState::HasCoin(const sigma::PublicCoin& pubCoin) {
    return mintedPubCoins.find(pubCoin) != mintedPubCoins.end();
}

int CSigmaState::GetCoinSetForSpend(
        CChain *chain,
        int maxHeight,
        sigma::CoinDenomination denomination,
        int coinGroupID,
        uint256& blockHash_out,
        std::vector<sigma::PublicCoin>& coins_out) {

    pair<sigma::CoinDenomination, int> denomAndId = std::make_pair(denomination, coinGroupID);

    if (coinGroups.count(denomAndId) == 0)
        return 0;

    SigmaCoinGroupInfo coinGroup = coinGroups[denomAndId];

    int numberOfCoins = 0;
    for (CBlockIndex *block = coinGroup.lastBlock;
            ;
            block = block->pprev) {
        if (block->sigmaMintedPubCoins[denomAndId].size() > 0) {
            if (block->nHeight <= maxHeight) {
                if (numberOfCoins == 0) {
                    // latest block satisfying given conditions
                    // remember block hash
                    blockHash_out = block->GetBlockHash();
                }
                numberOfCoins += block->sigmaMintedPubCoins[denomAndId].size();
                coins_out.insert(coins_out.end(),
                        block->sigmaMintedPubCoins[denomAndId].begin(),
                        block->sigmaMintedPubCoins[denomAndId].end());
            }
        }
        if (block == coinGroup.firstBlock) {
            break ;
        }
    }
    return numberOfCoins;
}

std::pair<int, int> CSigmaState::GetMintedCoinHeightAndId(
        const sigma::PublicCoin& pubCoin) {
    auto coinIt = mintedPubCoins.find(pubCoin);

    if (coinIt != mintedPubCoins.end()) {
        return std::make_pair(coinIt->second.nHeight, coinIt->second.id);
    }
    return std::make_pair(-1, -1);
}

bool CSigmaState::AddSpendToMempool(const vector<Scalar> &coinSerials, uint256 txHash) {
    BOOST_FOREACH(Scalar coinSerial, coinSerials){
        if (IsUsedCoinSerial(coinSerial) || mempoolCoinSerials.count(coinSerial))
            return false;

        mempoolCoinSerials[coinSerial] = txHash;
    }

    return true;
}

bool CSigmaState::AddSpendToMempool(const Scalar &coinSerial, uint256 txHash) {
    if (IsUsedCoinSerial(coinSerial) || mempoolCoinSerials.count(coinSerial))
        return false;

    mempoolCoinSerials[coinSerial] = txHash;
    return true;
}

void CSigmaState::RemoveSpendFromMempool(const Scalar& coinSerial) {
    mempoolCoinSerials.erase(coinSerial);
}

uint256 CSigmaState::GetMempoolConflictingTxHash(const Scalar& coinSerial) {
    if (mempoolCoinSerials.count(coinSerial) == 0)
        return uint256();

    return mempoolCoinSerials[coinSerial];
}

bool CSigmaState::CanAddSpendToMempool(const Scalar& coinSerial) {
    return !IsUsedCoinSerial(coinSerial) && mempoolCoinSerials.count(coinSerial) == 0;
}

void CSigmaState::Reset() {
    coinGroups.clear();
    usedCoinSerials.clear();
    latestCoinIds.clear();
    mintedPubCoins.clear();
    mempoolCoinSerials.clear();
}

CSigmaState* CSigmaState::GetState() {
    return &sigmaState;
}

int CSigmaState::GetLatestCoinID(sigma::CoinDenomination denomination) const {
    auto iter = latestCoinIds.find(denomination);
    if (iter == latestCoinIds.end()) {
        // Do not throw here, if there was no sigma mint, that's fine.
        return 0;
    }
    return iter->second;
}

} // end of namespace sigma.


#ifndef __abstract_MSSMNoFV_onshell_mass_eigenstates_gm2calc_1_3_0_hpp__
#define __abstract_MSSMNoFV_onshell_mass_eigenstates_gm2calc_1_3_0_hpp__

#include <cstddef>
#include <iostream>
#include <string>
#include <ostream>
#include <Eigen/Core>
#include <complex>
#include "gambit/Backends/abstractbase.hpp"
#include "forward_decls_abstract_classes.hpp"
#include "forward_decls_wrapper_classes.hpp"
#include "wrapper_MSSMNoFV_onshell_physical_decl.hpp"
#include "wrapper_MSSMNoFV_onshell_problems_decl.hpp"
#include "wrapper_MSSMNoFV_onshell_soft_parameters_decl.hpp"

#include "identification.hpp"

namespace CAT_3(BACKENDNAME,_,SAFE_VERSION)
{

   namespace gm2calc
   {
      class Abstract_MSSMNoFV_onshell_mass_eigenstates : virtual public gm2calc::Abstract_MSSMNoFV_onshell_soft_parameters
      {
         public:

            virtual void calculate_DRbar_masses() =0;

            virtual void clear() =0;

            virtual void clear_DRbar_parameters() =0;

            virtual void copy_DRbar_masses_to_pole_masses() =0;

            virtual void do_force_output(bool) =0;

            virtual bool do_force_output() const =0;

            virtual void reorder_DRbar_masses() =0;

            virtual void reorder_pole_masses() =0;

            virtual void set_physical__BOSS(const gm2calc::Abstract_MSSMNoFV_onshell_physical&) =0;

            virtual const gm2calc::Abstract_MSSMNoFV_onshell_physical& get_physical__BOSS() const =0;

            virtual gm2calc::Abstract_MSSMNoFV_onshell_physical& get_physical__BOSS() =0;

            virtual const gm2calc::Abstract_MSSMNoFV_onshell_problems& get_problems__BOSS() const =0;

            virtual gm2calc::Abstract_MSSMNoFV_onshell_problems& get_problems__BOSS() =0;

            virtual int solve_ewsb_tree_level() =0;

            virtual int solve_ewsb() =0;

            virtual ::std::basic_string<char, std::char_traits<char>, std::allocator<char> > name() const =0;

            virtual void print(::std::basic_ostream<char, std::char_traits<char> >&) const =0;

            virtual double get_MVG() const =0;

            virtual double get_MGlu() const =0;

            virtual double get_MVP() const =0;

            virtual double get_MVZ() const =0;

            virtual double get_MFd() const =0;

            virtual double get_MFs() const =0;

            virtual double get_MFb() const =0;

            virtual double get_MFu() const =0;

            virtual double get_MFc() const =0;

            virtual double get_MFt() const =0;

            virtual double get_MFve() const =0;

            virtual double get_MFvm() const =0;

            virtual double get_MFvt() const =0;

            virtual double get_MFe() const =0;

            virtual double get_MFm() const =0;

            virtual double get_MFtau() const =0;

            virtual double get_MSveL() const =0;

            virtual double get_MSvmL() const =0;

            virtual double get_MSvtL() const =0;

            virtual const ::Eigen::Array<double, 2, 1, 0, 2, 1>& get_MSd() const =0;

            virtual double get_MSd(int) const =0;

            virtual const ::Eigen::Array<double, 2, 1, 0, 2, 1>& get_MSu() const =0;

            virtual double get_MSu(int) const =0;

            virtual const ::Eigen::Array<double, 2, 1, 0, 2, 1>& get_MSe() const =0;

            virtual double get_MSe(int) const =0;

            virtual const ::Eigen::Array<double, 2, 1, 0, 2, 1>& get_MSm() const =0;

            virtual double get_MSm(int) const =0;

            virtual const ::Eigen::Array<double, 2, 1, 0, 2, 1>& get_MStau() const =0;

            virtual double get_MStau(int) const =0;

            virtual const ::Eigen::Array<double, 2, 1, 0, 2, 1>& get_MSs() const =0;

            virtual double get_MSs(int) const =0;

            virtual const ::Eigen::Array<double, 2, 1, 0, 2, 1>& get_MSc() const =0;

            virtual double get_MSc(int) const =0;

            virtual const ::Eigen::Array<double, 2, 1, 0, 2, 1>& get_MSb() const =0;

            virtual double get_MSb(int) const =0;

            virtual const ::Eigen::Array<double, 2, 1, 0, 2, 1>& get_MSt() const =0;

            virtual double get_MSt(int) const =0;

            virtual const ::Eigen::Array<double, 2, 1, 0, 2, 1>& get_Mhh() const =0;

            virtual double get_Mhh(int) const =0;

            virtual const ::Eigen::Array<double, 2, 1, 0, 2, 1>& get_MAh() const =0;

            virtual double get_MAh(int) const =0;

            virtual const ::Eigen::Array<double, 2, 1, 0, 2, 1>& get_MHpm() const =0;

            virtual double get_MHpm(int) const =0;

            virtual const ::Eigen::Array<double, 4, 1, 0, 4, 1>& get_MChi() const =0;

            virtual double get_MChi(int) const =0;

            virtual const ::Eigen::Array<double, 2, 1, 0, 2, 1>& get_MCha() const =0;

            virtual double get_MCha(int) const =0;

            virtual double get_MVWm() const =0;

            virtual ::Eigen::Array<double, 1, 1, 0, 1, 1> get_MChargedHiggs() const =0;

            virtual ::Eigen::Array<double, 1, 1, 0, 1, 1> get_MPseudoscalarHiggs() const =0;

            virtual const ::Eigen::Matrix<double, 2, 2, 0, 2, 2>& get_ZD() const =0;

            virtual double get_ZD(int, int) const =0;

            virtual const ::Eigen::Matrix<double, 2, 2, 0, 2, 2>& get_ZU() const =0;

            virtual double get_ZU(int, int) const =0;

            virtual const ::Eigen::Matrix<double, 2, 2, 0, 2, 2>& get_ZE() const =0;

            virtual double get_ZE(int, int) const =0;

            virtual const ::Eigen::Matrix<double, 2, 2, 0, 2, 2>& get_ZM() const =0;

            virtual double get_ZM(int, int) const =0;

            virtual const ::Eigen::Matrix<double, 2, 2, 0, 2, 2>& get_ZTau() const =0;

            virtual double get_ZTau(int, int) const =0;

            virtual const ::Eigen::Matrix<double, 2, 2, 0, 2, 2>& get_ZS() const =0;

            virtual double get_ZS(int, int) const =0;

            virtual const ::Eigen::Matrix<double, 2, 2, 0, 2, 2>& get_ZC() const =0;

            virtual double get_ZC(int, int) const =0;

            virtual const ::Eigen::Matrix<double, 2, 2, 0, 2, 2>& get_ZB() const =0;

            virtual double get_ZB(int, int) const =0;

            virtual const ::Eigen::Matrix<double, 2, 2, 0, 2, 2>& get_ZT() const =0;

            virtual double get_ZT(int, int) const =0;

            virtual const ::Eigen::Matrix<double, 2, 2, 0, 2, 2>& get_ZH() const =0;

            virtual double get_ZH(int, int) const =0;

            virtual const ::Eigen::Matrix<double, 2, 2, 0, 2, 2>& get_ZA() const =0;

            virtual double get_ZA(int, int) const =0;

            virtual const ::Eigen::Matrix<double, 2, 2, 0, 2, 2>& get_ZP() const =0;

            virtual double get_ZP(int, int) const =0;

            virtual const ::Eigen::Matrix<std::complex<double>, 4, 4, 0, 4, 4>& get_ZN() const =0;

            virtual const ::std::complex<double>& get_ZN(int, int) const =0;

            virtual const ::Eigen::Matrix<std::complex<double>, 2, 2, 0, 2, 2>& get_UM() const =0;

            virtual const ::std::complex<double>& get_UM(int, int) const =0;

            virtual const ::Eigen::Matrix<std::complex<double>, 2, 2, 0, 2, 2>& get_UP() const =0;

            virtual const ::std::complex<double>& get_UP(int, int) const =0;

            virtual void set_PhaseGlu(::std::complex<double>) =0;

            virtual ::std::complex<double> get_PhaseGlu() const =0;

            virtual double get_mass_matrix_VG() const =0;

            virtual void calculate_MVG() =0;

            virtual double get_mass_matrix_Glu() const =0;

            virtual void calculate_MGlu() =0;

            virtual double get_mass_matrix_VP() const =0;

            virtual void calculate_MVP() =0;

            virtual double get_mass_matrix_VZ() const =0;

            virtual void calculate_MVZ() =0;

            virtual double get_mass_matrix_Fd() const =0;

            virtual void calculate_MFd() =0;

            virtual double get_mass_matrix_Fs() const =0;

            virtual void calculate_MFs() =0;

            virtual double get_mass_matrix_Fb() const =0;

            virtual void calculate_MFb() =0;

            virtual double get_mass_matrix_Fu() const =0;

            virtual void calculate_MFu() =0;

            virtual double get_mass_matrix_Fc() const =0;

            virtual void calculate_MFc() =0;

            virtual double get_mass_matrix_Ft() const =0;

            virtual void calculate_MFt() =0;

            virtual double get_mass_matrix_Fve() const =0;

            virtual void calculate_MFve() =0;

            virtual double get_mass_matrix_Fvm() const =0;

            virtual void calculate_MFvm() =0;

            virtual double get_mass_matrix_Fvt() const =0;

            virtual void calculate_MFvt() =0;

            virtual double get_mass_matrix_Fe() const =0;

            virtual void calculate_MFe() =0;

            virtual double get_mass_matrix_Fm() const =0;

            virtual void calculate_MFm() =0;

            virtual double get_mass_matrix_Ftau() const =0;

            virtual void calculate_MFtau() =0;

            virtual double get_mass_matrix_SveL() const =0;

            virtual void calculate_MSveL() =0;

            virtual double get_mass_matrix_SvmL() const =0;

            virtual void calculate_MSvmL() =0;

            virtual double get_mass_matrix_SvtL() const =0;

            virtual void calculate_MSvtL() =0;

            virtual ::Eigen::Matrix<double, 2, 2, 0, 2, 2> get_mass_matrix_Sd() const =0;

            virtual void calculate_MSd() =0;

            virtual ::Eigen::Matrix<double, 2, 2, 0, 2, 2> get_mass_matrix_Su() const =0;

            virtual void calculate_MSu() =0;

            virtual ::Eigen::Matrix<double, 2, 2, 0, 2, 2> get_mass_matrix_Se() const =0;

            virtual void calculate_MSe() =0;

            virtual ::Eigen::Matrix<double, 2, 2, 0, 2, 2> get_mass_matrix_Sm() const =0;

            virtual void calculate_MSm() =0;

            virtual ::Eigen::Matrix<double, 2, 2, 0, 2, 2> get_mass_matrix_Stau() const =0;

            virtual void calculate_MStau() =0;

            virtual ::Eigen::Matrix<double, 2, 2, 0, 2, 2> get_mass_matrix_Ss() const =0;

            virtual void calculate_MSs() =0;

            virtual ::Eigen::Matrix<double, 2, 2, 0, 2, 2> get_mass_matrix_Sc() const =0;

            virtual void calculate_MSc() =0;

            virtual ::Eigen::Matrix<double, 2, 2, 0, 2, 2> get_mass_matrix_Sb() const =0;

            virtual void calculate_MSb() =0;

            virtual ::Eigen::Matrix<double, 2, 2, 0, 2, 2> get_mass_matrix_St() const =0;

            virtual void calculate_MSt() =0;

            virtual ::Eigen::Matrix<double, 2, 2, 0, 2, 2> get_mass_matrix_hh() const =0;

            virtual void calculate_Mhh() =0;

            virtual ::Eigen::Matrix<double, 2, 2, 0, 2, 2> get_mass_matrix_Ah() const =0;

            virtual void calculate_MAh() =0;

            virtual ::Eigen::Matrix<double, 2, 2, 0, 2, 2> get_mass_matrix_Hpm() const =0;

            virtual void calculate_MHpm() =0;

            virtual ::Eigen::Matrix<double, 4, 4, 0, 4, 4> get_mass_matrix_Chi() const =0;

            virtual void calculate_MChi() =0;

            virtual ::Eigen::Matrix<double, 2, 2, 0, 2, 2> get_mass_matrix_Cha() const =0;

            virtual void calculate_MCha() =0;

            virtual double get_mass_matrix_VWm() const =0;

            virtual void calculate_MVWm() =0;

            virtual double get_ewsb_eq_hh_1() const =0;

            virtual double get_ewsb_eq_hh_2() const =0;

            virtual double ThetaW() const =0;

            virtual double v() const =0;

         public:
            using gm2calc::Abstract_MSSMNoFV_onshell_soft_parameters::pointer_assign__BOSS;
            virtual void pointer_assign__BOSS(Abstract_MSSMNoFV_onshell_mass_eigenstates*) =0;
            virtual Abstract_MSSMNoFV_onshell_mass_eigenstates* pointer_copy__BOSS() =0;

         private:
            MSSMNoFV_onshell_mass_eigenstates* wptr;
            bool delete_wrapper;
         public:
            MSSMNoFV_onshell_mass_eigenstates* get_wptr() { return wptr; }
            void set_wptr(MSSMNoFV_onshell_mass_eigenstates* wptr_in) { wptr = wptr_in; }
            bool get_delete_wrapper() { return delete_wrapper; }
            void set_delete_wrapper(bool del_wrp_in) { delete_wrapper = del_wrp_in; }

         public:
            Abstract_MSSMNoFV_onshell_mass_eigenstates()
            {
               wptr = 0;
               delete_wrapper = false;
            }

            Abstract_MSSMNoFV_onshell_mass_eigenstates(const Abstract_MSSMNoFV_onshell_mass_eigenstates& in) :
               gm2calc::Abstract_MSSMNoFV_onshell_susy_parameters(in), gm2calc::Abstract_MSSMNoFV_onshell_soft_parameters(in)
            {
               wptr = 0;
               delete_wrapper = false;
            }

            Abstract_MSSMNoFV_onshell_mass_eigenstates& operator=(const Abstract_MSSMNoFV_onshell_mass_eigenstates&) { return *this; }

            virtual void init_wrapper() =0;

            MSSMNoFV_onshell_mass_eigenstates* get_init_wptr()
            {
               init_wrapper();
               return wptr;
            }

            MSSMNoFV_onshell_mass_eigenstates& get_init_wref()
            {
               init_wrapper();
               return *wptr;
            }

            virtual ~Abstract_MSSMNoFV_onshell_mass_eigenstates() =0;
      };
   }

}

#include "gambit/Backends/backend_undefs.hpp"

#endif /* __abstract_MSSMNoFV_onshell_mass_eigenstates_gm2calc_1_3_0_hpp__ */