// Copyright 2021 Google LLC.
#include "experimental/sktext/editor/Editor.h"
#include "experimental/sktext/src/Paint.h"

using namespace skia::text;

namespace skia {
namespace editor {

std::unique_ptr<Editor> Editor::Make(std::u16string text, SkSize size) {
    return std::make_unique<Editor>(text, size);
}

Editor::Editor(std::u16string text, SkSize size)
        : fDefaultPositionType(PositionType::kGraphemeCluster)
        , fInsertMode(true) {

    fParent = nullptr;
    fCursor = Cursor::Make();
    fMouse = std::make_unique<Mouse>();
    {
        SkPaint foreground; foreground.setColor(DEFAULT_TEXT_FOREGROUND);
        SkPaint background; background.setColor(DEFAULT_TEXT_BACKGROUND);
        static FontBlock textBlock(text.size(), sk_make_sp<TrivialFontChain>("Roboto", 40, SkFontStyle::Normal()));
        static DecoratedBlock textDecor(text.size(), foreground, background);
        auto textSize = SkSize::Make(size.width(), size.height() - DEFAULT_STATUS_HEIGHT);
        fEditableText = std::make_unique<EditableText>(
                text, SkPoint::Make(0, 0), textSize,
                SkSpan<FontBlock>(&textBlock, 1), SkSpan<DecoratedBlock>(&textDecor, 1),
                DEFAULT_TEXT_DIRECTION, DEFAULT_TEXT_ALIGN);
    }
    {
        SkPaint foreground; foreground.setColor(DEFAULT_STATUS_FOREGROUND);
        SkPaint background; background.setColor(DEFAULT_STATUS_BACKGROUND);
        std::u16string status = u"This is the status line";
        static FontBlock statusBlock(status.size(), sk_make_sp<TrivialFontChain>("Roboto", 20, SkFontStyle::Normal()));
        static DecoratedBlock statusDecor(status.size(), foreground, background);
        auto statusPoint = SkPoint::Make(0, size.height() - DEFAULT_STATUS_HEIGHT);
        fStatus = std::make_unique<DynamicText>(
                status, statusPoint, SkSize::Make(size.width(), SK_ScalarInfinity),
                SkSpan<FontBlock>(&statusBlock, 1), SkSpan<DecoratedBlock>(&statusDecor, 1),
                        DEFAULT_TEXT_DIRECTION, TextAlign::kCenter);
    }
    // Place the cursor at the end of the output text
    // (which is the end of the text for LTR and the beginning of the text for RTL
    // or possibly something in the middle for a combination of LTR & RTL)
    // In order to get that position we look for a position outside of the text
    // and that will give us the last glyph on the line
    auto endOfText = fEditableText->lastElement(fDefaultPositionType);
    fEditableText->recalculateBoundaries(endOfText);
    fCursor->place(endOfText.fBoundaries);
}

void Editor::update() {

    if (!fEditableText->isInvalidated()) {
        return;
    }

    // Update the (shift it to point at the grapheme edge)
    auto position = fEditableText->adjustedPosition(fDefaultPositionType, fCursor->getCenterPosition());
    fEditableText->recalculateBoundaries(position);
    fCursor->place(position.fBoundaries);

    // TODO: Update the mouse
    fMouse->clearTouchInfo();
}

// Moving the cursor by the output grapheme clusters (shifting to another line if necessary)
// We don't want to move by the input text indexes because then we will have to take in account LTR/RTL
bool Editor::moveCursor(skui::Key key) {
    auto cursorPosition = fCursor->getCenterPosition();
    auto position = fEditableText->adjustedPosition(PositionType::kGraphemeCluster, cursorPosition);
    if (position.fRun == nullptr) {
        return false;
    }
    if (key == skui::Key::kLeft) {
        position = fEditableText->previousElement(position);
    } else if (key == skui::Key::kRight) {
        position = fEditableText->nextElement(position);
    } else if (key == skui::Key::kHome) {
        position = fEditableText->firstElement(PositionType::kGraphemeCluster);
    } else if (key == skui::Key::kEnd) {
        position = fEditableText->lastElement(PositionType::kGraphemeCluster);
    } else if (key == skui::Key::kUp) {
        // Move one line up (if possible)
        if (position.fLineIndex == 0) {
            return false;
        }
        cursorPosition.offset(0, - fEditableText->lineHeight(position.fLineIndex));
        position = fEditableText->adjustedPosition(PositionType::kGraphemeCluster, cursorPosition);
    } else if (key == skui::Key::kDown) {
        // Move one line down (if possible)
        if (position.fLineIndex == fEditableText->lineCount() - 1) {
            return false;
        }
        cursorPosition.offset(0, fEditableText->lineHeight(position.fLineIndex));
        position = fEditableText->adjustedPosition(PositionType::kGraphemeCluster, cursorPosition);
     }

    // Place the cursor at the new position
    fEditableText->recalculateBoundaries(position);
    fCursor->place(position.fBoundaries);
    this->invalidate();

    return true;
}

void Editor::onPaint(SkSurface* surface) {
    SkCanvas* canvas = surface->getCanvas();
    SkAutoCanvasRestore acr(canvas, true);
    canvas->clipRect({0, 0, (float)fWidth, (float)fHeight});
    canvas->drawColor(SK_ColorWHITE);
    this->paint(canvas);
}

void Editor::onResize(int width, int height) {
    if (SkISize{fWidth, fHeight} != SkISize{width, height}) {
        fHeight = height;
        if (width != fWidth) {
            fWidth = width;
        }
        this->invalidate();
    }
}

bool Editor::onChar(SkUnichar c, skui::ModifierKey modi) {
    using sknonstd::Any;

    modi &= ~skui::ModifierKey::kFirstPress;
    if (!Any(modi & (skui::ModifierKey::kControl |
                     skui::ModifierKey::kOption |
                     skui::ModifierKey::kCommand))) {
        if (((unsigned)c < 0x7F && (unsigned)c >= 0x20) || c == 0x000A) {
            insertCodepoint(c);
            return true;
        }
    }
    static constexpr skui::ModifierKey kCommandOrControl =
            skui::ModifierKey::kCommand | skui::ModifierKey::kControl;
    if (Any(modi & kCommandOrControl) && !Any(modi & ~kCommandOrControl)) {
        return false;
    }
    return false;
}

bool Editor::deleteElement(skui::Key key) {

    if (fEditableText->isEmpty()) {
        return false;
    }

    auto cursorPosition = fCursor->getCenterPosition();
    auto position = fEditableText->adjustedPosition(fDefaultPositionType, cursorPosition);
    TextRange textRange = position.fTextRange;

    // IMPORTANT: We assume that a single element (grapheme cluster) does not cross the run boundaries;
    // It's not exactly true but we are going to enforce in by breaking the grapheme by the run boundaries
    if (key == skui::Key::kBack) {
        // Move to the previous element (could be another run and/or line)
        if (position.fLineIndex > 0 && fEditableText->isFirstOnTheLine(position)) {
            auto line = fEditableText->line(position.fLineIndex - 1);
            if (line->isHardLineBreak()) {
                // We remove invisible hard line break from the previous line
                textRange = line->whitespaces();
            } else {
                // We remove the last non-whitespace element on the previous line (ignoring trailing whitespaces)
                position = fEditableText->previousElement(position);
                textRange = position.fTextRange;
            }
        } else {
            // We remove the previous element on the current line
            position = fEditableText->previousElement(position);
            textRange = position.fTextRange;
        }
    }

    fEditableText->removeElement(textRange);

    // Find the grapheme the cursor points to
    position = fEditableText->adjustedPosition(fDefaultPositionType, textRange.fStart);
    fCursor->place(position.fBoundaries);
    this->invalidate();

    return true;
}

bool Editor::insertCodepoint(SkUnichar unichar) {
    auto cursorPosition = fCursor->getCenterPosition();
    auto position = fEditableText->adjustedPosition(fDefaultPositionType, cursorPosition);

    if (fInsertMode) {
        fEditableText->insertElement(unichar, position.fTextRange.fStart);
    } else {
        fEditableText->replaceElement(unichar, position.fTextRange);
    }

    this->update();

    // Find the element the cursor points to
    position = fEditableText->adjustedPosition(fDefaultPositionType, cursorPosition);

    // Move the cursor to the next element
    position = fEditableText->nextElement(position);
    fEditableText->recalculateBoundaries(position);
    fCursor->place(position.fBoundaries);

    this->invalidate();

    return true;
}

bool Editor::onKey(skui::Key key, skui::InputState state, skui::ModifierKey modifiers) {

    if (state != skui::InputState::kDown) {
        return false;
    }
    using sknonstd::Any;
    skui::ModifierKey ctrlAltCmd = modifiers & (skui::ModifierKey::kControl |
                                                skui::ModifierKey::kOption  |
                                                skui::ModifierKey::kCommand);
    //bool shift = Any(modifiers & (skui::ModifierKey::kShift));
    if (!Any(ctrlAltCmd)) {
        // no modifiers
        switch (key) {
            case skui::Key::kLeft:
            case skui::Key::kRight:
            case skui::Key::kUp:
            case skui::Key::kDown:
            case skui::Key::kHome:
            case skui::Key::kEnd:
                this->moveCursor(key);
                break;
            case skui::Key::kDelete:
            case skui::Key::kBack:
                this->deleteElement(key);
                return true;
            case skui::Key::kOK:
                return this->onChar(0x000A, modifiers);
            default:
                break;
        }
    }
    return false;
}

bool Editor::onMouse(int x, int y, skui::InputState state, skui::ModifierKey modifiers) {

    if (!fEditableText->contains(x, y)) {
        // We only support mouse on an editable area
    }
    if (skui::InputState::kDown == state) {
        auto position = fEditableText->adjustedPosition(fDefaultPositionType, SkPoint::Make(x, y));
        if (fMouse->isDoubleClick(SkPoint::Make(x, y))) {
            // Select the element
            fEditableText->select(position.fTextRange, position.fBoundaries);
            position.fBoundaries.fLeft = position.fBoundaries.fRight - DEFAULT_CURSOR_WIDTH;
            // Clear mouse
            fMouse->up();
        } else {
            // Clear selection
            fMouse->down();
            fEditableText->clearSelection();
        }

        fCursor->place(position.fBoundaries);
        this->invalidate();
        return true;
    }
    fMouse->up();
    return false;
}

void Editor::paint(SkCanvas* canvas) {

    fEditableText->paint(canvas);
    fCursor->paint(canvas);

    SkPaint background; background.setColor(DEFAULT_STATUS_BACKGROUND);
    canvas->drawRect(SkRect::MakeXYWH(0, fHeight - DEFAULT_STATUS_HEIGHT, fWidth, DEFAULT_STATUS_HEIGHT), background);
    fStatus->paint(canvas);
}

std::unique_ptr<Editor> Editor::MakeDemo(SkScalar width, SkScalar height) {

    std::u16string text0 = u"In a hole in the ground there lived a hobbit. Not a nasty, dirty, "
                            "wet hole full of worms and oozy smells.\nThis was a hobbit-hole and "
                            "that means good food, a warm hearth, and all the comforts of home.";

    return Editor::Make(text0, SkSize::Make(width, height));
}
} // namespace editor
} // namespace skia
