/* Copyright (c) 2018
   Charles Jordan (Hokkaido University, Sapporo, Japan),
   Michael Joswig, Lars Kastner (Technische Universitaet Berlin, Germany)

   This program is free software; you can redistribute it and/or modify it
   under the terms of the GNU General Public License as published by the
   Free Software Foundation; either version 3, or (at your option) any
   later version: http://www.gnu.org/licenses/gpl.txt.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.
-----------------------------------------------------------------------------
*/
/* Interface between ReverseSearch and mts */

#define MTS

#include "CommandlineOptions.hh"
#include "ComputeTriangs.hh"
#include "PlacingTriang.hh"
#include "ReverseSearchTriangulations.hh"
#include "version.hh"
#include <iostream>
#include <sstream>

// MtsErr mtsErr;
// StateOutput stateOutput;
using namespace std;

Chirotope chiro;
PointConfiguration points;
size_type no;
size_type rrank;
SymmetryGroup *symmetries;
SimplicialComplex seed;
SymmetryGroup *seed_symmetries;
ReverseSearchWrapper *r;
size_type N;

#define MPTOPCOM_FOR_MTS "mptopcom (" MPTOPCOM_VERSION_STRING ")"

void put_output(size_type count, const std::string& outputString);

extern "C" {
	#include "mts.h"
	struct bts_data {
		mts_stream *output;
		mts_stream *err;
		int delta;
	};
	Node *n;
	bts_data *bdata;
	int procno;
	void from_node(Node *, TriangNode &);
	void to_node(Node *, const TriangNode &);
	char name[] = MPTOPCOM_FOR_MTS;
	/* option and number of parameters to option */
	const mtsoption bts_options[] = {{"--nosymmetries", 0},
                                    {"--flip-cache", 1},
                                    {"--regular", 0},
                                    {"-v", 0},
                                    {"--orbit-cache",1},
                                    {"--full", 0},
                                    {"--small-group", 0},
                                    {"--large-group", 0},
                                    {"--central", 0},
                                    {"--cyclic-flips", 0},
                                    {"--regularity-cache", 1},
                                    {"--polymake-lp-client", 1},
                                    {"--all-edges", 0},
                                    {"--version", 0}
                                    };
	const int bts_options_size = 14;
	Node node;

void bts_finish(bts_data *b, shared_data *sdata, int size, int checkpointing)
{
	int i;
	unsigned long long tot_count = 0;

	for (i=0; i<size; i++)
	{
		if (sdata[i].size_sdlong>0)
			tot_count += sdata[i].sdlong[0];
	}

	stream_printf(b->err, "Total triangulations: %llu\n", tot_count);
	return;
}

/* mts process startup */
bts_data *bts_init(int argc, char **argv, Data *in, int proc_no)
{
	procno = proc_no;
	const bool preprocess = false;

	bts_data *b = (bts_data *)malloc(sizeof(bts_data));
	bdata = b;

	b->output = open_stream(MTSOUT);
	b->err = open_stream(MTSERR);
   
   // Trigger redirect of output.
   cerrWrap.redirect();
   cerrFirstWorker.redirect();
   // This line lets TOPCOM read through the command line arguments to extract
   // its options.
   CommandlineOptions::init(argc, (const char **)argv);

	stringstream stb(string(in->input));

	if (!points.read(stb)) {
		freebdata(b);
		return NULL;
	}
	if ((points.no() < 2) || (points.rank() < 2)) {
		freebdata(b);
		return NULL;
	}
	if (points.rank() > points.no()) {
		freebdata(b);
		return NULL;
	}
	chiro = Chirotope(points, preprocess);
	no = chiro.no();
	rrank = chiro.rank();
	symmetries = new SymmetryGroup(no);
	if (!CommandlineOptions::ignore_symmetries()) {
		symmetries->read(stb);
	}
	if (!seed.read(stb)) {
		seed = PlacingTriang(chiro);
	}
   r = constructReverseSearch(points, chiro, *symmetries);
	b->delta = r->size();
	return b;
}

/* get initial mts root */
Node *get_root(bts_data *b)
{
	Node *root = (Node *)malloc(sizeof(Node));
	root->vlong = NULL;
	root->size_vlong = 0;
	root->vint = NULL;
	root->size_vint = 0;
	root->vchar = NULL;
	root->size_vchar = 0;
	root->vfloat = NULL;
	root->size_vfloat = 0;
	root->vdouble = NULL;
	root->size_vdouble = 0;
	root->depth = 0;
	root->unexplored = 0;
	const TriangNode initial_triang = r->get_initial_triang(no, rrank, seed);
	TriangNode lex_optimal = r->find_max_triang(initial_triang);
	to_node(root, lex_optimal);
	return root;
}

void freebdata(bts_data *b)
{
	free(b);
	delete r;
	delete symmetries;
	delete seed_symmetries;
	return;
}

long bts(bts_data *b, Node *localRoot, long max_depth, long max_nodes,
	 shared_data *sdata, int rk, int size)
{
	unsigned long count;
	unsigned long startc = r->totalcount();
	long ret;
	n = localRoot;
	TriangNode tn;
	from_node(localRoot, tn);
	ret = r->generic_reverse_search(tn, max_nodes, max_depth);
	count = r->totalcount();
	if (sdata[rk].size_sdlong<1)
	{
		sdata[rk].size_sdlong = 1;
		sdata[rk].sdlong = (long *)malloc(sizeof(long));
		sdata[rk].update_final = 1;
		sdata[rk].sdlong[0] = 0;
	}
	sdata[rk].sdlong[0] += (count-startc);
	return ret;
}

} /* extern "C" */

/* pack the TriangNode into an mts Node */
void to_node(Node *mtsNode, const TriangNode &tn)
{
	stringstream stb;
	stb << tn;
	string str = stb.str();
	const char *cs = str.c_str();
	char *buf;
	int len = strlen(cs);

	if (mtsNode->size_vchar <= len)
	{
		buf = (char *)realloc(mtsNode->vchar, sizeof(char)*(len+1));
		if (buf == NULL)
			emergencystop("No memory");
		mtsNode->vchar = buf;
	}

	strcpy(mtsNode->vchar, cs);
	mtsNode->size_vchar = len+1;
}

/* unpack the mts Node into the TriangNode */
void from_node(Node *mtsNode, TriangNode& tn)
{
	stringstream stb(mtsNode->vchar);
	stb >> tn;
}

/* over budget so return this one */
void mtstcreturn(const TriangNode& tn)
{
	to_node(n, tn);
	n->unexplored = 1;
	return_unexp(n);
}

void put_output(size_type count, const std::string& outputString)
{
   if(CommandlineOptions::verbose()){
      if(count % 1000 == 0){
        stringstream ste;
	ste <<  "Worker " << procno << " found " << count << " triangulations." << std::endl;
        string stre = ste.str();
	const char *ce = stre.c_str();
	stream_printf(bdata->err, "%s", ce);
      }
   }
	stringstream stb;
	stb << "(" << procno << "." << count << "): " << outputString << endl;
	string str = stb.str();
	const char *cs = str.c_str();
	stream_printf(bdata->output, "%s", cs);
}

void put_error(const stringstream& stb)
{
	string str = stb.str();
	const char *cs = str.c_str();
	stream_printf(bdata->err, "%s", cs);
}

void report_state(const stringstream& stb)
{
   if(procno == MTS_FIRSTWORKER){
      string str = stb.str();
      const char *cs = str.c_str();
      stream_printf(bdata->err, "%s", cs);
   }
}
