#include <luna/String.h>
#include <luna/Utf8.h>
#include <stdlib.h>
#include <time.h>
#include <ui/Alignment.h>
#include <ui/App.h>
#include <ui/Font.h>
#include <ui/Layout.h>

static ui::Color colors[] = {
    ui::Color::from_rgb(255, 255, 0), ui::Color::from_rgb(255, 230, 0), ui::Color::from_rgb(255, 210, 0),
    ui::Color::from_rgb(255, 190, 0), ui::Color::from_rgb(255, 170, 0), ui::Color::from_rgb(255, 150, 0),
    ui::Color::from_rgb(255, 130, 0), ui::Color::from_rgb(255, 110, 0), ui::Color::from_rgb(255, 90, 0),
    ui::Color::from_rgb(255, 70, 0),  ui::Color::from_rgb(255, 50, 0),
};

struct Tile
{
    int number { 0 };
    int color { 0 };
};

class GameWidget final : public ui::Widget
{
  public:
    GameWidget(ui::Window* window, ui::Widget* parent) : ui::Widget(window, parent)
    {
    }

    void show_tree(int indent) override
    {
        os::println("%*s- 2048 GameWidget (%d,%d,%d,%d)", indent, "", m_rect.pos.x, m_rect.pos.y, m_rect.width,
                    m_rect.height);
    }

    static constexpr int MARGIN = 5;

    Result<void> draw(ui::Canvas& canvas) override
    {
        int width = m_rect.width / 4;
        int height = m_rect.height / 4;

        for (int i = 0; i < 4; i++)
        {
            for (int j = 0; j < 4; j++)
            {
                auto subcanvas = canvas.subcanvas(
                    ui::Rect { width * j + MARGIN, height * i + MARGIN, width - MARGIN, height - MARGIN });
                int index = i * 4 + j;
                TRY(draw_tile(index, subcanvas));
            }
        }

        return {};
    }

    Result<ui::EventResult> handle_key_event(const ui::KeyEventRequest& request) override
    {
        if (!request.pressed) return ui::EventResult::DidNotHandle;

        bool should_add_tile = false;

        switch (request.code)
        {
        case moon::K_UpArrow: {
            bool changed;
            changed = move_up();
            if (changed) should_add_tile = true;
            join_up();
            changed = move_up();
            if (changed) should_add_tile = true;
        }
        break;
        case moon::K_LeftArrow: {
            bool changed;
            changed = move_left();
            if (changed) should_add_tile = true;
            join_left();
            changed = move_left();
            if (changed) should_add_tile = true;
        }
        break;
        case moon::K_DownArrow: {
            bool changed;
            changed = move_down();
            if (changed) should_add_tile = true;
            join_down();
            changed = move_down();
            if (changed) should_add_tile = true;
        }
        break;
        case moon::K_RightArrow: {
            bool changed;
            changed = move_right();
            if (changed) should_add_tile = true;
            join_right();
            changed = move_right();
            if (changed) should_add_tile = true;
        }
        break;
        case moon::K_Home: {
            reset();
            return ui::EventResult::DidHandle;
        }
        break;
        default: return ui::EventResult::DidNotHandle;
        }

        if (should_add_tile) add_tile();

        return ui::EventResult::DidHandle;
    }

    bool move_left()
    {
        Tile new_tiles[16];

        bool changed = false;

        for (int i = 0; i < 4; i++)
        {
            int pos = 0;

            for (int j = 0; j < 4; j++)
            {
                if (tiles[i * 4 + j].number != 0)
                {
                    new_tiles[i * 4 + pos] = tiles[i * 4 + j];
                    pos += 1;
                    changed = true;
                }
            }
        }

        memcpy(tiles, new_tiles, sizeof(tiles));
        return changed;
    }

    void join_left()
    {
        for (int i = 0; i < 4; i++)
        {
            for (int j = 0; j < 3; j++)
            {
                auto& from_tile = tiles[i * 4 + j];
                auto& to_tile = tiles[i * 4 + j + 1];
                if (from_tile.number != 0 && from_tile.number == to_tile.number)
                {
                    from_tile.number *= 2;
                    from_tile.color += 1;
                    to_tile.number = 0;
                }
            }
        }
    }

    bool move_right()
    {
        Tile new_tiles[16];

        bool changed = false;

        for (int i = 0; i < 4; i++)
        {
            int pos = 3;

            for (int j = 0; j < 4; j++)
            {
                if (tiles[i * 4 + j].number != 0)
                {
                    new_tiles[i * 4 + pos] = tiles[i * 4 + j];
                    pos -= 1;
                    changed = true;
                }
            }
        }

        memcpy(tiles, new_tiles, sizeof(tiles));
        return changed;
    }

    void join_right()
    {
        for (int i = 0; i < 4; i++)
        {
            for (int j = 1; j < 4; j++)
            {
                auto& from_tile = tiles[i * 4 + j];
                auto& to_tile = tiles[i * 4 + j - 1];
                if (from_tile.number != 0 && from_tile.number == to_tile.number)
                {
                    from_tile.number *= 2;
                    from_tile.color += 1;
                    to_tile.number = 0;
                }
            }
        }
    }

    bool move_up()
    {
        Tile new_tiles[16];

        bool changed = false;

        for (int j = 0; j < 4; j++)
        {
            int pos = 0;

            for (int i = 0; i < 4; i++)
            {
                if (tiles[i * 4 + j].number != 0)
                {
                    new_tiles[pos * 4 + j] = tiles[i * 4 + j];
                    pos += 1;
                    changed = true;
                }
            }
        }

        memcpy(tiles, new_tiles, sizeof(tiles));
        return changed;
    }

    void join_up()
    {
        for (int i = 0; i < 3; i++)
        {
            for (int j = 0; j < 4; j++)
            {
                auto& from_tile = tiles[i * 4 + j];
                auto& to_tile = tiles[i * 4 + j + 4];
                if (from_tile.number != 0 && from_tile.number == to_tile.number)
                {
                    from_tile.number *= 2;
                    from_tile.color += 1;
                    to_tile.number = 0;
                }
            }
        }
    }

    bool move_down()
    {
        Tile new_tiles[16];

        bool changed = false;

        for (int j = 0; j < 4; j++)
        {
            int pos = 3;

            for (int i = 0; i < 4; i++)
            {
                if (tiles[i * 4 + j].number != 0)
                {
                    new_tiles[pos * 4 + j] = tiles[i * 4 + j];
                    pos -= 1;
                    changed = true;
                }
            }
        }

        memcpy(tiles, new_tiles, sizeof(tiles));
        return changed;
    }

    void join_down()
    {
        for (int i = 1; i < 4; i++)
        {
            for (int j = 0; j < 4; j++)
            {
                auto& from_tile = tiles[i * 4 + j];
                auto& to_tile = tiles[i * 4 + j - 4];
                if (from_tile.number != 0 && from_tile.number == to_tile.number)
                {
                    from_tile.number *= 2;
                    from_tile.color += 1;
                    to_tile.number = 0;
                }
            }
        }
    }

    void add_tile()
    {
        bool can_add_tile = false;
        for (int i = 0; i < 16; i++)
        {
            if (tiles[i].number == 0)
            {
                can_add_tile = true;
                break;
            }
        }

        if (!can_add_tile)
        {
            reset();
            return;
        }

        int start;
        do {
            start = rand() % 16;
        } while (tiles[start].number != 0);
        tiles[start].number = 2;
        tiles[start].color = 0;
    }

    void reset()
    {
        for (int i = 0; i < 16; i++)
        {
            tiles[i].number = 0;
            tiles[i].color = 0;
        }

        add_tile();
    }

    Tile tiles[16];

  private:
    Result<void> draw_tile(int index, ui::Canvas& canvas)
    {
        auto tile = tiles[index];

        if (tile.number == 0)
        {
            canvas.fill(ui::GRAY);
            return {};
        }

        canvas.fill(colors[tile.color]);

        auto fmt = TRY(String::format("%d"_sv, tile.number));

        auto font = ui::Font::default_bold_font();
        auto rect = ui::align({ 0, 0, canvas.width, canvas.height },
                              { 0, 0, (int)fmt.length() * font->width(), font->height() },
                              ui::VerticalAlignment::Center, ui::HorizontalAlignment::Center);
        auto subcanvas = canvas.subcanvas(rect);

        Utf8StringDecoder decoder(fmt.chars());
        wchar_t buf[4096];
        TRY(decoder.decode(buf, sizeof(buf)));

        font->render(buf, ui::BLACK, subcanvas);

        return {};
    }
};

Result<int> luna_main(int, char**)
{
    srand((unsigned)time(NULL));

    ui::App app;
    TRY(app.init());

    auto* window = TRY(ui::Window::create(ui::Rect { 300, 300, 400, 400 }));
    app.set_main_window(window);

    window->set_background(ui::BLACK);
    window->set_title("2048");

    auto* game = TRY(window->create_main_widget<GameWidget>());
    game->reset();

    return app.run();
}