#include "LevelDebugger.h"

#include "Constants.h"
#include "CustomLevels.h"
#include "Entity.h"
#include "Font.h"
#include "Graphics.h"
#include "KeyPoll.h"
#include "Localization.h"
#include "Map.h"
#include "Script.h"
#include "UtilityClass.h"
#include "VFormat.h"

namespace level_debugger
{
    bool active = false;
    bool should_pause = true;
    bool tab_held = false;
    bool debug_held = false;
    bool forced = false;

    // Moving entities/blocks
    bool mouse_held = false;
    int held_entity = -1;
    int held_block = -1;
    int grabber_offset_x = 0;
    int grabber_offset_y = 0;

    bool right_mouse_held = false;

    bool is_pausing(void)
    {
        return active && should_pause;
    }

    bool is_active(void)
    {
        return active;
    }

    void toggle_active(void)
    {
        active = !active;
    }

    bool mouse_within(SDL_Rect* rect)
    {
        SDL_Point mouse = { key.mousex, key.mousey };
        return SDL_PointInRect(&mouse, rect);
    }

    void set_forced(void)
    {
        forced = true;
    }

    void input(void)
    {
        if (!forced && (!map.custommode || map.custommodeforreal))
        {
            active = false;
            return;
        }

        if (key.isDown(SDLK_y))
        {
            if (!debug_held)
            {
                debug_held = true;
                active = !active;
            }
        }
        else
        {
            debug_held = false;
        }

        if (!active)
        {
            return;
        }

        if (key.isDown(SDLK_TAB))
        {
            if (!tab_held)
            {
                tab_held = true;
                should_pause = !should_pause;
            }
        }
        else
        {
            tab_held = false;
        }

        for (int i = 0; i < (int) obj.entities.size(); i++)
        {
            SDL_Rect bounding_box = {
                obj.entities[i].xp + obj.entities[i].cx,
                obj.entities[i].yp + obj.entities[i].cy - map.ypos,
                obj.entities[i].w,
                obj.entities[i].h
            };

            if (key.leftbutton)
            {
                if (mouse_within(&bounding_box))
                {
                    if (!mouse_held)
                    {
                        mouse_held = true;
                        held_entity = i;
                        grabber_offset_x = key.mousex - obj.entities[i].xp;
                        grabber_offset_y = key.mousey - obj.entities[i].yp;

                        if (!key.keymap[SDLK_LSHIFT] && !key.keymap[SDLK_RSHIFT])
                        {
                            for (int j = 0; j < (int) obj.blocks.size(); j++)
                            {
                                if (obj.entities[i].xp == obj.blocks[j].rect.x && obj.entities[i].yp == obj.blocks[j].rect.y)
                                {
                                    held_block = j;
                                }
                            }
                        }
                    }
                    break;
                }
            }
            else
            {
                mouse_held = false;
                held_entity = -1;
                held_block = -1;
            }

            if (key.rightbutton)
            {
                if (mouse_within(&bounding_box))
                {
                    if (!right_mouse_held)
                    {
                        right_mouse_held = true;
                        obj.entities[i].state = obj.entities[i].onentity;
                        obj.entities[i].statedelay = -1;
                    }
                    break;
                }
            }
            else
            {
                right_mouse_held = false;
            }
        }

        if (held_entity == -1)
        {
            for (int i = 0; i < (int) obj.blocks.size(); i++)
            {
                SDL_Rect bounding_box = {
                    obj.blocks[i].rect.x,
                    obj.blocks[i].rect.y - map.ypos,
                    obj.blocks[i].rect.w,
                    obj.blocks[i].rect.h
                };

                if (key.leftbutton)
                {
                    if (mouse_within(&bounding_box))
                    {
                        if (!mouse_held)
                        {
                            mouse_held = true;
                            held_block = i;
                            grabber_offset_x = key.mousex - obj.blocks[i].rect.x;
                            grabber_offset_y = key.mousey - obj.blocks[i].rect.y;
                        }
                        break;
                    }
                }
                else
                {
                    held_entity = -1;
                    mouse_held = false;
                    held_block = -1;
                }
            }
        }
    }

    void logic(void)
    {
        if (!active)
        {
            return;
        }

        if (INBOUNDS_VEC(held_entity, obj.entities))
        {
            int new_xp = key.mousex - grabber_offset_x;
            int new_yp = key.mousey - grabber_offset_y;

            if (key.isDown(SDLK_LSHIFT) || key.isDown(SDLK_RSHIFT))
            {
                new_xp -= new_xp % 8;
                new_yp -= new_yp % 8;
            }

            obj.entities[held_entity].xp = new_xp;
            obj.entities[held_entity].yp = new_yp;
            obj.entities[held_entity].lerpoldxp = new_xp;
            obj.entities[held_entity].lerpoldyp = new_yp;
            obj.entities[held_entity].oldxp = new_xp;
            obj.entities[held_entity].oldyp = new_yp;
        }

        if (INBOUNDS_VEC(held_block, obj.blocks))
        {
            int new_xp = key.mousex - grabber_offset_x;
            int new_yp = key.mousey - grabber_offset_y;

            if (key.isDown(SDLK_LSHIFT) || key.isDown(SDLK_RSHIFT))
            {
                new_xp -= new_xp % 8;
                new_yp -= new_yp % 8;
            }

            obj.blocks[held_block].xp = new_xp;
            obj.blocks[held_block].yp = new_yp;
            obj.blocks[held_block].rect.x = new_xp;
            obj.blocks[held_block].rect.y = new_yp;
        }
    }

    void render_info(int y, const char* text)
    {
        font::print(PR_BOR | PR_FONT_8X8, 5, 32 + (10 * y), text, 220 - (help.glow), 220 - (help.glow), 255 - (help.glow / 2));
    }

    void render_coords(int y, const char* text, int first, int second)
    {
        char buffer[SCREEN_WIDTH_CHARS + 1];
        vformat_buf(buffer, sizeof(buffer), "{text}: ({first},{second})", "text:str, first:int, second:int", text, first, second);
        render_info(y, buffer);
    }

    void render_info(int y, const char* text, std::string value)
    {
        char buffer[SCREEN_WIDTH_CHARS + 1];
        vformat_buf(buffer, sizeof(buffer), "{text}: {value}", "text:str, value:str", text, value.c_str());
        render_info(y, buffer);
    }

    void render(void)
    {
        if (!active)
        {
            return;
        }

        int hovered = -1;
        bool hovered_entity = true;
        SDL_Rect hover_box;

        for (int i = 0; i < (int) obj.entities.size(); i++)
        {
            SDL_Rect bounding_box = {
                obj.entities[i].xp + obj.entities[i].cx,
                obj.entities[i].yp + obj.entities[i].cy - map.ypos,
                obj.entities[i].w,
                obj.entities[i].h
            };

            if (hovered == -1 && mouse_within(&bounding_box))
            {
                hovered = i;
                hovered_entity = true;
                hover_box = bounding_box;
            }

            graphics.draw_rect(bounding_box.x, bounding_box.y, bounding_box.w, bounding_box.h, graphics.getRGB(15, 90, 90));

            // For gravity lines, show the true hitbox.
            if (obj.entities[i].type == 9)
            {
                graphics.draw_rect(bounding_box.x - 1, bounding_box.y + 1, bounding_box.w + 2, bounding_box.h, graphics.getRGB(90, 90, 15));
            }
            else if (obj.entities[i].type == 10)
            {
                graphics.fill_rect(bounding_box.x - 2, bounding_box.y - 1, bounding_box.w + 1, bounding_box.h + 2, graphics.getRGB(90, 90, 15));
            }

        }

        for (int i = 0; i < (int) obj.blocks.size(); i++)
        {
            SDL_Rect bounding_box = {
                obj.blocks[i].rect.x,
                obj.blocks[i].rect.y - map.ypos,
                obj.blocks[i].rect.w,
                obj.blocks[i].rect.h
            };

            if (hovered == -1 && mouse_within(&bounding_box))
            {
                hovered = i;
                hovered_entity = false;
                hover_box = bounding_box;
            }

            graphics.draw_rect(bounding_box.x, bounding_box.y, bounding_box.w, bounding_box.h, graphics.getRGB(90, 15, 15));
        }

        int line = 0;

        if (key.isDown(SDLK_u))
        {
            SDL_Color on = graphics.getRGB(220 - (help.glow), 220 - (help.glow), 255 - (help.glow / 2));
            SDL_Color off = graphics.getRGB(220 / 1.5 - (help.glow), 220 / 1.5 - (help.glow), 255 / 1.5 - (help.glow / 2));

            graphics.set_blendmode(SDL_BLENDMODE_BLEND);
            graphics.fill_rect(NULL, 0, 0, 0, 127);
            graphics.set_blendmode(SDL_BLENDMODE_NONE);

            int x = 0;
            int y = 0;

            for (int i = 0; i < (int) SDL_arraysize(obj.flags); i++)
            {
                SDL_Color color = obj.flags[i] ? on : off;
                font::print(PR_BOR | PR_FONT_8X8, 48 + x * 24, 48 + y * 16, help.String(i), color.r, color.g, color.b);

                x++;
                if (x >= 10)
                {
                    x = 0;
                    y++;
                }
            }
        }
        else if (hovered == -1)
        {
            render_coords(line++, "Room", game.roomx % 100, game.roomy % 100);
            render_coords(line++, "Cursor", key.mousex, key.mousey);
            render_info(line++, "Entities", help.String(obj.entities.size()));
            line++;

            render_info(line++, "State", help.String(game.state));
            render_info(line++, "State Delay", help.String(game.statedelay));
            line++;

            render_info(line++, "AEM Target", help.String(script.i));
            render_info(line++, "Warp Background", help.String(cl.getroomprop(game.roomx % 100, game.roomy % 100)->warpdir));

            line++;

            if (script.running)
            {
                render_info(line++, "Script", script.scriptname);
                render_info(line++, "Delay", help.String(script.scriptdelay));
                render_info(line++, "Position", help.String(script.position));
                line++;
                render_info(line++, "Loop Line", help.String(script.looppoint));
                render_info(line++, "Loop Count", help.String(script.loopcount));
            }
        }
        else
        {
            if (hovered_entity)
            {
                entclass* entity = &obj.entities[hovered];
                render_info(line++, "Index", help.String(hovered));
                render_coords(line++, "Position", entity->xp, entity->yp);
                render_coords(line++, "Size", entity->w, entity->h);
                line++;
                render_info(line++, "Rule", help.String(entity->rule));
                render_info(line++, "Type", help.String(entity->type));
                render_info(line++, "Behave", help.String(entity->behave));
                render_info(line++, "Para", help.String(entity->para));
                render_info(line++, "State", help.String(entity->state));
                line++;
                render_info(line++, "Tile", help.String(entity->tile));
                render_info(line++, "Draw Frame", help.String(entity->drawframe));
                render_info(line++, "Size", help.String(entity->size));
                render_info(line++, "Direction", help.String(entity->dir));

                line++;

                // Mostly contains duplicates, but for ease of use
                switch (entity->type)
                {
                case 0:
                    // Player
                    render_info(line++, "Gravity", help.String(game.gravitycontrol));
                    render_info(line++, "Checkpoint", help.String(game.savepoint));
                    break;
                case 1:
                    // Moving platforms and enemies
                    render_info(line++, "Speed", help.String(entity->para));
                    render_info(line++, "Movement type", help.String(entity->behave));
                    break;
                case 7:
                    // Trinkets
                    render_info(line++, "ID", help.String(entity->para));
                    break;
                case 8:
                    // Checkpoints
                    render_info(line++, "ID", help.String(entity->para));
                    render_info(line++, "Active", game.savepoint == entity->para ? "True" : "False");
                    break;
                case 9:
                    // Horizontal gravity lines
                    render_info(line++, "Horizontal");
                    break;
                case 10:
                    // Vertical gravity lines
                    render_info(line++, "Vertical");
                    break;
                }


                graphics.draw_rect(hover_box.x, hover_box.y, hover_box.w, hover_box.h, graphics.getRGB(32, 255 - help.glow, 255 - help.glow));
            }
            else
            {
                blockclass* block = &obj.blocks[hovered];
                render_info(line++, "Index", help.String(hovered));
                render_coords(line++, "Position", block->rect.x, block->rect.y);
                render_coords(line++, "Size", block->rect.w, block->rect.h);

                line++;

                if (block->type == TRIGGER || block->type == ACTIVITY)
                {
                    render_info(line++, "Script", block->script);
                    render_info(line++, "State", help.String(block->trigger));
                }
                else if (block->type == DIRECTIONAL)
                {
                    render_info(line++, "Direction", help.String(block->trigger));
                }

                graphics.draw_rect(hover_box.x, hover_box.y, hover_box.w, hover_box.h, graphics.getRGB(255 - help.glow, 32, 32));
            }
        }

        const char* text;
        if (should_pause)
        {
            text = loc::gettext("[Press {button} to unfreeze gameplay]");
        }
        else
        {
            text = loc::gettext("[Press {button} to freeze gameplay]");
        }
        char buffer[SCREEN_WIDTH_CHARS + 1];
        vformat_buf(
            buffer, sizeof(buffer),
            text,
            "button:str",
            loc::gettext("TAB")
        );

        font::print(PR_BOR, 5, 14, buffer, 220 - (help.glow), 220 - (help.glow), 255 - (help.glow / 2));
    }
}
