/*
 * To change this license header, choose License Headers in Project Properties.
 * To change this template file, choose Tools | Templates
 * and open the template in the editor.
 */

package kr.co.kihyun.service;

import java.lang.reflect.Field;
import java.math.BigDecimal;
import java.util.List;
import java.util.Map;
import javax.jdo.PersistenceManager;
import javax.jdo.Query;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 *
 * @author Kts
 */
public class BaseService {
    private static final Logger LOG = LoggerFactory.getLogger(BaseService.class);
    int recordCount = 0;
    protected StringBuilder countQuery;
    protected StringBuilder searchQuery;
    protected Map           params;
    protected PersistenceManager pm;

    /**
     * 기본생성자
     */
    public BaseService() { }

    /**
     * 생성자
     * @param pm PersistenceManager
     */
    public BaseService(PersistenceManager pm) {
        this.pm = pm;
    }

    /**
     * PersistenceManager객체를 저장한다.
     * @param pm PersistenceManager
     */
    public void setPm(PersistenceManager pm) {
        this.pm = pm;
    }

    /**
     * 조건을 만족하는 레코드갯수를 반환한다.
     * @return int
     */
    public int getCount() {
        return getCount(null);
    }

    /**
     * 쿼리를 파리미터로 받고 조건을 만족하는 레코드갯수를 반환한다.
     * @param sb StringBuilder
     * @return int
     */
    public int getCount(StringBuilder sb) {
        StringBuilder sql = sb;
        if( sb == null ) {
            sql = countQuery;
        }

        return getResultCount(sql);
    }

    /**
     * Main Query로 부터 쿼리를 생성하고 조건을 만족하는 레코드갯수를 반환한다.
     * @return int
     */
    public int getCountFromMainQuery() {
        return getCountFromMainQuery(null);
    }

    /**
     * Main Query로 부터 쿼리를 생성하고 조건을 만족하는 레코드갯수를 반환한다.
     * @param sb StringBuilder
     * @return int
     */
    public int getCountFromMainQuery(StringBuilder sb) {
        StringBuilder sql = sb;
        if( sb == null ) {
            sql = countQuery;
            sql.insert(0, "SELECT COUNT(*) AS CNT FROM (\n");
            sql.append(")  ");
        }

        return getResultCount(sql);
    }

    /**
     * 레코드갯수를 반환하는 쿼리를 실행하는 메소드
     * @param sql
     * @return int
     */
    private int getResultCount(StringBuilder sql) {
        Query query = pm.newQuery("javax.jdo.query.SQL",sql.toString());
        //LOG.debug("count query={}", sql.toString());
        if( params != null && params.size() > 0 ) {
            List list = (List)query.executeWithMap(params);
            return ((BigDecimal)list.get(0)).intValue();
        }
        return -1;
    }

    /**
     * Main Query로 부터 Paginating Query를 생성한다.
     * 쿼리성능을 위해서 마지막레코드 위치를 파라미터로 넘겨준다.
     * DB2에 최적화된 쿼리이므로 다른 DBMS에 적용 시 변경 필요.
     * @param sb StringBuilder
     * @param endNo int - 마지막레코드 위치
     * @return int
     */
    protected StringBuilder procPagenatingQuery(StringBuilder sb, int endNo) {
        StringBuilder pagenatingQuery = new StringBuilder();
        pagenatingQuery.append(sb);
        pagenatingQuery.insert(0, "SELECT * FROM (\n");
        //변수를 설정할 수 없음
        //sb.append("FETCH FIRST :endNo ROW ONLY\n");
        pagenatingQuery.append("FETCH FIRST ");
        pagenatingQuery.append(endNo);
        pagenatingQuery.append(" ROW ONLY\n");
        pagenatingQuery.append(")R WHERE R.ROWNO BETWEEN :startNo AND :endNo with UR \n");
        return pagenatingQuery;
    }

    /**
     * Main Query로 부터 Paginating Query를 생성한다.
     * 쿼리성능을 위해서 마지막레코드 위치를 파라미터로 넘겨준다.
     * DB2에 최적화된 쿼리이므로 다른 DBMS에 적용 시 변경 필요.
     * @param sb StringBuilder
     * @return int
     */
    public StringBuilder procPagenatingQuery(StringBuilder sb) {
        return procPagenatingQuery(sb, (Integer)this.getParams().get("endNo"));
    }

    /**
     * 메인검색쿼리를 페이징처리하는 쿼리로 변경하는 메소드
     */
    public void convertToPagenatingQuery() {
//        searchQuery.insert(0, "SELECT * FROM (\n");
//        searchQuery.append("FETCH FIRST ");
//        searchQuery.append((Integer)this.getParams().get("endNo") == 0 ? 1 : (Integer)this.getParams().get("endNo") );
//        searchQuery.append(" ROW ONLY\n");
//        searchQuery.append(")R WHERE R.ROWNO BETWEEN :startNo AND :endNo  \n");
	      searchQuery.insert(0, "SELECT * FROM (SELECT ROWNUM AS ROWNO, R.* FROM( \n");
	      searchQuery.append(")R)S WHERE S.ROWNO BETWEEN :startNo AND :endNo  \n");

        if( LOG.isDebugEnabled() ) {
            LOG.debug("paginating query={}", searchQuery.toString());
            LOG.debug("params={}", params);
        }
    }

    /**
     * 멤버변수 searchQuery에 저장된 쿼리를 실행한 결과를 반환하는 메소드
     * @param cls - VO객체에 대한 클래스객체
     * @return List - 검색결과목록
     */
    public List executeQuery(Class cls) {
        return executeQuery(null, null, cls);
    }

    /**
     * 쿼리를 실행한 결과를 반환하는 메소드
     * @param sbQuery - 쿼리객체
     * @param params - 검색조건맵
     * @param cls - VO객체에 대한 클래스객체
     * @return List - 검색결과목록
     */
    public List executeQuery(final StringBuilder sbQuery, final Map params, final Class cls) {
        StringBuilder sql = sbQuery;
        if( sql == null || sql.length() == 0 ) sql = searchQuery;
        Query query = pm.newQuery("javax.jdo.query.SQL", sql.toString());

        if( cls != null ) {
            query.setResultClass(cls);
        }
        Map parameters = params;
        if( parameters == null || parameters.isEmpty() )  parameters = this.params;

        List list = (List)query.executeWithMap(parameters);

        return list;
    }

    /**
     * 멤버변수 searchQuery에 저장된 쿼리를 실행한 결과를 반환하는 메소드
     * @return List - 검색결과목록
     */
    public List executeQuery() {
        return executeQuery(null);
    }

    /**
     * 레코드갯수조회 쿼리를 반환하는 멤버메소드
     * @return
     */
    public StringBuilder getCountQuery() {
        return countQuery;
    }

    /**
     * 레코드갯수조회 쿼리를 설정하는 멤버메소드
     * @param countQuery - 레코드갯수조회 쿼리
     */
    public void setCountQuery(StringBuilder countQuery) {
        this.countQuery = new StringBuilder(countQuery);
    }

    /**
     * 검색조회 쿼리를 반환하는 멤버메소드
     * @return
     */
    public StringBuilder getSearchQuery() {
        return searchQuery;
    }

    /**
     * 검색조회 쿼리를 설정하는 멤버메소드
     * @param searchQuery
     */
    public void setSearchQuery(StringBuilder searchQuery) {
        this.searchQuery = searchQuery;
    }

    /**
     * 검색조건파라미터맵을 반환하는 멤버메소드
     * @return
     */
    public Map getParams() {
        return params;
    }

    /**
     * 검색조건파라미터맵을 설정하는 멤버메소드
     * @param params
     */
    public void setParams(Map params) {
        this.params = params;
    }

    /**
     * List에 존재하는 객체의 멤버변수에 대한 로그출력 메소드
     * @param list - 동일한 객체가 존재해야 한다.
     */
    public void logResultList(List list) {
        //Method[] methods = list.get(0).getClass().getDeclaredMethods();
        int size = list.size();
        if( size == 0 ) return;
        for( int i = 0; i < size; i++ ) {
            Object obj = list.get(i);
            Field[] fields = obj.getClass().getDeclaredFields();
            for( Field field : fields ) {
                try {
                    field.setAccessible(true);
                    Object fObj = field.get(obj);
                    if( fObj == null ) continue;
                    if( LOG.isDebugEnabled() ) {
                        LOG.debug("Row {}:{}={}", new Object[]{(i+ 1),field.getName(), fObj});
                    }
                }catch(IllegalAccessException ex) {
                    if( LOG.isErrorEnabled() )
                        LOG.debug("field error1={}", ex.getMessage());
                } catch (IllegalArgumentException ex) {
                    if( LOG.isErrorEnabled() )
                        LOG.debug("field error2={}", ex.getMessage());
                } catch (SecurityException ex) {
                    if( LOG.isErrorEnabled() )
                        LOG.debug("field error3={}", ex.getMessage());
                }
            }
//            for( Method method : methods ) {
//                String methodName = method.getName();
//                if( !methodName.startsWith("get") ) continue;
//
//                try {
//                    Object rtnObj = method.invoke(obj, new Object[]{});
//                    if( rtnObj == null ) continue;
//                    LOG.debug("{}={}",methodName, rtnObj);
//
//                }catch(Exception ex) {
//                    LOG.debug("error={}", ex.getMessage());
//                }
//            }

        }
    }
}
