#include <Log/Log.hpp>
#include <Sound/Sound.hpp>
#include <Widget/TextField/TextField.hpp>
#include <Utilities/Clipboard.hpp>

void TextField::insert(std::string insertion)
{
    std::string left = text.substr(0, selectStart);
    std::string right = text.substr(selectEnd);

    text = left + insertion + right;

    selectStart += insertion.size();
    selectEnd = selectStart;

    onChanged();
}

void TextField::backspace()
{
    if (selectStart != selectEnd)
    {
        std::string left = text.substr(0, selectStart);
        std::string right = text.substr(selectEnd);

        text = left + right;
        onChanged();
    }
    else if (selectStart != 0)
    {
        size_t prevPos = selectStart;
        utf8move(text, prevPos, -1);

        std::string left = text.substr(0, prevPos);
        std::string right = text.substr(selectStart);

        text = left + right;

        selectStart = prevPos;
        onChanged();
    }

    selectEnd = selectStart;
}

size_t TextField::getPosByX(int x)
{
    size_t resultingOffset = text.size();
    x -= 2;

    GraphicsContext::layoutText(
        Font::generic,
        text,
        [](glm::vec4){},
        [this, &resultingOffset, x](Font::Sheet &sheet, glm::ivec2 pos, glm::ivec2 sheetBase, glm::ivec2 size, size_t offset)
        {
            if (pos.x <= x && pos.x + size.x >= x)
            {
                int offsetIntoGlyph = x - pos.x;
                
                if (offsetIntoGlyph < (size.x / 2))
                {
                    resultingOffset = offset;
                }
                else
                {
                    resultingOffset = offset;
                    utf8move(text, resultingOffset, 1);
                }
            }
        },
        masked
    );

    return resultingOffset;
}

void TextField::updateFromShiftDrag(const ShiftDragStatus &status)
{
    selectStart = std::min(status.anchor, status.cursor);
    selectEnd = std::max(status.anchor, status.cursor);
}

std::string TextField::getSelectedText()
{
    return text.substr(selectStart, selectEnd - selectStart);
}

TextField::TextField(std::string &text, std::function<void()> onAccept, bool masked) :
    selectStart(text.size()),
    selectEnd(text.size()),
    text(text),
    onAccept(onAccept),
    masked(masked)
{}

TextField* TextField::withPlaceholder(std::string placeholder)
{
    this->placeholder = placeholder;
    return this;
}

TextField* TextField::withChangedCallback(const ChangedCallback &callback)
{
    onChanged = callback;
    return this;
}

TextField* TextField::withMinWidth(int width)
{
    minWidth = width;
    return this;
}

TextField* TextField::withAutoSelect()
{
    autoSelect = true;
    return this;
}

void TextField::selectAll()
{
    selectStart = 0;
    selectEnd = text.size();
}

void TextField::draw(GraphicsContext &context)
{
    if (isMouseInside())
    {
        Widget::setCursor(Material::textCursor, glm::ivec2(2, 4));
    }

    glm::vec4 targetBorder = isFocused() ? glm::vec4(1.0f) : Color::faded;
    context.color = borderLerp.sample(targetBorder);
    context.drawRect(0, 0, m_size[0], m_size[1]);
    context.color = glm::ivec4(0.0f, 0.0f, 0.0f, 1.0f);
    context.drawRect(1, 1, m_size[0]-2, m_size[1]-2);

    int selectStartX = 2 + context.measureText(Font::generic, text.substr(0, selectStart), masked).x;
    int selectEndX = 2 + context.measureText(Font::generic, text.substr(0, selectEnd), masked).x;

    if (selectStart != selectEnd)
    {
        context.color = Color::selection;
        context.drawRect(
            selectStartX, 2,
            selectEndX - selectStartX + 1, m_size.y-4
        );
    }

    context.color = glm::vec4(1.0f);
    context.drawText(Font::generic, 2, (m_size[1] - 7)/2, text, masked);

    if (text.size() == 0)
    {
        context.color = Color::faded;
        context.drawText(Font::generic, 2, (m_size[1] - 7)/2, placeholder, false);
    }

    bool isInVisiblePeriod = (((SDL_GetTicks() - blinkStartTime) / cursorBlinkPeriod) % 2) == 0;
    if (isFocused() && selectStart == selectEnd && isInVisiblePeriod)
    {
        context.color = Color::textInput;
        context.drawRect(selectStartX, 2, 1, m_size.y-4);
    }
}

glm::ivec2 TextField::getMinSize()
{
    return glm::ivec2(minWidth, 13);
}

void TextField::onKeyDown(SDL_Keycode keycode)
{
    const bool* state = SDL_GetKeyboardState(nullptr);
    bool ctrlPressed = state[SDL_SCANCODE_LCTRL] || state[SDL_SCANCODE_RCTRL];
    bool shiftPressed = state[SDL_SCANCODE_LSHIFT] || state[SDL_SCANCODE_RSHIFT];

    if (keycode == SDLK_BACKSPACE)
    {
        if (text.size() != 0)
        {
            backspace();
        }
    }
    else if (keycode == SDLK_RETURN)
    {
        onAccept();
    }
    else if (shiftPressed && keycode == SDLK_LEFT)
    {
        if (!shiftDragStatus.has_value())
        {
            shiftDragStatus = {selectStart, selectStart};
        }

        ShiftDragStatus &status = *shiftDragStatus;
        utf8move(text, status.cursor, -1);
        updateFromShiftDrag(status);
    }
    else if (shiftPressed && keycode == SDLK_RIGHT)
    {
        if (!shiftDragStatus.has_value())
        {
            shiftDragStatus = {selectEnd, selectEnd};
        }

        ShiftDragStatus &status = *shiftDragStatus;
        utf8move(text, status.cursor, 1);
        updateFromShiftDrag(status);
    }
    else if (keycode == SDLK_LEFT)
    {
        if (selectStart == selectEnd && selectStart != 0)
        {
            utf8move(text, selectStart, -1);
        }

        selectEnd = selectStart;
    }
    else if (keycode == SDLK_RIGHT)
    {
        if (selectStart == selectEnd && selectEnd != text.size())
        {
            utf8move(text, selectEnd, 1);
        }

        selectStart = selectEnd;
    }
    else if (ctrlPressed && keycode == SDLK_A)
    {
        selectAll();
    }
    else if (ctrlPressed && keycode == SDLK_X)
    {
        if (!masked && selectStart != selectEnd)
        {
            Clipboard::put(getSelectedText());
            backspace();
        }
    }
    else if (ctrlPressed && keycode == SDLK_C)
    {
        if (!masked && selectStart != selectEnd)
        {
            Clipboard::put(getSelectedText());
        }
    }
    else if (ctrlPressed && keycode == SDLK_V)
    {
        if (Clipboard::hasText())
        {
            insert(Clipboard::get());
        }
    }
}

void TextField::onKeyUp(SDL_Keycode keycode)
{
    Widget::onKeyUp(keycode);
    
    if (keycode == SDLK_LSHIFT || keycode == SDLK_RSHIFT)
    {
        shiftDragStatus = std::nullopt;
    }
}

bool TextField::isTextInput()
{
    return true;
}

void TextField::onTextInput(std::string text)
{
    insert(text);
}

void TextField::onMouseDown(Uint8 button)
{
    if (isMouseInside())
    {
        mouseMovedSinceClick = isFocused();
        Widget::onMouseDown(button);
        Sound::select.play();
        blinkStartTime = SDL_GetTicks();

        selectAnchor = selectStart = selectEnd = getPosByX(m_mousePos.x);
    }
}

void TextField::onMouseUp(Uint8 button)
{
    selectAnchor = std::nullopt;
    if (isMouseInside() && !mouseMovedSinceClick && autoSelect)
    {
        selectStart = 0;
        selectEnd = text.size();
    }

    Widget::onMouseUp(button);
}

void TextField::onMouseMoved(glm::ivec2 pos)
{
    Widget::onMouseMoved(pos);

    if (selectAnchor.has_value())
    {
        mouseMovedSinceClick = true;
        size_t cursorPos = getPosByX(pos.x);

        selectStart = std::min(cursorPos, *selectAnchor);
        selectEnd = std::max(cursorPos, *selectAnchor);
    }
}

void TextField::getInternalTabOrder(std::vector<Widget*> &tabOrder)
{
    tabOrder.push_back(this);
}