#include <string>
#include <locale>
#include <vector>
#include <iostream>
#include <ctime>
#include <chrono>
#include <cstdarg>

#include "SendMessage.h"
#include "utils.h"
#include "get_information.h"


#ifdef _WIN32
#include <Windows.h>
#include <winsock.h>

#define WM_USER        0x0400 //сообщения для обработчика сообщений
#define STATUS_MESSAGE WM_USER + 16

// link with Ws2_32.lib
#pragma comment(lib,"Ws2_32.lib")

#else
#include <sys/types.h>
#include <sys/socket.h>
#include <unistd.h>
#include <netinet/in.h>
#include <arpa/inet.h>

#define SOCKET int
#define INVALID_SOCKET (SOCKET)(~0)
#define SOCKADDR sockaddr 
#define closesocket close

void WSACleanup() { }
int  WSAGetLastError() { return 0; }
void Sleep(int millisec) { usleep(millisec * 1000); }

#endif

using std::wstring;
using std::string;
using std::vector;
using std::chrono::high_resolution_clock;
using std::chrono::duration_cast;
using std::chrono::milliseconds;

static SOCKET clientSocket = INVALID_SOCKET;
static FILE* logFile = NULL;

#if _WIN32
#define FILE_LOG "Components\\Sapfor_log.txt"
#else
#define FILE_LOG "Components/Sapfor_log.txt"
#endif

#define __print(prefix, format, ...) do { } while (0)
#define __print_log(file, format, ...) do { } while (0)

/*#define __print(prefix, format, ...) do {\
   printf((string("%s: ") + format + string("\n")).c_str(), prefix, ##__VA_ARGS__); \
   fflush(NULL); \
} while (0)

#define __print_log(file, format, ...) do {\
   if (file) { \
       time_t now = time(0);\
       auto dt = string(ctime(&now)); \
       dt = dt.erase(dt.size() - 1); \
       fprintf(file, (string("%s: ") + format + string("\n")).c_str(), dt.c_str(), ##__VA_ARGS__); \
       fflush(file); \
   } \
} while (0)
*/
#define CLIENT "[SAPFOR]"

static int doRecv(SOCKET& soc, string& command)
{
    int count = 0; //общее количество прочитаного
    int err = 0; //количество прочитанного на данной итерации.
    const int maxSize = 4096; //максимальный размер прочитываемой за раз порции.
    char* buf = NULL; //буфер
    do
    {
        buf = new char[maxSize + 1];
        err = recv(soc, buf, maxSize, 0);
        if (err > 0)
        {
            count += err;
            buf[err] = '\0';
            command += buf;
        }
        delete[] buf;
        buf = NULL;

    } while (err > 0 && command.back() != '\n');
    __print(CLIENT, "'%s' length='%d'\n", command.c_str(), command.length());
    return err < 0 ? err : count;
}

static int decodeMessage(const string& message, vector<string>& pars, int &winH, int countPars)
{
    int sI = 0;
    for (int z = 0; z < countPars; ++z)
    {
        string d = "";
        for ( ;sI != message.size() && message[sI] != ' '; ++sI)
            d += message[sI];        
        if (sI == message.size())
            return -1;
        ++sI;

        int countElems = -1;
        if (sscanf(d.c_str(), "%d", &countElems) != 1)
            return -2;

        string par = "";

        for (int z = 0; sI != message.size() && z < countElems; ++z, ++sI)
            par += message[sI];
        if (sI == message.size())
            return -3;
        pars.push_back(par);
    }

    string d = "", toPrint = "";
    for (; sI != message.size() && message[sI] != ' '; ++sI)
        d += message[sI];

    if (d[d.size() - 1] == '\n' && d.size() >= 2)
        toPrint = d.substr(0, d.size() - 2);
    else
        toPrint = d;

    __print(CLIENT, "Read port from '%s'", toPrint.c_str());
    if (sscanf(d.c_str(), "%d", &winH) != 1)
    {
        __print(CLIENT, "PORT ERROR");
        return -4;
    }
    else
        __print(CLIENT, "PORT = %d", winH);

    return 0;
}

static void sendMessage_1lvl(const wstring& toSend)     { MessageManager::sendFirstLvl (toSend); }
static void sendMessage_2lvl(const wstring& toSend)     { MessageManager::sendSecondLvl(toSend); }
void sendMessage_progress(const wstring& toSend) { MessageManager::sendProgress (toSend); }

void sendMessage_1lvl(int kind, ...)
{
    va_list list;
    va_start(list, kind);

    if (kind == 0)
    {
        int iters = va_arg(list, int);
#ifdef _WIN32
        sendMessage_1lvl(L"выполняется " + std::to_wstring(iters) + L" итерация синтаксического анализа");
#else
        sendMessage_1lvl(L"running " + std::to_wstring(iters) + L" iteration of syntax analisys");
#endif        
    }
    else if (kind == 1)
    {
        string str = va_arg(list, string);
#ifdef _WIN32
        sendMessage_1lvl(wstring(L"выполняется проход '") + wstring(str.begin(), str.end()) + L"'");
#else
        sendMessage_1lvl(wstring(L"running pass '") + wstring(str.begin(), str.end()) + L"'");
#endif
    }

    va_end(list);
}

void sendMessage_2lvl(int kind, ...)
{
    va_list list;
    va_start(list, kind);

    if (kind == 0)
    {
        bool isFunction = va_arg(list, int);
        string fName = va_arg(list, string);
#if _WIN32
        if (isFunction)
            sendMessage_2lvl(wstring(L"обработка функции '") + to_wstring(fName) + L"'");
        else
            sendMessage_2lvl(wstring(L"обработка модуля '") + to_wstring(fName) + L"'");
#else
        if (isFunction)
            sendMessage_2lvl(wstring(L"processing function '") + wstring(fName.begin(), fName.end()) + L"'");
        else
            sendMessage_2lvl(wstring(L"processing module '") + wstring(fName.begin(), fName.end()) + L"'");
#endif
    }
    else if (kind == 1)
    {
        int idx = va_arg(list, int);
        int all = va_arg(list, int);
#ifdef _WIN32
        sendMessage_2lvl(wstring(L"обработка цикла ") + std::to_wstring(idx) + L"/" + std::to_wstring(all));
#else
        sendMessage_2lvl(wstring(L"processing loop ") + std::to_wstring(idx) + L"/" + std::to_wstring(all));
#endif
    }
    else if (kind == 2)
    {
        sendMessage_2lvl(L"");
    }
    else if (kind == 3)
    {
        string file = va_arg(list, string);
#ifdef _WIN32
        sendMessage_2lvl(L" обработка файла '" + to_wstring(file) + L"'");
#else 
        sendMessage_2lvl(L" processing file '" + to_wstring(file) + L"'");
#endif 
    }
    else if (kind == 4)
    {
        int k = va_arg(list, int);
        int all = va_arg(list, int);
#if _WIN32
        wstring treeM = L"разрешение конфликтов, обработка группы " + std::to_wstring(k + 1) + L"/" + std::to_wstring(all);
#else
        wstring treeM = L"conflict resolution, processing group " + std::to_wstring(k + 1) + L"/" + std::to_wstring(all);
#endif
        sendMessage_2lvl(treeM);
    }
    else if (kind == 5)
    {
        int z = va_arg(list, int);
        int all = va_arg(list, int);
#ifdef _WIN32
        wstring treeM = L"разрешение конфликтов, обработка массива " + std::to_wstring(z + 1) + L"/" + std::to_wstring(all);
#else
        wstring treeM = L"conflict resolution, processing array " + std::to_wstring(z + 1) + L"/" + std::to_wstring(all);
#endif
        sendMessage_2lvl(treeM);
    }
    else if (kind == 6)
    {
        string funcName = va_arg(list, string);
#ifdef _WIN32
        sendMessage_2lvl(wstring(L"подстановка функции '") + to_wstring(funcName) + L"'");
#else 
        sendMessage_2lvl(wstring(L"inlinig of function '") + to_wstring(funcName) + L"'");
#endif
    }

    va_end(list);
}

static string utf8_encode(const wstring& wstr)
{
    if (wstr.empty())
        return string();
#ifdef _WIN32
    int size_needed = WideCharToMultiByte(CP_UTF8, 0, &wstr[0], (int)wstr.size(), NULL, 0, NULL, NULL);
    string strTo(size_needed, 0);
    WideCharToMultiByte(CP_UTF8, 0, &wstr[0], (int)wstr.size(), &strTo[0], size_needed, NULL, NULL);
#else
    /*char* buf = NULL;
    int size_needed = wcstombs(buf, wstr.c_str(), sizeof(char));
	printf("%d for \n", size_needed);
    buf = new char[size_needed + 1];
    wcstombs(buf, wstr.c_str(), sizeof(char));
    string strTo(buf);
    delete[] buf;*/

    string strTo(wstr.size(), 0);
    for (int z = 0; z < wstr.size(); ++z)
        strTo[z] = wstr[z];
#endif
    return strTo;
}

/*static wstring utf8_decode(const string& str)
{
    if (str.empty())
        return wstring();

    int size_needed = MultiByteToWideChar(CP_UTF8, 0, str.c_str(), str.length(), NULL, 0);
    wstring wstr;
    wstr.resize(size_needed, 0);
    MultiByteToWideChar(CP_UTF8, 0, str.c_str(), str.length(), &wstr[0], size_needed);
    return wstr;
}*/

void MessageManager::sendMessage(const wstring& toSend)
{
    if (WinHandler > 0) //to C#
    {
#ifdef _WIN32
        cachedMessages.push_back(toSend);
        PostMessage((HWND)WinHandler, STATUS_MESSAGE, (WPARAM)(cachedMessages.back().c_str()), (LPARAM)cachedMessages.back().size());
#endif
    }
    else if (WinHandler < -1) // to JAVA
    {
        vector<wstring> splited;
        splitString(toSend, '\n', splited);

        int i = 1;
        for (auto& elem : splited)
        {
            string result = utf8_encode(L"message_" + std::to_wstring(i++) + L":" + elem + L"\n");
            const char* ret = result.c_str();

            int size = result.size();
#ifdef _WIN32
            int iResult = send(clientSocket, ret, size, 0);
#else
            int iResult = write(clientSocket, ret, size);
#endif
            if (iResult < 0)
                printf("Wrong send is %d\n", iResult);
        }
    }
}

static int connectAndCreate(SOCKET& socket_t, string address, int port)
{
    socket_t = socket(AF_INET, SOCK_STREAM, 0);
    if (socket_t == INVALID_SOCKET)
    {
        printf("Wrong  socket\n");
        return -1;
    }

    sockaddr_in clientService;
    clientService.sin_family = AF_INET;
    clientService.sin_addr.s_addr = inet_addr(address.c_str());
    clientService.sin_port = htons(port);
    int result = connect(socket_t, (SOCKADDR*)(&clientService), sizeof(clientService));

    if (result != 0)
    {
        __print(CLIENT, "Wrong connect to socket %d\n", WSAGetLastError());
        return -1;
    }
    return 0;
}

void MessageManager::setWinHandler(const int winH)
{
    if (winH < -1) // to JAVA
    {
        if (MessageManager::init() != 0)
        {
            WinHandler = -1;
            return;
        }

        __print(CLIENT, "set handler with PORT = %d", winH);
        if (connectAndCreate(clientSocket, "127.0.0.1", winH == -2 ? 8888 : -winH) != 0)
        {
            WinHandler = -1;
            return;
        }        

    }
    else if (winH == -1 && WinHandler < -1) // close JAVA
    {
        if (Started == 0)
        {
            Sleep(100);
            closesocket(clientSocket);
            clientSocket == INVALID_SOCKET;
        }
    }
    WinHandler = winH;
}

//from get_information.cpp
extern bool showDebug;

static int send(SOCKET& client, const wstring& messageIn)
{
    wstring message = messageIn;
    for (int z = 0; z < message.size(); ++z)
        if (message[z] == L'\n')
            message[z] = 1;
    message += L"\n";

    string result = utf8_encode(message);
    string size = std::to_string(result.size());

    if (showDebug)
    {
        wprintf(L"%s: Original message '%s'", CLIENT, message.c_str());
        printf("%s: Converted message '%s'", CLIENT, result.c_str());
    }

#ifdef _WIN32
    int err = send(client, size.c_str(), size.size(), 0);
#else
    size += "\n";
    int err = write(client, size.c_str(), size.size());
#endif
    __print(CLIENT, "Send message size: %d", (int)result.size());
    if (err != size.size())
    {
        __print(CLIENT, "Error of send(): %d", err); // exit
        return -1;
    }

    char buf;
    recv(client, &buf, 1, 0);

    __print(CLIENT, "send start\n", CLIENT);
    auto timeForPass = high_resolution_clock::now();
#ifdef _WIN32
    err = send(client, result.c_str(), result.size(), 0);
#else
    err = write(client, result.c_str(), result.size());
#endif

    const float elapsed = duration_cast<milliseconds>(high_resolution_clock::now() - timeForPass).count() / 1000.;
    __print(CLIENT, "send end with time %f sec\n", elapsed);
    __print(CLIENT, "Send message with size %d", (int)result.size());

    if (err != result.size())
    {
        __print(CLIENT, "Error of send(): %d", err); // exit
        return -1;
    }
    else
    {
        err = recv(client, &buf, 1, 0);
        if (err != 1)
        {
            __print(CLIENT, "Error of recv(): %d", err); // exit
            return -1;
        }
        else
            return 0;
    }
}

void MessageManager::sendProgress(const std::wstring& str)
{
    if (WinHandler < -1) // to JAVA
    {
        string result = utf8_encode(L"progress:" + str + L"\n");
        const char* ret = result.c_str();

        int size = result.size();
#ifdef _WIN32
        int iResult = send(clientSocket, ret, size, 0);
#else
        int iResult = write(clientSocket, ret, size);
#endif
        if (iResult == -1)
            __print(CLIENT, "Error of send(): %d", iResult);
    }
}

int MessageManager::init()
{
#if _WIN32	
    if (Started == -1)
    {
        WSADATA wsaData = { 0 };
        int result = WSAStartup(MAKEWORD(2, 0), &wsaData);
        if (result != 0)
        {
            __print(CLIENT, "Wrong WSAStartup(): %d", result);
            Started = -2;
        }
        else
            Started = 0;
    }    
#else
    Started = 0;
#endif
    return Started;
}

SOCKET client = INVALID_SOCKET;

void sendErrorCode(const wstring& message)
{
    send(client, message);
    closesocket(client);
    client = INVALID_SOCKET;
}

void RunSapforAsClient(int serverPort)
{
    if (MessageManager::init() != 0)
        return;

    logFile = fopen(FILE_LOG, "w");
    if (logFile == NULL)
        __print(CLIENT, "Can not open log file '%s'", FILE_LOG);

    client = INVALID_SOCKET;
    __print_log(logFile, "create and connect to server socket with port %d", serverPort > 0 ? serverPort : 8889);
    if (connectAndCreate(client, "127.0.0.1", serverPort > 0 ? serverPort : 8889) != 0)
        return;
    __print_log(logFile, "done");

    __print_log(logFile, "start main communications");
    while (true)
    {
        wstring result = L"";
        string message = "";

        __print(CLIENT, "start recv message");
        __print_log(logFile, "wait for command from server");

        const int count = doRecv(client, message);        

        __print(CLIENT, "end recv message with size %d", count);
        __print_log(logFile, "done with message size %d", count);

        if (count > 0)
        {
            string code;
            /*string copy = message;
            if (copy.back() == '\n')
                copy = copy.erase(copy.size() - 1);
            __print(CLIENT, "Recv message: '%s'", copy.c_str());*/

            int z = 0;
            for (; z < message.size(); ++z)
            {
                if (message[z] == ':')
                {
                    z++;
                    break;
                }
                else
                    code += message[z];
            }

            if (z == message.size()) // wrong message
            {
                __print(CLIENT, "Wrong message");

                __print_log(logFile, "send WRONG message to server ");
                int err = send(client, L"WRONG");
                __print_log(logFile, "done with code %d", err);
                if (err != 0)
                    break;
                continue;
            }

            message = message.substr(z);

            vector<string> pars;
            int winHandler = -1;

            if (code == "analysis")
            {
                __print_log(logFile, "decode message as analysis");
                int err = decodeMessage(message, pars, winHandler, 3);

                if (err == 0)
                    result = Sapfor_RunAnalysis(pars[0].c_str(), pars[1].c_str(), pars[2].c_str(), winHandler);
                else
                    result = Sapfor_RunAnalysis(NULL, NULL, NULL, -1);
            }
            else if (code == "transformation")
            {
                __print_log(logFile, "decode message as transformation");
                int err = decodeMessage(message, pars, winHandler, 5);
                if (err == 0)
                    result = Sapfor_RunTransformation(pars[0].c_str(), pars[1].c_str(), pars[2].c_str(), pars[3].c_str(), pars[4].c_str(), winHandler);
                else
                    result = Sapfor_RunTransformation(NULL, NULL, NULL, NULL, NULL, -1);
            }
            else if (code == "modification")
            {
                __print_log(logFile, "decode message as modification");
                int err = decodeMessage(message, pars, winHandler, 6);
                if (err == 0)
                    result = Sapfor_RunModification(pars[0].c_str(), pars[1].c_str(), pars[2].c_str(), pars[3].c_str(), pars[4].c_str(), pars[5].c_str(), winHandler);
                else
                    result = Sapfor_RunModification(NULL, NULL, NULL, NULL, NULL, NULL, -1);
            }
            else
            {
                __print(CLIENT, "Wrong code of message: %s", code.c_str()); // exit
                __print_log(logFile, "wrong code of message, send WRONG");
                int err = send(client, L"WRONG");
                __print_log(logFile, "done with code %d", err);

                if (err != 0)
                    break;
                continue;
            }

            __print_log(logFile, "send results to server");
            int err = send(client, result.c_str());
            __print_log(logFile, "done with code %d", err);

            if (err != 0)
                break;
        }
        else if (count == 0) // close and exit
        {
            closesocket(client);
            __print(CLIENT, "Socket was closed");
            __print_log(logFile, "done with normal exit");
            break;
        }
        else // error
        {
            closesocket(client);
            __print(CLIENT, "Recv return error code %d", count);
            __print_log(logFile, "done with error exit");
            break;
        }
    }
}

unsigned int GetPid()
{
    unsigned int pid = 0;
#ifdef _WIN32
    pid = GetCurrentProcessId();
#else
    pid = getpid();
#endif
    return pid;
}

vector<wstring> MessageManager::cachedMessages = vector<wstring>();
int MessageManager::WinHandler = -1;
wstring MessageManager::firstLvlMessage = L"";
wstring MessageManager::secondLvlMessage = L"";
int MessageManager::Started = -1;
