#include "NetQuake.h"
#include <iostream> // basic io
#include <vector>  // dynamic arrays
#include <map>    // keyval maps
#include <asio.hpp>
#include <asio/ts/buffer.hpp>
#include <asio/ts/internet.hpp>
#include <print>
#include <string>
#include <fstream>
#include <excpt.h>

NetQuake::SerializedGenericPacket NetQuake::Serialize(NetworkGenericPacket ngp)
{
    SerializedGenericPacket sgp;


    unsigned short typeAsShort;

    typeAsShort = (ngp.type[0] << 8) + ngp.type[1];
    sgp.type = (PacketType)typeAsShort;
    sgp.length = (ngp.length[0] << 8) + ngp.length[1];

    sgp.remainder = ngp.remainder;

    return sgp;
}

NetQuake::GenericGamePacket NetQuake::toGenericGamePacket(SerializedGenericPacket sgp)
{
    GenericGamePacket retval;
    try {
        
        int currentPacketLength = 0;
        int currentPacketAt = 0;
        bool inString = false;
        for (int i = 0; i < sgp.remainder.size(); i++) {
            if (currentPacketLength == 0 && !inString) {
                if (sgp.remainder[i] == CLIENT_MESSAGE_NOOP) {
                    ClientMessage msg;
                    msg.type = CLIENT_MESSAGE_NOOP;
                    retval.messageBlock.messages.push_back(msg);
                }
                if (sgp.remainder[i] == CLIENT_MESSAGE_KEEPALIVE) {
                    ClientMessage msg;
                    msg.type = CLIENT_MESSAGE_NOOP;
                    retval.messageBlock.messages.push_back(msg);
                }
                if (sgp.remainder[i] == CLIENT_MESSAGE_MOVEMENT) {
                    ClientMessage msg;
                    msg.type = CLIENT_MESSAGE_MOVEMENT;
                    retval.messageBlock.messages.push_back(msg);
                    currentPacketLength = 15;
                }
                if (sgp.remainder[i] == CLIENT_MESSAGE_CONSOLE) {
                    ClientMessage msg;
                    msg.type = CLIENT_MESSAGE_CONSOLE;
                    retval.messageBlock.messages.push_back(msg);
                    
                }
            }
            else if (inString) {
                if (sgp.remainder[i] == 0x00) inString = false;
                else {
                    retval.messageBlock.messages[retval.messageBlock.messages.size() - 1].data += sgp.remainder[i];
                }
            }
            else {
                retval.messageBlock.messages[retval.messageBlock.messages.size() - 1].data += sgp.remainder[i];
                currentPacketAt++;
                if (currentPacketAt == currentPacketLength) {
                    currentPacketLength = 0;
                    currentPacketAt = 0;
                }
            }
        }

        retval.valid = true;
    }
    catch (std::exception& e) {
        std::println("Something went wrong in serialized generic packet to generic game packet conversion: {}", e.what());
    }

    return retval;
}

std::string NetQuake::Serialize(ServerInfoReply sirp)
{
    std::string retval = "";

    using namespace std::string_literals;
    retval += CONTROL_SERVER_INFO_REPLY;
    retval += sirp.address.ip + ":";
    retval += std::to_string(sirp.address.port) + "\0"s;
    retval += sirp.hostname + "\0"s;
    retval += sirp.levelname + "\0"s;
    retval += sirp.currentPlayers;
    retval += sirp.maxPlayers;
    retval += sirp.netVersion;

    short retvalsize = retval.size() + 4;
    unsigned char retvalsizechar[2];

    retvalsizechar[0] = retvalsize & 0xff;
    retvalsizechar[1] = (retvalsize >> 8) & 0xff;


    std::string fretval = "\x80";
    fretval += "\0"s;
    fretval += retvalsizechar[1];
    fretval += retvalsizechar[0];
    fretval += retval;
    return fretval;
}

std::string NetQuake::Serialize(ConnectionRequestReply crr)
{
    std::string retval = "";
    using namespace std::string_literals;


    if (crr.reject) {
        retval = "\x82" + crr.reason + "\0"s;
    }
    else {

        unsigned char portchar[2];
        portchar[1] = crr.port & 0xff;
        portchar[0] = (crr.port >> 8) & 0xff;
        retval = "\x81"s;
        retval += portchar[1];
        retval += portchar[0];
        retval += (unsigned char)crr.unknown[0];
        retval += (unsigned char)crr.unknown[1];
    }


    short retvalsize = retval.size() + 4;
    unsigned char retvalsizechar[2];

    retvalsizechar[0] = retvalsize & 0xff;
    retvalsizechar[1] = (retvalsize >> 8) & 0xff;

    std::string fretval = "\x80";
    fretval += "\0"s;
    fretval += retvalsizechar[1];
    fretval += retvalsizechar[0];
    fretval += retval;
    return fretval;
}

std::string NetQuake::AcknowledgePacket(int packet)
{
    std::string retval;
    using namespace std::string_literals;
    retval += reinterpret_cast<char*>(packet);
    short retvalsize = retval.size() + 4;
    unsigned char retvalsizechar[2];

    retvalsizechar[0] = retvalsize & 0xff;
    retvalsizechar[1] = (retvalsize >> 8) & 0xff;

    std::string fretval = "\0"s;
    fretval += "\x10"s;
    fretval += retvalsizechar[1];
    fretval += retvalsizechar[0];
    fretval += retval;
    return fretval;
}

void NetQuake::NetQuake_ControlServer(Internal::InternalRequestConsole* internalRequestConsole, Config::Config* config, bool* serverRunning)
{

    asio::io_context io_context;
    asio::ip::udp::socket socket(io_context, asio::ip::udp::endpoint(asio::ip::udp::v4(), config->udpControl));
    std::print("Started control server on {}\n", config->udpControl);

    NetQuake::ServerInfoReply sir;
    sir.address.ip = config->ip;
    sir.address.port = config->udpControl;
    sir.currentPlayers = 0;
    sir.maxPlayers = config->maxPlayers;

    sir.hostname = config->serverName;
    sir.levelname = "start";
    sir.netVersion = NetQuake::QGOLD;


    while (true) {
        asio::ip::udp::endpoint remote_endpoint;
        std::array<char, 1024> recv_buf;


        socket.receive_from(asio::buffer(recv_buf), remote_endpoint);

        NetQuake::NetworkGenericPacket gpac;

        gpac.type[0] = recv_buf[0];
        gpac.type[1] = recv_buf[1];
        gpac.length[0] = recv_buf[2];
        gpac.length[1] = recv_buf[3];
        for (int i = 4; i < recv_buf.size(); i++) {
            gpac.remainder += recv_buf[i];
        }

        NetQuake::SerializedGenericPacket sgpac = NetQuake::Serialize(gpac);

        std::cout << "new packet\n";
        switch (sgpac.type) {

        case NetQuake::NQ_CONTROL_PACKET:
        {
            NetQuake::GenericControlPacket gcpac(sgpac);

            std::cout << "GenericControlPacket: " << gcpac.opcode << "\n";

            // the 0x01 i had so much trouble getting through
            if (gcpac.opcode == NetQuake::CONTROL_CONNECTION_REQUEST) {
                std::cout << "Connection Request\n";

                int endpointRequestId = internalRequestConsole->newEndpointRequest(remote_endpoint);

                while (!internalRequestConsole->getEndpointRequest(endpointRequestId).ready) {
                    // waiting for endpoint request to go through
                }

                Internal::NewEndpointRequestReply nerr = internalRequestConsole->getEndpointRequest(endpointRequestId);
                
                NetQuake::ConnectionRequestReply crr;

                crr.reject = nerr.error;
                crr.reason = nerr.reason;
                crr.port = nerr.port;

                crr.unknown[0] = config->unknownMagic[0];
                crr.unknown[1] = config->unknownMagic[1];

                asio::error_code e;
                socket.send_to(asio::buffer(NetQuake::Serialize(crr)), remote_endpoint, 0, e);
                /*NetQuake::ConnectionRequestReply crr;

                crr.reject = false;
                crr.port = 26000;
                crr.unknown[0] = config->unknownMagic[0];
                crr.unknown[1] = config->unknownMagic[1];

                asio::error_code e;
                socket.send_to(asio::buffer(NetQuake::Serialize(crr)), remote_endpoint, 0, e);
                */
            }
            if (gcpac.opcode == NetQuake::CONTROL_SERVER_INFO_REQUEST) {
                NetQuake::ServerInfoRequest sirqpac(gcpac);

                std::cout << "ServerInfoRequest: server info request for game \"" << sirqpac.gameName << "\", netversion " << sirqpac.netVersion << "\n";
                
                std::string serializedTemplate = NetQuake::Serialize(sir);

                std::ofstream templatestream("template.txt", std::ios::binary);
                templatestream << serializedTemplate;
                templatestream.close();
                asio::error_code e;
                socket.send_to(asio::buffer(serializedTemplate), remote_endpoint, 0, e);
                std::cout << e.message() << "\n";
            }

        }
        break;

        default:
            std::cout << "[WARNING] Unknown or unsupported packet came through.\n";
            break;
        }
        // std::cout << gcpac.opcode << "\n";

    }

}

void NetQuake::NetQuake_EndpointServer(Config::Config* config, int port, asio::ip::udp::endpoint startRemote)
{
    asio::io_context io_context;
    asio::error_code e;
    

    asio::ip::udp::socket socket(io_context, asio::ip::udp::endpoint(asio::ip::udp::v4(), port));
    
    socket.connect(startRemote);
    
    std::print("New endpoint server started on {}.\nClient Start Info: {} | {}\n", port, startRemote.address().to_string(), startRemote.port());

    { // startup block
        ServerMessageBlock smb;
        smb.type = NQ_MESSAGE_BLOCK_END;
        smb.order = 100;
        ServerMessages::Print serverBanner;

        serverBanner.text = config->serverBanner;
        smb.serverMessages.push_back(&serverBanner);


        ServerMessages::ServerInfo serverInfo;

        serverInfo.mapName = "start";
        serverInfo.maxClients = config->maxPlayers;
        serverInfo.multi = 1;
        serverInfo.numModels = 0;
        serverInfo.numSounds = 0;
        serverInfo.serverVersion = 15;
        
        smb.serverMessages.push_back(&serverInfo);

        ServerMessages::SignOn signOn;
        signOn.signon = 1; // prespawn(2=light,3=render)
        smb.serverMessages.push_back(&signOn);

        std::print("Tried sending server banner to client.\n");

        socket.send_to(asio::buffer(smb.Serialize()), startRemote, 0, e);
        std::ofstream smbattempt("smb.txt");
        smbattempt << smb.Serialize();
        smbattempt.close();


        std::print("Server banner should be sent.\n");
    }

    while (true) {
        asio::ip::udp::endpoint rec;
        std::string recv_buf;
        socket.receive_from(asio::buffer(recv_buf), rec);
        std::cout << "packet on secondary line " << port << "\n";
        std::cout << "received packet size: " << recv_buf.size() << "\n";
        if(recv_buf.size() > 4) {
            try {
                NetQuake::NetworkGenericPacket ngp;
                ngp.type[0] = recv_buf[0];
                ngp.type[1] = recv_buf[1];
                ngp.length[0] = recv_buf[2];
                ngp.length[1] = recv_buf[3];
                for (int i = 4; i < recv_buf.size(); i++) {
                    ngp.remainder += recv_buf[i];
                }
                SerializedGenericPacket sgpac = Serialize(ngp);
                GenericGamePacket ggp = toGenericGamePacket(sgpac);
                
                
                if (ggp.type == NQ_MESSAGE_BLOCK_CHUNK || ggp.type == NQ_MESSAGE_BLOCK_END) {
                    socket.send_to(asio::buffer(AcknowledgePacket(ggp.packetNumber)), rec);
                    std::println("Server acknowledged packet {}", ggp.packetNumber);
                }
            }
            catch (...) {
                std::println("Error on packet on {}", port);
                break;
            }
        }
        else {
            std::println("Weird packet received... :shrug:");

        }
    }
}

NetQuake::GenericControlPacket::GenericControlPacket(SerializedGenericPacket sgp)
{

    if (sgp.type != NQ_CONTROL_PACKET) {
        throw std::runtime_error("Wrong packet type given to generic control packet constructor.");
    }

    opcode = (Control_OpCode)sgp.remainder[0];

    remainder = sgp.remainder;
    remainder.erase(0, 1);


}

NetQuake::ServerInfoRequest::ServerInfoRequest(GenericControlPacket gcp)
{
    if (gcp.opcode != CONTROL_SERVER_INFO_REQUEST) {
        throw std::runtime_error("Wrong packet type given to server info request constructor.");
    }

    std::string toParse = gcp.remainder;

    int i = 0;
    for (; toParse[i] != '\0'; i++) {
        gameName += toParse[i];
    }
    netVersion = (NetProtocolVersion) toParse[i+1];

}

NetQuake::ConnectionRequest::ConnectionRequest(GenericControlPacket gcp)
{
    if (gcp.opcode != CONTROL_SERVER_INFO_REQUEST) {
        throw std::runtime_error("Wrong packet type given to server info request constructor.");
    }

    std::string toParse = gcp.remainder;

    int i = 0;
    for (; toParse[i] != '\0'; i++) {
        gameName += toParse[i];
    }
    netVersion = (NetProtocolVersion)toParse[i + 1];

}

NetQuake::Internal::InternalRequestConsole::InternalRequestConsole()
{
    std::print("New Internal Request Console Created.\n");
}

int NetQuake::Internal::InternalRequestConsole::newEndpointRequest(asio::ip::udp::endpoint ep)
{
    NewEndpointRequestReply nerr;
    nerr.ready = false;
    nerr.startRemote = ep;
    newEndpointRequests.push_back(nerr);
    return newEndpointRequests.size() - 1;
}

NetQuake::Internal::NewEndpointRequestReply NetQuake::Internal::InternalRequestConsole::getEndpointRequest(int id)
{
    return newEndpointRequests[id];
}

void NetQuake::Internal::InternalRequestConsole::Update(Config::Config *config, std::vector<GameEndpoint>* gameEndpointsVector)
{

    for (int i = lastEndpointRequestTreated; i < newEndpointRequests.size(); i++) {
        if (!newEndpointRequests[i].ready) {
            if(gameEndpointsVector->size() < config->maxPlayers){
                GameEndpoint* endpoint = new GameEndpoint(config, 1000+i, newEndpointRequests[i].startRemote);
                newEndpointRequests[i].port = 1000 + i;
            }
            else {
                newEndpointRequests[i].error = true;
                newEndpointRequests[i].reason = "too many players";
            }

            newEndpointRequests[i].ready = true;
        }
    }

}

NetQuake::Internal::InternalRequestConsole::~InternalRequestConsole()
{
    newEndpointRequests.clear();
    std::print("Internal Request Console Destroyed.\n");
}

NetQuake::GameEndpoint::GameEndpoint()
{
    throw; // constructor musn't be empty
}

NetQuake::GameEndpoint::GameEndpoint(Config::Config* config, int port, asio::ip::udp::endpoint startRemote)
{
    std::thread internalInternalThread(NetQuake_EndpointServer, config, port, startRemote);
    internalThread.swap(internalInternalThread);
}

NetQuake::GameEndpoint::~GameEndpoint()
{
    internalThread.~thread();
}

std::string NetQuake::ServerMessages::Print::Serialize() {
    using namespace std::string_literals;
    std::string retval = text;
    retval += "\0"s;
    return retval;
}

std::string NetQuake::ServerMessages::ServerInfo::Serialize() {
    std::string retval = "";
    using namespace std::string_literals;
    retval += serverVersion;
    retval += maxClients;
    retval += mapName + "\0"s;
    for (int i = 0; i < precacheModels.size(); i++) {
        retval += precacheModels[i] + "\0"s;
    }
    retval += numModels;
    for (int i = 0; i < precacheSounds.size(); i++) {
        retval += precacheSounds[i] + "\0"s;
    }

    retval += numSounds;
    return retval;
}


std::string NetQuake::ServerMessages::SignOn::Serialize() {
    std::string retval = "";

    retval += signon;

    return retval;
}

std::string NetQuake::ServerMessageBlock::Serialize()
{
    std::string retval;

    using namespace std::string_literals;

    for (int i = 0; i < serverMessages.size(); i++) {
        retval += serverMessages[i]->type;
        retval += serverMessages[i]->Serialize();
    }



    short retvalsize = retval.size() + 4;
    unsigned char retvalsizechar[2];
    unsigned char typechar[2];

    retvalsizechar[0] = retvalsize & 0xff;
    retvalsizechar[1] = (retvalsize >> 8) & 0xff;
    typechar[1] = this->type & 0xff;
    typechar[0] = (this->type >> 8) & 0xff;

    std::string fretval = std::string({(char)typechar[0]});
    fretval += typechar[1];
    fretval += retvalsizechar[1];
    fretval += (char)retvalsizechar[0];
    fretval += retval;
    return fretval;
}
