/*
 * 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/>.
 */

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.activation.DataHandler;
import javax.mail.Address;
import javax.mail.MessagingException;
import javax.mail.Session;
import javax.mail.internet.AddressException;
import javax.mail.internet.ContentType;
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.GB18030DataSource;
import com.aimluck.eip.mail.util.SJISDataSource;
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;

  // add start
  /** 開封確認送信済みフラグ */
  private boolean notificationFlg = false;

  /** メールID */
  private int mailId;

  // add end

  /** 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("Exception", e);
    }
  }

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

  /**
   * メールボディ部のテキストを取得する．
   * 
   * @return
   */
  public String getBodyText() {
    String text = null;
    try {
      // change start 2011/12/20 IBM拡張文字対応
      // text =
      // UnicodeCorrecter.correctToCP932(MultipartUtility
      // .getFirstPlainText(this));

      DataHandler dh = this.getDataHandler();
      String old_contentType = dh.getContentType();
      String contentType = old_contentType;
      // add start 2次開発 要件 No.20 キャラクタセットCP932メール対応
      String[] transferEncodings = this.getHeader("Content-Transfer-Encoding");
      String transferEncodingSymbol = null;
      if (transferEncodings != null && transferEncodings.length > 0) {
        transferEncodingSymbol = transferEncodings[0];
      }
      // add end

      // change start 2012.1.23 受入障害 No.244

      // if (contentType.indexOf("multipart") == -1) {
      // contentType = contentType.toLowerCase();
      // contentType = contentType.replaceAll("iso-2022-jp", "Windows-31J");
      // }
      String judgeMultipart = old_contentType.toLowerCase();
      if (judgeMultipart.indexOf("multipart") == -1) {
        contentType = judgeMultipart.replaceAll("iso-2022-jp", "Windows-31J");

        // add start 2次開発 要件 No.20 キャラクタセットCP932メール対応
        contentType = contentType.toLowerCase();
        contentType = contentType.replaceAll(ALMailUtils.CP932, ALMailUtils.WINDOWS_31J);
        // add end

      }
      // change end 2012.1.23 受入障害 No.244

      // change start 2012.1.17 受入障害対応No.216
      // this.setDataHandler(new DataHandler(new SJISDataSource(this
      // .getInputStream(), contentType)));
      // text =
      // UnicodeCorrecter.correctToCP932(ALMailUtils.parseContent(this
      // .getContent()));
      // this.setDataHandler(dh);
      // this.setHeader(CONTENT_TYPE, old_contentType);

      if (ALMailUtils.isCharsetUTF7(new ContentType(this.getContentType()))) {
        // UTF-7の場合は自前デコーダーで処理
        this.setDataHandler(dh);
        this.setHeader(CONTENT_TYPE, old_contentType);
        // change start 2次開発 要件No.23 テキスト部を持たないメール（HTMLメール）対応
        // text = ALMailUtils.getUTF7BodyText(this);
        text = ALMailUtils.parseContent(ALMailUtils.getUTF7BodyText(this), this);
        // change end 2次開発 要件No.23 テキスト部を持たないメール（HTMLメール）対応
      } else if (ALMailUtils.isChineseMail(new ContentType(this.getContentType()))) {
        // 中文メールの場合は自前デコーダーで処理
        // change start 2012.2.7 base64エンコード形式の中国語メール対応
        // base64形式に対応するためbase
        // this.setDataHandler(dh);
        // this.setHeader(CONTENT_TYPE, old_contentType);
        // text =
        // ALMailUtils.getChineseMailBodyText(new ContentType(this
        // .getContentType()), this);

        this.setDataHandler(new DataHandler(new GB18030DataSource(this.getInputStream(), contentType)));
        text = UnicodeCorrecter.correctToCP932(ALMailUtils.parseContent(this.getContent(), this));
        this.setDataHandler(dh);
        this.setHeader(CONTENT_TYPE, old_contentType);
        // change end
      } else {
        this.setDataHandler(new DataHandler(new SJISDataSource(this.getInputStream(), contentType)));
        text = UnicodeCorrecter.correctToCP932(ALMailUtils.parseContent(this.getContent(), this));
        this.setDataHandler(dh);
        this.setHeader(CONTENT_TYPE, old_contentType);
      }
      // change end 2012.1.17 受入障害対応No.216

      // change end

      // add start 2次開発 要件 No.20 キャラクタセットCP932メール対応
      if (transferEncodingSymbol != null) {
        this.setHeader("Content-Transfer-Encoding", transferEncodingSymbol);
      }
      // add end

      // FirstPlainPartExtractor h = new FirstPlainPartExtractor();
      // MultipartUtility.process(this, h);
      // text = UnicodeCorrecter.correctToCP932(h.getText());
    } catch (Exception e) {
      // logger.error("Exception", e);
      logger.error("メールボディ部のテキスト取得に失敗しました", e);
    }
    return text;
  }

  /**
   * メールの全てのヘッダを取得する． エンコードを変換する．
   * 
   * @return
   */
  public String getHeader() {
    StringBuffer sb = new StringBuffer();
    try {
      Enumeration<?> enu = getAllHeaderLines();
      while (enu.hasMoreElements()) {
        String line = (String) enu.nextElement();
        // change start 2011/12/21 IBM拡張文字対応
        // sb.append(MailUtility.decodeText(line)).append(ALMailUtils.CR);
        // ヘッダデコード処理
        sb.append(ALMailUtils.decodeText(line)).append(ALMailUtils.CR);
        // change end
      }
    } catch (Exception e) {
      logger.error("Exception", 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("Exception", 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("Exception", 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("Exception", 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("Exception", 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("Exception", 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("Exception", 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 = this.getHeader(recipienttype.toString(), null);
    if (recipients == null) {
      return super.getRecipients(recipienttype);
    }

    StringTokenizer st = new StringTokenizer(recipients, ",");
    String token = null;
    boolean found = false;
    // add start by motegi 受入テスト障害248
    boolean foundUndisclosedRecipient = false;
    // add end
    while (st.hasMoreTokens()) {
      token = st.nextToken();
      if ((token.indexOf('<') >= 0 && token.indexOf('>') == -1) || (token.indexOf('<') == -1 && token.indexOf('>') >= 0)) {
        // 「<user@domain」や「user@domain>」のように
        // 片方のカッコのみ見つかった場合の処理
        found = true;
      } else {
        if ((token.indexOf('<') >= 0 && token.indexOf('<') != token.lastIndexOf('<'))
          || (token.indexOf('>') >= 0 && token.indexOf('>') != token.lastIndexOf('>'))) {
          // 「<<user@domain>」や「<user@domain>>」のように
          // カッコの対が揃っていない場合の処理
          found = true;
        }
      }
      // add start by motegi 受入テスト障害248
      String tmp = token.toLowerCase();
      if (tmp.contains("undisclosed-recipient:;") && !tmp.contains("@")) {
        foundUndisclosedRecipient = true;
      }
      // add end
    }

    // add start by motegi 受入テスト障害248
    if (foundUndisclosedRecipient) {
      // ``Undisclosed-Recipient:;''用の処理
      Address[] addressesUndisclosedRecipient = new InternetAddress[1];
      addressesUndisclosedRecipient[0] = new InternetAddress("Undisclosed-Recipient:;", false);
      return addressesUndisclosedRecipient;
      // Address[] addressesUndisclosedRecipient = new InternetAddress[0];
      // return addressesUndisclosedRecipient;
    }
    // add end

    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();
          ((InternetAddress) addresses[index]).setAddress(token);
        }
        index++;
      }
      return addresses;
    } else {
      // change start 運用フェーズ課題・障害台帳No.143
      // return super.getRecipients(recipienttype);
      Address[] works = null;
      try {
        works = super.getRecipients(recipienttype);
        if (works != null) {
          // メールアドレスに不正な形式が含まれるかチェックする。
          for (Address wk : works) {
            new InternetAddress(((InternetAddress) wk).getAddress(), true);
          }
        }
        // 問題無ければそのまま返却
        return works;
      } catch (Exception e) {
        // 不正なフォーマットのアドレスが含まれ、JavaMailで処理できない場合
        String rawStr = this.getHeader(recipienttype.toString(), null);
        st = new StringTokenizer(rawStr, ",");
        works = new InternetAddress[st.countTokens()];
        int i = 0;
        while (st.hasMoreTokens()) {
          token = st.nextToken();
          token = token.replaceAll("[\n\r\t]", "");
          try {
            // 改めて１件毎にInternetAddressに変換する。
            works[i] = new InternetAddress(token, false);
          } catch (Exception ge) {
            // 不正なフォーマット用の処理
            try {
              works[i] = new InternetAddress();
              int delim = token.lastIndexOf("<");
              if (delim > -1) {
                String name = token.substring(0, delim);
                String addr = token.substring(delim + 1, token.length() - 1);
                ((InternetAddress) works[i]).setPersonal(ALMailUtils.decodeText(name));
                ((InternetAddress) works[i]).setAddress(addr);
              } else {
                // "<"が含まれないため、表示名無しとなる。
                ((InternetAddress) works[i]).setAddress(token);
              }
            } catch (Exception e1) {
              logger.warn("メールアドレスがデコードできないため、そのまま出力します[" + token + "]", e1);
              works[i] = new InternetAddress();
              ((InternetAddress) works[i]).setAddress(token);
            }
          }
          i++;
        }
      }
      return works;
      // change end
    }
  }

  @Override
  public void clearContents() {

  }

  // add start 開封確認対応
  /**
   * 開封通知送信先アドレスの取得
   * <p>
   * 
   * @return
   * @throws MessagingException
   * @throws MessagingException
   */
  public String getDispositionNotificationTo() throws MessagingException {
    String notificationTo = getHeader("Disposition-Notification-To", null);
    String contentType = getContentType();

    if (notificationTo == null || contentType.contains("disposition-notification")) {
      return null;
    }

    // // 送信先アドレスを"<"で分割
    // String[] address = notificationTo.split("<");
    // // 送信先アドレスに"<"が含まれていない場合、そのまま送信先アドレスを返す
    // int addressSplitLength = address.length;
    // if (addressSplitLength == 1) {
    // return notificationTo;
    // }
    // // 送信先アドレスに"<"が含まれている場合
    // // String tmpAddress = address.toString();
    // String tmpAddress = address[1];
    // // さらに">"で分割し、その１個目を返す
    // address = tmpAddress.split(">");
    // return address[0];

    return notificationTo;
  }

  /**
   * 開封確認送信済みフラグを返す。
   * <p>
   * 
   * @return 開封確認送信済みフラグ
   */
  public boolean isNotificationFlg() {
    return notificationFlg;
  }

  /**
   * 開封確認送信済みフラグをセットする。
   * <p>
   * 
   * @param notificationFlg
   *            開封確認送信済みフラグ
   */
  public void setNotificationFlg(boolean notificationFlg) {
    this.notificationFlg = notificationFlg;
  }

  /**
   * メールIDを返す。
   * <p>
   * 
   * @return メールID
   */
  public int getMailId() {
    return mailId;
  }

  /**
   * 開封確認送信済みフラグをセットする。
   * <p>
   * 
   * @param mailId
   *            メールID
   */
  public void setMailId(int mailId) {
    this.mailId = mailId;
  }

  // add end

  // add start 代理送信設定取得処理追加 2011/12/8
  /**
   * 代理送信アカウント情報の取得
   * <p>
   * 
   * @return
   * @throws MessagingException
   */
  public String getDraftAccountId() throws MessagingException {
    String draftAccountId = getHeader("X-Aipo-Draft", null);

    if (draftAccountId == null) {
      return null;
    }
    return draftAccountId;
  }

  // add end

  // add start 2011.1214 受入テスト障害109
  /**
   * 下書きメールタイプの取得
   * <p>
   * 
   * @return
   * @throws MessagingException
   */
  public int getDraftMailType() throws MessagingException {
    String draftMailType = getHeader("X-Aipo-Draft-Mail-Type", null);

    if (draftMailType == null) {
      return 0;
    }
    return Integer.valueOf(draftMailType);
  }

  /**
   * 下書きメールの元になったメールが入っていたタブ取得
   * <p>
   * 
   * @return
   * @throws MessagingException
   */
  public String getDraftMailTub() throws MessagingException {
    String draftMailTub = getHeader("X-Aipo-Draft-Mail-Tub", null);

    if (draftMailTub == null) {
      return null;
    }
    return draftMailTub;
  }

  /**
   * 下書きメールの元になったメールのID取得
   * <p>
   * 
   * @return
   * @throws MessagingException
   */
  public String getDraftMailParentId() throws MessagingException {
    String draftMailParentId = getHeader("X-Aipo-Draft-Mail-Parent-Id", null);

    if (draftMailParentId == null) {
      return null;
    }
    return draftMailParentId;
  }

  // add end
}
