#include <stdlib.h>
#include <fstream>
#include <irdb-core>

#include "mg.hpp"
#include <getopt.h>

using namespace std;
using namespace IRDB_SDK;

#define ALLOF(a) begin(a),end(a)

class MoveGlobalsDriver_t :  public TransformStep_t
{


void usage(string programName)
{

	auto et_names=string();
	for(const auto &str : elftable_names )
		et_names+=str+" ";

	cerr << "Usage: " << programName << " <variant id> <annotation file>" <<endl;
	cout<<"--elftables-only,-o		Move sections titled \""<<et_names<<"\""<<endl;
	cout<<"--aggressive 			Use aggressive heuristics to move more variables (does not affect elftables) "<<endl;
	cout<<"--no-conservative 		alias for --aggressive"<<endl;
	cout<<"--conservative 			(default) Use conservative heuristics to increase reliability (does not affect elftables) "<<endl;
	cout<<"--no-aggressive 			alias for --conservative"<<endl;

	cout<<endl;
	cout<<"---- debugging options (power users only) "<<endl;
	cout<<"--use-stars 			Enable STARS deep analysis for more analysis precision (current default). "<<endl;
	cout<<"--no-use-stars 			Disable STARS deep analysis for more analysis precision. "<<endl;
	cout<<"--move,-m			Move only the given objects."<<endl;
	cout<<"--dont,-d			Dont move the listed objects (overrides --only)."<<endl;
	cout<<"--number,-n			Max number of scoops to move."<<endl;
        cout<<"--random,-r                      Randomly select scoops to move."<<endl;
	cout<<"--help,--usage,-?,-h		Display this message"<<endl;
}

DatabaseID_t variantID = BaseObj_t::NOT_IN_DATABASE;
string dont_move = "";
string move_only = "";
size_t max_moveables = 0;
bool random = false;
bool aggressive = false;
VariantID_t* pidp = nullptr;
const string programName=string("libmove_globals.so");
bool use_stars=true;

int parseArgs(const vector<string> step_args) 
{	

        auto argv = vector<char*>({const_cast<char*>("libmove_globals.so")});
        transform(ALLOF(step_args), back_inserter(argv), [](const string &s) -> char* { return const_cast<char*>(s.c_str()); } );
	const auto argc = argv.size();

	// Parse some options for the transform
	const static struct option long_options[] = {
		{"elftables-only", no_argument, 0, 'o'},
		{"use-stars", no_argument, 0, 's'},
		{"no-use-stars", no_argument, 0, 't'},
		{"move", required_argument, 0, 'm'},
		{"dont", required_argument, 0, 'd'},
		{"number", required_argument, 0, 'n'},
		{"aggressive", no_argument, 0, 'a'},
		{"no-aggressive", no_argument, 0, 'A'},
		{"conservative", no_argument, 0, 'A'},
		{"no-conservative", no_argument, 0, 'a'},
                {"random", no_argument, 0, 'r'},
		{"help", no_argument, 0, 'h'},
		{"usage", no_argument, 0, '?'},
		{0,0,0,0}
	};
	auto short_opts="b:oh?m:d:n:aAst";
	while(1) 
	{
		int c = getopt_long(argc, &argv[0], short_opts, long_options, nullptr);
		if (c == -1)
			break;
		switch(c) {
			case 0:
				break;
			case 'c':
			case 'o':
				// add elftables to move only list
				for(const auto &str : elftable_names )
					move_only+= str+" ";
				break;
			case 's':
					use_stars=true;
				break;
			case 't':
					use_stars=false;
				break;
			case 'm':
				move_only+=string(" ") + optarg;
				break;
			case 'd':
				dont_move+=string(" ") + optarg;
				break;
			case 'n':
				max_moveables+=strtoll(optarg,nullptr,0);
				break;
                       case 'r':
                               random=true;
                               break;
			case 'a':
				cout<<"Setting aggressive mode"<<endl;
				aggressive=true;
				break;
			case 'A':
				cout<<"Setting conservative mode"<<endl;
				aggressive=false;
				break;
			case '?':
			case 'h':
				usage("libmove_globals.so");
				return 1;
			default:
				break;
		}
	}
	return 0;
}


int executeStep()
{
	variantID=getVariantID();
	auto irdb_objects=getIRDBObjects();

	auto exit_code = (int) 0;

	/* setup the interface to the sql server */
	const auto pqxx_interface=irdb_objects->getDBInterface();
	BaseObj_t::setInterface(pqxx_interface);

	// get the variant info from the database
	pidp=irdb_objects->addVariant(variantID); // new VariantID_t(variantID);
	assert(pidp && pidp->isRegistered()==true);
	auto transformExitCode = (int) 0;

	for(auto this_file : pidp->getFiles())
	{
		try 
		{
			/* read the IR from the DB */
			auto firp =  irdb_objects->addFileIR(variantID, this_file->getBaseID()); // new FileIR_t(*pidp, this_file);
			cout<<"Transforming "<<this_file->getURL()<<endl;
			assert(firp && pidp);


			/*
			 * Create a transformation and then
			 * invoke its execution.
			 */
			if (firp->getArchitectureBitWidth() == 64)
			{	
                		MoveGlobals64_t mg(pidp, firp, dont_move, move_only, max_moveables, random, aggressive, use_stars);
				transformExitCode = mg.execute(*pqxx_interface);
			}
			else
			{
                		MoveGlobals32_t mg(pidp, firp, dont_move, move_only, max_moveables, random, aggressive, use_stars);
				transformExitCode = mg.execute(*pqxx_interface);
			}
			/*
			 * If everything about the transformation
			 * went okay, then we will write the updated
			 * set of instructions to the database.
			 */
			if (transformExitCode != 0)
			{
				cerr << programName << ": transform failed. Check logs." << endl;
				exit_code=2;
			}
		}
		catch (DatabaseError_t pnide)
		{
			cerr << programName << ": Unexpected database error: " << pnide << endl;
			return 1;
		}
		catch (const std::exception &exc)
		{
		    // catch anything thrown within try block that derives from std::exception
			std::cerr << "Unexpected exception: " << exc.what();
			return 1;
		}
		catch (...)
		{
			cerr << programName << ": Unexpected error" << endl;
			return 1;
		}
	}

	return exit_code;
}

std::string getStepName(void) const override
{
        return std::string("move_globals");
}

};

extern "C"
shared_ptr<TransformStep_t> getTransformStep(void)
{
        return shared_ptr<TransformStep_t>(new MoveGlobalsDriver_t());
}