#include <stdio.h>
#include <cstdlib>
#include <cmath>
#include <cstring>
#include <unistd.h>

#include "GAMSInOut.h"
#include "DataContainers.h"
#include "Exceptions.h"

void PHA::runPHA(double cR, int s) {
    double dist;
    int iter = 1;
    bool c = true;

    while (c && (iter < 1000)) {
        printf("==============================\n");
        printf("======= iteration %2d =========\n", iter);
        printf("==============================\n\n");
        S->makeGAMSSourceFiles(templet);
        S->changeRho(cR);
        runGAMSinParallel(s);
        S->loadAllScenarioSolutionsFromLSTFiles();
        //S->printAllScenarioSolutions();
        S->calcAverageSolution();
        printf("Average solution:\n");
        S->averageSolution1->printVector();
        for (int i = 0; i < S->numScenarios; i++) {
            printf("Average second-stage solution for scenario %d:\n", i + 1);
            S->averageSolution2[i]->printVector();
        }

        dist = S->calcTerminationCondition();
        S->saveAllScenarioSolutionsToFile(iter, dist, "vysledky.txt");
        printf("Rho = %f\n", S->rho);
        printf("Epsilon in %d-th iteration is %0.9f\n\n", iter++, dist);

        if (dist < eps) {
            //S->averageSolution->printVector();
            c = false;
        } else {
            S->updateAllWeights(rho);
        }
    }
    printf("Finished!\n\n");
    printf("Optimal solution found:\n");
    printf("1st stage decision:");
    S->averageSolution1->printVector();
    for (int i = 0; i < S->numScenarios; i++) {
        printf("2nd stage decision for scenario %d:\n", i + 1);
        S->averageSolution2[i]->printVector();
    }
}

void SCENARIOS::saveAllScenarioSolutionsToFile(int it, double delta, char * fileName) {
    FILE * f = fopen(fileName, "at");
    if (f == NULL)
        throw FileNotExists(fileName);

    fprintf(f, "Iteration %d:\n", it);
    for (int i = 0; i < numScenarios; i++) {
        fprintf(f, " Scenario %d\n", i + 1);
        fprintf(f, " : 1st stage decision:\n");
        for (int j = 0; j < numVariables1; j++) {
            fprintf(f, "  > %0.10f\n", scenarios[i]->solution1->data[j]);
        }
        fprintf(f, " : 2nd stage decision:\n");
        for (int j = 0; j < numVariables2; j++) {
            fprintf(f, "  > %0.10f\n", scenarios[i]->solution2->data[j]);
        }
    }
    fprintf(f, "Average solution:\n");
    for (int j = 0; j < numVariables1; j++) {
            fprintf(f, "  > %0.10f\n", averageSolution1->data[j]);
    }
    fprintf(f, "Dist = %0.10f\n\n", delta);
    fclose(f);
}


void SCENARIOS::changeRho(double A) {
    rho *= A;
}

void PHA::runGAMSinParallel(int s) {
    system("gnome-terminal --execute mpirun -np 3 parallelGAMSrun");
    sleep(s);
}

PHA::PHA(SCENARIOS * _S, char * dataFile, double e, double r) {
    this->S = _S;
    this->templet = dataFile;
    eps = e;
    rho = r;
    S->rho = r;
}

void SCENARIOS::printAllScenarioSolutions() {
    for (int i = 0; i < this->numScenarios; i++) {
        printf("First-stage solution for scenario %d:\n", i + 1);
        this->scenarios[i]->solution1->printVector();
        printf("Second-stage solution for scenario %d:\n", i + 1);
        this->scenarios[i]->solution2->printVector();
    }
}

void SCENARIOS::updateAllWeights(double r) {
    for (int i = 0; i < this->numScenarios; i++) {
        for (int j = 0; j < this->numVariables1; j++) {
            scenarios[i]->weights1->data[j] += r * (scenarios[i]->solution1->data[j] - averageSolution1->data[j]);
        }
        for (int j = 0; j < this->numVariables2; j++) {
            scenarios[i]->weights2->data[j] += r * (scenarios[i]->solution2->data[j] - averageSolution2[i]->data[j]);
        }
    }
}

SCENARIOS::~SCENARIOS() {
    for (int i = 0; i < numScenarios; i++) {
        delete scenarios[i];
        delete averageSolution2[i];
    }
    free(scenarios);
    free(averageSolution2);
    scenarios = NULL;
    averageSolution2 = NULL;
}

SCENARIO::~SCENARIO() {
    free(data);
    data = NULL;
    delete weights1;
    delete weights2;
    weights1 = NULL;
    weights2 = NULL;
    delete solution1;
    delete solution2;
    solution1 = NULL;
    solution2 = NULL;
}

SCENARIO::SCENARIO(SCENARIO & s) {
    numParameters = s.numParameters;
    probability = s.probability;
    if (numParameters > 0) {
        for (int i = 0; i < numParameters; i++) {
            data[i] = s.data[i];
        }
    } else {
        data = NULL;
    }
    weights1 = new VECTOR(s.weights1->size);
    for (int i = 0; i < s.weights1->size; i++) {
        this->weights1[i] = s.weights1[i];
    }
    weights2 = new VECTOR(s.weights2->size);
    for (int i = 0; i < s.weights2->size; i++) {
        this->weights2[i] = s.weights2[i];
    }
    solution1 = new VECTOR(s.solution1->size);
    for (int i = 0; i < s.solution1->size; i++) {
        solution1->data[i] = s.solution1->data[i];
    }
    solution2 = new VECTOR(s.solution2->size);
    for (int i = 0; i < s.solution2->size; i++) {
        solution2->data[i] = s.solution2->data[i];
    }
}

SCENARIO::SCENARIO(int n1, int n2) {
    numParameters = 0;
    probability = 0;
    data = NULL;
    weights1 = new VECTOR(n1);
    weights2 = new VECTOR(n2);
    solution1 = new VECTOR(n1);
    solution2 = new VECTOR(n2);
}

void SCENARIOS::loadScenariosSettingFromFile(char * fileName) {
    FILE * f = fopen(fileName, "rt");
    if (f == NULL)
        throw FileNotExists(fileName);
    
    scenarios = NULL;
    numParameters = 0;
    numScenarios = 0;

    char line[100];

    while (fgets(line, 99, f) != NULL) {
        if (line[0] != '%') {
            scenarios = (SCENARIO**)realloc(scenarios, (numScenarios + 1) * sizeof(SCENARIO*));
            scenarios[numScenarios] = new SCENARIO(numVariables1, numVariables2);
            readScenarioFromLine(scenarios[numScenarios++], line);
        }
    }
    numParameters = scenarios[numScenarios - 1]->numParameters;
}

void SCENARIOS::calcAverageSolution(void) {
    for (int i = 0; i < numVariables1; i++) {
        averageSolution1->data[i] = 0;
        for (int j = 0; j < numScenarios; j++) {
            averageSolution1->data[i] += scenarios[j]->probability * scenarios[j]->solution1->data[i];
        }
    }
    for (int i = 0; i < numVariables2; i++) {
        for (int j = 0; j < numScenarios; j++) {
            averageSolution2[j]->data[i] = scenarios[j]->solution2->data[i];
        }
    }
}

double SCENARIOS::calcTerminationCondition(void) {
    double d = 0;
    for (int i = 0; i < this->numScenarios; i++) {
        //d += this->scenarios[i]->probability * calcSquareOfVectors(this->scenarios[i]->solution, this->averageSolution);
        //d += calcSquareOfVectors(this->scenarios[i]->solution1, this->averageSolution1) + calcSquareOfVectors(this->scenarios[i]->solution2, this->averageSolution2[i]);
        d += calcSquareOfVectors(this->scenarios[i]->solution1, this->averageSolution1);
    }
    return d;
}

double calcSquareOfVectors(VECTOR * u, VECTOR * v) {
    double r = 0;
    for (int i = 0; i < u->size; i++) {
        r += pow(u->data[i] - v->data[i], 2);
    }
    return r;
}

void SCENARIOS::loadAllScenarioSolutionsFromLSTFiles(void) {
    char file[20];
    for (int i = 0; i < this->numScenarios; i++) {
        sprintf(file, "scenario%d.lst", i + 1);
        readVectorDataFromLSTFile(file , "x", *(this->scenarios[i]->solution1));
        readVectorDataFromLSTFile(file , "y", *(this->scenarios[i]->solution2));
        checkInfeasibility(file, i + 1);
    }
}

void SCENARIOS::checkInfeasibility(char * nameFile, int s) {
    FILE * f = fopen(nameFile, "rt");
    char line[200], check[25];
    if (f == NULL)
        throw FileNotExists(nameFile);
    while(fgets(line, 199, f) != NULL) {
        strncpy(check, line, 24);
        check[24] = '\0';
        if (strcmp(check, " ** Infeasible solution.") == 0) {
            printf(" !!! Infeasible solution in scenario %d.\n", s);
            exit(1);
        }
    }
}

void SCENARIOS::readScenarioFromLine(SCENARIO * s, char * line) {
    int i = 0, n = 0;
    int length = 0;
    bool isItProbability = true;
    s->data = NULL;
    s->numParameters = 0;
    char * item = NULL;

    while (line[i] != NULL) {
        if (line[i] != ' ') {
            item = (char*)realloc(item, (length + 1) * sizeof(char));
            item[length++] = line[i];
        }
        if (line[i] == ' ' && length != 0 && isItProbability) {
            s->probability = atof(item);
            item = NULL;
            length = 0;
            isItProbability = false;
        }
        if ((line[i] == ' ' && length != 0) || line[i+1] == NULL) {
            s->data = (double*)realloc(s->data, (n + 1) * sizeof(double));
            s->data[n++] = atof(item);
            s->numParameters++;
            item = NULL;
            length = 0;
        }
        i++;
    }
}

SCENARIOS::SCENARIOS(char * fileName, int nV1, int nV2) {
    numVariables1 = nV1;
    numVariables2 = nV2;
    averageSolution1 = new VECTOR(nV1);
    loadScenariosSettingFromFile(fileName);
    averageSolution2 = (VECTOR**)calloc(numScenarios, sizeof(VECTOR*));
    for (int i = 0; i < numScenarios; i++) {
        averageSolution2[i] = new VECTOR(nV2);
    }
}

void SCENARIOS::printAllScenarios(void) {
    for (int i = 0; i < numScenarios; i++) {
        printf(">> SCENARIO %d: probability = %1.2f, number of parameters = %d\n   ", i + 1, scenarios[i]->probability, scenarios[i]->numParameters);
        for (int j = 0; j < numParameters; j++) {
            printf("%2.2f  ", scenarios[i]->data[j]);
        }
        printf("\n\n");
    }
}

void SCENARIOS::checkIntegrity(double epsilon) {
    double sum = 0;
    bool numParamConsistency = true;
    int numParam = scenarios[numScenarios - 1]->numParameters;
    for (int i = 0; i < numScenarios; i++) {
        sum += scenarios[i]->probability;
        if (numParam != scenarios[i]->numParameters) {
            numParamConsistency = false;
        }
    }
    if (fabs(sum - 1) < epsilon) {
        printf("Test of the inner probability scenarios consistency >> OK\n");
    } else {
        throw SumProbabilitiesException(sum);
    }
    if (numParamConsistency) {
        printf("Test of the inner parameters scenarios consistency >> OK\n\n");
    } else {
        throw NumParametersException();
    }
}

void SCENARIOS::makeGAMSSourceFiles(char * templetName) {
    FILE * templet = fopen(templetName, "rt");
    if (templet == NULL)
        throw FileNotExists(templetName);

    char input[200], output[200], buffer[20], fileOutName[20];
    char * num;
    int i, j, k, parameter, l;
    FILE * fileOut;

    for (int s = 0; s < numScenarios; s++) {
        sprintf(fileOutName, "scenario%d.gms", s + 1);
        fileOut = fopen(fileOutName, "wt");
        if (fileOut == NULL)
            throw FileNotExists(fileOutName);
        for (int i = 0; i < 200; i++) {
            input[i] = '\0';
            output[i] = '\0';
        }


        while(fgets(input, 199, templet) != NULL) {
            i = j = 0;
            while (input[i + 1] != NULL) {
                if (input[i] != '@') {
                    output[j++] = input[i];
                } else {
                    k = 0;
                    num = NULL;
                    while (input[++i] != ' ') {
                        num = (char*)realloc(num, (k + 1) * sizeof(char));
                        num[k++] = input[i];
                    }
                    parameter = atoi(num);
                    for (int i = 0; i < 20; i++) {
                        buffer[i] = '\0';
                    }
                    if (parameter < 100) {
                        l = sprintf(buffer, "%0.3f", scenarios[s]->data[parameter]);
                    } 
                    else if (parameter < 200) {
                        if ((parameter - 100) < numVariables1) {
                            l = sprintf(buffer, "%0.3f", scenarios[s]->weights1->data[parameter - 100]);
                        } else {
                            l = sprintf(buffer, "%0.3f", scenarios[s]->weights2->data[parameter - 100 - numVariables1]);
                        }
                    } else if (parameter < 300) {
                        if ((parameter - 200) < numVariables1) {
                            l = sprintf(buffer, "%0.3f", this->averageSolution1->data[parameter - 200]);
                        } else {
                            l = sprintf(buffer, "%0.3f", this->averageSolution2[s]->data[parameter - 200 - numVariables1]);
                        }
                    } else if (parameter == 300) {
                        l = sprintf(buffer, "%0.3f", this->rho);
                    }
                    for (int q = 0; q < l; q++) {
                        output[j++] = buffer[q];
                    }
                }
                i++;
            }
            output[j] = '\0';
            fputs(output, fileOut);
            fputs("\n", fileOut);

            for (int i = 0; i < 200; i++) {
                input[i] = '\0';
                output[i] = '\0';
            }
        }
        fseek(templet, 0, SEEK_SET);
        fclose(fileOut);
        printf("> GAMS source file '%s' with scenario %d has been created.\n", fileOutName, s + 1);
    }
    printf("\n");
    fclose(templet);
    //system("gnome-terminal --command=./skript.sh");
}

void SCENARIOS::readMatrixDataFromLSTFile(char * sourceFile, char * nameOfVariable, MATRIX & m) {
    FILE * file = fopen(sourceFile, "rt");
    if (file == NULL)
        throw FileNotExists(sourceFile);

    char line[100];
    char mark[9], n[10];
    while (fgets(line, 99, file)) {
        strncpy(mark, line, 8);
        mark[8] = '\0';
        if (strcmp(mark, "---- VAR") == 0) {
            int i = 9, j = 0;
            while(line[i] != ' ') {
                n[j++] = line[i++];
            }
            n[j] = '\0';
            if (strcmp(n, nameOfVariable) == 0) {
                m.isEmpty = false;
                for (int i = 0; i < 4; i++)
                    fgets(line, 99, file);
                while (line[0] != '\n') {
                    int k = 0, l = 0;
                    char buffer[20];
                    while (line[k] != '.') {
                        buffer[l++] = line[k++];
                    }
                    buffer[l] = '\0';
                    int row = atoi(buffer);
                    l = 0;
                    k++;
                    while (line[k] != ' ') {
                        buffer[l++] = line[k++];
                    }
                    buffer[l] = '\0';
                    int col = atoi(buffer);
                    l = 0;
                    while (line[k] == ' ')
                        k++;
                    while (line[k] != ' ')
                        k++;
                    while (line[k] == ' ')
                        k++;
                    while (line[k] != ' ') {
                        buffer[l++] = line[k++];
                    }
                    buffer[l] = '\0';

                    m.data[row - 1][col - 1] = atof(buffer);

                    fgets(line, 99, file);
                }
            }
        }
    }
    if (m.isEmpty == true)
        throw NoOccurenceOfVariableInLSTFile(nameOfVariable, sourceFile);
    fclose(file);
}

void SCENARIOS::readVectorDataFromLSTFile(char * sourceFile, char * nameOfVariable, VECTOR & v) {
    FILE * file = fopen(sourceFile, "rt");
    if (file == NULL)
        throw FileNotExists(sourceFile);

    char line[100];
    char mark[9], n[10];
    while (fgets(line, 99, file)) {
        strncpy(mark, line, 8);
        mark[8] = '\0';
        if (strcmp(mark, "---- VAR") == 0) {
            int i = 9, j = 0;
            while(line[i] != ' ') {
                n[j++] = line[i++];
            }
            n[j] = '\0';
            if (strcmp(n, nameOfVariable) == 0) {
                v.isEmpty = false;
                for (int i = 0; i < 4; i++)
                    fgets(line, 99, file);
                while (line[0] != '\n') {
                    int k = 0, l = 0;
                    char buffer[20];
                    while (line[k] != ' ') {
                        buffer[l++] = line[k++];
                    }
                    buffer[l] = '\0';
                    int position = atoi(buffer);
                    l = 0;
                    while (line[k] == ' ')
                        k++;
                    while (line[k] != ' ')
                        k++;
                    while (line[k] == ' ')
                        k++;
                    while (line[k] != ' ') {
                        buffer[l++] = line[k++];
                    }
                    buffer[l] = '\0';

                    v.data[position - 1] = atof(buffer);

                    fgets(line, 99, file);
                }
            }
        }
    }
    if (v.isEmpty == true)
        throw NoOccurenceOfVariableInLSTFile(nameOfVariable, sourceFile);
    fclose(file);
}

void SCENARIOS::readObjValueFromLSTFile(char * sourceFile, char * nameOfVariable, SCALAR & s) {
    FILE * file = fopen(sourceFile, "rt");
    if (file == NULL)
        throw FileNotExists(sourceFile);
    
    char line[100];
    char mark[9], n[10];
    while (fgets(line, 99, file)) {
        strncpy(mark, line, 8);
        mark[8] = '\0';
        if (strcmp(mark, "---- VAR") == 0) {
            int i = 9, j = 0;
            while(line[i] != ' ') {
                n[j++] = line[i++];
            }
            n[j] = '\0';
            if (strcmp(n, nameOfVariable) == 0) {
                s.isEmpty = false;
                int l = 0;
                char buffer[20];
                while (line[i] == ' ') {
                    i++;
                }
                while (line[i] != ' ') {
                    i++;
                }
                while (line[i] == ' ') {
                    i++;
                }
                while (line[i] != ' ') {
                    buffer[l++] = line[i++];
                }
                buffer[l] = '\0';
                s.data = atof(buffer);
            }
        }
    }
    if (s.isEmpty == true)
        throw NoOccurenceOfVariableInLSTFile(nameOfVariable, sourceFile);
    fclose(file);
}
