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

import java.util.ArrayList;
import java.util.BitSet;
import java.util.Calendar;
import java.util.Date;
import java.util.List;
import java.util.Properties;

import javax.mail.AuthenticationFailedException;
import javax.mail.Flags;
import javax.mail.Folder;
import javax.mail.Message;
import javax.mail.MessagingException;
import javax.mail.Session;
import javax.mail.Store;
import javax.mail.internet.MimeMessage;

import org.apache.jetspeed.services.logging.JetspeedLogFactoryService;
import org.apache.jetspeed.services.logging.JetspeedLogger;

import com.aimluck.eip.mail.util.ALMailUtils;
import com.sun.mail.pop3.POP3Folder;
import com.sun.mail.pop3.POP3Message;

/**
 * メール受信（POP3）を操作する抽象クラスです。 <br />
 * 
 */
public abstract class ALPop3MailReceiver implements ALMailReceiver {

  private static final JetspeedLogger logger = JetspeedLogFactoryService
      .getLogger(ALPop3MailReceiver.class.getName());

  /** <code>AUTH_RECEIVE_NORMAL</code> 受信時の認証方式（標準） */
  public static final int AUTH_RECEIVE_NORMAL = 0;

  /** <code>AUTH_RECEIVE_APOP</code> 受信時の認証方式（APOP） */
  public static final int AUTH_RECEIVE_APOP = 1;

  /** メール受信時の処理結果（ロックがかかっていて，受信に失敗した） */
  public static final int RECEIVE_MSG_FAIL_LOCKED = -2;

  /** メール受信時の処理結果（メールサイズが受信可能サイズよりも大きいため，受信に失敗した） */
  public static final int RECEIVE_MSG_FAIL_OVER_MAIL_MAX_SIZE = -3;

  /** メール受信時の処理結果（POP3 サーバと接続できず，受信に失敗した） */
  public static final int RECEIVE_MSG_FAIL_CONNECT = -4;

  /** メール受信時の処理結果（POP3 サーバとの認証に失敗し，受信に失敗した） */
  public static final int RECEIVE_MSG_FAIL_AUTH = -5;

  /** メール受信時の処理結果（Exception のエラーが発生し，受信に失敗した） */
  public static final int RECEIVE_MSG_FAIL_EXCEPTION = -6;

  /** メール受信時の処理結果（OutOfMemory のエラーが発生し，受信に失敗した） */
  public static final int RECEIVE_MSG_FAIL_OUTOFMEMORY = -7;

  /** 受信可能なメール容量（7MB）．Base64 処理後のサイズ */
  public static final int MAIL_MAX_SIZE = 10485760; // 2097152;

  /** 接続時のタイムアウト時間 */
  private static final String CONNECTION_TIMEOUT = "60000";

  /** 接続後のタイムアウト時間 */
  private static final String TIMEOUT = "300000";

  protected ALPop3MailReceiverContext rcontext;

  /**
   * コンストラクタ
   * 
   * @param rcontext
   */
  public ALPop3MailReceiver(ALMailReceiverContext rcontext) {
    this.rcontext = (ALPop3MailReceiverContext) rcontext;
  }

  protected abstract ALFolder getALFolder();

  /**
   * POP3 サーバからメールを受信する．
   * 
   * @param delete
   *            受信したメールを削除するかどうか．削除する場合は，true．
   * @param enableSavingDays
   *            指定日数を超えたメールを削除するかどうか．削除する場合は，true．
   * @param savingDays
   *            メール削除の指定日数
   * @param denyReceivedMail
   *            受信済みメッセージを取り込まない場合は，true．
   * @param authReceiveFlag
   *            受信時の認証方式
   * @throws Exception
   */
  public int receive(String orgId) throws Exception {

    // POP3 サーバ上のストア
    Store pop3Store = null;
    // POP3 サーバ上のフォルダ
    POP3Folder pop3Folder = null;
    // ローカルストア
    ALFolder receiveFolder = null;
    // 新着メールを保時するリスト
    int newMailSum = 0;
    // このセッション中のメールの UID の一覧
    ArrayList newUIDL = null;
    // このセッションで保存したメールの UID の一覧
    ArrayList receivedUIDL = new ArrayList();
    // 今，受信しているメールの UID
    String nowReceivedUID = null;
    boolean overMailMaxSize = false;

    // 一通のメールの受信を完了したかのフラグ
    boolean finishedReceiving = false;

    try {
      // POP3 サーバへの接続
      Session session = getSession(rcontext.getAuthReceiveFlag());
      pop3Store = session.getStore("pop3");
      pop3Store.connect(rcontext.getPop3Host(), Integer.parseInt(rcontext
          .getPop3Port()), rcontext.getPop3UserId(), rcontext
          .getPop3UserPasswd());

      // hosting-link.ne.jp のメールサーバとの通信で
      // isConnected() を呼ぶと，メール受信不可だったため，
      // 調査が済むまで，コメントアウトした（Tadashi Komori 2005/7/31）
      // if (!pop3Store.isConnected()) {
      // // POP3 サーバへの接続失敗時の処理
      // close(pop3Store, pop3Folder, receiveFolder);
      // return RECEIVE_MSG_FAIL_CONNECT;
      // }

      // POP3 サーバ上のメールフォルダを開く
      pop3Folder = (POP3Folder) pop3Store.getFolder("INBOX");
      if (pop3Folder == null) {
        close(pop3Store, pop3Folder, receiveFolder);
        return RECEIVE_MSG_FAIL;
      }
      pop3Folder.open(Folder.READ_WRITE);

      // ローカルストアに接続
      receiveFolder = getALFolder();
      if (receiveFolder == null) {
        close(pop3Store, pop3Folder, receiveFolder);
        return RECEIVE_MSG_FAIL;
      }

      // POP3 サーバに保存されているメッセージ数を取得
      int totalMessages = pop3Folder.getMessageCount();
      if (totalMessages == 0) {
        // 新着メール数を保存する．
        receiveFolder.setNewMailNum(0);
        close(pop3Store, pop3Folder, receiveFolder);
        return RECEIVE_MSG_SUCCESS;
      }

      // 保存してある UID の一覧を取得
      List oldUIDL = receiveFolder.loadUID();

      // 受信するメールの UID の一覧を取得
      newUIDL = new ArrayList();
      Message[] messages = pop3Folder.getMessages();

      String uid = null;
      totalMessages = messages.length;
      for (int i = 0; i < totalMessages; i++) {
        uid = pop3Folder.getUID((MimeMessage) messages[i]);
        if (uid == null) {
          String[] xuidls = messages[i].getHeader("X-UIDL");
          if (xuidls != null && xuidls.length > 0) {
            uid = xuidls[0];
          } else {
            uid = ((MimeMessage) messages[i]).getMessageID();
          }
        }
        newUIDL.add(uid);
      }

      // UID の差分を取得
      BitSet retrieveFlags = new BitSet();
      if (rcontext.getDenyReceivedMail()) {
        for (int i = 0; i < totalMessages; i++) {
          if (!oldUIDL.contains(newUIDL.get(i))) {
            retrieveFlags.set(i);
          } else {
            receivedUIDL.add(newUIDL.get(i));
          }
        }
      } else {
        for (int i = 0; i < totalMessages; i++) {
          retrieveFlags.set(i);
        }
      }
      oldUIDL.clear();
      newUIDL.clear();

      // 現時点から指定日数を引いた日時を取得．
      Calendar cal = Calendar.getInstance();
      cal.setTime(new Date());
      cal.set(Calendar.DATE, cal.get(Calendar.DATE) - rcontext.getSavingDays());
      Date limitDate = cal.getTime();

      MimeMessage tmpMessage = null;
      ALMailMessage alPop3MailMessage = null;
      for (int i = 0; i < totalMessages; i++) {
        finishedReceiving = false;
        tmpMessage = (MimeMessage) pop3Folder.getMessage(i + 1);
        // 新着メールであるかを確認
        if (retrieveFlags.get(i)) {
          nowReceivedUID = pop3Folder.getUID(tmpMessage);
          if (nowReceivedUID == null) {
            String[] xuidls = tmpMessage.getHeader("X-UIDL");
            if (xuidls != null && xuidls.length > 0) {
              nowReceivedUID = xuidls[0];
            } else {
              nowReceivedUID = tmpMessage.getMessageID();
            }
          }
          // （新着メール）メールを受信し保存する．
          alPop3MailMessage = new ALPop3Message((POP3Message) tmpMessage, i + 1);
          if (tmpMessage.getSize() <= MAIL_MAX_SIZE) {
            // 受信可能なメール容量以下であれば，登録する．
            receiveFolder.saveMail(alPop3MailMessage, orgId);
            if (rcontext.getDelete()) {
              // 受信したメールを POP3 サーバ上から削除する
              tmpMessage.setFlag(Flags.Flag.DELETED, rcontext.getDelete());
            } else {
              if (rcontext.getEnableSavingDays()) {
                // 指定日数を過ぎたメールを削除する（ヘッダ Received の日付で判断する）．
                Date receivedDate = ALMailUtils.getReceivedDate(tmpMessage);
                if (receivedDate != null && receivedDate.before(limitDate)) {
                  // 受信したメールを POP3 サーバ上から削除する
                  tmpMessage.setFlag(Flags.Flag.DELETED, true);
                }
              }
            }
          } else {
            receiveFolder.saveDefectiveMail(alPop3MailMessage, orgId);
            overMailMaxSize = true;
          }
          receivedUIDL.add(nowReceivedUID);
          newMailSum++;
        }
        
        finishedReceiving = true;
      }

      // POP3 サーバとの接続を閉じる
      close(pop3Store, pop3Folder, null);
    } catch (AuthenticationFailedException ae) {
      logger.error("Exception", ae);
      // 受信済みの最新の UID の一覧を保存する．
      if (!finishedReceiving)
        receivedUIDL.remove(nowReceivedUID);
      receiveFolder.saveUID(receivedUIDL);
      receivedUIDL.clear();
      // 新着メール数を保存する．
      receiveFolder.setNewMailNum(newMailSum);
      // サーバとのコネクションを切断する．
      close(pop3Store, pop3Folder, receiveFolder);
      return RECEIVE_MSG_FAIL_AUTH;
    } catch (Exception e) {
      logger.error("Exception", e);
      // 受信済みの最新の UID の一覧を保存する．
      if (!finishedReceiving)
        receivedUIDL.remove(nowReceivedUID);
      receiveFolder.saveUID(receivedUIDL);
      receivedUIDL.clear();
      // 新着メール数を保存する．
      receiveFolder.setNewMailNum(newMailSum);
      // サーバとのコネクションを切断する．
      close(pop3Store, pop3Folder, receiveFolder);
      return RECEIVE_MSG_FAIL_EXCEPTION;
    } catch (Throwable t) {
      logger.error("Throwable", t);
      // 受信済みの最新の UID の一覧を保存する．
      if (!finishedReceiving)
        receivedUIDL.remove(nowReceivedUID);
      receiveFolder.saveUID(receivedUIDL);
      receivedUIDL.clear();
      // 新着メール数を保存する．
      receiveFolder.setNewMailNum(newMailSum);
      // サーバとのコネクションを切断する．
      close(pop3Store, pop3Folder, receiveFolder);
      return RECEIVE_MSG_FAIL_OUTOFMEMORY;
    }

    try {
      // 受信済みの最新の UID の一覧を保存する．
      receiveFolder.saveUID(receivedUIDL);
      receivedUIDL.clear();

      // 新着メール数を保存する．
      receiveFolder.setNewMailNum(newMailSum);

      // ローカルフォルダを閉じる．
      receiveFolder.close();
    } catch (Exception e) {
      logger.error("Exception", e);
      return RECEIVE_MSG_FAIL_EXCEPTION;
    } catch (Throwable t) {
      logger.error("Throwable", t);
      return RECEIVE_MSG_FAIL_OUTOFMEMORY;
    }

    if (overMailMaxSize) {
      return RECEIVE_MSG_FAIL_OVER_MAIL_MAX_SIZE;
    }
    return RECEIVE_MSG_SUCCESS;
  }

  /**
   * POP3 サーバとの接続を終了する．
   */
  public void close(Store pop3Store, Folder pop3Folder, ALFolder receiveFolder) {
    try {
      if (pop3Folder != null && pop3Folder.isOpen()) {
        pop3Folder.close(true);
      }
      if (pop3Store != null && pop3Store.isConnected()) {
        pop3Store.close();
      }
      if (receiveFolder != null) {
        receiveFolder.close();
      }
    } catch (Exception e) {
      logger.error("Exception", e);
    }
  }

  /**
   * Pop3 before SMTP 用のユーザ認証．
   * 
   * @param pop3Host
   * @param pop3Port
   * @param pop3UserId
   * @param pop3UserPasswd
   * @return
   */
  public static boolean isAuthenticatedUser(String pop3Host, String pop3Port,
      String pop3UserId, String pop3UserPasswd) {
    boolean res = false;
    Store pop3Store = null;
    try {
      // POP3 サーバへの接続
      Session session = Session
          .getDefaultInstance(System.getProperties(), null);
      pop3Store = session.getStore("pop3");
      pop3Store.connect(pop3Host, Integer.parseInt(pop3Port), pop3UserId,
          pop3UserPasswd);

      res = pop3Store.isConnected();
    } catch (Exception ex) {
      logger.error("Exception", ex);
      res = false;
    } finally {
      if (pop3Store != null && pop3Store.isConnected()) {
        try {
          pop3Store.close();
        } catch (MessagingException ex) {
          logger.error("Exception", ex);
          res = false;
        }
      }
    }
    return res;
  }

  /**
   * 新着メール数を取得する．
   * 
   * @param denyReceivedMail
   *            受信済みメッセージを取り込まない場合は，true．
   * @param authReceiveFlag
   *            受信時の認証方式
   * @return
   */
  public int getNewMailSum() {
    // POP3 サーバ上のストア
    Store pop3Store = null;
    // POP3 サーバ上のフォルダ
    POP3Folder pop3Folder = null;
    // ローカルストア
    ALFolder receiveFolder = null;

    int newMailSum = -1;
    try {
      // POP3 サーバへの接続
      Session session = getSession(rcontext.getAuthReceiveFlag());
      pop3Store = session.getStore("pop3");
      pop3Store.connect(rcontext.getPop3Host(), Integer.parseInt(rcontext
          .getPop3Port()), rcontext.getPop3UserId(), rcontext
          .getPop3UserPasswd());

      // POP3 サーバ上のメールフォルダを開く
      pop3Folder = (POP3Folder) pop3Store.getFolder("INBOX");
      if (pop3Folder == null) {
        // POP3 サーバとの接続を閉じる
        close(pop3Store, pop3Folder, receiveFolder);
        return -1;
      }
      pop3Folder.open(Folder.READ_WRITE);

      // ローカルストアに接続
      receiveFolder = getALFolder();
      if (receiveFolder == null) {
        // POP3 サーバとの接続を閉じる
        close(pop3Store, pop3Folder, receiveFolder);
        return -1;
      }

      // POP3 サーバに保存されているメッセージ数を取得
      int totalMessages = pop3Folder.getMessageCount();
      if (totalMessages == 0) {
        // POP3 サーバとの接続を閉じる
        close(pop3Store, pop3Folder, receiveFolder);
        return 0;
      }

      if (!rcontext.getDenyReceivedMail()) {
        // POP3 サーバとの接続を閉じる
        close(pop3Store, pop3Folder, receiveFolder);

        // 受信済みメッセージを取り込む場合，
        // POP3 サーバ上に存在するメール数を新着メール数として返す．
        return totalMessages;
      }

      // ローカルに保存してある UID の一覧を取得
      List oldUIDL = receiveFolder.loadUID();

      // POP3 サーバから UID を取得
      // 受信するメールの UID の一覧を取得
      ArrayList newUIDL = new ArrayList();
      Message[] messages = pop3Folder.getMessages();
      String uid = null;
      totalMessages = messages.length;
      for (int i = 0; i < totalMessages; i++) {
        uid = pop3Folder.getUID((MimeMessage) messages[i]);
        if (uid == null) {
          String[] xuidls = messages[i].getHeader("X-UIDL");
          if (xuidls != null && xuidls.length > 0) {
            uid = xuidls[0];
          } else {
            uid = ((MimeMessage) messages[i]).getMessageID();
          }
        }
        newUIDL.add(uid);
      }

      // UID の差分から新着数を取得
      newMailSum = 0;
      for (int i = 0; i < totalMessages; i++) {
        if (!oldUIDL.contains(newUIDL.get(i))) {
          newMailSum++;
        }
      }

      // POP3 サーバとの接続を閉じる
      close(pop3Store, pop3Folder, receiveFolder);
    } catch (MessagingException e) {
      logger.error("Exception", e);
      // POP3 サーバとの接続を閉じる
      close(pop3Store, pop3Folder, null);
      return -1;
    }
    return newMailSum;
  }

  /**
   * 
   * @param authReceiveFlag
   *            受信時の認証方式
   * @return
   */
  private Session getSession(int authReceiveFlag) {
    // POP3 サーバへの接続
    Properties props = new Properties();
    props.put("mail.pop3.connectiontimeout", CONNECTION_TIMEOUT);
    props.put("mail.pop3.timeout", TIMEOUT);
    if (authReceiveFlag == AUTH_RECEIVE_APOP) {
      props.put("mail.pop3.apop.enable", "true");
    }
    Session session = Session.getInstance(props, null);
    return session;
  }
}
