package gdsearch;

import java.util.*;
import java.util.logging.Logger;
import java.text.*;

import javax.cache.Cache;
import javax.cache.CacheException;
import javax.cache.CacheFactory;
import javax.cache.CacheManager;
import javax.jdo.PersistenceManager;
import com.google.appengine.api.datastore.*;
import com.google.appengine.api.datastore.Query.FilterOperator;
import com.google.appengine.api.memcache.jsr107cache.GCacheException;

import gdsearch.vo.GameData;
import gdsearch.vo.GameDataSearchCondition;
import gdsearch.vo.GameDataUrl;

public class GameDataDAO  {
    private Cursor cursor;
    private String strCursor;
    private Cache cache;

    /**
     * コンストラクタ
     */
    public GameDataDAO() {
        try {
            CacheFactory cacheFactory = CacheManager.getInstance().getCacheFactory();
            cache = cacheFactory.createCache(Collections.emptyMap());
        } catch (CacheException e) {
        }
    }

    /**
     * GameDataの挿入
     */
     public void insertGameData(GameData gd) {
         // DBに登録
         PersistenceManager pm = PMF.get().getPersistenceManager();
         try {
             pm.makePersistent(gd); // オブジェクトの内容をDBに登録
         } finally {
             pm.close();
         }
     }

     /**
      * 指定したGameDataが既に登録されているかチェックする。
      * 既に登録されている場合はfalse
      */
     public boolean chkDuplicateGameData(GameData gd) {
         // データストアサービスの取得
         DatastoreService service = DatastoreServiceFactory.getDatastoreService();
         // クエリ生成
         Query qry = new Query(GameData.class.getSimpleName());
         // IDを指定
         qry.addFilter("code"    , FilterOperator.EQUAL, gd.getCode());
         qry.addFilter("hard"    , FilterOperator.EQUAL, gd.getHard());
         qry.addFilter("title"   , FilterOperator.EQUAL, gd.getTitle());
         qry.addFilter("title2"  , FilterOperator.EQUAL, gd.getTitle2());
         qry.addFilter("maker"   , FilterOperator.EQUAL, gd.getMaker());
         qry.addFilter("maker2"  , FilterOperator.EQUAL, gd.getMaker2());
         qry.addFilter("genre"   , FilterOperator.EQUAL, gd.getGenre());
         qry.addFilter("media"   , FilterOperator.EQUAL, gd.getMedia());
         qry.addFilter("price"   , FilterOperator.EQUAL, gd.getPrice());
         qry.addFilter("saledate", FilterOperator.EQUAL, gd.getSaleDate());
         qry.addFilter("restrict", FilterOperator.EQUAL, gd.getRestrict());
         qry.addFilter("comment" , FilterOperator.EQUAL, gd.getComment());
         // データベースからデータを取得
         List<Entity> lst = service.prepare(qry).asList(FetchOptions.Builder.withOffset(0).limit(1));
         return lst.size() <= 0;
     }

    /**
     * 検索条件で設定した検索結果を取得
     *
     * 内容、ハード、表示件数による絞込み、およびソートはデータ取得時に行う
     * キーワード、発売年による絞込みはデータ取得後にJava側で行う
     *
     * @param request
     * @return
     * @throws ParseException
     */
    public List<Entity> getSearchResult(GameDataSearchCondition gdsc) throws ParseException {
        List<Entity> lstGd    = new ArrayList<Entity>();
        // 検索条件の表示開始位置、表示件数を記録しておく
        int displayStart  = gdsc.getDisplayStart();
        int displayNumber = gdsc.getDisplayNumber();
        // 500件単位で全データをスキャンする
        gdsc.setDisplayNumber(500);

        List<Entity> lstGdTmp;
        int selectCount = 0;
        boolean flgDisplayStart = false;
        for(int i = 0;/* 無限ループ */;i++) {
            // 500件単位で検索開始位置を書き換え
            gdsc.setDisplayStart(500 * i);

            // GameDataテーブルからデータを取得
            lstGdTmp = getGameData(gdsc);

            if(lstGdTmp.size() == 0) {
                break;
            }

            // 取得したデータを検索条件で絞込み
            lstGdTmp = extractGameData(lstGdTmp, gdsc);

            //--------------------------------------------------------------------
            // ここから行う処理の例；
            //
            // 表示開始:21(displayStart)
            // 表示:    20(displayNumber)
            //
            //         selectCount  lstGdTmp.size()
            //  1回目：4            4
            //  2回目：9            5
            //  3回目：15           6
            //  4回目：18           3
            // ------------------------------------ここからカウント
            //  5回目：22           4               最後の1件のみ追加
            //  6回目：27           5               5件すべて追加
            // …
            // 11回目：38           3               3件すべて追加
            // 12回目：42           4               4件すべて追加した後最後の2件を削除
            //--------------------------------------------------------------------

            selectCount += lstGdTmp.size();
            // 表示開始件数に達した場合
            if(!flgDisplayStart && selectCount >= displayStart) {
                flgDisplayStart = true;
                // 差分を抽出
                int start = displayStart - (selectCount - lstGdTmp.size());
                int end   = displayStart - (selectCount - lstGdTmp.size()) + displayNumber;
                if(end > lstGdTmp.size()) {
                   end = lstGdTmp.size();
                }
                lstGdTmp = lstGdTmp.subList(start,end);

            }
            if(flgDisplayStart) {
                // 絞り込んだデータを追加
                lstGd.addAll(lstGdTmp);
            }

            // 表示件数に達していた場合
            if(lstGd.size() >= displayNumber) {
                // 表示件数を超えた分を削除
                lstGd = lstGd.subList(0, displayNumber);
                break;
            }
        }

        return lstGd;
    }

    /**
     * GameDataテーブルの検索結果を取得
     * 絞り込みは取得後、Javaロジック側で行う
     */
    private List<Entity> getGameData(GameDataSearchCondition gdsc) {
        List<Entity> lstGameData = null;
        QueryResultList<Entity> qrlstGameData;

        try {
            // memcacheからデータを取得
            lstGameData = (List<Entity>)cache.get("Data:" + gdsc.getCacheKey());
        } catch (GCacheException e) {
            Logger logger = Logger.getLogger(this.getClass().getName());
            logger.warning("Memcache exception:" + "Data:" + gdsc.getCacheKey());
        }

        // 取得できた場合
        if(null != lstGameData) {
            try {
               // memcacheからカーソル文字列を取得
                strCursor = (String)cache.get("Cursor:" + gdsc.getCacheKey());
            } catch (GCacheException e) {
                Logger logger = Logger.getLogger(this.getClass().getName());
                logger.warning("Memcache exception:" + "Cursor:" + gdsc.getCacheKey());
            }
        // 取得できなかった場合
        } else {

            FetchOptions fetchOptions;
            if(strCursor == null) {
                fetchOptions = FetchOptions.Builder.withOffset(gdsc.getDisplayStart()).limit(gdsc.getDisplayNumber());
            } else {
                // Cursor.toWebSafeString()の文字列をカーソルに復元
                Cursor cursor = Cursor.fromWebSafeString(strCursor);
                // カーソルをもとに、そこから単位数分取得するオプションを作成
                fetchOptions = FetchOptions.Builder.withLimit(gdsc.getDisplayNumber()).cursor(cursor);
            }

            // データストアサービスの取得
            DatastoreService service = DatastoreServiceFactory.getDatastoreService();
            // クエリ生成
            Query qry = new Query(GameData.class.getSimpleName());

            // ハード名が選択されていた場合
            if("" != gdsc.getHard()) {
                qry.addFilter("hard", FilterOperator.EQUAL, gdsc.getHard());
            }
            // 内容が選択されていた場合
            if("" != gdsc.getGenre()) {
                qry.addFilter("genre", FilterOperator.EQUAL, gdsc.getGenre());
            }

            // 発売年（開始）が設定されている場合
            if( null != gdsc.getSaleStartDate() ) {
                qry.addFilter("saledate", FilterOperator.GREATER_THAN, gdsc.getSaleStartDate());
            }
            // 発売年（終了）が設定されている場合
            if( null != gdsc.getSaleEndDate() ) {
                qry.addFilter("saledate", FilterOperator.LESS_THAN, gdsc.getSaleEndDate());
            }

            // ソート順の設定
            qry.addSort(gdsc.getSortKey(), gdsc.getSortDirection());
            // データベースからデータを取得
            qrlstGameData = service.prepare(qry).asQueryResultList(fetchOptions);

            // ここまでの結果をカーソルにする
            cursor = qrlstGameData.getCursor();
            if (cursor == null) {
                // プロダクション環境では、末尾に達しているとnullになる模様
            }
            // カーソルをBase64エンコード文字列に変換
            strCursor = cursor.toWebSafeString();

            lstGameData = new ArrayList<Entity>(qrlstGameData);

            // キャシュに保存
            cache.put("Cursor:" + gdsc.getCacheKey(), strCursor);
            cache.put("Data:" + gdsc.getCacheKey(), lstGameData);
        }


        return lstGameData;
    }

    /**
     * 取得したGameDataを検索条件で絞込み
     * @throws ParseException
     */
    private List<Entity> extractGameData(List<Entity> lstGd, GameDataSearchCondition gdsc) throws ParseException {
        List<Entity> result = new ArrayList<Entity>();
        for (Entity etyGd : lstGd) {
/*
            // ハード名が選択されていた場合
            if("" != gdsc.getHard()) {
                // 選択されたハード名でない場合は除外
                if(!gdsc.getHard().equals((String)etyGd.getProperty("hard"))) {
                    continue;
                }
            }
            // 内容が選択されていた場合
            if("" != gdsc.getGenre()) {
                // 選択された内容でない場合は除外
                if(!gdsc.getGenre().equals((String)etyGd.getProperty("genre"))) {
                    continue;
                }
            }

            // 発売年が設定されている場合
            if( null != gdsc.getSaleStartDate() || null != gdsc.getSaleEndDate() ) {
                // 発売年でデータを絞り込み
                DateFormat df = new SimpleDateFormat("yyyy/MM/dd");
                if(null == gdsc.getSaleStartDate()) {
                    gdsc.setSaleStartDate(df.parse("1980/01/01"));
                }
                if(null == gdsc.getSaleEndDate()) {
                    gdsc.setSaleEndDate(df.parse("9999/12/31"));
                }
                Date  saleDate = (Date)etyGd.getProperty("saledate");
                // 選択された範囲でない場合は除外
                if(gdsc.getSaleStartDate().getTime() >= saleDate.getTime() ||
                   gdsc.getSaleEndDate().getTime()   <= saleDate.getTime()) {
                    continue;
                }
            }
*/
            // キーワードが入力されていた場合
            if("" != gdsc.getKeyword()) {
                // 半角スペースを削除して比較
            	String keyword = gdsc.getKeyword().replaceAll(" ","");
                String code   = ((String)etyGd.getProperty("code"  )).replaceAll(" ","");
                String title  = ((String)etyGd.getProperty("title" )).replaceAll(" ","");;
                String title2 = ((String)etyGd.getProperty("title2")).replaceAll(" ","");;
                String maker  = ((String)etyGd.getProperty("maker" )).replaceAll(" ","");;
                String maker2 = ((String)etyGd.getProperty("maker2")).replaceAll(" ","");;
                // どのキーワードともマッチしなかったら除外
                if(  code.indexOf(keyword) == -1 &&
                    title.indexOf(keyword) == -1 &&
                   title2.indexOf(keyword) == -1 &&
                    maker.indexOf(keyword) == -1 &&
                   maker2.indexOf(keyword) == -1 ) {
                    continue;
                }
            }
            // 全ての条件を満たしたら追加
            result.add(etyGd);
        }
        return result;
    }
    /**
     * GameDataUrlの挿入
     */
     public void insertGameDataUrl(GameDataUrl gdurl) {
         // DBに登録
         PersistenceManager pm = PMF.get().getPersistenceManager();
         try {
             pm.makePersistent(gdurl); // オブジェクトの内容をDBに登録
         } finally {
             pm.close();
         }
     }
    /**
     * 指定したGameDataUrlが既に登録されているかチェックする。
     * 既に登録されている場合はfalse
     */
    public boolean chkDuplicateGameDataUrl(GameDataUrl gdurl) {
        // データストアサービスの取得
        DatastoreService service = DatastoreServiceFactory.getDatastoreService();
        // クエリ生成
        Query qry = new Query(GameDataUrl.class.getSimpleName());
        // IDを指定
        qry.addFilter("keystr", FilterOperator.EQUAL, gdurl.getKeystr());
//        qry.addFilter("url",    FilterOperator.EQUAL, gdurl.getUrl());
        // データベースからデータを取得
        List<Entity> lst = service.prepare(qry).asList(FetchOptions.Builder.withOffset(0).limit(1));
        return lst.size() <= 0;
    }
    /**
     * GameDataUrlを取得
     */
    public String getGameDataUrl(String keystr) {
        String result;
        // データストアサービスの取得
        DatastoreService service = DatastoreServiceFactory.getDatastoreService();
        // クエリ生成
        Query qry = new Query(GameDataUrl.class.getSimpleName());
        // IDを指定
        qry.addFilter("keystr", FilterOperator.EQUAL, keystr);
        // データベースからデータを取得
        Entity ety = service.prepare(qry).asSingleEntity();
        if(null == ety) {
            result = "";
        } else {
            result = (String)ety.getProperty("url");
        }
        return result;
    }

}
