#include <iostream>
#include <string>
#include <format>

#include <SDL2/SDL.h>
#include <SDL2/SDL_video.h>
#include <SDL2/SDL_image.h>
#include <SDL2/SDL_ttf.h>

#include <openssl/evp.h>
#include <openssl/sha.h>
#include <openssl/aes.h>
#include <openssl/err.h>
#include <openssl/rand.h>
#include <openssl/kdf.h>

#include "../include/util.cpp"

const int SCREEN_WIDTH = 950;
const int SCREEN_HEIGHT = 600;

SDL_Window*     mainWindow  = NULL;
SDL_Surface*    mainSurface = NULL;
SDL_Texture*    image       = NULL;
SDL_Renderer*   renderer    = NULL;

enum chickenState 
{
    CHICKEN_BASE,
    CHICKEN_LEFT,
    CHICKEN_RIGHT,
    CHICKEN_UP,
    CHICKEN_DOWN,
    CHICKEN_MAX
};
chickenState ch_st = chickenState::CHICKEN_BASE;

enum arrowsState
{
    ARROW_NONE,
    ARROW_LEFT,
    ARROW_RIGHT,
    ARROW_UP,
    ARROW_DOWN,
    ARROW_MAX
};
arrowsState key_pressed[6] = {ARROW_NONE,ARROW_NONE,ARROW_NONE,ARROW_NONE,ARROW_NONE,ARROW_NONE};

arrowsState combo_50[6] = {ARROW_LEFT, ARROW_RIGHT, ARROW_UP, ARROW_DOWN, ARROW_LEFT, ARROW_RIGHT};
arrowsState combo_55[6] = {ARROW_LEFT,ARROW_LEFT,ARROW_RIGHT,ARROW_RIGHT,ARROW_UP,ARROW_UP};
arrowsState combo_100[6] = {ARROW_UP, ARROW_RIGHT, ARROW_DOWN,ARROW_LEFT, ARROW_UP, ARROW_DOWN};
arrowsState combo_150[6] = {ARROW_RIGHT, ARROW_DOWN, ARROW_LEFT, ARROW_DOWN, ARROW_RIGHT,ARROW_UP};
arrowsState combo_1337[6] = {ARROW_UP, ARROW_UP, ARROW_UP, ARROW_LEFT,ARROW_RIGHT,ARROW_DOWN};
arrowsState combo_200[6] = {ARROW_UP, ARROW_UP, ARROW_UP, ARROW_UP, ARROW_UP, ARROW_UP};

int init()
{
    if (SDL_Init(SDL_INIT_VIDEO) != 0)
    {
        Util::Log_Error("Can't SDL_Init()");
        Util::Log_Error(SDL_GetError());
        return 1;
    }


    int img_flags = IMG_INIT_PNG;
    if (!(IMG_Init(img_flags) & img_flags))
    {
        Util::Log_Error("Can't IMG_Init()");
        Util::Log_Error(IMG_GetError());
        return 1;
    }

    if (TTF_Init() != 0)
    {
        Util::Log_Error("Can't TTF_Init()");
        Util::Log_Error(TTF_GetError());
        return 1;
    }


    mainWindow = SDL_CreateWindow("Chicken Dance - VSOSH Edition", 
                                SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, 
                                SCREEN_WIDTH, SCREEN_HEIGHT, 
                                SDL_WINDOW_SHOWN);
    SDL_SetWindowResizable(mainWindow, (SDL_bool)false);
    SDL_SetWindowMinimumSize(mainWindow, SCREEN_WIDTH, SCREEN_HEIGHT);
    SDL_SetWindowMaximumSize(mainWindow, SCREEN_WIDTH, SCREEN_HEIGHT);
    if (!mainWindow)
    {
        Util::Log_Error("Can't create window");
        Util::Log_Error(SDL_GetError());
        return 1;
    }

    renderer = SDL_CreateRenderer(mainWindow, -1, SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC);
    if (renderer == NULL)
    {
        Util::Log_Error("Can't SDL_CreateRenderer()");
        Util::Log_Error(SDL_GetError());
        return 1;
    }
    SDL_SetRenderDrawColor(renderer, 255,255,255,255);

    mainSurface = SDL_GetWindowSurface(mainWindow);

    if (!mainSurface)
    {
        Util::Log_Error("Can't get window surface");
        Util::Log_Error(SDL_GetError());
        return 1;
    }

    Util::Log_Ok("Init completed");
    return 0;
}

int quit()
{
    SDL_DestroyWindow(mainWindow);
    mainWindow = NULL;

    SDL_DestroyRenderer(renderer);
    renderer = NULL;

    SDL_Quit();
    IMG_Quit();
    TTF_Quit();

    Util::Log_Ok("Quit completed");
    
    return 0;
}


SDL_Texture*  load_texture_png(std::string path)
{
    SDL_Surface* resource = IMG_Load(path.c_str());
    if (resource == NULL)
    {
        Util::Log_Error("(load_texture_png) Can't load PNG resource "+path);
        Util::Log_Error(IMG_GetError());
        return NULL;
    }

    SDL_Texture* temp = SDL_CreateTextureFromSurface(renderer, resource);
    if (temp == NULL)
    {
        Util::Log_Error("(load_texture_png) Can't convert surface to texture");
        Util::Log_Error(SDL_GetError());
        return NULL;
    }

    SDL_FreeSurface(resource);

    Util::Log_Ok("PNG image "+path+" loaded");

    return temp;
}

TTF_Font* load_resource_ttf(std::string path, unsigned int size)
{
    TTF_Font* resource = TTF_OpenFont("resources/minecraft.ttf", size);
    if (resource == NULL)
    {
        Util::Log_Error("Can't load minecraft.ttf");
        Util::Log_Error(TTF_GetError());
        return NULL;
    }

    return resource;
}

static const char* password = "reverse-from-greg0r0-with-love";

static unsigned char salt[] = {
    0x12,0x34,0x56,0x78,0x90,0xab,0xcd,0xef
};

static unsigned char iv[16] = {
    0x10,0x11,0x12,0x13,0x14,0x15,0x16,0x17,
    0x18,0x19,0x1a,0x1b,0x1c,0x1d,0x1e,0x1f
};

static unsigned char ciphertext_1[] = {
    0x53, 0x4a, 0xd7, 0xb1, 0xdf, 0x96, 0x76, 0x45, 0x11, 0x33, 0xca, 0x4e,
  0x66, 0x5e, 0x11, 0xa8, 0x3a, 0x1b, 0x0a, 0xf7, 0x4c, 0xbd, 0x52, 0x3a,
  0xcb, 0x41, 0x04, 0xb4, 0xae, 0x55, 0xfc, 0xf4
};

static unsigned char ciphertext_2[] = {
  0x8a, 0x8a, 0x07, 0xdc, 0x3e, 0x7d, 0xa1, 0x44, 0xea, 0xb1, 0xdb, 0x82,
  0xb0, 0xae, 0x42, 0xb0, 0x85, 0x37, 0xcf, 0xd9, 0xdf, 0x20, 0x2e, 0x57,
  0x1d, 0x83, 0x8b, 0xd2, 0x2e, 0xfb, 0x67, 0x87, 0x0d, 0xb0, 0x26, 0x0b,
  0xfc, 0x68, 0xc1, 0xd7, 0xeb, 0xef, 0x1a, 0xb9, 0x97, 0xb8, 0x8c, 0xe9
};

void handleErrors()
{
    ERR_print_errors_fp(stderr);
}


std::string decrypt_flag1()
{
    int ciphertext_len = sizeof(ciphertext_1);

    unsigned char key[32];
    if(!PKCS5_PBKDF2_HMAC(
        password,
        strlen(password),
        salt,
        sizeof(salt),
        100000,
        EVP_sha256(),
        32,
        key))
    {
        std::cerr << "PBKDF2 failed\n";
        handleErrors();
        return "";
    }

    EVP_CIPHER_CTX* ctx = EVP_CIPHER_CTX_new();

    unsigned char plaintext[128];
    int len;
    int plaintext_len;

    if(!EVP_DecryptInit_ex(ctx, EVP_aes_256_cbc(), NULL, key, iv))
    {
        handleErrors();
        return "";
    }

    if(!EVP_DecryptUpdate(ctx, plaintext, &len, ciphertext_1, ciphertext_len))
    {
        handleErrors();
        return "";
    }

    plaintext_len = len;

    if(!EVP_DecryptFinal_ex(ctx, plaintext + len, &len))
    {
        std::cerr << "bad decrypt\n";
        handleErrors();
        return "";
    }

    plaintext_len += len;

    EVP_CIPHER_CTX_free(ctx);

    return std::string((reinterpret_cast<char*>(plaintext)),plaintext_len);
}

std::string decrypt_flag2()
{
    int ciphertext_len = sizeof(ciphertext_2);

    unsigned char key[32];
    if(!PKCS5_PBKDF2_HMAC(
        password,
        strlen(password),
        salt,
        sizeof(salt),
        100000,
        EVP_sha256(),
        32,
        key))
    {
        std::cerr << "PBKDF2 failed\n";
        handleErrors();
        return "";
    }

    EVP_CIPHER_CTX* ctx = EVP_CIPHER_CTX_new();

    unsigned char plaintext[128];
    int len;
    int plaintext_len;

    if(!EVP_DecryptInit_ex(ctx, EVP_aes_256_cbc(), NULL, key, iv))
    {
        handleErrors();
        return "";
    }

    if(!EVP_DecryptUpdate(ctx, plaintext, &len, ciphertext_2, ciphertext_len))
    {
        handleErrors();
        return "";
    }

    plaintext_len = len;

    if(!EVP_DecryptFinal_ex(ctx, plaintext + len, &len))
    {
        std::cerr << "bad decrypt\n";
        handleErrors();
        return "";
    }

    plaintext_len += len;

    EVP_CIPHER_CTX_free(ctx);

    return std::string((reinterpret_cast<char*>(plaintext)),plaintext_len);
}



int main(int argc, char** argv)
{
    if (init())
    {
        return 1;
    }

    image = load_texture_png("resources/background.png");
    SDL_Texture* chicken_pose_base  = load_texture_png("resources/chicken/chicken_base_a.png");
    SDL_Texture* chicken_pose_left  = load_texture_png("resources/chicken/chicken_left_a.png");
    SDL_Texture* chicken_pose_right = load_texture_png("resources/chicken/chicken_right_a.png");
    SDL_Texture* chicken_pose_up    = load_texture_png("resources/chicken/chicken_up_a.png");
    SDL_Texture* chicken_pose_down  = load_texture_png("resources/chicken/chicken_down_a.png");

    SDL_Texture* left_arrow_t   = load_texture_png("resources/arrows/arrow_left.png");
    SDL_Texture* right_arrow_t  = load_texture_png("resources/arrows/arrow_right.png");
    SDL_Texture* up_arrow_t     = load_texture_png("resources/arrows/arrow_up.png");
    SDL_Texture* down_arrow_t   = load_texture_png("resources/arrows/arrow_down.png");


    TTF_Font* font = load_resource_ttf("resources/minecraft.ttf", 24);

    bool active = true;

    SDL_Event e;

    int x = 0;
    int y = 0;
    int action_id = 0;
    int score = 0;
    std::string nonce = "";

    while (active)
    {
        ch_st = chickenState::CHICKEN_BASE;


        while(SDL_PollEvent(&e) != 0)
        {
            if (e.type == SDL_QUIT)
            {
                active = false;
            }


            if (e.type == SDL_KEYDOWN)
            {
                nonce = "";
                switch (e.key.keysym.sym) 
                {   
                    case SDLK_UP:
                        score += rand() % 5;
                        ch_st = chickenState::CHICKEN_UP;
                        key_pressed[action_id % 6] = arrowsState::ARROW_UP;
                        action_id += 1;
                        break;
                    case SDLK_DOWN:
                        score += rand() % 5;
                        ch_st = chickenState::CHICKEN_DOWN;
                        key_pressed[action_id % 6] = arrowsState::ARROW_DOWN;
                        action_id += 1;
                        break;
                    case SDLK_LEFT:
                        score += rand() % 5;
                        ch_st = chickenState::CHICKEN_LEFT;
                        key_pressed[action_id % 6] = arrowsState::ARROW_LEFT;
                        action_id += 1;
                        break;
                    case SDLK_RIGHT:
                        score += rand() % 5;
                        ch_st = chickenState::CHICKEN_RIGHT;
                        key_pressed[action_id % 6] = arrowsState::ARROW_RIGHT;
                        action_id += 1;
                        break;
                    //TODO - добавить по какому-то хоткею отображение автора программы.

                    default:
                        ch_st = chickenState::CHICKEN_BASE;
                        break;
                }
            }
            

        }
        
        SDL_RenderClear(renderer);
        
        // Отрисовка задника
        SDL_RenderCopy(renderer, image, NULL, NULL);


        // Отрисовка количества очков
        std::string format_score = std::format("Score: {:06} // {}", score % 1000000, nonce);
        int tWidth = 0;
        int tHeight = 0;
        SDL_Color white_c  = {255,255,255};
        SDL_Surface* fontSurf = TTF_RenderText_Solid(font, format_score.c_str(), white_c);
        if (!fontSurf)
        {
            Util::Log_Error("Can't generate score string surface");
            Util::Log_Error(SDL_GetError());
            active = false;
        }
        SDL_Texture* score_string = SDL_CreateTextureFromSurface(renderer, fontSurf);
        if (score_string == NULL)
        {
            Util::Log_Error("Can't generate score string texture");
            Util::Log_Error(SDL_GetError());
            active = false;
        } 
        else 
        {
            tWidth = fontSurf->w;
            tHeight = fontSurf->h;
        }
        SDL_FreeSurface(fontSurf);

        SDL_Rect textBox = {110, 60, tWidth, tHeight};
        SDL_RenderCopy(renderer, score_string, NULL, &textBox);
        SDL_DestroyTexture(score_string);

        // Отрисовка Нажатых клавиш
        // x, y, w, h
        SDL_Rect arrows_box = {100,100,225,375};
        //SDL_RenderDrawRect(renderer, &arrows_box);
        
        SDL_Rect arrow_tiles[6] = {
            {100,100,75,75}, {250,100,75,75},
            {100,250,75,75}, {250,250,75,75},
            {100,400,75,75}, {250,400,75,75},
        };

        for (int i = 0; i < 6 ; i++)
        {

            switch (key_pressed[i])
            {
                case arrowsState::ARROW_NONE:
                    SDL_RenderDrawRect(renderer, &arrow_tiles[i]);
                    break;
                case arrowsState::ARROW_LEFT:
                    SDL_RenderCopy(renderer, left_arrow_t, NULL, &arrow_tiles[i]);
                    break;
                case arrowsState::ARROW_RIGHT:
                    SDL_RenderCopy(renderer, right_arrow_t, NULL, &arrow_tiles[i]);
                    break;
                case arrowsState::ARROW_UP:
                    SDL_RenderCopy(renderer, up_arrow_t, NULL, &arrow_tiles[i]);
                    break;
                case arrowsState::ARROW_DOWN:
                    SDL_RenderCopy(renderer, down_arrow_t, NULL, &arrow_tiles[i]);
                    break;
                default:
                    Util::Log_Wtf("(render of arrows) Unreachable!");
                    break;
            }
        }

         // Отрисовка Курицы

        // x, y, w, h
        SDL_Rect chickenBox = {SCREEN_WIDTH / 2 + 10, 50, SCREEN_WIDTH/2 - 10, SCREEN_HEIGHT - 150};
        
        SDL_Texture* current_chicken_pose = NULL;

        switch (ch_st)
        {
            case  chickenState::CHICKEN_BASE:
                current_chicken_pose = chicken_pose_base;
                break;
            case  chickenState::CHICKEN_DOWN :
                current_chicken_pose = chicken_pose_down;
                break;
            case  chickenState::CHICKEN_UP:
                current_chicken_pose = chicken_pose_up;
                break;
            case  chickenState::CHICKEN_LEFT:
                current_chicken_pose = chicken_pose_left;
                break;
            case  chickenState::CHICKEN_RIGHT:
                current_chicken_pose = chicken_pose_right;
                break;
        }

        SDL_RenderCopy(renderer, current_chicken_pose, NULL, &chickenBox);


        // Cобственно, рендерим
        SDL_RenderPresent(renderer);


        // Проверка комбинаций и отрисовка флага
        if (action_id >= 6)
        {


            bool check_combo_50 = std::equal(std::begin(key_pressed), std::end(key_pressed), std::begin(combo_50));
            if (check_combo_50)
            {
                score += 50;
                nonce = "Combo 50!";
            }

            bool check_combo_55 = std::equal(std::begin(key_pressed), std::end(key_pressed), std::begin(combo_55));
            if (check_combo_55)
            {
                score += 55;
                nonce = "Combo 55!";
            }

            bool check_combo_100 = std::equal(std::begin(key_pressed), std::end(key_pressed), std::begin(combo_100));
            if (check_combo_100)
            {
                score += 100;
                nonce = "Combo 100!";
            }

            bool check_combo_150 = std::equal(std::begin(key_pressed), std::end(key_pressed), std::begin(combo_150));
            if (check_combo_150)
            {
                score += 150;
                nonce = "Combo 150!";
            }

            bool check_combo_1337 = std::equal(std::begin(key_pressed), std::end(key_pressed), std::begin(combo_1337));
            if (check_combo_1337)
            {
                score += 1337;
                nonce = decrypt_flag1();
            }

            bool check_combo_200 = std::equal(std::begin(key_pressed), std::end(key_pressed), std::begin(combo_200));
            if (check_combo_200)
            {
                score += 200;
                nonce = "Secret-Combo 200!";
            }

            for (int i =0; i<6;i++)
            {
                key_pressed[i] = arrowsState::ARROW_NONE;
            }
            action_id = 0;


            if (score >= 13371337)
                nonce = decrypt_flag2();

            SDL_Delay(100);
        }



        SDL_Delay(100);

    }

    TTF_CloseFont(font);
    SDL_DestroyTexture(image);
    SDL_DestroyTexture(chicken_pose_base);
    SDL_DestroyTexture(chicken_pose_left);
    SDL_DestroyTexture(chicken_pose_right);
    SDL_DestroyTexture(chicken_pose_up);
    SDL_DestroyTexture(chicken_pose_down);

    SDL_DestroyTexture(left_arrow_t);
    SDL_DestroyTexture(right_arrow_t);
    SDL_DestroyTexture(up_arrow_t);
    SDL_DestroyTexture(down_arrow_t);
    image = NULL;
    quit();

    return 0;
}