#include "functions.hh"
#include "statistical_class.hh"
#include "movement_statistics.hh"
#include "forecasting_data.hh"
#include "network_trainer.hh"
#include "network_tester.hh"
#include "../../TradeCore/exceptions.hh"
#include "../../TradeCore/chart_candle.hh"
#include "../../3rdParty/opennn/opennn.h"
#include <iostream>
#include <memory>

constexpr char folderPath[] = "C:/Users/petr/Desktop/NeuralNetworks/";
constexpr char dataFile[] = "_2015_01-03_GBPUSD_M5_6d.csv";
constexpr char histogramFile[] = "histogram.csv";
constexpr char boundsFile[] = "bounds.txt";
constexpr char networkFile[] = "network.net";
constexpr char trainingFile[] = "training.csv";
constexpr char testingFile[] = "testing.csv";
constexpr char validationFile[] = "validation.csv";
constexpr char trainingResultFile[] = "training_result.csv";
constexpr char thTrainingResultFile[] = "th_train.csv";
constexpr char thTestingResultFile[] = "th_test.csv";
constexpr char thValidationResultFile[] = "th_validation.csv";

void saveBounds(const std::vector<StatisticalClass>& bounds)
{
    std::ofstream stream;
    stream.open(std::string(folderPath) + boundsFile);

    for (auto& statClass : bounds)
        stream << statClass.toString() << std::endl;

    stream << std::endl;

    stream
        << "$-\\infty$" << " & "
        << bounds[0].max()  << " & "
        << bounds[0].count() << " & "
        << bounds[1].count() << " & "
        << bounds[2].min() << " & "
        << bounds[2].max() << " & "
        << bounds[2].count() << " & "
        << bounds[3].count() << " & "
        << bounds[4].min()  << " & "
        << "$\\infty$" << " & "
        << bounds[4].count() << std::endl;
}

template<typename T>
std::string toString(const T& value)
{
    std::ostringstream stream;
    stream << value;
    return stream.str();
}

std::string networkFolder(int month, int network, const std::string& pair)
{
    std::string monthStr = toString(month);

    if (monthStr.length() == 1)
        monthStr = "0" + monthStr;

    return monthStr + "_" + pair + "_" + toString(network) + "/";
}

void retestFolder(const std::string& folder)
{
    std::cout << "Retesting folder ..." << std::endl;
    std::cout << folder << std::endl;

    std::shared_ptr<OpenNN::DataSet> trainingSet =
        std::make_shared<OpenNN::DataSet>();

    std::shared_ptr<OpenNN::DataSet> testingSet =
        std::make_shared<OpenNN::DataSet>();

    std::shared_ptr<OpenNN::DataSet> validationSet =
        std::make_shared<OpenNN::DataSet>();

    std::shared_ptr<OpenNN::NeuralNetwork> network;

    std::cout << "Loading training data ..." << std::endl;
    trainingSet->set_data_file_name(folder + trainingFile);
    trainingSet->load_data();

    std::cout << "Loading testing data ..." << std::endl;
    testingSet->set_data_file_name(folder + testingFile);
    testingSet->load_data();

    /*std::cout << "Loading validation data ..." << std::endl;
    validationSet->set_data_file_name(folder + validationFile);
    validationSet->load_data();*/

    std::cout << "Loading neural network ..." << std::endl;
    network = std::make_shared<OpenNN::NeuralNetwork>();
    network->load(folder + networkFile);

    std::cout << "Testing with training data ..." << std::endl;

    NetworkTester trainingDataTester;
    trainingDataTester.neuralNetwork(network);
    trainingDataTester.testingDataset(trainingSet);
    trainingDataTester.resultFile(folder + thTrainingResultFile);
    trainingDataTester.testNetwork();

    std::cout << "Testing with testing data ..." << std::endl;

    NetworkTester testingDataTester;
    testingDataTester.neuralNetwork(network);
    testingDataTester.testingDataset(testingSet);
    testingDataTester.resultFile(folder + thTestingResultFile);
    testingDataTester.testNetwork();

    /*std::cout << "Testing with validation data ..." << std::endl;

    NetworkTester validationDataTester;
    validationDataTester.neuralNetwork(network);
    validationDataTester.testingDataset(validationSet);
    validationDataTester.resultFile(folder + thValidationResultFile);
    validationDataTester.testNetwork();*/

    std::cout << std::endl;
}

void retestPair(const std::string& pair)
{
    std::string pairFolder =
        std::string(folderPath) + pair + "/";

    for (int m = 4; m <= 4; m++) {
        int netCount = 5;

        //if (pair == "GBPUSD" && m == 4)
        //    netCount = 10;

        for (int n = 1; n <= netCount; n++)
            retestFolder(pairFolder + networkFolder(m, n, pair));
    }
}

void makeNetwork()
{
    std::string folder(folderPath);

    std::shared_ptr<std::vector<ChartCandle>> data =
        std::make_shared<std::vector<ChartCandle>>();

    if (!load(folder + dataFile, *data))
        throw LogicException("Unable to open input file");

    std::cout << "Loaded " << data->size() << " candles ...";
    std::cout << std::endl;

    auto priceClasses = generateClasses(-0.02, 2000, 0.00002);
    auto histogramClasses = generateClasses(-0.02, 40, 0.001);

    std::cout << "Price classes: " << priceClasses.size() << std::endl;
    std::cout << "Histogram classes: " << histogramClasses.size() << std::endl;
    std::cout << std::endl;

    MovementStatistics histStats;
    histStats.histogramFile(folder + histogramFile);
    histStats.data(data);
    histStats.classes(histogramClasses);
    histStats.calculate(175);

    MovementStatistics statistics;
    statistics.data(data);
    statistics.classes(priceClasses);
    auto bounds = statistics.calculate(175);

    saveBounds(bounds);

    for (auto& statClass : bounds)
        std::cout << statClass.toString() << std::endl;

    std::cout << std::endl;

    std::shared_ptr<OpenNN::DataSet> trainingSet =
        std::make_shared<OpenNN::DataSet>();

    std::shared_ptr<OpenNN::DataSet> testingSet =
        std::make_shared<OpenNN::DataSet>();

    std::shared_ptr<OpenNN::DataSet> validationSet =
        std::make_shared<OpenNN::DataSet>();

    if (!fileExists(folder + trainingFile) || !fileExists(folder + testingFile)) {
        std::cout << "Making training and testing data ..." << std::endl;
        ForecastingData trainingData;
        trainingData.data(data);
        trainingData.trainingFile(folder + trainingFile);
        trainingData.testingFile(folder + testingFile);
        trainingData.priceBounds(bounds);
        trainingData.dateTimeFilter(&trainingTimeFilter);
        *trainingSet = trainingData.makeDataset().first;
        *testingSet = trainingData.makeDataset().second;
    }
    else {
        std::cout << "Loading training data ..." << std::endl;
        trainingSet->set_data_file_name(folder + trainingFile);
        trainingSet->load_data();

        auto instances = trainingSet->get_instances().get_instances_number();

        for (unsigned int i = 0; i < instances; i++)
            trainingSet->get_instances_pointer()->set_training();

        std::cout << "Loading testing data ..." << std::endl;
        testingSet->set_data_file_name(folder + testingFile);
        testingSet->load_data();
    }

    /*if (!fileExists(validationFile)) {
        std::cout << "Making validation data ..." << std::endl;
        ForecastingData validationData;
        validationData.data(data);
        validationData.trainingFile(folder + validationFile);
        validationData.priceBounds(bounds);
        validationData.dateTimeFilter(&validationTimeFilter);
        validationData.validation(true);
        *validationSet = validationData.makeDataset().first;
    }
    else {
        std::cout << "Loading validation data ..." << std::endl;
        validationSet->set_data_file_name(folder + validationFile);
        validationSet->load_data();
    }*/

    std::shared_ptr<OpenNN::NeuralNetwork> network;

    if (!fileExists(networkFile)) {
        std::cout << "Training neural network ..." << std::endl << std::endl;
        NetworkTrainer trainer;
        trainer.networkFile(folder + networkFile);
        trainer.trainingDataset(trainingSet);
        trainer.resultFile(folder + trainingResultFile);
        network = trainer.trainNetwork();
    } else {
        std::cout << "Loading neural network ..." << std::endl;
        network = std::make_shared<OpenNN::NeuralNetwork>();
        network->load(folder + networkFile);
    }

    std::cout << std::endl << "Testing with training data ..." << std::endl;

    NetworkTester trainingDataTester;
    trainingDataTester.neuralNetwork(network);
    trainingDataTester.testingDataset(trainingSet);
    trainingDataTester.resultFile(folder + thTrainingResultFile);
    trainingDataTester.testNetwork();

    std::cout << "Testing with testing data ..." << std::endl;

    NetworkTester testingDataTester;
    testingDataTester.neuralNetwork(network);
    testingDataTester.testingDataset(testingSet);
    testingDataTester.resultFile(folder + thTestingResultFile);
    testingDataTester.testNetwork();

    /*std::cout << "Testing with validation data ..." << std::endl;

    NetworkTester validationDataTester;
    validationDataTester.neuralNetwork(network);
    validationDataTester.testingDataset(validationSet);
    validationDataTester.resultFile(folder + thValidationResultFile);
    validationDataTester.testNetwork();*/
}

int main()
{   
    //retestPair("EURUSD");
    //retestPair("GBPUSD");
    makeNetwork();
    return 0;
}
