#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <math.h>
#include <stdbool.h>
#include <unistd.h>
#include <string.h>
#include <limits.h>
#include <time.h>
#include <sys/time.h>
#include <SDL2/SDL.h>
#include <SDL2/SDL_image.h>

#include "structure.h"
#include "base.h"
#include "music.h"
#include "display.h"
#include "collisions.h"
#include "rooms.h"
#include "cars.h"

bool WAIT = true;
bool SHOW = true;
bool matchDone = false;

bool noise = true;

int playerToMove = 0;

FILE* save;

void cameraActions(level* lvl, bool* halt, int* cx, int* cy, int* dezoom, int* sizeR) {
    SDL_Event event;
    while(SDL_PollEvent(&event)) {
        switch (event.type) {
            case SDL_QUIT:
                break;
            case SDL_KEYDOWN:
                switch (event.key.keysym.sym) {
                    case SDLK_SPACE:
                        *halt = true;
                        break;

                    case SDLK_q:
                        *cy = max(0, *cy-1);
                        break;

                    case SDLK_z:
                        *cx = max(0, *cx-1);
                        break;

                    case SDLK_d:
                        *cy = min(lvl->cols-1, *cy+1);
                        break;

                    case SDLK_s:
                        *cx = min(lvl->lines-1, *cx+1);
                        break;

                    case SDLK_p:
                        *dezoom = max(1, *dezoom-1);
                        *sizeR = min(WIDTH, HEIGHT)/(2*(*dezoom)+1)+1;
                        break;

                    case SDLK_m:
                        *dezoom = min(5, *dezoom+1);
                        *sizeR = min(WIDTH, HEIGHT)/(2*(*dezoom)+1)+1;
                        break;

                    case SDLK_TAB:
                        WAIT = !WAIT;
                        break;

                    case SDLK_LSHIFT:
                        SHOW = !SHOW;
                        break;

                    default:
                        break;
                }
        }
    }
}

double playerSpeedModifier(int nPl) {
    // gives players a speed boost/penalty depending on their position
    // -20% if in the lead, and +20% if last
    if(maxRemD-minRemD >= 0.0001) {
        double theta = (remainingD[nPl]-minRemD)/(maxRemD-minRemD);
        //printf("%lf\n", theta);
        return (1.0 + (40.0*theta-20.0)/100.0);
    } else {
        return 1.0;
    }
}

void player_turn(int id, char* exec) {
    if(noise) {
        system(exec);
        FILE* ptr = fopen("answer.txt", "r");
        int angle, mult;
        fscanf(ptr, "%d %d", &angle, &mult);
        if(mult > 100 || mult < 0) {
            mult = 0;
        }
        fclose(ptr);
        double dvx = 1.0+(rand()%200-100)/100.0*(DELTA_V/100.0);
        double dvy = 1.0+(rand()%200-100)/100.0*(DELTA_V/100.0);
        double psm = playerSpeedModifier(id);
        double dtheta = (rand()%200-100)*DELTA_THETA/100.0;
        set_speed_car(players[id].c,
            psm*dvx*MAX_SPEED*(mult)/100.0*cos((angle+dtheta)*3.14159/180.0),
            psm*dvy*MAX_SPEED*(mult)/100.0*sin((angle+dtheta)*3.14159/180.0)
        );
        fprintf(save, "%lf %lf\n",
            psm*dvx*MAX_SPEED*(mult)/100.0*cos((angle+dtheta)*3.14159/180.0),
            psm*dvy*MAX_SPEED*(mult)/100.0*sin((angle+dtheta)*3.14159/180.0)
        );
    } else {
        double vx, vy;
        fscanf(save, "%lf %lf", &vx, &vy);
        set_speed_car(players[id].c, vx, vy);
    }
}

void parse_all_players(level* lvl, char** execs) {
    currentTurn += 1;
    for(int p = 0; p < nPlayers; p++) {
        if(0==winners[p]) {
            write_output("output.txt", lvl, p);
            if(WAIT)usleep(1000000/10);
            player_turn(p, execs[p]);
        }
    }
}

int main(int argc, char** argv) {
    if(argc < 2) {
        fprintf(stderr, "usage : ./back ?<-R> <level_path> <P1 executable> <P2 executable> ...\n");
        exit(1);
    }

    int offsetP = 0;
    if(argv[1+offsetP][0] == '-' && argv[1+offsetP][1] == 'R' && argv[1+offsetP][2] == '\0') {
        // replay mode
        noise = false;
        printf("---------------------------\n");
        printf("--| Replay mode enabled |--\n");
        printf("---------------------------\n");
        offsetP = 1;
        save = fopen("lastActions.txt", "r");
    } else {
        save = fopen("lastActions.txt", "w");
    }

    int N_PLAYERS = argc-2-offsetP;

    char** execs = malloc(sizeof(char*)*N_PLAYERS);
    int* ranks = malloc(sizeof(int)*N_PLAYERS);
    for(int k = 0; k < N_PLAYERS; k++) {
        ranks[k] = N_PLAYERS;
        execs[k] = malloc(sizeof(char)*64);
        execs[k][0] = '.';
        execs[k][1] = '/';
        int i = 2;
        while(i-2 < 64 && argv[k+2+offsetP][i-2] != '\0') {
            execs[k][i] = argv[k+2+offsetP][i-2];
            i += 1;
        }
        execs[k][i] = '\0';
    }

    for(int k = 0; k < N_PLAYERS; k++) {
        printf("[%d] %s\n", k, execs[k]);
    }

    double elapsed = 0.0;
    bool halt = false;
    srand(time(NULL));
    playerToMove = rand()%(max(1,nPlayers));
    if (SDL_Init(SDL_INIT_EVERYTHING) != 0) {
        printf("error initializing SDL: %s\n", SDL_GetError());
    }
    SDL_Window* win = SDL_CreateWindow("Game",
                                       SDL_WINDOWPOS_CENTERED,
                                       SDL_WINDOWPOS_CENTERED,
                                       WIDTH, HEIGHT, 0);
 
    Uint32 render_flags = SDL_RENDERER_ACCELERATED;
    SDL_Renderer* rend = SDL_CreateRenderer(win, -1, render_flags);
    SDL_SetRenderDrawBlendMode(rend, SDL_BLENDMODE_BLEND);

    import_letters(rend);
    import_digits(rend);
    init_music();
    // ---------------------------- //
    //stage_file("levels/stage.txt");
    level* stage = parse_level(argv[1+offsetP]);
    init_all(N_PLAYERS);
    printf("\n");
    path* pth = getPath(stage);
    printLvl(stage);
    printPath(pth);

    printf("Starting direction : ");
    if(START_DIR == 0) {
        printf("North\n");
    } else if(START_DIR == 1) {
        printf("East\n");
    } else if(START_DIR == 2) {
        printf("South\n");
    } else if(START_DIR == 3) {
        printf("West\n");
    } else {
        printf("???\n");
    }
    printf("From (%d %d) to (%d %d)\n", START_CHX, START_CHY, FINISH_CHX, FINISH_CHY);

    int cx = START_CHX;
    int cy = START_CHY;
    int dezoom = 2;
    int sizeR = 200;

    int count = 0;
    while(!halt) {
        resetRenderer(rend);
        minRemD = 999999.0;
        maxRemD = 0.0;
        renderMap(rend, stage, pth, cx, cy, dezoom, sizeR, SHOW);
        for(int p = 0; p < N_PLAYERS; p++) {
            pt proj = project(p, cx, cy, dezoom, sizeR);
            drawStringToRenderer(rend, &(execs[p][1]), proj.ix, proj.iy, 75/(4+dezoom), 105/(4+dezoom));
        }
        if(elapsed <= 0.7) {
            // just selected speeds
            for(int p = 0; p < N_PLAYERS; p++) {
                pt proj = project(p, cx, cy, dezoom, sizeR);
                double nm = sqrt(proj.ix*1.0*proj.ix + proj.iy*1.0*proj.iy);
                drawLineWithThicc(rend, 2,
                    proj.ix,
                    proj.ix+(int)(players[p].c->vel.fx/5.0*(nm+PLAYER_R)/nm),
                    proj.iy,
                    proj.iy+(int)(players[p].c->vel.fy/5.0*(nm+PLAYER_R)/nm),
                    players[p].rgb.red, players[p].rgb.green, players[p].rgb.blue, 255
                );
            }
            placeRectToRenderer(rend, 0, HEIGHT-50, 50, 50, 255, 255, 32, 192);
        } else if(updateCars(stage)) {
            // movement
            placeRectToRenderer(rend, 0, HEIGHT-50, 50, 50, 32, 255, 32, 192);
        } else {
            // no movement
            placeRectToRenderer(rend, 0, HEIGHT-50, 50, 50, 255, 32, 32, 192);
            //B *= -1.0;
            // update stable positions
            for(int p = 0; p < nPlayers; p++) {
                if(!offTheMap[p]) {
                    players[p].c->lastPos.fx = players[p].c->pos.fx;
                    players[p].c->lastPos.fy = players[p].c->pos.fy;
                    players[p].c->lastChx = players[p].c->chx;
                    players[p].c->lastChy = players[p].c->chy;
                } else {
                    players[p].c->pos.fx = players[p].c->lastPos.fx;
                    players[p].c->pos.fy = players[p].c->lastPos.fy;
                    players[p].c->chx = players[p].c->lastChx;
                    players[p].c->chy = players[p].c->lastChy;
                }
                offTheMap[p] = false;
            }
            updateWins(ranks);
            if(ONEATATIME == 1) {
                if(remainingTurns != 0 && 0==winners[playerToMove]) {
                    write_output("output.txt", stage, playerToMove);
                    if(WAIT)usleep(1000000/10);
                    player_turn(playerToMove, execs[playerToMove]);
                }
                playerToMove += 1;
                while(playerToMove >= nPlayers) {
                    playerToMove -= nPlayers;
                    currentTurn += 1;
                    if(remainingTurns > 0) {
                        remainingTurns -= 1;
                        if(remainingTurns == 0) {
                            matchDone = true;
                            //halt = true;
                        }
                    }
                }
            } else {
                if(remainingTurns != 0)parse_all_players(stage, execs);
                if(remainingTurns > 0) {
                    remainingTurns -= 1;
                    if(remainingTurns == 0) {
                        matchDone = true;
                        //halt = true;
                    }
                }
            }
            elapsed = 0.0;
        }
        if(!noise) {
            drawStringToRenderer(rend, "replay", 10, HEIGHT-75, 75/3, 105/3);
        }
        if(remainingTurns >= 0) {
            draw7SegNumberToRenderer(rend, remainingTurns, WIDTH/2, HEIGHT-75, 25, 25, 3, 255, 32, 32, 255, 0, 0);
        } else {
            draw7SegNumberToRenderer(rend, currentTurn, WIDTH/2, HEIGHT-75, 25, 25, 3, 255, 255, 255, 255, 0, 0);
        }
        updateRenderer(rend);
        elapsed += DT;
        if(WAIT && count%3==0)usleep((int)(1000000*DT));
        cameraActions(stage, &halt, &cx, &cy, &dezoom, &sizeR);
        count += 1;
    }
    
    if(matchDone && noise)updateLeaderBoard(execs, ranks);
    // ---------------------------- //
    free_digits(digits);
    free_digits(letters);
    free_level(stage);
    freePath(pth);
    fclose(save);
    end_music();
    for(int k = 0; k < N_PLAYERS; k++) {
        free(execs[k]);
    }
    free(execs);
    free(remainingD);
    free(ranks);

    SDL_DestroyRenderer(rend);
    SDL_DestroyWindow(win);
    SDL_Quit();
    return 0;
}

/* 
TODO :
    - add level generation
    - check seg faults
    + add items (wip)
    + add hazards

KNOWN ISSUES :
    - /
*/