package org.maachang.comet.httpd.engine.script.dao;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.maachang.comet.httpd.engine.script.image.ImageOp;
import org.maachang.dao.DaoSession;
import org.maachang.dao.ExecutionDao;
import org.maachang.dao.MaachangDao;
import org.maachang.dao.MaachangDaoException;
import org.maachang.dao.OptimisticLockException;
import org.maachang.dao.dbms.DbUtil;
import org.maachang.dao.dbms.MetaColumn;
import org.maachang.dao.dbms.Record;


/**
 * 1つのテーブルアクセス用Dao.
 * 
 * @version 2007/11/04
 * @author masahito suzuki
 * @since MaachangComet 1.00
 */
public class Dao {
    
    /**
     * 各フィードバックカラム名.
     */
    private static final String FD_COLUMN_ID = DbUtil.convertDBNameByJavaName( false,ExecutionDao.SEQ_COLUMN ) ;
    private static final String FD_COLUMN_CREATE_TIME = DbUtil.convertDBNameByJavaName( false,ExecutionDao.CREATE_TIME ) ;
    private static final String FD_COLUMN_UPDATE_TIME = DbUtil.convertDBNameByJavaName( false,ExecutionDao.UPDATE_TIME ) ;
    private static final String FD_COLUMN_OPTIMISTIC_LOCK = DbUtil.convertDBNameByJavaName( false,ExecutionDao.OPTIMISTIC_LOCK_COLUMN ) ;
    
    /**
     * LOG.
     */
    private static final Log LOG = LogFactory.getLog( Dao.class ) ;
    
    /**
     * モデル名.
     */
    private String model = null ;
    
    /**
     * MetaJson.
     */
    private MetaJson metajson = null ;
    
    /**
     * コンストラクタ.
     */
    private Dao() {
        
    }
    
    /**
     * コンストラクタ.
     * <BR><BR>
     * 条件を指定してオブジェクトを生成します.
     * <BR>
     * @param model 対象のモデル名を設定します.
     * @param metajson 対象のMetaJsonを設定します.
     * @exception Exception 例外.
     */
    public Dao( String model,MetaJson metajson )
        throws Exception {
        if( model == null || metajson == null || ( model = model.trim() ).length() <= 0 ) {
            throw new IllegalArgumentException( "引数は不正です" ) ;
        }
        this.model = model ;
        this.metajson = metajson ;
    }
    
    /**
     * モデル名を取得.
     * <BR><BR>
     * モデル名が返されます.
     * <BR>
     * @return String モデル名が返されます.
     */
    public String getModel() {
        return model ;
    }
    
    /**
     * メタデータをJSON形式で取得.
     * <BR><BR>
     * メタデータをJSON形式で取得します.
     * <BR>
     * @return HashMap<String,Object> JSON形式のメタデータが返されます.
     */
    public HashMap<String,Object> getMetaJson() {
        return metajson.getJson() ;
    }
    
    /**
     * MetaJsonを取得.
     * <BR><BR>
     * MetaJsonを取得します.
     * <BR>
     * @return MetaJson MetaJsonが返されます.
     */
    public MetaJson getMeta() {
        return metajson ;
    }
    
    /**
     * データ保存.
     * <BR><BR>
     * 対象の情報を保存します.
     * <BR>
     * @param record 対象のセッションを設定します.
     * @param data 保存対象のデータを設定します.
     * @return boolean [false]の場合、楽観的ロック例外が発生しました.
     * @exception MaachangDaoException MaachangDao例外.
     */
    public boolean save( Record record,Map<String,Object> data )
        throws MaachangDaoException {
        DaoSession session = null ;
        try {
            session = getSession( record ) ;
            Map<String,Object> map = convertBeanByJoin( data ) ;
            map = convertInputMap( map ) ;
            map = MaachangDao.getInstance().save( session,model,map ) ;
            if( data.get( FD_COLUMN_ID ) == null ) {
                data.put( FD_COLUMN_ID,map.get( FD_COLUMN_ID ) ) ;
            }
            if( map.get( FD_COLUMN_CREATE_TIME ) != null ) {
                data.put( FD_COLUMN_CREATE_TIME,map.get( FD_COLUMN_CREATE_TIME ) ) ;
            }
            if( map.get( FD_COLUMN_UPDATE_TIME ) != null ) {
                data.put( FD_COLUMN_UPDATE_TIME,map.get( FD_COLUMN_UPDATE_TIME ) ) ;
            }
            if( map.get( FD_COLUMN_OPTIMISTIC_LOCK ) != null ) {
                data.put( FD_COLUMN_OPTIMISTIC_LOCK,map.get( FD_COLUMN_OPTIMISTIC_LOCK ) ) ;
            }
            return true ;
        } catch( OptimisticLockException ol ) {
            LOG.warn( "optimisticLockError",ol ) ;
            return false ;
        } catch( MaachangDaoException md ) {
            throw md ;
        } catch( Exception e ) {
            throw new MaachangDaoException( e ) ;
        } finally {
            if( session != null ) {
                session.clear() ;
            }
        }
    }
    
    /**
     * データアップデート.
     * <BR><BR>
     * 対象の情報をアップデートします.
     * <BR>
     * @param record 対象のレコードを設定します.
     * @param where 対象の条件を設定します.
     * @param params 対象のパラメータ群を設定します.
     * @exception MaachangDaoException MaachangDao例外.
     */
    public void update( Record record,String where,ArrayList<Object> params )
        throws MaachangDaoException {
        DaoSession session = null ;
        try {
            session = getSession( record ) ;
            ArrayList<String> sql = parseSql( where ) ;
            Object[] pms = convertInputList( sql,params ) ;
            where = srcSql( sql ) ;
            MaachangDao.getInstance().update( session,model,where,pms ) ;
        } catch( MaachangDaoException md ) {
            throw md ;
        } catch( Exception e ) {
            throw new MaachangDaoException( e ) ;
        } finally {
            if( session != null ) {
                session.clear() ;
            }
        }
    }
    
    /**
     * データ削除.
     * <BR><BR>
     * 対象のデータを削除します.
     * <BR>
     * @param record 対象のレコードを設定します.
     * @param im イメージ操作オブジェクトを設定します.
     * @param delJoins 削除Joing群を設定します.
     * @param srcJoin 連結テーブル内容を設定します.
     * @param where 対象の条件を設定します.
     * @param params 対象のパラメータ群を設定します.
     * @exception MaachangDaoException MaachangDao例外.
     */
    public void remove( Record record,ImageOp im,ArrayList<String> delJoins,
        Map<String,List<String>> srcJoin,String where,ArrayList<Object> params )
        throws MaachangDaoException {
        DaoSession session = null ;
        try {
            session = getSession( record ) ;
            ArrayList<String> sql = parseSql( where ) ;
            Object[] pms = convertInputList( sql,params ) ;
            where = srcSql( sql ) ;
            MaachangDao.getInstance().delete( session,im,delJoins,srcJoin,model,where,pms ) ;
        } catch( MaachangDaoException md ) {
            throw md ;
        } catch( Exception e ) {
            throw new MaachangDaoException( e ) ;
        } finally {
            if( session != null ) {
                session.clear() ;
            }
        }
    }
    
    /**
     * １つのデータを削除.
     * <BR><BR>
     * １つのデータを削除します.
     * <BR>
     * @param record 対象のセッションを設定します.
     * @param im イメージ操作オブジェクトを設定します.
     * @param delJoins 削除Joing群を設定します.
     * @param srcJoin 連結テーブル内容を設定します.
     * @param data 保存対象のデータを設定します.
     * @exception MaachangDaoException MaachangDao例外.
     */
    public void remove( Record record,ImageOp im,ArrayList<String> delJoins,
        Map<String,List<String>> srcJoin,Map<String,Object> data )
        throws MaachangDaoException {
        DaoSession session = null ;
        try {
            session = getSession( record ) ;
            Map<String,Object> map = convertBeanByJoin( data ) ;
            map = convertInputMap( map ) ;
            MaachangDao.getInstance().delete( session,im,delJoins,srcJoin,model,map ) ;
        } catch( MaachangDaoException md ) {
            throw md ;
        } catch( Exception e ) {
            throw new MaachangDaoException( e ) ;
        } finally {
            if( session != null ) {
                session.clear() ;
            }
        }
    }
    
    /**
     * 対象条件を取得.
     * <BR><BR>
     * 対象の条件を取得します.
     * <BR>
     * @param record 対象のレコードを設定します.
     * @param join 連結テーブル内容を設定します.
     * @param srcJoin 対象のsrcJoin条件を設定します.
     * @param where 対象の条件を設定します.
     * @param params 対象のパラメータ群を設定します.
     * @return List<Map<String,Object>> 結果情報が返されます.
     * @exception MaachangDaoException MaachangDao例外.
     */
    public List<Map<String,Object>> find( Record record,Map<String,String> join,
        Map<String,List<String>> srcJoin,String where,List<Object> params )
        throws MaachangDaoException {
        DaoSession session = null ;
        try {
            session = getSession( record ) ;
            ArrayList<String> sql = parseSql( where ) ;
            Object[] pms = convertInputList( sql,params ) ;
            where = srcSql( sql ) ;
            List<Map<String,Object>> res = MaachangDao.getInstance().find(
                session,join,srcJoin,model,where,pms ) ;
            return convertResult( res ) ;
        } catch( MaachangDaoException md ) {
            throw md ;
        } catch( Exception e ) {
            throw new MaachangDaoException( e ) ;
        } finally {
            if( session != null ) {
                session.clear() ;
            }
        }
    }
    
    /**
     * 対象条件を取得.
     * <BR><BR>
     * 対象の条件を取得します.<BR>
     * また、このメソッドでは、リミット条件を指定して、情報を取得します.
     * <BR>
     * @param record 対象のレコードを設定します.
     * @param join 連結テーブル内容を設定します.
     * @param srcJoin 対象のsrcJoin条件を設定します.
     * @param where 対象の条件を設定します.
     * @param params 対象のパラメータ群を設定します.
     * @return List<Map<String,Object>> 結果情報が返されます.
     * @exception MaachangDaoException MaachangDao例外.
     */
    public List<Map<String,Object>> limit( Record record,Map<String,String> join,
        Map<String,List<String>> srcJoin,int offset,int limit,String where,List<Object> params )
        throws MaachangDaoException {
        DaoSession session = null ;
        try {
            session = getSession( record ) ;
            ArrayList<String> sql = parseSql( where ) ;
            Object[] pms = convertInputList( sql,params ) ;
            where = srcSql( sql ) ;
            List<Map<String,Object>> res = MaachangDao.getInstance().find(
                session,join,srcJoin,model,where,offset,limit,pms ) ;
            return convertResult( res ) ;
        } catch( MaachangDaoException md ) {
            throw md ;
        } catch( Exception e ) {
            throw new MaachangDaoException( e ) ;
        } finally {
            if( session != null ) {
                session.clear() ;
            }
        }
    }
    
    /**
     * 対象条件を取得.
     * <BR><BR>
     * 対象の条件を取得します.<BR>
     * また、このメソッドでは、最初の1件だけ情報を取得します.
     * <BR>
     * @param record 対象のレコードを設定します.
     * @param join 連結テーブル内容を設定します.
     * @param srcJoin 対象のsrcJoin条件を設定します.
     * @param where 対象の条件を設定します.
     * @param params 対象のパラメータ群を設定します.
     * @return List<Map<String,Object>> 結果情報が返されます.
     * @exception MaachangDaoException MaachangDao例外.
     */
    public List<Map<String,Object>> first( Record record,Map<String,String> join,
        Map<String,List<String>> srcJoin,String where,List<Object> params )
        throws MaachangDaoException {
        return limit( record,join,srcJoin,0,1,where,params ) ;
    }
    
    /**
     * 対象条件の件数を取得.
     * <BR><BR>
     * 対象条件の件数を取得します.
     * <BR>
     * @param record 対象のレコードを設定します.
     * @param where 対象の条件を設定します.
     * @param params 対象のパラメータ群を設定します.
     * @return int 件数が返されます.
     * @exception MaachangDaoException MaachangDao例外.
     */
    public int count( Record record,String where,List<Object> params )
        throws MaachangDaoException {
        DaoSession session = null ;
        try {
            session = getSession( record ) ;
            ArrayList<String> sql = parseSql( where ) ;
            Object[] pms = convertInputList( sql,params ) ;
            where = srcSql( sql ) ;
            return MaachangDao.getInstance().count( session,model,where,pms ) ;
        } catch( MaachangDaoException md ) {
            throw md ;
        } catch( Exception e ) {
            throw new MaachangDaoException( e ) ;
        } finally {
            if( session != null ) {
                session.clear() ;
            }
        }
    }
    
    /**
     * 指定条件を設定して、セッションオブジェクトを生成.
     */
    private DaoSession getSession( Record record )
        throws MaachangDaoException {
        return new DaoSession( record,model,metajson.getMeta() ) ;
    }
    
    /**
     * 入力HashMap内のパラメータを変換.
     */
    private Map<String,Object> convertInputMap( Map<String,Object> map ) {
        if( map == null && map.size() <= 0 ) {
            return null ;
        }
        Object[] names = map.keySet().toArray() ;
        int len = names.length ;
        MetaColumn meta = metajson.getMeta() ;
        boolean[] result = new boolean[ 1 ] ;
        for( int i = 0 ; i < len ; i ++ ) {
            String name = ( String )names[ i ] ;
            Object value = DaoUtil.convertByDBObject( result,meta,model,name,map.get( name ) ) ;
            if( result[ 0 ] == true ) {
                map.put( name,value ) ;
            }
        }
        return map ;
    }
    
    /**
     * 指定SQL条件を解析.
     */
    private ArrayList<String> parseSql( String where )
        throws Exception {
        if( where == null || ( where = where.trim() ).length() <= 0 ) {
            return null ;
        }
        return ParseSql.parseSQL( where ) ;
    }
    
    /**
     * 解析されたSQLを元に戻す.
     */
    private String srcSql( ArrayList<String> list )
        throws Exception {
        if( list == null || list.size() <= 0 ) {
            return null ;
        }
        return ParseSql.srcSQL( list ) ;
    }
    
    /**
     * 入力パラメータを変換.
     */
    private Object[] convertInputList( ArrayList<String> sql,List<Object> params )
        throws Exception {
        if( sql == null || sql.size() <= 0 || params == null || params.size() <= 0 ) {
            return null ;
        }
        ArrayList<String> paramNames = ParseSql.getValues( sql ) ;
        int len = paramNames.size() ;
        if( len >= params.size() ) {
            len = params.size() ;
        }
        if( len <= 0 ) {
            return null ;
        }
        MetaColumn meta = metajson.getMeta() ;
        Object[] ret = new Object[ len ] ;
        boolean[] result = new boolean[ 1 ] ;
        int cnt = 0 ;
        for( int i = 0 ; i < len ; i ++ ) {
            String name = paramNames.get( i ) ;
            Object o = DaoUtil.convertByDBObject( result,meta,model,name,params.get( i ) ) ;
            if( result[ 0 ] == true ) {
                ret[ cnt ] = o ;
                cnt ++ ;
            }
        }
        return ret ;
    }
    
    /**
     * 取得結果情報を変換.
     */
    private List<Map<String,Object>> convertResult( List<Map<String,Object>> result )
        throws Exception {
        if( result == null || result.size() <= 0 ) {
            return null ;
        }
        int len = result.size() ;
        for( int i = 0 ; i < len ; i ++ ) {
            Map<String,Object> map = result.get( i ) ;
            if( map.size() <= 0 ) {
                return null ;
            }
            Object[] names = map.keySet().toArray() ;
            int lenJ = names.length ;
            for( int j = 0 ; j < lenJ ; j ++ ) {
                String name = ( String )names[ j ] ;
                Object value = map.get( name ) ;
                if( value != null ) {
                    if( value instanceof List ) {
                        if( ( ( List )value ).size() > 0 ) {
                            value = convertResult( ( List )value ) ;
                        }
                    }
                    else if( value instanceof Map ) {
                        Map valueMap = ( Map )value ;
                        if( valueMap.size() > 0 ) {
                            Object[] valueMapNames = valueMap.keySet().toArray() ;
                            int lenK = valueMap.size() ;
                            for( int k = 0 ; k < lenK ; k ++ ) {
                                String valueMapName = ( String )valueMapNames[k] ;
                                Object valueMapValue = valueMap.get( valueMapName ) ;
                                valueMapValue = DaoUtil.convertResult( model,valueMapName,valueMapValue ) ;
                                valueMap.put( valueMapName,valueMapValue ) ;
                            }
                        }
                    }
                    else {
                        value = DaoUtil.convertResult( model,name,value ) ;
                    }
                }
                map.put( name,value ) ;
            }
        }
        return result ;
    }
    
    /**
     * bean情報内にJoin情報が含まれている場合は、その情報をIDに変換する.
     */
    private Map<String,Object> convertBeanByJoin( Map<String,Object> map )
        throws Exception {
        if( map == null || map.size() <= 0 ) {
            return map ;
        }
        Map<String,Object> ret = new HashMap<String,Object>() ;
        Object[] names = map.keySet().toArray() ;
        int len = names.length ;
        for( int i = 0 ; i < len ; i ++ ) {
            String name = ( String )names[ i ] ;
            Object value = map.get( name ) ;
            if( value != null ) {
                if( value instanceof List ) {
                    if( ( ( List )value ).size() > 0 ) {
                        value = ( ( Map )( ( List )value ).get( 0 ) ).get( "id" ) ;
                    }
                }
                else if( value instanceof Map ) {
                    value = ( ( Map )value ).get( "id" ) ;
                }
            }
            ret.put( name,value ) ;
        }
        return ret ;
    }
    
}
