#include <windows.h>
#include <stdio.h>
#include <wchar.h>
#include <conio.h>
#include <tchar.h>
#include <time.h>
#include <vector>
#include <string>
#include <fstream>
#include <iterator>
#include <iostream>
#include <chrono>
#include <algorithm>
#include <atomic>
#include <cassert>
#include <deque>
#include <mutex>
#include <thread>
#define NOMINMAX
#include <fstream>
#include <string>
#include <iomanip>
#include <cstdlib>
#include <stdlib.h>
#include <atlstr.h>
#include <stdio.h>
#include <tuple>

#pragma comment(lib, "user32.lib")

using namespace std;

#pragma pack(1)
struct Data {
    double bid;
    double ask;
    double qtyBid;
    double qtyAsk;
    long long digits;
    unsigned long long unixTimeStamp;
    unsigned char checksum;
};

union UData
{
    Data  d;
    unsigned char c[sizeof(Data)];
};

// log file for logging each ticket
FILE* ticket_log_file;
FILE* gap_log_file;

inline string convert_epoch_time(unsigned long long epoch_time)
{
    auto millis = epoch_time % 1000;
    time_t t = epoch_time / 1000;
    tm tm;
    localtime_s(&tm, &t);
    char buffer[24];
    sprintf_s(buffer, "%04d-%02d-%02d %02d:%02d:%02d.%03lld",
        tm.tm_year + 1900,
        tm.tm_mon + 1,
        tm.tm_mday,
        tm.tm_hour,
        tm.tm_min,
        tm.tm_sec,
        millis);
    string tmp_timestamp(buffer);
    return tmp_timestamp;
}

inline string getTime()
{
    auto now = chrono::system_clock::now();
    auto milis = chrono::duration_cast<chrono::milliseconds>(now.time_since_epoch()).count() % 1000;
    time_t t = chrono::system_clock::to_time_t(now);
    tm tm;
    localtime_s(&tm, &t);
    char buffer[sizeof "9999-12-31 29:59:59.9999"];
    sprintf_s(buffer, "%04d-%02d-%02d %02d:%02d:%02d.%lld",
        tm.tm_year + 1900,
        tm.tm_mon + 1,
        tm.tm_mday,
        tm.tm_hour,
        tm.tm_min,
        tm.tm_sec,
        milis);
    string tmp_timestamp(buffer);
    return tmp_timestamp;
}

void get_settings(double& gap, string& bridge_map, int& window, string& symbol)
{
    ifstream file("Config.txt");
    if (!file) {
        cout << "no config file found!\n";
        exit(-1);
    }
    string line;
    int c = 0;
    while (getline(file, line))
    {
        string delimiter = " = ";
        string value = line.substr(line.find(delimiter) + 3, line.length());
        if (c == 0) { symbol = value; bridge_map = bridge_map + value; }
        else if (c == 1) { gap = atof(value.c_str()); }
        else if (c == 2) { window = atoi(value.c_str()); }
        c++;
    }
}

BOOL WINAPI consoleHandler(DWORD signal) {

    if (signal == CTRL_C_EVENT)
    {
        fflush(ticket_log_file);
        fflush(gap_log_file);
        printf("\nExit Code via Ctrl-C\n");
        exit(0);
    }
    return TRUE;
}

inline pair<deque<double>::reverse_iterator, deque<double>::reverse_iterator> custom_minmax_element(deque<double>& LmaxL)
{
    deque<double>::reverse_iterator min_ptr = LmaxL.rbegin();
    deque<double>::reverse_iterator max_ptr = LmaxL.rbegin();

    for (auto it = LmaxL.rbegin(); it != LmaxL.rend(); ++it)
    {
        if (*it < *min_ptr)
            min_ptr = it;
        else if (*it > *max_ptr)
            max_ptr = it;
    }

    return { min_ptr, max_ptr };
}

int main()
{
    if (!SetConsoleCtrlHandler(consoleHandler, TRUE)) {
        printf("ERROR: Could not set control handler\n");
        return 1;
    }
    ios_base::sync_with_stdio(false);
    double gap;
    string bridge_map = "Global\\Lmax!!";
    string symbol;
    int window;
    UData dest;
    HANDLE memHandler;
    LPVOID view;

    get_settings(gap, bridge_map, window, symbol);
    // each ticket from feeder
    UData current_ticket_union;
    // previous ticket
    Data previous_ticket;
    // average of buy and sell from previous ticket 
    double pre_avg = 0;
    // difference between current ticket average and previous ticket average
    double l_change = 0;
    // previous unixtimestamp
    unsigned long long pre_unixtimestamp = 0;
    // difference between current ticket unixtimstamp and previous ticket unixtimstamp
    int t_change = 0;

    deque<double> lmaxL;
    deque<int> lmaxT;
    deque<Data> tickets_queue;

    // initialising datetime
    time_t t = time(NULL);
    struct tm tm;
    wchar_t server_time[70] = { 0 };
    wchar_t ticket_time[70] = { 0 };

    errno_t err;

    string ticket_log_file_name = "ticket_log_" + symbol + ".txt";
    string gap_log_file_name = "gap_log_" + symbol + ".txt";
    ticket_log_file = _fsopen(ticket_log_file_name.c_str(), "w", _SH_DENYNO);
    gap_log_file = _fsopen(gap_log_file_name.c_str(), "w", _SH_DENYNO);

    // first non_stop loop, waiting for feeder to start.
    while (true)
    {
        memHandler = OpenFileMappingA(FILE_MAP_READ, 0, bridge_map.c_str());
        if (memHandler == 0)
        {
            cout << "memHandler is null!\n";
            continue;
        }
        view = MapViewOfFile(memHandler, FILE_MAP_READ, 0, 0, sizeof(Data));
        if (view == 0)
        {
            cout << "view is null!\n";
            continue;
        }

        memcpy(current_ticket_union.c, view, sizeof(Data));
        Data current_ticket = current_ticket_union.d;
        tickets_queue.push_back(current_ticket);
        // checking checksum
        unsigned char checksum = 0;
        for (int i = 0; i < sizeof(Data) - sizeof(current_ticket.checksum); i++)
        {
            checksum += current_ticket_union.c[i];
        }
        

        if (checksum != current_ticket.checksum)
        {
            continue;
        }

        previous_ticket = current_ticket;
        pre_avg = (previous_ticket.ask + previous_ticket.bid) / 2;
        fprintf(ticket_log_file, "%s\t%llu\t%f\t%f\t%f\t%u\t%u\n", convert_epoch_time(previous_ticket.unixTimeStamp).c_str(), previous_ticket.unixTimeStamp, previous_ticket.bid, previous_ticket.ask, pre_avg, previous_ticket.checksum, checksum);
        break;
    }

    Data current_ticket;

    while (true)
    {
        chrono::steady_clock::time_point time_begin = chrono::steady_clock::now();
        memcpy(current_ticket_union.c, view, sizeof(Data));
        // number of unique tickets
        current_ticket = current_ticket_union.d;
        // checking checksum
        unsigned char checksum = 0;
        for (int i = 0; i < sizeof(Data) - sizeof(current_ticket.checksum); i++)
        {
            checksum += current_ticket_union.c[i];
        }
        if (checksum != current_ticket.checksum)
        {
            continue;
        }

        if (current_ticket.unixTimeStamp != previous_ticket.unixTimeStamp ||
            current_ticket.ask != previous_ticket.ask ||
            current_ticket.bid != previous_ticket.bid)
        {
            tickets_queue.push_back(current_ticket);
            // in the start or after each successful gap detection
            if (lmaxT.empty() && window < current_ticket.unixTimeStamp - previous_ticket.unixTimeStamp)
            {
                tickets_queue.erase(tickets_queue.begin(), tickets_queue.end() - 1);
                previous_ticket = current_ticket;
                pre_avg = (previous_ticket.ask + previous_ticket.bid) / 2;
                fprintf(ticket_log_file, "%s\t%llu\t%f\t%f\t%f\t%u\t%u\n", convert_epoch_time(previous_ticket.unixTimeStamp).c_str(), previous_ticket.unixTimeStamp, previous_ticket.bid, previous_ticket.ask, pre_avg, previous_ticket.checksum, checksum);
                continue;
            }
            t_change = current_ticket.unixTimeStamp - previous_ticket.unixTimeStamp;
            previous_ticket = current_ticket;

            double avg = (previous_ticket.ask + previous_ticket.bid) / 2;
            l_change = avg - pre_avg;
            pre_avg = avg;



            fprintf(ticket_log_file, "%s\t%llu\t%f\t%f\t%f\t%u\t%u\n", convert_epoch_time(previous_ticket.unixTimeStamp).c_str(), previous_ticket.unixTimeStamp, previous_ticket.bid, previous_ticket.ask, pre_avg, previous_ticket.checksum, checksum);

            auto now = chrono::duration_cast<chrono::milliseconds>(chrono::system_clock::now().time_since_epoch()).count();

            if (!lmaxL.empty()) {
                for (auto& l : lmaxL)
                {
                    l += l_change;
                }
            }

            // update entire time deque
            if (!lmaxT.empty()) {
                for (auto& t : lmaxT)
                {
                    t += t_change;
                }
            }

            lmaxT.push_back(t_change);
            lmaxL.push_back(l_change);


            deque<int>::iterator t_it = lmaxT.begin();
            deque<double>::iterator l_it = lmaxL.begin();
            deque<Data>::iterator tq_it = tickets_queue.begin();
            while (t_it != lmaxT.end()) {
                if (*t_it > window) {
                    t_it = lmaxT.erase(t_it);
                    l_it = lmaxL.erase(l_it);
                    tq_it = tickets_queue.erase(tq_it);

                }
                else {
                    ++t_it;
                    ++l_it;
                    ++tq_it;
                }
            }

            // check if list is empty
            if (lmaxL.empty())
            {
                continue;
            }

            // gap detection
            auto [min_it, max_it] = custom_minmax_element(lmaxL);
            if (*max_it >= gap && *max_it > 0)
            {
                int ticket_cnt = distance(lmaxL.rbegin(), max_it) + 2;
                fprintf(gap_log_file, "\n---------------------------------------------\nQueue Size is : %d\nAscending gap +%f (Buy)found in %d tickets\nServer DateTime: %s\n", lmaxL.size(), *max_it, ticket_cnt, getTime().c_str());
                fprintf(gap_log_file, "LmaxL : [");
                for (auto i = lmaxL.begin(); i != lmaxL.end(); i++) {
                    fprintf(gap_log_file, "%f, ", *i);
                }
                fprintf(gap_log_file, "]\n");
                fprintf(gap_log_file, "LmaxT : [");
                for (auto i = lmaxT.begin(); i != lmaxT.end(); i++) {
                    fprintf(gap_log_file, "%llu, ", *i);
                }
                fprintf(gap_log_file, "]\n");
                fprintf(gap_log_file, "tickets history : [\n");
                for (auto i = tickets_queue.begin(); i != tickets_queue.end(); i++) {
                    Data tmp_ticket = *i;
                    fprintf(gap_log_file, "%s\t%llu\t%f\t%f\t%f\n", convert_epoch_time(tmp_ticket.unixTimeStamp).c_str(), tmp_ticket.unixTimeStamp, tmp_ticket.bid, tmp_ticket.ask, (tmp_ticket.bid + tmp_ticket.ask) / 2.0);
                }
                fprintf(gap_log_file, "]\n");
                lmaxT.clear();
                lmaxL.clear();
                tickets_queue.erase(tickets_queue.begin(), tickets_queue.end() - 1);
                chrono::steady_clock::time_point time_end = chrono::steady_clock::now();
                cout << "Time Ellapsed = " << chrono::duration_cast<chrono::microseconds>(time_end - time_begin).count() << "[µs]\n";
                continue;
            }
            else if (*min_it <= -gap && *min_it < 0)
            {
                int ticket_cnt = distance(lmaxL.rbegin(), min_it) + 2;
                fprintf(gap_log_file, "\n---------------------------------------------\nQueue Size is : %d\nAscending gap %f (Sell)found in %d tickets\nServer DateTime: %s\n", lmaxL.size(), *min_it, ticket_cnt, getTime().c_str());
                fprintf(gap_log_file, "LmaxL : [");
                for (auto i = lmaxL.begin(); i != lmaxL.end(); i++) {
                    fprintf(gap_log_file, "%f, ", *i);
                }
                fprintf(gap_log_file, "]\n");
                fprintf(gap_log_file, "LmaxT : [");
                for (auto i = lmaxT.begin(); i != lmaxT.end(); i++) {
                    fprintf(gap_log_file, "%llu, ", *i);
                }
                fprintf(gap_log_file, "]\n");
                fprintf(gap_log_file, "tickets history : [\n");
                for (auto i = tickets_queue.begin(); i != tickets_queue.end(); i++) {
                    Data tmp_ticket = *i;
                    fprintf(gap_log_file, "%s\t%llu\t%f\t%f\t%f\n", convert_epoch_time(tmp_ticket.unixTimeStamp).c_str(), tmp_ticket.unixTimeStamp, tmp_ticket.bid, tmp_ticket.ask, (tmp_ticket.bid + tmp_ticket.ask) / 2.0);
                }
                fprintf(gap_log_file, "\n]\n");
                lmaxT.clear();
                lmaxL.clear();
                tickets_queue.erase(tickets_queue.begin(), tickets_queue.end() - 1);
                chrono::steady_clock::time_point time_end = chrono::steady_clock::now();
                cout << "Time Ellapsed = " << chrono::duration_cast<chrono::microseconds>(time_end - time_begin).count() << "[µs]\n";
                continue;
            }
        }
    }
    return 0;
}