package org.maachang.util;

import java.util.ArrayList;

/**
 * 文字操作ユーティリティ.
 * 
 * @version 2007/10/18
 * @author masahito suzuki
 * @since MaachangBase 1.00
 */
public class StringUtil {

    /**
     * チェック情報単位で情報を区切ります。 <BR>
     * <BR>
     * 
     * @param str
     *            区切り対象の情報を設置します.
     * @param check
     *            区切り対象の文字情報をセットします.<BR>
     *            区切り対象文字を複数設定する事により、それらに対応した区切りとなります.
     * @return ArrayList 区切られた情報が格納されています.
     * @exception IllegalArgumentException
     *                入力例外.
     */
    public static final ArrayList<String> cutString(String str, String check)
            throws IllegalArgumentException {
        ArrayList<String> ret = null;

        ret = new ArrayList<String>();

        cutString(ret, false, str, check);

        return ret;
    }

    /**
     * チェック情報単位で情報を区切ります。 <BR>
     * <BR>
     * 
     * @param out
     *            区切られた情報が格納されます.
     * @param str
     *            区切り対象の情報を設置します.
     * @param check
     *            区切り対象の文字情報をセットします.<BR>
     *            区切り対象文字を複数設定する事により、それらに対応した区切りとなります.
     * @exception IllegalArgumentException
     *                入力例外.
     */
    public static final void cutString(ArrayList<String> out, String str,
            String check) throws IllegalArgumentException {
        cutString(out, false, str, check);
    }

    /**
     * チェック情報単位で情報を区切ります。 <BR>
     * <BR>
     * 
     * @param mode
     *            区切られた時の文字列が無い場合に、無視するかチェックします.<BR>
     *            [true]の場合は、無視しません. [false]の場合は、無視します.
     * @param str
     *            区切り対象の情報を設置します.
     * @param check
     *            区切り対象の文字情報をセットします.<BR>
     *            区切り対象文字を複数設定する事により、それらに対応した区切りとなります.
     * @return ArrayList 区切られた情報が格納されています.
     * @exception IllegalArgumentException
     *                入力例外.
     */
    public static final ArrayList<String> cutString(boolean mode, String str,
            String check) throws IllegalArgumentException {
        ArrayList<String> ret = null;

        ret = new ArrayList<String>();

        cutString(ret, mode, str, check);

        return ret;
    }

    /**
     * チェック情報単位で情報を区切ります。 <BR>
     * <BR>
     * 
     * @param out
     *            区切られた情報が格納されます.
     * @param mode
     *            区切られた時の文字列が無い場合に、無視するかチェックします.<BR>
     *            [true]の場合は、無視しません. [false]の場合は、無視します.
     * @param str
     *            区切り対象の情報を設置します.
     * @param check
     *            区切り対象の文字情報をセットします.<BR>
     *            区切り対象文字を複数設定する事により、それらに対応した区切りとなります.
     * @exception IllegalArgumentException
     *                入力例外.
     */
    public static final void cutString(ArrayList<String> out, boolean mode,
            String str, String check) throws IllegalArgumentException {
        int i, j;
        int len;
        int lenJ;
        int s = -1;
        char strCode;

        char[] checkCode = null;
        String tmp = null;

        if (out == null || str == null || (len = str.length()) <= 0
                || check == null || check.length() <= 0) {
            throw new IllegalArgumentException("引数が不正です");
        }

        out.clear();

        lenJ = check.length();
        checkCode = new char[lenJ];
        check.getChars(0, lenJ, checkCode, 0);

        if (lenJ == 1) {

            for (i = 0, s = -1; i < len; i++) {

                strCode = str.charAt(i);
                s = (s == -1) ? i : s;

                if (strCode == checkCode[0]) {

                    if (s < i) {
                        tmp = str.substring(s, i);
                        out.add(tmp);
                        tmp = null;
                        s = -1;
                    } else if (mode == true) {
                        out.add("");
                        s = -1;
                    } else {
                        s = -1;
                    }

                }

            }

        } else {

            for (i = 0, s = -1; i < len; i++) {

                strCode = str.charAt(i);
                s = (s == -1) ? i : s;

                for (j = 0; j < lenJ; j++) {
                    if (strCode == checkCode[j]) {

                        if (s < i) {
                            tmp = str.substring(s, i);
                            out.add(tmp);
                            tmp = null;
                            s = -1;
                        } else if (mode == true) {
                            out.add("");
                            s = -1;
                        } else {
                            s = -1;
                        }

                        break;

                    }
                }

            }

        }

        if (s != -1) {

            tmp = str.substring(s, len);
            out.add(tmp);
            tmp = null;
        }

        checkCode = null;
        tmp = null;
    }

    /**
     * チェック情報単位で情報を区切ります。 <BR>
     * <BR>
     * 
     * @param cote
     *            コーテーション対応であるか設定します.<BR>
     *            [true]を設定した場合、各コーテーション ( ",' ) で囲った情報内は 区切り文字と判別しません.<BR>
     *            [false]を設定した場合、コーテーション対応を行いません.
     * @param coteFlg
     *            コーテーションが入っている場合に、コーテーションを範囲に含むか否かを 設定します.<BR>
     *            [true]を設定した場合、コーテーション情報も範囲に含みます.<BR>
     *            [false]を設定した場合、コーテーション情報を範囲としません.
     * @param str
     *            区切り対象の情報を設置します.
     * @param check
     *            区切り対象の文字情報をセットします.<BR>
     *            区切り対象文字を複数設定する事により、それらに対応した区切りとなります.
     * @return ArrayList 区切られた情報が格納されています.
     * @exception IllegalArgumentException
     *                入力例外.
     */
    public static final ArrayList<String> cutString(boolean cote,
            boolean coteFlg, String str, String check)
            throws IllegalArgumentException {
        ArrayList<String> ret = null;

        ret = new ArrayList<String>();

        cutString(ret, cote, coteFlg, str, check);

        return ret;
    }

    /**
     * チェック情報単位で情報を区切ります。 <BR>
     * <BR>
     * 
     * @param out
     *            区切られた情報が格納されます.
     * @param cote
     *            コーテーション対応であるか設定します.<BR>
     *            [true]を設定した場合、各コーテーション ( ",' ) で囲った情報内は 区切り文字と判別しません.<BR>
     *            [false]を設定した場合、コーテーション対応を行いません.
     * @param coteFlg
     *            コーテーションが入っている場合に、コーテーションを範囲に含むか否かを 設定します.<BR>
     *            [true]を設定した場合、コーテーション情報も範囲に含みます.<BR>
     *            [false]を設定した場合、コーテーション情報を範囲としません.
     * @param str
     *            区切り対象の情報を設置します.
     * @param check
     *            区切り対象の文字情報をセットします.<BR>
     *            区切り対象文字を複数設定する事により、それらに対応した区切りとなります.
     * @exception IllegalArgumentException
     *                入力例外.
     */
    public static final void cutString(ArrayList<String> out, boolean cote,
            boolean coteFlg, String str, String check)
            throws IllegalArgumentException {
        int i, j;
        int len;
        int lenJ;
        int s = -1;

        char coteChr;
        char nowChr;
        char strCode;

        char[] checkCode = null;
        String tmp = null;

        if (cote == false) {
            StringUtil.cutString(out, str, check);
        } else {

            if (out == null || str == null || (len = str.length()) <= 0
                    || check == null || check.length() <= 0) {
                throw new IllegalArgumentException("引数が不正です");
            }

            out.clear();

            lenJ = check.length();
            checkCode = new char[lenJ];
            check.getChars(0, lenJ, checkCode, 0);

            if (lenJ == 1) {
                int befCode = -1 ;
                for (i = 0, s = -1, coteChr = 0; i < len; i++) {

                    strCode = str.charAt(i);
                    nowChr = strCode;
                    s = (s == -1) ? i : s;

                    if (coteChr == 0) {

                        if (nowChr == '\'' || nowChr == '\"') {

                            coteChr = nowChr;

                            if (s < i) {
                                tmp = str.substring(s, i);
                                out.add(tmp);
                                tmp = null;
                                s = -1;
                            } else {
                                s = -1;
                            }

                        } else if (strCode == checkCode[0]) {

                            if (s < i) {
                                tmp = str.substring(s, i);
                                out.add(tmp);
                                tmp = null;
                                s = -1;
                            } else {
                                s = -1;
                            }

                        }
                    } else if (befCode!= '\\' && coteChr == nowChr) {

                        coteChr = 0;

                        if (s == i && coteFlg == true) {
                            out.add(new StringBuilder().append(strCode).append(
                                    strCode).toString());
                            s = -1;
                        } else if (s < i) {

                            if (coteFlg == true) {
                                tmp = str.substring(s - 1, i + 1);
                            } else {
                                tmp = str.substring(s, i);
                            }

                            out.add(tmp);
                            tmp = null;
                            s = -1;
                        } else {
                            s = -1;
                        }

                    }
                    befCode = strCode ;
                }
            } else {
                int befCode = -1 ;
                for (i = 0, s = -1, coteChr = 0; i < len; i++) {

                    strCode = str.charAt(i);
                    nowChr = strCode;
                    s = (s == -1) ? i : s;

                    if (coteChr == 0) {

                        if (nowChr == '\'' || nowChr == '\"') {

                            coteChr = nowChr;

                            if (s < i) {
                                tmp = str.substring(s, i);
                                out.add(tmp);
                                tmp = null;
                                s = -1;
                            } else {
                                s = -1;
                            }

                        } else {

                            for (j = 0; j < lenJ; j++) {

                                if (strCode == checkCode[j]) {

                                    if (s < i) {
                                        tmp = str.substring(s, i);
                                        out.add(tmp);
                                        tmp = null;
                                        s = -1;
                                    } else {
                                        s = -1;
                                    }

                                    break;

                                }

                            }

                        }
                    } else if (befCode != '\\' && coteChr == nowChr) {

                        coteChr = 0;

                        if (s == i && coteFlg == true) {
                            out.add(new StringBuilder().append(strCode).append(
                                    strCode).toString());
                            s = -1;
                        } else if (s < i) {

                            if (coteFlg == true) {
                                tmp = str.substring(s - 1, i + 1);
                            } else {
                                tmp = str.substring(s, i);
                            }

                            out.add(tmp);
                            tmp = null;
                            s = -1;
                        } else {
                            s = -1;
                        }

                    }
                    befCode = strCode ;
                }
            }

            if (s != -1) {

                if (coteChr != 0 && coteFlg == true) {
                    tmp = str.substring(s - 1, len) + (char) coteChr;
                } else {
                    tmp = str.substring(s, len);
                }

                out.add(tmp);
                tmp = null;

            }

            checkCode = null;
            tmp = null;

        }
    }

    /**
     * コーテーション以外の文字列の中で、指定文字で区切る. <BR>
     * <BR>
     * 
     * @param string
     *            対象文字列を設定します.
     * @param check
     *            区切り文字列群を設定します.
     * @return ArrayList<String> 区切られた文字列が返されます.
     */
    public static final ArrayList<String> parseString(String string,
            String check) {
        int cote = -1;
        int len = string.length();
        ArrayList<String> lst = new ArrayList<String>();
        StringBuilder buf = new StringBuilder();
        int lenJ = check.length();
        char b = 0;
        boolean end = false;
        for (int i = 0; i < len; i++) {
            end = false;
            char c = string.charAt(i);
            if (cote != -1) {
                buf.append(c);
                if (c == cote && ((i != 0 && b != '\\') || i == 0)) {
                    cote = -1;
                }
            } else if ((c == '\'' || c == '\"')
                    && ((i != 0 && b != '\\') || i == 0)) {
                buf.append(c);
                cote = c;
            } else {
                boolean chk = false;
                for (int j = 0; j < lenJ; j++) {
                    char x = check.charAt(j);
                    if (c == x) {
                        end = true;
                        lst.add(buf.toString().trim());
                        buf = new StringBuilder();
                        chk = true;
                        break;
                    }
                }
                if (chk == false) {
                    buf.append(c);
                }
            }
            b = c;
        }
        if (buf.length() > 0 || end == true) {
            lst.add(buf.toString().trim());
        }
        buf = null;
        return lst;
    }

    /**
     * 指定文字位置の１つ前の情報を取得. <BR>
     * <BR>
     * 
     * @param string
     *            対象の文字列を設定します.
     * @param no
     *            対象の項番を設定します.
     * @return int 対象の文字コードが返されます.
     */
    public static final int getBefore(String string, int no) {
        if (string == null || string.length() <= 0 || no <= 0) {
            return -1;
        }
        return (int) (string.charAt(no - 1) & 0x0000ffff);
    }

    /**
     * 指定文字位置の１つ後ろの情報を取得. <BR>
     * <BR>
     * 
     * @param string
     *            対象の文字列を設定します.
     * @param no
     *            対象の項番を設定します.
     * @return int 対象の文字コードが返されます.
     */
    public static final int getNext(String string, int no) {
        if (string == null || string.length() <= no + 1) {
            return -1;
        }
        return (int) (string.charAt(no + 1) & 0x0000ffff);
    }

    /**
     * 指定内容の文字数を取得. <BR>
     * <BR>
     * 指定内容の文字列を取得します.<BR>
     * また、コーテーション内の情報はカウントしません. <BR>
     * 
     * @param string
     *            対象の文字列を設定します.
     * @param check
     *            カウント対象のキャラクタを設定します.
     * @return int 対象の文字数が返されます.
     */
    public static final int getCount(String string, char check) {
        if (string == null || string.length() <= 0) {
            return 0;
        }
        int ret = 0;
        int cote = -1;
        char b = 0;
        int len = string.length();
        for (int i = 0; i < len; i++) {
            char c = string.charAt(i);
            if (cote != -1) {
                if (c == cote && ((i != 0 && b != '\\') || i == 0)) {
                    cote = -1;
                }
            } else if ((c == '\'' || c == '\"')
                    && ((i != 0 && b != '\\') || i == 0)) {
                cote = c;
            } else if (check == c) {
                ret++;
            }
            b = c;
        }
        return ret;
    }

    /**
     * 指定コードを置き換えます. <BR>
     * <BR>
     * 指定文字列に対して、変更対象の文字列を指定して 変更後の文字列に置き換えます. <BR>
     * 
     * @param str
     *            変更を行う文字列を指定します.
     * @param src
     *            変更対象の文字列を指定します.
     * @param dest
     *            変更後の文字列を指定します.
     * @return String 変更された文字列が返されます.
     * @exception IllegalArgumentException
     *                入力例外.
     */
    public static final String changeString(String str, String src, String dest)
            throws IllegalArgumentException {
        int pnt;
        int end;
        int srcLen;

        StringBuilder buf = null;

        if (str == null || str.length() <= 0 || src == null
                || (srcLen = src.length()) <= 0) {
            throw new IllegalArgumentException("引数は不正です");
        }

        if (dest == null || dest.length() <= 0) {
            dest = "";
        }

        buf = new StringBuilder();

        pnt = str.indexOf(src);
        if (pnt == -1) {
            return str;
        } else if (pnt == 0) {
            buf.append(dest);
            end = srcLen;
        } else {
            buf.append(str.substring(0, pnt));
            buf.append(dest);
            end = pnt + srcLen;
        }

        for (pnt += srcLen;;) {
            if ((pnt = str.indexOf(src, pnt)) == -1) {
                buf.append(str.substring(end));
                break;
            }
            if (pnt == end) {
                buf.append(dest);
            } else {
                buf.append(str.substring(end, pnt));
                buf.append(dest);
            }

            end = pnt + srcLen;
            pnt += srcLen;

        }

        return buf.toString();
    }

    /**
     * 文字列の前後コーテーションを削除. <BR>
     * <BR>
     * 文字列の前後コーテーションを削除します. <BR>
     * 
     * @param str
     *            変換対象の文字列を設定します.
     * @return String 変換された結果が返されます.
     */
    public static final String catCote(String str) {
        if (str == null || str.length() <= 0) {
            if (str == null) {
                return null;
            } else {
                return "";
            }
        }

        str = str.trim();

        return ((str.startsWith("\'") == true && str.endsWith("\'") == true) || (str
                .startsWith("\"") == true && str.endsWith("\"") == true)) ? str
                .substring(1, str.length() - 1) : str;
    }

    /**
     * 指定文字から、N文字情報を取得. <BR>
     * <BR>
     * 指定文字から、N文字情報を取得します. <BR>
     * 
     * @param str
     *            対象文字列を設定します.
     * @param start
     *            対象開始位置を設定します.
     * @param length
     *            対象文字列を設定します.
     * @return String 文字列が返されます.
     */
    public static final String getNLength(String str, int start, int length) {
        return str.substring(start, ((start + length >= str.length()) ? str
                .length() : start + length))
                + "...";
    }

    /**
     * 指定文字数を生成. <BR>
     * <BR>
     * 
     * @param chr
     *            対象の文字を設定します.
     * @param length
     *            生成対象の情報数を設定します.
     * @return String 文字列が返されます.
     */
    public static final String createString(char chr, int length) {
        if (length <= 0) {
            return "";
        }
        StringBuilder buf = new StringBuilder(length);
        for (int i = 0; i < length; i++) {
            buf.append(chr);
        }
        return buf.toString();
    }
    
    /**
     * CSVを区切ります.
     * <BR>
     * @param cote [true]の場合ダブルコーテーションに統一します.
     *              [false]の場合シングルコーテーションに統一します.
     * @param line １行の内容を設定します.
     * @param cutCode 区切り文字を設定します.
     * @return ArrayList<String> 区切られた内容が返されます.
     * @exception Exception 例外.
     */
    public static final ArrayList<String> getCsv( boolean cote,String line,String cutCode )
        throws Exception {
        if( line == null || line.length() <= 0 ) {
            return new ArrayList<String>() ;
        }
        if( cutCode == null || cutCode.length() <= 0 ) {
            cutCode = "," ;
        }
        ArrayList<String> tmp = new ArrayList<String>() ;
        int p = -1 ;
        int b = 0 ;
        int cutLen = cutCode.length() ;
        for( ;; ) {
            p = line.indexOf( cutCode,b ) ;
            if( p <= -1 ) {
                if( b == 0 ) {
                    tmp.add( line ) ;
                }
                else if( line.length() > b ) {
                    tmp.add( line.substring( b ) ) ;
                }
                break ;
            }
            char c = line.charAt( b ) ;
            if( c == '\'' || c == '\"' ) {
                int x = line.indexOf( c,b+1 ) ;
                if( x <= -1 ) {
                    tmp.add( new StringBuilder().append( line.substring( b ) ).append( c ).toString() ) ;
                    break ;
                }
                tmp.add( line.substring( b,x+1 ) ) ;
                b = x + 1 + cutLen ;
            }
            else {
                tmp.add( line.substring( b,p ) ) ;
                b = p + cutLen ;
            }
        }
        ArrayList<String> ret = new ArrayList<String>() ;
        int len = tmp.size() ;
        for( int i = 0 ; i < len ; i ++ ) {
            String x = tmp.get( i ) ;
            if( cote == true && x.startsWith( "\'" ) && x.endsWith( "\'") ) {
                ret.add( new StringBuilder().append( "\"" ).
                    append( x.substring( 1,x.length()-1 ) ).
                    append( "\"" ).toString() ) ;
            }
            else if( cote == false && x.startsWith( "\"" ) && x.endsWith( "\"") ) {
                ret.add( new StringBuilder().append( "\'" ).
                    append( x.substring( 1,x.length()-1 ) ).
                    append( "\'" ).toString() ) ;
            }
            else {
                ret.add( x ) ;
            }
        }
        tmp.clear() ;
        tmp = null ;
        return ret ;
    }
    
    /**
     * ShiftJis - MS932変換表.
     */
    private static final char[] SJIS_MS932_MAP = {
        '\u301C', '\uFF5E',
        '\u2016', '\u2225',
        '\u2212', '\uFF0D',
        '\u00A2', '\uFFE0',
        '\u00A3', '\uFFE1',
        '\u00AC', '\uFFE2'
    } ;
    
    /**
     * 指定文字コードをMS932に変換.
     * @param c 変換対象の文字を設定します.
     * @return char 変換された文字が返されます.
     */
    public static final char toMS932(char c) {
        int len = SJIS_MS932_MAP.length ;
        for (int i = 0; i < len ; i += 2) {
            if (c == SJIS_MS932_MAP[i]) {
                c = SJIS_MS932_MAP[i + 1];
                break;
            }
        }
        return c;
    }
    
    /**
     * 指定文字コードをShiftJISに変換.
     * @param c 変換対象の文字を設定します.
     * @return char 変換された文字が返されます.
     */
    public static final char toSJIS(char c) {
        int len = SJIS_MS932_MAP.length ;
        for (int i = 0; i < len ; i += 2) {
            if (c == SJIS_MS932_MAP[i + 1]) {
                c = SJIS_MS932_MAP[i];
                break;
            }
        }
        return c;
    }
    
    /**
     * 指定文字列をMS932に変換.
     * @param str 変換対象の文字列を設定します.
     * @return String 変換された文字列が返されます.
     */
    public static final String toMS932(String str) {
        if (str == null) {
            return null;
        }
        char[] c = new char[str.length()];
        int len = str.length() ;
        for (int i=0; i<len; i++) {
            c[i] = toMS932(str.charAt(i));
        }
        return new String(c);
    }
    
    /**
     * 指定文字列をShiftJISに変換.
     * @param str 変換対象の文字列を設定します.
     * @return String 変換された文字列が返されます.
     */
    public static final String toSJIS(String str) {
        if (str == null) {
            return null;
        }
        char[] c = new char[str.length()];
        int len = str.length() ;
        for (int i=0; i<len; i++) {
            c[i] = toSJIS(str.charAt(i));
        }
        return new String(c);
    }
    
    /**
     * 指定文字内のコーテーションインデントをあげる.
     * @param string 対象の文字列を設定します.
     * @return String 変換された文字列が返されます.
     */
    public static final String upIndentCote( String string ) {
        if( string == null || string.length() <= 0 ) {
            return string ;
        }
        if( string.indexOf( "\'" ) != -1 || string.indexOf( "\"" ) != -1 ) {
            int len = string.length() ;
            int yenCnt = 0 ;
            StringBuilder buf = new StringBuilder() ;
            for( int i = 0 ; i < len ; i ++ ) {
                int c = ( int )( string.charAt( i ) & 0x0000ffff ) ;
                if( c == '\'' || c == '\"' ) {
                    if( yenCnt == 0 ) {
                        buf.append( "\\" ) ;
                    }
                    else {
                        buf.append( "\\\\" ) ;
                    }
                }
                buf.append( ( char)c ) ;
                if( c == '\\' ) {
                    yenCnt ++ ;
                }
                else {
                    yenCnt = 0 ;
                }
            }
            return buf.toString() ;
        }
        return string ;
    }
}
