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

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.Enumeration;
import java.util.GregorianCalendar;
import java.util.Properties;
import java.util.Random;
import java.util.StringTokenizer;
import java.util.TimeZone;

import javax.mail.Address;
import javax.mail.MessagingException;
import javax.mail.Session;
import javax.mail.internet.AddressException;
import javax.mail.internet.InternetAddress;
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.ALAttachmentsExtractor;
import com.aimluck.eip.mail.util.ALMailUtils;
import com.aimluck.eip.mail.util.UnicodeCorrecter;
import com.aimluck.eip.services.storage.ALStorageService;
import com.sk_jp.mail.MailUtility;
import com.sk_jp.mail.MultipartUtility;

/**
 * ローカルに保存するメールを表すクラスです。 <br />
 * 
 */
public class ALLocalMailMessage extends MimeMessage implements ALMailMessage {

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

  public static final String MESSAGE_ID = "Message-ID";

  public static final String RETURN_PATH = "Return-Path";

  public static final String DELIVERED_TO = "Delivered-To";

  public static final String RECEIVED = "Received";

  public static final String DATE = "Date";

  public static final String FROM = "From";

  public static final String MIME_VERSION = "MIME-Version";

  public static final String TO = "To";

  public static final String CC = "Cc";

  public static final String BCC = "Bcc";

  public static final String SUBJECT = "Subject";

  public static final String CONTENT_TYPE = "Content-Type";

  public static final String CONTENT_TRANSFER_ENCORDING =
    "Content-Transfer-Encoding";

  public static final String X_Mailer = "X-Mailer";

  public static final String X_Mailer_Value = "Groupware Aipo";

  public static final String X_AIPO_ATTACHMENT_FILE = "X-AIPO-Attachment-File";

  /** 自身をファイルに保存するときのファイル名 */
  private String fileName = null;

  /** HTML メールのファイル名につけるカウンタ */
  // private int attachmentHtmlNum = 0;

  /**
   * コンストラクタ
   * 
   * @param source
   *          メール
   * @param fileName
   *          自身の保存先のファイル名
   * @throws MessagingException
   */
  public ALLocalMailMessage(MimeMessage source, String fileName)
      throws MessagingException {
    super(source);
    this.fileName = fileName;
  }

  /**
   * コンストラクタ
   * 
   * @param fileName
   *          自身の保存先のファイル名
   * @throws MessagingException
   */
  public ALLocalMailMessage(String fileName) throws MessagingException {
    super(Session.getDefaultInstance(new Properties()));
    this.fileName = fileName;
  }

  /**
   * コンストラクタ
   * 
   * @param session
   */
  public ALLocalMailMessage(Session session) throws MessagingException {
    super(session);
  }

  public ALLocalMailMessage(Session session, java.io.InputStream is)
      throws MessagingException {
    super(session, is);
  }

  /**
   * ファイル名を返す．
   * 
   * @return
   */
  public String getMailMassageFileName() {
    return fileName;
  }

  /**
   * メールヘッダを追加する．
   * 
   * @param line
   * @throws MessagingException
   */
  @Override
  public void addHeaderLine(String line) throws MessagingException {
    StringTokenizer st = new StringTokenizer(line, ":");
    if (!st.hasMoreTokens()) {
      return;
    }
    String key = st.nextToken();
    if (!st.hasMoreTokens()) {
      return;
    }
    String value = st.nextToken().trim();
    addHeader(key, value);
  }

  /**
   * 指定されたフォルダからメールを読み込む．
   * 
   * @param folderPath
   */
  public void readMail(String folderPath) {
    try {
      parse(ALStorageService.getFile(folderPath
        + ALStorageService.separator()
        + getMailMassageFileName()));

    } catch (Exception e) {
      logger.error("ALLocalMailMessage.readMail", e);
    }
  }

  /**
   * 指定されたフォルダにメールを保存する．
   * 
   * @param folderPath
   */
  public void saveMail(String folderPath) {
    try {
      ALStorageService.createNewFile(
        getInputStream(),
        folderPath,
        getMailMassageFileName());
    } catch (Exception e) {
      logger.error("ALLocalMailMessage.saveMail", e);
    }
  }

  /**
   * 指定されたフォルダに添付ファイルを保存する．
   * 
   * @param filePath
   * @param fileBytes
   */
  public void saveAttachmentFile(String folderPath, String fileName,
      byte[] fileBytes) {
    try {

      ALStorageService.createNewFile(
        new ByteArrayInputStream(fileBytes),
        folderPath,
        fileName);

    } catch (Exception e) {
      logger.error("ALLocalMailMessage.saveAttachmentFile", e);
    }
  }

  /**
   * メールボディ部のテキストを取得する．
   * 
   * @return
   */
  public String getBodyText() {
    String text = null;
    try {
      String contentType = this.getContentType();

      // au iPhone 対策
      this.setHeader("Content-Type", contentType
        .replace("cp932", "Windows-31J"));
      text = MultipartUtility.getFirstPlainText(this);
      this.setHeader("Content-Type", contentType);

    } catch (Exception e) {
      logger.error("ALLocalMailMessage.getBodyText", e);
    }
    return text;
  }

  /**
   * メールの全てのヘッダを取得する． エンコードを変換する．
   * 
   * @return
   */
  public String getHeader() {
    StringBuffer sb = new StringBuffer();
    try {
      Enumeration<?> enu = getAllHeaderLines();
      while (enu.hasMoreElements()) {
        String line = (String) enu.nextElement();
        sb.append(MailUtility.decodeText(line)).append(ALMailUtils.CR);
      }
    } catch (Exception e) {
      logger.error("ALLocalMailMessage.getHeader", e);
      return "";
    }
    return UnicodeCorrecter.correctToCP932(sb.toString());
  }

  /**
   * メールの全てのヘッダ情報を配列として取得する．
   * 
   * @return
   */
  public String[] getHeaderArray() {
    return ALMailUtils.getLines(getHeader());
  }

  /**
   * メールの本文を一行毎に格納した配列を取得する．
   * 
   * @return
   */
  public String[] getBodyTextArray() {
    return ALMailUtils.getLines(getBodyText());
  }

  /**
   * 添付ファイルのファイル名を配列として取得する．
   * 
   * @return
   */
  public String[] getAttachmentFileNameArray() {
    String[] filenames = null;
    ALAttachmentsExtractor h = new ALAttachmentsExtractor();
    try {
      MultipartUtility.process(this, h);
      filenames = h.getFileNames();
    } catch (Exception e) {
      logger.error("ALLocalMailMessage.getAttachmentFileNameArray", e);
      return null;
    }
    return filenames;
  }

  /**
   * メッセージ ID を独自形式にするためのオーバーライドメソッド．
   * 
   * @throws MessagingException
   */
  @Override
  protected void updateHeaders() throws MessagingException {
    super.updateHeaders();

    // メッセージ ID をセット
    setHeader(MESSAGE_ID, "<" + getMessageId() + ">");
  }

  /**
   * メッセージ ID を生成する．
   * 
   * @return
   */
  private String getMessageId() {

    Calendar cal = new GregorianCalendar();
    // 日付を表示形式に変換
    SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmssSS");
    sdf.setTimeZone(TimeZone.getDefault());
    String time = sdf.format(cal.getTime());

    Random random = new Random(cal.getTimeInMillis());
    int tmp = random.nextInt();
    int randomNumber =
      (tmp != Integer.MIN_VALUE ? Math.abs(tmp) : Math.abs(tmp + 1));

    String smtpHostName = session.getProperty(ALSmtpMailSender.MAIL_SMTP_HOST);

    StringBuffer messageId = new StringBuffer();
    messageId.append(time).append(".").append(randomNumber).append("@").append(
      smtpHostName);

    return messageId.toString();
  }

  /**
   * 件名を取得する．
   * 
   * @return
   * @throws MessagingException
   */
  @Override
  public String getSubject() throws MessagingException {
    String subject =
      UnicodeCorrecter.correctToCP932(MailUtility
        .decodeText(super.getSubject()));

    if (subject == null || subject.equals("")) {
      subject = "";
    }
    return subject;
  }

  /**
   * このメールが，HTML メールかを検証する． HTML メールの場合は，true．
   * 
   * @return
   */
  public boolean isHtmlMail() {
    try {
      // 添付ファイルの有無
      ALAttachmentsExtractor h = new ALAttachmentsExtractor();
      MultipartUtility.process(this, h);
      boolean hasAttachments = (h.getCount() > 0) ? true : false;
      return hasAttachments;
    } catch (Exception e) {
      logger.error("ALLocalMailMessage.isHtmlMail", e);
      return false;
    }
  }

  /*
   * boolean htmlMail = false; try { htmlMail = isHtmlMailSub(this); } catch
   * (MessagingException me) { htmlMail = false; } catch (IOException e) {
   * htmlMail = false; } return htmlMail; ? }
   * 
   * private boolean isHtmlMailSub(Part part) throws IOException,
   * MessagingException { if (part.isMimeType("text/plain")) { return false; }
   * else if (part.isMimeType("text/html")) { return true; } else if
   * (part.isMimeType("multipart/*")) { Multipart mp = (Multipart)
   * part.getContent(); for (int i = 0; i < mp.getCount(); i++) { if
   * (isHtmlMailSub(mp.getBodyPart(i))) { return true; } } }
   * 
   * return false; }
   * 
   * /** 添付ファイルを含んでいるかを検証する． 含んでいる場合は，true． @return
   */
  public boolean hasAttachments() {
    ALAttachmentsExtractor h = new ALAttachmentsExtractor();
    try {
      MultipartUtility.process(this, h);
      if (h.getCount() > 0) {
        return true;
      }
    } catch (Exception e) {
      logger.error("ALLocalMailMessage.hasAttachments", e);
      return false;
    }
    return false;
  }

  /**
   * 指定したインデックスのコンテンツの InputStream を取得する．
   * 
   * @param attachmentIndex
   * @return
   */
  public InputStream getInputStream(int attachmentIndex) {
    InputStream in = null;
    ALAttachmentsExtractor h = new ALAttachmentsExtractor();
    try {
      MultipartUtility.process(this, h);
      in = h.getInputStream(attachmentIndex);
    } catch (Exception e) {
      logger.error("ALLocalMailMessage.getInputStream", e);
      return null;
    }
    return in;
  }

  /**
   * 指定したインデックスの添付ファイル名を取得する．
   * 
   * @param attachmentIndex
   * @return
   */
  public String getFileName(int attachmentIndex) {
    String filename = null;
    ALAttachmentsExtractor h = new ALAttachmentsExtractor();
    try {
      MultipartUtility.process(this, h);
      filename = h.getFileName(attachmentIndex);
    } catch (Exception e) {
      logger.error("ALLocalMailMessage.getFileName", e);
      return null;
    }
    return filename;
  }

  @Override
  public int getSize() {
    byte[] b = null;
    ByteArrayOutputStream output = new ByteArrayOutputStream();
    try {
      writeTo(output);
      b = output.toByteArray();
    } catch (Exception e) {
      logger.error("ALLocalMailMessage.getSize", e);
      return -1;
    } finally {
      try {
        output.close();
      } catch (IOException ioee) {
        return -1;
      }
    }
    return b.length;
  }

  /**
   * メールの送信日時を取得する．
   * 
   * @return
   * @throws MessagingException
   */
  @Override
  public Date getSentDate() throws MessagingException {
    return MailUtility.parseDate(getHeader(DATE, null));
  }

  /**
   * 受信したメールの TO，CC，BCC のフォーマットが RFC に則っていない場合には， 独自処理で対処する： ・「
   * <user@domain」や「user@domain>」のように片方のカッコのみ見つかった場合の処理 ・「 < <user@domain>」や「
   * <user@domain>>」のようにカッコの対が揃っていない場合の処理
   * 
   * @param recipienttype
   * @return
   * @throws MessagingException
   */
  @Override
  public Address[] getRecipients(javax.mail.Message.RecipientType recipienttype)
      throws MessagingException {

    // RFC に則っているかを検証する．
    String recipients =
      MailUtility.decodeText(this.getHeader(recipienttype.toString(), null));
    if (recipients == null) {
      return super.getRecipients(recipienttype);
    }

    StringTokenizer st = new StringTokenizer(recipients, ",;");
    String token = null;
    boolean found = false;
    while (st.hasMoreTokens()) {
      token = st.nextToken();
      if ((token.indexOf('<') >= 0 && token.indexOf('>') == -1)
        || (token.indexOf('<') == -1 && token.indexOf('>') >= 0)
        // 「<user@domain」や「user@domain>」のように
        // 片方のカッコのみ見つかった場合の処理
        || (token.indexOf('(') >= 0 && token.indexOf('>') == -1)
        || (token.indexOf('(') == -1 && token.indexOf(')') >= 0)) {
        // 「foo) <user@domain>」や「(foo <user@domain>」のように
        // 片方のカッコのみ見つかった場合の処理
        found = true;
      } else {
        if ((token.indexOf('<') >= 0 && token.indexOf('<') != token
          .lastIndexOf('<'))
          || (token.indexOf('>') >= 0 && token.indexOf('>') != token
            .lastIndexOf('>'))
          // 「<<user@domain>」や「<user@domain>>」のように
          // カッコの対が揃っていない場合の処理
          || (token.indexOf('(') >= 0 && token.indexOf(')') != token
            .lastIndexOf('('))
          || (token.indexOf(')') >= 0 && token.indexOf(')') != token
            .lastIndexOf(')'))) {
          // 「((foo) <user@domain>」や「(foo)) <user@domain>」のように
          // カッコの対が揃っていない場合の処理
          found = true;
        }
      }
    }

    if (found) {
      int index = 0;
      st = new StringTokenizer(recipients, ",;");
      Address[] addresses = new InternetAddress[st.countTokens()];
      while (st.hasMoreTokens()) {
        token = st.nextToken();
        try {
          addresses[index] = new InternetAddress(token, false);
        } catch (AddressException ae) {
          addresses[index] = new InternetAddress();
          if (index > 0 && token.contains("\r\n\t")) {
            token = token.substring(4);
          }
          ((InternetAddress) addresses[index]).setAddress(token);
        }
        index++;
      }
      return addresses;
    } else {
      return super.getRecipients(recipienttype);
    }
  }

  /**
   * TO，CC，BCC のフォーマットをチェックしない場合は strict = false
   * 
   * @param recipienttype
   * @param strict
   * @return
   * @throws MessagingException
   */
  public Address[] getRecipients(
      javax.mail.Message.RecipientType recipienttype, boolean strict)
      throws MessagingException {
    if (strict) {
      return getRecipients(recipienttype);
    } else {
      Address[] addresses;
      try {
        addresses = super.getRecipients(recipienttype);
      } catch (AddressException e) {
        String recipients =
          MailUtility
            .decodeText(this.getHeader(recipienttype.toString(), null));
        if (recipients == null) {
          return super.getRecipients(recipienttype);
        }
        int index = 0;
        String token = null;
        StringTokenizer st = new StringTokenizer(recipients, ",;");
        addresses = new InternetAddress[st.countTokens()];
        while (st.hasMoreTokens()) {
          token = st.nextToken();
          try {
            addresses[index] = new InternetAddress(token, false);
          } catch (AddressException ae) {
            addresses[index] = new InternetAddress();
            ((InternetAddress) addresses[index]).setAddress(token);
          }
          index++;
        }
      }
      return addresses;
    }
  }

  @Override
  public void clearContents() {

  }

}
