package aipo.webservice.util;

/*
 * Aipo is a groupware program developed by Aimluck,Inc.
 * Copyright (C) 2004-2011 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/>.
 *
 */

import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.List;

import org.apache.cayenne.exp.Expression;
import org.apache.cayenne.exp.ExpressionFactory;
import org.apache.commons.lang.StringUtils;
import org.apache.jetspeed.services.logging.JetspeedLogFactoryService;
import org.apache.jetspeed.services.logging.JetspeedLogger;

import aipo.webservice.soap.axis2.ReminderService;
import aipo.webservice.soap.axis2.bean.OutlookAddressBookBean;
import aipo.webservice.soap.axis2.bean.OutlookScheduleBean;

import com.aimluck.eip.cayenne.om.portlet.EipMAddressGroup;
import com.aimluck.eip.cayenne.om.security.TurbineUser;
import com.aimluck.eip.orm.Database;
import com.aimluck.eip.orm.query.SelectQuery;

/**
 * <HR>
 * Outlookデータ移行用ユーティリティクラス
 * <p>
 * 
 * Outlookデータ移行関連のユーティリティクラス 繰り返しパターン変換、曜日変換、バリデーションチェック、 会社情報取得、ユーザー情報取得
 * <P>
 * <HR>
 * <P>
 * 
 */
public class OutlookTransferUtils {

  /** スケジュール繰り返しパターンサフィックス(最後の文字) */
  private static final String EIP_T_SCHEDULE_REPEAT_PATTERN_SUFFIX = "L";

  private static DateFormat format =
    new SimpleDateFormat("yyyy/MM/dd HH:mm:ss");

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

  /**
   * Outlookスケジュールバリデーションチェック
   * <p>
   * Outlookスケジュールの入力文字列のバリデーションチェックを行う。<br>
   * NGの場合はエラーメッセージを作成してパラメータのバッファに格納する。<br>
   * 
   * @param olSchedule
   *            Outlookスケジュールのインスタンス
   * @return String エラーメッセージ
   */
  public static String IsValidSchedule(OutlookScheduleBean olSchedule) {
    StringBuffer msgBuf = new StringBuffer();

    if (!IsValidLength(olSchedule.getSubject(), 99, "件名", msgBuf)) {
      msgBuf.append("件名：[" + olSchedule.getSubject() + "] ");
    }
    if (!IsValidLength(olSchedule.getLocation(), 99, "場所", msgBuf)) {
      msgBuf.append("場所：[" + olSchedule.getLocation() + "] ");
    }
    if (!IsValidLength(olSchedule.getBody(), 1000, "内容", msgBuf)) {
      msgBuf.append("内容：[" + olSchedule.getLocation() + "] ");
    }
    if (!isValidDateTime(olSchedule.getStart(), "開始時刻", msgBuf)) {
      msgBuf.append("開始時刻：[" + olSchedule.getStart() + "] ");
    }
    if (!isValidDateTime(olSchedule.getEnd(), "終了時刻", msgBuf)) {
      msgBuf.append("終了時刻：[" + olSchedule.getEnd() + "] ");
    }
    if (!isValidDateTime(olSchedule.getPatternStartDate(), "開始日", msgBuf)) {
      msgBuf.append("開始日：[" + olSchedule.getPatternStartDate() + "] ");
    }
    if (!isValidDateTime(olSchedule.getPatternEndDate(), "終了日", msgBuf)) {
      msgBuf.append("終了日：[" + olSchedule.getPatternEndDate() + "] ");
    }

    if (msgBuf.length() > 0) {
      // エラーメッセージ作成
      msgBuf.insert(0, "入力チェックでエラーが発生しました。"
        + System.getProperty("line.separator")
        + "件名：["
        + olSchedule.getSubject()
        + "] 場所：["
        + olSchedule.getLocation()
        + "] 開始日：["
        + olSchedule.getStart()
        + "] 終了日：["
        + olSchedule.getEnd()
        + "]");
      msgBuf.append(System.getProperty("line.separator"));
    }
    return msgBuf.toString();
  }

  /**
   * 繰り返しパターン判定
   * <p>
   * Outlookの繰り返しタイプからAipoのスケジュール繰り返しパターンを判定し、 パターン文字列を返す。<br>
   * 
   * @param isReccuring
   *            繰り返し有無
   * @param recurrenceType
   *            スケジュールの繰り返しパターン
   * @param interval
   *            頻度
   * @param dayOfWeekMask
   *            曜日
   * @param dayOfMonth
   *            日付
   * @param monthOfYear
   *            月
   * @param instance
   *            N回目
   * @param startDateTime
   *            開始時刻
   * @param endDateTime
   *            終了時刻
   * @throws Exception
   *             繰り返しパターン文字列異常などで発生
   * @return String 繰り返しパターン文字列
   */

  public static String getScheduleRepeatPattern(boolean isRecurring,
      boolean isAllDayEvent, String recurrenceTypeString,
      String intervalString, String dayOfWeekMaskString,
      String dayOfMonthString, String monthOfYearString, String instanceString,
      String startDateTime, String endDateTime, StringBuffer errMsg) {

    try {
      // 文字列を数値に変換する。
      int dayOfWeek = 0;
      int recurrenceType = Integer.parseInt(recurrenceTypeString);
      int interval = Integer.parseInt(intervalString);
      // 間隔は毎日や毎週を選択した時、Outlookでは0となるのでAipoでは1とする。
      if (interval == 0) {
        interval = 1;
      }
      int dayOfWeekMask = Integer.parseInt(dayOfWeekMaskString);
      int dayOfMonth = Integer.parseInt(dayOfMonthString);
      int monthOfYear = Integer.parseInt(monthOfYearString);
      int instance = Integer.parseInt(instanceString);

      // 繰り返しなしなら1日だけ(単発)のスケジュール、期間、日またぎのいずれか
      if (!isRecurring) {

        // 終日なら期間指定
        if (isAllDayEvent) {
          return "S"; // 期間指定
        }

        // 開始時刻と終了時刻の年月日を比較
        Date startDate = format.parse(startDateTime);
        Date endDate = format.parse(endDateTime);

        if (WsUtils.compareToYMD(startDate, endDate) == 0) {
          // 同日なら単発
          return "N"; // 通常
        }
        // 異なる日なら日またぎ
        return "Z"; // 日跨ぎ

      }

      // ----------------------------------------
      // 繰り返しパターンを判定
      // ----------------------------------------
      switch (recurrenceType) {

        // 日次パターン
        case 0:

          // パラメータ.（Outlook）曜日の値が62の場合：
          if (dayOfWeekMask == 62) {
            return "B" // 週
              + "01"
              + "0111110"
              + EIP_T_SCHEDULE_REPEAT_PATTERN_SUFFIX; // すべての平日
          }

          return "A" // 日
            + String.format("%03d", interval)
            + EIP_T_SCHEDULE_REPEAT_PATTERN_SUFFIX; // 間隔xxx日ごと

          // 週次パターン
        case 1:
          // 曜日指定は1～127まで
          if (dayOfWeekMask < 1 || dayOfWeekMask > 127) {
            errMsg.append("曜日の値が不正です。曜日：[" + dayOfWeekMaskString + "]");
            logger.error(errMsg.toString());
            return "";
          }

          // 曜日の数値をAipoの繰り返しパターンに変換
          String repeatPattern = Integer.toBinaryString(dayOfWeekMask);
          // 7桁にするため0でパディング
          repeatPattern = StringUtils.leftPad(repeatPattern, 7, "0");
          // StringBuffer型の文字列に変換
          StringBuffer bf = new StringBuffer(repeatPattern);
          // 逆順にソートする
          bf.reverse();

          return "B" // 週
            + String.format("%02d", interval)
            + bf.toString()
            + EIP_T_SCHEDULE_REPEAT_PATTERN_SUFFIX; // 間隔xx週ごとn曜日

          // 月次パターン
        case 2:
          return "C" // 月
            + String.format("%02d", interval)
            + String.format("%02d", dayOfMonth)
            + EIP_T_SCHEDULE_REPEAT_PATTERN_SUFFIX; // xxヶ月ごとのdd日に設定

          // 月次/曜日パターン
        case 3:

          // パラメータ.（Outlook）曜日の値が62(平日指定)の場合：
          if (dayOfWeekMask == 62) {

            // 平日指定パターン
            return "E" // 月/曜日
              + String.format("%02d", interval)
              + "W"
              + Integer.toString(instance)
              + "A" // 平日
              + EIP_T_SCHEDULE_REPEAT_PATTERN_SUFFIX; // xxヶ月ごとの第y番目の平日に設定

            // パラメータ.（Outlook）曜日の値が65(週末指定)の場合：
          } else if (dayOfWeekMask == 65) {

            // 週末指定パターン
            return "E" // 月/曜日
              + String.format("%02d", interval)
              + "W"
              + Integer.toString(instance)
              + "B" // 週末
              + EIP_T_SCHEDULE_REPEAT_PATTERN_SUFFIX; // xxヶ月ごとの第y番目の週末に設定

            // パラメータ.（Outlook）曜日の値が127(日指定)の場合：
          } else if (dayOfWeekMask == 127) {

            if (instance >= 1 && instance <= 4) {
              // N番目の日
              return "C" // 月
                + String.format("%02d", interval)
                + String.format("%02d", instance)
                + EIP_T_SCHEDULE_REPEAT_PATTERN_SUFFIX; // xxヶ月ごとの第dd番目の日に設定

            } else if (instance == 5) {
              // 最終日
              return "C" // 月
                + String.format("%02d", interval)
                + "99"
                + EIP_T_SCHEDULE_REPEAT_PATTERN_SUFFIX; // xxヶ月ごとの最終日に設定

            } else {
              // 不正な値
              errMsg.append("N回目の値が不正です。N回目：[" + instanceString + "]");
              logger.error(errMsg.toString());
              return "";
            }
          }

          // 62(平日)、65(週末)、127(日)以外の場合(＝曜日指定の場合)

          // Outlookの曜日をCalendarクラスの曜日に変換
          dayOfWeek = getCalendarWeekMask(dayOfWeekMask);
          if (dayOfWeek < 0) {
            errMsg.append("曜日の値が不正です。曜日：[" + dayOfWeekMaskString + "]");
            logger.error(errMsg.toString());
            return "";
          }

          return "E" // 月/曜日
            + String.format("%02d", interval)
            + "W"
            + Integer.toString(instance)
            + Integer.toString(dayOfWeek)
            + EIP_T_SCHEDULE_REPEAT_PATTERN_SUFFIX; // xxヶ月ごとの第y z曜日に設定

          // 年次パターン
        case 5:
          return "F" // 年
            + String.format("%02d", monthOfYear)
            + String.format("%02d", dayOfMonth)
            + EIP_T_SCHEDULE_REPEAT_PATTERN_SUFFIX; // mm月dd日に設定

          // 年次/曜日パターン
        case 6:
          // パラメータ.（Outlook）曜日の値が62(平日指定)の場合：
          if (dayOfWeekMask == 62) {

            // 平日指定パターン
            return "G" // 年/曜日
              + String.format("%02d", monthOfYear)
              + "W"
              + Integer.toString(instance)
              + "A" // 平日
              + EIP_T_SCHEDULE_REPEAT_PATTERN_SUFFIX; // mm月第y番目の平日に設定

            // パラメータ.（Outlook）曜日の値が65(週末指定)の場合：
          } else if (dayOfWeekMask == 65) {

            // 週末指定パターン
            return "G" // 年/曜日
              + String.format("%02d", monthOfYear)
              + "W"
              + Integer.toString(instance)
              + "B" // 週末
              + EIP_T_SCHEDULE_REPEAT_PATTERN_SUFFIX; // mm月第y番目の週末に設定

            // パラメータ.（Outlook）曜日の値が127(日指定)の場合：
          } else if (dayOfWeekMask == 127) {

            if (instance >= 1 && instance <= 4) {
              // N番目の日
              return "F" // 年
                + String.format("%02d", monthOfYear)
                + String.format("%02d", instance)
                + EIP_T_SCHEDULE_REPEAT_PATTERN_SUFFIX; // mm月第dd番目の日に設定

            } else if (instance == 5) {
              // 最終日
              return "F" // 年
                + String.format("%02d", monthOfYear)
                + "99"
                + EIP_T_SCHEDULE_REPEAT_PATTERN_SUFFIX; // mm月の最終日に設定

            } else {
              // 不正な値
              errMsg.append("N回目の値が不正です。N回目：[" + instanceString + "]");
              logger.error(errMsg.toString());
              return "";
            }
          }

          // 62(平日)、65(週末)、127(日)以外の場合(＝曜日指定の場合)

          // Outlookの曜日をCalendarクラスの曜日に変換
          dayOfWeek = getCalendarWeekMask(dayOfWeekMask);
          if (dayOfWeek < 0) {
            errMsg.append("曜日の値が不正です。曜日：[" + dayOfWeekMaskString + "]");
            logger.error(errMsg.toString());
            return "";
          }
          return "G" // 年/曜日
            + String.format("%02d", monthOfYear)
            + "W"
            + Integer.toString(instance)
            + Integer.toString(dayOfWeek)
            + EIP_T_SCHEDULE_REPEAT_PATTERN_SUFFIX; // mm月第y z曜日に設定

        default:
          errMsg.append("繰り返しパターンの値が不正です。繰り返しパターン：["
            + recurrenceTypeString
            + "]");
          logger.error(errMsg.toString());
          return "";
      }

    } catch (Exception e) {
      // 予期せぬエラーの場合、0件のスケジュールリストを返す
      errMsg.append("繰り返しパターン判定で予期せぬ例外が発生しました。繰り返し有無：["
        + Boolean.valueOf(isRecurring)
        + "] 終日フラグ：["
        + Boolean.valueOf(isAllDayEvent)
        + "] 繰り返しパターン：["
        + recurrenceTypeString
        + "] 頻度：["
        + intervalString
        + "] 曜日：["
        + dayOfWeekMaskString
        + "] 日付：["
        + dayOfMonthString
        + "] 月：["
        + monthOfYearString
        + "] N回目：["
        + instanceString
        + "] 開始日時：["
        + startDateTime
        + "] 終了日時：["
        + endDateTime
        + "]");
      logger.error(errMsg.toString(), e);
      return "";
    }
  }

  /**
   * Outlook曜日変換
   * <p>
   * Outlookの曜日をCalendarクラスの曜日に変換して返す。<br>
   * 
   * @param dayOfWeekMask
   *            Outlookの曜日数値
   * @return int javaの曜日数値
   */
  public static int getCalendarWeekMask(int dayOfWeekMask) {

    switch (dayOfWeekMask) {
      case 1:
        return Calendar.SUNDAY; // 日曜日
      case 2:
        return Calendar.MONDAY; // 月曜日
      case 4:
        return Calendar.TUESDAY; // 火曜日
      case 8:
        return Calendar.WEDNESDAY; // 水曜日
      case 16:
        return Calendar.THURSDAY; // 木曜日
      case 32:
        return Calendar.FRIDAY; // 金曜日
      case 64:
        return Calendar.SATURDAY; // 土曜日
      default:
        return -1; // 不正な値
    }
  }

  /**
   * Outlookアドレス帳バリデーションチェック
   * <p>
   * Outlookアドレス帳の入力文字列のバリデーションチェックを行う。 <br>
   * NGの場合はエラーメッセージを作成してパラメータのバッファに格納する。<br>
   * 
   * @param olSchedule
   *            Outlookアドレス帳のインスタンス
   * @return String エラーメッセージ
   */

  public static String IsValidAddress(OutlookAddressBookBean olAddressBook) {
    StringBuffer msgBuf = new StringBuffer();

    // 姓
    if (!IsValidLength(olAddressBook.getFirstName(), 99, "姓", msgBuf)) {
      msgBuf.append("姓：[" + olAddressBook.getFirstName());
    }
    // 名
    if (!IsValidLength(olAddressBook.getLastName(), 99, "名", msgBuf)) {
      msgBuf.append("名：[" + olAddressBook.getLastName() + "] ");
    }
    // 姓カナ
    if (!IsValidLength(olAddressBook.getYomiFirstName(), 99, "姓カナ", msgBuf)) {
      msgBuf.append("姓カナ：[" + olAddressBook.getYomiFirstName() + "] ");
    }
    // 名カナ
    if (!IsValidLength(olAddressBook.getYomiLastName(), 99, "名カナ", msgBuf)) {
      msgBuf.append("名カナ：[" + olAddressBook.getYomiLastName() + "] ");
    }
    // 電子メール
    if (!IsValidLength(olAddressBook.getEmail1Address(), 99, "電子メール", msgBuf)) {
      msgBuf.append("電子メール：[" + olAddressBook.getEmail1Address() + "] ");
    }
    // 会社電話
    if (!IsValidLength(
      olAddressBook.getBusinessTelephoneNumber(),
      99,
      "会社電話",
      msgBuf)) {
      msgBuf.append("会社電話：["
        + olAddressBook.getBusinessTelephoneNumber()
        + "] ");
    }
    // 携帯電話
    if (!IsValidLength(
      olAddressBook.getMobileTelephoneNumber(),
      99,
      "携帯電話",
      msgBuf)) {
      msgBuf.append("携帯電話：["
        + olAddressBook.getBusinessTelephoneNumber()
        + "] ");
    }
    // 電子メール2
    if (!IsValidLength(olAddressBook.getEmail2Address(), 99, "電子メール2", msgBuf)) {
      msgBuf.append("電子メール2：[" + olAddressBook.getEmail2Address() + "] ");
    }
    // 会社
    if (!IsValidLength(olAddressBook.getCompanyName(), 99, "会社", msgBuf)) {
      msgBuf.append("会社：[" + olAddressBook.getCompanyName() + "] ");
    }
    // 役職名
    if (!IsValidLength(olAddressBook.getJobTitle(), 64, "役職名", msgBuf)) {
      msgBuf.append("役職名：[" + olAddressBook.getJobTitle() + "] ");
    }
    // 会社FAX
    if (!IsValidLength(
      olAddressBook.getBusinessFaxNumber(),
      99,
      "会社FAX",
      msgBuf)) {
      msgBuf.append("会社FAX：[" + olAddressBook.getBusinessFaxNumber() + "] ");
    }
    // 作成日時
    if (!isValidDateTime(olAddressBook.getCreationTime(), "作成日時", msgBuf)) {
      msgBuf.append("作成日時：[" + olAddressBook.getCreationTime() + "] ");
    }
    // 更新日時
    if (!isValidDateTime(
      olAddressBook.getLastModificationTime(),
      "更新日時",
      msgBuf)) {
      msgBuf.append("更新日時：[" + olAddressBook.getLastModificationTime() + "] ");
    }

    if (msgBuf.length() > 0) {
      // メッセージ格納用文字列バッファにエラーメッセージを追加
      msgBuf.insert(0, "入力チェックでエラーが発生しました。"
        + System.getProperty("line.separator")
        + "姓：["
        + olAddressBook.getFirstName()
        + "] 名：["
        + olAddressBook.getLastName()
        + "]");
      msgBuf.append(System.getProperty("line.separator"));
    }
    return msgBuf.toString();
  }

  /**
   * アドレス帳社外グループ取得
   * <p>
   * 会社名でアドレス帳社外グループ情報テーブルを検索し、アドレス帳社外グループのインスタンスを返す。<br>
   * 
   * @param groupName
   *            グループ名
   * @throws Exception
   *             DBアクセス異常などで発生
   * @return EipMAddressGroupアドレス帳社外グループ情報のインスタンス
   */

  public static EipMAddressGroup getEipMAddressGroupFromGroupName(
      String groupName, int ownerId) throws Exception {
    try {
      SelectQuery<EipMAddressGroup> query =
        Database.query(EipMAddressGroup.class);
      // 会社名をキーに検索
      Expression exp1 =
        ExpressionFactory.matchExp(
          EipMAddressGroup.GROUP_NAME_PROPERTY,
          groupName);
      Expression exp2 =
        ExpressionFactory.matchExp(EipMAddressGroup.OWNER_ID_PROPERTY, ownerId);
      query.setQualifier(exp1.andExp(exp2));
      List<EipMAddressGroup> maps = query.fetchList();

      // 1件以上なら先頭のインスタンスを返す
      if (maps.size() > 0) {
        return maps.get(0);
      }
      return new EipMAddressGroup();
    } catch (Exception e) {
      String errMsg = "アドレス帳社外グループ取得処理で予期せぬ例外が発生しました。";
      logger.error(errMsg, e);
      throw new Exception(errMsg + e.toString());
    }
  }

  /**
   * アドレスユーザー検索
   * <p>
   * ユーザー情報テーブルをメールアドレスで検索し、結果を返す。<br>
   * 
   * @param email
   *            メールアドレス
   * @throws Exception
   *             DBアクセス異常などで発生
   * @return TurbineUser ユーザー情報のインスタンス
   */

  public static TurbineUser getUserFromEmail(String email) throws Exception {
    try {
      SelectQuery<TurbineUser> query = Database.query(TurbineUser.class);
      // 検索条件：メールアドレスと一致
      Expression exp =
        ExpressionFactory.matchExp(TurbineUser.EMAIL_PROPERTY, email);
      query.setQualifier(exp); // 検索実行
      List<TurbineUser> resultList = query.fetchList();
      // 1件以上なら先頭のインスタンスを返す
      if (resultList.size() > 0) {
        return resultList.get(0);
      }
    } catch (Exception e) {
      String errMsg = "ユーザー情報取得処理で予期せぬ例外が発生しました。";
      logger.error(errMsg, e);
      throw new Exception(errMsg + e.toString());
    }
    String errMsg =
      "出席者のメールアドレスがユーザー情報に存在しません。当ユーザーはスケジュールの登録をスキップします。メールアドレス：["
        + email
        + "]";
    logger.warn(errMsg);
    throw new Exception(errMsg);
  }

  /**
   * 文字列長チェック
   * <p>
   * Outlook項目の入力文字列の長さが正しいかどうかを判定する。<br>
   * 
   * @param str
   *            文字列
   * @param length
   *            長さ
   * @param msgBuf
   *            エラーメッセージ格納用文字列
   * @return boolean 正しい場合true、正しくない場合false
   */

  public static boolean IsValidLength(String str, int length, String itemName,
      StringBuffer msgBuf) {
    if (StringUtils.isEmpty(str)) {
      return true;
    }
    // メッセージ格納用文字列バッファにエラーメッセージを追加
    if (str.length() > length) {
      msgBuf.append(itemName
        + "の文字数が制限を超えています。最大長："
        + Integer.toString(length)
        + "文字 ");
      return false;
    }
    return true;
  }

  /**
   * 日時の妥当性チェック
   * <p>
   * 正しい日時かどうかチェックする。<br>
   * 
   * @param dateTime
   *            比較対象の日時
   * @return boolean 正しい場合true、正しくない場合false
   * @throws Exception
   *             日付文字列異常時に発生
   */
  public static boolean isValidDateTime(String strDateTime, String itemName,
      StringBuffer msgBuf) {
    if (StringUtils.isEmpty(strDateTime)) {
      return true;
    }

    // DateFormat format =
    // DateFormat.getDateTimeInstance(DateFormat.MEDIUM, DateFormat.MEDIUM);
    // 日付/時刻解析を厳密に行うかどうかを設定する。
    DateFormat fmt = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss");
    fmt.setLenient(false);
    try {
      fmt.parse(strDateTime);
      return true;
    } catch (Exception e) {
      msgBuf.append(itemName + "の日付形式が正しくありません。");
      return false;
    }
  }

}
