#include <string>
#include <map>
#include <iostream>
#include <fstream>
#include <list>
#include <stack>

#include "arguments_manager.h"
#include "exceptions.h"

using namespace std;
using namespace Exceptions;

bool file_exists(const char * filename)
{
    if (FILE * file = fopen(filename, "r")) //I'm sure, you meant for READING =)
    {
        fclose(file);
        return true;
    }
    return false;
}

void ArgumentsManager::addParam(string name, string value)
{
    stringValues[name] = value;
    loadingTimes[name] = ticks++;
}

bool listContains(list<string> l, string val)
{
    for (list<string>::iterator i = l.begin(); i != l.end(); ++i)
    {
        if (*i == val)
        {
            return true;
        }
    }
    return false;
}

list<string> ArgumentsManager::unpresentFiles;

void ArgumentsManager::openConfig(const char* filename)
{

    if (!file_exists(filename))
    {
        if (!listContains(this->unpresentFiles, filename))
        {
            this->unpresentFiles.push_back(filename);
            printf("No such file or empty file: %s\n", filename);
        }
        return;
    }
    
    string line;
    ifstream *file = new ifstream(filename);
    stack<ifstream *> files;

    while (!file->eof())
    {
        getline(*file, line);
        line = trim(line);
        if (line[0] == '@')
        {
            if (strStartsWith(line, "@include "))
            {
                string path(line.c_str() + 9);
                string** splited = split(path, '"');
                if ((size_t)splited[-1] == 3)
                {
                    path = "etc/";
                    path += *splited[1];
                    if (file_exists(path.c_str()))
                    {
                        files.push(file);
                        file = new ifstream(path.c_str());
                    }
                }
            }
        }
        else if (line[0] != '#')
        {
            string** values = split(line, '=');
            if ((size_t)values[-1] == 2)
            {
                string key = trim(*values[0]);
                string value = trim(*values[1]);

                addParam(key, value);
            }
            splitFree(values);
        }
        if (file->eof())
            if (!files.empty())
            {
                file = files.top();
                files.pop();
            }
    }
    castValues();
}

void ArgumentsManager::reloadArgs()
{
    string s;
    string val;

    list<string> configs;

    for (int i = 1; i < argc; i++)
    {
        s = argv[i];

        if (strStartsWith(s, "--"))
            s = s.substr(2, s.length() - 2);
        else if (strStartsWith(s, "-"))
            s = s.substr(1, s.length() - 1);
        else
        {
            configs.push_back(s);
            continue;
        }

        if (i + 1 >= argc)
        {
            addParam(s, "true");
            continue;
        }

        if (argv[i + 1][0] == '-')
            continue;

        val = argv[++i];

        addParam(s, val);

    }
    castValues();
    list<string>::iterator it;
    for (it = configs.begin(); it != configs.end(); ++it)
        openConfig(it->c_str());

}

void ArgumentsManager::castValues()
{
    for(map<string,string>::iterator i = stringValues.begin(); i != stringValues.end(); ++i)
    {

        int tmp;
        if (sscanf(i->second.c_str(), "%d", &tmp) != EOF)
            intValues[i->first] = tmp;
        
        double tmp2;
        if (sscanf(i->second.c_str(), "%lf", &tmp2) != EOF)
            doubleValues[i->first] = tmp2;
        
        loadingTimes[i->first] = ticks++;

    }
}

ArgumentsManager::ArgumentsManager(int argc, char** argv)
{
    this->ticks = 0;
    this->argc = argc;
    this->argv = argv;
    reloadArgs();
}

bool ArgumentsManager::strStartsWith(string s, const char* beggining)
{
    char* value = (char*)s.c_str();
    char* tchar = (char*)beggining;
    while (*value)
    {
        if (!*tchar)
            return true;
        if (*value != *tchar)
            return false;
        tchar++;
        value++;
    }
    return false;
}

/*******************************************************************************
 * Erases the white spaces from the start and the end of the given in the      *
 * parameter string s and returns "trimmed" value in a result.                 *
 *******************************************************************************/

string ArgumentsManager::trim(string s)
{
    const char* cs = s.c_str();
    while ( (*cs != 0) && (*cs == ' ') )
        cs++;

    char copyCs[s.length()];
    char* copyBuf = copyCs;

    while (*cs != 0)
        *(copyBuf++) = *(cs++);
    *(copyBuf--) = 0;
    while ((copyBuf != copyCs) && ((*copyBuf==' ')||(*copyBuf=='\n')) )
        *(copyBuf--) = 0;

    return string(copyCs);
}

/*******************************************************************************
 * Splits given string s to an array of strings by the character c
 * the length of an array is returned in (size_t)result[-1] additionally
 * result[length] == NULL. An array an values won't be free automatically
 * and must be free using function splitFree.
 *******************************************************************************/

string** ArgumentsManager::split(string s, char c)
{
    char buf[s.length()];
    char* bufCarret = buf;

    const char* sCarret = s.c_str();

    int counter = 0;

    while (*sCarret)
        if (*(sCarret++)==c)
            counter++;

    string** result = new string*[counter + 3];
    result++;
    result[-1] = (string*)(counter + 1);
    result[counter + 1] = NULL;

    counter = 0;
    sCarret = s.c_str();
    while (*sCarret)
    {
        if (*sCarret==c)
        {
            *bufCarret = 0;
            result[counter++] = new string(buf);
            bufCarret = buf;
        }
        else
            *(bufCarret++) = *sCarret;
        sCarret++;
    }
    *bufCarret = 0;
    result[counter++] = new string(buf);

    return result;
}

/*******************************************************************************
 * Frees the memory allocated for spliting purposes                            *
 *******************************************************************************/
void ArgumentsManager::splitFree(string** spl)
{
    string** buf = spl--;
    while (*buf)
        delete *(buf++);
    delete[] spl;
}



const char* ArgumentsManager::getParam(const char* longName, const char* shortName)
{
    //string val = argumentsValues[shortName];
    map<string, string>::const_iterator tester = stringValues.find(longName);
    if (tester != stringValues.end())
        return tester->second.c_str();
    tester = stringValues.find(shortName);
    if (tester != stringValues.end())
        return tester->second.c_str();
    return NULL;
}

string ArgumentsManager::getStringParam(string longName, string shortName)
{
    int shortTime = -1;
    int longTime  = -1;

    map<string, int>::const_iterator shortTimeEstablisher = loadingTimes.find(longName);
    if (shortTimeEstablisher != loadingTimes.end())
        shortTime = shortTimeEstablisher->second;

    map<string, int>::const_iterator longTimeEstablisher = loadingTimes.find(shortName);
    if (longTimeEstablisher != loadingTimes.end())
        longTime = longTimeEstablisher->second;

    map<string, string>::const_iterator tester;
    
    if (shortTime > longTime)
    {
        tester = stringValues.find(longName);
        if (tester != stringValues.end())
            return tester->second;
    }
    else
    {
        tester = stringValues.find(shortName);
        if (tester != stringValues.end())
            return tester->second;
    }

    string message = "The following parameter is missing: ";
    message += longName + " (" + shortName + ").";
    throw new ParamDoesntExistException(message.c_str());
}

int ArgumentsManager::getIntParam(string longName, string shortName)
{
    int shortTime = -1;
    int longTime  = -1;

    map<string, int>::const_iterator shortTimeEstablisher = loadingTimes.find(longName);
    if (shortTimeEstablisher != loadingTimes.end())
        shortTime = shortTimeEstablisher->second;

    map<string, int>::const_iterator longTimeEstablisher = loadingTimes.find(shortName);
    if (longTimeEstablisher != loadingTimes.end())
        longTime = longTimeEstablisher->second;

    map<string, int>::const_iterator tester;

    if (shortTime > longTime)
    {
        tester = intValues.find(longName);
        if (tester != intValues.end())
            return tester->second;
    }
    else
    {
        tester = intValues.find(shortName);
        if (tester != intValues.end())
            return tester->second;
    }

    string message = "The following parameter is missing: ";
    message += longName + " (" + shortName + ").";
    throw new ParamDoesntExistException(message.c_str());
}

double ArgumentsManager::getDoubleParam(string longName, string shortName)
{
    int shortTime = -1;
    int longTime  = -1;

    map<string, int>::const_iterator shortTimeEstablisher = loadingTimes.find(longName);
    if (shortTimeEstablisher != loadingTimes.end())
        shortTime = shortTimeEstablisher->second;

    map<string, int>::const_iterator longTimeEstablisher = loadingTimes.find(shortName);
    if (longTimeEstablisher != loadingTimes.end())
        longTime = longTimeEstablisher->second;

    map<string, double>::const_iterator tester;
    if (shortTime > longTime)
    {
        tester = doubleValues.find(longName);
        if (tester != doubleValues.end())
            return tester->second;
    }
    else
    {
        tester = doubleValues.find(shortName);
        if (tester != doubleValues.end())
            return tester->second;
    }
    string message = "The following parameter is missing: ";
    message += longName + " (" + shortName + ").";
    throw new ParamDoesntExistException(message.c_str());
}

bool ArgumentsManager::getBoolParam(string longName, string shortName)
{
    string stringResult = getStringParam(longName, shortName);
    if (stringResult == "true")
        return true;
    if (stringResult == "false")
        return false;

    string message = "The following boolean parameter is missing or has wrong value: ";
    message += longName + " (" + shortName + ").";
    throw new ParamDoesntExistException(message.c_str());
}

bool ArgumentsManager::containParam(string longName, string shortName)
{
    map<string, double>::const_iterator tester = doubleValues.find(longName);
    if (tester != doubleValues.end())
        return true;
    tester = doubleValues.find(shortName);
    if (tester != doubleValues.end())
        return true;
    return false;
}
