/*
 * Copyright 2007 Kickmogu Co. Ltd. All rights reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 
 * either express or implied. See the License for the specific language
 * governing permissions and limitations under the License.
 */



/*
 * 作成日: 2006/11/28
 *
 */
package com.kickmogu.rhythm.mail.ws;

import java.io.*;
import java.util.*;
import java.net.URLDecoder;
import java.util.regex.*;

import com.kickmogu.*;
import com.kickmogu.rhythm.mail.*;
import com.kickmogu.rhythm.core.BaseResult;
import com.kickmogu.rhythm.core.MailSetting;
import com.kickmogu.rhythm.core.Member;
import com.kickmogu.rhythm.core.dao.MailSettingDao;
import com.kickmogu.s2.S2Util;

import javax.mail.*;
import javax.mail.internet.*;
import javax.activation.*;

import org.apache.commons.io.FileUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.log4j.Category;
import javax.mail.internet.MimeUtility;
import javax.mail.util.ByteArrayDataSource;

public class MailWs {
	static Category log = Category.getInstance(MailWs.class.getName());

	static public final String OriginalMessageIdPrefix = "ORG\u0000_";
	MailSettingDao mailsettingdao = (MailSettingDao) S2Util.getS2Container().getComponent(MailSettingDao.class);
	
	Session imap_session = null;
	Store imap_store = null;
	
	public MailSetting getMailSetting(int memberId, int settingId) {
		return mailsettingdao.getMailSetting(memberId, settingId);
	}
	
	private void listFolders(Folder target, String current, StringBuffer resultXml) throws MessagingException {
		Folder[] folders = target.list("%");
		for (int i = 0; i < folders.length; i++) {
			resultXml.append("<folder label=\"" + folders[i].getName() + "\""
				+ "path=\"" + current + folders[i].getName() + "\">\n");
			listFolders(folders[i], current + folders[i].getName() + ".", resultXml);
			resultXml.append("</folder>\n");
		}
	}
	
	public BaseResult listFolders(Member loginMember, int mailSettingId) throws MessagingException {
		BaseResult result = new BaseResult();
		if (!loginMember.isLogined(result)) {
			return result;
		}
		
		Folder inbox = getTargetFolder(loginMember, mailSettingId, "INBOX");
		//Folder[] folders = inbox.list("%");
		//String[] retFolder = new String[folders.length + 1];
		StringBuffer resultXml = new StringBuffer();
		resultXml.append("<folder label=\"INBOX\" path=\"INBOX\">\n");
		listFolders(inbox, "", resultXml);
		resultXml.append("</folder>\n");
		/*
		retFolder[0] = "INBOX";
		for (int i = 0; i < folders.length; i++) {
			retFolder[i + 1] = "\t" + folders[i].getName();
		}
		*/
		result.setResult(resultXml.toString());
		return result;
	}

	private Store getIMAPStore(String host, String user, String password) throws MessagingException {
		Properties props = System.getProperties();
		imap_session = Session.getDefaultInstance(props, null);
		imap_store = imap_session.getStore("imap");
		imap_store.connect(host, user, password);
		return imap_store;
	}

	public Folder getInbox(String host, String user, String password) throws MessagingException {
		Store store = getIMAPStore(host, user, password);
	    Folder[] root = null;
		root = store.getDefaultFolder().list("INBOX");
		return root[0];
	}
	
	public String getAttachFilename(String attachName) throws UnsupportedEncodingException {
	    if (attachName.indexOf(";") > -1) {
	        String[] attachParts = attachName.split(";");
	        StringBuffer fileName = new StringBuffer();
	        String encType = "ISO-2022-JP";
	        for (int ai = 1; ai < attachParts.length; ai++) {
	            String[] fnameParts = attachParts[ai].split("=");
	            String[] encdParts = fnameParts[1].split("''");
	            if (encdParts.length == 1) {
	                fileName.append(encdParts[0]);
	            } else if (encdParts.length == 2) {
	                encType = encdParts[0];
	                fileName.append(encdParts[1]);
	            }
	        }
	        return URLDecoder.decode(fileName.toString(), encType);
	    } else {
	        return decodeMime(attachName);
	    }
	}

	public String decodeMime(String src) throws UnsupportedEncodingException {
    	if (src == null) {
    		return null;
    	}
    	Pattern mime_regex = Pattern.compile("(=\\?.+\\?B\\?.+\\?=)");
		int cnt = 0;
		Matcher matcher = mime_regex.matcher(src);
		while (matcher.matches()) {
			String mime = matcher.group(1);
			try {
				src = matcher.replaceFirst(MimeUtility.decodeText(mime));
			} catch (Exception ex) {
				mime = mime.replace("=\\", "");
				mime = mime.replace("\\?=", "");
				src = matcher.replaceFirst(mime);
			}
			if (cnt++ > 20)
				break;				// can not decode
			matcher = mime_regex.matcher(src);
		}
		if (src.indexOf(0x1b) >= 0)
			src = new String(src.getBytes("8859_1"), "ISO2022JP");
		return src;
	}

    private static String getTextMessage(Object content)
			throws javax.mail.MessagingException, java.io.IOException {
		if (content instanceof String) {
			return content.toString().indexOf(0x1b) >= 0 
				? new String(content.toString().getBytes("8859_1"), "ISO2022JP") : content.toString();
		} else {
			Multipart part = (Multipart) content;
			for (int i = 0; i < part.getCount(); i++) {
				BodyPart body = part.getBodyPart(i);
				String ctype = body.getContentType().toUpperCase();
				if (ctype.startsWith("TEXT/PLAIN"))
					return body.getContent().toString().indexOf(0x1b) >= 0 
						? new String(body.getContent().toString().getBytes("8859_1"), "ISO2022JP") : body.getContent().toString();
				if (body.getContent() instanceof Multipart) {
					return getTextMessage(body.getContent());
				}
			}
		}
		return null;
	}
    
    public Folder getTargetFolder(Member loginMember, int mailSettingId, String folderName) throws MessagingException {
    	MailSetting setting = getMailSetting(loginMember.getId(), mailSettingId);
		Folder inbox = getInbox(setting.getImapHost(), setting.getImapLoginid() , setting.getImapPassword());
		Folder target = null;
		if (folderName.equals("INBOX")) {
			target = inbox;
		} else {
			target = inbox.getFolder(folderName);
		}
		return target;
    }
    
    private String[] address2Str(Address[] addrs) throws UnsupportedEncodingException {
        if (addrs == null)
            return null;
        String[] results = new String[addrs.length];
        for (int i = 0; i < addrs.length; i++) {
        	try {
        		results[i] = MimeUtility.decodeText(addrs[i].toString());
		    } catch (Exception ex) {
		    	results[i] = addrs[i].toString();
		    }
        }
        return results;
    }
    
    static public String getOriginalMessageId(Message msg) throws MessagingException {
    	return OriginalMessageIdPrefix + msg.getFolder().getFullName() + ":" + msg.getMessageNumber() + ":" + msg.getHeader("Date");
    }
    
    private void setMsgId(Mail mail, Message msg) throws MessagingException {
	     if (msg.getHeader("Message-ID") != null 
	    		 && !StringUtils.isEmpty(msg.getHeader("Message-ID")[0])) {
	    	 mail.setMsgId(msg.getHeader("Message-ID")[0]);
	     } else {
	    	 mail.setMsgId(getOriginalMessageId(msg));
	     }
	     
    }
    
    public Mail getMessage2Mail(int idx, Message msg, boolean containBody) throws MessagingException, UnsupportedEncodingException, IOException {
		 Mail mail = new Mail();
		 mail.setIdx(idx);
		 mail.setSubject("");
	     try {
	    	 mail.setSubject(decodeMime(msg.getSubject()));
	     } catch (Exception ex) {
	    	 mail.setSubject(msg.getSubject());
	     }				 

	     mail.setFromaddr(address2Str(msg.getFrom()));
	     mail.setCcaddr(address2Str(msg.getRecipients(Message.RecipientType.CC)));
	     mail.setBccaddr(address2Str(msg.getRecipients(Message.RecipientType.BCC)));
	     mail.setReplyto(address2Str(msg.getReplyTo()));
	     
	     if (containBody) {
		     try {
		         mail.setBody(getTextMessage(msg.getContent()));
		     } catch (Exception ex) {
		    	 mail.setBody("");
		     }
		     try {
			     List<AttachFile> attachFiles = new ArrayList<AttachFile>();
			 	 if (!(msg.getContent() instanceof String)) {
			 		 Multipart part = (Multipart) msg.getContent();
			 		 for (int i = 0; i < part.getCount(); i++) {
			 			 BodyPart body = part.getBodyPart(i);
			 			 if (!body.getContentType().toUpperCase().startsWith("TEXT/")) {
			 				 AttachFile af = new AttachFile();
			 				 af.setUrl("folder=" + msg.getFolder().getName() + "&msgNo=" + msg.getMessageNumber() + "&partNo=" + i);
			 				 af.setName(getAttachFilename(FileDownloader.getFileName(body)));
			 				 attachFiles.add(af);
			 			 }
			 		 }
			 		 AttachFile[] arryAttach = new AttachFile[attachFiles.size()];
			 		 for (int i = 0; i < arryAttach.length; i++) {
			 			 arryAttach[i] = attachFiles.get(i);
			 		 }
			 		 mail.setAttachFile(arryAttach);
			 	 }
		     } catch (Exception ex) {
		    	 // 解釈不能なENCODINGはムシ
		     }
		     msg.setFlag(Flags.Flag.SEEN, true);
	     }
	     
	     mail.setSendDate(msg.getSentDate());
	     setMsgId(mail, msg);
	     return mail;
    }
     
    public BaseResult getMail(Member loginMember, int mailSettingId, String folderName, int idx) throws MessagingException, IOException {
		BaseResult result = new BaseResult();
		if (!loginMember.isLogined(result)) {
			return result;
		}
		Folder target = getTargetFolder(loginMember, mailSettingId, folderName);
		target.open(Folder.READ_ONLY);
		try {
			Message msg = target.getMessage(idx);
			result.setResult(getMessage2Mail(idx, msg, true));
			return result;
		} finally {
			target.close(false);
		}
    }
    
    public BaseResult listMails(Member loginMember, int mailSettingId, String folderName, boolean containBody) throws MessagingException, IOException {
		BaseResult result = new BaseResult();
		if (!loginMember.isLogined(result)) {
			return result;
		}
		Folder target = getTargetFolder(loginMember, mailSettingId, folderName);
		target.open(Folder.READ_ONLY);
		Mail[] resMails = null;
		try {
			Message[] msgs = target.getMessages();
			if (!containBody) {
				FetchProfile fp = new FetchProfile();
				fp.add(FetchProfile.Item.ENVELOPE);
				fp.add(FetchProfile.Item.FLAGS);
				target.fetch(msgs, fp);
			}
			  
			int validMail = 0;
			for (int i = 0; i < msgs.length; i++) {
				 if (!msgs[i].getFlags().contains(Flags.Flag.DELETED)) {
					 validMail++;
				 }
			}
			resMails = new Mail[validMail];
			validMail = 0;
			for (int i = 0; i < msgs.length; i++) {
				 if (!msgs[i].getFlags().contains(Flags.Flag.DELETED)) {
				     resMails[validMail] = getMessage2Mail(i, msgs[i], containBody);
				     validMail++;
				 }
			}
		} finally {
			target.close(false);
		}
		result.setResult(resMails);
		return result;
	}
    

    private Session getSMTPSession(Member member, MailSetting mailSetting) throws MessagingException {
        Properties props = System.getProperties();
        props.put("mail.smtp.host", mailSetting.getSmtpHost());
        Session smtp_session = Session.getDefaultInstance(props, null);
        return smtp_session;
    }
    
    private MimeMessage getNewMessage(Member member, MailSetting mailSetting) throws MessagingException {
    	Session smtp_session = getSMTPSession(member, mailSetting);
        MimeMessage msg = new MimeMessage(smtp_session);
        return msg;
    }
    
    private static InternetAddress[] parseStrAddress(String[] addr_s) throws Exception {
        InternetAddress[] addr = new InternetAddress[addr_s.length];
        Pattern addr_regex = Pattern.compile("(.+)<(.+)>");
        for (int i = 0; i < addr.length; i++) {
        	Matcher matcher = addr_regex.matcher(addr_s[i]);
            if (matcher.matches()) {
                addr[i] = new InternetAddress(matcher.group(2),
                    MimeUtility.encodeText(matcher.group(1),
                    "iso-2022-jp", "B"));
            } else {
                addr[i] = new InternetAddress(addr_s[i]);
            }
        }
		return addr;
    }
    
	private Multipart setAttachFile(MimeMessage msg, File dir) throws Exception {
		File[] attach_files = dir.listFiles();
		Multipart mp = null;
		for (int i = 0; attach_files != null && i < attach_files.length; i++) {
			if (mp == null)
				mp = new MimeMultipart();
			MimeBodyPart part = new MimeBodyPart();
			part.setDataHandler(new DataHandler(
				new FileDataSource(attach_files[i])));
			String attachName = Util.decodeHex(attach_files[i].getName());
			part.setFileName(MimeUtility.encodeText(attachName, "iso-2022-jp", "B"));
			mp.addBodyPart(part);
		}
		return mp;
	}

    public BaseResult send(Member loginMember, int mailSettingId, Mail mail) throws Exception {
		BaseResult result = new BaseResult();
		if (!loginMember.isLogined(result)) {
			return result;
		}
		MailSetting setting = getMailSetting(loginMember.getId(), mailSettingId);
    	MimeMessage new_msg = getNewMessage(loginMember, setting);
    	new_msg.setFrom(new InternetAddress(setting.getEmail()));
    	new_msg.setRecipients(Message.RecipientType.TO, parseStrAddress(mail.getToaddr()));
    	new_msg.setRecipients(Message.RecipientType.CC, parseStrAddress(mail.getCcaddr()));
    	new_msg.setRecipients(Message.RecipientType.BCC, parseStrAddress(mail.getBccaddr()));
    	new_msg.setSubject(mail.getSubject(), "ISO-2022-JP");

		File attachDir = new File(Config.getProperty("mail.attach.dir"), "" + loginMember.getId());
		attachDir = new File(attachDir, mail.getAttachFileKey());
		Multipart mp = setAttachFile(new_msg, attachDir);
    	
		if (mp == null) {
			new_msg.setDataHandler(new DataHandler(new ByteArrayDataSource(mail.getBody(), "text/plain; charset=ISO-2022-JP" )));
			new_msg.setHeader("Content-Transfer-Encoding", "7bit");
		} else {
			MimeBodyPart mainMsg = new MimeBodyPart();
			mainMsg.setDataHandler(new DataHandler(new ByteArrayDataSource(mail.getBody(), "text/plain; charset=ISO-2022-JP" )));
            mainMsg.setHeader("Content-Transfer-Encoding", "7bit");
			mp.addBodyPart(mainMsg);
			new_msg.setContent(mp);
		}
    	Transport.send(new_msg);
        String sended = setting.getMailSendedFolder();
		if (!StringUtils.isEmpty(sended)) {
			Folder sended_mbox = getTargetFolder(loginMember, mailSettingId, sended);
			if (sended_mbox.exists()) {
				new_msg.setSentDate(new Date());
				new_msg.setFlag(Flags.Flag.SEEN, true);
				Message[] new_msgs = {new_msg};
				sended_mbox.appendMessages(new_msgs);
			} else {
				result.setStatusCode(1);
				return result;
			}
		}
		FileUtils.deleteDirectory(attachDir);
		return result;
    }
    
    public Message[] msgId2MsgNo(Folder searchTarget, String[] msgIds) throws MessagingException {
    	return searchTarget.search(new MessageIdSearchTerm(msgIds));
    }
    
	public BaseResult deleteMail(Member loginMember, int mailSettingId, String folderName, String[] msgIds)
			throws javax.mail.MessagingException, IllegalArgumentException {
		BaseResult result = new BaseResult();
		if (!loginMember.isLogined(result)) {
			return result;
		}
		Folder folder_from = getTargetFolder(loginMember, mailSettingId, folderName);
		Folder folder_to = getTargetFolder(loginMember, mailSettingId, "Trash");
		if (folder_to != null && !folder_to.exists()) {
			result.setStatusCode(1);
			return result;
		}
		try {
			folder_from.open(Folder.READ_WRITE);
			Message[] msgs = msgId2MsgNo(folder_from, msgIds);
			if (folder_to != null
				&& !folder_from.getName().equals(folder_to.getName())) {
				folder_to.open(Folder.READ_WRITE);
				folder_from.copyMessages(msgs, folder_to);
			}
			for (int i = 0; i < msgs.length; i++) {
				msgs[i].setFlag(Flags.Flag.DELETED, true);
			}
		} finally {
			folder_from.close(true);
			if (folder_to != null && folder_to.isOpen())
				folder_to.close(false);
		}
		return result;
	}

	public BaseResult moveMail(Member loginMember, int mailSettingId, String mb_from, String mb_to, String[] msgIds)
		throws javax.mail.MessagingException {
		BaseResult result = new BaseResult();		
		if (!loginMember.isLogined(result)) {
			return result;
		}
		Folder folder_from = getTargetFolder(loginMember, mailSettingId, mb_from);
		Folder folder_to = getTargetFolder(loginMember, mailSettingId, mb_to);
		if (folder_from.getFullName().equals(folder_to.getFullName())) {
			return result;
		}
		try {
			folder_from.open(Folder.READ_WRITE);
			folder_to.open(Folder.READ_WRITE);
			Message[] msgs = msgId2MsgNo(folder_from, msgIds);
			folder_from.copyMessages(msgs, folder_to);
			for (int i = 0; i < msgs.length; i++)
				msgs[i].setFlag(Flags.Flag.DELETED, true);
		} finally {
			folder_from.close(true);
			folder_to.close(false);
		}
		return result;
	}

	public BaseResult createAttachFileKey(Member loginMember) {
		BaseResult result = new BaseResult();		
		if (!loginMember.isLogined(result)) {
			return result;
		}
		result.setResult(com.kickmogu.Util.createPassword(20));
		return result;
	}

	public BaseResult addFolder(Member loginMember, String parent, String newName, int mailSettingId) throws javax.mail.MessagingException {
		BaseResult result = new BaseResult();		
		if (!loginMember.isLogined(result)) {
			return result;
		}
		
		Folder parentFolder = getTargetFolder(loginMember, mailSettingId, parent);
		Folder newFolder = parentFolder.getFolder(newName);
		newFolder.create(Folder.HOLDS_FOLDERS + Folder.HOLDS_MESSAGES);
		return result;
	}

	public BaseResult deleteFolder(Member loginMember, String deleteName, int mailSettingId) throws javax.mail.MessagingException {
		BaseResult result = new BaseResult();		
		if (!loginMember.isLogined(result)) {
			return result;
		}
		
		Folder targetFolder = getTargetFolder(loginMember, mailSettingId, deleteName);
		targetFolder.delete(true);
		return result;
	}

}
