/****************************************************************************
Copyright 2016  Martin Gräßlin <mgraesslin@kde.org>

This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) version 3, or any
later version accepted by the membership of KDE e.V. (or its
successor approved by the membership of KDE e.V.), which shall
act as a proxy defined in Section 6 of version 3 of the license.

This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
Lesser General Public License for more details.

You should have received a copy of the GNU Lesser General Public
License along with this library.  If not, see <http://www.gnu.org/licenses/>.
****************************************************************************/
#include "textinput_p.h"
#include "event_queue.h"
#include "seat.h"
#include "surface.h"
#include "wayland_pointer_p.h"

#include <wayland-text-input-v2-client-protocol.h>

namespace KWayland
{
namespace Client
{

class TextInputUnstableV2::Private : public TextInput::Private
{
public:
    Private(TextInputUnstableV2 *q, Seat *seat);

    void setup(zwp_text_input_v2 *textinputmanagerunstablev0);

    bool isValid() const override;
    void enable(Surface *surface) override;
    void disable(Surface * surface) override;
    void showInputPanel() override;
    void hideInputPanel() override;
    void setCursorRectangle(const QRect &rect) override;
    void setPreferredLanguage(const QString &lang) override;
    void setSurroundingText(const QString &text, quint32 cursor, quint32 anchor) override;
    void reset() override;
    void setContentType(ContentHints hint, ContentPurpose purpose) override;

    WaylandPointer<zwp_text_input_v2, zwp_text_input_v2_destroy> textinputunstablev2;

private:
    static void enterCallback(void *data, zwp_text_input_v2 *zwp_text_input_v2, uint32_t serial, wl_surface *surface);
    static void leaveCallback(void *data, zwp_text_input_v2 *zwp_text_input_v2, uint32_t serial, wl_surface *surface);
    static void inputPanelStateCallback(void *data, zwp_text_input_v2 *zwp_text_input_v2, uint32_t state, int32_t x, int32_t y, int32_t width, int32_t height);
    static void preeditStringCallback(void *data, zwp_text_input_v2 *zwp_text_input_v2, const char *text, const char *commit);
    static void preeditStylingCallback(void *data, zwp_text_input_v2 *zwp_text_input_v2, uint32_t index, uint32_t length, uint32_t style);
    static void preeditCursorCallback(void *data, zwp_text_input_v2 *zwp_text_input_v2, int32_t index);
    static void commitStringCallback(void *data, zwp_text_input_v2 *zwp_text_input_v2, const char *text);
    static void cursorPositionCallback(void *data, zwp_text_input_v2 *zwp_text_input_v2, int32_t index, int32_t anchor);
    static void deleteSurroundingTextCallback(void *data, zwp_text_input_v2 *zwp_text_input_v2, uint32_t before_length, uint32_t after_length);
    static void modifiersMapCallback(void *data, zwp_text_input_v2 *zwp_text_input_v2, wl_array *map);
    static void keysymCallback(void *data, zwp_text_input_v2 *zwp_text_input_v2, uint32_t time, uint32_t sym, uint32_t state, uint32_t modifiers);
    static void languageCallback(void *data, zwp_text_input_v2 *zwp_text_input_v2, const char *language);
    static void textDirectionCallback(void *data, zwp_text_input_v2 *zwp_text_input_v2, uint32_t direction);
    static void configureSurroundingTextCallback(void *data, zwp_text_input_v2 *zwp_text_input_v2, int32_t before_cursor, int32_t after_cursor);
    static void inputMethodChangedCallback(void *data, zwp_text_input_v2 *zwp_text_input_v2, uint32_t serial, uint32_t flags);

    TextInputUnstableV2 *q;

    static const zwp_text_input_v2_listener s_listener;
};

const zwp_text_input_v2_listener TextInputUnstableV2::Private::s_listener = {
    enterCallback,
    leaveCallback,
    inputPanelStateCallback,
    preeditStringCallback,
    preeditStylingCallback,
    preeditCursorCallback,
    commitStringCallback,
    cursorPositionCallback,
    deleteSurroundingTextCallback,
    modifiersMapCallback,
    keysymCallback,
    languageCallback,
    textDirectionCallback,
    configureSurroundingTextCallback,
    inputMethodChangedCallback
};

void TextInputUnstableV2::Private::enterCallback(void *data, zwp_text_input_v2 *zwp_text_input_v2, uint32_t serial, wl_surface *surface)
{
    auto t = reinterpret_cast<TextInputUnstableV2::Private*>(data);
    Q_ASSERT(t->textinputunstablev2 == zwp_text_input_v2);
    t->latestSerial = serial;
    t->enteredSurface = Surface::get(surface);
    emit t->q->entered();
}

void TextInputUnstableV2::Private::leaveCallback(void *data, zwp_text_input_v2 *zwp_text_input_v2, uint32_t serial, wl_surface *surface)
{
    Q_UNUSED(surface)
    auto t = reinterpret_cast<TextInputUnstableV2::Private*>(data);
    Q_ASSERT(t->textinputunstablev2 == zwp_text_input_v2);
    t->enteredSurface = nullptr;
    t->latestSerial = serial;
    emit t->q->left();
}

void TextInputUnstableV2::Private::inputPanelStateCallback(void *data, zwp_text_input_v2 *zwp_text_input_v2, uint32_t state, int32_t x, int32_t y, int32_t width, int32_t height)
{
    Q_UNUSED(x)
    Q_UNUSED(y)
    Q_UNUSED(width)
    Q_UNUSED(height)
    auto t = reinterpret_cast<TextInputUnstableV2::Private*>(data);
    Q_ASSERT(t->textinputunstablev2 == zwp_text_input_v2);
    // TODO: add rect
    if (t->inputPanelVisible != state) {
        t->inputPanelVisible = state;
        emit t->q->inputPanelStateChanged();
    }
}

void TextInputUnstableV2::Private::preeditStringCallback(void *data, zwp_text_input_v2 *zwp_text_input_v2, const char *text, const char *commit)
{
    auto t = reinterpret_cast<TextInputUnstableV2::Private*>(data);
    Q_ASSERT(t->textinputunstablev2 == zwp_text_input_v2);
    t->pendingPreEdit.commitText = QByteArray(commit);
    t->pendingPreEdit.text = QByteArray(text);
    if (!t->pendingPreEdit.cursorSet) {
        t->pendingPreEdit.cursor = t->pendingPreEdit.text.length();
    }
    t->currentPreEdit = t->pendingPreEdit;
    t->pendingPreEdit = TextInput::Private::PreEdit();
    emit t->q->composingTextChanged();
}

void TextInputUnstableV2::Private::preeditStylingCallback(void *data, zwp_text_input_v2 *zwp_text_input_v2, uint32_t index, uint32_t length, uint32_t style)
{
    Q_UNUSED(index)
    Q_UNUSED(length)
    Q_UNUSED(style)
    // TODO: implement
    auto t = reinterpret_cast<TextInputUnstableV2::Private*>(data);
    Q_ASSERT(t->textinputunstablev2 == zwp_text_input_v2);
}

void TextInputUnstableV2::Private::preeditCursorCallback(void *data, zwp_text_input_v2 *zwp_text_input_v2, int32_t index)
{
    auto t = reinterpret_cast<TextInputUnstableV2::Private*>(data);
    Q_ASSERT(t->textinputunstablev2 == zwp_text_input_v2);
    t->pendingPreEdit.cursor = index;
    t->pendingPreEdit.cursorSet = true;
}

void TextInputUnstableV2::Private::commitStringCallback(void *data, zwp_text_input_v2 *zwp_text_input_v2, const char *text)
{
    auto t = reinterpret_cast<TextInputUnstableV2::Private*>(data);
    Q_ASSERT(t->textinputunstablev2 == zwp_text_input_v2);
    t->pendingCommit.text = QByteArray(text);
    t->currentCommit = t->pendingCommit;
    // TODO: what are the proper values it should be set to?
    t->pendingCommit = TextInput::Private::Commit();
    t->pendingCommit.deleteSurrounding.beforeLength = 0;
    t->pendingCommit.deleteSurrounding.afterLength = 0;
    emit t->q->committed();
}

void TextInputUnstableV2::Private::cursorPositionCallback(void *data, zwp_text_input_v2 *zwp_text_input_v2, int32_t index, int32_t anchor)
{
    auto t = reinterpret_cast<TextInputUnstableV2::Private*>(data);
    Q_ASSERT(t->textinputunstablev2 == zwp_text_input_v2);
    t->pendingCommit.cursor = index;
    t->pendingCommit.anchor = anchor;
}

void TextInputUnstableV2::Private::deleteSurroundingTextCallback(void *data, zwp_text_input_v2 *zwp_text_input_v2, uint32_t before_length, uint32_t after_length)
{
    auto t = reinterpret_cast<TextInputUnstableV2::Private*>(data);
    Q_ASSERT(t->textinputunstablev2 == zwp_text_input_v2);
    t->pendingCommit.deleteSurrounding.beforeLength = before_length;
    t->pendingCommit.deleteSurrounding.afterLength = after_length;
}

void TextInputUnstableV2::Private::modifiersMapCallback(void *data, zwp_text_input_v2 *zwp_text_input_v2, wl_array *map)
{
    // TODO: implement
    Q_UNUSED(map)
    auto t = reinterpret_cast<TextInputUnstableV2::Private*>(data);
    Q_ASSERT(t->textinputunstablev2 == zwp_text_input_v2);
}

void TextInputUnstableV2::Private::keysymCallback(void *data, zwp_text_input_v2 *zwp_text_input_v2, uint32_t time, uint32_t sym, uint32_t wlState, uint32_t modifiers)
{
    // TODO: add support for modifiers
    Q_UNUSED(modifiers)
    auto t = reinterpret_cast<TextInputUnstableV2::Private*>(data);
    Q_ASSERT(t->textinputunstablev2 == zwp_text_input_v2);
    TextInput::KeyState state;
    switch (wlState) {
    case WL_KEYBOARD_KEY_STATE_RELEASED:
        state = TextInput::KeyState::Released;
        break;
    case WL_KEYBOARD_KEY_STATE_PRESSED:
        state = TextInput::KeyState::Pressed;
        break;
    default:
        // invalid
        return;
    }
    emit t->q->keyEvent(sym, state, Qt::KeyboardModifiers(), time);
}

void TextInputUnstableV2::Private::languageCallback(void *data, zwp_text_input_v2 *zwp_text_input_v2, const char *language)
{
    auto t = reinterpret_cast<TextInputUnstableV2::Private*>(data);
    Q_ASSERT(t->textinputunstablev2 == zwp_text_input_v2);
    if (qstrcmp(t->language, language) != 0) {
        t->language = QByteArray(language);
        emit t->q->languageChanged();
    }
}

void TextInputUnstableV2::Private::textDirectionCallback(void *data, zwp_text_input_v2 *zwp_text_input_v2, uint32_t wlDirection)
{
    auto t = reinterpret_cast<TextInputUnstableV2::Private*>(data);
    Q_ASSERT(t->textinputunstablev2 == zwp_text_input_v2);
    Qt::LayoutDirection direction;
    switch (wlDirection) {
    case ZWP_TEXT_INPUT_V2_TEXT_DIRECTION_LTR:
        direction = Qt::LeftToRight;
        break;
    case ZWP_TEXT_INPUT_V2_TEXT_DIRECTION_RTL:
        direction = Qt::RightToLeft;
        break;
    case ZWP_TEXT_INPUT_V2_TEXT_DIRECTION_AUTO:
        direction = Qt::LayoutDirectionAuto;
        break;
    default:
        // invalid
        return;
    }
    if (direction != t->textDirection) {
        t->textDirection = direction;
        emit t->q->textDirectionChanged();
    }
}

void TextInputUnstableV2::Private::configureSurroundingTextCallback(void *data, zwp_text_input_v2 *zwp_text_input_v2, int32_t before_cursor, int32_t after_cursor)
{
    // TODO: implement
    Q_UNUSED(before_cursor)
    Q_UNUSED(after_cursor)
    auto t = reinterpret_cast<TextInputUnstableV2::Private*>(data);
    Q_ASSERT(t->textinputunstablev2 == zwp_text_input_v2);
}

void TextInputUnstableV2::Private::inputMethodChangedCallback(void *data, zwp_text_input_v2 *zwp_text_input_v2, uint32_t serial, uint32_t flags)
{
    Q_UNUSED(serial)
    Q_UNUSED(flags)
    // TODO: implement
    auto t = reinterpret_cast<TextInputUnstableV2::Private*>(data);
    Q_ASSERT(t->textinputunstablev2 == zwp_text_input_v2);
}

TextInputUnstableV2::Private::Private(TextInputUnstableV2 *q, Seat *seat)
    : TextInput::Private(seat)
    , q(q)
{
}

void TextInputUnstableV2::Private::setup(zwp_text_input_v2 *ti)
{
    Q_ASSERT(ti);
    Q_ASSERT(!textinputunstablev2);
    textinputunstablev2.setup(ti);
    zwp_text_input_v2_add_listener(ti, &s_listener, this);
}

bool TextInputUnstableV2::Private::isValid() const
{
    return textinputunstablev2.isValid();
}

void TextInputUnstableV2::Private::enable(Surface *surface)
{
    zwp_text_input_v2_enable(textinputunstablev2, *surface);
}

void TextInputUnstableV2::Private::disable(Surface * surface)
{
    zwp_text_input_v2_disable(textinputunstablev2, *surface);
}

void TextInputUnstableV2::Private::showInputPanel()
{
    zwp_text_input_v2_show_input_panel(textinputunstablev2);
}

void TextInputUnstableV2::Private::hideInputPanel()
{
    zwp_text_input_v2_hide_input_panel(textinputunstablev2);
}

void TextInputUnstableV2::Private::setCursorRectangle(const QRect &rect)
{
    zwp_text_input_v2_set_cursor_rectangle(textinputunstablev2, rect.x(), rect.y(), rect.width(), rect.height());
}

void TextInputUnstableV2::Private::setPreferredLanguage(const QString &lang)
{
    zwp_text_input_v2_set_preferred_language(textinputunstablev2, lang.toUtf8().constData());
}

void TextInputUnstableV2::Private::setSurroundingText(const QString &text, quint32 cursor, quint32 anchor)
{
    zwp_text_input_v2_set_surrounding_text(textinputunstablev2, text.toUtf8().constData(),
                                           text.leftRef(cursor).toUtf8().length(),
                                           text.leftRef(anchor).toUtf8().length());
}

void TextInputUnstableV2::Private::reset()
{
    zwp_text_input_v2_update_state(textinputunstablev2, latestSerial, ZWP_TEXT_INPUT_V2_UPDATE_STATE_RESET);
}

void TextInputUnstableV2::Private::setContentType(ContentHints hints, ContentPurpose purpose)
{
    uint32_t wlHints = 0;
    uint32_t wlPurpose = 0;
    if (hints.testFlag(ContentHint::AutoCompletion)) {
        wlHints |= ZWP_TEXT_INPUT_V2_CONTENT_HINT_AUTO_COMPLETION;
    }
    if (hints.testFlag(ContentHint::AutoCorrection)) {
        wlHints |= ZWP_TEXT_INPUT_V2_CONTENT_HINT_AUTO_CORRECTION;
    }
    if (hints.testFlag(ContentHint::AutoCapitalization)) {
        wlHints |= ZWP_TEXT_INPUT_V2_CONTENT_HINT_AUTO_CAPITALIZATION;
    }
    if (hints.testFlag(ContentHint::LowerCase)) {
        wlHints |= ZWP_TEXT_INPUT_V2_CONTENT_HINT_LOWERCASE;
    }
    if (hints.testFlag(ContentHint::UpperCase)) {
        wlHints |= ZWP_TEXT_INPUT_V2_CONTENT_HINT_UPPERCASE;
    }
    if (hints.testFlag(ContentHint::TitleCase)) {
        wlHints |= ZWP_TEXT_INPUT_V2_CONTENT_HINT_TITLECASE;
    }
    if (hints.testFlag(ContentHint::HiddenText)) {
        wlHints |= ZWP_TEXT_INPUT_V2_CONTENT_HINT_HIDDEN_TEXT;
    }
    if (hints.testFlag(ContentHint::SensitiveData)) {
        wlHints |= ZWP_TEXT_INPUT_V2_CONTENT_HINT_SENSITIVE_DATA;
    }
    if (hints.testFlag(ContentHint::Latin)) {
        wlHints |= ZWP_TEXT_INPUT_V2_CONTENT_HINT_LATIN;
    }
    if (hints.testFlag(ContentHint::MultiLine)) {
        wlHints |= ZWP_TEXT_INPUT_V2_CONTENT_HINT_MULTILINE;
    }
    switch (purpose) {
    case ContentPurpose::Normal:
        wlPurpose = ZWP_TEXT_INPUT_V2_CONTENT_PURPOSE_NORMAL;
        break;
    case ContentPurpose::Alpha:
        wlPurpose = ZWP_TEXT_INPUT_V2_CONTENT_PURPOSE_ALPHA;
        break;
    case ContentPurpose::Digits:
        wlPurpose = ZWP_TEXT_INPUT_V2_CONTENT_PURPOSE_DIGITS;
        break;
    case ContentPurpose::Number:
        wlPurpose = ZWP_TEXT_INPUT_V2_CONTENT_PURPOSE_NUMBER;
        break;
    case ContentPurpose::Phone:
        wlPurpose = ZWP_TEXT_INPUT_V2_CONTENT_PURPOSE_PHONE;
        break;
    case ContentPurpose::Url:
        wlPurpose = ZWP_TEXT_INPUT_V2_CONTENT_PURPOSE_URL;
        break;
    case ContentPurpose::Email:
        wlPurpose = ZWP_TEXT_INPUT_V2_CONTENT_PURPOSE_EMAIL;
        break;
    case ContentPurpose::Name:
        wlPurpose = ZWP_TEXT_INPUT_V2_CONTENT_PURPOSE_NAME;
        break;
    case ContentPurpose::Password:
        wlPurpose = ZWP_TEXT_INPUT_V2_CONTENT_PURPOSE_PASSWORD;
        break;
    case ContentPurpose::Date:
        wlPurpose = ZWP_TEXT_INPUT_V2_CONTENT_PURPOSE_DATE;
        break;
    case ContentPurpose::Time:
        wlPurpose = ZWP_TEXT_INPUT_V2_CONTENT_PURPOSE_TIME;
        break;
    case ContentPurpose::DateTime:
        wlPurpose = ZWP_TEXT_INPUT_V2_CONTENT_PURPOSE_DATETIME;
        break;
    case ContentPurpose::Terminal:
        wlPurpose = ZWP_TEXT_INPUT_V2_CONTENT_PURPOSE_TERMINAL;
        break;
    }
    zwp_text_input_v2_set_content_type(textinputunstablev2, wlHints, wlPurpose);
}

TextInputUnstableV2::TextInputUnstableV2(Seat *seat, QObject *parent)
    : TextInput(new Private(this, seat), parent)
{
}

TextInputUnstableV2::~TextInputUnstableV2()
{
    release();
}

TextInputUnstableV2::Private *TextInputUnstableV2::d_func() const
{
    return reinterpret_cast<Private*>(d.data());
}

void TextInputUnstableV2::setup(zwp_text_input_v2 *textinputunstablev2)
{
    Q_D();
    d->setup(textinputunstablev2);
}

void TextInputUnstableV2::release()
{
    Q_D();
    d->textinputunstablev2.release();
}

void TextInputUnstableV2::destroy()
{
    Q_D();
    d->textinputunstablev2.destroy();
}

TextInputUnstableV2::operator zwp_text_input_v2*()
{
    Q_D();
    return d->textinputunstablev2;
}

TextInputUnstableV2::operator zwp_text_input_v2*() const
{
    Q_D();
    return d->textinputunstablev2;
}

class TextInputManagerUnstableV2::Private : public TextInputManager::Private
{
public:
    Private() = default;

    void release() override;
    void destroy() override;
    bool isValid() override;
    void setupV2(zwp_text_input_manager_v2 *ti) override;
    TextInput *createTextInput(Seat *seat, QObject *parent = nullptr) override;
    using TextInputManager::Private::operator wl_text_input_manager*;
    operator zwp_text_input_manager_v2*() override {
        return textinputmanagerunstablev2;
    }
    operator zwp_text_input_manager_v2*() const override {
        return textinputmanagerunstablev2;
    }

    WaylandPointer<zwp_text_input_manager_v2, zwp_text_input_manager_v2_destroy> textinputmanagerunstablev2;
};

void TextInputManagerUnstableV2::Private::release()
{
    textinputmanagerunstablev2.release();
}

void TextInputManagerUnstableV2::Private::destroy()
{
    textinputmanagerunstablev2.destroy();
}

bool TextInputManagerUnstableV2::Private::isValid()
{
    return textinputmanagerunstablev2.isValid();
}

TextInputManagerUnstableV2::TextInputManagerUnstableV2(QObject *parent)
    : TextInputManager(new Private, parent)
{
}

TextInputManagerUnstableV2::~TextInputManagerUnstableV2() = default;

void TextInputManagerUnstableV2::Private::setupV2(zwp_text_input_manager_v2 *ti)
{
    Q_ASSERT(ti);
    Q_ASSERT(!textinputmanagerunstablev2);
    textinputmanagerunstablev2.setup(ti);
}

TextInput *TextInputManagerUnstableV2::Private::createTextInput(Seat *seat, QObject *parent)
{
    Q_ASSERT(isValid());
    TextInputUnstableV2 *t = new TextInputUnstableV2(seat, parent);
    auto w = zwp_text_input_manager_v2_get_text_input(textinputmanagerunstablev2, *seat);
    if (queue) {
        queue->addProxy(w);
    }
    t->setup(w);
    return t;
}

}
}
