package org.maachang.conf;

import java.io.BufferedReader;
import java.io.IOException;

/**
 * INIデータ解析用オブジェクト.
 * 
 * @version 2007/10/18
 * @author masahito suzuki
 * @since MaachangBase 1.00
 */
public class ReadIni {

    /**
     * デフォルトキーコード情報.
     */
    private static final String DEFAULT_KEY = "default";

    /**
     * セクションワード : 開始コード.
     */
    private static final char SECTION_ST = '[';

    /**
     * セクションワード : 終了コード.
     */
    private static final char SECTION_ED = ']';

    /**
     * コメントコード.
     */
    private static final String COMMENT_CD = ";#";

    /**
     * 次行リンクコード:string.
     */
    private static final String NEXT_LINE_CDSTR = "|";

    /**
     * 次行リンクコード.
     */
    private static final char NEXT_LINE_CD = ReadIni.NEXT_LINE_CDSTR.charAt(0);

    /**
     * キーコード参照値表現.
     */
    private static final char NEXT_VALUE = '=';

    /**
     * タブコード.
     */
    private static final char TAB_CD = '\t';

    /**
     * 半角スペースコード.
     */
    private static final char SPC_CD = ' ';

    /**
     * 全角スペースコード.
     */
    private static final char SPC2_CD = '　';

    /**
     * シングルコーテーション.
     */
    private static final char SNGL_COTE = '\'';

    /**
     * ダブルコーテーション.
     */
    private static final char DBL_COTE = '\"';

    /**
     * INI読み込みモード : 読み込み中.
     */
    private static final int MODE_NON = 0;

    /**
     * INI読み込みモード : セクション参照.
     */
    private static final int MODE_SECTION = 1;

    /**
     * INI読み込みモード : キーコード参照.
     */
    private static final int MODE_KEY = 2;

    /**
     * INI読み込みモード : キー参照コード参照.
     */
    private static final int MODE_VALUE = 3;

    /**
     * INIコーテーションモード : コーテーションなし.
     */
    private static final int MODE_COTE_NON = 0;

    /**
     * INIコーテーションモード : シングルコーテーション.
     */
    private static final int MODE_COTE_SINGLE = 1;

    /**
     * INIコーテーションモード : ダブルコーテーション.
     */
    private static final int MODE_COTE_DOUBLE = 2;

    /**
     * INIデータ解析. <BR>
     * <BR>
     * INIデータを解析します. <BR>
     * 
     * @param param
     *            解析対象の情報を設定します.
     * @param index
     *            解析元の情報を設定します.
     * @exception Exception
     *                例外.
     */
    public static final void analisys(Config param, BufferedReader index)
            throws Exception {
        analisys(param, "", index);
    }

    /**
     * INIデータ解析. <BR>
     * <BR>
     * INIデータを解析します. <BR>
     * 
     * @param param
     *            解析対象の情報を設定します.
     * @param header
     *            セクション名の前に付加する内容を設定します.
     * @param index
     *            解析元の情報を設定します.
     * @exception Exception
     *                例外.
     */
    public static final void analisys(Config param, String header,
            BufferedReader index) throws Exception {
        int i, j, k;
        int lenJ;
        int mode;
        int mode_cote;
        int line, count;
        int oneLineNextCd;
        boolean link_line;
        boolean lastLinkLine;
        boolean nextcheck;

        char nowWd;

        char[] oneLines = null;
        StringBuilder buf = null;
        String nowSection = null;
        String nowKey = null;
        String nowWord = null;
        String lastKey = null;

        String tmp = null;

        if (param == null) {
            throw new IllegalArgumentException("引数は不正です");
        }
        // param.clear();

        line = 0;
        count = 0;
        mode_cote = ReadIni.MODE_COTE_NON;

        try {
            nowSection = null;
            mode = ReadIni.MODE_NON;
            lastKey = ReadIni.DEFAULT_KEY;
            lastLinkLine = false;

            // 全体読み込み.
            for (i = 0;; i++) {

                // 行番号を定義.
                line = i + 1;

                tmp = (String) index.readLine();
                if (tmp == null) {
                    break;
                }
                oneLineNextCd = tmp.indexOf(ReadIni.NEXT_LINE_CDSTR);

                // 内容が存在しない場合.
                if ((lenJ = tmp.length()) <= 0) {
                    continue;
                }

                // １行情報の読み込み.
                oneLines = new char[lenJ];
                tmp.getChars(0, lenJ, oneLines, 0);

                mode_cote = MODE_COTE_NON;
                link_line = false;

                // １行情報解析.
                for (j = 0; j < lenJ; j++) {

                    // 列番号を定義.
                    count = j + 1;

                    // 対象位置の１文字情報を取得.
                    nowWd = oneLines[j];

                    // コーテーション括りでない場合の処理.
                    if (mode_cote == ReadIni.MODE_COTE_NON) {

                        // コメントが存在する場合.
                        if (ReadIni.isCode(nowWd, ReadIni.COMMENT_CD) == true) {

                            link_line = false;
                            break;

                        }
                        // 次行リンク指定の場合.
                        else if (mode != ReadIni.MODE_SECTION
                                && nowWd == ReadIni.NEXT_LINE_CD) {
                            link_line = true;
                            break;
                        }

                    }

                    // 前回の行情報と連結している場合.
                    if (lastLinkLine == true) {

                        // コーテーション区切りの場合.
                        if (mode == ReadIni.MODE_VALUE
                                && (nowWd == ReadIni.SNGL_COTE || nowWd == ReadIni.DBL_COTE)) {

                            mode_cote = (nowWd == ReadIni.SNGL_COTE) ? ReadIni.MODE_COTE_SINGLE
                                    : ReadIni.MODE_COTE_DOUBLE;
                            lastLinkLine = false;

                        }
                        // コーテーション区切り以外の場合.
                        // モード条件が存在しない場合.
                        else if (mode == ReadIni.MODE_NON
                                && ((nowWd >= 'a' && nowWd <= 'z')
                                        || (nowWd >= 'A' && nowWd <= 'Z') || nowWd == ReadIni.SECTION_ST)) {

                            buf = new StringBuilder();

                            // セクション情報開始の場合.
                            if (nowWd == ReadIni.SECTION_ST) {
                                mode = ReadIni.MODE_SECTION;
                            }
                            // キーワード情報読み込み開始の場合.
                            else {

                                mode = ReadIni.MODE_KEY;
                                buf.append(nowWd);

                            }

                            lastLinkLine = false;

                        }
                        // ワード情報検出の場合.
                        else if (mode == ReadIni.MODE_VALUE
                                && (nowWd != ReadIni.TAB_CD && nowWd != ReadIni.SPC_CD)) {

                            buf.append(nowWd);
                            lastLinkLine = false;

                        }

                        continue;

                    }

                    // 各モードセット.
                    switch (mode) {

                    // 読み込み状態.
                    case ReadIni.MODE_NON:

                        if (nowWd == ReadIni.SECTION_ST) {

                            buf = new StringBuilder();
                            mode = ReadIni.MODE_SECTION;

                        }
                        // 文字列が英字の場合.
                        else if ((nowWd >= 'a' && nowWd <= 'z')
                                || (nowWd >= 'A' && nowWd <= 'Z')
                                || nowWd == '$' || nowWd == '@') {
                            // キーコード対象とする.
                            mode = ReadIni.MODE_KEY;
                            buf = new StringBuilder();
                            buf.append(nowWd);

                        }
                        // 以前のキーコードとしての値検知.
                        else if (nowWd == ReadIni.NEXT_VALUE) {

                            nowKey = lastKey;
                            buf = new StringBuilder();
                            mode = ReadIni.MODE_VALUE;

                        }
                        // 文字列が英字,タブ,スペース以外の場合.
                        else if (nowWd != ReadIni.TAB_CD
                                && nowWd != ReadIni.SPC_CD && nowWd != SPC2_CD) {
                            // 読み込み内容が不正.
                            throw new IOException("シンタックスエラー(" + tmp
                                    + ")が検出されました-(行:" + line + " 列:" + count
                                    + " )");
                        }

                        break;

                    // セクション名読み込み状態.
                    case ReadIni.MODE_SECTION:

                        if (nowWd == ReadIni.SECTION_ED) {

                            nowSection = buf.toString().trim();
                            lastKey = ReadIni.DEFAULT_KEY;
                            buf = null;
                            mode = ReadIni.MODE_NON;

                        } else {

                            if (nowWd == ReadIni.NEXT_LINE_CD
                                    || nowWd == ReadIni.SNGL_COTE
                                    || nowWd == ReadIni.DBL_COTE) {
                                // 読み込み内容が不正.
                                throw new IOException("セクション名内に不正なコードを検出("
                                        + tmp + ")しました-(行:" + line + " 列:"
                                        + count + " )");
                            }

                            buf.append(nowWd);

                        }

                        break;

                    // キーコード読み込み状態.
                    case ReadIni.MODE_KEY:

                        if (nowWd == ReadIni.NEXT_VALUE) {

                            nowKey = buf.toString().trim();
                            lastKey = nowKey;
                            buf = null;
                            buf = new StringBuilder();
                            mode = ReadIni.MODE_VALUE;

                        } else {

                            if (nowWd == ReadIni.SNGL_COTE
                                    || nowWd == ReadIni.DBL_COTE) {
                                // 読み込み内容が不正.
                                throw new IOException("キーコード内に不正なコードを検出(" + tmp
                                        + ")しました-(行:" + line + " 列:" + count
                                        + " )");
                            }

                            buf.append(nowWd);

                        }

                        break;

                    // ワード情報読み込み状態.
                    case ReadIni.MODE_VALUE:

                        // コーテーション区切りの場合.
                        if (nowWd == ReadIni.SNGL_COTE
                                || nowWd == ReadIni.DBL_COTE) {
                            // シングルコーテーションが検出された場合.
                            if (nowWd == ReadIni.SNGL_COTE) {

                                // ダブルコーテーションが存在
                                if (mode_cote == ReadIni.MODE_COTE_DOUBLE) {

                                    // 既にシングルコーテーションが検出されたため
                                    // ダブルコーテーションは通常文字列として
                                    // 有効とする.
                                    buf.append(nowWd);
                                    break;

                                }

                                mode_cote = (mode_cote == ReadIni.MODE_COTE_NON) ? ReadIni.MODE_COTE_SINGLE
                                        : ReadIni.MODE_COTE_NON;

                            }
                            // ダブルコーテーションが検出された場合.
                            else {

                                // シングルコーテーションが存在
                                if (mode_cote == ReadIni.MODE_COTE_SINGLE) {

                                    // 既にダブルコーテーションが検出されたため
                                    // シングルコーテーションは通常文字列として
                                    // 有効とする.
                                    buf.append(nowWd);
                                    break;

                                }

                                mode_cote = (mode_cote == ReadIni.MODE_COTE_NON) ? ReadIni.MODE_COTE_DOUBLE
                                        : ReadIni.MODE_COTE_NON;

                            }

                        }
                        // コーテーションが検出された場合.
                        else if (mode_cote != MODE_COTE_NON) {

                            buf.append(nowWd);

                        }
                        // コーテーションが検出されていない場合.
                        else {

                            // ワード情報終端が検出された場合.
                            if ((buf != null && buf.length() != 0)
                                    && (nowWd == ReadIni.TAB_CD || nowWd == ReadIni.SPC_CD)) {
                                nextcheck = false;

                                // ￥コードが、この行中に存在する場合.
                                if (oneLineNextCd > j) {
                                    nextcheck = true;
                                    for (k = j + 1; k < oneLineNextCd; k++) {
                                        if (oneLines[k] != ReadIni.TAB_CD
                                                && oneLines[k] != ReadIni.SPC_CD) {
                                            nextcheck = false;
                                            break;
                                        }
                                    }
                                }

                                if (nextcheck == false) {

                                    nowWord = (buf == null || buf.length() <= 0) ? ""
                                            : buf.toString();
                                    mode = ReadIni.MODE_NON;
                                } else {
                                    j = oneLineNextCd - 1;
                                    break;
                                }

                            }
                            // ワード情報が検出された場合.
                            else {

                                buf.append(nowWd);

                            }

                        }

                        break;
                    }
                }

                // コーテーション括りのまま、行終了の場合.
                if (mode_cote != ReadIni.MODE_COTE_NON) {
                    // 読み込み内容が不正.
                    throw new IOException("コーテーションの終端(" + tmp + ")が存在しません-(行:"
                            + line + " 列:" + count + " )");
                }
                // セクション名読み込み最中の場合.
                else if (mode == ReadIni.MODE_SECTION) {
                    // 読み込み内容が不正.
                    throw new IOException("セクション名の終端(" + tmp + ")が存在しません-(行:"
                            + line + " 列:" + count + " )");
                }
                // 次行情報に跨らない場合.
                else if (link_line != true) {

                    // 格納対象情報が存在する場合.
                    if (nowKey != null) {

                        nowWord = (buf == null || buf.length() <= 0) ? "" : buf
                                .toString();
                        buf = null;

                        mode = ReadIni.MODE_NON;
                        if (nowSection == null) {
                            nowSection = "";
                        }
                        param.put(header + nowSection, nowKey, nowWord);
                        lastKey = nowKey;
                        nowKey = null;
                        nowWord = null;

                    }

                }

                // 次行情報跨り判別情報を、バックアップ.
                lastLinkLine = link_line;

            }

            // 情報を読み取り中の場合.
            if (nowKey != null || mode_cote == ReadIni.MODE_COTE_NON) {

                nowWord = (buf == null || buf.length() <= 0) ? "" : buf
                        .toString();
                buf = null;

                if (nowSection == null) {
                    nowSection = "";
                }
                param.put(header + nowSection, nowKey, nowWord);
                mode = ReadIni.MODE_NON;
            } else if (mode == ReadIni.MODE_SECTION
                    || mode_cote != ReadIni.MODE_COTE_NON) {
                if (mode == ReadIni.MODE_SECTION) {
                    // 読み込み内容が不正.
                    throw new IOException("セクション名の終端(" + tmp + ")が存在しません-(行:"
                            + line + " 列:" + count + " )");
                } else {
                    // 読み込み内容が不正.
                    throw new IOException("コーテーションの終端(" + tmp + ")が存在しません-(行:"
                            + line + " 列:" + count + " )");
                }
            }

        } catch (Exception e) {
            throw e;
        } finally {
            oneLines = null;
            buf = null;
            nowSection = null;
            nowKey = null;
            nowWord = null;
            lastKey = null;
            tmp = null;
        }

    }

    /**
     * 指定文字列を、対象文字コードが一致するかチェック.
     */
    private static final boolean isCode(char code, String checks) {
        int i;
        int len;

        boolean ret;

        len = checks.length();
        for (i = 0, ret = false; i < len; i++) {
            if (code == checks.charAt(i)) {
                ret = true;
                break;
            }
        }

        return ret;
    }

}
