/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
 *  Copyright (C) 2004 Hiroyuki Ikezoe
 *  Copyright (C) 2004 Takuro Ashie
 *  Copyright (C) 2012 CSSlayer
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2, or (at your option)
 *  any later version.
 *
 *  This program 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 General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.
 */

#include <fcitx/module/xkb/fcitx-xkb.h>
#include "key2kana.h"
#include "factory.h"
#include "imengine.h"
#include "utils.h"

Key2KanaConvertor::Key2KanaConvertor (AnthyInstance    & anthy,
                                      Key2KanaTableSet & tables)
    : m_anthy                   (anthy),
      m_tables                  (tables),
      m_is_in_pseudo_ascii_mode (false)
{
    set_case_sensitive (false);
    set_pseudo_ascii_mode (0);
}

Key2KanaConvertor::~Key2KanaConvertor ()
{
}

bool
Key2KanaConvertor::can_append (const KeyEvent & key,
                               bool             ignore_space)
{
    // ignore key release.
    if (key.is_release)
        return false;

    // ignore short cut keys of apllication.
    if ((key.state & FcitxKeyState_Ctrl) ||
        (key.state & FcitxKeyState_Alt) ||
        (key.state & FcitxKeyState_Super))
    {
        return false;
    }

    if (isprint(key.get_ascii_code ()) &&
        (ignore_space || !isspace(key.get_ascii_code ())))
        return true;

    if (util_key_is_keypad (key))
        return true;

    return false;
}

bool
Key2KanaConvertor::append (const KeyEvent & key,
                           std::string & result,
                           std::string & pending,
                           std::string &raw)
{
    if (!can_append (key))
        return false;

    m_last_key = key;

    util_keypad_to_string (raw, key);

    if (util_key_is_keypad (key)) {
        bool retval = false;
        std::string wide;
        TenKeyType ten_key_type = m_anthy.get_config()->m_ten_key_type;

        // convert key pad string to wide
        if ((ten_key_type == FCITX_ANTHY_TEN_KEY_TYPE_FOLLOWMODE &&
             (m_anthy.get_input_mode () == FCITX_ANTHY_MODE_LATIN ||
              m_anthy.get_input_mode () == FCITX_ANTHY_MODE_HALF_KATAKANA)) ||
            ten_key_type == FCITX_ANTHY_TEN_KEY_TYPE_HALF)
        {
            wide = raw;
        } else {
            util_convert_to_wide (wide, raw);
        }

        // join to previous string
        if (!m_exact_match.is_empty()) {
            if (!m_exact_match.get_result(0).empty() &&
                m_exact_match.get_result(1).empty())
            {
                result = m_exact_match.get_result(0);
            } else {
                retval = true; /* commit prev pending */
            }
            result += wide;
        } else {
            if (m_pending.length () > 0)
                retval = true; /* commit prev pending */
            result = wide;
        }

        m_pending.clear ();
        m_exact_match.clear ();

        return retval;

    } else {
        // the key isn't keypad
        return append (raw, result, pending);
    }
}

static
int split_string_list(std::vector<std::string>& vec, const std::string& str)
{
    int count = 0;

    std::string temp;
    std::string::const_iterator bg, ed;

    vec.clear ();

    bg = str.begin ();
    ed = str.begin ();

    while (bg != str.end () && ed != str.end ()) {
        for (; ed != str.end (); ++ed) {
            if (*ed == ',')
                break;
        }
        temp.assign (bg, ed);
        vec.push_back (temp);
        ++count;

        if (ed != str.end ())
            bg = ++ ed;
    }
    return count;
}

static inline bool CheckLayout(FcitxInstance* instance)
{
    char *layout = NULL, *variant = NULL;
    bool layout_is_jp = false;
    FcitxXkbGetCurrentLayout(instance, &layout, &variant);
    if (layout && strcmp(layout, "jp") == 0)
        layout_is_jp = true;

    fcitx_utils_free(layout);
    fcitx_utils_free(variant);

    return layout_is_jp;
}

bool
Key2KanaConvertor::append (const std::string & str,
                           std::string & result, std::string & pending)
{
    std::string widestr = str;
    std::string matching_str = m_pending + widestr;
    Key2KanaRule exact_match;
    bool has_partial_match = false;
    bool retval = false;

    if (m_pseudo_ascii_mode != 0 && process_pseudo_ascii_mode (widestr)) {
        m_pending += widestr;
        pending = m_pending;
        return false;
    }
    if (!m_case_sensitive) {
        std::string half = matching_str;
        for (unsigned int i = 0; i < half.length (); i++)
            half[i] = tolower (half[i]);
        matching_str = half;
    }

    /* find matched table */
    if ((m_anthy.get_typing_method () == FCITX_ANTHY_TYPING_METHOD_KANA) &&
        (CheckLayout(m_anthy.get_owner())) &&
        (m_last_key.sym == FcitxKey_backslash) &&
        (!(m_last_key.keycode == 132 || m_last_key.keycode == 133)) &&
        (m_anthy.get_config()->m_kana_layout_ro_key[0]))
    {
        // Special treatment for Kana "Ro" key.
        // This code is a temporary solution. It doesn't care some minor cases.
        std::vector<std::string> kana_ro_result;
        split_string_list (kana_ro_result,
                                m_anthy.get_config()->m_kana_layout_ro_key);
        Key2KanaRule kana_ro_rule("\\", kana_ro_result);
        result = kana_ro_rule.get_result (0);
        m_pending.clear ();
        m_exact_match.clear ();
        if (matching_str == "\\") {
            return false;
        } else {
            return true;
        }

    } else {
        std::vector<Key2KanaTable*> &tables = m_tables.get_tables();
        for (unsigned int j = 0; j < tables.size(); j++) {
            if (!tables[j])
                continue;

            Key2KanaRules &rules = tables[j]->get_table ();

            for (unsigned int i = 0; i < rules.size(); i++) {
                /* matching */
                std::string seq = rules[i].get_sequence ();
                if (!m_case_sensitive) {
                    for (unsigned int j = 0; j < seq.length (); j++)
                        seq[j] = tolower (seq[j]);
                }
                std::string romaji = seq;
                if (romaji.find (matching_str) == 0) {
                    if (romaji.length () == matching_str.length ()) {
                        /* exact match */
                        exact_match = rules[i];
                    } else {
                        /* partial match */
                        has_partial_match = true;
                    }
                }
            }
        }
    }

    /* return result */
    if (has_partial_match) {
        m_exact_match = exact_match;
        result.clear ();
        m_pending += widestr;
        pending   =  m_pending;

    } else if (!exact_match.is_empty()) {
        if (!exact_match.get_result(1).empty())
            m_exact_match = exact_match;
        else
            m_exact_match.clear ();
        m_pending = exact_match.get_result (1);
        result    = exact_match.get_result (0);
        pending   = m_pending;

    } else {
        if (!m_exact_match.is_empty()) {
            if (!m_exact_match.get_result(0).empty() &&
                m_exact_match.get_result(1).empty())
            {
                result = m_exact_match.get_result(0);
            } else {
                retval = true; /* commit prev pending */
            }
            m_pending.clear ();
            m_exact_match.clear ();

            std::string tmp_result;
            append(str, tmp_result, pending);
            result += tmp_result;

        } else {
            if (m_pending.length () > 0) {
                retval     = true; /* commit prev pending */
                m_pending  = widestr;
                pending    = m_pending;

            } else {
                result     = widestr;
                pending.clear();
                m_pending.clear ();
            }
        }
    }

    return retval;
}

void
Key2KanaConvertor::clear (void)
{
    m_pending.clear ();
    m_exact_match.clear ();
    m_last_key = KeyEvent ();
    reset_pseudo_ascii_mode();
}

bool
Key2KanaConvertor::is_pending (void)
{
    if (m_pending.length () > 0)
        return true;
    else
        return false;
}

std::string
Key2KanaConvertor::get_pending (void)
{
    return m_pending;
}

std::string
Key2KanaConvertor::flush_pending (void)
{
    std::string result;
    if (!m_exact_match.is_empty ()) {
        if (!m_exact_match.get_result(0).empty() &&
            m_exact_match.get_result(1).empty())
        {
            result = m_exact_match.get_result(0);
        } else if (!m_exact_match.get_result(1).empty()) {
            result += m_exact_match.get_result(1);
        } else if (m_pending.length () > 0) {
            result += m_pending;
        }
    }
    clear ();
    return result;
}

void
Key2KanaConvertor::reset_pending (const std::string &result, const std::string &raw)
{
    m_last_key = KeyEvent ();

    for (unsigned int i = 0; i < util_utf8_string_length(raw); i++) {
        std::string res, pend;
        append (util_utf8_string_substr(raw, i, 1), res, pend);
    }
}

bool
Key2KanaConvertor::process_pseudo_ascii_mode (const std::string & wstr)
{
    for (unsigned int i = 0; i < wstr.length (); i++) {
        if ((wstr[i] >= 'A' && wstr[i] <= 'Z') ||
            isspace(wstr[i]))
        {
            m_is_in_pseudo_ascii_mode = true;
        } else if (wstr[i] & 0x80) {
            m_is_in_pseudo_ascii_mode = false;
        }
    }

    return m_is_in_pseudo_ascii_mode;
}

void
Key2KanaConvertor::reset_pseudo_ascii_mode (void)
{
    if (m_is_in_pseudo_ascii_mode)
        m_pending.clear();
    m_is_in_pseudo_ascii_mode = false;
}

/*
vi:ts=4:nowrap:ai:expandtab
*/
