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 = 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);
                String fkClassName = fkRecordMap.getClassName();
                //外部参照用RecordMapに基づくRecordMapインスタンスを新たに生成する
                RecordMap joinRecordMap = RecordMapFactory.createRecordMap(fkClassName);
                //外部参照オブジェクトを作成する
                Object fkObj = createObject(joinRecordMap, record);
                //外部参照オブジェクトを結果オブジェクトにセットする
                BeanUtil.setProperty(obj, propertyName, fkObj);
            }
            result.add(obj);
        }
        
        return result;
    }
    
    /**
     * 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;
    }
    
//    /**
//     * コミットを行います<BR>
//     * コミットに失敗するとロールバック処理を行います
//     * @throws TransactionException
//     */
//    public void commit() throws TransactionException{
//        try{
//            // コミットします
//            transaction.commit();
//        }catch(TransactionException e){
//            throw e;
//        }
//    }
//
//    /**
//     * ロールバック処理を行います
//     * @throws TransactionException
//     */
//    public void rollback() throws TransactionException{
//        transaction.rollback();
//    }
//
//  /**
//   * 結合テーブルが持っているフィールド情報をオブジェクトのプロパティ
//   * にマッピングします
//   * @param bean フィールドにセットされた値をセットするBeanオブジェクト
//   * @param crt Criteriaインスタンス
//   * @param rs RecordMapインスタンスの配列
//   * @throws TransactionException
//   */
//  private void addLinkObject(Object bean, Criteria crt,
//  		HashMap linkRecords, HashMap rs) throws TransactionException{
//  	Set keySet = linkRecords.keySet();
//  	Iterator it = keySet.iterator();
//      while( it.hasNext() ){
//          Object key = it.next();
//          RecordMap record = (RecordMap)linkRecords.get(key);
//          Object property = createObject(record, crt, rs);
//
//          //System.out.println(record.getClassOnlyName());
//          try{
//          	BeanUtil.setProperty(bean, record.getClassOnlyName(), property);
//
//          }catch(Exception e){
//          	e.printStackTrace();
//          }
//      }
//  }
    
}
