/*
 * Aipo is a groupware program developed by Aimluck,Inc.
 * Copyright (C) 2004-2012 Aimluck,Inc.
 * http://www.aipo.com
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero 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 Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package com.aimluck.eip.schedule.util;

import java.util.Calendar;
import java.util.Date;
import java.util.List;

import com.aimluck.eip.schedule.ScheduleDayContainer;
import com.aimluck.eip.schedule.ScheduleResultData;

/**
 * スケジュール印刷（週、日）用ユーティリティ
 */
public class SchedulePrintUtils {

  /**
   * 時間外スケジュールかどうかを判定する。
   * 
   * @param rd
   *          予定データ
   * @param defaultStartViewHour
   *          表示開始時刻
   * @param defaultEndViewHour
   *          表示終了時刻
   * @return 時間外：true、時間内：false
   */
  public static boolean isOutSchedule(ScheduleResultData rd, int defaultStartViewHour, int defaultEndViewHour) {
    Date startDate = rd.getStartDate().getValue();
    Date endDate = rd.getEndDate().getValue();

    // 処理対象日の00：00（比較用の処理対象日）
    Calendar cal1 = Calendar.getInstance();
    cal1.setTime(startDate);
    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(startDate);
    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, defaultStartViewHour);
    calStart.set(Calendar.MINUTE, 0);
    calStart.set(Calendar.SECOND, 0);
    calStart.set(Calendar.MILLISECOND, 0);
    Date viewStartTime = calStart.getTime();

    // 表示終了時間
    Calendar calEnd = Calendar.getInstance();
    calEnd.setTime(startDate);
    calEnd.set(Calendar.HOUR_OF_DAY, defaultEndViewHour);
    calEnd.set(Calendar.MINUTE, 0);
    calEnd.set(Calendar.SECOND, 0);
    calEnd.set(Calendar.MILLISECOND, 0);
    Date viewEndTime = calEnd.getTime();

    // 予定が表示開始時刻と同一の場合は、時間内コンテナとする。
    if (startDate.compareTo(viewStartTime) == 0 && endDate.compareTo(viewStartTime) == 0) {
      return false;
    }

    // 予定が0:00と表示開始時刻の間にあるかどうか判定
    if ((startDate.compareTo(borderStartDate) >= 0 && startDate.compareTo(viewStartTime) <= 0)
      && (endDate.compareTo(borderStartDate) >= 0 && endDate.compareTo(viewStartTime) <= 0)) {
      return true;
    }

    // 予定が表示終了時刻と23:59の間にあるかどうか判定
    if ((startDate.compareTo(viewEndTime) >= 0 && startDate.compareTo(borderEndDate) <= 0)
      && (endDate.compareTo(viewEndTime) >= 0 && endDate.compareTo(borderEndDate) <= 0)) {
      return true;
    }

    // いずれの条件にも合致しなければ処理対象外（時間内コンテナのまま）
    return false;

  }

  /**
   * 予定が重複しているかどうかを判定して返す。
   * 
   * @param startDate1
   *          比較元予定データの開始時間
   * @param endDate1
   *          比較元予定データの終了時間
   * @param startDate2
   *          比較先予定データの開始時間
   * @param endDate2
   *          比較先予定データの終了時間
   * @param minMinutes
   *          最低表示時間（分）
   * 
   * @return true:重複している false:重複していない
   */
  public static boolean isDuplicateSchedule(Date startDate1, Date endDate1, Date startDate2, Date endDate2, int defaultEndViewHour, int minMinutes) {
    // 最小表示時間未満の場合は、開始時刻から最小表示時間足した時間を終了時間とする。
    Date tempEndDate1 = getDisplayEndDate(startDate1, endDate1, minMinutes);
    Date tempEndDate2 = getDisplayEndDate(startDate2, endDate2, minMinutes);

    // 表示終了時刻まで30分（15分）未満の予定の開始時刻を取得する．
    Date tempStartDate1 = getDisplayStartDate(startDate1, defaultEndViewHour, minMinutes);
    Date tempStartDate2 = getDisplayStartDate(startDate2, defaultEndViewHour, minMinutes);

    // <通常予定>の開始時間 ＜ <検索対象通常予定>の開始時間 ＜ <通常予定>の終了時間
    if (tempStartDate1.before(tempStartDate2) && tempStartDate2.before(tempEndDate1)) {
      return true;
    }
    // <検索対象通常予定>の開始時間 ＜ <通常予定>の開始時間 ＜ <検索対象通常予定>の終了時間
    if (tempStartDate2.before(tempStartDate1) && tempStartDate1.before(tempEndDate2)) {
      return true;
    }
    // <通常予定>の開始時間 ＜ <検索対象通常予定>の終了時間 ＜ <通常予定>の終了時間
    if (tempStartDate1.before(tempEndDate2) && tempEndDate2.before(tempEndDate1)) {
      return true;
    }
    // <検索対象通常予定>の開始時間 ＜ <通常予定>の終了時間 ＜ <検索対象通常予定>の終了時間
    if (tempStartDate2.before(tempEndDate1) && tempEndDate1.before(tempEndDate2)) {
      return true;
    }
    // <検索対象通常予定>の開始時間 = <通常予定>の開始時間、かつ <通常予定>の終了時間 = <検索対象通常予定>の終了時間
    if (tempStartDate1.compareTo(tempStartDate2) == 0 && tempEndDate1.compareTo(tempEndDate2) == 0) {
      return true;
    }
    return false;
  }

  /**
   * 日またぎスケジュールを表示日当日のスケジュールに変換する。
   * 
   * @param rd
   *          予定データ
   * @param viewDate
   *          表示日
   */
  public static void setDayCrossoverDate2DayScheduleDate(ScheduleResultData rd, Date viewDate) {
    // 処理対象日の00：00（比較用の処理対象日）
    Calendar cal1 = Calendar.getInstance();
    cal1.setTime(viewDate);
    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(viewDate);
    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();

    // 開始日時と終了日時を変換する。
    if (rd.getStartDate().getValue().before(borderStartDate)) {
      // 対象日の0:00よりも日またぎの開始日が前
      // 開始日は00：00、終了日はそのまま
      rd.setStartDate(borderStartDate);
    }
    if (rd.getEndDate().getValue().after(borderEndDate)) {
      // 対象日の23:59よりも日またぎの終了日が後
      // 開始日はそのまま、終了日は23：59
      rd.setEndDate(borderEndDate);
    }
  }

  /**
   * 繰り返しスケジュールが表示対象外かどうかを判定します。
   * 
   * @param con
   *          日コンテナ
   * @param rd
   *          予定データ
   * @return 表示対象：true、表示対象外：false
   */
  public static boolean isDeletedRepeatSchedule(ScheduleDayContainer con, ScheduleResultData rd) {
    List<ScheduleResultData> scheduleList = con.getScheduleListDesc();
    int size = scheduleList.size();
    boolean repeat_del = false;
    for (int i = 0; i < size; i++) {
      repeat_del = false;
      ScheduleResultData rd2 = scheduleList.get(i);
      if (rd.isRepeat()
        && rd2.isDummy()
        && rd.getScheduleId().getValue() == rd2.getParentId().getValue()
        && ScheduleUtils.equalsToDate(rd.getStartDate().getValue(), rd2.getStartDate().getValue(), false)) {
        // [繰り返しスケジュール] 親の ID を検索

        // 対象外
        repeat_del = true;
        break;
      }
      if (rd2.isRepeat()
        && rd.isDummy()
        && rd2.getScheduleId().getValue() == rd.getParentId().getValue()
        && ScheduleUtils.equalsToDate(rd.getStartDate().getValue(), rd2.getStartDate().getValue(), false)) {
        // [繰り返しスケジュール] 親の ID を検索

        // 削除済み
        repeat_del = true;
      }
    }
    return repeat_del;
  }

  /**
   * 表示用予定データへデータを乗せかえる
   * 
   * @param rd
   *          元データ
   * @param temp
   *          開始日時
   * @param temp2
   *          終了日時
   * @param isRepeat
   *          繰り返しフラグ
   * @return 表示用予定データ
   * 
   * @author motegi-takeshi
   */
  public static ScheduleResultData resultData2resultData(ScheduleResultData rd, Calendar temp, Calendar temp2, boolean isRepeat) {
    ScheduleResultData rd3 = new ScheduleResultData();
    rd3.initField();
    rd3.setScheduleId((int) rd.getScheduleId().getValue());
    rd3.setParentId((int) rd.getParentId().getValue());
    rd3.setName(rd.getName().getValue());
    // 開始日を設定し直す
    rd3.setStartDate(temp.getTime());
    // 終了日を設定し直す
    rd3.setEndDate(temp2.getTime());
    rd3.setTmpreserve(rd.isTmpreserve());
    rd3.setPublic(rd.isPublic());
    rd3.setHidden(rd.isHidden());
    rd3.setDummy(rd.isDummy());
    rd3.setLoginuser(rd.isLoginuser());
    rd3.setOwner(rd.isOwner());
    rd3.setMember(rd.isMember());
    rd3.setType(rd.getType());
    // // 繰り返しフラグ
    rd3.setRepeat(isRepeat);
    // 場所
    rd3.setPlace(rd.getPlace().getValue());
    // add start
    // 状態
    rd3.setStatus(rd.getStatus());
    // 重要フラグ
    rd3.setPriority(rd.getPriority());
    // 必須／任意フラグ
    rd3.setRequired(rd.getRequired());
    // 繰り返しフラグ(携帯表示判定用)
    rd3.setPattern(rd.getPattern());
    // add end
    // add start 要件No.9 スケジュール表示 （仮の予定、確定した予定）
    rd3.setTemporaryFlag(rd.getTemporaryFlag());
    // add end

    return rd3;
  }

  /**
   * 予定の縦幅 を取得する． 時間外にまたぐ場合、時間を調整する。
   * 
   * @param rd
   *          予定データ
   * @param startViewHour
   *          表示開始時刻
   * @param endViewHour
   *          表示終了時刻
   * @param oneHourHeight
   *          1時間の縦幅
   * @param minMinutes
   *          最低表示時間（分）
   * 
   * @return 縦幅
   */
  public static String getScheduleHeight(ScheduleResultData rd, int startViewHour, int endViewHour, int oneHourHeight, int minMinutes) {
    Date startDate = null;
    Date endDate = null;

    // 表示開始時刻のDateを取得
    Calendar calStart = Calendar.getInstance();
    calStart.setTime(rd.getStartDate().getValue());
    calStart.set(Calendar.HOUR_OF_DAY, startViewHour);
    calStart.set(Calendar.MINUTE, 0);
    calStart.set(Calendar.SECOND, 0);
    calStart.set(Calendar.MILLISECOND, 0);
    Date viewStartTime = calStart.getTime();

    // 表示終了時刻のDateを取得
    Calendar calEnd = Calendar.getInstance();
    calEnd.setTime(rd.getEndDate().getValue());
    calEnd.set(Calendar.HOUR_OF_DAY, endViewHour);
    calEnd.set(Calendar.MINUTE, 0);
    calEnd.set(Calendar.SECOND, 0);
    calEnd.set(Calendar.MILLISECOND, 0);
    Date viewEndTime = calEnd.getTime();

    // 予定の開始時間が表示開始時間より早い場合、表示開始時間を開始時間とする
    if (rd.getStartDate().getValue().before(viewStartTime)) {
      startDate = viewStartTime;
    } else {
      startDate = rd.getStartDate().getValue();
    }
    // 予定の終了時間が表示終了時間より遅い場合、表示終了時間を終了時間とする
    if (rd.getEndDate().getValue().after(viewEndTime)) {
      endDate = viewEndTime;
    } else {
      endDate = rd.getEndDate().getValue();
    }
    // 分単位で差を算出
    int diff = ScheduleUtils.differenceMinutes(startDate, endDate);
    // 最低表示時間以内の場合は最低表示時間（週：30分、日：15分）を最小縦幅とする。
    // 開始時間から表示終了時刻までが最低表示時間に満たない場合も、
    // 開始時間を前倒しするため、最小縦幅のままとする。
    if (diff < minMinutes) {
      diff = minMinutes;
    }
    // 1時間の縦幅を60分で割って実際の縦幅との拡大率を算出する
    float rate = (float) oneHourHeight / 60;
    float actual = diff * rate;
    return String.valueOf(actual);
  }

  /**
   * 予定の表示開始時刻からの高さ を取得する．
   * 
   * @param rd
   *          予定データ
   * @param startViewHour
   *          表示開始時刻
   * @param oneHourHeight
   *          1時間の高さ
   * 
   * @return 高さ
   */
  public static String getScheduleTop(ScheduleResultData rd, int startViewHour, int endViewHour, int oneHourHeight, int minMinutes) {
    int startHour = Integer.parseInt(rd.getStartDate().getHour());
    int startMin = Integer.parseInt(rd.getStartDate().getMinute());
    // 予定の開始時間が表示開始時刻より早い場合、表示開始時刻を開始時間とする
    if (startHour < startViewHour) {
      startHour = startViewHour;
      startMin = 0;
    }
    // 予定の開始時間＋最低表示縦幅が表示終了時刻を超える場合は、表示終了時刻－最低表示縦幅を開始時間とする。
    // 表示終了時刻のDateを取得
    Calendar calEnd = Calendar.getInstance();
    calEnd.setTime(rd.getStartDate().getValue());
    calEnd.set(Calendar.HOUR_OF_DAY, endViewHour);
    calEnd.set(Calendar.MINUTE, 0);
    calEnd.set(Calendar.SECOND, 0);
    calEnd.set(Calendar.MILLISECOND, 0);
    Date viewEndTime = calEnd.getTime();
    // 開始時刻と表示終了時刻の差が15(30)分未満の場合
    if (ScheduleUtils.differenceMinutes(rd.getStartDate().getValue(), viewEndTime) < minMinutes) {
      startHour = endViewHour - 1;
      startMin = 60 - minMinutes;
    }

    // 1時間の縦幅を60分で割って実際の縦幅との拡大率を算出する
    float rate = (float) oneHourHeight / 60;
    float top = ((startHour - startViewHour) * 60 + startMin) * rate;
    return String.valueOf(top);
  }

  /**
   * 予定の描画幅 を取得する．
   * 
   * @param rd
   *          予定データ
   * @param oneDayWidth
   *          1日の横幅
   * @return 横幅
   */
  public static String getScheduleWidth(ScheduleResultData rd, int oneDayWidth) {
    // 予定グループの表示列数を取得する
    float columnCount = rd.getColumnCount();

    // 1日分の横幅 ／ カラム数 × 表示列数
    float width = (oneDayWidth / columnCount);
    return String.valueOf(width);
  }

  /**
   * 予定の左端からの描画開始位置 を取得する．
   * 
   * @param rd
   *          予定データ
   * @param oneDayWidth
   *          1日の横幅
   * 
   * @return 開始位置
   */
  public static String getScheduleLeft(ScheduleResultData rd, int oneDayWidth) {
    // 予定グループの表示列数を取得する。
    float columnCount = rd.getColumnCount();

    // 1日の横幅を重複スケジュールの件数で割り、スケジュールの開始位置を算出する
    float left = rd.getSortNo() * (oneDayWidth / columnCount);
    return String.valueOf(left);
  }

  /**
   * 予定の表示列数を算出し、個々のスケジュールデータに格納する。<br />
   * 
   * SchedulePrintOnedaySelectData#createDuplicateList、及び、<br />
   * SchedulePrintWeeklySelectData#createDuplicateListで作成した<br />
   * 予定の重複リスト（列ごとに1つのリストとなっている。）を降順にサーチする。 <br />
   * 比較元の予定よりも前のリスト内の予定を比較し、 <br />
   * 重複していたら比較元の予定が持っている列数を比較先の予定の列数としてセットする。<br />
   * 
   * サーチが完了したら、次は重複リストを昇順にサーチする。 <br />
   * 比較元の予定と次のリスト内の予定を比較し、<br />
   * 重複していたら比較元の予定が持っている列数を比較先の予定の列数としてセットする。<br />
   * 次のように重複する予定を想定。<br />
   * 
   * ┌――┬―――――┐ ┌――┬――┬――┐<br />
   * │　　│　　　　　│ │　　│　　│　　│<br />
   * │　　├――┬――┤ │　　├――┴――┤<br />
   * │　　│　　│　　│ │　　│　　　　　│<br />
   * └――┴――┴――┘ └――┴―――――┘<br />
   * 
   * @param duplicateList
   *          予定重複リスト
   * @param defaultEndViewHour
   *          表示終了時刻の時間
   * @param minMinutes
   *          最低表示時間（分）
   */
  public static void setScheduleColumnCount(List<List<ScheduleResultData>> duplicateList, int defaultEndViewHour, int minMinutes) {
    int listSize = duplicateList.size();

    // スケジュールリストを降順にサーチ（0番目のリストは対象外）
    for (int listIndex = listSize - 1; listIndex > 0; listIndex--) {

      // 比較元のスケジュールリスト
      List<ScheduleResultData> srcList = duplicateList.get(listIndex);

      for (ScheduleResultData rd : srcList) {
        // 比較対象のスケジュールリスト（自身より前のスケジュールリスト）
        for (int listTargetIndex = listIndex - 1; listTargetIndex >= 0; listTargetIndex--) {
          List<ScheduleResultData> targetList = duplicateList.get(listTargetIndex);

          // 列リストからスケジュールリストを取得
          for (ScheduleResultData rd2 : targetList) {
            Date startDate1 = rd.getStartDate().getValue();
            Date startDate2 = rd2.getStartDate().getValue();
            Date endDate1 = rd.getEndDate().getValue();
            Date endDate2 = rd2.getEndDate().getValue();

            // 重複しているかどうか判定
            if (isDuplicateSchedule(startDate1, endDate1, startDate2, endDate2, defaultEndViewHour, minMinutes)) {
              // 重複しているスケジュールに対して、自身の列数を上書き
              if (rd2.getColumnCount() < rd.getColumnCount()) {
                rd2.setColumnCount(rd.getColumnCount());
              }
            }
          }
        }
      }
    }

    // スケジュールリストを昇順にサーチ（最後のリストは対象外）
    for (int listIndex = 0; listIndex < listSize - 1; listIndex++) {
      // 比較元のスケジュールリスト
      List<ScheduleResultData> srcList = duplicateList.get(listIndex);
      // 比較対象のスケジュールリスト（自身の1つ後のスケジュールリスト）
      List<ScheduleResultData> targetList = duplicateList.get(listIndex + 1);

      for (ScheduleResultData rd : srcList) {
        // 列リストからスケジュールリストを取得
        for (ScheduleResultData rd2 : targetList) {
          Date startDate1 = rd.getStartDate().getValue();
          Date startDate2 = rd2.getStartDate().getValue();
          Date endDate1 = rd.getEndDate().getValue();
          Date endDate2 = rd2.getEndDate().getValue();

          // 表示終了時刻まで30分（15分）未満の予定の開始時刻を取得する．
          Date tempStartDate1 = getDisplayStartDate(rd.getStartDate().getValue(), defaultEndViewHour, minMinutes);
          Date tempStartDate2 = getDisplayStartDate(rd2.getStartDate().getValue(), defaultEndViewHour, minMinutes);

          // 比較対象の開始日時が比較元の開始日時以前の場合、対象外とする。
          if (tempStartDate1.compareTo(tempStartDate2) > 0) {
            continue;
          }

          // 重複しているかどうか判定
          if (isDuplicateSchedule(startDate1, endDate1, startDate2, endDate2, defaultEndViewHour, minMinutes)) {
            // 重複しているスケジュールに対して、自身の列数を上書き
            rd2.setColumnCount(rd.getColumnCount());
          }
        }
      }
    }
  }

  /**
   * 表示終了時刻まで30分（15分）未満の予定の開始時刻を取得する．
   * 
   * @param startDate
   *          予定データの開始時間
   * @param defaultEndViewHour
   *          表示終了時刻の時間
   * @param minMinutes
   *          最低表示時間（分）
   * 
   * @return 終了時刻
   */
  public static Date getDisplayStartDate(Date startDate, int defaultEndViewHour, int minMinutes) {
    // 表示終了時間
    Calendar calEnd = Calendar.getInstance();
    calEnd.setTime(startDate);
    calEnd.set(Calendar.HOUR_OF_DAY, defaultEndViewHour);
    calEnd.set(Calendar.MINUTE, 0);
    calEnd.set(Calendar.SECOND, 0);
    calEnd.set(Calendar.MILLISECOND, 0);
    Date viewEndTime = calEnd.getTime();

    // 15(30)分未満の場合は開始時刻＋15(30)分を終了時刻とする。
    if (ScheduleUtils.differenceMinutes(startDate, viewEndTime) < minMinutes) {
      Calendar cal = Calendar.getInstance();
      cal.setTime(viewEndTime);
      cal.add(Calendar.MINUTE, -minMinutes);
      return cal.getTime();
    }
    return startDate;
  }

  /**
   * 30分（15分）未満の予定の終了時刻を取得する．
   * 
   * @param startDate
   *          予定データの開始時間
   * @param endDate
   *          予定データの終了時間
   * @param minMinutes
   *          最低表示時間（分）
   * 
   * @return 終了時刻
   */
  public static Date getDisplayEndDate(Date startDate, Date endDate, int minMinutes) {
    // 15(30)分未満の場合は開始時刻＋15(30)分を終了時刻とする。
    if (ScheduleUtils.differenceMinutes(startDate, endDate) < minMinutes) {
      Calendar cal = Calendar.getInstance();
      cal.setTime(startDate);
      cal.add(Calendar.MINUTE, minMinutes);
      return cal.getTime();
    }
    return endDate;
  }

}
