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

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

import javax.script.ScriptContext;

import org.maachang.comet.ServiceDef;
import org.maachang.comet.httpd.engine.script.dao.Dao;
import org.maachang.comet.httpd.engine.script.dao.DaoFactory;
import org.maachang.comet.httpd.engine.script.dao.MasterCache;
import org.maachang.comet.httpd.engine.script.dao.MetaJson;
import org.maachang.comet.httpd.engine.script.image.ImageManager;
import org.maachang.comet.httpd.engine.script.image.ImageOp;
import org.maachang.comet.mdbm.SingleMDbm;
import org.maachang.dao.ExecutionDao;
import org.maachang.dao.MaachangDaoException;
import org.maachang.dao.dbms.Record;
import org.maachang.dbm.MDbm;
import org.maachang.manager.GlobalManager;

/**
 * モデル実行処理格納用.
 * 
 * @version 2007/08/27
 * @author masahito suzuki
 * @since MaachangComet 1.00
 */
public class BaseModelImpl implements BaseModel {
    
    /**
     * SQL実行処理用.
     */
    private Record record = null ;
    
    /**
     * スクリプトコンテキスト.
     */
    private ScriptContext context = null ;
    
    /**
     * MDBM.
     */
    private MDbm mdbm = null ;
    
    /**
     * イメージ操作管理.
     */
    private ImageOp imageOp = null ;
    
    /**
     * 更新/削除テーブル管理.
     */
    private ArrayList<String> updateTables = null ;
    
    /**
     * 生成チェック用.
     */
    private boolean createFlag = false ;
    
    /**
     * コンストラクタ.
     */
    public BaseModelImpl() {
        
    }
    
    /**
     * デストラクタ.
     */
    protected void finalize() throws Exception {
        this.clear() ;
    }
    
    /**
     * 生成処理.
     * <BR><BR>
     * 条件を設定して生成します.
     * <BR>
     * @param mode [true]の場合、コンソール以外の実行モードです.
     * @param context 対象のスクリプトコンテキストを設定します.
     * @param record 対象のレコードオブジェクトを設定します.
     * @exception Exception 例外.
     */
    public synchronized void create( boolean mode,ScriptContext context,Record record )
        throws Exception {
        if( record == null ) {
            throw new IOException( "Recordは不正です" ) ;
        }
        this.record = record ;
        this.context = context ;
        if( SingleMDbm.getInstance().isCreate() == false ) {
            if( mode == true ) {
                SingleMDbm.getInstance().createMDbm( false ) ;
            }
            else {
                SingleMDbm.getInstance().createMDbm( true ) ;
            }
        }
        this.mdbm = SingleMDbm.getInstance().getMDbm( record.getConnection().getAutoCommit() == false ) ;
        this.imageOp = ImageManager.getImageOp( mdbm ) ;
        this.updateTables = null ;
        this.createFlag = true ;
    }
    
    /**
     * クリア処理.
     */
    public void clear() {
        if( mdbm != null ) {
            try {
                mdbm.commit() ;
            } catch( Exception e ) {
            }
        }
        if( record != null ) {
            try {
                record.commit() ;
            } catch( Exception e ) {
            }
            try {
                record.close() ;
            } catch( Exception e ) {
            }
        }
        if( updateTables != null ) {
            updateTables.clear() ;
        }
        record = null ;
        context = null ;
        imageOp = null ;
        mdbm = null ;
        updateTables = null ;
        createFlag = false ;
        
    }
    
    /**
     * コミット処理.
     * <BR><BR>
     * コミット処理を実施します.
     * <BR>
     * @exception Exception 例外.
     */
    public void commit() throws Exception {
        if( createFlag == true ) {
            checkCommit() ;
            if( mdbm != null ) {
                mdbm.commit() ;
            }
            record.commit() ;
            // マスターキャッシュに対する更新テーブルが存在する場合は、
            // その内容を破棄する.
            if( updateTables != null && updateTables.size() >= 0 ) {
                MasterCache cache = ( MasterCache )GlobalManager.getInstance().get( ServiceDef.MASTER_DB_CACHE_MANAGER ) ;
                if( cache != null ) {
                    int len = updateTables.size() ;
                    for( int i = 0 ; i < len ; i ++ ) {
                        cache.clearCache( updateTables.get( i ) ) ;
                    }
                }
                updateTables.clear() ;
                updateTables = null ;
            }
        }
    }
    
    /**
     * ロールバック処理.
     * <BR><BR>
     * ロールバック処理を実施します.
     * <BR>
     * @exception Exception 例外.
     */
    public void rollback() throws Exception {
        if( createFlag == true ) {
            if( mdbm != null ) {
                try {
                    mdbm.rollback() ;
                } catch( Exception e ) {
                }
            }
            if( record != null ) {
                record.rollback() ;
            }
        }
    }
    
    /**
     * 更新／削除テーブルを追加.
     * <BR><BR>
     * @param name 更新／削除テーブルを追加します.
     */
    public void setUpdateOrDeleteTable( String name ) {
        if( name == null || ( name = name.trim() ).length() <= 0 ) {
            return ;
        }
        if( updateTables == null ) {
            updateTables = new ArrayList<String>() ;
        }
        updateTables.add( name ) ;
    }
    
    /**
     * 生成チェック.
     * <BR><BR>
     * 生成チェック処理を行います.
     * <BR>
     * @return boolean [true]の場合生成されています.
     */
    public synchronized boolean isCreate() {
        return createFlag ;
    }
    
    /**
     * オブジェクトタイプを取得.
     * <BR><BR>
     * オブジェクトタイプを取得します.
     * <BR>
     * @return boolean [true]の場合、実行可能オブジェクトです.
     */
    public boolean isType() {
        return true ;
    }
    
    /**
     * スクリプトコンテキストを取得.
     * <BR><BR>
     * スクリプトコンテキストを取得します.
     * <BR>
     * @return ScriptContext 対象のスクリプトコンテキストが返されます.
     */
    public ScriptContext getScriptContext() {
        return context ;
    }
    
    /**
     * イメージ操作オブジェクトを取得.
     * <BR><BR>
     * イメージ操作オブジェクトを取得します.
     * <BR>
     * @return ImageOp イメージ操作オブジェクトが返されます.
     */
    public ImageOp getImageOp() {
        return imageOp ;
    }
    
    /**
     * MDBMオブジェクトを取得.
     * <BR><BR>
     * @return MDbm MDBMオブジェクトが返されます.
     */
    public MDbm getMDbm() {
        return mdbm ;
    }
    
    /**
     * コネクションオブジェクトを取得.
     * <BR><BR>
     * コネクションオブジェクトを取得します.
     * <BR>
     * @return Record コネクションオブジェクトが返されます.
     */
    public Record getRecord() {
        return record ;
    }
    
    /**
     * 指定モデル名から、メタデータを取得.
     * <BR><BR>
     * 指定モデル名から、メタデータを取得します.
     * <BR>
     * @param model 対象のモデル名を設定します.
     * @return MetaJson 対象のメタデータが返されます.
     * @exception Exception 例外.
     */
    public MetaJson getMeta( String model )
        throws Exception {
        checkCreate() ;
        DaoFactory factory = getDaoFactory() ;
        Dao dao = factory.getDao( record,model ) ;
        if( dao == null ) {
            throw new MaachangDaoException( "対象のModel情報[" + model + "]は存在しません" ) ;
        }
        return dao.getMeta() ;
    }
    
    /**
     * メタデータをJSON形式に変換.
     * <BR><BR>
     * メタデータをJSON形式に変換します.
     * <BR>
     * @param model 対象のモデル名を設定します.
     * @return HashMap<String,Object> JSON形式のメタデータが返されます.
     * @exception Exception 例外.
     */
    public HashMap<String,Object> getMetaByJson( String model )
        throws Exception {
        checkCreate() ;
        MetaJson meta = getMeta( model ) ;
        return meta.getJson() ;
    }
    
    /**
     * データを保存.
     * <BR><BR>
     * スクリプトから渡されたデータを保存します.
     * <BR>
     * @param model 対象のモデル名を設定します.
     * @param args 対象のデータを設定します.
     * @return Long 更新されたIDが返されます.
     * @exception Exception 例外.
     */
    public Long save( String model,HashMap<String,Object> args )
        throws Exception {
        checkCreate() ;
        DaoFactory factory = getDaoFactory() ;
        Dao dao = factory.getDao( record,model ) ;
        if( dao.save( record,args ) == false ) {
            return new Long( -1L ) ;
        }
        Object ret = args.get( "id" ) ;
        if( ret != null && ret instanceof String ) {
            ret = new Long( ( String )ret ) ;
        }
        return ( Long )ret ;
    }
    
    /**
     * データをアップデート.
     * <BR><BR>
     * スクリプトから渡されたデータを更新します.
     * <BR>
     * @param model 対象のモデル名を設定します.
     * @param where 対象の条件を設定します.
     * @param args 対象のデータを設定します.
     * @exception Exception 例外.
     */
    public void update( String model,String where,ArrayList<Object> args )
        throws Exception {
        checkCreate() ;
        DaoFactory factory = getDaoFactory() ;
        Dao dao = factory.getDao( record,model ) ;
        dao.update( record,where,args ) ;
    }
    
    /**
     * データを削除.
     * <BR><BR>
     * スクリプトから渡されたデータを削除します.
     * <BR>
     * @param model 対象のモデル名を設定します.
     * @param im イメージマネージャを設定します.
     * @param delJoins 削除Joing群を設定します.
     * @param srcJoin 連結テーブル内容を設定します.
     * @param where 対象の条件を設定します.
     * @param args 対象のデータを設定します.
     * @exception Exception 例外.
     */
    public void remove( String model,ImageOp im,ArrayList<String> delJoins,
        Map<String,List<String>> srcJoin,String where,ArrayList<Object> args )
        throws Exception {
        checkCreate() ;
        DaoFactory factory = getDaoFactory() ;
        Dao dao = factory.getDao( record,model ) ;
        dao.remove( record,im,delJoins,srcJoin,where,args ) ;
    }
    
    /**
     * データを削除.
     * <BR><BR>
     * スクリプトから渡されたデータを削除します.
     * <BR>
     * @param model 対象のモデル名を設定します.
     * @param im イメージマネージャを設定します.
     * @param delJoins 削除Joing群を設定します.
     * @param srcJoin 連結テーブル内容を設定します.
     * @param args 対象のデータを設定します.
     * @exception Exception 例外.
     */
    public void removeData( String model,ImageOp im,ArrayList<String> delJoins,
        Map<String,List<String>> srcJoin,HashMap<String,Object> args )
        throws Exception {
        checkCreate() ;
        DaoFactory factory = getDaoFactory() ;
        Dao dao = factory.getDao( record,model ) ;
        dao.remove( record,im,delJoins,srcJoin,args ) ;
    }
    
    /**
     * データを取得.
     * <BR><BR>
     * データを取得します.
     * <BR>
     * @param model 対象のモデル名を設定します.
     * @param join 連結テーブル内容を設定します.
     * @param srcJoin 対象のsrcJoin条件を設定します.
     * @param where 対象の条件を設定します.
     * @param args 条件に対するパラメータを設定します.
     * @return List<Map<String,Object>> 処理結果が返されます.
     * @exception Exception 例外.
     */
    public List<Map<String,Object>> find( String model,Map<String,String> join,
        Map<String,List<String>> srcJoin,String where,ArrayList<Object> args )
        throws Exception {
        checkCreate() ;
        DaoFactory factory = getDaoFactory() ;
        Dao dao = factory.getDao( record,model ) ;
        return dao.find( record,join,srcJoin,where,args ) ;
    }
    
    /**
     * データを取得.
     * <BR><BR>
     * データを取得します.
     * <BR>
     * @param model 対象のモデル名を設定します.
     * @param join 連結テーブル内容を設定します.
     * @param srcJoin 対象のsrcJoin条件を設定します.
     * @param offset 対象のオフセット値を設定します.
     * @param limit 対象のリミット値を設定します.
     * @param where 対象の条件を設定します.
     * @param args 条件に対するパラメータを設定します.
     * @param options 条件に対するオプションを設定します.
     * @return List<Map<String,Object>> 処理結果が返されます.
     * @exception Exception 例外.
     */
    public List<Map<String,Object>> limit( String model,Map<String,String> join,
        Map<String,List<String>> srcJoin,int offset,int limit,String where,ArrayList<Object> args )
        throws Exception {
        checkCreate() ;
        DaoFactory factory = getDaoFactory() ;
        Dao dao = factory.getDao( record,model ) ;
        return dao.limit( record,join,srcJoin,offset,limit,where,args ) ;
    }
    
    /**
     * データを取得.
     * <BR><BR>
     * データを取得します.
     * <BR>
     * @param model 対象のモデル名を設定します.
     * @param join 連結テーブル内容を設定します.
     * @param srcJoin 対象のsrcJoin条件を設定します.
     * @param offset 対象のオフセット値を設定します.
     * @param limit 対象のリミット値を設定します.
     * @param where 対象の条件を設定します.
     * @param args 条件に対するパラメータを設定します.
     * @param options 条件に対するオプションを設定します.
     * @return List<Map<String,Object>> 処理結果が返されます.
     * @exception Exception 例外.
     */
    public List<Map<String,Object>> first( String model,Map<String,String> join,
        Map<String,List<String>> srcJoin,String where,ArrayList<Object> args )
        throws Exception {
        checkCreate() ;
        DaoFactory factory = getDaoFactory() ;
        Dao dao = factory.getDao( record,model ) ;
        return dao.first( record,join,srcJoin,where,args ) ;
    }
    
    /**
     * データ数を取得.
     * <BR><BR>
     * データ数を取得します.
     * <BR>
     * @param model 対象のモデル名を設定します.
     * @param where 対象の条件を設定します.
     * @param args 条件に対するパラメータを設定します.
     * @return int 処理結果が返されます.
     * @exception Exception 例外.
     */
    public int count( String model,String where,ArrayList<Object> args )
        throws Exception {
        checkCreate() ;
        DaoFactory factory = getDaoFactory() ;
        Dao dao = factory.getDao( record,model ) ;
        return dao.count( record,where,args ) ;
    }
    
    /**
     * SQL実行.
     * <BR><BR>
     * SQL文を実行します.
     * <BR>
     * @param sql 対象の更新系SQL文を設定します.
     * @return Object 処理結果が返されます.
     * @exception Exception 例外.
     */
    public Object sql( String sql ) throws Exception {
        return ExecutionDao.executionStatement( record,sql ) ;
    }
    
    /**
     * 更新系SQL実行.
     * <BR><BR>
     * 更新系のSQL文を実行します.
     * <BR>
     * @param sql 対象の更新系SQL文を設定します.
     * @return int 件数が返されます.
     * @exception Exception 例外.
     */
    public int executionUpdate( String sql ) throws Exception {
        checkCreate() ;
        return record.executeUpdate( sql ) ;
    }
    
    /**
     * デバッグモードを取得.
     * <BR>
     * @return [true]の場合、デバッグは有効です.
     */
    public boolean isDebug() {
        if( record != null ) {
            return record.isDebug() ;
        }
        return false ;
    }
    
    /**
     * DaoFactoryを取得.
     */
    private DaoFactory getDaoFactory() {
        return ( DaoFactory )GlobalManager.getInstance().get( ServiceDef.DAO_FACTORY ) ;
    }
    
    /**
     * DBが生成されているかチェック.
     */
    private void checkCreate() throws Exception {
        if( isCreate() == false ) {
            throw new IOException( "BaseModelは生成されていません" ) ;
        }
    }
    
    /**
     * コミットチェック.
     */
    private void checkCommit() throws Exception {
        if( record == null ) {
            throw new IOException( "レコードは利用できません" ) ;
        }
        if( record.isUse() == false ) {
            throw new IOException( "レコード処理エラー" ) ;
        }
    }
}

