package org.maachang.comet.net;

import java.io.IOException;
import java.io.OutputStream;

import org.maachang.comet.conf.MimeConfig;
import org.maachang.comet.conf.ServiceDef;
import org.maachang.comet.httpd.HttpdErrorDef;
import org.maachang.comet.httpd.HttpdHeaders;
import org.maachang.comet.httpd.HttpdVersionDef;
import org.maachang.comet.httpd.engine.HttpdDef;
import org.maachang.comet.httpd.engine.HttpdTimestamp;
import org.maachang.comet.httpd.engine.script.ScriptDef;
import org.maachang.comet.net.nio.ConnectionInfo;
import org.maachang.manager.GlobalManager;
import org.maachang.util.FileUtil;

/**
 * HTTPD出力用.
 * 
 * @version 2007/08/19
 * @author masahito suzuki
 * @since MaachangComet 1.00
 */
public class NetWriteHttpd {
    
    /**
     * キャラクタセット.
     */
    private static final String CHARSET = "UTF8" ;
    
    /**
     * 改行情報.
     */
    private static final String ENTER = "\r\n" ;
    
    /**
     * URL.
     */
    private String url = null ;
    
    /**
     * データ出力用.
     */
    private OutputStream outputStream = null ;
    
    /**
     * ヘッダキャッシュ.
     */
    private StringBuilder cacheHeader = null ;
    
    /**
     * コンテンツ長.
     */
    private String contentLength = null ;
    
    /**
     * GZIPモード.
     */
    private boolean gzipFlag = false ;
    
    /**
     * not-cacheフラグ.
     */
    private boolean notCacheFlag = false ;
    
    /**
     * クローズフラグ.
     */
    private boolean closeFlag = true ;
    
    /**
     * KeepAliveFlag.
     */
    private boolean keepAliveFlag = false ;
    
    /**
     * chunkedFlag.
     */
    private boolean chunkedFlag = true ;
    
    /**
     * HTTPバージョン.
     */
    private String version = null ;
    
    /**
     * ステータス.
     */
    private int state = 200 ;
    
    /**
     * コンストラクタ.
     */
    private NetWriteHttpd() {
        
    }
    
    /**
     * コンストラクタ.
     * <BR><BR>
     * 条件を指定してオブジェクトを設定します.
     * <BR>
     * @param connectinoInfo コネクション情報を設定します.
     * @param url 対象のURLを設定します.
     * @param state 対象のステータスを設定します.
     * @exception Exception 例外.
     */
    public NetWriteHttpd( ConnectionInfo connectinoInfo,String url,int state )
        throws Exception {
        this( connectinoInfo,url,state,null,false ) ;
    }
    
    /**
     * コンストラクタ.
     * <BR><BR>
     * 条件を指定してオブジェクトを設定します.
     * <BR>
     * @param connectinoInfo コネクション情報を設定します.
     * @param url 対象のURLを設定します.
     * @param state 対象のステータスを設定します.
     * @param version HTTPバージョンを設定します.
     * @exception Exception 例外.
     */
    public NetWriteHttpd( ConnectionInfo connectinoInfo,String url,int state,String version )
        throws Exception {
        this( connectinoInfo,url,state,version,false ) ;
    }
    
    /**
     * コンストラクタ.
     * <BR><BR>
     * 条件を指定してオブジェクトを設定します.
     * <BR>
     * @param connectionInfo コネクション情報を設定します.
     * @param url 対象のURLを設定します.
     * @param state 対象のステータスを設定します.
     * @param version HTTPバージョンを設定します.
     * @param keepAliveFlag 対象のKeepAliveFlagを設定します.
     * @exception Exception 例外.
     */
    public NetWriteHttpd( ConnectionInfo connectinoInfo,String url,int state,String version,boolean keepAliveFlag )
        throws Exception {
        if( connectinoInfo == null || connectinoInfo.isUse() == false ) {
            throw new IOException( "コネクションオブジェクトは無効です" ) ;
        }
        if( url == null || ( url = url.trim() ).length() <= 0 ) {
            throw new IOException( "URLは不正です" ) ;
        }
        if( version == null || ( version = version.trim() ).length() <= 0 ) {
            version = HttpdDef.VERSION_11 ;
        }
        this.version = version ;
        this.keepAliveFlag = keepAliveFlag ;
        this.state = state ;
        this.outputStream = connectinoInfo.getOutputStream() ;
        this.url = url ;
    }
    
    /**
     * オブジェクトクローズ.
     * <BR><BR>
     * オブジェクトをクローズします.
     */
    public void close() {
        if( outputStream != null ) {
            try {
                outputStream.flush() ;
            } catch( Exception e ) {
            }
            try {
                outputStream.close() ;
            } catch( Exception e ) {
            }
        }
        outputStream = null ;
        url = null ;
        outputStream = null ;
        cacheHeader = null ;
        contentLength = null ;
        version = null ;
    }
    
    /**
     * 基本ヘッダを生成.
     * <BR><BR>
     * 基本ヘッダを生成します.
     */
    public void baseHeader() {
        outputHttp( version,state ) ;
    }
    
    /**
     * ヘッダを追加.
     * <BR><BR>
     * ヘッダを追加します.
     * <BR>
     * @param key 対象のキーを設定します.
     * @param value 対象のValueを設定します.
     */
    public void pushHeader( String key,String value ) {
        this.outputHeader( key,value ) ;
    }
    
    /**
     * ヘッダ群を追加.
     * <BR><BR>
     * ヘッダ群を追加します.
     * <BR>
     * @param header 対象のヘッダ群を設定します.
     */
    public void pushHeaders( HttpdHeaders header ) {
        if( header == null ) {
            return ;
        }
        String[] key = header.getKeys() ;
        if( key != null && key.length > 0 ) {
            int len = key.length ;
            for( int i = 0 ; i < len ; i ++ ) {
                int lenJ = header.size( key[ i ] ) ;
                for( int j = 0 ; j < lenJ ; j ++ ) {
                    this.outputHeader( key[ i ],header.getHeader( key[ i ],j ) ) ;
                }
            }
        }
    }
    
    /**
     * キャッシュをOFFに設定.
     * <BR><BR>
     * HTTPキャッシュをOFFに設定します.
     */
    public void cacheOff() {
        notCacheFlag = true ;
    }
    
    /**
     * クローズヘッダをONに設定.
     * <BR><BR>
     * HTTPクローズヘッダをONに設定します.
     */
    public void closeOn() {
        closeFlag = true ;
    }
    
    /**
     * クローズヘッダをOFFに設定.
     * <BR><BR>
     * HTTPクローズヘッダをOFFに設定します.
     */
    public void closeOff() {
        closeFlag = false ;
    }
    
    /**
     * GZIPモードを設定.
     * <BR><BR>
     * GZIPモードを設定します.
     * <BR>
     * @param mode [true]の場合、有効になります.
     */
    public void setGzip( boolean mode ) {
        this.gzipFlag = mode ;
    }
    
    /**
     * クローズヘッダ条件を取得.
     * <BR><BR>
     * HTTPクローズヘッダ条件を取得します.
     * <BR>
     * @return boolean [true]の場合、ONになります.
     */
    public boolean isClose() {
        return closeFlag ;
    }
    
    /**
     * チャングフラグを取得.
     * <BR><BR>
     * チャングフラグを取得します.
     * <BR>
     * @return boolean [true]の場合は、チャングは有効です.
     */
    public boolean isChunked() {
        return chunkedFlag ;
    }
    
    /**
     * OutputStreamを取得.
     * <BR><BR>
     * OutputStreamを取得します.
     * <BR>
     * @return OutputStream OutputStreamが返されます.
     * @exception Exception 例外.
     */
    public OutputStream getOutputStream()
        throws Exception {
        OutputStream ret = null ;
        if( cacheHeader != null ) {
            writeHeader() ;
            ret = this.outputStream ;
        }
        else {
            ret = this.outputStream ;
        }
        return ret ;
    }
    
    /**
     * ヘッダ情報が出力可能かチェック.
     * <BR><BR>
     * ヘッダ情報が出力可能かチェックします.
     * <BR>
     * @return boolean ヘッダが出力可能かチェックします.
     */
    public boolean isWriteHeader() {
        return ( cacheHeader != null ) ? true : false ;
    }
    
    /**
     * 指定ヘッダが存在するかチェック.
     * <BR><BR>
     * 指定ヘッダが存在するかチェックします.
     * <BR>
     * @param key 対象のキーを設定します.
     * @return boolean [true]の場合存在します.
     */
    public boolean isHeader( String key ) {
        if( key == null || ( key = key.trim() ).length() <= 0 ) {
            return false ;
        }
        if( cacheHeader != null && cacheHeader.indexOf( key+": " ) != -1 ) {
            return true ;
        }
        return false ;
    }
    
    /**
     * コンテンツ長が設定されているかチェック.
     * <BR><BR>
     * コンテンツ長が設定されているかチェックします.
     * <BR>
     * @return boolean [true]の場合、設定されています.
     */
    public boolean isContentLength() {
        return ( contentLength != null ) ? true : false ;
    }
    
    /**
     * HTTP情報を出力.
     */
    private void outputHttp( String version,int state ) {
        cacheHeader = new StringBuilder() ;
        cacheHeader.append( "HTTP/" ).append( version ).
            append( " " ).append( state ).append( " " ).
            append( HttpdErrorDef.convertErrorMessage( state ) ).append( " " ).
            append( ENTER ) ;
        // デフォルトヘッダを出力.
        outputHeader( "Server",HttpdVersionDef.getPrintServerName() ) ;
        outputHeader( "Date",HttpdTimestamp.getNowTimestamp() ) ;
        // HTTPバージョンが1.1以外の場合は、KeepAliveは無効とする.
        if( HttpdDef.VERSION_11.equals( version ) == false ) {
            outputHeader( "Connection","close" ) ;
        }
        else {
            // close条件がTRUEの場合は[close].
            if( closeFlag == true ) {
                outputHeader( "Connection","close" ) ;
            }
            // close条件がFALSEの場合は[keep-alive].
            else {
                outputHeader( "Connection","Keep-Alive" ) ;
            }
        }
    }
    
    /**
     * キャッシュOFFの設定.
     */
    private void notCache() {
        if( cacheHeader != null && isHeader( "Expires" ) == false &&
            isHeader( "Pragma" ) == false ) {
            outputHeader( "Expires",HttpdTimestamp.getTimestamp( 0L ) );
            outputHeader( "Pragma","no-cache" ) ;
            if( isHeader( HttpdDef.VALUE_LAST_UPDATE ) == false ) {
                outputHeader( HttpdDef.VALUE_LAST_UPDATE,HttpdTimestamp.getNowTimestamp() ) ;
            }
            outputHeader( "Cache-Control","private" ) ;
            outputHeader( "Cache-Control","no-cache" ) ;
            outputHeader( "Cache-Control","no-store" ) ;
            outputHeader( "Cache-Control","max-age=0" ) ;
        }
    }
    
    /**
     * ヘッダ情報を出力.
     */
    private void outputHeader( String key,String value ) {
        if( key == null || ( key = key.trim() ).length() <= 0 ||
            value == null || ( value = value.trim() ).length() <= 0 ) {
            return ;
        }
        if( cacheHeader != null ) {
            if( key.equals( HttpdDef.VALUE_CONTENT_LENGTH ) ) {
                contentLength = new StringBuilder().append( key ).
                    append( ": " ).append( value ).toString() ;
            }
            else {
                cacheHeader.append( key ).append( ": " ).append( value ).append( ENTER ) ;
            }
        }
    }
    
    /**
     * ヘッダ情報を出力.
     */
    private void writeHeader()
        throws Exception {
        try {
            if( cacheHeader != null ) {
                // mimeTypeが設定されていない場合.
                if( isContentType( cacheHeader ) == false ) {
                    if( state >= 400 ) {
                        cacheHeader.append( HttpdDef.VALUE_CONTENT_TYPE ).append( ": " ).
                            append( HttpdDef.ERROR_PAGE_MIME_TYPE ).append( ENTER ) ;
                    }
                    else {
                        noSetByContentType( cacheHeader,url ) ;
                    }
                }
                // gzipが有効な場合.
                if( HttpdDef.VERSION_11.equals( version ) == true && gzipFlag == true ) {
                    chunkedFlag = true ;
                    if( cacheHeader.indexOf( "Transfer-Encoding:" ) == -1 ) {
                        cacheHeader.append( "Transfer-Encoding: chunked" ).append( ENTER ) ;
                    }
                }
                // gzipが無効な場合.
                else {
                    // KeepAliveが有効な場合は、チャングも有効にする.
                    if( keepAliveFlag == true ) {
                        chunkedFlag = true ;
                    }
                    // ただし、contentLengthが定義されている場合は、
                    // チャングは無効とする.
                    if( contentLength != null ) {
                        cacheHeader.append( contentLength ).append( ENTER ) ;
                        chunkedFlag = false ;
                    }
                    // HTTPバージョンが1.1以外の場合は、KeepAliveと、チャングは無効とする.
                    if( HttpdDef.VERSION_11.equals( version ) == false ) {
                        keepAliveFlag = false ;
                        chunkedFlag = false ;
                    }
                    // チャングが有効な場合.
                    if( chunkedFlag == true ) {
                        if( cacheHeader.indexOf( "Transfer-Encoding:" ) == -1 ) {
                            cacheHeader.append( "Transfer-Encoding: chunked" ).append( ENTER ) ;
                        }
                    }
                }
                // 非キャッシュ設定の場合.
                if( notCacheFlag == true ) {
                    notCache() ;
                }
                // ヘッダ終点を設定.
                cacheHeader.append( ENTER ) ;
                String hd = cacheHeader.toString() ;
                cacheHeader = null ;
                byte[] writeHd = hd.getBytes( CHARSET ) ;
                hd = null ;
                this.outputStream.write( writeHd,0,writeHd.length ) ;
                writeHd = null ;
            }
        } catch( Exception e ) {
            if( this.outputStream != null ) {
                try {
                    this.outputStream.flush() ;
                } catch( Exception ee ) {
                }
                this.outputStream = null ;
            }
            throw e ;
        }
    }
    
    /*
     * コンテンツタイプが設定されていない場合.
     */
    private static final void noSetByContentType( StringBuilder buf,String url ) {
        url = FileUtil.getFileName( url ) ;
        if( url.indexOf( "." ) == -1 && url.endsWith( ScriptDef.SCRIPT_PLUS ) == true ) {
            buf.append( HttpdDef.VALUE_CONTENT_TYPE ).append( ": " ).
                append( HttpdDef.AJAX_MIME_TYPE ).append( ENTER ) ;
        }
        else if( url.endsWith( ScriptDef.SCRIPT_HTML_PLUS ) == true ) {
            buf.append( HttpdDef.VALUE_CONTENT_TYPE ).append( ": " ).
                append( HttpdDef.MHTML_MIME_TYPE ).append( ENTER ) ;
        }
        else {
            MimeConfig conf = ( MimeConfig )GlobalManager.getValue(
                ServiceDef.MANAGER_BY_MIME_TYPE ) ;
            String mime = conf.getNameByMimeType( url ) ;
            if( mime.indexOf( "xml" ) != -1 || mime.indexOf( "html" ) != -1 ) {
                buf.append( HttpdDef.VALUE_CONTENT_TYPE ).append( ": " ).append( mime ).
                    append( HttpdDef.MIME_CHARSET_UTF8 ).append( ENTER ) ;
            }
            else {
                buf.append( HttpdDef.VALUE_CONTENT_TYPE ).append( ": " ).
                    append( mime ).append( ENTER ) ;
            }
        }
    }
    
    /**
     * コンテンツタイプが設定されていないかチェック.
     */
    private static final boolean isContentType( StringBuilder buf ) {
        if( buf.indexOf( HttpdDef.VALUE_CONTENT_TYPE+": " ) == -1 ) {
            return false ;
        }
        return true ;
    }
}
