/***************************************************************************
 *   Copyright (C) 2004 by TAM(Teppei Tamra)                               *
 *   tam-t@par.odn.ne.jp                                                   *
 *                                                                         *
 *   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 of the License, 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, Inc.,                                       *
 *   59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.             *
 ***************************************************************************/
#include "romkan.h"

#ifdef HAVE_CONFIG_H
  #include <config.h>
#endif

// 国際化のおまじない。
#ifdef HAVE_GETTEXT
  #include <libintl.h>
  #define _(String) dgettext(GETTEXT_PACKAGE,String)
  #define N_(String) (String)
#else
  #define _(String) (String)
  #define N_(String) (String)
  #define bindtextdomain(Package,Directory)
  #define textdomain(domain)
  #define bind_textdomain_codeset(domain,codeset)
#endif

// プライグイン化のおまじないです。
HonokaPluginRegister(RomkanPlugin);
HonokaPluginSetup(RomkanPlugin);

HonokaSetupCorePage *RomkanPlugin::setup()
{
    bindtextdomain (GETTEXT_PACKAGE, HONOKA_LOCALEDIR);
    bind_textdomain_codeset(GETTEXT_PACKAGE, "UTF-8");
    HonokaSetupPage *page = new HonokaSetupPage(_("Romkan-plugin"),"","");
    page->append(new HonokaSetupFileItem(
        _("_Roma-Kana table: "),
        HONOKA_CONFIG_ROMKAN_TABLE_FILE,
        _("input the path of Roma-Kana convert table file."),
        HONOKA_DEFAULT_ROMKAN_TABLE_FILE
    ));
    page->append(new HonokaSetupBoolItem(
        _("Ascii to Kana convert: "),
        HONOKA_CONFIG_ROMKAN_AUTO_A2K,
        _("convert ascii to kana when be switched to ROMKAN ."),
        HONOKA_DEFAULT_ROMKAN_AUTO_A2K
    ));
    page->append(new HonokaSetupKeyItem(
        _("Ascii to Kana convert key: "),
        HONOKA_CONFIG_ROMKAN_A2K_KEY,
        _("The key events to convert the ascii string to kana."),
        HONOKA_DEFAULT_ROMKAN_A2K_KEY
    ));
    HonokaSetupPage *sc = new HonokaSetupPage(_("shortcut keys: "),"","");
    sc->append(new HonokaSetupKeyItem(
        _("Romkan input: "),
        String(HONOKA_CONFIG_KEY_PREEDITOR_PREFIX) + String("/Romkan"),
        "",
        ""
    ));
    page->append(sc);

    return page;
};

RomkanPlugin::RomkanPlugin(ConfigPointer cfg) : HonokaMultiplePluginBase(cfg)
{
    bindtextdomain (GETTEXT_PACKAGE, HONOKA_LOCALEDIR);
    bind_textdomain_codeset(GETTEXT_PACKAGE, "UTF-8");
    String c = config->read(HONOKA_CONFIG_ROMKAN_TABLE_FILE,String(HONOKA_DEFAULT_ROMKAN_TABLE_FILE));
    if (c.length()) romkans.push_back(new Romkan(cfg,String("")));
    for(unsigned int i = 1;i < 10;i ++) {
        char a[3];
        sprintf(a,"%d",i);
        c = config->read(String(HONOKA_CONFIG_ROMKAN_TABLE_FILE) + String(a),String(""));
        if (c.length()) romkans.push_back(new Romkan(cfg,String(a)));
    }
}

RomkanPlugin::~RomkanPlugin()
{
    for(unsigned int i = 0;i < romkans.size();i ++) delete romkans[i];
}

HonokaPluginBase * RomkanPlugin::getPluginInstanceAt(int p)
{
    if ((p < 0) || (p >= romkans.size())) return 0;
    return romkans[p];
}

int RomkanPlugin::getPluginCount()
{
    return romkans.size();
}

String RomkanPlugin::getName()
{
    return String("RomkanPlugin");
}

Romkan::Romkan(ConfigPointer cfg,String _postfix) : PreEditor(cfg)
{
    reset();
    iconvert.set_encoding ("EUC-JP");
    mode = ROMA;
    postfix = _postfix;
    init();
}


Romkan::~Romkan()
{
}


/*!
    \fn Romkan::loadTable(const String &filename, bool inc)
 */
void Romkan::loadTable(const String &filename, bool inc)
{
    FILE *f = fopen(filename.c_str(),"r");
    if (!f) {
        #ifdef HONOKA_DATADIR
        String fn = HONOKA_DATADIR;
        fn += "/" + filename;
        f = fopen(fn.c_str(),"r");
        if (!f) return;
        #else
        return;
        #endif
    }
    
    while(-1) {
        char s[256];
        if(fgets(s,256,f) == NULL) break;
        String k,r;
        unsigned int i = 0;
        bool ex = false;
        for(;s[i];i ++) {
            if (s[i] == ' ') {
                if (k.size()) break;
                else continue;
            }
            if (s[i] == '\n') break;
            if ((s[i] == '!') || (s[i] == '$') ||(s[i] == '%')) {
                if (!k.size()) ex = true;
            }
            if (s[i] == '#') break;
            if ((s[i] == '\\') && (s[i + 1] != 0)) {
                i ++;
            }
            k += s[i];
        }
        if ((!k.size()) || (s[i] == '#')) continue;
        if (s[i] != 0) {
            i ++;
            for(;s[i];i ++) {
                if ((s[i] == '#') || (s[i] == '\n')) break;
                if (s[i] == ' ') {
                    if (r.size()) break;
                    else continue;
                }
                if ((s[i] == '\\') && (s[i + 1] != 0)) {
                    i ++;
                }
                r += s[i];
            }
        }
        if (ex && (k[0] == '%')) {
            loadTable(k.substr(1),true);
            continue;
        }
        if ((k[0] == '$') && (!inc) && ex) {
            tableConfig.insert(pair<String,String>(k.substr(1),r));
            continue;
        }
        if ((k[0] == '!') && (!inc) && ex) {
            RomkanKeyEventList kev;
            scim_string_to_key_list(kev,k.substr(1));
            kev.p = utf8_mbstowcs(r);
            hookKey.push_back(kev);
            continue;
        }
        if (!r.size()) continue;
        RomkanTable.insert(pair<String,WideString>(k,utf8_mbstowcs(r)));
        
        for(unsigned int j = k.length();j > 1;j --) {
            String keep = k.substr(0,j - 1);
            if ((keepTable.find(keep) == keepTable.end()) && (RomkanTable.find(keep) == RomkanTable.end())) {
                keepTable.insert(keep);
            }
        }
    }
    fclose(f);
        
}


/*!
    \fn Romkan::string2bool(const String &s)
 */
bool Romkan::string2bool(const String &s)
{
    if ((s == "FALSE") || (s == "false") || (s == "False") || (s == "0") || (!s.size())) return false;
    else return true;
}


/*!
    \fn Romkan::init()
 */
void Romkan::init()
{
    RomkanTable.clear();
    keepTable.clear();
    tableConfig.clear();
    
    //String rk_table = config->read(HONOKA_CONFIG_ROMKAN_TABLE_FILE,String(HONOKA_DEFAULT_ROMKAN_TABLE_FILE));
    String rk_table = config->read(String(HONOKA_CONFIG_ROMKAN_TABLE_FILE) + postfix,String(HONOKA_DEFAULT_ROMKAN_TABLE_FILE));
    scim_string_to_key_list(key_a2k,config->read(HONOKA_CONFIG_ROMKAN_A2K_KEY,String(HONOKA_DEFAULT_ROMKAN_A2K_KEY)));
    aa2k = config->read(HONOKA_CONFIG_ROMKAN_AUTO_A2K,HONOKA_DEFAULT_ROMKAN_AUTO_A2K);
    if (rk_table.size()) {
        loadTable(rk_table);
    }
    
    if (tableConfig.find("nnMode") != tableConfig.end())
        nnMode = string2bool(tableConfig["nnMode"]);
    else nnMode = false;
    if (tableConfig.find("nProc") != tableConfig.end())
        nProc = string2bool(tableConfig["nProc"]);
    else nProc = true;
    if (tableConfig.find("xtProc") != tableConfig.end())
        xtProc = string2bool(tableConfig["xtProc"]);
    else xtProc = true;
    if (tableConfig.find("AsciiModeCancel") != tableConfig.end())
        asciiCancel = string2bool(tableConfig["AsciiModeCancel"]);
    else asciiCancel = true;
    if (tableConfig.find("RemoveRemainder") != tableConfig.end())
        removeRemainder = string2bool(tableConfig["RemoveRemainder"]);
    else removeRemainder = false;
    if (tableConfig.find("Key/Ascii") != tableConfig.end())
        scim_string_to_key_list(key_ascii_mode,tableConfig["Key/Ascii"]);
    if (tableConfig.find("Key/WideAscii") != tableConfig.end())
        scim_string_to_key_list(key_wascii_mode,tableConfig["Key/WideAscii"]);
    if (tableConfig.find("Key/ToggleHalfOrWide") != tableConfig.end())
        scim_string_to_key_list(key_toggle_hw,tableConfig["Key/ToggleHalfOrWide"]);
    if (tableConfig.find("Key/ToggleHiraOrKata") != tableConfig.end())
        scim_string_to_key_list(key_toggle_hk,tableConfig["Key/ToggleHiraOrKata"]);
    if (tableConfig.find("Hook") != tableConfig.end())
        hookp = tableConfig["Hook"];
    else hookp = "";

}




/*!
    \fn Romkan::setPos(int p)
 */
void Romkan::setPos(int p)
{
    if (p < 0) p = 0;
    else if (p > getTextLength()) p = getTextLength();
    pos = p;
    buf.clear();
    rmChars.clear();
}


/*!
    \fn Romkan::clear()
 */
void Romkan::clear()
{
    text.clear();
    buf.clear();
    rmChars.clear();
}


/*!
    \fn Romkan::insert(char k)
 */
WideString Romkan::insert(char k)
{
    String s;
    s = k;
    if (k == 0) return text;
    switch(mode) {
        case ASCII: {
            buf.clear();
            rmChars.clear();
            text = text.substr(0,pos) + utf8_mbstowcs(s) + text.substr(pos);
            pos ++;
            return text;
            break;
        }
        case WASCII: {
            buf.clear();
            rmChars.clear();
            WideString w = utf8_mbstowcs(s);
            convHanZen(w,pos);
            text = text.substr(0,pos) + w + text.substr(pos);
            pos ++;
            return text;
            break;
        }
        case ROMA: {
            buf += s;
            text = text.substr(0,pos) + utf8_mbstowcs(s) + text.substr(pos);
            pos ++;
            return eval();
            break;
        }
        case KROMA: {
            buf += s;
            text = text.substr(0,pos) + utf8_mbstowcs(s) + text.substr(pos);
            pos ++;
            return eval();
            break;
        }
        case HROMA: {
            buf += s;
            text = text.substr(0,pos) + utf8_mbstowcs(s) + text.substr(pos);
            pos ++;
            return eval();
            break;
        }
    }
    return text;
}


/*!
    \fn Romkan::eval()
 */
WideString Romkan::eval()
{
    // エヴァる。

    if (buf.length() == 2) {

        // n＋母音以外は「ん」＋子音である
        // ただし、nyは除外である。
        if (nProc && (buf[0] == 'n')) {
            String b = "aiueoy";
            if (nnMode) b += "n";
            bool boin = false;
            for(unsigned int i = 0;i < b.length();i ++) {
                if (buf[1] == b[i]) boin = true;
            }
            if (!boin) {
                WideString r;
                r += convChars[KANA_N];
                if (mode == HROMA) convZenHan(r,0);
                else if (mode == KROMA) convHiraKata(r);
                text = text.substr(0,pos - 2) + r + text.substr(pos - 1);
                buf = buf.substr(buf.length() - 1,1);
                rmChars.clear();
            }
        }

        // 同じ文字が２文字続くとそれは「っ」＋子音である。母音の連打がbufに残ってはいないはず。
        else if (xtProc && (buf[0] == buf[1])) {
            WideString r;
            r += convChars[KANA_XTU];
            if (mode == HROMA) convZenHan(r,0);
            else if (mode == KROMA) convHiraKata(r);
            text = text.substr(0,pos - 2) + r + text.substr(pos - 1);
            buf = buf.substr(buf.length() - 1,1);
            rmChars.clear();
            return text;
        }
    }

    // 保留テーブルを検索する。
    if (keepTable.find(buf) != keepTable.end()) return text;
    // テーブルを検索して入力する。
    map<String,WideString>::iterator it = RomkanTable.find(buf);
    if (it != RomkanTable.end()) {
        WideString r;
        r += it->second;
        if (mode == HROMA) convZenHan(r,0);
        else if (mode == KROMA) convHiraKata(r);
        text = text.substr(0,pos - buf.length()) + r + text.substr(pos);
        pos = pos - buf.length() + r.length();
        buf.clear();
        rmChars.clear();
        return text;
    }
    if (buf.length()) {
        if (removeRemainder) {
            text = text.substr(0,pos - buf.length()) + text.substr(pos - buf.length() + 1);
            pos --;
        }
        rmChars = rmChars + buf.substr(0,1);
        buf = buf.substr(1);
        return eval();
    }

    return text;
}




/*!
    \fn Romkan::reset()
 */
void Romkan::reset()
{
    clear();
    pos = 0;
    mode = ROMA;
}

/*!
    \fn PreEditor::setText(const WideString &t)
 */
void Romkan::setText(const WideString &t)
{
    text = t;
    buf.clear();
    rmChars.clear();
}


/*!
    \fn Romkan::getText(bool hosei)
 */
WideString Romkan::getText(bool hosei)
{
    if (hosei) {
        if (buf.length()) {
            if (removeRemainder) {
                text = text.substr(0,pos - buf.length()) + text.substr(pos);
                pos -= buf.length();
                if (buf.substr(buf.length() - 1,1) == "n") {
                    WideString r;
                    r += convChars[KANA_N];
                    if (mode == HROMA) convZenHan(r,0);
                    else if (mode ==KROMA) convHiraKata(r);
                    text = text.substr(0,pos) + r + text.substr(pos);
                    pos ++;
                }
            } else 
            if (buf.substr(buf.length() - 1,1) == "n") {
                WideString r;
                r += convChars[KANA_N];
                if (mode == HROMA) convZenHan(r,0);
                else if (mode ==KROMA) convHiraKata(r);
                text = text.substr(0,pos - 1) + r + text.substr(pos);
            }
        }
    }
    return text;
}


/*!
    \fn Romkan::backspace()
 */
void Romkan::backspace()
{
    if (getPos() == 0) return;
    text = text.substr(0,pos - 1) + text.substr(pos);
    pos --;
    // BSは小バッファもBSするゾ。
    if (buf.length()) buf = buf.substr(0,buf.length() - 1);
    else if (rmChars.length()) rmChars = rmChars.substr(0,rmChars.length() - 1);
    // BS時にバッファの頭１文字を復帰。
    // その一文字がシングルバイトでなかった場合は破棄。
    if (pos && (!removeRemainder) && rmChars.size()) {
        /*
        while((pos - buf.size()) > 0) {
            String s = utf8_wcstombs(text.substr(pos - (buf.length() + 1),1));
            if (s.length() == 1) {
                buf = s + buf;
            } else break;
        }*/
        buf = rmChars + buf;
        rmChars.clear();
        eval();
        return;

    }
}



/*!
    \fn Romkan::del()
 */
void Romkan::del()
{
    if (getPos() == getTextLength()) return;
    text = text.substr(0,pos) + text.substr(pos + 1);
}



/*!
    \fn Romkan::hiraKata()
 */
void Romkan::hiraKata()
{
    convHiraKata(text);
}


/*!
    \fn Romkan::kataHira()
 */
void Romkan::kataHira()
{
    convKataHira(text);
}

/*!
    \fn Romkan::toHalf()
 */
void Romkan::toHalf()
{
    setPos(convZenHan(text,getPos()));
    buf.clear();
    rmChars.clear();
}

/*!
    \fn Romkan::toWide()
 */
void Romkan::toWide()
{
    setPos(convHanZen(text,getPos()));
    buf.clear();
    rmChars.clear();
}



/*!
    \fn Romkan::keyEventHook(const KeyEvent &key)
 */
bool Romkan::keyEventHook(const KeyEvent &key)
{
    if (key.is_key_release()) return false;
    if (key_toggle_hw.comp(key)) {
        switch (mode) {
            case ASCII: {
                mode = WASCII;
                break;
            }
            case WASCII: {
                mode = ASCII;
                break;
            }
            case ROMA: {
                mode = HROMA;
                break;
            }
            case HROMA: {
                mode = ROMA;
                break;
            }
        }
        return true;
    }
    if (key_toggle_hk.comp(key)) {
        if (mode == ROMA) mode = KROMA;
        else if (mode == KROMA) mode = ROMA;
        return true;
    }

    if ((isprint(key.code)) && ((mode == ASCII) || (mode == WASCII))) {
        if ((!key.is_alt_down()) && (!key.is_control_down())) {
            insert(key.get_ascii_code());
            return true;
        }
    }
    
    for(vector<RomkanKeyEventList>::iterator it = hookKey.begin();it != hookKey.end();it ++) {
        if (it->comp(key)) {
            text = text.substr(0,pos) + it->p + text.substr(pos);
            pos += it->p.length();
            return true;
        }
    }

    if (!hookp.length())  return false;
    if ((buf.length() < hookp.length()) || (!hookp.length())) return false;
    else if (buf.substr(0,hookp.length()) == hookp) {
        return inputEvent(key);
    }
    return false;
}


/*!
    \fn Romkan::getModeName()
 */
String Romkan::getModeName()
{
    switch(mode) {
        case ROMA: {
            return String(_("Roma-Kana"));
            break;
        }
        case KROMA: {
            return String(_("Roma-Katakana"));
            break;
        }
        case HROMA: {
            return String(_("Half Roma-Kana"));
            break;
        }
        case ASCII: {
            return String(_("Ascii"));
            break;
        }
        case WASCII: {
            return String(_("Wide Ascii"));
            break;
        }
    }
}


/*!
    \fn Romkan::cancelEvent()
 */
bool Romkan::cancelEvent()
{
    if (((mode == ASCII) || (mode == WASCII)) && (asciiCancel)) {
        mode = ROMA;
        return true;
    }
    return false;
}


/*!
    \fn Romkan::inputEvent(const KeyEvent &key)
 */
bool Romkan::inputEvent(const KeyEvent &key)
{
    if (PreEditor::isThrough(key)) return true;

    if (key_a2k.comp(key)) {
        asciiToKana();
        return true;
    }
    if (key_ascii_mode.comp(key)) {
        mode = ASCII;
        return true;
    }
    if (key_wascii_mode.comp(key)) {
        mode = WASCII;
        return true;
    }

    if (key.get_ascii_code() && (!key.is_alt_down()) && (!key.is_control_down())) {
        if ((key.get_ascii_code() == ' ') && (!getTextLength())) return false;
        // 素直に返すべきもの
        if ((key.code == SCIM_KEY_Return) ||
            (key.code == SCIM_KEY_Linefeed) ||
            (key.code == SCIM_KEY_Tab)) return false;
        if (!isprint(key.get_ascii_code())) return false;
        insert(key.get_ascii_code());
        return true;
    }
    if (key.get_unicode_code() && (!key.is_alt_down()) && (!key.is_control_down())) {
        buf.clear();
        rmChars.clear();
        text = getText(true) + key.get_unicode_code();
        pos ++;
        return true;
    }
    return false;
}


/*!
    \fn Romkan::getName()
 */
String Romkan::getName()
{
    return String("Romkan") + postfix;
}

/*!
    \fn Romkan::getPropertyName()
 */
String Romkan::getPropertyName()
{
    return String(_("Roma")) + postfix;
}




/*!
    \fn Romkan::getAttributeList()
 */
AttributeList Romkan::getAttributeList()
{
    AttributeList l;
    if (buf.length()) {
        Attribute a(pos - buf.length(),buf.length(),SCIM_ATTR_FOREGROUND,SCIM_RGB_COLOR(255,0,0));
        l.push_back(a);
    }
    return l;
}


/*!
    \fn Honoka::Romkan::asciiToKana()
 */
void Honoka::Romkan::asciiToKana()
{
    inputMode m = mode;
    WideString t = text;
    reset();
    mode = ROMA;
    WideString b;
    for(unsigned int i = 0;i < t.length();i ++) {
        if (((t[i] >= 0x0041) && (t[i] <= 0x005A)) || ((t[i] >= 0x0061) && (t[i] <= 0x007A))) {
            b += t[i];
        } else {
            if (b.length()) {
                String s = utf8_wcstombs(b).c_str();
                for(unsigned int j = 0;j < s.length();j ++) insert(s[j]);
                b.clear();
            }
            text += t[i];
            setPos(getPos() + 1);
        }
    }
    if (b.length()) {
        String s = utf8_wcstombs(b).c_str();
        for(unsigned int j = 0;j < s.length();j ++) insert(s[j]);
    }
    mode = m;
    return;

}


/*!
    \fn Honoka::Romkan::selected()
 */
void Honoka::Romkan::selected()
{
    if (aa2k) asciiToKana();
}
