#define WIN32_LEAN_AND_MEAN
#define SDL_MAIN_HANDLED
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <windows.h>   // Sleep() 
#include "net.h"
#include "event.h"
#include "player.h"

#include <SDL2/SDL.h>

CRITICAL_SECTION g_playerLock;
static Player g_players[MAX_PLAYERS];
static int g_numPlayers = 0;
static SOCKET g_clients[MAX_CLIENTS];
static int g_clientCount = 0;

static uint32_t g_nextID = 1; // to assign unique IDs

// Forward declarations
DWORD WINAPI client_thread(LPVOID param);
void broadcast_packet(Packet* pkt, SOCKET except);


void on_player_move(Packet* pkt, SOCKET client) {
    PlayerMove pm;
    memcpy(&pm, pkt->data, sizeof(PlayerMove));

    EnterCriticalSection(&g_playerLock);
    for(int i = 0; i < g_numPlayers; i++) {
        if(g_players[i].id == pm.id) {
            g_players[i].x += pm.dx;
            g_players[i].y += pm.dy;
            break;
        }
    }

    // broadcast updated positions
    int numStates = g_numPlayers;
    int dataSize = sizeof(PlayerState) * numStates;
    PlayerState* states = malloc(dataSize);
    for(int i = 0; i < g_numPlayers; i++) {
        states[i].id = g_players[i].id;
        states[i].x  = g_players[i].x;
        states[i].y  = g_players[i].y;
    }
    Packet* statePkt = create_packet(PACKET_PLAYER_STATE, states, dataSize);
    free(states);
    broadcast_packet(statePkt, INVALID_SOCKET);
    destroy_packet(statePkt);

    LeaveCriticalSection(&g_playerLock);
}


void broadcast_packet(Packet* pkt, SOCKET except) {
    // serialize
    char buffer[BUFFER_SIZE];
    int len = serialize_packet(pkt, buffer);

    EnterCriticalSection(&g_playerLock);
    for(int i = 0; i < g_clientCount; i++) {
        if(g_clients[i] == except) continue;
        send(g_clients[i], buffer, len, 0);
    }
    LeaveCriticalSection(&g_playerLock);
}

DWORD WINAPI client_thread(LPVOID param) {
    SOCKET client = *(SOCKET*)param;
    free(param);

    // Non-blocking for easier single-thread dispatch or we do blocking in a thread
    set_socket_nonblocking(client, 0); // blocking in this thread

    // Assign a new player
    Player newPlayer;
    newPlayer.id = g_nextID++;
    newPlayer.x  = 100; // start at 100,100
    newPlayer.y  = 100;

    EnterCriticalSection(&g_playerLock);
    g_players[g_numPlayers++] = newPlayer;
    g_clients[g_clientCount++] = client;
    LeaveCriticalSection(&g_playerLock);

    printf("[SERVER] Client %u connected.\n", newPlayer.id);

    // Now run receive loop
    char buffer[BUFFER_SIZE];
    while(1) {
        int received = recv(client, buffer, BUFFER_SIZE, 0);
        if (received <= 0) {
            break; // client disconnected
        }
        Packet* pkt = deserialize_packet(buffer, received);
        if (pkt) {
            // Dispatch
            handle_packet(pkt, client);
            destroy_packet(pkt);
        }
    }

    // Remove from global state
    EnterCriticalSection(&g_playerLock);
    // remove from g_clients
    for(int i = 0; i < g_clientCount; i++) {
        if(g_clients[i] == client) {
            g_clients[i] = g_clients[--g_clientCount];
            break;
        }
    }
    // remove from g_players
    for(int i = 0; i < g_numPlayers; i++) {
        if(g_players[i].id == newPlayer.id) {
            g_players[i] = g_players[--g_numPlayers];
            break;
        }
    }
    LeaveCriticalSection(&g_playerLock);

    closesocket(client);
    printf("[SERVER] Client %u disconnected.\n", newPlayer.id);
    return 0;
}

void run_server(int port) {
    net_init();
    InitializeCriticalSection(&g_playerLock);

    // Register server handlers
    register_handler(PACKET_PLAYER_MOVE, on_player_move);

    SOCKET server = net_bind_and_listen(port);
    if (server == INVALID_SOCKET) {
        printf("Failed to bind on port %d\n", port);
        return;
    }
    printf("[SERVER] Listening on port %d...\n", port);

    while(1) {
        SOCKET s = net_accept(server);
        if (s == INVALID_SOCKET) {
            Sleep(10);
            continue;
        }
        // Start a client thread
        SOCKET* ptr = malloc(sizeof(SOCKET));
        *ptr = s;
        CreateThread(NULL, 0, client_thread, ptr, 0, NULL);
    }

    DeleteCriticalSection(&g_playerLock);
    closesocket(server);
    net_cleanup();
}


static Player g_localPlayer = {0};
static PlayerState g_otherPlayers[MAX_PLAYERS];
static int g_otherCount = 0;

// For now, store the socket globally
static SOCKET g_sock = INVALID_SOCKET;

// Handler: PACKET_PLAYER_STATE
void on_player_state(Packet* pkt, SOCKET _) {
    // We'll parse an array of PlayerStates
    int num = pkt->size / sizeof(PlayerState);
    if(num > MAX_PLAYERS) num = MAX_PLAYERS;

    memcpy(g_otherPlayers, pkt->data, pkt->size);
    g_otherCount = num;
}

void run_client(const char* ip, int port) {
    net_init();

    // Register client handlers
    register_handler(PACKET_PLAYER_STATE, on_player_state);

    // Connect
    g_sock = net_connect(ip, port);
    if (g_sock == INVALID_SOCKET) {
        printf("[CLIENT] Could not connect to server.\n");
        return;
    }
    printf("[CLIENT] Connected to server %s:%d\n", ip, port);

    // We'll assign an ID after reading from server in a real scenario,
    // but for simplicity, let's guess the server will do so. 
    // We'll just store local ID = 999, or we wait for some handshake. 
    // For now, let's store a random number:
    g_localPlayer.id = 9999;
    g_localPlayer.x  = 200;
    g_localPlayer.y  = 200;

    // Setup SDL
    if (SDL_Init(SDL_INIT_VIDEO) != 0) {
        printf("SDL_Init failed: %s\n", SDL_GetError());
        return;
    }

    SDL_Window* window = SDL_CreateWindow(
        "MPGC Client",
        SDL_WINDOWPOS_CENTERED,
        SDL_WINDOWPOS_CENTERED,
        800, 600,
        0
    );
    if(!window) {
        printf("SDL_CreateWindow failed: %s\n", SDL_GetError());
        SDL_Quit();
        return;
    }

    SDL_Renderer* renderer = SDL_CreateRenderer(window, -1, 0);
    if(!renderer) {
        printf("SDL_CreateRenderer failed: %s\n", SDL_GetError());
        SDL_DestroyWindow(window);
        SDL_Quit();
        return;
    }

    // Non-blocking so we can poll in main loop
    set_socket_nonblocking(g_sock, 1);

    int running = 1;
    while (running) {
        // ---- Handle SDL events
        SDL_Event e;
        while(SDL_PollEvent(&e)) {
            if(e.type == SDL_QUIT) {
                running = 0;
            }
        }

        // ---- Keyboard input (WASD)
        const Uint8* state = SDL_GetKeyboardState(NULL);
        float dx = 0.f;
        float dy = 0.f;
        if(state[SDL_SCANCODE_W]) { dy -= 2.f; }
        if(state[SDL_SCANCODE_S]) { dy += 2.f; }
        if(state[SDL_SCANCODE_A]) { dx -= 2.f; }
        if(state[SDL_SCANCODE_D]) { dx += 2.f; }

        if(dx != 0 || dy != 0) {
            // Move local player
            g_localPlayer.x += dx;
            g_localPlayer.y += dy;

            // Send PACKET_PLAYER_MOVE
            PlayerMove pm;
            pm.id = g_localPlayer.id;
            pm.dx = dx;
            pm.dy = dy;

            Packet* movePkt = create_packet(PACKET_PLAYER_MOVE, &pm, sizeof(pm));
            char buffer[BUFFER_SIZE];
            int len = serialize_packet(movePkt, buffer);
            send(g_sock, buffer, len, 0);
            destroy_packet(movePkt);
        }

        // ---- Check for incoming data
        while (1) {
            char netbuf[BUFFER_SIZE];
            int r = recv(g_sock, netbuf, BUFFER_SIZE, 0);
            if (r <= 0) break; // no data or error
            Packet* pkt = deserialize_packet(netbuf, r);
            if(pkt) {
                handle_packet(pkt, g_sock);
                destroy_packet(pkt);
            }
        }

        // ---- Render scene
        SDL_SetRenderDrawColor(renderer, 20, 20, 20, 255);
        SDL_RenderClear(renderer);

        // Draw local player
        SDL_Rect rLoc = {
            (int)g_localPlayer.x,
            (int)g_localPlayer.y,
            30, 30
        };
        SDL_SetRenderDrawColor(renderer, 200, 200, 50, 255);
        SDL_RenderFillRect(renderer, &rLoc);

        // Draw other players
        SDL_SetRenderDrawColor(renderer, 100, 200, 200, 255);
        for(int i = 0; i < g_otherCount; i++) {
            SDL_Rect rOth = {
                (int)g_otherPlayers[i].x,
                (int)g_otherPlayers[i].y,
                30, 30
            };
            SDL_RenderFillRect(renderer, &rOth);
        }
        SDL_RenderPresent(renderer);
    }

    // Cleanup
    SDL_DestroyRenderer(renderer);
    SDL_DestroyWindow(window);
    SDL_Quit();

    closesocket(g_sock);
    net_cleanup();
}

int main(int argc, char* argv[]) {
    printf("[INFO] Starting...\n");
    SDL_SetMainReady();

    if (argc < 3) {
        printf("Usage:\n  %s server <port>\n  %s client <ip> <port>\n", argv[0], argv[0]);
        return 1;
    }


    if(strcmp(argv[1], "server") == 0) {
        int port = atoi(argv[2]);
        run_server(port);
        printf("[SERVER] Listening on port %d\n", port);
    } else if(strcmp(argv[1], "client") == 0 && argc >= 4) {
        const char* ip = argv[2];
        int port = atoi(argv[3]);
        run_client(ip, port);
    } else {
        printf("Invalid arguments.\n");
    }

    return 0;
}
