package org.maachang.comet.httpd.engine.auth ;

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

import org.maachang.comet.httpd.HttpdParams;
import org.maachang.comet.httpd.HttpdRequest;
import org.maachang.comet.httpd.engine.HttpdDef;
import org.maachang.comet.httpd.engine.HttpdUtil;
import org.maachang.util.FileUtil;
import org.maachang.util.StringUtil;

/**
 * HTTPD-認証情報管理.
 *
 * @version 2007/08/19
 * @author  masahito suzuki
 * @since   MaachangComet 1.00
 */
public class HttpdAuthManager {
    
    /**
     * 認証ファイル名.
     */
    private static final String AUTH_FILE = "$auth-file" ;
    
    /**
     * 要素管理.
     */
    private HashMap<String,HttpdAuthElement> manager = null ;
    
    /**
     * 基本path.
     */
    private String basePath = null ;
    
    /**
     * コンストラクタ.
     */
    public HttpdAuthManager() {
        
    }
    
    /**
     * デストラクタ.
     */
    protected void finalize() throws Exception {
        this.clear() ;
    }
    
    /**
     * 指定条件を設定してオブジェクトを生成.
     * <BR><BR>
     * 指定条件を設定してオブジェクトを生成します.
     * <BR>
     * @param basePath 対象の基本Pathを設定します.
     * @exception Exception 例外.
     */
    public synchronized void create( String basePath )
        throws Exception {
        if( basePath == null || ( basePath = basePath.trim() ).length() <= 0 ) {
            throw new IllegalArgumentException( "引数は不正です" ) ;
        }
        if( FileUtil.isDirExists( basePath ) == false ) {
            throw new IOException( "指定パス[" + basePath + "]は存在しません" ) ;
        }
        basePath = FileUtil.getFullPath( basePath ) ;
        if( basePath.endsWith( "/" ) == false && basePath.endsWith( "\\" ) == false ) {
            basePath += FileUtil.FILE_SPACE ;
        }
        this.basePath = basePath ;
    }
    
    /**
     * 情報クリア.
     * <BR><BR>
     * 管理されている情報をクリアします.
     */
    public synchronized void clear() {
        manager = null ;
        basePath = null ;
    }
    
    /**
     * 指定URLから、要素群を取得.
     * <BR><BR>
     * 指定URLから、要素群を取得します.
     * <BR>
     * @param path 対象のPathを設定します.
     * @return HttpdAuthElement 要素群一覧が返されます.
     * @exception Exception 例外.
     */
    public synchronized HttpdAuthElement getElement( String path )
        throws Exception {
        if( path == null || ( path = path.trim() ).length() <= 0 ) {
            throw new IllegalArgumentException( "指定パスは不正です" ) ;
        }
        if( manager == null ) {
            manager = new HashMap<String,HttpdAuthElement>() ;
        }
        if( path.endsWith( "/" ) == false ) {
            int p = path.lastIndexOf( "/" ) ;
            if( p > 0 ) {
                path = path.substring( 0,p+1 ) ;
            }
        }
        return getByNextElement( manager,this.basePath,path ) ;
    }
    
    /**
     * 認証ユーザ、パスワードをリクエストパラメータに設定.
     * <BR><BR>
     * 認証ユーザ、パスワードをリクエストパラメータに設定します.
     * <BR>
     * @param request 対象のリクエストを設定します.
     * @param user 対象の認証ユーザを設定します.
     * @param passwd 対象の認証パスワードを設定します.
     */
    public static final void setSuccessAuth( HttpdRequest request,String user,String passwd ) {
        HttpdParams params = request.getQuery() ;
        params.addParam( HttpdDef.SUCCESS_AUTH_USER,user ) ;
        params.addParam( HttpdDef.SUCCESS_AUTH_PASSWD,( passwd == null ) ? "" : passwd ) ;
    }
    
    /**
     * 前回パスを取得.
     */
    private static final String beforePath( String path ) {
        if( path.endsWith( "/" ) ) {
            path = path.substring( 0,path.length()-1 ) ;
        }
        int p = path.lastIndexOf( "/" ) ;
        if( p >= 0 ) {
            path = path.substring( 0,p+1 ) ;
            return path ;
        }
        else {
            return null ;
        }
    }
    
    /**
     * ターゲットとなる認証条件を検索.
     */
    private static final HttpdAuthElement getByNextElement( HashMap<String,HttpdAuthElement> map,String basePath,String path )
        throws Exception {
        String readAuthFile = new StringBuilder().append( HttpdUtil.convertFullPath( basePath,path ) ).append( AUTH_FILE ).toString() ;
        HttpdAuthElement em = map.get( path ) ;
        if( em != null ) {
            if( FileUtil.isFileExists( readAuthFile ) == true ) {
                if( em.getFileTime() != FileUtil.getLastTime( readAuthFile ) ) {
                    em = createAuthElement( readAuthFile,path ) ;
                    if( em != null ) {
                        map.put( path,em ) ;
                    }
                }
            }
            else {
                map.remove( path ) ;
                em = null ;
            }
        }
        else {
            if( FileUtil.isFileExists( readAuthFile ) == true ) {
                em = createAuthElement( readAuthFile,path ) ;
                if( em != null ) {
                    map.put( path,em ) ;
                }
            }
        }
        if( em != null ) {
            return em ;
        }
        else {
            String before = beforePath( path ) ;
            if( before == null ) {
                return null ;
            }
            readAuthFile = null ;
            em = null ;
            return getByNextElement( map,basePath,before ) ;
        }
    }
    
    /**
     * ファイルパラメータ:realm.
     */
    private static final String PARAM_REALM = "realm" ;
    
    /**
     * ファイルパラメータ:authType.
     */
    private static final String PARAM_AUTH_TYPE = "auth" ;
    
    /**
     * ファイルパラメータ:authUsers.
     */
    private static final String PARAM_AUTH_USERS = "users" ;
    
    /**
     * 対象ファイルを解析し、要素を生成.
     */
    private static final HttpdAuthElement createAuthElement( String path,String targetPath ) {
        BufferedReader br = null ;
        try {
            br = new BufferedReader(
                new InputStreamReader(
                    new FileInputStream( path ),HttpdDef.DEF_CHARSET ) ) ;
            boolean newFlag = false ;
            int pattern = -1 ;
            int befPattern = -1 ;
            int firstPos = -1 ;
            int valuePos = -1 ;
            StringBuilder buf = null ;
            String[] values = new String[ 3 ] ;
            for( ;; ) {
                String oneLine = br.readLine() ;
                if( oneLine == null ) {
                    // 前回条件が存在する場合は、処理する.
                    if( buf != null ) {
                        paramByValue( values,pattern,buf.toString(),null,firstPos,valuePos ) ;
                    }
                    break ;
                }
                if( ( oneLine = oneLine.trim() ).length() <= 0 ) {
                    continue ;
                }
                int p ;
                String lowLine = oneLine.toLowerCase() ;
                // realmパラメータ.
                if( ( p = lowLine.indexOf( PARAM_REALM ) ) != -1 ) {
                    int pp = lowLine.indexOf( ":",p+PARAM_REALM.length() ) ;
                    if( pp != -1 ) {
                        befPattern = pattern ;
                        pattern = 0 ;
                        firstPos = p ;
                        valuePos = pp+1 ;
                        newFlag = true ;
                    }
                }
                // authパラメータ.
                if( ( p = lowLine.indexOf( PARAM_AUTH_TYPE ) ) != -1 ) {
                    int pp = lowLine.indexOf( ":",p+PARAM_AUTH_TYPE.length() ) ;
                    if( pp != -1 ) {
                        befPattern = pattern ;
                        pattern = 1 ;
                        firstPos = p ;
                        valuePos = pp+1 ;
                        newFlag = true ;
                    }
                }
                // usersパラメータ.
                if( ( p = lowLine.indexOf( PARAM_AUTH_USERS ) ) != -1 ) {
                    int pp = lowLine.indexOf( ":",p+PARAM_AUTH_USERS.length() ) ;
                    if( pp != -1 ) {
                        befPattern = pattern ;
                        pattern = 2 ;
                        firstPos = p ;
                        valuePos = pp+1 ;
                        newFlag = true ;
                    }
                }
                lowLine = null ;
                
                // 新しいパラメータが検出できた場合.
                if( newFlag == true ) {
                    String str = null ;
                    if( buf == null ) {
                        str = paramByValue( values,befPattern,null,oneLine,firstPos,valuePos ) ;
                    }
                    else {
                        str = paramByValue( values,befPattern,buf.toString(),oneLine,firstPos,valuePos ) ;
                    }
                    buf = new StringBuilder() ;
                    if( str != null && str.length() > 0 ) {
                        buf.append( str ) ;
                    }
                    newFlag = false ;
                }
                else {
                    if( pattern != -1 ) {
                        if( buf == null ) {
                            buf = new StringBuilder() ;
                        }
                        buf.append( oneLine ) ;
                    }
                }
            }
            return convertElement( values,path,targetPath ) ;
        } catch( Exception e ) {
            return null ;
        } finally {
            if( br != null ) {
                try {
                    br.close() ;
                } catch( Exception e ) {
                }
            }
            br = null ;
        }
    }
    
    /**
     * パラメータを更新.
     */
    private static final String paramByValue( String[] out,int pattern,String before,String thisData,int fpos,int vpos ) {
        if( before == null || ( before = before.trim() ).length() <= 0 ||
            pattern <= -1 || out.length <= pattern ) {
            if( thisData != null ) {
                return thisData.substring( vpos,thisData.length() ) ;
            }
            else {
                return null ;
            }
        }
        out[ pattern ] = before.trim() ;
        if( thisData != null ) {
            return thisData.substring( vpos,thisData.length() ) ;
        }
        else {
            return null ;
        }
    }
    
    /**
     * 取得パラメータ群をコンバート.
     */
    private static final HttpdAuthElement convertElement( String[] out,String path,String targetPath )
        throws Exception {
        if( out[ 0 ] == null || ( out[ 0 ] = out[ 0 ].trim() ).length() <= 0 ||
            out[ 1 ] == null || ( out[ 1 ] = out[ 1 ].trim() ).length() <= 0 ) {
            return null ;
        }
        
        HttpdAuthElement ret = new HttpdAuthElement() ;
        int authType = -1 ;
        String authTypeByString = out[ 1 ].toLowerCase() ;
        if( authTypeByString.equals( HttpdAuthBasic.AUTH_TYPE_BY_BASIC ) == true ) {
            authType = HttpdAuthElement.AUTH_BY_BASIC ;
        }
        else if( authTypeByString.equals( HttpdAuthDigest.AUTH_TYPE_BY_DIGEST ) == true ) {
            authType = HttpdAuthElement.AUTH_BY_DIGEST ;
        }
        ret.create( out[ 0 ],authType,targetPath,FileUtil.getLastTime( path ) ) ;
        if( out[ 2 ] != null ) {
            ArrayList<String> lst = StringUtil.cutString( out[ 2 ],",\n\r" ) ;
            if( lst != null && lst.size() > 0 ) {
                int len = lst.size() ;
                for( int i = 0 ; i < len ; i ++ ) {
                    ret.addUser( lst.get( i ) ) ;
                }
            }
        }
        return ret ;
    }
    
}
