package gdsearch;

import java.util.*;
import java.text.*;

import javax.jdo.PersistenceManager;
import com.google.appengine.api.datastore.*;
import com.google.appengine.api.datastore.Query.FilterOperator;
import gdsearch.vo.GameData;
import gdsearch.vo.GameDataSearchCondition;

public class GameDataDAO  {

    /**
     * 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);

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

            // GameDataテーブルからデータを取得
            qrlstGdTmp = getGameData(gdsc, page);
            if(qrlstGdTmp.size() == 0) {
                break;
            }

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

            // 取得したデータを検索条件で絞込み
            List<Entity> lstGdTmp = extractGameData(qrlstGdTmp, 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 QueryResultList<Entity> getGameData(GameDataSearchCondition gdsc, String page) {
        FetchOptions fetchOptions;
        if(page == "") {
            fetchOptions = FetchOptions.Builder.withOffset(gdsc.getDisplayStart()).limit(gdsc.getDisplayNumber());
        } else {
            // Cursor.toWebSafeString()の文字列をカーソルに復元
            Cursor cursor = Cursor.fromWebSafeString(page);
            // カーソルをもとに、そこから単位数分取得するオプションを作成
            fetchOptions = FetchOptions.Builder.withLimit(gdsc.getDisplayNumber()).cursor(cursor);
        }

        // データストアサービスの取得
        DatastoreService service = DatastoreServiceFactory.getDatastoreService();
        // クエリ生成
        Query qry = new Query(GameData.class.getSimpleName());
        // ソート順の設定
        qry.addSort(gdsc.getSortKey(), gdsc.getSortDirection());
        // データベースからデータを取得
        QueryResultList<Entity> lstGameData = service.prepare(qry).asQueryResultList(fetchOptions);

        return lstGameData;
    }

    /**
     * 取得したGameDataを検索条件で絞込み
     * @throws ParseException
     */
    private List<Entity> extractGameData(QueryResultList<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 code   = (String)etyGd.getProperty("code");
                String title  = (String)etyGd.getProperty("title");
                String title2 = (String)etyGd.getProperty("title2");
                String maker  = (String)etyGd.getProperty("maker");
                String maker2 = (String)etyGd.getProperty("maker2");
                // どのキーワードともマッチしなかったら除外
                if(  code.indexOf(gdsc.getKeyword()) == -1 &&
                    title.indexOf(gdsc.getKeyword()) == -1 &&
                   title2.indexOf(gdsc.getKeyword()) == -1 &&
                    maker.indexOf(gdsc.getKeyword()) == -1 &&
                   maker2.indexOf(gdsc.getKeyword()) == -1 ) {
                    continue;
                }
            }
            // 全ての条件を満たしたら追加
            result.add(etyGd);
        }
        return result;
    }

}
