/*
 * Aipo is a groupware program developed by Aimluck,Inc.
 * http://aipostyle.com/
 *
 * Copyright(C) 2011 avanza Co.,Ltd. All rights reserved.
 * http://www.avnz.co.jp/
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
 */
package com.aimluck.eip.schedule;

import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.List;

import org.apache.cayenne.exp.Expression;
import org.apache.cayenne.exp.ExpressionFactory;
import org.apache.jetspeed.services.logging.JetspeedLogFactoryService;
import org.apache.jetspeed.services.logging.JetspeedLogger;
import org.apache.turbine.util.RunData;
import org.apache.velocity.context.Context;

import com.aimluck.commons.field.ALDateTimeField;
import com.aimluck.eip.cayenne.om.portlet.EipTSchedule;
import com.aimluck.eip.cayenne.om.portlet.EipTScheduleMap;
import com.aimluck.eip.common.ALBaseUser;
import com.aimluck.eip.common.ALDBErrorException;
import com.aimluck.eip.common.ALPageNotFoundException;
import com.aimluck.eip.modules.actions.common.ALAction;
import com.aimluck.eip.orm.Database;
import com.aimluck.eip.orm.query.ResultList;
import com.aimluck.eip.orm.query.SelectQuery;
import com.aimluck.eip.schedule.util.SchedulePrintUtils;
import com.aimluck.eip.schedule.util.ScheduleUtils;
import com.aimluck.eip.util.ALEipUtils;

/**
 * 印刷用スケジュール表示の検索結果を管理するクラスです。
 * 
 */
public class SchedulePrintWeeklySelectData extends ScheduleWeeklySelectData {

  /** logger */
  private static final JetspeedLogger logger = JetspeedLogFactoryService.getLogger(SchedulePrintWeeklySelectData.class.getName());

  /** <code>userid</code> ログインユーザーID */
  private int login_user_id;

  /** <code>printDate</code> 印刷日時 */
  private ALDateTimeField printDate;

  /** <code>target_user_alias_name</code> 表示対象のユーザ 表示名 */
  private String targetUserAliasName;

  /** <code>target_user_id</code> 表示対象のユーザ ID */
  private String targetUserId;

  /** <code>TARGET_USER_ID</code> ユーザによる表示切り替え用変数の識別子 */
  private static final String TARGET_USER_ID = "target_user_id";

  /** <code>outWeekCon</code> 時間外スケジュールコンテナ */
  private List<ScheduleTermWeekContainer> outResultConList;

  /** デフォルト表示開始時刻 */
  private static final int DEFAULT_START_VIEW_HOUR = 7;

  /** デフォルト表示終了時刻 */
  private static final int DEFAULT_END_VIEW_HOUR = 20;

  /** １時間の高さ(px指定) */
  private static final int ONE_HOUR_HEIGHT = 40;

  /** １日の横幅(px指定) */
  private static final int ONE_DAY_WIDTH = 146;

  /** スケジュールの最低表示時間（分） */
  private static final int MIN_SCHEDULE_MINUTES = 30;

  /** 期間スケジュールの最大件数 */
  private static final int MAX_TERM_SCHEDULE_COUNT = 5;

  /** 時間外スケジュールの最大件数 */
  private static final int MAX_OUT_SCHEDULE_COUNT = 5;

  /** 表示できない期間スケジュールがあるかどうかのフラグリスト */
  private List<Boolean> cannotTermDisplayFlagList;

  /** 表示できない時間外スケジュールがあるかどうかのフラグリスト */
  private List<Boolean> cannotOutDisplayFlagList;

  // 表示可否
  private String scheduleAcl;

  // IE9かどうか
  private boolean isIe9;

  /** <code>duplicateList</code> 重複スケジュールリスト */
  private List<List<List<ScheduleResultData>>> duplicateList;

  // add

  /**
   * 初期処理
   * 
   * @param action
   *          アクションクラス
   * @param rundata
   *          JetSpeedランデータ
   * @param context
   *          JetSpeedコンテキスト
   * @see com.aimluck.eip.common.ALAbstractSelectData#init(com.aimluck.eip.modules.actions.common.ALAction,
   *      org.apache.turbine.util.RunData, org.apache.velocity.context.Context)
   */
  @Override
  public void init(ALAction action, RunData rundata, Context context) throws ALPageNotFoundException, ALDBErrorException {

    // スーパークラスのメソッドを呼び出す。
    super.init(action, rundata, context);
    login_user_id = ALEipUtils.getUserId(rundata);

    // add start No.4 スケジュール印刷（週単位）

    // 印刷日時
    printDate = new ALDateTimeField("yyyy/MM/dd HH:mm");
    Calendar now = Calendar.getInstance();
    printDate.setValue(now.getTime());
    // パラメータから印刷対象ユーザーIDを取得
    if (rundata.getParameters().containsKey(TARGET_USER_ID)) {
      targetUserId = rundata.getParameters().getString(TARGET_USER_ID);
    }
    ALBaseUser targetUser = ALEipUtils.getBaseUser(Integer.parseInt(targetUserId));
    // ユーザー表示名を取得
    targetUserAliasName = targetUser.getDisplayName();

    // 時間外スケジュールリストの初期化
    outResultConList = new ArrayList<ScheduleTermWeekContainer>();

    // 表示できない期間スケジュールがあるかどうかのフラグリストと、
    // 表示できない時間外スケジュールがあるかどうかのフラグリストをfalseで初期化する。
    cannotTermDisplayFlagList = new ArrayList<Boolean>();
    cannotOutDisplayFlagList = new ArrayList<Boolean>();
    // 重複スケジュールリストを初期化
    duplicateList = new ArrayList<List<List<ScheduleResultData>>>();
    for (int dayIndex = 0; dayIndex < 7; dayIndex++) {
      cannotTermDisplayFlagList.add(false);
      cannotOutDisplayFlagList.add(false);
      List<List<ScheduleResultData>> dayDupList = new ArrayList<List<ScheduleResultData>>();
      List<ScheduleResultData> dayColList = new ArrayList<ScheduleResultData>();
      dayDupList.add(dayColList);
      duplicateList.add(dayDupList);
    }
    isIe9 = ScheduleUtils.isIE9(rundata);

    // add end
  }

  /**
   * 一覧表示します。
   * 
   * @param action
   * @param rundata
   * @param context
   * @return TRUE 成功 FASLE 失敗
   */
  @Override
  public boolean doViewList(ALAction action, RunData rundata, Context context) {
    boolean result = super.doViewList(action, rundata, context);

    for (int dayIndex = 0; dayIndex < 7; dayIndex++) {
      ScheduleDayContainer dayCon = weekCon.getDayList().get(dayIndex);
      // 重複リストを作成
      for (ScheduleResultData rd : dayCon.getScheduleListDesc()) {
        createDuplicateList(rd, dayIndex);
      }
      // 重複の列数を設定
      SchedulePrintUtils.setScheduleColumnCount(duplicateList.get(dayIndex), DEFAULT_END_VIEW_HOUR, MIN_SCHEDULE_MINUTES);
    }
    return result;
  }

  /**
   * 一覧データを取得します。 <BR>
   * 
   * @param rundata
   *          JetSpeedランデータ
   * @param context
   *          JetSpeedコンテキスト
   * @return 結果リスト
   * @see com.aimluck.eip.common.ALAbstractListData#selectData(org.apache.turbine.util.RunData,
   *      org.apache.velocity.context.Context)
   * @throws ALPageNotFoundException
   *           , ALDBErrorException
   */
  @Override
  protected ResultList<EipTScheduleMap> selectList(RunData rundata, Context context) throws ALPageNotFoundException, ALDBErrorException {
    try {

      // change start No.4 スケジュール印刷（週単位）
      // ソート順が異なるため、期間スケジュールと通常スケジュールを別々に取得して合体する
      List<EipTScheduleMap> termList = getSelectQueryForTerm(rundata, context).fetchList();
      List<EipTScheduleMap> normalList = getSelectQueryForNormal(rundata, context).fetchList();
      termList.addAll(normalList);

      return new ResultList<EipTScheduleMap>(ScheduleUtils.sortByDummySchedule(termList));
      // change end
    } catch (Exception e) {
      // change start No.4 スケジュール印刷（週単位）
      // logger.error("スケジュール印刷用画面の表示に失敗しました[" +
      // ALEipUtils.getBaseUser(login_user_id).getUserName() + "]", e);
      logger.error("週間スケジュール印刷用画面の表示に失敗しました[" + ALEipUtils.getBaseUser(Integer.parseInt(targetUserId)).getUserName() + "]", e);
      // change end
      return null;
    }
  }

  // add start No.4 スケジュール印刷（週単位）

  /**
   * 検索条件を設定した SelectQuery を返します。 ユーザーIDの条件をログインユーザーから印刷対象ユーザーに変更
   * 期間スケジュールのみを対象とし、開始日時の昇順、終了日時の昇順、スケジュールIDの昇順でソートする。
   * 
   * @param rundata
   * @param context
   * @return query
   * @see com.aimluck.eip.schedule.ScheduleWeeklySelectData
   */
  protected SelectQuery<EipTScheduleMap> getSelectQueryForTerm(RunData rundata, Context context) {
    SelectQuery<EipTScheduleMap> query = Database.query(EipTScheduleMap.class);

    // 印刷対象ユーザ
    Expression exp1 = ExpressionFactory.matchExp(EipTScheduleMap.USER_ID_PROPERTY, Integer.valueOf(targetUserId));
    query.setQualifier(exp1);
    // ユーザのスケジュール
    Expression exp2 = ExpressionFactory.matchExp(EipTScheduleMap.TYPE_PROPERTY, ScheduleUtils.SCHEDULEMAP_TYPE_USER);
    query.andQualifier(exp2);
    // 期間スケジュール
    Expression exp3 =
      ExpressionFactory.matchExp(EipTScheduleMap.EIP_TSCHEDULE_PROPERTY + "." + EipTSchedule.REPEAT_PATTERN_PROPERTY, ScheduleConst.SCHEDULE_PATTERN_SPAN);
    query.andQualifier(exp3);

    // 開始日時
    Expression exp11 = ExpressionFactory.greaterOrEqualExp(EipTScheduleMap.EIP_TSCHEDULE_PROPERTY + "." + EipTSchedule.END_DATE_PROPERTY, viewStart.getValue());
    // 終了日時
    Expression exp12 = ExpressionFactory.lessOrEqualExp(EipTScheduleMap.EIP_TSCHEDULE_PROPERTY + "." + EipTSchedule.START_DATE_PROPERTY, viewEndCrt.getValue());
    query.andQualifier(exp11.andExp(exp12));
    // 開始日時 昇順＞終了日時 降順＞スケジュールID(スケジュールマップ) 昇順でソート
    query.orderAscending(EipTScheduleMap.EIP_TSCHEDULE_PROPERTY + "." + EipTSchedule.START_DATE_PROPERTY);
    query.orderAscending(EipTScheduleMap.EIP_TSCHEDULE_PROPERTY + "." + EipTSchedule.END_DATE_PROPERTY);
    query.orderAscending(EipTScheduleMap.SCHEDULE_ID_PROPERTY);

    return query;
  }

  /**
   * 検索条件を設定した SelectQuery を返します。 ユーザーIDの条件をログインユーザーから印刷対象ユーザーに変更
   * 通常スケジュールのみを対象とし、開始日時の昇順、終了日時の昇順、スケジュールIDの昇順でソートする。
   * 
   * @param rundata
   * @param context
   * @return query
   * @see com.aimluck.eip.schedule.ScheduleWeeklySelectData
   */
  protected SelectQuery<EipTScheduleMap> getSelectQueryForNormal(RunData rundata, Context context) {
    SelectQuery<EipTScheduleMap> query = Database.query(EipTScheduleMap.class);

    // 印刷対象ユーザ
    Expression exp1 = ExpressionFactory.matchExp(EipTScheduleMap.USER_ID_PROPERTY, Integer.valueOf(targetUserId));
    query.setQualifier(exp1);
    // ユーザのスケジュール
    Expression exp2 = ExpressionFactory.matchExp(EipTScheduleMap.TYPE_PROPERTY, ScheduleUtils.SCHEDULEMAP_TYPE_USER);
    query.andQualifier(exp2);
    // 期間スケジュール以外
    Expression exp3 =
      ExpressionFactory.noMatchExp(EipTScheduleMap.EIP_TSCHEDULE_PROPERTY + "." + EipTSchedule.REPEAT_PATTERN_PROPERTY, ScheduleConst.SCHEDULE_PATTERN_SPAN);
    query.andQualifier(exp3);

    // 終了日時
    Expression exp11 = ExpressionFactory.greaterOrEqualExp(EipTScheduleMap.EIP_TSCHEDULE_PROPERTY + "." + EipTSchedule.END_DATE_PROPERTY, viewStart.getValue());
    // 開始日時
    Expression exp12 = ExpressionFactory.lessOrEqualExp(EipTScheduleMap.EIP_TSCHEDULE_PROPERTY + "." + EipTSchedule.START_DATE_PROPERTY, viewEndCrt.getValue());
    query.andQualifier(exp11.andExp(exp12));
    // 開始日時 昇順＞終了日時 降順＞スケジュールID(スケジュールマップ) 昇順でソート
    query.orderAscending(EipTScheduleMap.EIP_TSCHEDULE_PROPERTY + "." + EipTSchedule.START_DATE_PROPERTY);
    query.orderDesending(EipTScheduleMap.EIP_TSCHEDULE_PROPERTY + "." + EipTSchedule.END_DATE_PROPERTY);
    query.orderAscending(EipTScheduleMap.SCHEDULE_ID_PROPERTY);

    return query;
  }

  /**
   * 結果を取得する。
   * 
   * 
   * @param record
   * @return
   * @throws ALPageNotFoundException
   * @throws ALDBErrorException
   */
  @Override
  protected Object getResultData(EipTScheduleMap record) throws ALPageNotFoundException, ALDBErrorException {
    ScheduleResultData rd = new ScheduleResultData();
    rd.initField();
    try {
      EipTSchedule schedule = record.getEipTSchedule();
      // スケジュールが棄却されている場合は表示しない
      if ("R".equals(record.getStatus())) {
        return rd;
      }

      SelectQuery<EipTScheduleMap> mapquery = Database.query(EipTScheduleMap.class);
      Expression mapexp1 = ExpressionFactory.matchExp(EipTScheduleMap.SCHEDULE_ID_PROPERTY, schedule.getScheduleId());
      mapquery.setQualifier(mapexp1);
      Expression mapexp2 = ExpressionFactory.matchExp(EipTScheduleMap.USER_ID_PROPERTY, Integer.toString(login_user_id));
      mapquery.andQualifier(mapexp2);

      List<EipTScheduleMap> schedulemaps = mapquery.fetchList();
      boolean is_member = (schedulemaps != null && schedulemaps.size() > 0) ? true : false;

      // <閲覧予定ユーザー>のログインユーザーに対する<表示可否>を取得する。
      scheduleAcl = ScheduleUtils.getScheduleAcl(Integer.toString(login_user_id), targetUserId);

      // 完全に非公開(コンテナに格納しません）
      if (ScheduleConst.SCHEDULE_ACL_PUBLIC.equals(scheduleAcl) && !"D".equals(record.getStatus()) && "P".equals(schedule.getPublicFlag()) && !is_member) {
        return rd;
      }

      // 秘書設定元ユーザーが作成者で完全に非公開(コンテナに格納しません）
      if (ScheduleConst.SCHEDULE_ACL_SECRETARY.equals(scheduleAcl)
        && !"D".equals(record.getStatus())
        && "P".equals(schedule.getPublicFlag())
        && (schedule.getCreateUserId().intValue() == record.getUserId().intValue())
        && !is_member) {
        return rd;
      }

      // 役員自身が「非公開」設定した場合は秘書でも詳細閲覧はできない
      if ("C".equals(schedule.getPublicFlag())
        && (ScheduleConst.SCHEDULE_ACL_PUBLIC.equals(scheduleAcl) || (ScheduleConst.SCHEDULE_ACL_SECRETARY.equals(scheduleAcl) && schedule
          .getCreateUserId()
          .intValue() == record.getUserId().intValue()))
        && !is_member) {
        // 予定名
        rd.setName("非公開");
        // 場所
        rd.setPlace("");
        // 状態
        rd.setStatus("");
        // 重要フラグ
        rd.setPriority("");
        // 仮予定フラグ
        rd.setTemporaryFlag("");
      } else {
        // 予定名
        rd.setName(schedule.getName());
        // 場所
        rd.setPlace(schedule.getPlace());
        // 状態
        rd.setStatus(record.getStatus());
        // 重要フラグ
        rd.setPriority(record.getPriority());
        // 仮予定フラグ
        rd.setTemporaryFlag(schedule.getTemporaryFlag());
      }
      // ID
      rd.setScheduleId(schedule.getScheduleId().intValue());
      // 親スケジュール ID
      rd.setParentId(schedule.getParentId().intValue());
      // 開始日時
      rd.setStartDate(schedule.getStartDate());
      // 終了日時
      rd.setEndDate(schedule.getEndDate());
      // 公開するかどうか
      rd.setPublic("O".equals(schedule.getPublicFlag()));
      // ダミーか
      rd.setDummy("D".equals(record.getStatus()));
      // 繰り返しパターン
      rd.setPattern(schedule.getRepeatPattern());

      // 期間スケジュールの場合
      if (rd.getPattern().equals("S")) {
        int stime = -(int) ((viewStart.getValue().getTime() - rd.getStartDate().getValue().getTime()) / 86400000);
        int etime = -(int) ((viewStart.getValue().getTime() - rd.getEndDate().getValue().getTime()) / 86400000);
        if (stime < 0) {
          stime = 0;
        }
        int count = stime;
        int col = etime - stime + 1;
        // 行をはみ出す場合
        if (count + col > 7) {
          col = 7 - count;
        }
        // rowspan を設定
        rd.setRowspan(col);
        if (col > 0) {
          // 期間スケジュール を格納
          addTermResultData(count, rd);
        }

        return rd;
      }

      // コンテナ内のスケジュールを、時間内スケジュールと時間外スケジュールに仕分けして格納する
      createResultData(rd);

    } catch (Exception e) {
      logger.error("Exception", e);

      return null;
    }
    return rd;
  }

  /**
   * 時間内か、時間外か判定し、スケジュールをそれぞれのコンテナに追加します。
   * 
   * @param con
   *          日コンテナ
   * @param rd
   *          スケジュールデータ
   */
  public void addResultData(ScheduleDayContainer con, ScheduleResultData rd) {
    // 時間外かどうかを判定。ダミースケジュールは通常コンテナへ格納
    if (!rd.isDummy() && SchedulePrintUtils.isOutSchedule(rd, DEFAULT_START_VIEW_HOUR, DEFAULT_END_VIEW_HOUR)) {
      // 時間外コンテナへ追加し、時間外削除用リストに追加
      int count = -(int) ((viewStart.getValue().getTime() - rd.getStartDate().getValue().getTime()) / 86400000);
      addOutResultData(count, rd);
      return;
    }
    // 通常スケジュールコンテナに追加
    con.addResultData(rd);

  }

  /**
   * 期間スケジュールを追加する。<br>
   * 1日の期間スケジュールの上限を超えない場合のみ追加し、 日ごとの期間スケジュールの件数を更新する。
   * 
   * @param count
   *          該当週の日付けカウント
   * @param rd
   *          スケジュールデータ
   */
  private void addTermResultData(int count, ScheduleResultData rd) {

    // 1日の期間スケジュールの上限を超えないかどうかチェックする
    if (canAddTermResultData(termWeekConList, rd, MAX_TERM_SCHEDULE_COUNT)) {
      // 期間スケジュールをコンテナに格納
      ScheduleUtils.addTermSchedule(termWeekConList, viewStart.getValue(), count, rd);
    } else {
      // 1日の期間スケジュールの上限を超えた場合はコンテナに格納しない。
      // 表示できないスケジュールがあるかどうかのフラグを立てる。
      // このフラグによって画面でアイコンを表示するかどうかを制御する。
      for (int i = 0; rd.getRowspan() > i; i++) {
        cannotTermDisplayFlagList.set(count + i, true);
      }
    }
  }

  /**
   * 時間外スケジュールを追加する。<br>
   * 
   * 1日の時間外スケジュールの上限を超えない場合のみ追加し、 日ごとの時間外スケジュールの件数を更新する。
   * 繰り返し判定部分はcom.aimluck.eip
   * .schedule.ScheduleDayContainer.addResultData(ScheduleResultData)から抜粋
   * 
   * @param count
   *          日付カウント
   * @param rd
   *          スケジュールデータ
   */
  private void addOutResultData(int count, ScheduleResultData rd) {
    // 通常スケジュールコンテナに格納されているダミースケジュールを探す。
    ScheduleDayContainer dayCon = weekCon.getDayList().get(count);
    int size = dayCon.getScheduleList().size();

    for (int i = 0; i < size; i++) {
      ScheduleResultData rd2 = dayCon.getScheduleList().get(i);
      if (rd.isRepeat()
        && rd2.isDummy()
        && rd.getScheduleId().getValue() == rd2.getParentId().getValue()
        && ScheduleUtils.equalsToDate(rd.getStartDate().getValue(), rd2.getStartDate().getValue(), false)) {
        // [繰り返しスケジュール] 子の ID を検索
        // ダミースケジュールがある場合、削除されたスケジュールとし、コンテナに格納しない。
        return;
      }
    }

    // 1日の時間外スケジュールの上限を超えないかどうかチェックする
    if (canAddTermResultData(outResultConList, rd, MAX_OUT_SCHEDULE_COUNT)) {
      // 時間外スケジュールをコンテナに格納
      ScheduleUtils.addTermSchedule(outResultConList, viewStart.getValue(), count, rd);
    } else {
      // 1日の時間外スケジュールの上限を超えた場合はコンテナに格納しない。
      // 表示できないスケジュールがあるかどうかのフラグを立てる。
      // このフラグによって画面でアイコンを表示するかどうかを制御する。
      cannotOutDisplayFlagList.set(count, true);
    }
  }

  /**
   * 期間(時間外)スケジュールが追加可能かチェックする。
   * 
   * @param list
   *          週間のスケジュールリスト（期間or時間外）
   * @param rd
   *          スケジュールデータ
   * @param maxCount
   *          最大件数
   * @return 追加可能な場合true。そうでない場合false。
   */
  private boolean canAddTermResultData(List<ScheduleTermWeekContainer> list, ScheduleResultData rd, int maxCount) {
    boolean canAdd = false;

    // 該当週の期間(時間外)スケジュール件数がまだ最大に達していない場合はtrue。
    // 最大件数に達している場合は、既にある期間(時間外)スケジュール内に追加できるかどうかをチェックする。
    if (maxCount > list.size()) {
      canAdd = true;
    } else {
      for (ScheduleTermWeekContainer termWeek : list) {
        if (termWeek.canAddTerm(rd)) {
          canAdd = true;
          break;
        }
      }
    }
    return canAdd;
  }

  /**
   * 時間外スケジュールコンテナを取得します。
   * 
   * @return outResultConList 時間外コンテナリスト
   */
  public List<ScheduleTermWeekContainer> getOutResultDataList() {
    // 繰り返しや日またぎのデータが混在するため、ソートし直す。
    sortOutResultData(outResultConList);
    return outResultConList;
  }

  /**
   * 日ごとの表示できない期間スケジュールがあるかどうかのフラグリストを取得します。<br>
   * 週のリスト内に日のリストが存在します。
   * 
   * @return 期間非表示フラグリスト
   */
  public List<Boolean> getCannotTermDisplayFlagList() {
    return cannotTermDisplayFlagList;
  }

  /**
   * 日ごとの表示できない時間外スケジュールがあるかどうかのフラグリストを取得します。<br>
   * 週のリスト内に日のリストが存在します。
   * 
   * @return 時間外非表示フラグリスト
   */
  public List<Boolean> getCannotOutDisplayFlagList() {
    return cannotOutDisplayFlagList;
  }

  /**
   * 印刷日時を取得します。
   * 
   * @return 印刷日時
   */
  public ALDateTimeField getPrintDate() {
    return printDate;
  }

  /**
   * 表示開始時刻を取得します。
   * 
   * @return 表示開始時刻
   */
  public int getStartViewHour() {
    return DEFAULT_START_VIEW_HOUR;
  }

  /**
   * 表示終了時刻を取得します。
   * 
   * @return 表示終了時刻
   */
  public int getEndViewHour() {
    return DEFAULT_END_VIEW_HOUR;
  }

  /**
   * １時間の高さを取得します。
   * 
   * @return 1時間あたりの縦幅(px単位）
   */
  public int getOneHourHeight() {
    return ONE_HOUR_HEIGHT;
  }

  /**
   * １日の横幅を取得します。
   * 
   * @return 1日あたりの横幅(px単位）
   */
  public int getOneDayWidth() {
    return ONE_DAY_WIDTH;
  }

  /**
   * 期間スケジュール最大表示可能件数を取得します。
   * 
   * @return 期間スケジュール最大表示可能件数
   */
  public int getMaxTermScheduleCount() {
    return MAX_TERM_SCHEDULE_COUNT;
  }

  /**
   * 時間外スケジュール最大表示可能件数を取得します。
   * 
   * @return 時間外スケジュール最大表示可能件数
   */
  public int getMaxOutScheduleCount() {
    return MAX_OUT_SCHEDULE_COUNT;
  }

  /**
   * 印刷対象ユーザ ID を取得する．
   * 
   * @return 印刷対象ユーザーのユーザーID
   */
  public String getTargetUserId() {
    return targetUserId;
  }

  /**
   * 印刷画面に表示するユーザ の表示名
   * 
   * @return ユーザー表示名
   */
  public String getTargetUserAliasName() {
    return targetUserAliasName;
  }

  /**
   * 予定の縦幅 を取得する． 時間外にまたぐ場合、時間を調整する。
   * 
   * @param rd
   *          予定データ
   * 
   * @return 縦幅
   */
  public String getScheduleHeight(ScheduleResultData rd) {
    // Utilに定義した週、日共通メソッドから取得
    return SchedulePrintUtils.getScheduleHeight(rd, DEFAULT_START_VIEW_HOUR, DEFAULT_END_VIEW_HOUR, ONE_HOUR_HEIGHT, MIN_SCHEDULE_MINUTES);
  }

  /**
   * 予定の表示開始時刻からの高さ を取得する．
   * 
   * @param rd
   *          予定データ
   * 
   * @return 高さ
   */
  public String getScheduleTop(ScheduleResultData rd) {
    // Utilに定義した週、日共通メソッドから取得
    return SchedulePrintUtils.getScheduleTop(rd, DEFAULT_START_VIEW_HOUR, DEFAULT_END_VIEW_HOUR, ONE_HOUR_HEIGHT, MIN_SCHEDULE_MINUTES);
  }

  /**
   * 予定の左端からの描画開始位置 を取得する．
   * 
   * @param rd
   *          予定データ
   * 
   * @return 開始位置
   */
  public String getScheduleLeft(ScheduleResultData rd) {
    // Utilに定義した週、日共通メソッドから取得
    return SchedulePrintUtils.getScheduleLeft(rd, ONE_DAY_WIDTH);
  }

  /**
   * 予定の描画幅 を取得する．
   * 
   * @param rd
   *          予定データ
   * 
   * @return 横幅
   */
  public String getScheduleWidth(ScheduleResultData rd) {
    // Utilに定義した週、日共通メソッドから取得
    return SchedulePrintUtils.getScheduleWidth(rd, ONE_DAY_WIDTH);
  }

  /**
   * スケジュールをコンテナに格納します。
   * com.aimluck.eip.schedule.ScheduleWeekContainer.addResultData
   * (ScheduleResultData)より作成
   * 
   * @param rd
   */
  public boolean createResultData(ScheduleResultData rd) {
    int size = weekCon.getDayList().size();

    for (int i = 0; i < size; i++) {
      ScheduleDayContainer con = weekCon.getDayList().get(i);
      ALDateTimeField field = con.getDate();
      if (rd.getPattern().equals("Z")) {
        // 日またぎ予定を対象日の予定に変換する。
        ScheduleResultData rd2 = getDay2DayCrossoverSchedule(rd, con.getDate().getValue(), rd.getStartDate().getValue(), rd.getEndDate().getValue());
        // 開始日～終了日以外の場合はnullが返ってくるため、対象外とする。
        if (rd2 != null) {
          addResultData(con, rd2);
        }

      } else /* add end */if (!rd.getPattern().equals("N")) {
        // 繰り返しスケジュール
        if (ScheduleUtils.isView(con.getDate(), rd.getPattern(), rd.getStartDate().getValue(), rd.getEndDate().getValue())) {
          Calendar temp = Calendar.getInstance();
          temp.setTime(field.getValue());
          temp.set(Calendar.HOUR_OF_DAY, Integer.parseInt(rd.getStartDate().getHour()));
          temp.set(Calendar.MINUTE, Integer.parseInt(rd.getStartDate().getMinute()));
          temp.set(Calendar.SECOND, 0);
          temp.set(Calendar.MILLISECOND, 0);
          Calendar temp2 = Calendar.getInstance();
          temp2.setTime(field.getValue());
          temp2.set(Calendar.HOUR_OF_DAY, Integer.parseInt(rd.getEndDate().getHour()));
          temp2.set(Calendar.MINUTE, Integer.parseInt(rd.getEndDate().getMinute()));
          temp2.set(Calendar.SECOND, 0);
          temp2.set(Calendar.MILLISECOND, 0);

          // 新たにスケジュールを作成して格納する。
          addResultData(con, SchedulePrintUtils.resultData2resultData(rd, temp, temp2, true));

        }
      } else if (field.getYear().equals(rd.getStartDate().getYear())
        && field.getMonth().equals(rd.getStartDate().getMonth())
        && field.getDay().equals(rd.getStartDate().getDay())) {
        addResultData(con, rd);
        return true;
      }
    }
    return false;
  }

  /**
   * 重複予定のリスト を作成する．
   * 
   * @param rd
   *          予定データ
   * @param dayIndex
   *          日のindex
   */
  public void createDuplicateList(ScheduleResultData rd, int dayIndex) {

    List<List<ScheduleResultData>> dayList = duplicateList.get(dayIndex);

    // リストを順に検索し、重複していたらリストに追加する。
    for (int listIndex = 0; listIndex < dayList.size(); listIndex++) {

      // リスト内の列リストからスケジュールリストを取得
      List<ScheduleResultData> columnList = dayList.get(listIndex);

      // 列リストが空の場合は、スケジュールを追加して終了。
      if (columnList.size() == 0) {
        rd.setSortNo(listIndex);
        rd.setColumnCount(listIndex + 1);
        columnList.add(rd);
        return;
      }

      // 列リストの最後のスケジュールを取得
      ScheduleResultData rd2 = columnList.get(columnList.size() - 1);

      Date startDate1 = rd.getStartDate().getValue();
      Date startDate2 = rd2.getStartDate().getValue();
      Date endDate1 = rd.getEndDate().getValue();
      Date endDate2 = rd2.getEndDate().getValue();
      // 重複しているかどうか判定
      if (SchedulePrintUtils.isDuplicateSchedule(startDate1, endDate1, startDate2, endDate2, DEFAULT_END_VIEW_HOUR, MIN_SCHEDULE_MINUTES)) {
        // 重複している場合は、次の列リストへ

        // 列リストがない場合は、追加して終了。
        if (listIndex == dayList.size() - 1) {
          dayList.add(new ArrayList<ScheduleResultData>());
          rd.setSortNo(listIndex + 1);
          rd.setColumnCount(listIndex + 2);
          dayList.get(listIndex + 1).add(rd);
          return;
        }
      } else {
        // 重複していない場合は、リストの最後に追加して終わり。
        rd.setSortNo(listIndex);
        rd.setColumnCount(listIndex + 1);
        columnList.add(rd);
        return;
      }
    }

  }

  /**
   * 日跨ぎスケジュールをコンテナに格納する。
   * 
   * @param con
   *          スケジュール 日コンテナ
   * @param rd
   *          予定データ
   * @param targetDate
   *          処理対象日
   * @param startDate
   *          開始日
   * @param endDate
   *          終了日
   * @author motegi-takeshi
   */
  private ScheduleResultData getDay2DayCrossoverSchedule(ScheduleResultData rd, Date targetDate, Date startDate, Date endDate) {

    // 処理対象日の00：00（比較用の処理対象日）
    Calendar cal1 = Calendar.getInstance();
    cal1.setTime(targetDate);
    cal1.set(Calendar.HOUR_OF_DAY, 0);
    cal1.set(Calendar.MINUTE, 0);
    cal1.set(Calendar.SECOND, 0);
    cal1.set(Calendar.MILLISECOND, 0);
    Date borderStartDate = cal1.getTime();

    // 処理対象日の23：59
    Calendar cal2 = Calendar.getInstance();
    cal2.setTime(targetDate);
    cal2.set(Calendar.HOUR_OF_DAY, 23);
    cal2.set(Calendar.MINUTE, 59);
    cal2.set(Calendar.SECOND, 0);
    cal2.set(Calendar.MILLISECOND, 0);
    Date borderEndDate = cal2.getTime();

    // 開始日
    Calendar calStart = Calendar.getInstance();
    calStart.setTime(startDate);
    calStart.set(Calendar.HOUR_OF_DAY, 0);
    calStart.set(Calendar.MINUTE, 0);
    calStart.set(Calendar.SECOND, 0);
    calStart.set(Calendar.MILLISECOND, 0);
    Date tmpStartDate = calStart.getTime();

    // 終了日
    Calendar calEnd = Calendar.getInstance();
    calEnd.setTime(endDate);
    calEnd.set(Calendar.HOUR_OF_DAY, 0);
    calEnd.set(Calendar.MINUTE, 0);
    calEnd.set(Calendar.SECOND, 0);
    calEnd.set(Calendar.MILLISECOND, 0);
    Date tmpEndDate = calEnd.getTime();

    Calendar temp = Calendar.getInstance();
    Calendar temp2 = Calendar.getInstance();
    if (borderStartDate.compareTo(tmpStartDate) == 0) {
      // 対象日が日またぎの開始日
      // 開始日はそのまま、終了日は23：59
      temp.setTime(startDate);
      temp2.setTime(borderEndDate);
    } else if (tmpStartDate.before(borderStartDate) && borderStartDate.before(tmpEndDate)) {
      // 開始日<処理対象日 かつ 処理対象日<終了日
      // 開始日は00：00、終了日は23：59
      temp.setTime(borderStartDate);
      temp2.setTime(borderEndDate);
    } else if (borderStartDate.compareTo(tmpEndDate) == 0) {
      // 対象日が日またぎの終了日
      // 開始日は00：00、終了日はそのまま
      temp.setTime(borderStartDate);
      temp2.setTime(endDate);
    } else {
      // いずれの条件にも合致しなければ処理対象外
      return null;
    }

    // 新たにスケジュールを作成して返す
    return SchedulePrintUtils.resultData2resultData(rd, temp, temp2, false);
  }

  /**
   * 引数のスケジュール一覧を、日毎にソートし直す。
   * 
   * @param list
   *          スケジュール一覧
   */
  public void sortOutResultData(List<ScheduleTermWeekContainer> list) {
    // 1週間繰り返す
    for (int dayIndex = 0; dayIndex < 7; dayIndex++) {

      List<ScheduleTermDayContainer> tempList = new ArrayList<ScheduleTermDayContainer>();
      List<ScheduleResultData> sortList = new ArrayList<ScheduleResultData>();
      // 時間外スケジュールの行数分繰り返す
      for (int count = 0; count < list.size(); count++) {
        // スケジュールデータを取得し、リストに格納
        ScheduleTermWeekContainer weekTermCon = list.get(count);
        ScheduleTermDayContainer dayCon = weekTermCon.getDayList().get(dayIndex);
        // 一時リストに格納
        tempList.add(dayCon);
        sortList.add(dayCon.getTermResultData());
      }

      // 開始日時の昇順、終了日時の昇順、スケジュールIDの昇順でソートする。
      sortByTimeForResultDataAsc(sortList);

      for (int i = 0; i < sortList.size(); i++) {
        if (sortList.get(i) == null) {
          // nullは無視
          break;
        }
        for (int j = 0; j < tempList.size(); j++) {
          // ソート済みリストと同じindexの場所に格納する
          if (sortList.get(i).equals(tempList.get(j).getTermResultData())) {
            // スケジュールデータを取得し、リストに格納
            list.get(i).getDayList().set(dayIndex, tempList.get(j));
            break;
          }
        }
      }
    }
  }

  /**
   * 引数のスケジュール一覧を開始時刻の昇順＞終了時刻の昇順＞スケジュールIDの昇順でソートする。
   * 
   * @param list
   *          スケジュール一覧
   */
  public void sortByTimeForResultDataAsc(List<ScheduleResultData> list) {
    Collections.sort(list, new Comparator<ScheduleResultData>() {
      private final Calendar cal = Calendar.getInstance();

      @Override
      public int compare(ScheduleResultData o1, ScheduleResultData o2) {
        // null同士なら0を返して終了
        if (o1 == null && o2 == null) {
          return 0;
        }
        // o1がnullならo2を先にする
        if (o1 == null) {
          return -1;
        }
        // o2がnullならo1を先にする
        if (o2 == null) {
          return 1;
        }

        // 開始時刻を取得
        cal.setTime(o1.getStartDate().getValue());
        int hour1 = cal.get(Calendar.HOUR_OF_DAY);
        int minute1 = cal.get(Calendar.MINUTE);

        cal.setTime(o2.getStartDate().getValue());
        int hour2 = cal.get(Calendar.HOUR_OF_DAY);
        int minute2 = cal.get(Calendar.MINUTE);

        // 開始時刻で比較
        if (hour1 != hour2) {
          return hour1 - hour2;
        } else if (minute1 != minute2) {
          return minute1 - minute2;
        } else {
          // 終了時刻を取得
          cal.setTime(o1.getEndDate().getValue());
          hour1 = cal.get(Calendar.HOUR_OF_DAY);
          minute1 = cal.get(Calendar.MINUTE);

          cal.setTime(o2.getEndDate().getValue());
          hour2 = cal.get(Calendar.HOUR_OF_DAY);
          minute2 = cal.get(Calendar.MINUTE);

          // 終了時刻で比較
          if (hour1 != hour2) {
            return hour1 - hour2;
          } else if (minute1 != minute2) {
            return minute1 - minute2;
          } else {
            // スケジュールIDで比較
            long id1 = o1.getScheduleId().getValue();
            long id2 = o2.getScheduleId().getValue();
            if (id1 != id2) {
              return (int) (id1 - id2);
            }
          }
        }
        return 0;
      }
    });
  }

  /**
   * ブラウザがIE9かどうかを取得します。
   * 
   * @return true：IE9、false：IE9以外
   */
  public boolean isIE9() {
    return isIe9;
  }
  // add end
}
