/*
 * Aipo is a groupware program developed by Aimluck,Inc.
 * http://aipo.com/
 *
 * Copyright(C) 2012 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 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;

import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import net.sf.json.JSONObject;

import org.apache.jetspeed.services.logging.JetspeedLogFactoryService;
import org.apache.jetspeed.services.logging.JetspeedLogger;
import org.apache.turbine.services.TurbineServices;
import org.apache.turbine.util.RunData;
import org.apache.velocity.context.Context;

import com.aimluck.commons.field.ALDateTimeField;
import com.aimluck.eip.category.util.CommonCategoryUtils;
import com.aimluck.eip.cayenne.om.portlet.EipTCommonCategory;
import com.aimluck.eip.cayenne.om.portlet.EipTSchedule;
import com.aimluck.eip.cayenne.om.portlet.EipTScheduleMap;
import com.aimluck.eip.common.ALDBErrorException;
import com.aimluck.eip.common.ALEipConstants;
import com.aimluck.eip.common.ALEipHolidaysManager;
import com.aimluck.eip.common.ALEipUser;
import com.aimluck.eip.common.ALHoliday;
import com.aimluck.eip.common.ALPageNotFoundException;
import com.aimluck.eip.mail.util.ALEipUserAddr;
import com.aimluck.eip.mail.util.ALMailUtils;
import com.aimluck.eip.modules.actions.common.ALAction;
import com.aimluck.eip.orm.Database;
import com.aimluck.eip.schedule.beans.ScheduleBean;
import com.aimluck.eip.schedule.beans.ScheduleUserBean;
import com.aimluck.eip.schedule.util.ScheduleUtils;
import com.aimluck.eip.services.accessctl.ALAccessControlConstants;
import com.aimluck.eip.services.accessctl.ALAccessControlFactoryService;
import com.aimluck.eip.services.accessctl.ALAccessControlHandler;
import com.aimluck.eip.services.eventlog.ALEventlogConstants;
import com.aimluck.eip.services.eventlog.ALEventlogFactoryService;
import com.aimluck.eip.util.ALEipUtils;

/**
 * 1日スケジュールに表示する予定のフォームデータを管理するクラスです。
 */
public class ScheduleOneDayJSONFormData {

  /** ロガー */
  private static final JetspeedLogger logger = JetspeedLogFactoryService.getLogger(ScheduleOneDayJSONFormData.class.getName());

  /** 開始日時 */
  private ALDateTimeField startDate;

  /** 終了日時 */
  private ALDateTimeField endDate;

  /** 基準日 */
  private ALDateTimeField baseDate;

  /** リクエストパラメータ：開始日時 */
  private String start_date;

  /** リクエストパラメータ：終了日時 */
  private String end_date;

  /** リクエストパラメータ：基準日 */
  private String base_date;

  /** エンティティID（スケジュールID） */
  private int entityId;

  /** ログインユーザーID */
  private int userId;

  /** 更新対象予定 */
  private EipTSchedule targetSchedule;

  /** 更新対象予定に紐づく全スケジュールマップ */
  private List<EipTScheduleMap> targetScheduleMaps;

  /** 繰り返し予定更新フラグ（0：繰り返しでない予定/1：繰り返し予定） */
  private int edit_repeat_flag;

  /** 参加メンバー */
  private List<ALEipUser> memberList;

  /** 必須参加メンバー */
  private List<ALEipUser> reqMemberList;

  /** 任意参加メンバー */
  private List<ALEipUser> subMemberList;

  /** オーナー（主催者）ID */
  private int ownerId;

  /** エラーメッセージ一覧 */
  private ArrayList<String> msgList;

  /** ログインユーザー名 */
  private String loginUserName;

  /** 日またぎ更新モード */
  private String straddle_mode = null;

  /** 日またぎ更新：開始時刻 */
  private String straddle_start_time = null;

  /** 日またぎ更新：終了時刻 */
  private String straddle_end_time = null;

  /** 日またぎ更新モード：開始時刻変更 */
  private static final String STRADDLE_MODE_CHANGE_START_TIME = "changeStartTime";

  /** 日またぎ更新モード：終了時刻変更 */
  private static final String STRADDLE_MODE_CHANGE_END_TIME = "changeEndTime";

  /** スケジュールマップ登録用共有カテゴリ */
  private EipTCommonCategory commonCategory = null;

  /** ツールチップ表示判断値の1行文字数(ユーザー数, 1行の基準文字数) */
  private static final Map<Integer, Integer> rowLengthForHasToolTip = new HashMap<Integer, Integer>(0);
  static {
    rowLengthForHasToolTip.put(1, 70);
    rowLengthForHasToolTip.put(2, 30);
    rowLengthForHasToolTip.put(3, 20);
    rowLengthForHasToolTip.put(4, 15);
    rowLengthForHasToolTip.put(5, 12);
    rowLengthForHasToolTip.put(6, 10);
    rowLengthForHasToolTip.put(7, 8);
    rowLengthForHasToolTip.put(8, 7);
    rowLengthForHasToolTip.put(9, 6);
    rowLengthForHasToolTip.put(10, 5);
  }

  /**
   * 初期化処理
   * 
   * @param action
   *          呼び出し元アクション
   * @param rundata
   *          実行データ
   * @param context
   *          Velocityコンテキスト
   */
  public void init(ALAction action, RunData rundata, Context context) throws ALPageNotFoundException, ALDBErrorException {

    // ログインユーザー情報を取得
    ALEipUser loginuser = ALEipUtils.getALEipUser(rundata);
    loginUserName = loginuser.getName().getValue();

    // 各種フィールドの初期化
    start_date = null;
    end_date = null;
    base_date = null;
    entityId = 0;
    userId = 0;
    memberList = new ArrayList<ALEipUser>(0);
    reqMemberList = new ArrayList<ALEipUser>(0);
    subMemberList = new ArrayList<ALEipUser>(0);
    targetScheduleMaps = null;
    ownerId = 0;
    edit_repeat_flag = 0;
    startDate = new ALDateTimeField("yyyy-MM-dd-HH-mm");
    endDate = new ALDateTimeField("yyyy-MM-dd-HH-mm");
    baseDate = new ALDateTimeField("yyyy-MM-dd");
    msgList = new ArrayList<String>(0);

  }

  /**
   * 画面表示処理
   * 
   * @param action
   *          呼び出し元アクション
   * @param rundata
   *          実行データ
   * @param context
   *          Velocityコンテキスト
   * @param msgList
   *          エラーメッセージ一覧
   * @return JSON用出力文字列
   */
  public String doViewList(ALAction action, RunData rundata, Context context, List<String> msgList) {
    try {

      // 表示対象予定をDBより取得し、1日スケジュール用コンテナに展開する
      AjaxScheduleOneDayBaseContainer oneDayBaseCon = new AjaxScheduleOneDayBaseContainer();
      oneDayBaseCon.doSearchScheduleToUserOneDayContainer(rundata, context);

      // 各種予定展開結果をJSONデータに格納する
      JSONObject json = new JSONObject();
      json.put("hasAcl", hasAcl(rundata));
      json.put("today", oneDayBaseCon.getToday().toString());
      json.put("prevDate", oneDayBaseCon.getPrevDate().toString());
      json.put("nextDate", oneDayBaseCon.getNextDate().toString());
      json.put("prevWeek", oneDayBaseCon.getPrevWeek().toString());
      json.put("nextWeek", oneDayBaseCon.getNextWeek().toString());
      json.put("prevMonth", oneDayBaseCon.getPrevMonth().toString());
      json.put("nextMonth", oneDayBaseCon.getNextMonth().toString());
      json.put("startDate", oneDayBaseCon.getViewStart().toString());
      json.put("endDate", oneDayBaseCon.getViewEnd().toString());
      // 新規・編集用の対象日時
      json.put("formDateTime", oneDayBaseCon.getViewStart().toString() + "-00-00");

      // 祝日
      ALEipHolidaysManager holidaysManager = ALEipHolidaysManager.getInstance();
      String viewHoliday = "";
      ALHoliday hol = holidaysManager.isHoliday(oneDayBaseCon.getViewStart().getValue());
      if (null != hol) {
        viewHoliday = hol.getName().getValue();
      }
      json.put("holiday", viewHoliday);

      // 曜日
      json.put("dayOfWeek", oneDayBaseCon.getViewStart().getDayOfWeek());

      // 表示用期間予定一覧を生成
      List<List<ScheduleBean>> spanScheduleList = new ArrayList<List<ScheduleBean>>(0);

      // 期間予定を表示用期間予定一覧へ格納する
      for (AjaxSpanScheduleUserOneDayContainer spanUserContainer : oneDayBaseCon.getUserSpanContainerList()) {
        // 期間行コンテナに期間予定が無い場合は次のデータを対象とする
        if (!spanUserContainer.hasVisibleSpanRow()) {
          continue;
        }
        // 期間予定（閲覧メンバー分）を、表示用に変換してまとめる
        List<ScheduleBean> spanScheduleRow = new ArrayList<ScheduleBean>(0);
        int k = 0;
        for (Integer userId : oneDayBaseCon.getViewMemberUserIdList()) {
          // 現期間行の閲覧メンバー別期間予定を取得
          AjaxScheduleResultData rd = spanUserContainer.getUserDayConMap().get(userId).getTermResultData();
          if (rd != null) {
            // 結果データを表示用予定Beanへ変換
            ScheduleBean bean = new ScheduleBean();
            bean.initField();
            bean.setResultData(rd);
            bean.setColspanReal(1);
            bean.setIndex(k);
            bean.setIndexReal(k);
            // 開始終了日を設定
            if (ScheduleUtils.differenceDays(rd.getStartDate().getValue(), rd.getEndDate().getValue()) != 0) {
              // 開始日または終了日が異なる場合のみ開始日と終了日を表示
              bean.setViewStartEndDate(bean.getFormatStartEndDate("M/d"));
            } else {
              bean.setViewStartEndDate("");
            }
            // 表示情報文字数を設定
            int viewInfoLength = 0;
            // --予定名
            viewInfoLength += bean.getName().length();
            // --会議
            if (bean.getMemberList().size() > 1) {
              viewInfoLength += 4;
            }
            // --重要
            if (bean.getPriority()) {
              viewInfoLength += 4;
            }
            // --仮
            if (bean.isTemporary() && bean.isShowDetail()) {
              viewInfoLength += 3;
            }
            bean.setViewInfoLength(viewInfoLength);
            // ツールチップ表示判断値を設定(閲覧メンバー別の1行文字数)
            bean.setViewInfoLengthForHasToolTip(rowLengthForHasToolTip.get(oneDayBaseCon.getViewMemberUserIdList().size()));
            // ツールチップ表示有無
            // change start 二次開発受入テスト仕様変更対応
            // １日スケジュールツールチップは、強制表示する。
            // bean.setHasToolTip(bean.getViewInfoLength() >
            // bean.getViewInfoLengthForHasToolTip());
            bean.setHasToolTip(true);
            // change end
            // 非公開設定
            setNondisclosureMask(rd, bean, oneDayBaseCon);
            // ログインユーザーが主催者・参加メンバー
            // または
            // ログインユーザーが[秘書]である主催者・参加メンバーの予定を表示対象とする
            if (!rd.isHidden() || rd.isMember() || rd.isLoginuser()) {
              spanScheduleRow.add(bean);
            }
          }
          k++;
        }
        // 期間予定（1行分）を表示用期間予定一覧へ格納する
        spanScheduleList.add(spanScheduleRow);
      }

      // 表示用ユーザー別単発予定一覧を生成
      // List<ScheduleBean> userScheduleList = new ArrayList<ScheduleBean>(0);
      // 表示用単体予定一覧を生成
      List<ScheduleBean> schedules = new ArrayList<ScheduleBean>(0);

      // 閲覧メンバー分の通常予定を表示用期間予定一覧へ格納する
      int i = 0;
      AjaxScheduleUserOneDayContainer scUserContainer = oneDayBaseCon.getContainer();
      for (Integer userId : oneDayBaseCon.getViewMemberUserIdList()) {
        AjaxScheduleDayContainer dayContainer = scUserContainer.getUserDayConMap().get(userId);
        // １日中の予定一覧を、表示用に変換してまとめる
        // List<ScheduleBean> schedules = new ArrayList<ScheduleBean>(0);
        for (AjaxScheduleResultData rd : dayContainer.getScheduleList()) {
          // ダミースケジュールの場合は次データを対象とする
          if (rd.isDummy()) {
            continue;
          }
          // 結果データを表示用予定Beanへ変換
          ScheduleBean bean = new ScheduleBean();
          bean.initField();
          bean.setResultData(rd);
          // 表示情報文字数を設定
          int viewInfoLength = 0;
          // --予定名
          viewInfoLength += bean.getName().length();
          // --会議
          if (bean.getMemberList().size() > 1) {
            viewInfoLength += 4;
          }
          // --重要
          if (bean.getPriority()) {
            viewInfoLength += 4;
          }
          // --仮
          if (bean.isTemporary() && bean.isShowDetail()) {
            viewInfoLength += 3;
          }
          // -- 予定時間 開始時刻が≠00分かつ≠30分または、終了時刻が≠00分かつ≠30分の場合にカウント
          if ((!"0".equals(bean.getStartDateMinute()) && !"30".equals(bean.getStartDateMinute()))
            || (!"0".equals(bean.getEndDateMinute()) && !"30".equals(bean.getEndDateMinute()))) {
            viewInfoLength += bean.getStartDate().length() + bean.getEndDate().length() + 1;
          }
          // --場所
          if (bean.getPlace().length() > 0) {
            viewInfoLength += bean.getPlace().length() + 2;
          }
          bean.setViewInfoLength(viewInfoLength);
          // ツールチップ表示判断値を設定(閲覧メンバー別の1行文字数×30分単位の高さ)
          // -30分単位の高さを算出
          int betweenStartAndEnd = 0;
          betweenStartAndEnd = ScheduleUtils.differenceMinutes(rd.getStartDate().getValue(), rd.getEndDate().getValue()) / 30;
          // -端数切り上げ
          if (ScheduleUtils.differenceMinutes(rd.getStartDate().getValue(), rd.getEndDate().getValue()) % 30 > 0) {
            betweenStartAndEnd += 1;
          }
          bean.setViewInfoLengthForHasToolTip(rowLengthForHasToolTip.get(oneDayBaseCon.getViewMemberUserIdList().size()) * betweenStartAndEnd);
          // ツールチップ表示有無
          // change start 二次開発受入テスト仕様変更対応
          // １日スケジュールツールチップは、強制表示する。
          // bean.setHasToolTip(bean.getViewInfoLength() >
          // bean.getViewInfoLengthForHasToolTip());
          bean.setHasToolTip(true);
          // change end
          // 非公開設定
          setNondisclosureMask(rd, bean, oneDayBaseCon);
          bean.setIndex(i);
          // ログインユーザーが主催者・参加メンバー
          // または
          // ログインユーザーが[秘書]である主催者・参加メンバーの予定を表示対象とする
          if (!rd.isHidden() || rd.isMember() || rd.isLoginuser()) {
            schedules.add(bean);
          }
        }
        // userScheduleList.add(schedules);
        i++;
      }

      // 変換後の予定をJSONデータへ格納する
      json.put("spanSchedule", spanScheduleList);
      // json.put("schedule", userScheduleList);
      json.put("schedule", schedules);

      // 閲覧不可メンバーが1名以上存在する場合、エラーメッセージ表示領域へ設定する。
      String referenceErrMessage = oneDayBaseCon.getReferenceErrMessage();
      if (!"".equals(referenceErrMessage)) {
        json.put("referenceErr", referenceErrMessage);
      }
      // 閲覧可メンバーを再設定
      List<ScheduleUserBean> approvalUserList = new ArrayList<ScheduleUserBean>(0);
      for (Integer userId : oneDayBaseCon.getViewMemberUserIdList()) {
        ScheduleUserBean userInfo = ScheduleUtils.getScheduleUserBean(Integer.toString(ALEipUtils.getUserId(rundata)), Integer.toString(userId));
        if (!ScheduleConst.SCHEDULE_ACL_REFUSAL.equals(userInfo.getAccessCtrl())) {
          approvalUserList.add(userInfo);
        }
      }
      json.put("approvalMemberList", approvalUserList);

      // メッセージをJSONエラーメッセージデータとして格納する
      if ((msgList != null) && (msgList.size() > 0)) {
        json.put("errList", msgList);
      }

      return json.toString();
    } catch (Exception e) {
      // その他例外発生時、戻り値なしとする
      logger.error("１日スケジュールの表示に失敗しました。ログインユーザー:" + loginUserName, e);
      return null;
    }
  }

  /**
   * 非公開のマスク表示を設定します。
   * 
   * @param rd
   *          スケジュール検索結果
   * @param bean
   *          設定先のスケジュールBean
   * @param listData
   *          カレンダー用週間予定検索処理
   */
  private void setNondisclosureMask(AjaxScheduleResultData rd, ScheduleBean bean, AjaxScheduleOneDayBaseContainer listData) {
    // 公開フラグ=[非公開]
    // かつ ログインユーザーが参加ユーザーでない
    // かつ

    // ログインユーザーが対象ユーザーに対し[公開]
    // に該当する場合
    // もしくは
    // ログインユーザーが対象ユーザーに対し[秘書]
    // かつ対象ユーザー = 作成ユーザーID
    // に該当する場合

    // 非公開マスク表示にする
    if (!rd.isPublic()
      && !rd.isMember()
      && (listData.isPublicOfScheduleAcl(String.valueOf(rd.getUserId())) || listData.isSecretaryImpossible(String.valueOf(rd.getUserId()), String.valueOf(rd
        .getCreateUserId())))) {
      bean.setName("非公開");
      bean.setPlace("");
      bean.setStatus("");
      bean.setPriority(false);
      bean.setMember(false);
      bean.setLoginuser(false);
      bean.setOwner(false);
      bean.setViewStartEndDate("");
      bean.setViewInfoLength(3);
      bean.setHasToolTip(false);
      // add start 要件No.18 会議案内ファイル添付
      bean.setWithfiles(false);
      // add end
      // 個別色
      bean.setIndividualColor("");

    }
  }

  /**
   * カレンダー更新処理（予定のドラッグ＆ドロップで実行）
   * 
   * @param action
   *          呼び出し元アクション
   * @param rundata
   *          実行データ
   * @param context
   *          Velocityコンテキスト
   * @return 更新に成功した場合true、失敗した場合false
   */
  public boolean doUpdate(ALAction action, RunData rundata, Context context) {
    try {
      if (!doCheckSecurity(rundata, context)) {
        return false;
      }
      boolean res = (setFormData(rundata, context, msgList) && validate(msgList) && updateFormData(rundata, context, msgList));

      if (!res) {
        msgList.add("そのスケジュールは編集することができません。");
      }

      return res;

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

  /**
   * カレンダーコピー登録処理（予定のCTRLキー+ドラッグ＆ドロップで実行）
   * 
   * @param action
   *          呼び出し元アクション
   * @param rundata
   *          実行データ
   * @param context
   *          Velocityコンテキスト
   * @return コピー登録に成功した場合true、失敗した場合false
   */
  public boolean doInsert(ALAction action, RunData rundata, Context context) {
    try {
      if (!doCheckSecurity(rundata, context)) {
        return false;
      }
      // 編集元データをロード
      loadParameters(rundata, context, msgList);
      // 編集元データ
      boolean res = (setFormData(rundata, context, msgList) && validate(msgList) && insertFormData(rundata, context, msgList));

      if (!res) {
        msgList.add("そのスケジュールはコピー登録することができません。");
      }

      return res;

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

  /**
   * リクエストパラメータ取得処理
   * 
   * @param rundata
   *          実行データ
   * @param context
   *          Velocityコンテキスト
   * @param msgList
   *          エラーメッセージ一覧
   */
  public void loadParameters(RunData rundata, Context context, List<String> msgList) {
    ALDateTimeField dummy = new ALDateTimeField("yyyy-MM-dd-HH-mm");
    dummy.setNotNull(true);
    if (ALEipUtils.isMatch(rundata, context)) {
      if (rundata.getParameters().containsKey("start_date") && rundata.getParameters().containsKey("end_date")) {
        start_date = rundata.getParameters().getString("start_date");
        dummy.setValue(start_date);
        if (!dummy.validate(new ArrayList<String>())) {
          ALEipUtils.removeTemp(rundata, context, "start_date");
          ALEipUtils.removeTemp(rundata, context, "end_date");
          msgList.add("starDate_irregular");
          return;
        }
        end_date = rundata.getParameters().getString("end_date");
        dummy.setValue(end_date);
        if (!dummy.validate(new ArrayList<String>())) {
          ALEipUtils.removeTemp(rundata, context, "end_date");
          ALEipUtils.removeTemp(rundata, context, "end_date");
          msgList.add("endDate_irregular");
          return;
        }
        if (rundata.getParameters().containsKey("view_date")) {
          base_date = rundata.getParameters().getString("view_date");
          dummy.setValue(base_date);
          if (!dummy.validate(new ArrayList<String>())) {
            ALEipUtils.removeTemp(rundata, context, "view_date");
            ALEipUtils.removeTemp(rundata, context, "view_date");
            msgList.add("viewDate_irregular");
            return;
          }
        }
        if (rundata.getParameters().containsKey(ALEipConstants.ENTITY_ID)) {
          entityId = rundata.getParameters().getInt(ALEipConstants.ENTITY_ID);
        } else {
          msgList.add("entityId_missing");
          return;
        }
      } else if (rundata.getParameters().containsKey("straddle_mode")) {
        // 日またぎ更新モードの場合
        straddle_mode = rundata.getParameters().getString("straddle_mode");
        if (STRADDLE_MODE_CHANGE_START_TIME.equals(straddle_mode)) {
          // 開始時刻更新の場合
          straddle_start_time = rundata.getParameters().getString("straddle_start_time");
        } else if (STRADDLE_MODE_CHANGE_END_TIME.equals(straddle_mode)) {
          // 終了時刻更新の場合
          straddle_end_time = rundata.getParameters().getString("straddle_end_time");
        }
        // エンティティID取得
        entityId = rundata.getParameters().getInt(ALEipConstants.ENTITY_ID);
      } else {
        // null
      }
    } else {
      msgList.add("not own portlet");
      return;
    }
  }

  /**
   * 入力フォーム値やセッション情報を取得し、フィールドへ設定します。
   * 
   * @param rundata
   *          実行データ
   * @param context
   *          Velocityコンテキスト
   * @param msgList
   *          エラーメッセージ一覧(未使用)
   * @return フィールドへ値の設定に成功した場合true、失敗した場合false
   * @throws ALPageNotFoundException
   *           画面表示が不可能な問題が発生した場合
   * @throws ALDBErrorException
   *           DBエラーが発生した場合
   */
  public boolean setFormData(RunData rundata, Context context, List<String> msgList) throws ALPageNotFoundException, ALDBErrorException {
    try {
      userId = ALEipUtils.getUserId(rundata);
      targetSchedule = ScheduleUtils.getEipTSchedule(rundata, entityId, false, userId);

      if (rundata.getParameters().containsKey("edit_repeat_flag")) {
        edit_repeat_flag = rundata.getParameters().getInt("edit_repeat_flag");
      }
      // ユーザー表示順でソート
      targetScheduleMaps = ScheduleUtils.getEipTScheduleMapsByUserInfoSort(targetSchedule.getScheduleId());
      // 未分類共有カテゴリを取得
      commonCategory = CommonCategoryUtils.getEipTCommonCategory(Long.valueOf(1));

      int listSize = targetScheduleMaps.size();
      for (int i = 0; i < listSize; i++) {
        EipTScheduleMap map = targetScheduleMaps.get(i);
        if (ScheduleUtils.SCHEDULEMAP_TYPE_USER.equals(map.getType())) {
          int targetUserId = map.getUserId().intValue();
          // 必須/任意でメンバーを分割
          ALEipUser user = ALEipUtils.getALEipUser(targetUserId);
          if (ScheduleConst.SCHEDULEMAP_REQUIRED_T.equals(map.getRequired())) {
            reqMemberList.add(user);
          } else {
            subMemberList.add(user);
          }
          memberList.add(user);
        }
      }
      // オーナー（主催者）IDを取得
      ownerId = targetSchedule.getOwnerId().intValue();
      if (null != straddle_mode) {
        // 日またぎ更新モードの場合
        if (STRADDLE_MODE_CHANGE_START_TIME.equals(straddle_mode)) {
          // 開始時刻更新の場合
          startDate.setValue(straddle_start_time);
          endDate.setValue(targetSchedule.getEndDate());
        } else if (STRADDLE_MODE_CHANGE_END_TIME.equals(straddle_mode)) {
          // 終了時刻更新の場合
          startDate.setValue(targetSchedule.getStartDate());
          endDate.setValue(straddle_end_time);
        }
      } else {
        startDate.setValue(start_date);
        endDate.setValue(end_date);
      }
      baseDate.setValue(base_date);
    } catch (Exception e) {
      logger.error("Exception", e);
      return false;
    }
    return true;
  }

  /**
   * 入力チェック。このメソッドの実行前の処理結果を元に入力エラーか判定します。
   * 
   * @param msgList
   *          エラーメッセージ一覧
   * @return エラーが無い場合true、入力エラーの場合false
   */
  public boolean validate(List<String> msgList) throws ALDBErrorException, ALPageNotFoundException {
    return !(msgList.size() > 0);
  }

  /**
   * スケジュールを旧レコードから新レコードへコピーします。<br/>
   * 作成日と更新日は現在日付を設定します。
   * 
   * @param newSc
   *          新スケジュール
   * @param oldSc
   *          旧スケジュール
   * @param ownerUserId
   *          新スケジュールのオーナーのユーザーID
   * @param createUserId
   *          新スケジュールの作成者のユーザーID
   * @param updateUserId
   *          新スケジュールの更新者のユーザーID
   */
  private void copySchedule(EipTSchedule newSc, EipTSchedule oldSc, Integer ownerUserId, Integer createUserId, Integer updateUserId) {
    Date now = new Date();

    // 親ID
    newSc.setParentId(oldSc.getParentId());

    // ユーザID（オーナーID）
    newSc.setOwnerId(oldSc.getOwnerId());

    // 繰り返しパターン
    newSc.setRepeatPattern(oldSc.getRepeatPattern());

    // 開始時間
    newSc.setStartDate(oldSc.getStartDate());

    // 終了時間
    newSc.setEndDate(oldSc.getEndDate());

    // スケジュール名
    newSc.setName(oldSc.getName());

    // 場所
    newSc.setPlace(oldSc.getPlace());

    // 内容
    newSc.setNote(oldSc.getNote());

    // 公開フラグ
    newSc.setPublicFlag(oldSc.getPublicFlag());

    // 編集フラグ：不可固定
    newSc.setEditFlag("F");

    // 作成者：ログインユーザー
    newSc.setCreateUserId(userId);

    // 更新者：ログインユーザー
    newSc.setUpdateUserId(userId);

    // 作成日時
    newSc.setCreateDate(now);

    // 更新日時
    newSc.setUpdateDate(now);

    // add start 要件No.9 スケジュール表示 （仮の予定、確定した予定）
    newSc.setTemporaryFlag(oldSc.getTemporaryFlag());
    //
  }

  /**
   * スケジュールマップを旧レコードから新レコードへコピーします。<br/>
   * 回答状態は初期化します。
   * 
   * @param newScMap
   *          新スケジュールマップ
   * @param oldScMap
   *          旧スケジュールマップ
   * @param sc
   *          新スケジュールマップの親にあたるスケジュール
   */
  private void copyScheduleMap(EipTScheduleMap newScMap, EipTScheduleMap oldScMap, EipTSchedule sc) {
    newScMap.setEipTSchedule(sc);
    newScMap.setUserId(oldScMap.getUserId());

    if (oldScMap.getUserId().equals(sc.getOwnerId())) {
      // 新スケジュールの主催者の状態を[所有者]にする
      newScMap.setStatus(ScheduleConst.SCHEDULEMAP_STATUS_OWNER);
    } else {
      // 主催者以外は未回答固定（当処理は日時変更が前提であるため）
      newScMap.setStatus(ScheduleConst.SCHEDULEMAP_STATUS_NON_RES);
    }

    // 種別
    newScMap.setType(oldScMap.getType());

    // 共有カテゴリは未分類固定
    newScMap.setCommonCategoryId(1);
    newScMap.setEipTCommonCategory(commonCategory);

    // 必須フラグ：引き継ぎ
    newScMap.setRequired(oldScMap.getRequired());

    // 重要フラグ：引き継ぎ
    newScMap.setPriority(oldScMap.getPriority());

    // ダミー未回答フラグ：通常
    newScMap.setDummyNonResponse(ScheduleConst.SCHEDULEMAP_DUMMY_NON_RES_F);

    // 個別色
    newScMap.setIndividualColor(oldScMap.getIndividualColor());
  }

  /**
   * カレンダー更新詳細処理
   * 
   * @param rundata
   *          実行データ
   * @param context
   *          Velocityコンテキスト
   * @param msgList
   *          エラーメッセージ一覧
   * @return 更新に成功した場合true、失敗した場合false
   * @throws ALDBErrorException
   *           DBエラーが発生した場合
   */
  public boolean updateFormData(RunData rundata, Context context, List<String> msgList) throws ALPageNotFoundException, ALDBErrorException {

    boolean res = false;

    // 更新結果スケジュール
    EipTSchedule tempSc = null;

    // add start 要件No.18 会議案内ファイル添付
    Map<String, String> newFilesMap = new HashMap<String, String>();
    // add end

    try {
      if (edit_repeat_flag == 0) {
        // 繰り返しでないスケジュールをコピーしようとした場合
        // スケジュールに変更が加わっていない場合は、更新処理をスキップする
        if (targetSchedule.getStartDate().equals(startDate.getValue()) && targetSchedule.getEndDate().equals(endDate.getValue())) {
          return true;
        }

        targetSchedule.setStartDate(startDate.getValue());
        targetSchedule.setEndDate(endDate.getValue());
        Date now = new Date();
        targetSchedule.setUpdateDate(now);
        targetSchedule.setUpdateUserId(userId);

        // 回答状態の更新
        @SuppressWarnings("unchecked")
        List<EipTScheduleMap> scMaps = targetSchedule.getEipTScheduleMaps();

        for (EipTScheduleMap scMap : scMaps) {
          if (!ScheduleConst.SCHEDULEMAP_STATUS_OWNER.equals(scMap.getStatus()) && !ScheduleConst.SCHEDULEMAP_STATUS_DUMMY.equals(scMap.getStatus())) {
            // 主催者以外は未回答固定（当処理は日時変更が前提であるため）
            scMap.setStatus(ScheduleConst.SCHEDULEMAP_STATUS_NON_RES);
          }
          // ダミー未回答フラグ：通常
          scMap.setDummyNonResponse(ScheduleConst.SCHEDULEMAP_DUMMY_NON_RES_F);
        }

        Database.commit();
        res = true;

        // メール送信用に更新結果を格納
        tempSc = targetSchedule;

        // イベントログに保存
        sendEventLog(rundata, context);
        /* メンバー全員に新着ポートレット登録 */
        sendWhatsNew(targetSchedule, false);

      } else {
        // 繰り返しスケジュールを変更しようとした場合
        // 以下の場合は変更が加わっていないとみなし、更新をスキップする。
        // 保存されている開始時刻と終了時刻がendDateとstartDateと一致。 viewDateの日付がstartDateの物と一致。
        Calendar saved_startdate = Calendar.getInstance();
        saved_startdate.setTime(targetSchedule.getStartDate());
        Calendar saved_enddate = Calendar.getInstance();
        saved_enddate.setTime(targetSchedule.getEndDate());
        if (Integer.valueOf(startDate.getHour()) == saved_startdate.get(Calendar.HOUR_OF_DAY)
          && Integer.valueOf(startDate.getMinute()) == saved_startdate.get(Calendar.MINUTE)
          && Integer.valueOf(endDate.getHour()) == saved_enddate.get(Calendar.HOUR_OF_DAY)
          && Integer.valueOf(endDate.getMinute()) == saved_enddate.get(Calendar.MINUTE)
          && baseDate.getMonth().equals(startDate.getMonth())
          && baseDate.getDay().equals(startDate.getDay())
          && baseDate.getYear().equals(startDate.getYear())) {
          return true;
        }

        EipTSchedule newSchedule = Database.create(EipTSchedule.class);

        // 共通のスケジュールコピー処理を実行
        copySchedule(newSchedule, targetSchedule, ownerId, userId, userId);
        // 繰り返しの親スケジュール ID
        newSchedule.setParentId(targetSchedule.getScheduleId());

        // 終了時間
        newSchedule.setEndDate(endDate.getValue());
        // 繰り返しパターン：単発固定
        newSchedule.setRepeatPattern("N");
        // 開始時間
        newSchedule.setStartDate(startDate.getValue());

        int listSize = targetScheduleMaps.size();
        List<Integer> memberIdList = new ArrayList<Integer>();
        List<Integer> facilityIdList = new ArrayList<Integer>();

        for (int i = 0; i < listSize; i++) {
          EipTScheduleMap newMap = Database.create(EipTScheduleMap.class);
          EipTScheduleMap map = targetScheduleMaps.get(i);

          // 共通のスケジュールマップコピー処理を実行
          copyScheduleMap(newMap, map, newSchedule);

          if (ScheduleUtils.SCHEDULEMAP_TYPE_USER.equals(map.getType())) {
            memberIdList.add(map.getUserId().intValue());
          } else {
            facilityIdList.add(map.getUserId().intValue());
          }
        }

        // add start 要件No.18 会議案内ファイル添付
        // 添付ファイルをコピーする。
        ScheduleUtils.copyAvzTScheduleFile(newSchedule, targetSchedule, newFilesMap);
        // 添付ファイルをコピーする(ファイル)。
        ScheduleUtils.copyFiles(ownerId, newFilesMap);
        // add end

        if (baseDate != null) {
          ScheduleUtils.insertDummySchedule(targetSchedule, userId, baseDate.getValue(), baseDate.getValue(), memberIdList, facilityIdList);
        }

        Database.commit();
        res = true;

        // メール送信用に更新結果を格納
        tempSc = newSchedule;

        // イベントログに保存
        sendEventLog(rundata, context);
        // メンバー全員に新着ポートレット登録
        sendWhatsNew(newSchedule, false);

      }
    } catch (Exception e) {
      // add start 要件No.18 会議案内ファイル添付
      // ストアディレクトリに作成したファイルを削除する。
      ScheduleUtils.rollbackFiles(ownerId, newFilesMap);
      // add end
      Database.rollback();
      logger.error("予定の更新に失敗しました。ログインユーザー:" + loginUserName + "/スケジュールID:" + targetSchedule.getScheduleId(), e);
      return false;
    }
    // -----------------
    // メール送信処理
    // -----------------
    // memberListの各ユーザーの「状態」を判定し、削除、辞退のユーザーについては
    // memberListから除外
    List<EipTScheduleMap> smList = ScheduleUtils.getEipTScheduleMapsByUserInfoSort(targetSchedule.getScheduleId());
    memberList = ScheduleUtils.checkMemberList(memberList, smList);

    // メール送信先
    // 参加メンバー（ログインユーザーを除く）を送信先にする
    List<ALEipUserAddr> sendToUserList = ALMailUtils.getALEipUserAddrs(memberList, ALEipUtils.getUserId(rundata), false);
    // メール送信実行
    if (!ScheduleUtils.sendScheduleMail(
      sendToUserList,
      tempSc.getOwnerId(),
      tempSc,
      reqMemberList,
      subMemberList,
      ScheduleUtils.MEETING_UPDATE,
      "変更しました。",
      rundata,
      "",
      ScheduleUtils.CREATE_MSG_MODE_REQ)) {
      msgList.add("メールを送信できませんでした。");
      return false;
    }

    if (targetSchedule.getOwnerId().intValue() != userId) {

      // 秘書設定元へ代理操作の通知を行なう。
      // メール送信先(ALEipUser)秘書設定元送信用
      List<ALEipUser> sendToEipClientUserList = new ArrayList<ALEipUser>(0);

      sendToEipClientUserList.add(ALEipUtils.getALEipUser(targetSchedule.getOwnerId().intValue()));

      List<ALEipUserAddr> sendToClientUserList = ALMailUtils.getALEipUserAddrs(sendToEipClientUserList, userId, false);

      if (!ScheduleUtils.sendScheduleMail(
        sendToClientUserList,
        userId,
        targetSchedule.getOwnerId(),
        targetSchedule,
        reqMemberList,
        subMemberList,
        ScheduleUtils.SUBSTITUTE_UPDATE,
        "変更しました。",
        rundata,
        "",
        ScheduleUtils.CREATE_MSG_MODE_SUBREQ)) {
        msgList.add("mailErrMsg=メールを送信できませんでした。entityId=" + targetSchedule.getScheduleId());
        return false;
      }
    }
    return res;
  }

  /**
   * カレンダーコピー登録詳細処理
   * 
   * @param rundata
   *          実行データ
   * @param context
   *          Velocityコンテキスト
   * @param msgList
   *          エラーメッセージ一覧
   * @return コピー登録に成功した場合true、失敗した場合false
   * @throws ALDBErrorException
   *           DBエラーが発生した場合
   */
  protected boolean insertFormData(RunData rundata, Context context, List<String> msgList) throws ALDBErrorException {
    boolean res = false;
    // 更新結果スケジュール
    EipTSchedule tempSc = null;

    // add start 要件No.18 会議案内ファイル添付
    Map<String, String> newFilesMap = new HashMap<String, String>();
    // add end

    try {
      // 繰り返しでないスケジュールを変更しようとした場合
      if (edit_repeat_flag == 0) {

        // スケジュールをコピーする
        EipTSchedule newSchedule = Database.create(EipTSchedule.class);

        // 共通のスケジュールコピー処理を実行
        copySchedule(newSchedule, targetSchedule, ownerId, userId, userId);

        // 開始時間
        newSchedule.setStartDate(startDate.getValue());
        // 終了時間
        newSchedule.setEndDate(endDate.getValue());

        for (EipTScheduleMap scheduleMap : targetScheduleMaps) {
          EipTScheduleMap newScheduleMap = Database.create(EipTScheduleMap.class);
          // 共通のスケジュールマップコピー処理を実行
          copyScheduleMap(newScheduleMap, scheduleMap, newSchedule);
          // 重要フラグを初期化する
          newScheduleMap.setPriority(ScheduleConst.SCHEDULEMAP_PRIORITY_F);
          // 個別色
          newScheduleMap.setIndividualColor("");
        }

        // add start 要件No.18 会議案内ファイル添付
        // 添付ファイルをコピーする（DB）。
        ScheduleUtils.copyAvzTScheduleFile(newSchedule, targetSchedule, newFilesMap);
        // add end
        // add start 要件No.18 会議案内ファイル添付
        // 添付ファイルをコピーする(ファイル)。
        ScheduleUtils.copyFiles(ownerId, newFilesMap);
        // add end

        Database.commit();
        res = true;

        // メール送信用に更新結果を格納
        tempSc = newSchedule;

        // イベントログに保存
        sendEventLog(rundata, context);
        // メンバー全員に新着ポートレット登録
        sendWhatsNew(newSchedule, true);

      } else {
        // 繰り返しスケジュールを変更しようとした場合
        // 以下の場合は変更が加わっていないとみなし、更新をスキップする。
        // 保存されている開始時刻と終了時刻がendDateとstartDateと一致。 viewDateの日付がstartDateの物と一致。
        Calendar saved_startdate = Calendar.getInstance();
        saved_startdate.setTime(targetSchedule.getStartDate());
        Calendar saved_enddate = Calendar.getInstance();
        saved_enddate.setTime(targetSchedule.getEndDate());
        if (Integer.valueOf(startDate.getHour()) == saved_startdate.get(Calendar.HOUR_OF_DAY)
          && Integer.valueOf(startDate.getMinute()) == saved_startdate.get(Calendar.MINUTE)
          && Integer.valueOf(endDate.getHour()) == saved_enddate.get(Calendar.HOUR_OF_DAY)
          && Integer.valueOf(endDate.getMinute()) == saved_enddate.get(Calendar.MINUTE)
          && baseDate.getMonth().equals(startDate.getMonth())
          && baseDate.getDay().equals(startDate.getDay())
          && baseDate.getYear().equals(startDate.getYear())) {
          return true;
        }

        // スケジュールをコピーする
        EipTSchedule newSchedule = Database.create(EipTSchedule.class);

        // 共通のスケジュールコピー処理を実行
        copySchedule(newSchedule, targetSchedule, ownerId, userId, userId);
        // 繰り返しパターン：単発に変換
        newSchedule.setRepeatPattern(ScheduleConst.SCHEDULE_PATTERN_ONEDAY);

        // 開始時間
        newSchedule.setStartDate(startDate.getValue());
        // 終了時間
        newSchedule.setEndDate(endDate.getValue());

        for (EipTScheduleMap map : targetScheduleMaps) {
          EipTScheduleMap newMap = Database.create(EipTScheduleMap.class);
          // 共通のスケジュールマップコピー処理を実行
          copyScheduleMap(newMap, map, newSchedule);
          // 重要フラグを初期化する
          newMap.setPriority(ScheduleConst.SCHEDULEMAP_PRIORITY_F);
          // 個別色
          newMap.setIndividualColor("");
        }

        // add start 要件No.18 会議案内ファイル添付
        // 添付ファイルをコピーする(DB)。
        ScheduleUtils.copyAvzTScheduleFile(newSchedule, targetSchedule, newFilesMap);
        // add end
        // add start 要件No.18 会議案内ファイル添付
        // 添付ファイルをコピーする(ファイル)。
        ScheduleUtils.copyFiles(ownerId, newFilesMap);
        // add end

        Database.commit();
        res = true;
        // メール送信用に更新結果を格納
        tempSc = newSchedule;

        // イベントログに保存
        sendEventLog(rundata, context);
        // メンバー全員に新着ポートレット登録
        sendWhatsNew(newSchedule, true);

      }
    } catch (Exception e) {
      // add start 要件No.18 会議案内ファイル添付
      // ストアディレクトリに作成したファイルを削除する。
      ScheduleUtils.rollbackFiles(ownerId, newFilesMap);
      // add end
      Database.rollback();
      logger.error("予定のコピー登録に失敗しました。ログインユーザー:" + loginUserName + "/スケジュールID:" + targetSchedule.getScheduleId(), e);
      return false;
    }
    // -----------------
    // メール送信処理
    // -----------------
    // メール送信先
    // 参加メンバー（ログインユーザーを除く）を送信先にする
    List<ALEipUserAddr> sendToUserList = ALMailUtils.getALEipUserAddrs(memberList, ALEipUtils.getUserId(rundata), false);
    // メール送信実行
    if (!ScheduleUtils.sendScheduleMail(
      sendToUserList,
      tempSc.getOwnerId(),
      tempSc,
      reqMemberList,
      subMemberList,
      ScheduleUtils.MEETING_CREATE,
      "作成しました。",
      rundata,
      "",
      ScheduleUtils.CREATE_MSG_MODE_REQ)) {
      msgList.add("メールを送信できませんでした。");
      return false;
    }

    if (tempSc.getOwnerId().intValue() != userId) {

      // 秘書設定元へ代理操作の通知を行なう。

      // メール送信先(ALEipUser)秘書設定元送信用
      List<ALEipUser> sendToEipClientUserList = new ArrayList<ALEipUser>(0);

      sendToEipClientUserList.add(ALEipUtils.getALEipUser(tempSc.getOwnerId().intValue()));

      List<ALEipUserAddr> sendToClientUserList = ALMailUtils.getALEipUserAddrs(sendToEipClientUserList, userId, false);

      if (!ScheduleUtils.sendScheduleMail(
        sendToClientUserList,
        userId,
        tempSc.getOwnerId(),
        tempSc,
        reqMemberList,
        subMemberList,
        ScheduleUtils.SUBSTITUTE_CREATE,
        "作成しました。",
        rundata,
        "",
        ScheduleUtils.CREATE_MSG_MODE_SUBREQ)) {
        msgList.add("mailErrMsg=メールを送信できませんでした。entityId=" + tempSc.getScheduleId());
        return false;
      }
    }

    return res;
  }

  /**
   * イベントログ登録
   * 
   * @param rundata
   *          実行データ
   * @param context
   *          Velocityコンテキスト
   */
  private void sendEventLog(RunData rundata, Context context) {
    ALEipUtils.setTemp(rundata, context, ALEipConstants.MODE, ALEipConstants.MODE_UPDATE);
    ALEventlogFactoryService.getInstance().getEventlogHandler().log(
      targetSchedule.getScheduleId(),
      ALEventlogConstants.PORTLET_TYPE_SCHEDULE,
      targetSchedule.getName());
  }

  /**
   * 新着ポートレットへ情報送信
   * 
   * @param newSchedule
   *          送信対象の予定
   * @param isNew
   *          新規の場合true、そうでない場合false
   */
  private void sendWhatsNew(EipTSchedule newSchedule, boolean isNew) {

    ALEipUser loginUser = null;
    try {
      loginUser = ALEipUtils.getALEipUser(userId);
    } catch (ALDBErrorException e) {
      //
    }
    if (loginUser != null) {
      String loginName = loginUser.getName().getValue();
      List<String> recipients = new ArrayList<String>();
      for (ALEipUser user : memberList) {
        if (loginUser.getUserId().getValue() != user.getUserId().getValue()) {
          recipients.add(user.getName().getValue());
        }
      }
      ScheduleUtils.createShareScheduleActivity(targetSchedule, loginName, recipients, isNew);
    }
  }

  /**
   * 操作権限有無を取得
   * 
   * @param rundata
   *          実行データ
   * @return 操作権限有無
   */
  private boolean hasAcl(RunData rundata) {
    ALAccessControlFactoryService aclservice =
      (ALAccessControlFactoryService) ((TurbineServices) TurbineServices.getInstance()).getService(ALAccessControlFactoryService.SERVICE_NAME);
    ALAccessControlHandler aclhandler = aclservice.getAccessControlHandler();
    boolean hasAuthority =
      aclhandler.hasAuthority(
        ALEipUtils.getUserId(rundata),
        ALAccessControlConstants.POERTLET_FEATURE_SCHEDULE_OTHER,
        ALAccessControlConstants.VALUE_ACL_INSERT);
    if (!hasAuthority) {
      return false;
    }
    return true;
  }

  /**
   * メッセージ一覧取得
   * 
   * @return メッセージ一覧
   */
  public List<String> getMsgList() {
    return msgList;
  }

  /**
   * セキュリティをチェックします。
   * 
   * @return
   */
  private boolean doCheckSecurity(RunData rundata, Context context) {
    String reqSecid = rundata.getParameters().getString(ALEipConstants.SECURE_ID);
    String sessionSecid = (String) rundata.getUser().getTemp(ALEipConstants.SECURE_ID);
    if (reqSecid == null || !reqSecid.equals(sessionSecid)) {
      return false;
    }

    return true;
  }

}
