#include "SLIC.hpp"

#include <iostream>
#include <string>
#include <filesystem>
#include <chrono>
#include <cmath>
#include <boost/filesystem/convenience.hpp>

Slic::Slic(std::string filename){
  if_path = filename;
  std::cout << "Loading " << filename << " pixel data..." << std::endl;
  if(!image.loadFromFile(filename)){
    std::cout << "Problem loading filename: " << filename << std::endl;
    ready = false;
  }
  greyscale = false;
  ready = true;
  height = image.getSize().y;
  width = image.getSize().x;
  total_pixels = height * width;
  num_cuts = 0;
  std::cout << "Loaded " << width << "x" << height << " file " << filename << std::endl
    << "Total pixels: " << total_pixels << std::endl;
}

Slic::~Slic(){

}

//O(1) constant
double Slic::getColorDistance(HSV c1, HSV c2){
  //Remember, these are coming in as
  // Hue 0-360
  // Saturation 0-100
  // Value 0 - 100
  double h1 = c1.hue * deg_to_rad;
  double h2 = c2.hue * deg_to_rad;
  double s1 = c1.saturation/100.0;
  double s2 = c2.saturation/100.0;
  double v1 = c1.value / 100.0;
  double v2 = c2.value / 100.0;

  //We're going to project the color value into the HSV color space and then measure the distance.
  double p1 = std::pow(std::sin(h1)*s1*v1 - std::sin(h2)*s2*v2,2);
  double p2 = std::pow(std::cos(h1)*s1*v1 - std::cos(h2)*s2*v2,2);
  double p3 = std::pow(v1 - v2,2);
  double dist2 = p1 + p2 + p3; //Our distance squared.
  return sqrt(dist2);
}

//O(1) constant
HSV Slic::rgb2hsv(RGBA rgb){
	float MAX = std::max(std::max(rgb.r,rgb.g), rgb.b);
	float MIN = std::min(std::min(rgb.r,rgb.g), rgb.b);
	float hue=0;

  float eps = 0.00001;
	if(std::abs(MAX-MIN)<eps){
		hue = 0;
  }
	else if(std::abs(MAX-rgb.r)<eps){
		hue = 60*((rgb.g - rgb.b)/(MAX-MIN));
  }
	else if(std::abs(MAX-rgb.g)<eps){
		hue = 60*(2 + (rgb.b - rgb.r)/(MAX-MIN));
  }
	else if(std::abs(MAX-rgb.b)<eps){
		hue = 60*(4 + (rgb.r - rgb.g)/(MAX-MIN));
  }
	if(hue < 0)
		hue+=360;
	int saturation = 0;
	if(MAX==0)
		saturation = 0;
	else
		saturation = (MAX-MIN)/MAX * 100;
	int value = (MAX + MIN)/2.0 * 100;
	return HSV{hue,saturation,value};
}

// O(n) operation. Passes each pixel once.
void Slic::convertToGreyscale(){
  if(!ready)
    return;
  for(unsigned int i=0; i<height; i++){
    for(unsigned int j=0; j<width; j++){
      sf::Color pixel_color = image.getPixel(j,i);
      float gs = (pixel_color.r + pixel_color.g + pixel_color.b)/3; // Averages the pixel color in rgb. Which converts it to grey.
      image.setPixel(j,i, sf::Color(gs,gs,gs,255));
    }
  }
  greyscale = true;
}

void Slic::loadToGraph(){
  //Pixels are stored in a c style array in RGBA pixel format made of 8 bit integers.
  const sf::Uint8 *pixel_data = image.getPixelsPtr();
  for(unsigned int i=0; i<height; i++){
    for(unsigned int j=0; j<width; j++){
      //We're i units down height wise, and for each we have passed the width once. Then we're j units over on the current line.
      // 0 0 0 1
      // 1 0 2 0
      // 2 0 X 0
      // X is at spot (2,2), with a width of 4, and j of 2, we would have index = (2*4)+2 = 10. Which is correct.
      unsigned int index = (i*width) + j;
      //Extract our colors from the index
      sf::Uint8 r = pixel_data[4*index];
      sf::Uint8 g = pixel_data[4*index+1];
      sf::Uint8 b = pixel_data[4*index+2];
      sf::Uint8 a = pixel_data[4*index+3];
      //Convert to HSV colorspace.
      RGBA rgba = { int(r)/255.0, int(g)/255.0, int(b)/255.0, int(a)/255.0};
      HSV hsv = rgb2hsv(rgba);
      boost::add_vertex(vertex_data{rgba, hsv, std::vector<int>()}, graph);
    }
  }
  graph_loaded = true;
}
#include <cstdlib>
#include <ctime>
sf::Color getComponentColor(int component_id){
	int some_prime = 31907;
	srand(component_id * some_prime);
	int r = rand() % 256;
	int g = rand() % 256;
	int b = rand() % 256;
	return sf::Color(r,g,b,255);
}

struct rgb{
  double r;
  double g;
  double b;
};
//I legit yoinked this one from online.
RGBA hsv2rgb(HSV in)
{
    double      hh, p, q, t, ff;
    long        i;
    RGBA         out;
    in.saturation /= 100.0;
    in.value /= 100.0;
    if(in.saturation <= 0.0) {       // < is bogus, just shuts up warnings
        out.r = in.value;
        out.g = in.value;
        out.b = in.value;
        return out;
    }
    hh = in.hue;
    if(hh >= 360.0) hh = 0.0;
    hh /= 60.0;
    i = (long)hh;
    ff = hh - i;
    p = in.value * (1.0 - in.saturation);
    q = in.value * (1.0 - (in.saturation * ff));
    t = in.value * (1.0 - (in.saturation * (1.0 - ff)));

    switch(i) {
    case 0:
        out.r = in.value;
        out.g = t;
        out.b = p;
        break;
    case 1:
        out.r = q;
        out.g = in.value;
        out.b = p;
        break;
    case 2:
        out.r = p;
        out.g = in.value;
        out.b = t;
        break;

    case 3:
        out.r = p;
        out.g = q;
        out.b = in.value;
        break;
    case 4:
        out.r = t;
        out.g = p;
        out.b = in.value;
        break;
    case 5:
    default:
        out.r = in.value;
        out.g = p;
        out.b = q;
        break;
    }
    out.a = 255;
    return out;
}

void Slic::writeToDisk(std::string out_dir){
  if(!ready || !graph_loaded){
    std::cout << "Shit isn't ready:" << std::endl;
    return;
  }
  //Create a new pixel array.
	sf::Uint8 out_pixels[total_pixels*4];
  // const sf::Uint8 *pixel_data = image.getPixelsPtr();
	//Filling our pixel array with new colors based on our connected components vector.
	auto vp = boost::vertices(graph);
  std::cout << "Writing to disk" << std::endl;
	for(auto iter = vp.first; iter!=vp.second; iter++){
		unsigned int index = *iter;
    // std::cout << graph[index].cluster_ids[0] << std::endl;
    sf::Color col = getComponentColor(graph[index].cluster_ids[0]);
    // std::cout << graph[index].cluster_ids[0] << col.r << " " << col.g << std::endl;

    out_pixels[4*index] = col.r;
		out_pixels[4*index+1] = col.g;
		out_pixels[4*index+2] = col.b;
		out_pixels[4*index+3] = 255;
		// out_pixels[4*index] = graph[index].rgba.r;
		// out_pixels[4*index+1] = graph[index].rgba.g;
		// out_pixels[4*index+2] = graph[index].rgba.b;
		// out_pixels[4*index+3] = graph[index].rgba.a;
    // HSV hs = clusters[graph[index].cluster_ids[0]].color;
    // RGBA col = hsv2rgb(hs);
    // out_pixels[4*index] = col.r*255;
    // out_pixels[4*index+1] = col.g*255;
    // out_pixels[4*index+2] = col.b*255;
    // out_pixels[4*index+3] = 255;
	}

  //Strip path from input
  std::filesystem::path input_path(if_path);
  std::string no_path = input_path.filename();
  //Strip extension from pathless input
  std::filesystem::path pathless_path(no_path);
  std::string base_path = pathless_path.stem();
  std::string extension = "";
  if(pathless_path.has_extension())
    extension += pathless_path.extension().string();

  //Create our output filename
  base_path += "_output";
  if(greyscale)
    base_path += "_greyscale";
  if(extension.size()>0)
    base_path += extension;
  std::string out_path = out_dir + base_path;

  //Create our image to write and write it.
  sf::Image out_image;
  out_image.create(width, height, out_pixels);
  out_image.saveToFile(out_path);
}

void Slic::cleanEdges(){
  auto vp = boost::vertices(graph);
  //For every vertex/pixel.
  for(auto iter = vp.first; iter!=vp.second; iter++){
    //Check it against all associated cluster ids.
    edge_data best_fit = { -1 , -1 };
    if(graph[*iter].cluster_ids.size()==0)
      continue;
    for(int id : graph[*iter].cluster_ids){
      //It might be less costly to just remove each edge as we check it then add the last one. The other option is to loop twice.
      //Convert cluster coordinates to an indice
      int ci = clusters[id].cpos.y * width + clusters[id].cpos.x;

      auto e = boost::edge(*iter, ci, graph).first;
      //Find the best fitting edge and remove all of the rest.
      if(best_fit.distance == -1){
        best_fit = graph[e];
        boost::remove_edge(e,graph);
      }
      else if(best_fit.distance > graph[e].distance){
        best_fit = graph[e];
        boost::remove_edge(e,graph);
      }
    }
    graph[*iter].cluster_ids.clear();
    //Reinsert the best edge. wasteful but fuck it.
    int ci = clusters[best_fit.cluster_id].cpos.y * width + clusters[best_fit.cluster_id].cpos.x;
    boost::add_edge(*iter, ci, best_fit, graph);
    graph[*iter].cluster_ids.push_back(best_fit.cluster_id);
  }
}

//O(n) - We hit every pixel or vertex in this algorithm.
void Slic::updateClusterAverages(){
  // 1. Generate connected component vector to find which
  // 2.
  if(!graph_loaded)
    return;
  auto vp = boost::vertices(graph);
  //Containers to store shit in
  std::vector<HSV> colors(clusters.size(),{0,0,0});
  std::vector<int> color_count(clusters.size(),0);
  //Loop through and add up all of the color values for each cluster.
  for(auto iter = vp.first; iter!=vp.second; iter++){
    auto v = graph[*iter];
    if(v.cluster_ids.size()==0){
      return;
    }
    colors[v.cluster_ids[0]].hue+= v.hsv.hue;
    colors[v.cluster_ids[0]].saturation += v.hsv.saturation;
    colors[v.cluster_ids[0]].value += v.hsv.value;
    color_count[v.cluster_ids[0]]++;
  }
  //since we used the cluster id as the index, we can loop through just once to average it all and set it.
  for(unsigned int i=0; i<clusters.size(); i++){
    if(color_count[i]!=0){
      clusters[i].color.hue = colors[i].hue / color_count[i];
      clusters[i].color.saturation = colors[i].saturation / color_count[i];
      clusters[i].color.value = colors[i].value / color_count[i];
    }
  }
}

bool Slic::inBounds(unsigned int x, unsigned int y){
  //Can't be less than 0 since unsigned.
  //If our width is 10, then index 9 is on the edge.
//  std::cout << "Checking if : " << x << " " << y << " - " << width << " " << height << std::endl;
  if(x >= width || y >= height)
    return false;
  return true;
}
bool Slic::isEdge(unsigned int x, unsigned int y){
  if(x == width-1 || y == height-1)
    return true;
  if(x == 0 || y == 0)
    return true;
  return false;
}
void Slic::generateSuperpixels(unsigned int K, unsigned int error){
  this->K = K;
  unsigned int S = std::sqrt(total_pixels / K);
  std::cout << "S Value: " << S << " " << width << " " << height << std::endl;
  //Generate cluster centers.
  for(unsigned int i=0; i*S < height; i++){
    for(unsigned int j=0; j*S < width; j++){
      int x = j*S; // We're just j*S units over.
      int y = i*S; //We're i*S units down
      int indice = y*width + x;
      if(inBounds(x,y)){
        //Get cluster center color.
        HSV hsv = graph[indice].hsv;
        clusters.push_back(Cluster{hsv,vec2<int>{x,y}, clusters.size()});
        std::cout << "Cluster center(" << clusters.size() << "): " << x << " " << y << " | Color: " << hsv.hue << " " << hsv.saturation << " " << hsv.value <<  std::endl;
      }
    }
  }
  //todo - make these slightly more accurate and move off of edges.

  //So every pass we're going to do the following
  //Check every pixel around the cluster centers in a radius of 2S.
  //Draw edges between detected pixels and the cluster centers.
  //Clean edges
  //Calculate cluster color

  while(clusterPass(S*2,error)){
    /*
    How's it going today?
    For me...I just wrote almost 10000 lines of code for a graphics project that probably won't get a B and now I'm cramming this on may 12th and it's 9pm.
    Also it's my birthday yesterday and I think I spent it on caffeine and programming. Honestly not too bad. I got to learn a lot about opengl.
    I just started getting into geometry shaders yesterday and water stuff too. My water is super ghetto, I have like 3 implementations lmao. the first one was a
    mesh that I pulled the vertices on a sinfunction using elapsed time to simulate waves. I don't have much faith in this new algorithm, but I did do some research.
    Hopefully it's sufficient. I kinda wish I found a way to do graph cuts using boost graph library. I think if I had like 2 more days I could do this projecvt so much better.
    I'd probably do my own graph library and probably auto remove edges lmao.
    */
  }

}

bool Slic::clusterPass(int radius, unsigned int error){
  //For each cluster
  for(Cluster cluster : clusters){
    //Check every pixel in a square radius around it.
    for(int y=-radius; y<=radius; y++){
      for(int x=-radius; x<=radius; x++){
        int xpos = cluster.cpos.x + x;
        int ypos = cluster.cpos.y + y;
        if(inBounds(xpos,ypos)){
          int indice1 = ypos*width + xpos;
          int indice2 = cluster.cpos.y * width + cluster.cpos.x;
          //Add edge
          boost::add_edge(indice1,indice2, { getColorDistance(graph[indice1].hsv, graph[indice2].hsv), cluster.id},graph);
          //Update our node cluster id list.
          graph[indice1].cluster_ids.push_back(cluster.id);
        }
      }
    }
  }
  cleanEdges();
  updateClusterAverages();
  return updateClusterPositions(error);
}

//Updates cluster positions and calculates the error threshhold for stopping our loop.
bool Slic::updateClusterPositions(unsigned int error){
  if(!graph_loaded)
    return false;
  auto vp = boost::vertices(graph);
  //Containers to store shit in
  std::vector<vec2<int>> pos(clusters.size(),{0,0});
  std::vector<int> count(clusters.size(),0);
  //Loop through all of the vertices
  for(auto iter = vp.first; iter!=vp.second; iter++){
    auto v = graph[*iter];
    pos[v.cluster_ids[0]].x += clusters[v.cluster_ids[0]].cpos.x;
    pos[v.cluster_ids[0]].y += clusters[v.cluster_ids[0]].cpos.y;
    count[v.cluster_ids[0]]++;
  }
  for(unsigned int i = 0; i<count.size(); i++){
    if(count[i]!=0){
      //Compute error vector.
      vec2<int> npos;
      npos.x = pos[i].x / count[i];
      npos.y = pos[i].y / count[i];
      double l1 = std::sqrt(std::pow((npos.x),2) + std::pow(npos.y,2) + pow(clusters[i].color.hue,2) + pow(clusters[i].color.saturation,2) + pow(clusters[i].color.value,2));
      double l2 = std::sqrt(std::pow((clusters[i].cpos.x),2) + std::pow(clusters[i].cpos.y,2)+ pow(clusters[i].color.hue,2) + pow(clusters[i].color.saturation,2) + pow(clusters[i].color.value,2));

       std::cout << "v1: " << npos.x << " " << npos.y << " v2 " << clusters[i].cpos.x << " " << clusters[i].cpos.y << std::endl;
       if((l1-l2) < error && (npos.x !=0 && npos.y!=0)){
         std::cout << "Error distance reached: " << l1-l2 << std::endl;
         return false;
       }
      clusters[i].cpos = npos;
    }
    else{
      std::cout << "Sir we've encountered a critical error. DIV/0. Somehow there's a pixel with no shit associated." << std::endl;
    }
  }
  return true;
}


#include <iostream>
#include <cstdio>
#include <string>
#include <vector>
#include <fstream>
#include <Eigen/Eigenvalues>
#include <opencv2/opencv.hpp>
#include <algorithm>
#include <math.h>

#include "information.h"
#include "functionn.h"
#include "read.h"

using namespace std;
using Eigen::MatrixXd;
using Eigen::EigenSolver;
using Eigen::VectorXcd;
using Eigen::MatrixXcd;

string input,indicator1,indicator2 ;
string dataset = "ATT/";
string dash = "_";
string file_type = ".png";

double all_image_matrix[number_of_dataset][figure_height*figure_width] = { 0 };             //Create a vector*vector to store all image
vector<double> average_face;
vector<double> each_eigenvector(figure_height*figure_width,0);
vector<vector<double>> eigenvectors(number_of_eigenvectors,each_eigenvector);
double class_average_face[number_of_face_class][figure_height*figure_width] = { 0 };
double class_w[number_of_face_class][number_of_eigenvectors] = { 0 };
int test_index;
int main() {

	image_content image;                                                   //Create a class call "image_content" to store the information of image
	double each[figure_height*figure_width] = {0};                       //Create a vector to store one image

	//Data all_data;

	//Read and store the data from figure and store it into to image_matrix
	for (int i = 0, q = 1, w = 1; i < number_of_dataset && q < 41 && w < 11; i++) {
		indicator1 = to_string(q);
		indicator2 = to_string(w);
		input = dataset + indicator1 + dash + indicator2 + file_type;      //Read the file by substituting the character.
		image = read::figure(input);                                       //Via opencv to read the image and acquire the data in class "image_content" form.
		for (int j = 0; j < figure_height*figure_width; j++) {
			each[j] = (image.content[j]);                                  // Assign the data from class "image_content" to vector variable "each".
		}

		//This part is saving the image to a txt, in order to double check the matrix of input figure is correctly save into class "image_content" or not.
		//We save all data into a txt, then display it to check whether it is the original image or not.
		/*
		ofstream file;
		for (int i=0; i < image.height*image.width; i++) {

		file.open("test1.txt",fstream::app);
		file << image.content[i];
		file << "\n";
		file.close();
		cout << i << endl;
		cout << "\n";
		}
		cout << endl;
		*/

		//Store information of each figure to the image_matrix
		for (int k = 0; k < figure_height*figure_width; k++) {
			all_image_matrix[i][k] = each[k];
		}

		//This is just for the pointer.
		if (w < 11) { w++; }
		if (w == 11) { w = 1; q++; }
	}

	average_face = functionn::averagee(all_image_matrix);    //Feed the image_matrix to the function and acquire the average face.

	functionn::save_one_face(average_face);                     //This part is saving the average face to a txt, in order to double check the average face is normal or not.

	//********
	vector<vector<double>> A_matrix = functionn::calculate_A(all_image_matrix, average_face);
	//We got A!  (A = A_matrix)
	//********

	///////////////////////////////////////////////////////////////////////////////
	// Let's calculate eigenvectors!

	//We have two ways to calculate eigenvectors, first is via Matlab, the other is eigen.

	//This part is reading the eigenvector and eigenvalue which are calculated from matlab.
	//Read 6 eigenvalues and its relevant eigenvetors.
	string file_name = "For_matlab/eigenvector";
	string file_type = ".txt";
	ifstream eigenvector_file;
	for (int i = 1; i <= number_of_eigenvectors; i++) {
		int j = 0;
		cout << file_name + to_string(i) + file_type << endl;
		eigenvector_file.open(file_name + to_string(i) + file_type, ios::in);
		string line;
		double tem;
		while (getline(eigenvector_file, line)) {
			tem = stod(line);
			eigenvectors[i - 1][j] = tem;
			j++;
		}
		eigenvector_file.close();
	}

	//********
	//This is the eigen way
	//vector<vector<complex<double>>> eigenvectors = functionn::calculate_eigenvalues_and_eigenvectors(A_matrix);
	//********

	//Now we have the eigenvector(eigenface)!
	//Let's display our eigenface !

	//////////////////////////////////////////////////////////////////////////////////
	/*
	//This part is reflecting the pixel value to (0,1), then reflect it to (0,255)

	cout << eigenvectors[0][100] << endl;
	double maximum = 0;
	double minimum = 1;
	for (int count = 0; count < figure_height*figure_width; count++) {
		maximum = max(maximum, eigenvectors[display_index][count]);
		minimum = min(minimum, eigenvectors[display_index][count]);
	}

	for (int count = 0; count < figure_height*figure_width; count++) {
		eigenvectors[display_index][count] = (eigenvectors[display_index][count] - minimum) / (maximum - minimum) * 255;
	}

	//Finish reflecting to (0,255)!
	//////////////////////////////////////////////////////////////////////////////////

	//Assign the value in eigenface to file m (opencv Mat form) in order to display.
	if (eigenvectors[display_index].size() == figure_height*figure_width) // check that the rows and cols match the size of your vector
	{
		cv::Mat m(figure_height, figure_width, CV_8U); // initialize matrix of uchar of 1-channel where you will store vec data

		int p3 = 0;
		for (int p1 = 0; p1 < figure_height; p1++) {
			for (int p2 = 0; p2 < figure_width; p2++) {
				m.at<uchar>(p1, p2) = eigenvectors[display_index].at(p3);
				p3++;
			}
		}

		cv::imwrite("Eigenface.jpg", m);               //Save the eigenface!
	}

	*/
	//Now we have the essiential item to recognize face!
	cout << "Please tell me which image do you want to test? (0~49)" << endl;
	cin >> test_index;
	cout << endl;
	class_information all;
	//First calculate the aveverge face of each class
	all.calculate_all_average_face();

	//Let's calculate each omega of all classes
	all.calculate_all_w();

	//Now we got 40 omega(each omega indicates the weights of each class and has 6 dimensions(number_of_eigenvectors))
	//Definition of Omega refers to equation(8) in "Eigenfaces for Recognition" 1991.

	//Let's classify our image from class A, B and C!
	//Use equation(8) in "Eigenfaces for Recognition" 1991.

	classify result;

	result.calculate_w(all_image_matrix[test_index]);
	result.criteria_each();                          //Calculate the Euclidian distance
	result.determine_result();

	int ans;
	if (test_index < 10) {
		ans = 0;
	}
	else if (test_index < 20) {
		ans = 1;
	}
	else if (test_index < 30) {
		ans = 2;
	}
	else if (test_index < 40) {
		ans = 3;
	}
	else if (test_index < 50) {
		ans = 4;
	}
	cout << "And the ground truth is " << ans << " !" << endl;

	cout << endl;
	system("pause");
}


//==============================================================================
//         Copyright 2003 - 2011 LASMEA UMR 6602 CNRS/Univ. Clermont II
//         Copyright 2009 - 2011 LRI    UMR 8623 CNRS/Univ Paris Sud XI
//
//          Distributed under the Boost Software License, Version 1.0.
//                 See accompanying file LICENSE.txt or copy at
//                     http://www.boost.org/LICENSE_1_0.txt
//==============================================================================
#ifndef NT2_TRIGONOMETRIC_FUNCTIONS_COMPLEX_GENERIC_SINCOS_HPP_INCLUDED
#define NT2_TRIGONOMETRIC_FUNCTIONS_COMPLEX_GENERIC_SINCOS_HPP_INCLUDED
#include <nt2/trigonometric/functions/sincos.hpp>
#include <nt2/include/functions/sincos.hpp>
#include <nt2/include/functions/sinhcosh.hpp>
#include <nt2/include/functions/real.hpp>
#include <nt2/include/functions/imag.hpp>
#include <nt2/include/functions/is_imag.hpp>
#include <nt2/include/functions/is_real.hpp>
#include <nt2/include/functions/logical_and.hpp>
#include <nt2/include/functions/is_invalid.hpp>
#include <nt2/include/functions/is_inf.hpp>
#include <nt2/sdk/complex/meta/as_complex.hpp>
#include <nt2/sdk/complex/meta/as_real.hpp>
#include <nt2/sdk/meta/as_logical.hpp>
#include <nt2/include/functions/logical_or.hpp>
#include <nt2/include/functions/if_zero_else.hpp>
#include <nt2/include/functions/if_else.hpp>
#include <nt2/include/functions/any.hpp>
#include <nt2/include/constants/inf.hpp>
#include <nt2/include/constants/nan.hpp>
#include <boost/fusion/include/std_pair.hpp>

namespace nt2 { namespace ext
{
  BOOST_DISPATCH_IMPLEMENT  (sincos_, tag::cpu_,
                             (A0),
                             (generic_ < complex_<floating_ < A0> > > )
                             (generic_ < complex_<floating_ < A0> > > )
                             (generic_ < complex_<floating_ < A0> > > )
                            )
  {
    typedef void result_type;
    inline void operator()(A0 const& a0,A0 & a1,A0 & a2) const
    {
      typedef typename meta::as_real<A0>::type rtype;
      typedef typename meta::as_logical<A0>::type ltype;
      rtype c, s, ch, sh;
      sincos(nt2::real(a0), s, c);
      sinhcosh(nt2::imag(a0), sh, ch);
      rtype r1 = if_zero_else(is_imag(a0), s*ch);
      rtype i1 = if_zero_else(is_real(a0), c*sh);
      rtype r2 = c*ch;
      rtype i2 = if_zero_else(logical_or(is_imag(a0), is_real(a0)), -s*sh);
      a1 =  A0(r1, i1);
      a2 =  A0(r2, i2);
#ifndef BOOST_SIMD_NO_INVALIDS
      ltype t = logical_and(is_invalid(real(a0)), is_inf(imag(a0)));
      if (any(t))
      {
        A0 zs = A0(Nan<rtype>(),imag(a0));
        A0 zc = A0(Inf<rtype>(),Nan<rtype>());
        a1 = if_else(t, zs, a1);
        a2 = if_else(t, zc, a2);
      }
#endif

    }
  };

  BOOST_DISPATCH_IMPLEMENT  (sincos_, tag::cpu_,
                             (A0),
                             (generic_ < dry_<floating_ < A0> > > )
                             (generic_ < dry_<floating_ < A0> > > )
                             (generic_ < dry_<floating_ < A0> > > )
                            )
  {
    typedef void result_type;
    inline void operator()(A0 const& a0,A0 & a1,A0 & a2) const
    {
      typedef typename meta::as_real<A0>::type rtype;
      rtype c, s;
      sincos(nt2::real(a0), s, c);
      a1 =  bitwise_cast<A0>(c);
      a2 =  bitwise_cast<A0>(s);
    }
  };

  BOOST_DISPATCH_IMPLEMENT  (sincos_, tag::cpu_,
                             (A0),
                             (generic_ < complex_<floating_ < A0> > > )
                             (generic_ < complex_<floating_ < A0> > > )
                            )
  {
    typedef A0 result_type;
    inline A0 operator()(A0 const& a0,A0 & a2) const
    {
      result_type a1;
      sincos(a0, a1, a2);
      return a1;
    }
  };

  BOOST_DISPATCH_IMPLEMENT  (sincos_, tag::cpu_,
                             (A0),
                             (generic_ < complex_<floating_<A0> > > )
                            )
  {
    typedef std::pair<A0, A0>           result_type;
    NT2_FUNCTOR_CALL(1)
    {
      A0 first, second;
      sincos(a0, first, second);
      return result_type(first, second);
    }
  };
} }

#endif


#include <vector>
#include <random>
#include <iostream>
#include <chrono>
#include <boost/geometry/extensions/triangulation/strategies/cartesian/detail/precise_math.hpp>
#include "dynamic_exact.h"
#include "static_exact.h"

#include <CGAL/Exact_predicates_exact_constructions_kernel.h>
typedef CGAL::Exact_predicates_inexact_constructions_kernel Kernel;
typedef Kernel::Point_2 Point_2;

const int samples = 10'000'000;

struct problem { std::array<double, 2> a,b,c; };

int main()
{
	std::random_device rd;
	std::mt19937 gen(rd());
	std::uniform_real_distribution<> dis(0.0, 1.0);
	int mod = 1000;
	std::cout << "1 in " << mod << " is guaranteed to require higher precision.\n";
	std::vector<problem> problems;
	problems.reserve(samples);
	for(int i=0; i < samples; ++i) {
		double bfac = 1.0, cfac = 1.0;
		if(i % mod == 0) {
			bfac = 1e20;
			cfac = 1e40;
		}
		problems.emplace_back(problem{dis(gen), dis(gen), dis(gen) * bfac, dis(gen) * bfac, dis(gen) * cfac, dis(gen) * cfac});
	}
	auto start = std::chrono::system_clock::now();
	int sum1 = 0;
	for(int i = 0; i < samples; ++i) {
		auto det = boost::geometry::detail::precise_math::orient2d<double>(problems[i].a, problems[i].b, problems[i].c);
		auto r = det > 0 ? 1 : (det < 0 ? -1 : 0);
		sum1 += r;
	}
	auto end = std::chrono::system_clock::now();
	auto elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(end - start);
	std::cout << "Boost robust orient2:\t\t" << sum1 << "\t" << elapsed.count() << "ms\n";

	start = std::chrono::system_clock::now();
	int sum2 = 0;
	for(int i = 0; i < samples; ++i) {
	        Point_2 p(problems[i].a[0], problems[i].a[1]), q(problems[i].b[0], problems[i].b[1]), r(problems[i].c[0], problems[i].c[1]);
	        auto det = CGAL::orientation(p, q, r);
	        sum2 += det > 0 ? 1 : (det < 0 ? -1 : 0);
	}
	end = std::chrono::system_clock::now();
	elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(end - start);
	std::cout << "CGAL exact predicate:\t\t" << sum2 << "\t" << elapsed.count() << "ms\n";

	start = std::chrono::system_clock::now();
	int sum3 = 0;
	for(int i = 0; i < samples; ++i) {
	        auto det = (problems[i].a[0] - problems[i].c[0]) * (problems[i].b[1] - problems[i].c[1])
			- (problems[i].a[1] - problems[i].c[1]) * (problems[i].b[0] - problems[i].c[0]);
	        sum3 += det > 0 ? 1 : (det < 0 ? -1 : 0);
	}
	end = std::chrono::system_clock::now();
	elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(end - start);
	std::cout << "naive double computation:\t" << sum3 << "\t" << elapsed.count() << "ms\n";

	using A = float_wrapper<double>;
        start = std::chrono::system_clock::now();
        int sum4 = 0;
        for(int i = 0; i < samples; ++i) {
                auto det = ((A(problems[i].a[0]) + A(-problems[i].c[0])) * (A(problems[i].b[1]) + A(-problems[i].c[1]))
                        + (A(-problems[i].a[1]) + A(problems[i].c[1])) * (A(problems[i].b[0]) + A(-problems[i].c[0]))).sign();
                sum4 += det > 0 ? 1 : (det < 0 ? -1 : 0);
        }
        end = std::chrono::system_clock::now();
        elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(end - start);
        std::cout << "automatically generated exact:\t" << sum4 << "\t" << elapsed.count() << "ms\n";
	return 0;
}


#include <iostream>
#include <boost/graph/adjacency_list.hpp>
#include <boost/graph/cycle_canceling.hpp>
#include <boost/graph/push_relabel_max_flow.hpp>
#include <boost/graph/successive_shortest_path_nonnegative_weights.hpp>
#include <boost/graph/find_flow_cost.hpp>

// Graph Type with nested interior edge properties for Cost Flow Algorithms
typedef boost::adjacency_list_traits<boost::vecS, boost::vecS, boost::directedS> traits;
typedef boost::adjacency_list<boost::vecS, boost::vecS, boost::directedS, boost::no_property,
                              boost::property<boost::edge_capacity_t, long,
                                              boost::property<boost::edge_residual_capacity_t, long,
                                                              boost::property<boost::edge_reverse_t, traits::edge_descriptor,
                                                                              boost::property<boost::edge_weight_t, long>>>>>
    graph; // new! weightmap corresponds to costs
typedef boost::graph_traits<graph>::edge_descriptor edge_desc;
typedef boost::graph_traits<graph>::out_edge_iterator out_edge_it; // Iterator

// Custom edge adder class
class edge_adder
{
    graph &G;

public:
    explicit edge_adder(graph &G) : G(G) {}
    void add_edge(int from, int to, long capacity, long cost)
    {
        auto c_map = boost::get(boost::edge_capacity, G);
        auto r_map = boost::get(boost::edge_reverse, G);
        auto w_map = boost::get(boost::edge_weight, G); // new!
        const edge_desc e = boost::add_edge(from, to, G).first;
        const edge_desc rev_e = boost::add_edge(to, from, G).first;
        c_map[e] = capacity;
        c_map[rev_e] = 0; // reverse edge has no capacity!
        r_map[e] = rev_e;
        r_map[rev_e] = e;
        w_map[e] = cost;      // new assign cost
        w_map[rev_e] = -cost; // new negative cost
    }
};

using namespace std;

// Idea:
// Train has l seats.
// Each seat can either be occupied by an agent or a regular passenger.
// An agent on a mission brings reward q, a regular passenger 0.
// l people board the train
// At a given stop:
// - either start mission to target-stop with value q
// - or travel to next stop without reward
void solve()
{
    int stops, missions, maxAgents;
    cin >> stops >> missions >> maxAgents;

    int maxValue = 128; // 2^7

    graph G(stops);
    edge_adder adder(G);
    auto c_map = boost::get(boost::edge_capacity, G);
    auto r_map = boost::get(boost::edge_reverse, G);
    auto rc_map = boost::get(boost::edge_residual_capacity, G);

    auto source = boost::add_vertex(G);
    auto target = boost::add_vertex(G);

    adder.add_edge(source, 0, maxAgents, 0);
    adder.add_edge(stops - 1, target, maxAgents, 0);

    for (int stop = 0; stop < stops - 1; ++stop) {
        adder.add_edge(stop, stop + 1, maxAgents, maxValue);
    }

    int u, v, q;
    for (int mission = 0; mission < missions; ++mission) {
        cin >> u >> v >> q;
        adder.add_edge(u, v, 1, maxValue * (v - u) - q);
    }

    boost::successive_shortest_path_nonnegative_weights(G, source, target);
    auto cost = boost::find_flow_cost(G);
    cost = maxAgents * maxValue * (stops - 1) - cost;
    cout << cost << endl;
}

int main()
{
    int t; cin >> t;
    for (int i = 0; i < t; ++i) {
        solve();
    }
    return 0;
}


//---------------------------------------------------------------------------//
// Copyright (c) 2018-2020 Mikhail Komarov <nemo@nil.foundation>
// Copyright (c) 2020 Pavel Kharitonov <ipavrus@nil.foundation>
//
// 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 CRYPTO3_TIGER_FUNCTIONS_HPP
#define CRYPTO3_TIGER_FUNCTIONS_HPP

#include <boost/crypto3/hash/detail/tiger/basic_tiger_policy.hpp>
#include <boost/crypto3/detail/make_uint_t.hpp>

namespace boost {
    namespace crypto3 {
        namespace hashes {
            namespace detail {
                template<std::size_t DigestBits>
                struct tiger_functions : public basic_tiger_policy<DigestBits> {
                    typedef basic_tiger_policy<DigestBits> policy_type;

                    typedef typename policy_type::byte_type byte_type;

                    constexpr static const std::size_t word_bits = policy_type::word_bits;
                    typedef typename policy_type::word_type word_type;

                    constexpr static const std::size_t state_bits = basic_tiger_policy<DigestBits>::state_bits;
                    constexpr static const std::size_t state_words = basic_tiger_policy<DigestBits>::state_words;
                    typedef typename basic_tiger_policy<DigestBits>::state_type state_type;

                    constexpr static const std::size_t block_bits = basic_tiger_policy<DigestBits>::block_bits;
                    constexpr static const std::size_t block_words = basic_tiger_policy<DigestBits>::block_words;
                    typedef typename basic_tiger_policy<DigestBits>::block_type block_type;

                    inline static void mix(block_type &X) {
                        X[0] -= X[7] ^ 0xA5A5A5A5A5A5A5A5;
                        X[1] ^= X[0];
                        X[2] += X[1];
                        X[3] -= X[2] ^ ((~X[1]) << 19);
                        X[4] ^= X[3];
                        X[5] += X[4];
                        X[6] -= X[5] ^ ((~X[4]) >> 23);
                        X[7] ^= X[6];

                        X[0] += X[7];
                        X[1] -= X[0] ^ ((~X[7]) << 19);
                        X[2] ^= X[1];
                        X[3] += X[2];
                        X[4] -= X[3] ^ ((~X[2]) >> 23);
                        X[5] ^= X[4];
                        X[6] += X[5];
                        X[7] -= X[6] ^ 0x0123456789ABCDEF;
                    }

                    inline static void pass(word_type &A, word_type &B, word_type &C, block_type &X, byte_type mul) {
                        C ^= X[0];
                        A -= policy_type::sbox1[::boost::crypto3::detail::extract_uint_t<CHAR_BIT>(C, 7)] ^
                             policy_type::sbox2[::boost::crypto3::detail::extract_uint_t<CHAR_BIT>(C, 5)] ^
                             policy_type::sbox3[::boost::crypto3::detail::extract_uint_t<CHAR_BIT>(C, 3)] ^
                             policy_type::sbox4[::boost::crypto3::detail::extract_uint_t<CHAR_BIT>(C, 1)];
                        B += policy_type::sbox1[::boost::crypto3::detail::extract_uint_t<CHAR_BIT>(C, 0)] ^
                             policy_type::sbox2[::boost::crypto3::detail::extract_uint_t<CHAR_BIT>(C, 2)] ^
                             policy_type::sbox3[::boost::crypto3::detail::extract_uint_t<CHAR_BIT>(C, 4)] ^
                             policy_type::sbox4[::boost::crypto3::detail::extract_uint_t<CHAR_BIT>(C, 6)];
                        B *= mul;
                        A ^= X[1];
                        B -= policy_type::sbox1[::boost::crypto3::detail::extract_uint_t<CHAR_BIT>(A, 7)] ^
                             policy_type::sbox2[::boost::crypto3::detail::extract_uint_t<CHAR_BIT>(A, 5)] ^
                             policy_type::sbox3[::boost::crypto3::detail::extract_uint_t<CHAR_BIT>(A, 3)] ^
                             policy_type::sbox4[::boost::crypto3::detail::extract_uint_t<CHAR_BIT>(A, 1)];
                        C += policy_type::sbox1[::boost::crypto3::detail::extract_uint_t<CHAR_BIT>(A, 0)] ^
                             policy_type::sbox2[::boost::crypto3::detail::extract_uint_t<CHAR_BIT>(A, 2)] ^
                             policy_type::sbox3[::boost::crypto3::detail::extract_uint_t<CHAR_BIT>(A, 4)] ^
                             policy_type::sbox4[::boost::crypto3::detail::extract_uint_t<CHAR_BIT>(A, 6)];
                        C *= mul;
                        B ^= X[2];
                        C -= policy_type::sbox1[::boost::crypto3::detail::extract_uint_t<CHAR_BIT>(B, 7)] ^
                             policy_type::sbox2[::boost::crypto3::detail::extract_uint_t<CHAR_BIT>(B, 5)] ^
                             policy_type::sbox3[::boost::crypto3::detail::extract_uint_t<CHAR_BIT>(B, 3)] ^
                             policy_type::sbox4[::boost::crypto3::detail::extract_uint_t<CHAR_BIT>(B, 1)];
                        A += policy_type::sbox1[::boost::crypto3::detail::extract_uint_t<CHAR_BIT>(B, 0)] ^
                             policy_type::sbox2[::boost::crypto3::detail::extract_uint_t<CHAR_BIT>(B, 2)] ^
                             policy_type::sbox3[::boost::crypto3::detail::extract_uint_t<CHAR_BIT>(B, 4)] ^
                             policy_type::sbox4[::boost::crypto3::detail::extract_uint_t<CHAR_BIT>(B, 6)];
                        A *= mul;
                        C ^= X[3];
                        A -= policy_type::sbox1[::boost::crypto3::detail::extract_uint_t<CHAR_BIT>(C, 7)] ^
                             policy_type::sbox2[::boost::crypto3::detail::extract_uint_t<CHAR_BIT>(C, 5)] ^
                             policy_type::sbox3[::boost::crypto3::detail::extract_uint_t<CHAR_BIT>(C, 3)] ^
                             policy_type::sbox4[::boost::crypto3::detail::extract_uint_t<CHAR_BIT>(C, 1)];
                        B += policy_type::sbox1[::boost::crypto3::detail::extract_uint_t<CHAR_BIT>(C, 0)] ^
                             policy_type::sbox2[::boost::crypto3::detail::extract_uint_t<CHAR_BIT>(C, 2)] ^
                             policy_type::sbox3[::boost::crypto3::detail::extract_uint_t<CHAR_BIT>(C, 4)] ^
                             policy_type::sbox4[::boost::crypto3::detail::extract_uint_t<CHAR_BIT>(C, 6)];
                        B *= mul;
                        A ^= X[4];
                        B -= policy_type::sbox1[::boost::crypto3::detail::extract_uint_t<CHAR_BIT>(A, 7)] ^
                             policy_type::sbox2[::boost::crypto3::detail::extract_uint_t<CHAR_BIT>(A, 5)] ^
                             policy_type::sbox3[::boost::crypto3::detail::extract_uint_t<CHAR_BIT>(A, 3)] ^
                             policy_type::sbox4[::boost::crypto3::detail::extract_uint_t<CHAR_BIT>(A, 1)];
                        C += policy_type::sbox1[::boost::crypto3::detail::extract_uint_t<CHAR_BIT>(A, 0)] ^
                             policy_type::sbox2[::boost::crypto3::detail::extract_uint_t<CHAR_BIT>(A, 2)] ^
                             policy_type::sbox3[::boost::crypto3::detail::extract_uint_t<CHAR_BIT>(A, 4)] ^
                             policy_type::sbox4[::boost::crypto3::detail::extract_uint_t<CHAR_BIT>(A, 6)];
                        C *= mul;
                        B ^= X[5];
                        C -= policy_type::sbox1[::boost::crypto3::detail::extract_uint_t<CHAR_BIT>(B, 7)] ^
                             policy_type::sbox2[::boost::crypto3::detail::extract_uint_t<CHAR_BIT>(B, 5)] ^
                             policy_type::sbox3[::boost::crypto3::detail::extract_uint_t<CHAR_BIT>(B, 3)] ^
                             policy_type::sbox4[::boost::crypto3::detail::extract_uint_t<CHAR_BIT>(B, 1)];
                        A += policy_type::sbox1[::boost::crypto3::detail::extract_uint_t<CHAR_BIT>(B, 0)] ^
                             policy_type::sbox2[::boost::crypto3::detail::extract_uint_t<CHAR_BIT>(B, 2)] ^
                             policy_type::sbox3[::boost::crypto3::detail::extract_uint_t<CHAR_BIT>(B, 4)] ^
                             policy_type::sbox4[::boost::crypto3::detail::extract_uint_t<CHAR_BIT>(B, 6)];
                        A *= mul;
                        C ^= X[6];
                        A -= policy_type::sbox1[::boost::crypto3::detail::extract_uint_t<CHAR_BIT>(C, 7)] ^
                             policy_type::sbox2[::boost::crypto3::detail::extract_uint_t<CHAR_BIT>(C, 5)] ^
                             policy_type::sbox3[::boost::crypto3::detail::extract_uint_t<CHAR_BIT>(C, 3)] ^
                             policy_type::sbox4[::boost::crypto3::detail::extract_uint_t<CHAR_BIT>(C, 1)];
                        B += policy_type::sbox1[::boost::crypto3::detail::extract_uint_t<CHAR_BIT>(C, 0)] ^
                             policy_type::sbox2[::boost::crypto3::detail::extract_uint_t<CHAR_BIT>(C, 2)] ^
                             policy_type::sbox3[::boost::crypto3::detail::extract_uint_t<CHAR_BIT>(C, 4)] ^
                             policy_type::sbox4[::boost::crypto3::detail::extract_uint_t<CHAR_BIT>(C, 6)];
                        B *= mul;
                        A ^= X[7];
                        B -= policy_type::sbox1[::boost::crypto3::detail::extract_uint_t<CHAR_BIT>(A, 7)] ^
                             policy_type::sbox2[::boost::crypto3::detail::extract_uint_t<CHAR_BIT>(A, 5)] ^
                             policy_type::sbox3[::boost::crypto3::detail::extract_uint_t<CHAR_BIT>(A, 3)] ^
                             policy_type::sbox4[::boost::crypto3::detail::extract_uint_t<CHAR_BIT>(A, 1)];
                        C += policy_type::sbox1[::boost::crypto3::detail::extract_uint_t<CHAR_BIT>(A, 0)] ^
                             policy_type::sbox2[::boost::crypto3::detail::extract_uint_t<CHAR_BIT>(A, 2)] ^
                             policy_type::sbox3[::boost::crypto3::detail::extract_uint_t<CHAR_BIT>(A, 4)] ^
                             policy_type::sbox4[::boost::crypto3::detail::extract_uint_t<CHAR_BIT>(A, 6)];
                        C *= mul;
                    }
                };
            }   // namespace detail
        }   // namespace hashes
    }   // namespace crypto3
}    // namespace boost

#endif    // CRYPTO3_TIGER_FUNCTIONS_HPP


///////////////////////////////////////////////////////////////////////////////
//  Copyright 2014 Anton Bikineev
//  Copyright 2014 Christopher Kormanyos
//  Copyright 2014 John Maddock
//  Copyright 2014 Paul Bristow
//  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 BOOST_MATH_HYPERGEOMETRIC_ASYM_HPP
#define BOOST_MATH_HYPERGEOMETRIC_ASYM_HPP

#include <boost/math/special_functions/gamma.hpp>
#include <boost/math/special_functions/hypergeometric_2F0.hpp>

#ifdef BOOST_MSVC
#pragma warning(push)
#pragma warning(disable:4127)
#endif

  namespace boost { namespace math {

  namespace detail {

     //
     // Asymptotic series based on https://dlmf.nist.gov/13.7#E1
     //
     // Note that a and b must not be negative integers, in addition
     // we require z > 0 and so apply Kummer's relation for z < 0.
     //
     template <class T, class Policy>
     inline T hypergeometric_1F1_asym_large_z_series(T a, const T& b, T z, const Policy& pol, int& log_scaling)
     {
        BOOST_MATH_STD_USING
        static const char* function = "boost::math::hypergeometric_1F1_asym_large_z_series<%1%>(%1%, %1%, %1%)";
        T prefix;
        int e, s;
        if (z < 0)
        {
           a = b - a;
           z = -z;
           prefix = 1;
        }
        else
        {
           e = z > INT_MAX ? INT_MAX : itrunc(z, pol);
           log_scaling += e;
           prefix = exp(z - e);
        }
        if ((fabs(a) < 10) && (fabs(b) < 10))
        {
           prefix *= pow(z, a) * pow(z, -b) * boost::math::tgamma(b, pol) / boost::math::tgamma(a, pol);
        }
        else
        {
           T t = log(z) * (a - b);
           e = itrunc(t, pol);
           log_scaling += e;
           prefix *= exp(t - e);

           t = boost::math::lgamma(b, &s, pol);
           e = itrunc(t, pol);
           log_scaling += e;
           prefix *= s * exp(t - e);

           t = boost::math::lgamma(a, &s, pol);
           e = itrunc(t, pol);
           log_scaling -= e;
           prefix /= s * exp(t - e);
        }
        //
        // Checked 2F0:
        //
        unsigned k = 0;
        T a1_poch(1 - a);
        T a2_poch(b - a);
        T z_mult(1 / z);
        T sum = 0;
        T abs_sum = 0;
        T term = 1;
        T last_term = 0;
        do
        {
           sum += term;
           last_term = term;
           abs_sum += fabs(sum);
           term *= a1_poch * a2_poch * z_mult;
           term /= ++k;
           a1_poch += 1;
           a2_poch += 1;
           if (fabs(sum) * boost::math::policies::get_epsilon<T, Policy>() > fabs(term))
              break;
           if(fabs(sum) / abs_sum < boost::math::policies::get_epsilon<T, Policy>())
              return boost::math::policies::raise_evaluation_error<T>(function, "Large-z asymptotic approximation to 1F1 has destroyed all the digits in the result due to cancellation.  Current best guess is %1%",
                 prefix * sum, Policy());
           if(k > boost::math::policies::get_max_series_iterations<Policy>())
              return boost::math::policies::raise_evaluation_error<T>(function, "1F1: Unable to locate solution in a reasonable time:"
                 " large-z asymptotic approximation.  Current best guess is %1%", prefix * sum, Policy());
           if((k > 10) && (fabs(term) > fabs(last_term)))
              return boost::math::policies::raise_evaluation_error<T>(function, "Large-z asymptotic approximation to 1F1 is divergent.  Current best guess is %1%", prefix * sum, Policy());
        } while (true);

        return prefix * sum;
     }

  // experimental range
  template <class T, class Policy>
  inline bool hypergeometric_1F1_asym_region(const T& a, const T& b, const T& z, const Policy&)
  {
    BOOST_MATH_STD_USING
    int half_digits = policies::digits<T, Policy>() / 2;
    bool in_region = false;

    if (fabs(a) < 0.001f)
       return false; // Haven't been able to make this work, why not?  TODO!

    //
    // We use the following heuristic, if after we have had half_digits terms
    // of the 2F0 series, we require terms to be decreasing in size by a factor
    // of at least 0.7.  Assuming the earlier terms were converging much faster
    // than this, then this should be enough to achieve convergence before the
    // series shoots off to infinity.
    //
    if (z > 0)
    {
       T one_minus_a = 1 - a;
       T b_minus_a = b - a;
       if (fabs((one_minus_a + half_digits) * (b_minus_a + half_digits) / (half_digits * z)) < 0.7)
       {
          in_region = true;
          //
          // double check that we are not divergent at the start if a,b < 0:
          //
          if ((one_minus_a < 0) || (b_minus_a < 0))
          {
             if (fabs(one_minus_a * b_minus_a / z) > 0.5)
                in_region = false;
          }
       }
    }
    else if (fabs((1 - (b - a) + half_digits) * (a + half_digits) / (half_digits * z)) < 0.7)
    {
       if ((floor(b - a) == (b - a)) && (b - a < 0))
          return false;  // Can't have a negative integer b-a.
       in_region = true;
       //
       // double check that we are not divergent at the start if a,b < 0:
       //
       T a1 = 1 - (b - a);
       if ((a1 < 0) || (a < 0))
       {
          if (fabs(a1 * a / z) > 0.5)
             in_region = false;
       }
    }
    //
    // Check for a and b negative integers as these aren't supported by the approximation:
    //
    if (in_region)
    {
       if ((a < 0) && (floor(a) == a))
          in_region = false;
       if ((b < 0) && (floor(b) == b))
          in_region = false;
       if (fabs(z) < 40)
          in_region = false;
    }
    return in_region;
  }

  } } } // namespaces

#ifdef BOOST_MSVC
#pragma warning(pop)
#endif

#endif // BOOST_MATH_HYPERGEOMETRIC_ASYM_HPP


/*
   For more information, please see: http://software.sci.utah.edu

   The MIT License

   Copyright (c) 2020 Scientific Computing and Imaging Institute,
   University of Utah.

   Permission is hereby granted, free of charge, to any person obtaining a
   copy of this software and associated documentation files (the "Software"),
   to deal in the Software without restriction, including without limitation
   the rights to use, copy, modify, merge, publish, distribute, sublicense,
   and/or sell copies of the Software, and to permit persons to whom the
   Software is furnished to do so, subject to the following conditions:

   The above copyright notice and this permission notice shall be included
   in all copies or substantial portions of the Software.

   THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
   OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
   FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
   THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
   LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
   FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
   DEALINGS IN THE SOFTWARE.
*/

#include <Core/Algorithms/Base/AlgorithmPreconditions.h>
#include <Core/Algorithms/Math/SolveLinearSystemWithEigen.h>
#include <Core/Datatypes/DenseMatrix.h>
#include <Core/Datatypes/DenseColumnMatrix.h>
#include <Core/Datatypes/SparseRowMatrix.h>
#include <Core/Datatypes/MatrixTypeConversions.h>
#include <Eigen/Sparse>

using namespace SCIRun;
using namespace SCIRun::Core::Algorithms::Math;
using namespace SCIRun::Core::Datatypes;
using namespace SCIRun::Core::Algorithms;
using namespace SCIRun::Core;

namespace
{
  using EigenComputationInfo = boost::error_info<struct tag_eigen_computation, Eigen::ComputationInfo>;

  template <class ColumnMatrixType, template <typename> class SolverType>
  class SolveLinearSystemAlgorithmEigenCGImpl
  {
  public:
    SolveLinearSystemAlgorithmEigenCGImpl(SharedPointer<ColumnMatrixType> rhs, double tolerance, int maxIterations) :
        tolerance_(tolerance), maxIterations_(maxIterations), rhs_(rhs) {}

    using SolutionType = ColumnMatrixType;

    template <class MatrixType>
    typename ColumnMatrixType::EigenBase solveWithEigen(const MatrixType& lhs)
    {
      SolverType<typename MatrixType::EigenBase> solver;
      solver.compute(lhs);

      if (solver.info() != Eigen::Success)
        BOOST_THROW_EXCEPTION(AlgorithmInputException()
          << LinearAlgebraErrorMessage("Eigen solver initialization was unsuccessful")
          << EigenComputationInfo(solver.info()));

      solver.setTolerance(tolerance_);
      solver.setMaxIterations(maxIterations_);
      auto solution = solver.solve(*rhs_).eval();
      tolerance_ = solver.error();
      maxIterations_ = solver.iterations();
      return solution;
    }

    double tolerance_;
    int maxIterations_;
  private:
    SharedPointer<ColumnMatrixType> rhs_;
  };
}

SolveLinearSystemAlgorithm::Outputs SolveLinearSystemAlgorithm::run(const Inputs& input, const Parameters& params) const
{
  return runImpl<Inputs, Outputs>(input, params);
}

SolveLinearSystemAlgorithm::ComplexOutputs SolveLinearSystemAlgorithm::run(const ComplexInputs& input, const Parameters& params) const
{
  return runImpl<ComplexInputs, ComplexOutputs>(input, params);
}

template <typename T>
using CG = Eigen::ConjugateGradient<T>;
// Not available yet, need to upgrade Eigen
// template <typename T>
// using LSCG = Eigen::LeastSquaresConjugateGradient<T>;
template <typename T>
using BiCG = Eigen::BiCGSTAB<T>;

template <typename In, typename Out>
Out SolveLinearSystemAlgorithm::runImpl(const In& input, const Parameters& params) const
{
  auto A = std::get<0>(input);
  ENSURE_ALGORITHM_INPUT_NOT_NULL(A, "Null input matrix");

  auto b = std::get<1>(input);
  ENSURE_ALGORITHM_INPUT_NOT_NULL(b, "Null rhs vector");

  double tolerance = std::get<0>(params);
  ENSURE_POSITIVE_DOUBLE(tolerance, "Tolerance out of range!");

  int maxIterations = std::get<1>(params);
  ENSURE_POSITIVE_INT(maxIterations, "Max iterations out of range!");

  auto method = std::get<2>(params);

  using SolutionType = DenseColumnMatrixGeneric<typename std::tuple_element<0, In>::type::element_type::value_type>;
  using AlgoTypeCG = SolveLinearSystemAlgorithmEigenCGImpl<SolutionType, CG>;
  using AlgoTypeBiCG = SolveLinearSystemAlgorithmEigenCGImpl<SolutionType, BiCG>;

  if ("cg" == method)
    return solve<AlgoTypeCG, In, Out>(input, params);
  else if ("bicg" == method)
    return solve<AlgoTypeBiCG, In, Out>(input, params);
  else
  {
    BOOST_THROW_EXCEPTION(AlgorithmProcessingException() << ErrorMessage("Need to upgrade Eigen for LSCG."));
  }
}

template <typename SolverType, typename In, typename Out>
Out SolveLinearSystemAlgorithm::solve(const In& input, const Parameters& params) const
{
  auto A = std::get<0>(input);
  auto b = std::get<1>(input);
  double tolerance = std::get<0>(params);
  int maxIterations = std::get<1>(params);

  SolverType impl(b, tolerance, maxIterations);

  typename SolverType::SolutionType x;
  if (matrixIs::dense(A))
  {
    auto dense = castMatrix::toDense(A);
    x = impl.solveWithEigen(*dense);
  }
  else if (matrixIs::sparse(A))
  {
    auto sparse = castMatrix::toSparse(A);
    x = impl.solveWithEigen(*sparse);
  }
  else
    BOOST_THROW_EXCEPTION(AlgorithmProcessingException() << ErrorMessage("solveWithEigen can only handle dense and sparse matrices."));

  if (x.size() != 0)
  {
    auto solution(makeShared<typename SolverType::SolutionType>(x));
    return Out(solution, impl.tolerance_, impl.maxIterations_);
  }
  else
    BOOST_THROW_EXCEPTION(AlgorithmProcessingException() << ErrorMessage("solveWithEigen produced an empty solution."));
}

AlgorithmOutput SolveLinearSystemAlgorithm::run(const AlgorithmInput&) const
{
  throw 2;
}


/*
Program to translate text to DNA and vice versa
*/

#include <iostream>
#include <fstream>
#include <vector>
#include <boost/random/mersenne_twister.hpp>
//#include <boost/random/uniform_int_distribution.hpp>
#include <boost/random/uniform_int.hpp>
#include <boost/random/bernoulli_distribution.hpp>
#include <boost/program_options.hpp>
#include "../include/GF2M.hpp"
#include "../include/DFT.hpp"
#include "../include/helpers.hpp"
#include "../include/encodedecode.hpp"
#include "../include/ReedSolomon.hpp"
#include <string>
#include <fstream>
#include <streambuf>

using namespace std;

namespace po = boost::program_options;

int main(int ac, char* av[])
{

// command line options
int primer_length = 0;
int numblocks = 1;
bool rev = false;
string infile;
string fastq_infile;
string outfile;
string gtruthfile;

unsigned N = 34;
unsigned K = 32;

unsigned n = 16383;
unsigned k = 10977; // k = 0.67*n

unsigned l = 4; // length of the index
unsigned nuss = 12; // number of symbols of outer code per segment

int opt;

po::options_description desc("Allowed options");
desc.add_options()
    ("help", "produce help message")
    ("encode", "encode")
    ("decode", "decode")
	("singleseq","singleseq")

    ("disturb", "draw uniformly at random from the input lines, add errors to each line")
	("input",po::value<string>(&infile)->default_value(""),"inputfile")
	("fastq_infile",po::value<string>(&fastq_infile)->default_value(""),"fastq inputfile")
	("output",po::value<string>(&outfile)->default_value(""),"outputfile")
	("groundtruth",po::value<string>(&gtruthfile)->default_value(""),"groundtruthfile")
	("reverse", po::bool_switch(&rev), "Fastq reverse sequences")
	("primer_length",po::value<int>(&primer_length)->default_value(0),"primer_length")

	("numblocks",po::value<int>(&numblocks)->default_value(1),"numblocks")
	("n",po::value<unsigned>(&n)->default_value(16383),"n")
	("k",po::value<unsigned>(&k)->default_value(10977),"k")
	("N",po::value<unsigned>(&N)->default_value(34),"N")
	("K",po::value<unsigned>(&K)->default_value(32),"K")
	("l",po::value<unsigned>(&l)->default_value(4),"l")
	("nuss",po::value<unsigned>(&nuss)->default_value(12),"nuss")
;

po::variables_map vm;
po::store(po::parse_command_line(ac, av, desc), vm);
po::notify(vm);

if (vm.count("help")) {
    cout << desc << "\n";
    return 1;
}

///// parameters

typedef unsigned long long int uint;
// inner code
const uint mi = 6;
const uint prim_poly = 91;
typedef GF2M<uint,mi,prim_poly> GFI; // GF(2^6) with primitive polynomail 91
const uint N_u = 63; // the underlying length of the shortened inner code

GFI fai = GFI(2,0); // 64 + 2 = 1000010 = x + x^5
DFT_FFT<GFI> dftgfi(N_u,fai,7,9); // 7*9 = 63
typedef RScode<GFI, DFT_FFT<GFI> > Innercode;
Innercode innercode(N,K,fai,dftgfi,N_u);

// list of primitive polynomials can be found here: https://www.partow.net/programming/polynomials/index.html

//// parameter choices for outer code of length 16383
///*
const unsigned Q = 127;
const unsigned P = 129;
const unsigned n_u = P*Q; // 16383 = 2^14 - 1, Q is prime and P = 3*43
const uint prim_poly_o = 16553; // primitive polynomial for 2^14
const unsigned mo = 14;
typedef GF2M<uint,mo,prim_poly_o> GFO;
GFO fao = GFO(66,0); // 66 = 64 + 2 = 1000010 = x + x^5 // Element of order 16383
//*/

//// parameter choices for outer code of length 4095
/*
unsigned Q = 63;
unsigned P = 65;
unsigned n_u = 4095; // 2^12 - 1; // 2^12-1 = 5*3*3*7*13
const unsigned mo = 12;
const uint prim_poly_o = 4621; // primitive polynomial for GF(2^12), x^12 + x^9 + x^3 + x^2 + 1
typedef GF2M<uint,mo,prim_poly_o> GFO;
GFO fao = GFO(10,0); // 9 = 64 + 2 = 1010 = x + x^3 // Element of order 4095; must have order 4095
//cout << "element has order:" << fao.order() << endl; // check for order of element
*/

DFT_FFT<GFO> dftgfo(n_u,fao,P,Q); // Fourier transform for the outer code

typedef RScode<GFO,DFT_FFT<GFO> > Outercode;
Outercode outercode(n,k,fao,dftgfo,n_u);

// check all the parameter choices

if (!vm.count("singleseq")) {
if (K*mi != nuss*mo+l*mi) {
	cerr << "Must have: K*mi = nuss*mo + l*mi, where mi=6, mo=14, but got " << K*mi << ", " << nuss*mo+l*mi << endl;
	return 1;
}
}

if ( K > N) {
	cerr << "Must have: K <= N"  << endl;
	return 1;
}

if ( k > n) {
	cerr << "Must have: k <= n"  << endl;
	return 1;
}

if ( n > n_u) {
	cerr << "Must have: n <= n_u"  << endl;
	return 1;
}

if ( N > N_u) {
	cerr << "Must have: N <= N_u"  << endl;
	return 1;
}

if ( l*mi < log( numblocks*n )/log( 2 ) ) {
	cerr << "Index has length " << l*mi << " bit, but require " << log( numblocks*n )/log( 2 ) << " bits" << endl;
	return 1;
}

// tell the user the parameter choices

cout << "--------------------------------" << endl;
cout << "redundancy outer code: " << float(n-k)/float(k)*100 << "\%" << endl; // " (= (n-k)/k)" << endl;
cout << "redundancy inner code: " << float(N-K)/float(K)*100 << "\%" << endl; // " (= (N-K)/K)" << endl;
cout << "--------------------------------" << endl;

// encoder/decoder
EnDecode< Innercode , Outercode > endecode(innercode,outercode,l,nuss);
EnDecodeSingleSeq< Innercode > endecodesingleseq(innercode);

/////////////////////// encode
if (vm.count("encode")) {

	cout << "start encoding.." << endl;
	if( infile == "" || outfile ==""){
		cout << "in/outfile not specified " << endl;
		return 0;
	}
	cout << "infile:  " << infile << endl;
	cout << "outfile: " << outfile << endl;

	// read data
	std::ifstream t(infile.c_str());
	std::string str((std::istreambuf_iterator<char>(t)),std::istreambuf_iterator<char>());

	// encode - determines the number of blocks required automatically
	vector<string> urn(n*numblocks);

	if( vm.count("singleseq") ){
		// store only a signle sequence
		endecodesingleseq.encode(str, urn);
		numblocks = 1;
	} else {
		endecode.encode(str, urn);
		numblocks = endecode.numblocks;
	}

	if(numblocks*k*mo*nuss < str.size()*8){
		cerr << "trying to store " << str.size()*8 << " bits, but can only store " << numblocks*k*mo*nuss << "many" << endl;
		return 1;
	}

	if( vm.count("singleseq") ){
		cout << "encoded " << str.size() << " Bytes to one DNA segment of length " << urn[0].size() << endl;
	} else {
		cout << "encoded " << str.size() << " Bytes to " << numblocks << " blocks, resulting in "
	<< urn.size() << " DNA segments of length " << urn[0].size() << " each." << endl;
    }

	ofstream out;
	out.open(outfile.c_str());
	for(unsigned i=0;i<urn.size();++i) out << urn[i] << endl;
	out.close();
	return 0;
}

/////////////////////// decode
if (vm.count("decode")) {

	if( (infile == "" && fastq_infile == "") ) {
		cout << "infile not specified " << endl;
		return 0;
	}

	if( outfile ==""){
		cout << "outfile not specified " << endl;
		return 0;
	}

	if( numblocks==0 && !vm.count("singleseq") ) {
		cout << "numblocks not specified " << endl;
		return 0;
	}

	if(fastq_infile != ""){
		cout << "fastq infile:  " << fastq_infile << endl;
	} else {
		cout << "infile:  " << infile << endl;
	}
	cout << "outfile: " << outfile << endl;
	cout << "numblocks: "<<numblocks << endl;

	vector<string> drawnseg;

	if(infile != ""){
		cout << "assume text file as input format" << endl;

		string sLine = "";
		ifstream in;
		in.open(infile.c_str());
		if (in.fail()) {
        	cerr << "Error when opening infile - does infile exist?" << endl;
			return 1;
		}

		unsigned ctr = 0;
		while (!in.eof()){
			getline(in, sLine);

			int seq_length = N*mi/2;
			if(sLine.size() >= seq_length + primer_length){
				sLine = sLine.substr(primer_length, seq_length);
				drawnseg.push_back(sLine);
				ctr++;
			}
			cout << "\rLines read: " << ctr;
		}
		cout << endl;
		//drawnseg.resize(drawnseg.size()-1); // erase the last, empty line
	} else {
		cout << "assume fastq file as input format" << endl;

		int seq_length = N*mi/2;
		ifstream in;
		in.open(fastq_infile.c_str());
		if(in.fail()){
        	cerr << "Error when opening infile - does infile exist?" << endl;
			return 1;
		}

		string sLine = "";
		vector<int> hist(400,0);
		unsigned totalctr = 0;

		int ctr = 0;
		while (!in.eof()){
			getline(in, sLine);
			ctr++;
			if(ctr % 4 == 2){
				hist[sLine.size()]++;
				totalctr++;
				if(rev){
					flipvecdir(sLine);
					fliplett(sLine);
				}

				if(sLine.size() >= seq_length + primer_length){
					sLine = sLine.substr(primer_length, seq_length + primer_length );
					drawnseg.push_back(sLine);
				}
			}
			//if(ctr == 100000000) break;
		}
		in.close();
	}

	//

	string recstr;
	cout << "start decode.." << endl;

	if( vm.count("singleseq")){
		// store only a signle sequence
		endecodesingleseq.decode(recstr, drawnseg);
	} else {
		endecode.numblocks = numblocks;
		endecode.decode(recstr, drawnseg, gtruthfile);
	}

	ofstream out;
	out.open(outfile.c_str());
	out << recstr;
	out.close();
	cout << "Wrote file of length " << recstr.size() << " Bytes" << endl;
}

////////////////////////// disturb
if (vm.count("disturb")) {

	// take M random draws uniformly at random and disturb those

	const unsigned M = n*numblocks*6;
	const float substprob = 0.0005; // substitution error probability
	//const unsigned M = n*numblocks*20;
	//const float substprob = 0.0005; // substitution error probability
	cout << "disturb:" << endl;
	cout << "\tDraw " << M << " many times" << endl;
	cout << "\tsubstitution error probability " << substprob << endl;

	if(infile == "" || outfile =="") {
		cout << "in/outfile not specified " << endl;
		return 0;
	}
	cout << "infile:  " << infile << endl;
	cout << "outfile: " << outfile << endl;

	vector<string> urn;

	string sLine = "";
	ifstream in;
	in.open(infile.c_str());
	while (!in.eof()){
		getline(in, sLine);
		urn.push_back(sLine);
	}
	urn.resize(urn.size()-1); // erase the last, empty line

	boost::mt19937 rng;
	boost::uniform_int<> unif(0,urn.size()-1); // distribution that maps to 0,..,urn.size()-1
	boost::uniform_int<> unif_N(0,N-1); // uniform distribution over {0,..,N-1}
	boost::uniform_int<> unif_4(0,4-1); // uniform distribution over {0,1,2,3}
	boost::bernoulli_distribution<> bern(substprob);
	//boost::bernoulli_distribution<> faircoin(0.5);

	char nucl[] = "ACGT";

	vector<string> drawnseg(M);

	ofstream out;
	out.open(outfile.c_str());

	// draw M times
	string tmpstr;
	for(unsigned i=0;i<M;++i){
		unsigned randind = unif(rng);
		tmpstr = urn[randind];

		// introduce errors
		// introduce on error per inner cw
		for(unsigned j=0;j<1;++j){
			tmpstr[unif_N(rng)] = nucl[unif_4(rng)];
		}

		unsigned ctr = 0;
		for(unsigned j=0;j<tmpstr.size();++j)
			if(bern(rng)) {
				tmpstr[j] = nucl[unif_4(rng)];
				ctr++;
			}
			//cout << ctr << endl;
		//if(faircoin(rng)) flipvecdir(tmpstr); // flip every second

		// write to file
		out << tmpstr;
		if(i!= M-1) out << endl;
	}
	out.close();

}

}


//
//  Copyright Toon Knapen and Kresimir Fresl 2003
//
// 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 BOOST_BINDINGS_BLAS_BLAS2_HPP
#define BOOST_BINDINGS_BLAS_BLAS2_HPP

#include <boost/numeric/bindings/blas/blas2_overloads.hpp>
#include <boost/numeric/bindings/traits/traits.hpp>
#include <boost/numeric/bindings/traits/transpose.hpp>
#include <boost/static_assert.hpp>
#include <boost/type_traits.hpp>
#include <cassert>

namespace boost { namespace numeric { namespace bindings { namespace blas {

  // y <- alpha * op (A) * x + beta * y
  // op (A) == A || A^T || A^H
  // ! CAUTION this function assumes that all matrices involved are column-major matrices
  template < typename matrix_type, typename vector_type_x, typename vector_type_y, typename value_type >
  void gemv(const char TRANS,
            const value_type& alpha,
            const matrix_type &a,
            const vector_type_x &x,
            const value_type& beta,
            vector_type_y &y
            )
  {
    // precondition: matrix_type must be dense or dense_proxy
    /* not all compilers can handle the traits
    BOOST_STATIC_ASSERT( ( boost::is_same< typename mtraits::matrix_structure,
                                           boost::numeric::bindings::traits::general_t
                           >::value ) ) ;
    */

    const integer_t m = traits::matrix_size1( a ) ;
    const integer_t n = traits::matrix_size2( a ) ;
    assert ( traits::vector_size( x ) >= (TRANS == traits::NO_TRANSPOSE ? n : m) ) ;
    assert ( traits::vector_size( y ) >= (TRANS == traits::NO_TRANSPOSE ? m : n) ) ;
    const integer_t lda = traits::leading_dimension( a ) ;
    const integer_t stride_x = traits::vector_stride( x ) ;
    const integer_t stride_y = traits::vector_stride( y ) ;

    const value_type *a_ptr = traits::matrix_storage( a ) ;
    const value_type *x_ptr = traits::vector_storage( x ) ;
    value_type *y_ptr = traits::vector_storage( y ) ;

    detail::gemv( TRANS, m, n, alpha, a_ptr, lda, x_ptr, stride_x, beta, y_ptr, stride_y );
  }

  // A <- alpha * x * trans(y) ( outer product ), alpha, x and y are real-valued
  // ! CAUTION this function assumes that all matrices involved are column-major matrices
  template < typename vector_type_x, typename vector_type_y, typename value_type, typename matrix_type >
  void ger( const value_type& alpha,
            const vector_type_x &x,
            const vector_type_y &y,
            matrix_type &a
            )
  {
    // precondition: matrix_type must be dense or dense_proxy
    /* not all compilers can handle the traits
    BOOST_STATIC_ASSERT( ( boost::is_same< typename mtraits::matrix_structure,
                                           boost::numeric::bindings::traits::general_t
                           >::value ) ) ;
    */

    const integer_t m = traits::matrix_size1( a ) ;
    const integer_t n = traits::matrix_size2( a ) ;
    assert ( traits::vector_size( x ) <= m ) ;
    assert ( traits::vector_size( y ) <= n ) ;
    const integer_t lda = traits::leading_dimension( a ) ;
    const integer_t stride_x = traits::vector_stride( x ) ;
    const integer_t stride_y = traits::vector_stride( y ) ;

    const value_type *x_ptr = traits::vector_storage( x ) ;
    const value_type *y_ptr = traits::vector_storage( y ) ;
    value_type *a_ptr = traits::matrix_storage( a ) ;

    detail::ger( m, n, alpha, x_ptr, stride_x, y_ptr, stride_y, a_ptr, lda );
  }
/*
  // A <- alpha * x * trans(y) ( outer product ), alpha, x and y are complex-valued
  template < typename vector_type_x, typename vector_type_y, typename value_type, typename matrix_type >
  void geru( const value_type& alpha,
             const vector_type_x &x,
             const vector_type_y &y,
             matrix_type &a
             )
  {
    // precondition: matrix_type must be dense or dense_proxy
//    not all compilers can handle the traits
//    BOOST_STATIC_ASSERT( ( boost::is_same< typename mtraits::matrix_structure,
//                                           boost::numeric::bindings::traits::general_t
//                           >::value ) ) ;

//    BOOST_STATIC_ASSERT( ( boost::is_same< x.value_type(), FEMTown::Complex() >::value ) ) ;
    const integer_t m = traits::matrix_size1( a ) ;
    const integer_t n = traits::matrix_size2( a ) ;
    assert ( traits::vector_size( x ) <= m ) ;
    assert ( traits::vector_size( y ) <= n ) ;
    const integer_t lda = traits::leading_dimension( a ) ;
    const integer_t stride_x = traits::vector_stride( x ) ;
    const integer_t stride_y = traits::vector_stride( y ) ;

    const value_type *x_ptr = traits::vector_storage( x ) ;
    const value_type *y_ptr = traits::vector_storage( y ) ;
    value_type *a_ptr = traits::matrix_storage( a ) ;

    detail::geru( m, n, alpha, x_ptr, stride_x, y_ptr, stride_y, a_ptr, lda );
  }
*/
  /*
  // y <- alpha * A * x + beta * y
  template < typename matrix_type, typename vector_type_x, typename vector_type_y >
  void gemv(const typename traits::matrix_traits<matrix_type>::value_type &alpha,
            const matrix_type &a,
            const vector_type_x &x,
            const typename traits::vector_traits<vector_type_y>::value_type &beta,
            vector_type_y &y
            )
  {
    gemv( traits::NO_TRANSPOSE, alpha, a, x, beta, y );
  }

  // y <- A * x
  template < typename matrix_type, typename vector_type_x, typename vector_type_y >
  void gemv(const matrix_type &a, const vector_type_x &x, vector_type_y &y)
  {
    typedef typename traits::matrix_traits<matrix_type>::value_type val_t;
    gemv( traits::NO_TRANSPOSE, (val_t) 1, a, x, (val_t) 0, y );
  }
  */

}}}}

#endif // BOOST_BINDINGS_BLAS_BLAS2_HPP


//
// Created by yche on 12/17/17.
//
#include "sling.h"

#include <boost/format.hpp>

#include "tbb/parallel_sort.h"

#include "ground_truth/stat.h"
#include "ground_truth/graph_yche.h"
#include "ground_truth/yche_serialization.h"
#include "util/log.h"
#include "util/util.h"

const double Sling::BACKEPS = 7.28e-4; //EPS / 23.;
const double Sling::K = 10.;

int Sling::NUMTHREAD = std::thread::hardware_concurrency();
//int Sling::NUMTHREAD = 1;

using namespace std::chrono;

void Sling::init() {
    first = new bool[g->n];
    second = new bool[g->n];
    memset(first, 0, sizeof(bool) * g->n);
    memset(second, 0, sizeof(bool) * g->n);
    for (int u = 0; u < g->n; ++u) {
        if (!(first[u] = (g->inedge[u].size() <= K / BACKEPS))) {
            second[u] = false;
            continue;
        }
        int cnt = 0;
        for (auto itr = g->inedge[u].begin(); itr != g->inedge[u].end(); ++itr) {
            cnt += g->inedge[*itr].size();
        }
        second[u] = (cnt <= K / BACKEPS);
    }
    sqrtct[0] = 1.;
    for (int i = 1; i < 20; ++i)
        sqrtct[i] = sqrtc * sqrtct[i - 1];
}

//########################### indexing 1st: Diagonal Correction Matrix ####################################
double Sling::calcDi(int i, double eps, bool &early, int &R, int tid) {
    R = 0;
    early = true;
    if (g->inedge[i].size() == 0) return 1.;
    if (g->inedge[i].size() == 1) {
        if (g->inedge[i][0] == i) return 1.;
        else return 1. - c;
    }
    return calcDi_1(i, eps, early, R, tid);
}

double Sling::calcDi_1(int i, double eps, bool &early, int &R, int tid) {
    const auto &in_i = g->inedge[i];
    const vector<int> *inedge = g->inedge;
    const int isize = in_i.size();
    eps = eps * (double) isize / (double) (isize - 1) / c;
//    int Rs = 14. / 3. / eps * 2. * log(2 * g->n) / log(2.71828);
//    constexpr double failure_probability = 0.01;

    int Rs = 14. / 3. / eps * log(4.0 * g->n / failure_probability) / log(2.71828);
    int X = 0;
    double cc = c * (1l << 32);
    for (; R < Rs; ++R) {
        int rx = gen.rand(tid) % isize;
        int ry = gen.rand(tid) % (isize - 1);
        if (ry >= rx) ++ry;
        int x = in_i[rx];
        int y = in_i[ry];
        do {
            if (inedge[x].empty() || inedge[y].empty())
                break;
            x = inedge[x][gen.rand(tid) % inedge[x].size()];
            y = inedge[y][gen.rand(tid) % inedge[y].size()];
            if (x == y) {
                ++X;
                break;
            }
        } while (gen.rand(tid) < cc);
    }
    if (X / (double) R <= eps) {
        return 1. - c * c * (X / (double) R) * (double) (isize - 1) / (double) isize - c / (double) isize;
    }
    double upp = X / (double) R + sqrt(eps * X / (double) R);
//    int Rl = (2 * upp + 2. / 3. * eps) / (eps * eps) * log(2 * g->n) / log(2.71828);
    int Rl = (2 * upp + 2. / 3. * eps) / (eps * eps) * log(4.0 * g->n / failure_probability) / log(2.71828);
    for (; R < Rl; ++R) {
        int rx = gen.rand(tid) % isize;
        int ry = gen.rand(tid) % (isize - 1);
        if (ry >= rx) ++ry;
        int x = in_i[rx];
        int y = in_i[ry];
        do {
            if (inedge[x].empty() || inedge[y].empty())
                break;
            x = inedge[x][gen.rand(tid) % inedge[x].size()];
            y = inedge[y][gen.rand(tid) % inedge[y].size()];
            if (x == y) {
                ++X;
                break;
            }
        } while (gen.rand(tid) < cc);
    }
    early = false;
    return 1. - c * c * (X / (double) R) * (double) (isize - 1) / (double) isize - c / (double) isize;
}

void Sling::t_calcD(double eps, mutex *lock, int *cursor, int tid) {
    int i, u;
    while (true) {
        lock->lock();
        i = *cursor;
        u = ((*cursor) += BLOCKSIZE);
        lock->unlock();
        if (i >= g->n)
            return;
        if (u > g->n) u = g->n;
        for (; i < u; ++i) {
            int RWCNT;
            bool early;
            double di = calcDi(i, eps / c, early, RWCNT, tid);
            d[i] = di;
        }
    }
}

void __Sling_t_calcD(Sling *sim, double eps, mutex *lock, int *cursor, int tid) {
    sim->t_calcD(eps, lock, cursor, tid);
}

void Sling::calcD(double eps) {
    if (d != NULL) delete[] d;
    d = new double[g->n];
    int cursor = 0;
    mutex lock;
    vector<thread> threads;
    for (int i = 0; i < NUMTHREAD - 1; ++i)
        threads.emplace_back(__Sling_t_calcD, this, eps, &lock, &cursor, i);
    t_calcD(eps, &lock, &cursor, NUMTHREAD - 1);
    for (int t = 0; t < NUMTHREAD - 1; ++t)
        threads[t].join();
    d_bar = 0.;
    for (int i = 0; i < g->n; ++i)
        d_bar += d[i];
    d_bar /= g->n;
}

//########################### indexing 2nd: Backward Propagation ####################################
map<pair<int, int>, double, PairCmp> Sling::pushback(int u, double eps, int tid) {
    map<pair<int, int>, double, PairCmp> p;
    gmap<int, double> pt;
    gmap<int, double> ptt;
    gset<int> s;
    deque<int> q;
    deque<int> qq;

    int t = 0;
    pt[u] = 1.;
    q.push_back(u);

    while (!q.empty()) {
        int v = q.front();
        double puvt = pt[v];;
        p[make_pair(v, t)] = puvt;
        for (unsigned i = 0; i < g->edge[v].size(); ++i) {
            int vv = g->edge[v][i];
            double x = ptt[vv] += puvt * sqrtc / (double) g->inedge[vv].size();
            if (x > eps)// || (t < 2 && x > eps / K))
            {
                if (s.find(vv) == s.end()) {
                    qq.push_back(vv);
                    s.insert(vv);
                }
            }
        }
        q.pop_front();
        if (q.empty()) {
            s.clear();
            pt.swap(ptt);
            ptt.clear();
            q.swap(qq);
            qq.clear();
            ++t;
        }
    }
    p.erase(make_pair(u, 0));
    return p;
}

void Sling::t_backward(double eps, mutex *tasklock, int *cursor, int tid, mutex *plock) {
    int s, t;
//    vector<tuple<int, int, int, double>> vec;
    while (true) {
//        vec.clear();
        tasklock->lock();
        s = *cursor;
        t = ((*cursor) += BLOCKSIZE);
        tasklock->unlock();

        if (s >= g->n) return;
        if (t > g->n) t = g->n;

        for (int v = s; v < t; ++v) {
            auto pv = pushback(v, eps, tid);
            for (auto itr = pv.begin(); itr != pv.end(); ++itr) {
                if (itr->second >= eps || (itr->first.second < 2 && itr->second >= eps / K)) {
                    int u = itr->first.first;
                    int t = itr->first.second;
                    double value = itr->second;
                    if (t == 1 && first[u]) continue;
                    if (t == 2 && second[u]) continue;

//                    p.push_back(make_tuple(u, t, v, value));
                    con_vec_p.push_back(make_tuple(u, t, v, value));
//                    con_vec_p.emplace_back(u, t, v, value);
                }
            }
        }
    }
}

void __Sling_t_backward(Sling *sim, double eps, mutex *tasklock, int *cursor, int tid, mutex *plock) {
    sim->t_backward(eps, tasklock, cursor, tid, plock);
}

void Sling::backward(double eps) {
//    Sling::NUMTHREAD = 1;
    log_info("Backward Multi Threading: %d", Sling::NUMTHREAD);
    int plockNum = (g->n - 1) / BLOCKSIZE + 1;
    auto *plock = new mutex[plockNum];
    mutex tasklock;
    int cursor = 0;
//    if (!p.empty()) { p.clear(); }
//    p.reserve(2339768660l);

    if (!con_vec_p.empty()) { con_vec_p.clear(); }
//    con_vec_p.reserve(2339768660l);
    con_vec_p.reserve(233976866l);

    vector<thread> threads;
    for (int i = 0; i < NUMTHREAD - 1; ++i)
        threads.emplace_back(__Sling_t_backward, this, eps, &tasklock, &cursor, i, plock);
    t_backward(eps, &tasklock, &cursor, NUMTHREAD - 1, plock);
    for (int t = 0; t < NUMTHREAD - 1; ++t)
        threads[t].join();
    delete[] plock;
    cerr << "sort" << endl;
//    sort(p.begin(), p.end(), cmpTuple);
    tbb::parallel_sort(con_vec_p.begin(), con_vec_p.end(), cmpTuple);
    cerr << "sort finished" << endl;
    pstart.resize(g->n + 1);
    pstart[0] = 0;
    int x = 1;
    for (long long i = 0; i < con_vec_p.size(); ++i) {
        if (std::get<0>(con_vec_p[i]) >= x) {
            for (; x <= std::get<0>(con_vec_p[i]); ++x)
                pstart[x] = i;
        }
    }
    for (; x <= g->n; ++x)
        pstart[x] = con_vec_p.size();
    p = vector<tuple<int, int, int, double>>{begin(con_vec_p), end(con_vec_p)};
}

///----------------------------- 1st: single pair
double Sling::simrank(int u, int v) {
    assert(!p.empty() && d != nullptr);
    if (u == v) return 1.;
    google::sparse_hash_map<pair<int, int>, double, PairHash> pu;
    gmap<pair<int, int>, double, PairHash> pv;
    gmap<pair<int, int>, double, PairHash> ppu;
    gmap<pair<int, int>, double, PairHash> ppv;
    for (long long i = pstart[u]; i < pstart[u + 1]; ++i) {
        pu[make_pair(std::get<1>(p[i]), std::get<2>(p[i]))] = std::get<3>(p[i]);
    }
    for (long long i = pstart[v]; i < pstart[v + 1]; ++i) {
        pv[make_pair(std::get<1>(p[i]), std::get<2>(p[i]))] = std::get<3>(p[i]);
    }

    int cnt = 0;
    int thr = 1. / sqrt(BACKEPS);
    for (long long i = pstart[u]; i < pstart[u + 1] && cnt < thr; ++i) {
        int t = std::get<1>(p[i]);
        int x = std::get<2>(p[i]);
        if (g->inedge[x].size() > thr || g->inedge[x].empty()) continue;
        ++cnt;
        for (auto xitr = g->inedge[x].begin(); xitr != g->inedge[x].end(); ++xitr) {
            if (pu.find(make_pair(t + 1, *xitr)) == pu.end())
                ppu[make_pair(t + 1, *xitr)] += std::get<3>(p[i]) * c / g->inedge[x].size();
        }
    }
    cnt = 0;
    for (long long i = pstart[v]; i < pstart[v + 1] && cnt < thr; ++i) {
        int t = std::get<1>(p[i]);
        int x = std::get<2>(p[i]);
        if (g->inedge[x].size() > thr || g->inedge[x].empty()) continue;
        ++cnt;
        for (auto xitr = g->inedge[x].begin(); xitr != g->inedge[x].end(); ++xitr) {
            if (pv.find(make_pair(t + 1, *xitr)) == pv.end())
                ppv[make_pair(t + 1, *xitr)] += std::get<3>(p[i]) * c / g->inedge[x].size();
        }
    }
    for (auto itr = ppu.begin(); itr != ppu.end(); ++itr) {
        pu[itr->first] = itr->second;
    }
    for (auto itr = ppv.begin(); itr != ppv.end(); ++itr) {
        pv[itr->first] = itr->second;
    }
    if (first[u]) {
        for (auto itr = g->inedge[u].begin(); itr != g->inedge[u].end(); ++itr) {
            pu[make_pair(1, *itr)] = sqrtc / g->inedge[u].size();
        }
    }
    if (second[u]) {
        for (auto uitr = g->inedge[u].begin(); uitr != g->inedge[u].end(); ++uitr) {
            int v = *uitr;
            for (auto vitr = g->inedge[v].begin(); vitr != g->inedge[v].end(); ++vitr) {
                pu[make_pair(2, *vitr)] += c / g->inedge[u].size() / g->inedge[v].size();
            }
        }
    }
    if (first[v]) {
        for (auto itr = g->inedge[v].begin(); itr != g->inedge[v].end(); ++itr) {
            pv[make_pair(1, *itr)] = sqrtc / g->inedge[v].size();
        }
    }
    if (second[v]) {
        for (auto vitr = g->inedge[v].begin(); vitr != g->inedge[v].end(); ++vitr) {
            int vv = *vitr;
            for (auto vitr = g->inedge[vv].begin(); vitr != g->inedge[vv].end(); ++vitr) {
                pv[make_pair(2, *vitr)] += c / g->inedge[v].size() / g->inedge[vv].size();
            }
        }
    }
    double sim = 0.;
    for (auto uitr = pu.begin(); uitr != pu.end(); ++uitr) {
        auto vitr = pv.find(uitr->first);
        if (vitr == pv.end())
            continue;
        sim += uitr->second * d[uitr->first.second] * vitr->second;
    }
    return sim;
}

// TODO
///----------------------------- 2nd: single source
vector<double> Sling::simrank(int u) {
    assert(!p.empty() && d != NULL);
    map<pair<int, int>, double, PairCmp> pu;
    for (long long i = pstart[u]; i < pstart[u + 1]; ++i)
        pu[make_pair(std::get<1>(p[i]), std::get<2>(p[i]))] = std::get<3>(p[i]);
    gmap<pair<int, int>, double, PairHash> ppu;
    int cnt = 0;
    int thr = 1. / sqrt(BACKEPS);
    for (long long i = pstart[u]; i < pstart[u + 1] && cnt < thr; ++i) {
        int t = std::get<1>(p[i]);
        int x = std::get<2>(p[i]);
        if (g->inedge[x].size() > thr || g->inedge[x].empty()) continue;
        ++cnt;
        for (auto xitr = g->inedge[x].begin(); xitr != g->inedge[x].end(); ++xitr) {
            if (pu.find(make_pair(t + 1, *xitr)) == pu.end())
                ppu[make_pair(t + 1, *xitr)] += std::get<3>(p[i]) * c / g->inedge[x].size();
        }
    }
    for (auto itr = ppu.begin(); itr != ppu.end(); ++itr) {
        pu[itr->first] = itr->second;
    }
    if (second[u]) {
        for (auto uitr = g->inedge[u].begin(); uitr != g->inedge[u].end(); ++uitr) {
            int v = *uitr;
            for (auto vitr = g->inedge[v].begin(); vitr != g->inedge[v].end(); ++vitr) {
                pu[make_pair(2, *vitr)] += c / g->inedge[u].size() / g->inedge[v].size();
            }
        }
    }
    if (first[u]) {
        for (auto itr = g->inedge[u].begin(); itr != g->inedge[u].end(); ++itr) {
            pu[make_pair(1, *itr)] = sqrtc / g->inedge[u].size();
        }
    }
    vector<double> sim(g->n, 0.);
    vector<double> q;
    vector<double> qq;
    deque<int> s;
    deque<int> ss;
    for (int t = 1;; ++t) {
        auto start = pu.lower_bound(make_pair(t, 0));
        if (start == pu.end())
            break;
        auto end = pu.lower_bound(make_pair(t + 1, 0));
        if (start == end) continue;
        q.clear();
        q.resize(g->n, 0.);
        qq.clear();
        qq.resize(g->n, 0.);
        s.clear();
        ss.clear();
        int TTT = g->n / 10;
        for (; start != end; ++start) {
            q[start->first.second] = start->second * d[start->first.second];
            s.push_back(start->first.second);
        }
        double eps = sqrtct[t] * BACKEPS;
        for (int l = 1; l < t; ++l) {
            if (s.size() > TTT) {
                for (int u = 0; u < g->n; ++u) {
                    if (q[u] <= eps) continue;
                    for (auto vitr = g->edge[u].begin(); vitr != g->edge[u].end(); ++vitr) {
                        int v = *vitr;
                        qq[v] += sqrtc * q[u] / (double) g->inedge[v].size();
                    }
                }
                q.swap(qq);
                qq.clear();
                qq.resize(g->n, 0.);
            } else {
                for (auto uitr = s.begin(); uitr != s.end(); ++uitr) {
                    int u = *uitr;
                    for (auto vitr = g->edge[u].begin(); vitr != g->edge[u].end(); ++vitr) {
                        int v = *vitr;
                        double inc = sqrtc * q[u] / (double) g->inedge[v].size();
                        double x = qq[v] += inc;
                        if (x > eps && x <= eps + inc) {
                            ss.push_back(v);
                        }
                    }
                }
                q.swap(qq);
                qq.clear();
                qq.resize(g->n, 0.);
                s.swap(ss);
                ss.clear();
            }
        }
        if (s.size() > TTT) {
            for (int u = 0; u < g->n; ++u) {
                if (q[u] <= eps) continue;
                for (auto vitr = g->edge[u].begin(); vitr != g->edge[u].end(); ++vitr) {
                    int v = *vitr;
                    sim[v] += sqrtc * q[u] / (double) g->inedge[v].size();
                }
            }
        } else {
            for (auto uitr = s.begin(); uitr != s.end(); ++uitr) {
                int u = *uitr;
                for (auto vitr = g->edge[u].begin(); vitr != g->edge[u].end(); ++vitr) {
                    int v = *vitr;
                    sim[v] += sqrtc * q[u] / (double) g->inedge[v].size();
                }
            }
        }
    }

    sim[u] = 1.;
    return sim;
}

string Sling::get_file_path_base() {
    exec(string("mkdir -p " + SLING_INDEX_DIR).c_str());
    string file_path =
            SLING_INDEX_DIR + "/" + boost::str(boost::format("RLP_%s-%.3f-%.6f-%.6f") % g_name % c % eps_d % theta);
    return file_path;
}

void Sling::build_or_load_index() {
    string d_file_path = get_file_path_base() + ".d";
    string p_file_path = get_file_path_base() + ".p";
    string pstart_file_path = get_file_path_base() + ".pstart";
    if (file_exists(d_file_path) && file_exists(p_file_path) && file_exists(pstart_file_path)) {
        log_info("indexing exists.......");
        YcheSerializer serializer;
        log_info("d_file_path: %s", d_file_path.c_str());
        log_info("p_file_path: %s", p_file_path.c_str());
        FILE *pFile = fopen(d_file_path.c_str(), "r");
        size_t tmp;
        serializer.read_array_into_ref(pFile, d, tmp);
        log_info("d size: %s", to_string(tmp).c_str());
        fclose(pFile);

        FILE *pFile2 = fopen(p_file_path.c_str(), "r");
        serializer.read_tuple_vec(pFile2, p);
        fclose(pFile2);
        log_info("p size: %s", to_string(p.size()).c_str());

        FILE *pFile3 = fopen(pstart_file_path.c_str(), "r");
        serializer.read_vec(pFile3, pstart);
        fclose(pFile3);
    } else {
        //  build the index
        cout << "indexing..." << endl;

        auto tmp_start = std::chrono::high_resolution_clock::now();
        calcD(eps_d);
        auto tmp_end = std::chrono::high_resolution_clock::now();

        cout << "finish calcD " << float(duration_cast<microseconds>(tmp_end - tmp_start).count()) / (pow(10, 6))
             << " s\n";
        tmp_start = std::chrono::high_resolution_clock::now();
        backward(theta);
        tmp_end = std::chrono::high_resolution_clock::now();

        cout << "finish backward " << float(duration_cast<microseconds>(tmp_end - tmp_start).count()) / (pow(10, 6))
             << " s\n";
        cout << "mem size:" << getValue() << endl;

        cout << "store index......." << endl;
        // store indexing d
        YcheSerializer serializer;
        FILE *pFile = fopen(d_file_path.c_str(), "wb");
        serializer.write_array(pFile, d, static_cast<size_t>(g->n));
        fclose(pFile);
        // store indexing p
        FILE *pFile2 = fopen(p_file_path.c_str(), "wb");
        serializer.write_tuple_vec(pFile2, p);
        fclose(pFile2);
        // store indexing pstart

        FILE *pFile3 = fopen(pstart_file_path.c_str(), "wb");
        serializer.write_vec(pFile3, pstart);
        fclose(pFile2);
    }
}


#ifndef MATHLEXER_HPP
#define MATHLEXER_HPP

#include <iostream>
#include <memory>
#include <sstream>
#include <vector>

#include <boost/lexical_cast.hpp>

namespace simple_interpreter {

struct Token
{
   enum Type
   {
      integer,
      plus,
      minus,
      lparen,
      rparen
   } type;

   std::string text;

   Token(const Type t, const std::string& str) : type(t), text(str)
   {
   }

   friend std::ostream&
   operator<<(std::ostream& os, const Token& token)
   {
      os << "`" << token.text << "`";
      return os;
   }
};

std::vector< Token >
lex(const std::string text)
{
   std::vector< Token > tokens;
   for (size_t charIdx = 0; charIdx < text.size(); ++charIdx)
   {
      switch (text[charIdx])
      {
         case '+':
            tokens.emplace_back(Token::plus, "+");
            break;
         case '-':
            tokens.emplace_back(Token::minus, "-");
            break;
         case '(':
            tokens.emplace_back(Token::lparen, "(");
            break;
         case ')':
            tokens.emplace_back(Token::rparen, ")");
            break;
         default:
            if (std::isdigit(text[charIdx]))
            {
               std::ostringstream buffer;
               buffer << text[charIdx];
               for (size_t i = charIdx + 1; i < text.size(); ++i)
               {
                  if (std::isdigit(text[i]))
                  {
                     buffer << text[i];
                     ++charIdx;
                  }
                  else
                  {
                     break;
                  }
               }

               tokens.emplace_back(Token::integer, buffer.str());
            }
            else
            {
               throw std::runtime_error{std::string("error: Unexpected token: `") + text[charIdx] + '`'};
            }
      }
   }

   return tokens;
}

struct Element
{
   virtual int
   eval() const = 0;
};

struct Integer : Element
{
   int value;

   Integer(const int newVal) : value(newVal)
   {
   }

   int
   eval() const override
   {
      return value;
   }
};

struct BinaryOperation : Element
{
   enum Type
   {
      addition,
      subtraction
   } type;

   std::shared_ptr< Element > lhs, rhs;

   int
   eval() const override
   {
      switch (type)
      {
         case addition:
            return lhs->eval() + rhs->eval();
         case subtraction:
            return lhs->eval() - rhs->eval();
      }
      return 0;
   }
};

std::shared_ptr< Element >
parse(const std::vector< Token >& tokens)
{
   auto rootOp = std::make_shared< BinaryOperation >();

   bool haveLhs{false};

   for (size_t tokenIdx = 0; tokenIdx < tokens.size(); ++tokenIdx)
   {
      const auto& token = tokens[tokenIdx];
      switch (token.type)
      {
         case Token::integer:
         {
            const int value = boost::lexical_cast< int >(token.text);
            auto integer = std::make_shared< Integer >(value);
            if (!haveLhs)
            {
               rootOp->lhs = integer;
               haveLhs = true;
            }
            else
            {
               rootOp->rhs = integer;
            }
            break;
         }
         case Token::minus:
         {
            rootOp->type = BinaryOperation::subtraction;
            break;
         }
         case Token::plus:
         {
            rootOp->type = BinaryOperation::addition;
            break;
         }
         case Token::lparen:
         {
            bool rparenFound{false};
            size_t rparenIdx = tokenIdx + 1;
            for (; rparenIdx < tokens.size(); ++rparenIdx)
            {
               if (tokens[rparenIdx].type == Token::rparen)
               {
                  rparenFound = true;
                  break;
               }
               else if (tokens[rparenIdx].type == Token::lparen)
               {
                  throw std::runtime_error{"error: Nested parens not supported yet"};
               }
            }
            if (rparenFound)
            {
               std::vector< Token > subExpression{tokens.begin() + tokenIdx + 1, tokens.begin() + rparenIdx};
               auto element = parse(subExpression);
               if (!haveLhs)
               {
                  rootOp->lhs = element;
                  haveLhs = true;
               }
               else
               {
                  rootOp->rhs = element;
               }
               tokenIdx = rparenIdx;
            }
            else
            {
               throw std::runtime_error{"error: Missing `)`"};
            }
            break;
         }
         case Token::rparen:
         {
            throw std::runtime_error{"error: `)` should be ignored"};
            break;
         }
      }
   }

   return rootOp;
}

} // namespace simple_interpreter

#endif // MATHLEXER_HPP