/*
 * Copyright (C) 2008,2009  OMRON SOFTWARE Co., Ltd.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.hiroshica.android.input.nicownn2.EN;

import java.util.HashMap;
import java.util.ArrayList;

import com.hiroshica.android.input.nicownn2.*;

import android.content.SharedPreferences;

/**
 * The OpenWnn engine class for English IME.
 * 
 * @author Copyright (C) 2009 OMRON SOFTWARE CO., LTD.  All Rights Reserved.
 */
public class NicoWnnEngineEN implements WnnEngine {
    /** Normal dictionary */
    public static final int DICT_DEFAULT              = 0;
    /** Dictionary for mistype correction */
    public static final int DICT_FOR_CORRECT_MISTYPE  = 1;
    /** Score(frequency value) of word in the learning dictionary */
    public static final int FREQ_LEARN = 600;
    /** Score(frequency value) of word in the user dictionary */
    public static final int FREQ_USER = 500;
    /** Limitation of predicted candidates */
    public static final int PREDICT_LIMIT = 300;

    /** OpenWnn dictionary */
	private   WnnDictionary mDictionary;
    /** Word list */
    private ArrayList<WnnWord> mConvResult;
    /** HashMap for checking duplicate word */
    private HashMap<String, WnnWord> mCandTable;
    /** Input string */
    private String        mInputString;
    /** Searching string */
    private String        mSearchKey;
    /** Number of output candidates */
    private int           mOutputNum;
    /** The candidate filter */
    private CandidateFilter mFilter = null;
    
    /**
     * Candidate's case
     * <br>
     * CASE_LOWER: all letters are lower.<br>
     * CASE_HEAD_UPPER: the first letter is upper; others are lower.<br>
     * CASE_UPPER: all letters are upper.<br>
     */
    private int           mCandidateCase;
    private static final int CASE_LOWER = 0;
    private static final int CASE_UPPER = 1;
    private static final int CASE_HEAD_UPPER = 3;

    /**
     * Constructor
     * 
     * @param writableDictionaryName		Writable dictionary file name(null if not use)
     */
    public NicoWnnEngineEN(String writableDictionaryName) {
        mConvResult = new ArrayList<WnnWord>();
        mCandTable = new HashMap<String, WnnWord>();
        mSearchKey = null;
        mOutputNum = 0;

        mDictionary = new NicoWnnDictionaryImpl( 
        		"/data/data/com.hiroshica.android.input.nicownn2/lib/libnicoWnnEngDic.so",
        		writableDictionaryName);
        if (!mDictionary.isActive()) {
        	mDictionary = new NicoWnnDictionaryImpl(
        			"/system/lib/libnicoWnnEngDic.so",
        			writableDictionaryName);
        }
        mDictionary.clearDictionary( );
        
        mDictionary.setDictionary(0, 400, 550);
        mDictionary.setDictionary(1, 400, 550);
        mDictionary.setDictionary(2, 400, 550);
        mDictionary.setDictionary(WnnDictionary.INDEX_USER_DICTIONARY, FREQ_USER, FREQ_USER);
        mDictionary.setDictionary(WnnDictionary.INDEX_LEARN_DICTIONARY, FREQ_LEARN, FREQ_LEARN);

        mDictionary.setApproxPattern(WnnDictionary.APPROX_PATTERN_EN_QWERTY_NEAR);

        mDictionary.setInUseState( false );
    }

    /**
     * Get a candidate.
     *
     * @param index		Index of candidate
     * @return			A candidate; {@code null} if no candidate for the index.
     */
    private WnnWord getCandidate(int index) {
        WnnWord word;
        /* search the candidate from the dictionaries */
        while (mConvResult.size() < PREDICT_LIMIT && index >= mConvResult.size()) {
            while ((word = mDictionary.getNextWord()) != null) {
                /* adjust the case of letter */
                char c = word.candidate.charAt(0);
                if (mCandidateCase == CASE_LOWER) {
                    if (Character.isLowerCase(c)) {
                        break;
                    }
                } else if (mCandidateCase == CASE_HEAD_UPPER) {
                    if (Character.isLowerCase(c)) {
                        word.candidate = Character.toString(Character.toUpperCase(c)) + word.candidate.substring(1);
                    }
                    break;
                } else {
                    word.candidate = word.candidate.toUpperCase();
                    break;
                }
            }
            if (word == null) {
                break;
            }
            /* check duplication */
            addCandidate(word);
        }

        /* get the default candidates */
        if (index >= mConvResult.size()) {
            /* input string itself */
            addCandidate(new WnnWord(mInputString, mSearchKey));

            /* Capitalize the head of input */
            if (mSearchKey.length() > 1) {
                addCandidate(new WnnWord(mSearchKey.substring(0,1).toUpperCase() + mSearchKey.substring(1),
                                         mSearchKey));
            }

            /* Capitalize all */
            addCandidate(new WnnWord(mSearchKey.toUpperCase(), mSearchKey));
        }

        if (index >= mConvResult.size()) {
            return null;
        }
        return mConvResult.get(index);
    }

    /**
     * Add a word to the candidates list if there is no duplication.
     * 
     * @param word		A word
     * @return			{@code true} if the word is added to the list; {@code false} if not.
     */
    private boolean addCandidate(WnnWord word) {
        if (word.candidate == null || mCandTable.containsKey(word.candidate)) {
            return false;
        }
        if (mFilter != null && !mFilter.isAllowed(word)) {
        	return false;
        }
        mCandTable.put(word.candidate, word);
        mConvResult.add(word);
        return true;
    }

    private void clearCandidates() {
        mConvResult.clear();
        mCandTable.clear();
        mOutputNum = 0;
        mSearchKey = null;
    }

    /**
     * Set dictionary.
     *
     * @param type		Type of dictionary (DIC_DEFAULT or DIC_FOR_CORRECT_MISTYPE)
     * @return			{@code true} if the dictionary is changed; {@code false} if not.
     */
    public boolean setDictionary(int type) {
        if (type == DICT_FOR_CORRECT_MISTYPE) {
            mDictionary.clearApproxPattern();
            mDictionary.setApproxPattern(WnnDictionary.APPROX_PATTERN_EN_QWERTY_NEAR);
        } else {
            mDictionary.clearApproxPattern();
        }
        return true;
    }

    /**
     * Set search key for the dictionary.
     * <br>
     * To search the dictionary, this method set the lower case of
     * input string to the search key. And hold the input string's
     * capitalization information to adjust the candidates
     * capitalization later.
     *
     * @param input		Input string
     * @return			{@code true} if the search key is set; {@code false} if not.
     */
    private boolean setSearchKey(String input) {
        if (input.length() == 0) {
            return false;
        }

        /* set mInputString */
        mInputString = input;

        /* set mSearchKey */
        mSearchKey = input.toLowerCase();

        /* set mCandidateCase */
        if (Character.isUpperCase(input.charAt(0))) {
            if (input.length() > 1 && Character.isUpperCase(input.charAt(1))) {
                mCandidateCase = CASE_UPPER;
            } else {
                mCandidateCase = CASE_HEAD_UPPER;
            }
        } else {
            mCandidateCase = CASE_LOWER;
        }

        return true;
    }
    
    /**
     * Set the candidate filter
     * 
     * @param filter	The candidate filter
     */
    public void setFilter(CandidateFilter filter) {
    	mFilter = filter;
    }
    
    /***********************************************************************
     * WnnEngine's interface
     **********************************************************************/
    /** @see com.hiroshica.android.input.nicownn2.WnnEngine#init */
    public void init() {}

    /** @see com.hiroshica.android.input.nicownn2.WnnEngine#close */
    public void close() {}

    /** @see com.hiroshica.android.input.nicownn2.WnnEngine#predict */
    public int predict(ComposingText text, int minLen, int maxLen) {
        clearCandidates();

        if (text == null) { return 0; }
        
        String input = text.toString(2);
        if (!setSearchKey(input)) {
            return 0;
        }

        /* set dictionaries by the length of input */
        WnnDictionary dict = mDictionary;
        dict.setInUseState( true );

        dict.clearDictionary();
        dict.setDictionary(0, 400, 550);
        if (input.length() > 1) {
            dict.setDictionary(1, 400, 550);
        }
        if (input.length() > 2) {
            dict.setDictionary(2, 400, 550);
        }
        dict.setDictionary(WnnDictionary.INDEX_USER_DICTIONARY, FREQ_USER, FREQ_USER);
        dict.setDictionary(WnnDictionary.INDEX_LEARN_DICTIONARY, FREQ_LEARN, FREQ_LEARN);
        
        /* search dictionaries */
        dict.searchWord(WnnDictionary.SEARCH_PREFIX, WnnDictionary.ORDER_BY_FREQUENCY, mSearchKey);
        return 1;
    }

    /** @see com.hiroshica.android.input.nicownn2.WnnEngine#convert */
    public int convert(ComposingText text) {
        clearCandidates();
        return 0;
    }
    
    /** @see com.hiroshica.android.input.nicownn2.WnnEngine#searchWords */
    public int searchWords(String key) {
        clearCandidates();
        return 0;
    }

    /** @see com.hiroshica.android.input.nicownn2.WnnEngine#searchWords */
    public int searchWords(WnnWord word) {
        clearCandidates();
        return 0;
    }

	/** reset word count */
	public void resetCandidate() {
		mOutputNum = 0;
	}

    /** @see com.hiroshica.android.input.nicownn2.WnnEngine#getNextCandidate */
    public WnnWord getNextCandidate() {
        if (mSearchKey == null) {
            return null;
        }
        WnnWord word = getCandidate(mOutputNum);
        if (word != null) {
            mOutputNum++;
        }
        return word;
    }

    /** @see com.hiroshica.android.input.nicownn2.WnnEngine#learn */
    public boolean learn(WnnWord word) {
        return ( mDictionary.learnWord(word) == 0 );
    }

    /** @see com.hiroshica.android.input.nicownn2.WnnEngine#addWord */
    public int addWord(WnnWord word) {
        WnnDictionary dict = mDictionary;
        dict.setInUseState( true );
        dict.addWordToUserDictionary(word);
        dict.setInUseState( false );
        return 0;
    }

    /** @see com.hiroshica.android.input.nicownn2.WnnEngine#deleteWord */
    public boolean deleteWord(WnnWord word) {
        WnnDictionary dict = mDictionary;
        dict.setInUseState( true );
        dict.removeWordFromUserDictionary(word);
        dict.setInUseState( false );
        return false;
    }

    /** @see com.hiroshica.android.input.nicownn2.WnnEngine#setPreferences */
    public void setPreferences(SharedPreferences pref) {}

    /** @see com.hiroshica.android.input.nicownn2.WnnEngine#breakSequence */
    public void breakSequence()  {}

    /** @see com.hiroshica.android.input.nicownn2.WnnEngine#makeCandidateListOf */
    public int makeCandidateListOf(int clausePosition)  {return 0;}

    /** @see com.hiroshica.android.input.nicownn2.WnnEngine#initializeDictionary */
    public boolean initializeDictionary(int dictionary)  {
        WnnDictionary dict = mDictionary;

        switch( dictionary ) {
        case WnnEngine.DICTIONARY_TYPE_LEARN:
            dict.setInUseState( true );
            dict.clearLearnDictionary();
            dict.setInUseState( false );
            return true;

        case WnnEngine.DICTIONARY_TYPE_USER:
            dict.setInUseState( true );
            dict.clearUserDictionary();
            dict.setInUseState( false );
            return true;
        }
        return false;
    }
    
    /** @see com.hiroshica.android.input.nicownn2.WnnEngine#initializeDictionary */
    public boolean initializeDictionary(int dictionary, int type) {
    	return initializeDictionary(dictionary);
    }

    /** @see com.hiroshica.android.input.nicownn2.WnnEngine#getUserDictionaryWords */
    public WnnWord[] getUserDictionaryWords( ) {
        WnnDictionary dict = mDictionary;
        dict.setInUseState( true );
        WnnWord[] result = dict.getUserDictionaryWords( );
        dict.setInUseState( false );
        return result;
    }

}
