package org.itscool.commons.dao;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Set;

import org.itscool.commons.bean.BeanUtil;
import org.itscool.commons.connection.Record;
import org.itscool.commons.connection.Records;
import org.itscool.commons.connection.Transaction;
import org.itscool.commons.connection.TransactionException;
import org.itscool.commons.connection.TransactionUtil;

/**
 * <p>基本データアクセスクラスです</p>
 * このクラスはデータベースとPOJOクラス間をマッピングするためのインターフェース
 * を提供します<br/>
 * </p>
 * <p>
 * insert(), update(), delete(), select()メソッドはSQL句のINSERT句、UPDATE句
 * DELETE句、SELECT句を実行します。<br/>
 * これらの引数にはいずれもCriteriaインスタンスを指定する必要があります。<br/>
 * Criteriaクラスは、データベースのテーブルととPOJOクラス間のマッピング情報
 * が含まれています。<br/>
 * 詳細は、org.itscool.common.dao.Criteriaの説明をご参照ください<br/>
 * </p>
 * <p>
 * O/Rマッピング機能を利用するには、このクラスを継承したデータアクセスクラスを
 * 作成してください<br/>
 * </p>
 *
 * @author KANO
 * @since jdk1.4.1
 * @version 1.00A 2004/11/1
 */
public class BaseDao {
    /**
     * トランザクションインスタンス
     */
    protected Transaction transaction;
    
    public BaseDao(){
        transaction = TransactionUtil.getTransaction();
        if(transaction == null){
            throw new RuntimeException(BaseDao.class.getName() + " transaction is null.");
        }
    }
    
    public int getCount(String tableName){
        String sql = "SELECT COUNT(*) AS DATA_NUM FROM " + tableName;
        Transaction trn = TransactionUtil.getTransaction();
        Records records = trn.select(sql);
        Record record = (Record)records.get(0);
        int dataNum = record.getInt("DATA_NUM");
        
        return dataNum;
    }
    
    public int getCount(Criteria crt){
        Transaction trn = TransactionUtil.getTransaction();
        Records records = trn.select(crt.toCountString());
        Record record = (Record)records.get(0);
        int dataNum = record.getInt("DATA_NUM");
        
        return dataNum;
    }
    
    /**
     * DaoTransactionインスタンスを初期化します
     * @param trn Transactionインスタンス
     */
    public void setTransaction(Transaction trn){
        this.transaction = trn;
    }
    
    /**
     * Transactionインスタンスを取得します<BR>
     * SQLを直接実行する場合は、Transactionインスタンスが提供する
     * メソッドを使用します
     * @return Transactionインスタンスを返します
     */
    public Transaction getTransaction() {
        return transaction;
    }
    
    /**
     * INSERT句を実行します
     * @param crt Criteriaインスタンス
     * @param bean INSERTを実行するBeanインスタンス
     * @throws TransactionException
     */
    public int insert(Criteria crt, Object bean)
    throws TransactionException{
        String sql = crt.toInsertString(bean);
        return transaction.execute(sql);
    }
    
    /**
     * UPDATE句を実行します
     * @param crt Criteriaインスタンス
     * @throws TransactionException
     */
    public int update(Criteria crt, Object bean)
    throws TransactionException{
        String sql = crt.toUpdateString(bean);
        
        //更新に失敗した理由が他者ユーザの更新に寄るものかチェック
        int recNum = transaction.execute(sql);
        if( recNum > 0 ){
            return recNum;
        }
        String lckSql = crt.toSelectLockVersionString();
        if(lckSql==null){
            return recNum;
        }
        Record lckRec = transaction.selectOne(lckSql);
        if( lckRec == null ){
            return recNum;
        }
        int dbVersion = lckRec.getInt(FieldMap.FLD_LOCK_VERSION);
        Object obj = crt.getLockVersion(bean);
        if( obj instanceof Integer ){
            int beanVersion = Integer.parseInt(obj.toString());
            if( dbVersion != beanVersion ){
                String msg = "already update from other user.bean-version="
                    + beanVersion + " db-version=" + dbVersion;
                throw new LockVersionException(msg);
            }
        }else{
            return recNum;
        }
        return recNum;
    }
    
    /**
     * DELETE句を実行します
     * @param crt Criteriaインスタンス
     * @throws TransactionException
     */
    public int delete(Criteria crt)
    throws TransactionException{
        String sql = crt.toDeleteString();
        return transaction.execute(sql);
    }
    
    /**
     * SELECT句を実行し、実行結果をBeanインスタンスの配列で返します
     * @param crt Criteriaインスタンス
     * @return SELECT句の実行結果をBeanインスタンスの配列で返します
     * @throws TransactionException
     */
    public List select(Criteria crt) throws TransactionException{
        ArrayList result = new ArrayList();
        ArrayList list = null;
        
        //SQL実行
        //実行結果はRecordクラスの配列で取得します
        list = transaction.select(crt.toSelectString());
        
        //CriteriaインスタンスにセットされたRecordMapインスタンスを取得します
        RecordMap recordMap = crt.getRecord();
        //selectメソッドで取得したRecordインスタンスとCrieteriaインスタンス
        //でセットされているRecordMapインスタンスからJavaBeansインスタンスを
        //生成します
        for(int i=0; i<list.size(); i++){
            Record record = (Record)list.get(i);
            Object obj = createCurrentObject(crt, recordMap, record);
            
            HashMap oneToManyRecordMaps = crt.getOneToManyRecordMaps();
            Set mKeySet = oneToManyRecordMaps.keySet();
            Iterator mIt = mKeySet.iterator();
            int childCount=0;
            while(mIt.hasNext()){
                ArrayList childs = new ArrayList();
                String propertyName = (String)mIt.next();
                RecordMap oneToManyRecordMap = (RecordMap)oneToManyRecordMaps.get(propertyName);
                
                for( childCount=i;childCount<list.size(); childCount++){
                    Record child = (Record)list.get(childCount);
                    Object oneToManyObj = createObject(oneToManyRecordMap, child);
                    childs.add(oneToManyObj);
                    
                    Object chkObj = createObject(recordMap, child);
                    if( !RecordMap.isEquals(obj, chkObj, recordMap)){
                        if( childCount > 1 ){
                            childCount --;
                        }
                        break;
                    }
                }
                BeanUtil.setProperty(obj, propertyName, childs);
            }
            if( crt.isOneToMany() ){
                i = childCount;
            }
            
            result.add(obj);
        }
        
        return result;
    }

    private Object createCurrentObject(Criteria crt, RecordMap recordMap, Record record) 
    throws TransactionException {
        
        Object obj = createObject(recordMap, record);
        //外部参照情報の取得
        HashMap fkRecordMaps = crt.getFkRecordMaps();
        Set keySet = fkRecordMaps.keySet();
        Iterator it = keySet.iterator();
        //外部参照情報が定義されている場合は、外部参照情報に紐付く値をレコード
        //から取得し、外部参照オブジェクトを作成する
        while(it.hasNext()){
            String propertyName = (String)it.next();
            //外部参照用RecordMapインスタンスを取得
            RecordMap fkRecordMap = (RecordMap)fkRecordMaps.get(propertyName);
            Object fkObj = createObject(fkRecordMap, record);
            //外部参照オブジェクトを結果オブジェクトにセットする
            BeanUtil.setProperty(obj, propertyName, fkObj);
        }
        return obj;
    }
    
    /**
     * Transaction.selectメソッドで取得したRecordインスタンスとCriteriaインスタンス
     * でセットされているRecordMapインスタンスからJavaBeansインスタンスを生成します
     * @param recordMap RecordMapインスタンス
     * @param record Recordインスタンス
     * @return 生成されたJavaBeansインスタンスを返します
     * @throws TransactionException
     */
    private Object createObject(RecordMap recordMap, Record record)
    throws TransactionException{
        Object obj = BeanUtil.createInstance(recordMap.getClassName());
        //RecordMap newRecord = new RecordMap(recordMap.getClassName(), recordMap.getTableName());
        Set keySet = recordMap.getFields().keySet();
        Iterator it = keySet.iterator();
        while( it.hasNext() ){
            Object key = it.next();
            FieldMap field = (FieldMap)recordMap.getFields().get(key);
            Object value = record.get(field.getColumAsName());
            
            try{
                //BeanUtil.setProperty(obj,field.getPropertyName(),value);
                field.setPropertyValue(obj, value);
            }catch(Exception e){
                System.out.print(obj.getClass().getName() + ":");
                System.out.print(field.getPropertyName());
                System.out.println( ":" + value);
                e.printStackTrace();
            }
        }
        return obj;
    }
    /**
     * USERテーブルから新しいidを取得します
     * @return 新規登録用のidを返します
     */
    public int getNewId(String tableName, String pkCulomName){
        int id = 0;
        StringBuffer sql = new StringBuffer();
        sql.append("select MAX(");
        sql.append(tableName).append(".").append(pkCulomName);
        sql.append(") AS NEW_ID FROM ").append(tableName);
        
        Records result = transaction.select(sql.toString());
        if(result.size() > 0){
            Record record = (Record)result.getRecord(0);
            id = record.getInt("NEW_ID");
            id ++;
        }
        return id;
    }
    
    public int getNewId(String tableName){
        int id = 0;
        StringBuffer sql = new StringBuffer();
        sql.append("select COUNT(*) AS NEW_ID FROM " + tableName);
        
        Records result = transaction.select(sql.toString());
        if(result.size() > 0){
            Record record = (Record)result.getRecord(0);
            id = record.getInt("NEW_ID");
            id ++;
        }
        return id;
    }
    
    /**
     * AUTONUMBERテーブルを使ってUSERテーブルから新しいid
     * を取得します
     * @return 新規登録用のidを返します
     */
    public int getNewIdFromAutoNumber(String tableName){
        int id = 0;
        AutoNumberDao dao = new AutoNumberDao();
        try{
            id = dao.getNext(tableName);
        }catch(Exception e){
            id = getNewId(tableName);
        }
        return id;
    }
}
