/*
 * 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.Date;
import java.util.Iterator;
import java.util.Map;
import java.util.Properties;

import javax.activation.DataHandler;
import javax.activation.FileDataSource;
import javax.mail.AuthenticationFailedException;
import javax.mail.Message;
import javax.mail.MessagingException;
import javax.mail.Multipart;
import javax.mail.Session;
import javax.mail.Transport;
import javax.mail.internet.AddressException;
import javax.mail.internet.InternetAddress;
import javax.mail.internet.MimeBodyPart;
import javax.mail.internet.MimeMultipart;

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

import com.aimluck.eip.mail.util.ALMailUtils;
import com.sk_jp.mail.MailUtility;
import com.sun.mail.smtp.SMTPTransport;

/**
 * メール送信（SMTP）を操作する抽象クラスです。 <br />
 * 
 */
public abstract class ALSmtpMailSender implements ALMailSender {

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

  /** <code>AUTH_SEND_NONE</code> 送信時の認証方式（認証なし） */
  public static final int AUTH_SEND_NONE = 0;

  /** <code>AUTH_SEND_POP_BEFORE_SMTP</code> 送信時の認証方式（POP before SMTP） */
  public static final int AUTH_SEND_POP_BEFORE_SMTP = 1;

  /** <code>AUTH_SEND_SMTP_AUTH</code> 送信時の認証方式（SMTP 認証） */
  public static final int AUTH_SEND_SMTP_AUTH = 2;

  /** メール送信時の処理結果（送信に成功した） */
  public static final int SEND_MSG_SUCCESS = 0;

  /** メール送信時の処理結果（送信に失敗した） */
  public static final int SEND_MSG_FAIL = 1;

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

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

  /** メール送信時の処理結果（SMTP認証の認証失敗で送信に失敗した） */
  public static final int SEND_MSG_FAIL_SMTP_AUTH = 4;

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

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

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

  /** SMTP サーバ */
  public static final String MAIL_SMTP_HOST = "mail.smtp.host";

  /** SMTP サーバのポート番号 */
  public static final String MAIL_SMTP_PORT = "mail.smtp.port";

  /** SMTP サーバとの接続時のタイムアウト */
  public static final String MAIL_SMTP_CONNECTION_TIMEOUT = "mail.stmp.connectiontimeout";

  /** SMTP サーバとの接続後のタイムアウト */
  public static final String MAIL_SMTP_TIMEOUT = "mail.stmp.timeout";

  /** 文字コード（ISO-2022-JP） */
  public static final String CHARSET_ISO2022JP = "iso-2022-jp";

  /** SMTP サーバへの接続情報 */
  protected Properties smtpServerProp = null;

  /** 送信用セッション */
  protected Session session = null;

  protected ALSmtpMailSenderContext scontext = null;

  public ALSmtpMailSender(ALMailSenderContext scontext) {
    this.scontext = (ALSmtpMailSenderContext) scontext;
  }

  /**
   * 新規作成のメールを取得します。
   * 
   * @param mcontext
   * @return
   */
  private ALLocalMailMessage createMessage(ALSmtpMailContext mcontext) {
    System.setProperty("mail.mime.charset", "ISO-2022-JP");
    System.setProperty("mail.mime.decodetext.strict", "false");

    ALLocalMailMessage msg = null;
    smtpServerProp = new Properties();
    smtpServerProp.setProperty(MAIL_SMTP_HOST, scontext.getSmtpHost());
    smtpServerProp.setProperty(MAIL_SMTP_PORT, scontext.getSmtpPort());
    smtpServerProp.put(MAIL_SMTP_CONNECTION_TIMEOUT, CONNECTION_TIMEOUT);
    smtpServerProp.put(MAIL_SMTP_TIMEOUT, TIMEOUT);
    smtpServerProp.setProperty("mail.mime.address.strict", "false");
    smtpServerProp.put("mail.smtp.localhost", "localhost");

    // SMTP サーバのアドレスをセット
    if (scontext.getAuthSendFlag() == AUTH_SEND_SMTP_AUTH
        && scontext.getAuthSendUserId() != null
        && !"".equals(scontext.getAuthSendUserId())
        && scontext.getAuthSendUserPassword() != null
        && !"".equals(scontext.getAuthSendUserPassword())) {
      smtpServerProp.put("mail.smtp.auth", "true");
      session = Session.getInstance(smtpServerProp, new ALSmtpAuth(scontext
          .getAuthSendUserId(), scontext.getAuthSendUserPassword()));
    } else {
      session = Session.getInstance(smtpServerProp, null);
    }

    // デバックモード
    // session.setDebug(true);

    try {
      // メッセージを生成
      msg = new ALLocalMailMessage(session);
      // 送信元メールアドレスと送信者名をセット
      msg.setFrom(new InternetAddress(mcontext.getFrom(), ALMailUtils
          .encodeWordJIS(mcontext.getName())));
      // メールのあて先（to）をセット
      if (mcontext.getTo() == null)
        throw new MessagingException();
      setRecipient(msg, Message.RecipientType.TO, mcontext.getTo());
      // メールのあて先（cc）をセット
      if (mcontext.getCc() != null) {
        setRecipient(msg, Message.RecipientType.CC, mcontext.getCc());
      }
      // メールのあて先（bcc）をセット
      if (mcontext.getBcc() != null) {
        setRecipient(msg, Message.RecipientType.BCC, mcontext.getBcc());
      }
      // メールの形式をセット
      msg.setHeader(ALLocalMailMessage.CONTENT_TYPE, "text/plain");
      msg.setHeader(ALLocalMailMessage.CONTENT_TRANSFER_ENCORDING, "7bit");
      msg.setHeader(ALLocalMailMessage.X_Mailer,
          ALLocalMailMessage.X_Mailer_Value);
      // メールの件名をセット
      msg.setSubject(ALMailUtils.encodeWordJIS(mcontext.getSubject()));
      // メールの送信日時をセット
      msg.setSentDate(new Date());

      // 追加ヘッダをセットする
      Map headers = mcontext.getAdditionalHeaders();
      if (headers != null) {
        synchronized (headers) {
          String key = null;
          String value = null;
          Map.Entry entry = null;
          for (Iterator i = headers.entrySet().iterator(); i.hasNext();) {
            entry = (Map.Entry) i.next();
            key = (String) entry.getKey();
            value = (String) entry.getValue();
            msg.setHeader(key, value);
          }
        }
      }

      if (mcontext.getFilePaths() == null) {
        // メールの本文をセット
        ALMailUtils.setTextContent(msg, mcontext.getMsgText());
      } else {
        String[] checkedFilePaths = ALMailUtils.checkFilesExistance(mcontext
            .getFilePaths());
        int checkedFilePathsLength = checkedFilePaths.length;
        if (checkedFilePathsLength <= 0) {
          // MultiPart にせず，メールの本文をセット
          ALMailUtils.setTextContent(msg, mcontext.getMsgText());
        } else {
          // 複数のボディを格納するマルチパートオブジェクトを生成
          Multipart multiPart = new MimeMultipart();

          // テキストのボディパートを作成
          MimeBodyPart mimeText = new MimeBodyPart();
          // メールの内容を指定
          mimeText.setText(mcontext.getMsgText(), CHARSET_ISO2022JP);
          // １つ目のボディパートを追加
          multiPart.addBodyPart(mimeText);

          // 添付ファイルのボディパートを作成
          MimeBodyPart mimeFile = null;
          for (int i = 0; i < checkedFilePathsLength; i++) {
            mimeFile = new MimeBodyPart();
            mimeFile.setDataHandler(new DataHandler(new FileDataSource(
                checkedFilePaths[i])));
            MailUtility.setFileName(mimeFile, ALMailUtils
                .getFileNameFromText(checkedFilePaths[i]), "ISO-2022-JP", null);

            // 添付ファイルをボディパートに追加
            multiPart.addBodyPart(mimeFile);
          }

          // マルチパートオブジェクトをメッセージに設定
          msg.setContent(multiPart);
        }
      }

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

    return msg;
  }

  /**
   * SMTP サーバへメールを送信する．
   * 
   * @param to
   * @param cc
   * @param bcc
   * @param from
   * @param name
   * @param subject
   * @param msgText
   * @param filePaths
   * @return
   */
  public int send(ALMailContext context) {
    try {
      ALSmtpMailContext mcontext = (ALSmtpMailContext) context;

      ALLocalMailMessage msg = createMessage(mcontext);

      if (msg == null)
        return SEND_MSG_FAIL;

      int mailSize = msg.getSize();
      if (mailSize > MAIL_MAX_SIZE) {
        // メール容量のチェック
        return SEND_MSG_OVER_MAIL_MAX_SIZE;
      }

      if (scontext.getAuthSendFlag() == AUTH_SEND_NONE) {
        Transport.send(msg);
      } else if (scontext.getAuthSendFlag() == AUTH_SEND_POP_BEFORE_SMTP) {
        // POP before SMTP を実行する．
        // 認証のみ検証する．メールは受信しない．
        boolean success = ALPop3MailReceiver.isAuthenticatedUser(scontext
            .getPop3Host(), scontext.getPop3Port(), scontext.getPop3UserId(),
            scontext.getPop3UserPasswd());
        if (!success) {
          return AUTH_SEND_POP_BEFORE_SMTP;
        }

        Transport.send(msg);
      } else if (scontext.getAuthSendFlag() == AUTH_SEND_SMTP_AUTH) {
        Transport transport = session.getTransport("smtp");
        SMTPTransport smtpt = (SMTPTransport) transport;
        smtpt.setSASLRealm("localhost"); // [SASLレルム]
        smtpt.connect(scontext.getSmtpHost(), scontext.getAuthSendUserId(),
            scontext.getAuthSendUserPassword());
        smtpt.sendMessage(msg, msg.getAllRecipients());

        smtpt.close();
      }

      // メッセージの保存
      ALFolder sendFolder = getALFolder();
      sendFolder.saveMail(msg, null);
    } catch (AuthenticationFailedException ex) {
      logger.error("Exception", ex);
      return SEND_MSG_FAIL_SMTP_AUTH;
    } catch (Exception ex) {
      logger.error("Exception", ex);
      return SEND_MSG_FAIL;
    } catch (Throwable e) {
      logger.error("Throwable", e);
      return SEND_MSG_FAIL;
    }

    return SEND_MSG_SUCCESS;
  }

  abstract protected ALFolder getALFolder();

  /**
   * メールの宛名をセットする．
   * 
   * @param msg
   * @param recipientType
   * @param addrString
   * @throws AddressException
   * @throws MessagingException
   */
  private void setRecipient(Message msg, Message.RecipientType recipientType,
      String[] addrString) throws AddressException, MessagingException {
    if (addrString == null)
      return;
    int addrStringLength = addrString.length;
    InternetAddress[] address = new InternetAddress[addrStringLength];
    for (int i = 0; i < addrStringLength; i++) {
      address[i] = ALMailUtils.getInternetAddress(addrString[i]);
    }
    msg.setRecipients(recipientType, address);
  }

}
