package org.maachang.proxy.engine.mobile.emoji ;

import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.HashMap;

import org.maachang.proxy.engine.mobile.carrier.MobileData;
import org.maachang.proxy.engine.net.RequestInfo;
import org.maachang.util.AnalysisUtil;
import org.maachang.util.URLEncode;
import org.maachang.util.conf.Config;
import org.maachang.util.conf.ReadIni;

/**
 * 絵文字格納データ.
 *
 * @version 2008/04/10
 * @author  masahito suzuki
 * @since   MaachangProxy 1.01
 */
public class EmojiConfig {
    
    /**
     * 逆絵文字ヘッダ.
     */
    private static final String REV_EMOJI_HEAD = "%5Bemoji%3A" ;
    
    /**
     * 逆絵文字フッダ.
     */
    private static final String REV_EMOJI_FOODER = "%5D" ;
    
    /**
     * 変換失敗絵文字.
     */
    private static final String NOT_EMOJI = "〓" ;
    
    /**
     * 絵文字コンフィグファイル.
     */
    private static final String CONF = "./conf/carrier/emoji.conf" ;
    
    /**
     * 絵文字格納情報.
     */
    private HashMap<String,HashMap<String,byte[]>> manager = null ;
    
    /**
     * 絵文字逆変換用ヘッダ情報.
     */
    private HashMap<String,ArrayList<String>> reverseHeader = null ;
    
    /**
     * 絵文字逆変換用フッダ情報.
     */
    private HashMap<String,String> reverseFooder = null ;
    
    /**
     * 絵文字逆変換用ヘッダ詳細(絵文字格納長を設定).
     */
    private HashMap<String,Integer> reverseHeaderDetail = null ;
    
    /**
     * 絵文字逆変換用charset.
     */
    private HashMap<String,EmojiType> emojiType = null ;
    
    /**
     * 絵文字逆変換用情報.
     */
    private HashMap<String,HashMap<String,String>> reverse = null ;
    
    /**
     * コンストラクタ.
     * @exception Exception 例外.
     */
    public EmojiConfig()
        throws Exception {
        read() ;
    }
    
    private void read()
        throws Exception {
        try {
            Config conf = null ;
            BufferedReader br = new BufferedReader( new InputStreamReader(
                new FileInputStream( CONF ),"UTF8" ) ) ;
            try {
                conf = new Config() ;
                ReadIni.analisys( conf,br ) ;
                br.close() ;
                br = null ;
            } finally {
                if( br != null ) {
                    try {
                        br.close() ;
                    } catch( Exception e ) {
                    }
                }
            }
            manager = new HashMap<String,HashMap<String,byte[]>>() ;
            reverseHeader = new HashMap<String,ArrayList<String>>() ;
            reverseFooder = new HashMap<String,String>() ;
            reverseHeaderDetail = new HashMap<String,Integer>() ;
            reverse = new HashMap<String,HashMap<String,String>>() ;
            emojiType = new HashMap<String,EmojiType>() ;
            analysisConf( conf ) ;
        } catch( Exception e ) {
            manager = null ;
            throw e ;
        }
    }
    
    private void analysisConf( Config conf )
        throws Exception {
        Object[] sections = conf.getSections() ;
        if( sections != null ) {
            int len = sections.length ;
            for( int i = 0 ; i < len ; i ++ ) {
                String section = ( String )sections[ i ] ;
                manager.put( section,analysisSection( conf,section ) ) ;
                setReverseHeader( section,conf ) ;
                setReverseFooder( section,conf ) ;
                setReverse( section,conf ) ;
                setEmojiType( section,conf ) ;
            }
        }
    }
    
    private HashMap<String,byte[]> analysisSection( Config conf,String section )
        throws Exception {
        HashMap<String,byte[]> ret = null ;
        String headerStr = conf.get( section,"header",0 ) ;
        String fooderStr = conf.get( section,"fooder",0 ) ;
        if( headerStr == null || ( headerStr = headerStr.trim() ).length() <= 0 ) {
            headerStr = "" ;
        }
        if( fooderStr == null || ( fooderStr = fooderStr.trim() ).length() <= 0 ) {
            fooderStr = "" ;
        }
        String baseCharset = conf.get( section,"charset",0 ) ;
        String paramMode = conf.get( section,"param-mode",0 ) ;
        String[] keys = conf.getKeys( section ) ;
        if( keys != null && keys.length > 0 ) {
            ret = new HashMap<String,byte[]>() ;
            int len = keys.length ;
            for( int i = 0 ; i < len ; i ++ ) {
                String key = keys[ i ] ;
                if( key == null || ( key = key.trim() ).length() <= 0 ||
                    "header".equals( key ) || "fooder".equals( key ) ) {
                    continue ;
                }
                byte[] valueBin = null ;
                String value = conf.get( section,key,0 ) ;
                if( value.startsWith( "0x" ) ) {
                    byte[] cb = convertBinary( value ) ;
                    if( headerStr.length() > 0 || fooderStr.length() > 0 ) {
                        byte[] headerBin = null ;
                        byte[] fooderBin = null ;
                        int binLen = 0 ;
                        if( headerStr.length() > 0 ) {
                            headerBin = headerStr.getBytes( baseCharset ) ;
                            binLen += headerBin.length ;
                        }
                        if( fooderStr.length() > 0 ) {
                            fooderBin = fooderStr.getBytes( baseCharset ) ;
                            binLen += fooderBin.length ;
                        }
                        binLen += cb.length ;
                        valueBin = new byte[ binLen ] ;
                        int binP = 0 ;
                        if( headerBin != null ) {
                            System.arraycopy( headerBin,0,valueBin,binP,headerBin.length ) ;
                            binP += headerBin.length ;
                        }
                        System.arraycopy( cb,0,valueBin,binP,cb.length ) ;
                        binP += cb.length ;
                        if( fooderBin != null ) {
                            System.arraycopy( fooderBin,0,valueBin,binP,fooderBin.length ) ;
                            binP += fooderBin.length ;
                        }
                    }
                    else {
                        valueBin = cb ;
                    }
                }
                else if( isNumber( value,paramMode ) == true ) {
                    valueBin = new StringBuilder().
                        append( headerStr ).
                        append( value ).
                        append( fooderStr ).
                        toString().getBytes( baseCharset ) ;
                }
                else {
                    valueBin = value.getBytes( baseCharset ) ;
                }
                ret.put( key,valueBin ) ;
            }
        }
        return ret ;
    }
    
    private boolean isNumber( String code,String paramMode ) {
        if( code == null || code.length() <= 0 ) {
            return false ;
        }
        int len = code.length() ;
        if( "10".equals( paramMode ) ) {
            for( int i = 0 ; i < len ; i ++ ) {
                char c = code.charAt( i ) ;
                if( c >= '0' && c <= '9' ) {
                    continue ;
                }
                else {
                    return false ;
                }
            }
            return true ;
        }
        else if( "16".equals( paramMode ) ) {
            for( int i = 0 ; i < len ; i ++ ) {
                char c = code.charAt( i ) ;
                if( ( c >= '0' && c <= '9' ) ||
                    ( c >= 'a' && c <= 'f' ) ||
                    ( c >= 'A' && c <= 'F' ) ) {
                    continue ;
                }
                else {
                    return false ;
                }
            }
            return true ;
        }
        return false ;
    }
    
    private byte[] convertBinary( String value ) {
        value = value.substring( 2 ) ;
        int len = value.length() / 2 ;
        byte[] ret = new byte[ len ] ;
        int c = 0 ;
        for( int i = 0 ; i < len ; i ++ ) {
            ret[ i ] = ( byte )( Integer.parseInt( value.substring( c,c+2 ),16 ) & 0x000000ff ) ;
            c += 2 ;
        }
        return ret ;
    }
    
    private byte[] getPcEmoji( String code )
        throws Exception {
        return new StringBuilder().append( "<img src=\"/@emoji/" ).append( code ).
            append( "\">" ).toString().getBytes( "UTF8" ) ;
    }
    
    private void setReverseHeader( String section,Config conf )
        throws Exception {
        int len = conf.size( section,"post-header" ) ;
        if( len > 0 ) {
            ArrayList<String> lst = new ArrayList<String>() ;
            for( int i = 0 ; i < len ; i ++ ) {
                lst.add( conf.get( section,"post-header",i ) ) ;
            }
            reverseHeader.put( section,lst ) ;
        }
        if( conf.size( section,"post-fooder" ) > 0 ) {
            int pos = conf.getInt( section,"reverse-header-length",0 ) ;
            int fooder = conf.getInt( section,"reverse-fooder-length",0 ) ;
            int dataLen = ( conf.getInt( section,"reverse-length",0 ) - ( pos + fooder ) ) + 2 ;
            reverseHeaderDetail.put( section,new Integer( dataLen ) ) ;
        }
        else {
            reverseHeaderDetail.put( section,
                new Integer( conf.get( section,"reverse-length",0 ) ) ) ;
        }
    }
    
    private void setReverseFooder( String section,Config conf )
        throws Exception {
        if( conf.size( section,"post-fooder" ) > 0 ) {
            reverseFooder.put( section,conf.get( section,"post-fooder",0 ) ) ;
        }
    }
    
    private void setReverse( String section,Config conf )
        throws Exception {
        int n = conf.getInt( section,"post-emoji",0 ) ;
        if( n <= -1 ) {
            return ;
        }
        HashMap<String,String> map = new HashMap<String,String>() ;
        // SoftBank用.
        if( conf.getBoolean( section,"array",0 ) == true ) {
            int pos = conf.getInt( section,"reverse-header-length",0 ) ;
            int fooder = conf.getInt( section,"reverse-fooder-length",0 ) ;
            int dataLen = conf.getInt( section,"reverse-length",0 ) - ( pos + fooder ) ;
            for( int i = 1 ; i <= 176 ; i ++ ) {
                String key = convertReverseKey( i ) ;
                if( key == null ) {
                    continue ;
                }
                String val = convertReverseData( key,n,pos,dataLen,section,conf ) ;
                if( val == null ) {
                    continue ;
                }
                map.put( val,key.substring( 2 ) ) ;
            }
            for( int i = 201 ; i <= 276 ; i ++ ) {
                String key = convertReverseKey( i ) ;
                if( key == null ) {
                    continue ;
                }
                String val = convertReverseData( key,n,pos,dataLen,section,conf ) ;
                if( val == null ) {
                    continue ;
                }
                map.put( val,key.substring( 2 ) ) ;
            }
        }
        // DoCoMo,au用.
        else {
            for( int i = 1 ; i <= 176 ; i ++ ) {
                String key = convertReverseKey( i ) ;
                if( key == null ) {
                    continue ;
                }
                String val = convertReverseData( key,n,2,-1,section,conf ) ;
                if( val == null ) {
                    continue ;
                }
                map.put( val,key.substring( 2 ) ) ;
            }
            for( int i = 201 ; i <= 276 ; i ++ ) {
                String key = convertReverseKey( i ) ;
                if( key == null ) {
                    continue ;
                }
                String val = convertReverseData( key,n,2,-1,section,conf ) ;
                if( val == null ) {
                    continue ;
                }
                map.put( val,key.substring( 2 ) ) ;
            }
        }
        reverse.put( section,map ) ;
    }
    
    private void setEmojiType( String section,Config conf ) {
        emojiType.put( section,
            new EmojiType( conf.get( section,"charset",0 ),conf.get( section,"type",0 ) ) ) ;
    }
    
    private String convertReverseKey( int no ) {
        String key = String.valueOf( no ) ;
        return new StringBuilder().append( "e-" ).
            append( "000".substring( key.length() ) ).append( key ).toString() ;
    }
    
    private String convertReverseData( String key,int no,int off,int len,String section,Config conf )
        throws Exception {
        String val = conf.get( section,key,no ) ;
        if( val == null || ( val = val.trim() ).length() <= 0 ) {
            return null ;
        }
        if( val.startsWith( "0x" ) == false ) {
            return null ;
        }
        val = val.toLowerCase() ;
        StringBuilder buf = new StringBuilder() ;
        if( len <= 0 ) {
            len = val.length() ;
        }
        else {
            len += off ;
        }
        for( int i = off ; i < len ; i += 2 ) {
            buf.append( "%" ).append( val.substring( i,i+2 ) ) ;
        }
        return buf.toString() ;
    }
    
    /**
     * 指定キャリアのキャラクタセットを取得.
     * @param carrier 対象のキャリア名を設定します.
     * @return String 対象のキャリアキャラクタセットが返されます.
     */
    public String getCharset( String carrier ) {
        EmojiType type = emojiType.get( carrier ) ;
        if( type == null ) {
            return "UTF8" ;
        }
        return type.getCharset() ;
    }
    
    /**
     * 対象キャラクタセットを取得.
     * @param data 携帯データを設定します.
     * @return String 対象のキャリア名が返されます.
     */
    public String getCarrierName( MobileData data ) {
        if( data == null ) {
            return null ;
        }
        String ret = data.getCarrier() ;
        if( isUseType( ret ) == true && isType( ret,data.getType() ) == false ) {
            ret += "-next" ;
        }
        return ret ;
    }
    
    /**
     * 指定キャリア内に携帯タイプが存在するかチェック.
     * @param carrier 対象のキャリア名を設定します.
     * @return boolean [true]の場合、携帯タイプが存在します.
     */
    public boolean isUseType( String carrier ) {
        EmojiType t = emojiType.get( carrier ) ;
        if( t == null ) {
            return false ;
        }
        return ( t.getTypes() == null ) ? false : true ;
    }
    
    /**
     * 指定キャリア内の携帯タイプが一致するかチェック.
     * @param carrier 対象のキャリア名を設定します.
     * @param type 対象の携帯タイプを設定します.
     * @return boolean [true]の場合、携帯タイプとマッチします.
     */
    public boolean isType( String carrier,String type ) {
        if( type == null || ( type = type.trim() ).length() <= 0 ) {
            return false ;
        }
        EmojiType t = emojiType.get( carrier ) ;
        if( t == null ) {
            return false ;
        }
        return t.isType( type.toLowerCase() ) ;
    }
    
    /**
     * 指定絵文字IDから、格納用バイナリ情報を取得.
     * @param carrier 対象のキャリア名を設定します.
     * @param code 対象の絵文字コードを設定します.
     * @return byte[] 対象のバイナリ情報が返されます.
     * @exception Exception 例外.
     */
    public byte[] getEmoji( String carrier,String code )
        throws Exception {
        if( code == null || ( code = code.trim() ).length() <= 0 ) {
            return null ;
        }
        else if( isNumber( code,"10" ) == false || code.length() >= 4 ) {
            return null ;
        }
        code = "000".substring( code.length() ) + code ;
        if( carrier == null || ( carrier = carrier.trim() ).length() <= 0 ) {
            return getPcEmoji( code ) ;
        }
        HashMap<String,byte[]> ch = manager.get( carrier.toLowerCase() ) ;
        if( ch == null ) {
            return getPcEmoji( code ) ;
        }
        return ch.get( "e-"+code ) ;
    }
    
    /**
     * リクエストBody内容から、逆絵文字変換.
     * @param req 対象のリクエスト情報を設定します.
     * @param mobileData 対象のアクセス機種情報を設定します.
     * @param carrier 対象の携帯キャリア名を設定します.
     */
    public void convertRequestBody( RequestInfo request,MobileData mobileData,String carrier )
        throws Exception {
        ArrayList<String> header = reverseHeader.get( carrier ) ;
        String fooder = reverseFooder.get( carrier ) ;
        if( header == null ) {
            return ;
        }
        HashMap<String,String> revs = reverse.get( carrier ) ;
        int reverseLength = reverseHeaderDetail.get( carrier ).intValue() ;
        if( request.getBody() != null && request.getBody().length > 0 ) {
            String body = new String( request.getBody(),"ISO-8859-1" ) ;
            body = convertReverseData( mobileData,carrier,body,header,fooder,revs,reverseLength ) ;
            request.setBody( body.getBytes( "ISO-8859-1" ) ) ;
        }
        String body = getGetBody( request.getUrl() ) ;
        if( body != null && body.length() > 0 ) {
            body = convertReverseData( mobileData,carrier,body,header,fooder,revs,reverseLength ) ;
            request.setUrl( new StringBuilder().
                append( AnalysisUtil.cutGetParam( request.getUrl() ) ).
                append( "?" ).append( body ).toString() ) ;
        }
    }
    
    private String convertReverseData( MobileData mobileData,String carrier,String body,ArrayList<String> header,
        String fooder,HashMap<String,String> revs,int reverseLength )
        throws Exception {
        body = EmojiEncode.convertBodyByCharset( body,emojiType.get( carrier ).getCharset() ) ;
        // fooderが存在する場合.
        if( fooder != null ) {
            if( header.size() != 1 ) {
                return body ;
            }
            String head = header.get( 0 ) ;
            for( int b = 0;; ) {
                int p = body.indexOf( head,b ) ;
                if( p <= -1 ) {
                    break ;
                }
                int x = body.indexOf( fooder,p+head.length() ) ;
                if( x <= -1 ) {
                    break ;
                }
                int dataLen = x - ( p+head.length() ) ;
                if( dataLen % reverseLength != 0 ) {
                    b = p + head.length() ;
                    continue ;
                }
                int len = dataLen / reverseLength ;
                int ps = p + head.length() ;
                StringBuilder buf = new StringBuilder() ;
                for( int i = 0 ; i < len ; i ++ ) {
                    String s = body.substring( ps,ps+reverseLength ) ;
                    String val = revs.get( s ) ;
                    if( val != null ) {
                        buf.append( REV_EMOJI_HEAD ).
                            append( val ).append( REV_EMOJI_FOODER )  ;
                    }
                    else {
                        buf.append( URLEncode.convert( NOT_EMOJI,emojiType.get( carrier ).getCharset() ) ) ;
                    }
                    ps += reverseLength ;
                }
                String s = buf.toString() ;
                buf = null ;
                body = new StringBuilder().append( body.substring( 0,p ) ).
                    append( s ).append( body.substring( x + fooder.length() ) ).
                    toString() ;
            }
        }
        // fooderが存在しない場合.
        else {
            int len = header.size() ;
            for( int i = 0 ; i < len ; i ++ ) {
                String hd = header.get( i ) ;
                for( int b = 0;; ) {
                    int p = body.indexOf( hd,b ) ;
                    if( p <= -1 ) {
                        break ;
                    }
                    if( p + reverseLength > body.length() ) {
                        break ;
                    }
                    String key = body.substring( p,p+reverseLength ) ;
                    String val = revs.get( key ) ;
                    if( val != null ) {
                        val = new StringBuilder().append( REV_EMOJI_HEAD ).
                            append( val ).append( REV_EMOJI_FOODER ).toString() ;
                        b = p + val.length() ;
                    }
                    else {
                        val = URLEncode.convert( NOT_EMOJI,emojiType.get( carrier ).getCharset() ) ;
                        b = p + reverseLength ;
                    }
                    body = new StringBuilder().append( body.substring( 0,p ) ).
                        append( val ).append( body.substring( p + reverseLength ) ).
                        toString() ;
                }
            }
        }
        return body ;
    }
    
    private String getGetBody( String url ) {
        int p = url.indexOf( "?" ) ;
        if( p >= 0 ) {
            return url.substring( p+1 ) ;
        }
        return null ;
    }
    
    public static final void main( String[] args ) throws Exception {
        //String body = "%1b%24%47%72%47%6f%0fabc%1b%24%47%3c%0f" ;
        //String carrier = "softbank" ;
        //String type = "C4" ;
        String body = "%f8a3abc%f8a4" ;
        String carrier = "docomo" ;
        String type = "mova" ;
        EmojiConfig emj = new EmojiConfig() ;
        RequestInfo req = new RequestInfo() ;
        req.setUrl( "/" ) ;
        req.setMethod( "POST" ) ;
        req.setBody( body.getBytes("ISO-8859-1") ) ;
        MobileData m = new MobileData() ;
        m.setCarrier( carrier ) ;
        m.setType( type ) ;
        emj.convertRequestBody( req,m,emj.getCarrierName( m ) ) ;
        System.out.println( new String( req.getBody(),"ISO-8859-1" ) ) ;
    }
    
}


