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

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.util.ArrayList;
import java.util.Calendar;

import org.maachang.proxy.engine.mobile.carrier.MobileData;
import org.maachang.proxy.engine.mobile.image.lib.ImageConvertor;
import org.maachang.proxy.engine.mobile.image.lib.ImageInfo;
import org.maachang.proxy.engine.net.RequestInfo;
import org.maachang.proxy.engine.net.ResponseInfo;
import org.maachang.proxy.engine.net.http.HttpParams;
import org.maachang.util.AnalysisUtil;
import org.maachang.util.FileUtil;
import org.maachang.util.StringUtil;
import org.maachang.util.conf.ConvIniParam;

/**
 * レスポンス情報を画像変換.
 *
 * @version 2008/04/08
 * @author  masahito suzuki
 * @since   MaachangProxy 1.01
 */
public class ConvertImage {
    
    /**
     * キャッシュディレクトリ.
     */
    private static final String CACHE_DIR = "./cache/" ;
    
    /**
     * 画像変換パラメータ : 基本パラメータ.
     */
    private static final String PARAM_BASE = "mobile" ;
    
    /**
     * 画像変換パラメータ : 比率.
     */
    private static final String PERSENT = "persent" ;
    
    /**
     * 画像変換パラメータ : レート.
     */
    private static final String RATE = "rate" ;
    
    /**
     * キャッシュモード.
     */
    private static final String CACHE_MODE = "cache" ;
    
    /**
     * 他スレッドがキャッシュ生成中の待機時間.
     */
    private static final int WAIT = 100 ;
    
    /** 同期オブジェクト */
    private static final Object sync = new Object() ;
    
    /**
     * レスポンス情報を画像変換します.
     * @param req 対象のリクエスト情報を設定します.
     * @param res 対象のレスポンス情報を設定します.
     * @param conf 対象の絵文字コンフィグ情報を設定します.
     * @param data 対象の携帯機種情報を設定します.
     * @return boolean [true]の場合、画像変換処理されました.
     * @exception Exception 例外.
     */
    public static final boolean convert( RequestInfo req,ResponseInfo res,MobileData data )
        throws Exception {
        if( data == null ) {
            return false ;
        }
        String type = res.getHeader( "Content-Type",0 ) ;
        if( isExecution( type ) == false ) {
            return false ;
        }
        HttpParams params = new HttpParams() ;
        AnalysisUtil.convertAnalysisQuery( params,req ) ;
        if( params.isKey( PARAM_BASE ) == false ) {
            return false ;
        }
        byte[] body = res.getBody() ;
        if( body == null || body.length <= 0 ) {
            return false ;
        }
        int persent = ConvIniParam.getInt( ( String )params.get( PERSENT,0 ) ) ;
        int rate = ConvIniParam.getInt( ( String )params.get( RATE,0 ) ) ;
        String cacheMode = ( String )params.get( CACHE_MODE,0 ) ;
        String imageType = ImageConvertor.JPEG_TYPE ;
        if( data.isJpegFlag() == true ) {
            imageType = ImageConvertor.JPEG_TYPE ;
        }
        else if( data.isPngFlag() == true ) {
            imageType = ImageConvertor.PNG_TYPE ;
        }
        else if( data.isGifFlag() == true ) {
            imageType = ImageConvertor.GIF_TYPE ;
        }
        String name = createCacheName( params,data,imageType,
            AnalysisUtil.cutGetParam( req.getUrl() ),
            body,persent,rate ) ;
        String file = CACHE_DIR + name ;
        boolean useCache = false ;
        synchronized( sync ) {
            useCache = FileUtil.isFileExists( file ) ;
        }
        if( useCache == true ) {
            for( ;; ) {
                int len = 0 ;
                synchronized( sync ) {
                    len = ( int )FileUtil.getLength( file ) ;
                }
                if( len <= 1 ) {
                    Thread.sleep( WAIT ) ;
                    continue ;
                }
                synchronized( sync ) {
                    useCache = FileUtil.isFileExists( file ) ;
                }
                break ;
            }
            if( useCache == true ) {
                if( isCache( file,cacheMode ) == false ) {
                    synchronized( sync ) {
                        body = FileUtil.getFile( file ) ;
                    }
                    setContentType( res,imageType ) ;
                    res.setBody( body ) ;
                    return true ;
                }
            }
        }
        int x = data.getX() ;
        int y = data.getY() ;
        if( rate <= 35 ) {
            rate = 75 ;
        }
        if( persent <= 25 ) {
            persent = 100 ;
        }
        try {
            if( cacheMode != null && ( cacheMode.startsWith( "x" ) || cacheMode.startsWith( "X" ) ) == false ) {
                synchronized( sync ) {
                    FileUtil.setFile( file,new byte[]{ ( byte )0 } ) ;
                }
            }
            ImageInfo img = ImageConvertor.loadImage( new ByteArrayInputStream( body ) ) ;
            body = null ;
            int w = img.getWidth() ;
            int h = img.getHeight() ;
            int cx = ( x * persent ) / 100 ;
            int cy = ( y * persent ) / 100 ;
            if( w >= h ) {
                double r = ( double )h / ( double )w ;
                w = cx ;
                h = ( int )( ( ( double )cx * r ) + 0.5f ) ;
            }
            else {
                double r = ( double )w / ( double )h ;
                w = ( int )( ( ( double )cy * r ) + 0.5f ) ;
                h = cy ;
            }
            ImageConvertor.convert( img,w,h,-1 ) ;
            ByteArrayOutputStream bo = new ByteArrayOutputStream() ;
            ImageConvertor.saveImage( img,bo,imageType,rate ) ;
            body = bo.toByteArray() ;
            if( cacheMode != null && ( cacheMode.startsWith( "x" ) || cacheMode.startsWith( "X" ) ) == false ) {
                synchronized( sync ) {
                    FileUtil.setFile( file,body ) ;
                }
            }
            setContentType( res,imageType ) ;
            res.setBody( body ) ;
            return true ;
        } catch( Exception e ) {
            try {
                FileUtil.removeFile( file ) ;
            } catch( Exception ee ) {
            }
            throw e ;
        }
    }
    
    /** 画像変換対象のコンテンツタイプ. */
    private static final boolean isExecution( String type ) {
        return type != null && ( type.startsWith( "image/x-bmp" ) ||
            type.startsWith( "image/gif" ) ||
            type.startsWith( "image/jpeg" ) ||
            type.startsWith( "image/x-png" ) ||
            type.startsWith( "image/tiff" ) ) ;
    }
    
    /** 指定条件から、キャッシュ名を生成. */
    private static final String createCacheName( HttpParams params,MobileData data,
        String imageType,String url,byte[] body,int persent,int rate ) {
        StringBuilder buf = new StringBuilder() ;
        ArrayList<String> lst = StringUtil.cutString( url,":/.-" ) ;
        int len = lst.size() ;
        for( int i = 0 ; i < len ; i ++ ) {
            if( i != 0 ) {
                buf.append( "_" ) ;
            }
            buf.append( lst.get( i ) ) ;
        }
        buf.append( "_" ).append( imageType.substring( 0,1 ) ) ;
        lst = null ;
        if( params.isKey( "id" ) == true ) {
            buf.append( "_" ).append( "id-" ).append( params.get( "id",0 ) ) ;
        }
        buf.append( "_" ).append( Integer.toHexString( body.length ) ).
            append( "_" ).append( Integer.toHexString( getImageCode( body ) ) ) ;
        buf.append( "_" ).append( data.getX() ).
            append( "_" ).append( data.getY() ) ;
        buf.append( "_" ) ;
        if( rate <= -1 ) {
            buf.append( "@0" ) ;
        }
        else {
            buf.append( "@" ) ;
            buf.append( rate ) ;
        }
        buf.append( "_" ) ;
        if( persent <= -1 ) {
            buf.append( "0@" ) ;
        }
        else {
            buf.append( persent ) ;
            buf.append( "@" ) ;
        }
        return buf.toString() ;
    }
    
    /** バイナリから4点データを取得して、コードを生成. */
    private static final int getImageCode( byte[] body ) {
        int len = body.length ;
        return ( int )( body[ len-1 ] & 0x000000ff ) |
            ( int )( ( body[ len / 2 ] & 0x000000ff ) << 8 ) |
            ( int )( ( body[ ( len / 2 ) - ( len / 4 ) ] & 0x000000ff ) << 16 ) |
            ( int )( ( body[ ( len / 2 ) + ( len / 4 ) ] & 0x000000ff ) << 24 ) ;
    }
    
    /** キャッシュモードから、キャッシュ更新条件を取得. */
    private static final boolean isCache( String file,String cacheMode ) {
        if( cacheMode == null || ( cacheMode = cacheMode.trim() ).length() <= 0 ) {
            return false ;
        }
        long lastTime = 0L ;
        synchronized( sync ) {
            lastTime = FileUtil.getLastTime( file ) ;
        }
        // 月に１度更新.
        if( cacheMode.startsWith( "M" ) ) {
            synchronized( sync ) {
                Calendar cl = Calendar.getInstance() ;
                cl.setTimeInMillis( lastTime ) ;
                cl.add( Calendar.MONTH,1 ) ;
                lastTime = cl.getTime().getTime() ;
            }
        }
        // 日に１度更新.
        else if( cacheMode.startsWith( "d" ) ) {
            lastTime += 60L * 60L * 24L * 1000L ;
        }
        // 時間に１度更新.
        else if( cacheMode.startsWith( "h" ) ) {
            lastTime += 60L * 60L * 1000L ;
        }
        // 分に１度更新.
        else if( cacheMode.startsWith( "m" ) ) {
            lastTime += 60L * 1000L ;
        }
        // キャッシュを作成しない.
        else if( cacheMode.startsWith( "x" ) || cacheMode.startsWith( "X" ) ) {
            return true ;
        }
        // その他.
        else {
            return false ;
        }
        if( lastTime <= System.currentTimeMillis() ) {
            return true ;
        }
        return false ;
    }
    
    /** コンテンツタイプを設定 */
    private static final void setContentType( ResponseInfo res,String imageType ) {
        if( ImageConvertor.JPEG_TYPE.equals( imageType ) ) {
            res.putHeader( "Content-Type","image/jpeg" ) ;
        }
        else if( ImageConvertor.GIF_TYPE.equals( imageType ) ) {
            res.putHeader( "Content-Type","image/gif" ) ;
        }
        else if( ImageConvertor.PNG_TYPE.equals( imageType ) ) {
            res.putHeader( "Content-Type","image/x-png" ) ;
        }
    }
}
