#include <iostream>
#include <string>
#include <stdio.h>

#define WIN32_MEAN_AND_LEAN
#include <winsock2.h>
#include <ws2tcpip.h>
#include <windows.h>

#include "soldatinfo.h"

void SendLine(SOCKET sock, const char *line);
std::string GetLine(SOCKET sock);
void DumpInfo(SoldatInfo& info);

int main(int argc, char *argv[])
{
    if (argc != 4)
    {
        std::cout << "Usage:\n\tsoldatinfo host port pass" << std::endl;
        return 0;
    }

    const char *host = argv[1], *port = argv[2], *pass = argv[3];

    int winsock_err;
    SOCKET soldat_socket = INVALID_SOCKET;

    try
    {
        WSADATA winsock_data;
        winsock_err = WSAStartup(MAKEWORD(2, 0), &winsock_data);
        if (winsock_err != 0)
        {
            throw std::string("Failed to initialize Winsock");
        }

        if (LOBYTE(winsock_data.wVersion) != 2)
        {
            throw std::string("Required version of Winsock not available");
        }

        soldat_socket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
        if (soldat_socket == INVALID_SOCKET)
        {
            throw std::string("Socket creation failed");
        }

        addrinfo hints, *result;
        memset(&hints, 0, sizeof(hints));
        hints.ai_family = AF_INET;
        hints.ai_socktype = SOCK_STREAM;
        hints.ai_protocol = IPPROTO_TCP;

        if (getaddrinfo(host, port, &hints, &result) != 0)
        {
            throw std::string("Failed to create address info");
        }

        sockaddr soldat_addr = *result[0].ai_addr;
        freeaddrinfo(result);

        if (connect(soldat_socket, &soldat_addr, sizeof(soldat_addr)) != 0)
        {
            throw std::string("Unable to connect to server");
        }

        SendLine(soldat_socket, pass);
        SendLine(soldat_socket, "REFRESH");

        for (;;)
        {
            std::string line = GetLine(soldat_socket);
            if (line.empty())
                break;
            if (line == "REFRESH")
            {
                unsigned char buf[1188];
                if (recv(soldat_socket, reinterpret_cast<char*>(buf), sizeof(buf), 0) != 1188)
                {
                    throw std::string("Failed to fetch REFRESH packet");
                }
                // We got the packet! Let's parse it into a SoldatInfo object
                SoldatInfo info(buf);
                // Let's dump the info to standard output
                DumpInfo(info);
                // Then we'll close our admin connection by breaking from our network loop
                break;
            }
        }
    }
    catch (std::string exception)
    {
        std::cout << exception << std::endl;
    }

    if (winsock_err == 0)
    {
        if (soldat_socket != INVALID_SOCKET)
        {
            closesocket(soldat_socket);
        }

        WSACleanup();
    }

    return 0;
}

void SendLine(SOCKET sock, const char *line)
{
    std::string buf(line);
    buf.append("\r\n");
    send(sock, buf.c_str(), (int)buf.size(), 0);
}

std::string GetLine(SOCKET sock)
{
    std::string out;
    for (char c; recv(sock, &c, 1, 0) != 0;)
    {
        if (c == 10)
            break;
        if (c == 13)
            continue;
        out.append(1, c);
    }
    return out;
}

void DumpInfo(SoldatInfo& info)
{
    printf("----------------------------\n"
           "Soldat Refresh Packet Dumper\n"
           "----------------------------\n");

    std::string gamemode = SoldatInfo::GamemodeToStr(info.Gamemode());
    std::string map = info.Map();
    int num_players = info.NumPlayers();
    int num_specs = info.NumSpecs();
    int time_left_mins = SoldatInfo::TicksToMins(info.Timeleft());
    int time_left_secs = SoldatInfo::TicksToSecs(info.Timeleft()) % 60;
    int time_limit = info.Timelimit();
    int limit = info.Limit();

    printf("Info:\n\n");
    printf("%-10s%s\n", "Gamemode:", gamemode.c_str());
    printf("%-10s%i\n", "Players:", num_players);
    printf("%-10s%i\n", "Specs:", num_specs);
    printf("%-10s%s\n", "Map:", map.c_str());
    printf("%-10s%i:%02i / %i:00\n", "Timeleft:", time_left_mins, time_left_secs, time_limit);
    printf("%-10s%i\n", "Limit:", limit);

    if (num_players > 0)
    {
        printf("\n\nPlayers:\n");
        // You can either iterate through players using integer to index the players array
        // Or you can use iterators (see specs loop for example)
        for (int i = 0; i < num_players; i++)
        {
            SoldatInfo::Client player = info.Players[i];
            std::string name = player.Name();
            std::string team = SoldatInfo::TeamToStr(player.Team());
            std::string ip = player.Ip();
            int id = player.Id();
            int kills = player.Kills();
            int deaths = player.Deaths();
            int ping = player.Ping();

            printf("\n%s\n%s\n", name.c_str(), std::string(name.size(), '-').c_str());
            printf("%-8s%i\n", "Id:", id);
            printf("%-8s%s\n", "Team:", team.c_str());
            printf("%-8s%i\n", "Kills:", kills);
            printf("%-8s%i\n", "Deaths:", deaths);
            printf("%-8s%i\n", "Ping:", ping);
            printf("%-8s%s\n", "Ip:", ip.c_str());
        }
    }

    if (num_specs > 0)
    {
        printf("\n\nSpecs:\n");
        // Example of using iterators
        for (SoldatInfo::iterator spec = info.Specs.begin(); spec != info.Specs.end(); spec++)
        {
            std::string name = spec->Name();
            std::string ip = spec->Ip();
            int id = spec->Id();
            int ping = spec->Ping();

            printf("\n%s\n%s\n", name.c_str(), std::string(name.size(), '-').c_str());
            printf("%-8s%i\n", "Id:", id);
            printf("%-8s%i\n", "Ping:", ping);
            printf("%-8s%s\n", "Ip:", ip.c_str());
        }
    }
}