#include "Drawer.h"

static const char* tileImageNames[TILE_IMAGES_N] = {
    // clang-format off
    "Pin1",    "Pin2",    "Pin3",    "Pin4",    "Pin5",    "Pin6",   "Pin7",    "Pin8",    "Pin9",
    "Sou1",    "Sou2",    "Sou3",    "Sou4",    "Sou5",    "Sou6",   "Sou7",    "Sou8",    "Sou9",
    "Man1",    "Man2",    "Man3",    "Man4",    "Man5",    "Man6",   "Man7",    "Man8",    "Man9",
    "Chun",    "Haku",    "Hatsu",
    "Nan",     "Pei",     "Shaa",    "Ton",
    "Flower1", "Flower2", "Flower3", "Flower4",
    "Season1", "Season2", "Season3", "Season4"
    // clang-format on
};

static const wxColor lGreen{0x07, 0x55, 0x2b};
static const wxColor dGreen{0x01, 0x2d, 0x16};

Drawer::Drawer() : marked{-1, -1, -1} {
    for (int i = 0; i < TILE_IMAGES_N; i++) {
        bool succeed = tileImages[i].LoadFile(
            _("./resources/tiles/") + _(tileImageNames[i]) + _(".png"),
            wxBITMAP_TYPE_PNG);
        if (!succeed)
            wxLogDebug(_("failed to load tile ./resources/tiles/") +
                       _(tileImageNames[i]) + _(".png with index") +
                       wxString::Format("%i", i));
    }
}

void Drawer::drawTable(wxDC& dc) {
    dc.DrawBitmap(bgBitmap, 0, 0, false);
    if (boardBitmap.IsOk()) {
        wxLogDebug("Drawing board");
        dc.DrawBitmap(boardBitmap, tablePixelRect.GetPosition(), true);
    }
}

void Drawer::composeBG() {
    bgBitmap = wxBitmap(resolution);

    wxLogDebug(
        wxString::Format("Rebuild bg %i %i", resolution.x, resolution.y));

    wxMemoryDC dc;
    dc.SelectObject(bgBitmap);

    dc.GradientFillConcentric(wxRect(wxPoint(0, 0), resolution), lGreen,
                              dGreen);
}

void Drawer::composeBoard(const TLVec& layout, const Dimensions& gridSize) {
    boardBitmap = wxBitmap(tablePixelRect.GetSize());

    wxLogDebug(_("Rebuild board"));

    wxMemoryDC dc;
    dc.SelectObject(boardBitmap);

    for (int z = 0; z < gridSize.z; z++)
        for (int x = 0; x < gridSize.x; x++)
            for (int y = 0; y < gridSize.y; y++) {
                CardT c = layout[z][x][y];
                if (c >= 0)
                    drawTile(dc, c, fromGrid(x, y), z);
            }

    dc.SelectObject(wxNullBitmap);

    wxMask* mask = new wxMask(boardBitmap, wxColor(0x00, 0x00, 0x00));
    boardBitmap.SetMask(mask);
}

void Drawer::drawTile(wxDC& dc, int8_t index, const wxPoint& position,
                      uint8_t zIndex) const {
    wxBrush _bgColor = dc.GetBrush();

    wxBrush front = wxColor(0xff, 0xff, 0xff);
    wxBrush back = wxColor(0xc8, 0xc8, 0xc8);

    if (position == fromGrid({marked.x, marked.y}) && marked.z == zIndex) {
        front = wxColor(0xc8, 0xff, 0xc8);
        back = wxColor(0xbe, 0xdc, 0xbe);
    }

    dc.SetBrush(back);

    dc.DrawRoundedRectangle(position.x + (tilePixelSize.GetWidth() / TILE_WIDTH * TILE_PADDING_SCALE) -
                                (tilePixelSize.GetWidth() / TILE_WIDTH * TILE_PADDING_SCALE) * zIndex,
                            position.y + (tilePixelSize.GetHeight() / TILE_WIDTH * TILE_PADDING_SCALE) -
                                (tilePixelSize.GetHeight() / TILE_WIDTH * TILE_PADDING_SCALE) * zIndex,
                            tilePixelSize.GetWidth() * 2,
                            tilePixelSize.GetHeight() * 2, (tilePixelSize.GetHeight() / TILE_HEIGHT * TILE_PADDING_SCALE));

    dc.SetBrush(front);

    dc.DrawRoundedRectangle(
        position.x - (tilePixelSize.GetWidth() / TILE_WIDTH * TILE_PADDING_SCALE) * zIndex,
        position.y - (tilePixelSize.GetHeight() / TILE_WIDTH * TILE_PADDING_SCALE) * zIndex,
        tilePixelSize.GetWidth() * 2, tilePixelSize.GetHeight() * 2, (tilePixelSize.GetHeight() / TILE_HEIGHT * TILE_PADDING_SCALE));

    dc.SetBrush(_bgColor);

    if (tileImages[index].IsOk()) {
        wxPoint pos;
        pos.x = position.x + (tilePixelSize.GetWidth() / TILE_WIDTH * TILE_PADDING_SCALE) - (tilePixelSize.GetWidth() / TILE_WIDTH * TILE_PADDING_SCALE) * zIndex;
        pos.y = position.y + (tilePixelSize.GetHeight() / TILE_HEIGHT * TILE_PADDING_SCALE) - (tilePixelSize.GetHeight() / TILE_WIDTH * TILE_PADDING_SCALE) * zIndex;

        if (tileImages[index].GetWidth() != tilePixelSize.x * 2 - tilePixelSize.GetWidth() / TILE_WIDTH * TILE_PADDING_SCALE * 2)
            dc.DrawBitmap(tileImages[index].Scale(tilePixelSize.x * 2 - tilePixelSize.GetWidth() / TILE_WIDTH * TILE_PADDING_SCALE * 2,
                                                  tilePixelSize.y * 2 - tilePixelSize.GetHeight() / TILE_HEIGHT * TILE_PADDING_SCALE * 2),
                          pos);
        else
            dc.DrawBitmap(tileImages[index], pos);
    }
}

void Drawer::resizeBg(const wxSize& resolution) {
    if (this->resolution != resolution) {
        this->resolution = resolution;
        composeBG();
    }
}

/**
 * Resizes tile and whole board bitmap size to the resolution, set in this
 * instance
 */
bool Drawer::resizeBoard(const TLVec& layout, const Dimensions& gridSize, bool force) {
    bool res = false;

    const int gridPoint = mmin(
        resolution.x / (gridSize.x * TILE_WIDTH + gridSize.z * TILE_PADDING_SCALE),
        resolution.y * TILE_WIDTH /
            (gridSize.y * TILE_HEIGHT * TILE_WIDTH + TILE_HEIGHT * gridSize.z * TILE_PADDING_SCALE));

    wxLogDebug(wxString::Format("Resize board: %i", gridPoint));

    if (gridPoint != prevGridPoint || force) {
        tilePixelSize.Set(gridPoint * TILE_WIDTH, gridPoint * TILE_HEIGHT);

        boardPadding.x = (tilePixelSize.x / TILE_WIDTH * TILE_PADDING_SCALE) * (gridSize.z - 1); // Смещение, создаваемое самыми левыми картами на верхних позициях (их может и не быть, но проверять это дорого)
        boardPadding.y = (tilePixelSize.y / TILE_WIDTH * TILE_PADDING_SCALE) * (gridSize.z - 1); // Смещение, создаваемое самыми верхними (в плоскости xy) картами на верхних позициях (их может и не быть, но проверять это дорого)

        tablePixelRect.SetSize(
            wxSize((tilePixelSize.x * gridSize.x) + // Размер только плоских карт
             boardPadding.x + // см. выше
             (tilePixelSize.x / TILE_WIDTH * TILE_PADDING_SCALE), //  Смещение, даваемое подложками самых правых
             (tilePixelSize.y * gridSize.y) + // Размер только плоских карт
             boardPadding.y + // см. выше
             (tilePixelSize.y / TILE_WIDTH * TILE_PADDING_SCALE) //  Смещение, даваемое подложками самых нижних (в плоскости xy)
            )
        );
    }

    tablePixelRect.SetPosition({(resolution.x - tablePixelRect.width) / 2,
                                (resolution.y - tablePixelRect.height) / 2});

    if (gridPoint != prevGridPoint || force) {
        composeBoard(layout, gridSize);
        res = true;
    }

    prevGridPoint = gridPoint;

    return res;
}

wxPoint Drawer::toGrid(wxPoint point) const {
    wxPoint out(-1, -1);

    point.x -= boardPadding.x;
    point.y -= boardPadding.y;

    if (point.x >= tablePixelRect.x &&
        point.x <= tablePixelRect.x + tablePixelRect.width &&
        point.y >= tablePixelRect.y &&
        point.y <= tablePixelRect.y + tablePixelRect.height) {
        out.x = (point.x - tablePixelRect.x) / tilePixelSize.x;
        out.y = (point.y - tablePixelRect.y) / tilePixelSize.y;
    }

    return out;
}

wxPoint Drawer::fromGrid(int x, int y) const {
    return {x * tilePixelSize.x + boardPadding.x, y * tilePixelSize.y + boardPadding.y};
}

wxPoint Drawer::fromGrid(const wxPoint& point) const {
    return fromGrid(point.x, point.y);
}

wxSize Drawer::composeMinSize(const Dimensions& gridSize) {
    wxSize ms;

    ms.SetWidth(TILE_WIDTH * gridSize.x + gridSize.z * TILE_PADDING_SCALE);
    ms.SetHeight(TILE_HEIGHT * gridSize.y + gridSize.z * TILE_PADDING_SCALE * TILE_HEIGHT / TILE_WIDTH);

    ms += {1, 1};

    wxLogDebug(wxString::Format("MinSize %i %i", ms.x, ms.y));

    return ms;
}
