package com.jware.util.mail;

import java.io.UnsupportedEncodingException;
import java.util.Calendar;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Properties;

import javax.activation.DataHandler;
import javax.activation.FileDataSource;
import javax.mail.Message;
import javax.mail.Multipart;
import javax.mail.Session;
import javax.mail.Store;
import javax.mail.Transport;
import javax.mail.internet.AddressException;
import javax.mail.internet.InternetAddress;
import javax.mail.internet.MimeBodyPart;
import javax.mail.internet.MimeMessage;
import javax.mail.internet.MimeMultipart;
import javax.mail.internet.MimeUtility;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

/**
 * @author jrmt
 */
public class BaseMailSender {
	
	private final Log log = LogFactory.getLog( this.getClass() );

	private List mailDataList = null;
	
	private Map domainStatusMap = null;
	private String currentDomain = null;

	// メールサーバーに直接送信フラグ
	private boolean smtpDirectFlg = true;
	
	// 日本語文字化けを回避するフラグ
	private boolean shiftJISFlg = true;
	private boolean popBeforeSmtp = false;
	private String clientOsEncode = "UTF-8";
	private String serverOsEncode = "UTF-8";
	private String bccMailAdddress = ""; 
	
	private String smtpServerAddress = null; 
	private String popServerAddress = null; 
	private String popAccessUserName = null; 
	private String popAccessPassword = null; 
	private String smtpEncode = "iso-2022-jp"; 
	private String fileEncodingType = null; 
	private String fileDistination = null;
	
	// CC BCCにメールにも送るか送らないかのフラグ
	private boolean ccBccNotSendFlg = false;
	// 一回のセッションで送信出来るリミット　-1　リミット無し
	private int limitOnSession = -1;
	private int sendMailCount = 0;
	
	public int execute() throws UnsupportedEncodingException, AddressException{
    	
		if( mailDataList == null){
			return -1;
		}
		MailStatus mailStatus = null;
		if( domainStatusMap != null ){
			mailStatus = (MailStatus)domainStatusMap.get(currentDomain);
		}
		if( mailStatus == null ){
			mailStatus = new MailStatus();
			mailStatus.setMailDomain(currentDomain);
			mailStatus.setMailHost(MXLoockup.lookupHost(currentDomain));
			mailStatus.setStartTime( Calendar.getInstance().getTime() );
		}
		String currentHost = mailStatus.getMailHost();
		
		if( currentHost == null ){
			currentHost = smtpServerAddress;
		}
		// メールセッションオブジェクト作成
		if( smtpDirectFlg && currentHost != null){
			smtpServerAddress = currentHost;
		}
        Properties props = new Properties();
        props.put("mail.smtp.host", smtpServerAddress);
        Session session = Session.getInstance(props,null);
        sendMailCount = 0;
        for( int mailIndex=mailDataList.size()-1; mailIndex>=0; mailIndex-- ){
        	
        	BaseMailData maildata = (BaseMailData)mailDataList.get(mailIndex);
			// メールデータの取得
	        String from = maildata.getFrom();
	
			//タイトルの取得
	        String subject = maildata.getSubject();
	        if( shiftJISFlg ){
	        	 subject = toJIS(maildata.getSubject());
	        }
	
			//条件：タイトルがNULLである場合
			if(subject == null){
	
				subject = "";
	
			}else{
	
		        subject = 
		            new String(
		                subject.getBytes(clientOsEncode), 
		                		serverOsEncode);
	
			}
	
			String content = null;
			String address = null;
			//TOアドレスリストの取得
			List tos = maildata.getToList();
			if( tos!=null && tos.size() > 0 ) {
				address = (String)tos.get(0);
			}
			if( address!=null && maildata.isInsertTitleFlg() ) {
				content = maildata.getContent(address);
		        if( shiftJISFlg ){
		        	content = toJIS(maildata.getContent(address));
		        }
			} else {
				content = maildata.getContent();
		        if( shiftJISFlg ){
		        	content = toJIS(maildata.getContent());
		        }
			}
			//条件：コンテンツデータがNULLの場合
			if(content == null){
				content = "";
			} else {
		        content = 
		    	    new String(
		                content.getBytes(clientOsEncode), 
							serverOsEncode);
	
			}
			InternetAddress toList[] = new InternetAddress[0];
			//条件：リストがNULLでなく、長さが0以下でない場合
	
			if(tos != null && !(tos.size() <= 0)){
	
				toList = new InternetAddress[tos.size()];
				for(int i=0; i < tos.size(); i++){
					try {
						toList[i] = new InternetAddress((String)tos.get(i));
					} catch (AddressException e1) {
						maildata.setUnexistAddress((String)tos.get(i));
						throw new AddressException(e1.toString());
					}
				}
	
			}
	        
	        //CCのリスト取得
			List ccs = maildata.getCcList();
			
			InternetAddress[] ccList = new InternetAddress[0];
			//条件：リストがNULLでなく、長さが0以下でない場合
			if(ccs != null && !(ccs.size() <= 0)){
	
				//CCデータの追加
				ccList = new InternetAddress[ccs.size()];
				for(int i=0; i < ccs.size(); i++){
					try {
						ccList[i] = new InternetAddress((String)ccs.get(i));
					} catch (AddressException e1) {
						maildata.setUnexistAddress((String)tos.get(i));
						throw new AddressException(e1.toString());
					}
				}
	
			}
			InternetAddress[] bccList = new InternetAddress[0];
			if( bccMailAdddress != null && bccMailAdddress.length()>0 ){
				bccList = new InternetAddress[1];
				bccList[0] = new InternetAddress(bccMailAdddress);
			}
	
			MimeMessage message = null;
			
			try {
	            
	            if( !smtpDirectFlg && popBeforeSmtp ){
					Store store = session.getStore("pop3");
					store.connect(popServerAddress, -1,
							popAccessUserName,
							popAccessPassword);
	            }
	
	            // メールメッセージのセット
	            message = new MimeMessage(session);
	
				Calendar calendar = Calendar.getInstance();
				message.setSentDate( calendar.getTime() );
				
	            message.setFrom(new InternetAddress(from));
				message.setRecipients(Message.RecipientType.CC, ccList);
				if( bccList.length > 0 ){
					message.setRecipients(Message.RecipientType.BCC, bccList);
				}
				
				message.setSubject(MimeUtility.encodeWord(
					subject,smtpEncode,"B"));
	
				Map attachments = null;
				if( address!=null && maildata.isInsertTitleFlg() ) {
					attachments = maildata.getAttachments(address);
				} else {
					attachments = maildata.getAttachments();
				}
				
	            if( !attachments.isEmpty() ){
	                
	                Multipart mp = null;
	                
	            	if("multipart/alternative".equals(maildata.getMailType())){
	            	    mp = new MimeMultipart("alternative");
	            	} else if("multipart/related".equals(maildata.getMailType())){
	            	    mp = new MimeMultipart("related");
	            	} else {
	            	    mp = new MimeMultipart();
	            	}
	            	// "multipart/alternative"の形式で良いか分からなくて、"multipart/mixed"にします。
//	            	mp = new MimeMultipart();

					MimeBodyPart mbp1 = new MimeBodyPart();
					mbp1.setText(content, smtpEncode);
					mbp1.setHeader("Content-Type","text/plain;charset=" + smtpEncode);

					mp.addBodyPart(mbp1);

					Iterator attachFiles = attachments.keySet().iterator();
					while( attachFiles.hasNext() ) {
		                String fileName = (String)attachFiles.next(); 
		                Object attachBody = (Object)attachments.get( fileName );
		                if( attachBody != null ) {

		                    MimeBodyPart mbp2 = new MimeBodyPart();
		    				mbp2.setText(attachBody.toString(), smtpEncode);
//			                mbp2.setFileName(MimeUtility.encodeWord(fileName));
							mbp2.setHeader("Content-Type","text/html;charset=" + smtpEncode);
			                mp.addBodyPart(mbp2);
		                    
		                } else {
			                fileName = 
			                   	(new String(fileName.getBytes(
			                   			fileEncodingType),smtpEncode) );
			                MimeBodyPart mbp2 = new MimeBodyPart();
			                FileDataSource fds = 
			                	new FileDataSource(fileDistination+fileName);
			                mbp2.setDataHandler(new DataHandler(fds));
			                mbp2.setFileName(MimeUtility.encodeWord(fds.getName()));
			                mp.addBodyPart(mbp2);
		                }
					}
					message.setContent(mp);

	            } else {
	            	
					message.setText(content, smtpEncode);
	           	
	            }
	
				if(null == maildata.getMailType() || 
					"".equals(maildata.getMailType())){
	
					message.setHeader("Content-Type","text/html;charset=" + 
							smtpEncode);
	
	            	
				}else if("text".equals(maildata.getMailType())){
	   
					message.setHeader("Content-Type","text/plain;charset=" + 
							smtpEncode);
	
	            	
				}else if("html".equals(maildata.getMailType())){
	
					message.setHeader("Content-Type","text/html;charset=" + 
							smtpEncode);
	            	
				}
	
				message.saveChanges();
	
			} catch (Exception e) {
				e.printStackTrace();
			}
	
	        // メッセージの一斉送信
	        Transport transport = null;
			try {
	
				transport = session.getTransport("smtp");
				transport.connect();
				
				for(int i = 0; i < toList.length; i++){

					if( mailStatus.isAbnormalFlg() ){
						if( sendMailCount >= mailStatus.getMaxSendable() 
								&& mailStatus.getLimitInterval()!=0 ) {
							return mailStatus.getLimitInterval();
						}
						if( i>0 && sendMailCount>0 && mailStatus.getMaxSendable() > 0 
								&& (sendMailCount%mailStatus.getMaxSendable())==0 ) {
			        		transport.close();
							transport = session.getTransport("smtp");
							transport.connect();
						}
					} else if( i>0 && limitOnSession > 0 && (i%limitOnSession)==0 ){
		        		transport.close();
						transport = session.getTransport("smtp");
						transport.connect();
		        	}
					
					InternetAddress to[] = new InternetAddress[1];
					to[0] = toList[i];
	
					if( !"OK".equalsIgnoreCase(mailStatus.getStatus(toList[i].getAddress())) ){
						try {
		
							message.setRecipients(Message.RecipientType.TO, toList);
							transport.sendMessage(message, to);
							mailStatus.setStatus(toList[i].getAddress(), "OK");
							mailStatus.setDirty( true );
							if( log.isDebugEnabled() ){
								log.debug( " Mail Send success ( to address = " + toList[i].getAddress() + " )");
							}
					
						} catch(Exception e){
		
							mailStatus.setStatus(toList[i].getAddress(), "FAIL");
							mailStatus.setSendFailed( mailStatus.getSendFailed()+1);
							mailStatus.setDirty( true );
							if( log.isDebugEnabled() ){
								log.debug( " Mail Send Failed ( to address = " + toList[i].getAddress() + " )");
								log.debug(e.toString());
							}
						}
						sendMailCount++;
					}
				}
	
				if( !ccBccNotSendFlg ) {
					//Cc送信
					for(int i = 0; i < ccList.length; i++){

						if( mailStatus.isAbnormalFlg() ){
							if( sendMailCount >= mailStatus.getMaxSendable() 
									&& mailStatus.getLimitInterval()!=0 ) {
								return mailStatus.getLimitInterval();
							}
							if( sendMailCount>0 && mailStatus.getMaxSendable() > 0 
									&& (sendMailCount%mailStatus.getMaxSendable())==0 ) {
				        		transport.close();
								transport = session.getTransport("smtp");
								transport.connect();
							}
						} else if( limitOnSession > 0 && (i%limitOnSession)==0 ){
			        		transport.close();
							transport = session.getTransport("smtp");
							transport.connect();
			        	}
						InternetAddress cc[] = new InternetAddress[1];
						cc[0] = ccList[i];
		
						if( !"OK".equalsIgnoreCase(mailStatus.getStatus(ccList[i].getAddress())) ){
							
							try {
			
								message.setRecipient(Message.RecipientType.CC, ccList[i]);
								transport.sendMessage(message, cc);
								mailStatus.setStatus(ccList[i].getAddress(), "OK");
								mailStatus.setDirty( true );

							} catch(Exception e){
			
								mailStatus.setStatus(ccList[i].getAddress(), "FAIL");
								mailStatus.setSendFailed( mailStatus.getSendFailed()+1);
								mailStatus.setDirty( true );
								if( log.isDebugEnabled() ){
									log.debug( " Mail Send Failed ( cc address = " + ccList[i].getAddress() + " )");
									log.debug(e.toString());
								}
							}
							sendMailCount++;
						}
		
					}
		
					//Bcc送信
					for(int i = 0; i < bccList.length; i++){
		
						if( mailStatus.isAbnormalFlg() ){
							if( sendMailCount >= mailStatus.getMaxSendable() 
									&& mailStatus.getLimitInterval()!=0 ) {
								return mailStatus.getLimitInterval();
							}
							if( sendMailCount>0 && mailStatus.getMaxSendable() > 0 
									&& (sendMailCount%mailStatus.getMaxSendable())==0 ) {
				        		transport.close();
								transport = session.getTransport("smtp");
								transport.connect();
							}
						} else if( limitOnSession > 0 && (i%limitOnSession)==0 ){
			        		transport.close();
							transport = session.getTransport("smtp");
							transport.connect();
			        	}
						InternetAddress bcc[] = new InternetAddress[1];
						bcc[0] = bccList[i];
		
						if( !"OK".equalsIgnoreCase(mailStatus.getStatus(bccList[i].getAddress())) ){
							try {
			
								message.setRecipient(Message.RecipientType.BCC, bccList[i]);
								transport.sendMessage(message, bcc);
								mailStatus.setStatus(bccList[i].getAddress(), "OK");
								mailStatus.setDirty( true );
			
							} catch(Exception e){
			
								mailStatus.setStatus(bccList[i].getAddress(), "FAIL");
								mailStatus.setSendFailed( mailStatus.getSendFailed()+1);
								mailStatus.setDirty( true );
								if( log.isDebugEnabled() ){
									log.debug( " Mail Send Failed ( bcc address = " + bccList[i].getAddress() + " )");
									log.debug(e.toString());
								}
							}
							sendMailCount++;
						}
		
					}
				}
				transport.close();
	
			} catch (Exception e1) {
	
				e1.printStackTrace();
	
			}
			mailDataList.remove( mailIndex );
        }
        
        mailStatus.setRetryCount( mailStatus.getRetryCount()+1 );
        mailStatus.setCompleteFlg( true );
        mailStatus.setEndTime( Calendar.getInstance().getTime() );
        if( domainStatusMap != null ){
        	domainStatusMap.put( currentDomain, mailStatus);
        }
        
        return 0;
        
    }
    
	private String toJIS(String s) {

		if ( s == null )
			return s;
			 		
		StringBuffer sb = new StringBuffer();
		char c;
		for (int i = 0; i < s.length(); i++) {
			c  = s.charAt(i);
			switch (c) {
			case 0xff3c:	// FULLWIDTH REVERSE SOLIDUS ->
			c = 0x005c;	// REVERSE SOLIDUS
			break;
			case 0xff5e:	// FULLWIDTH TILDE ->
			c = 0x301c;	// WAVE DASH
			break;
			case 0x2225:	// PARALLEL TO ->
			c = 0x2016;	// DOUBLE VERTICAL LINE
			break;
			case 0xff0d:	// FULLWIDTH HYPHEN-MINUS ->
			c = 0x2212;	// MINUS SIGN
			break;
			case 0xffe0:	// FULLWIDTH CENT SIGN ->
			c = 0x00a2;	// CENT SIGN
			break;
			case 0xffe1:	// FULLWIDTH POUND SIGN ->
			c = 0x00a3;	// POUND SIGN
			break;
			case 0xffe2:	// FULLWIDTH NOT SIGN ->
			c = 0x00ac;	// NOT SIGN
			break;
			}
			sb.append(c);
		}
		return sb.toString();
	}
	
	
	/**
	 * @return Returns the bccMailAdddress.
	 */
	public String getBccMailAdddress() {
		return bccMailAdddress;
	}
	/**
	 * @param bccMailAdddress The bccMailAdddress to set.
	 */
	public void setBccMailAdddress(String bccMailAdddress) {
		this.bccMailAdddress = bccMailAdddress;
	}
	/**
	 * @return Returns the clientOsEncode.
	 */
	public String getClientOsEncode() {
		return clientOsEncode;
	}
	/**
	 * @param clientOsEncode The clientOsEncode to set.
	 */
	public void setClientOsEncode(String clientOsEncode) {
		this.clientOsEncode = clientOsEncode;
	}
	/**
	 * @return Returns the fileDistination.
	 */
	public String getFileDistination() {
		return fileDistination;
	}
	/**
	 * @param fileDistination The fileDistination to set.
	 */
	public void setFileDistination(String fileDistination) {
		this.fileDistination = fileDistination;
	}
	/**
	 * @return Returns the fileEncodingType.
	 */
	public String getFileEncodingType() {
		return fileEncodingType;
	}
	/**
	 * @param fileEncodingType The fileEncodingType to set.
	 */
	public void setFileEncodingType(String fileEncodingType) {
		this.fileEncodingType = fileEncodingType;
	}
	/**
	 * @return Returns the mailDataList.
	 */
	public List getMailDataList() {
		return mailDataList;
	}
	/**
	 * @param mailDataList The mailDataList to set.
	 */
	public void setMailDataList(List mailDataList) {
		this.mailDataList = mailDataList;
	}
	/**
	 * @return Returns the popAccessPassword.
	 */
	public String getPopAccessPassword() {
		return popAccessPassword;
	}
	/**
	 * @param popAccessPassword The popAccessPassword to set.
	 */
	public void setPopAccessPassword(String popAccessPassword) {
		this.popAccessPassword = popAccessPassword;
	}
	/**
	 * @return Returns the popAccessUserName.
	 */
	public String getPopAccessUserName() {
		return popAccessUserName;
	}
	/**
	 * @param popAccessUserName The popAccessUserName to set.
	 */
	public void setPopAccessUserName(String popAccessUserName) {
		this.popAccessUserName = popAccessUserName;
	}
	/**
	 * @return Returns the popBeforeSmtp.
	 */
	public boolean isPopBeforeSmtp() {
		return popBeforeSmtp;
	}
	/**
	 * @param popBeforeSmtp The popBeforeSmtp to set.
	 */
	public void setPopBeforeSmtp(boolean popBeforeSmtp) {
		this.popBeforeSmtp = popBeforeSmtp;
	}
	/**
	 * @return Returns the popServerAddress.
	 */
	public String getPopServerAddress() {
		return popServerAddress;
	}
	/**
	 * @param popServerAddress The popServerAddress to set.
	 */
	public void setPopServerAddress(String popServerAddress) {
		this.popServerAddress = popServerAddress;
	}
	/**
	 * @return Returns the serverOsEncode.
	 */
	public String getServerOsEncode() {
		return serverOsEncode;
	}
	/**
	 * @param serverOsEncode The serverOsEncode to set.
	 */
	public void setServerOsEncode(String serverOsEncode) {
		this.serverOsEncode = serverOsEncode;
	}
	/**
	 * @return Returns the shiftJISFlg.
	 */
	public boolean isShiftJISFlg() {
		return shiftJISFlg;
	}
	/**
	 * @param shiftJISFlg The shiftJISFlg to set.
	 */
	public void setShiftJISFlg(boolean shiftJISFlg) {
		this.shiftJISFlg = shiftJISFlg;
	}
	/**
	 * @return Returns the smtpEncode.
	 */
	public String getSmtpEncode() {
		return smtpEncode;
	}
	/**
	 * @param smtpEncode The smtpEncode to set.
	 */
	public void setSmtpEncode(String smtpEncode) {
		this.smtpEncode = smtpEncode;
	}
	/**
	 * @return Returns the smtpServerAddress.
	 */
	public String getSmtpServerAddress() {
		return smtpServerAddress;
	}
	/**
	 * @param smtpServerAddress The smtpServerAddress to set.
	 */
	public void setSmtpServerAddress(String smtpServerAddress) {
		this.smtpServerAddress = smtpServerAddress;
	}
	/**
	 * @return Returns the smtpDirectFlg.
	 */
	public boolean isSmtpDirectFlg() {
		return smtpDirectFlg;
	}
	/**
	 * @param smtpDirectFlg The smtpDirectFlg to set.
	 */
	public void setSmtpDirectFlg(boolean smtpDirectFlg) {
		this.smtpDirectFlg = smtpDirectFlg;
	}
	/**
	 * @return Returns the ccBccNotSendFlg.
	 */
	public boolean isCcBccNotSendFlg() {
		return ccBccNotSendFlg;
	}
	/**
	 * @param ccBccNotSendFlg The ccBccNotSendFlg to set.
	 */
	public void setCcBccNotSendFlg(boolean ccBccNotSendFlg) {
		this.ccBccNotSendFlg = ccBccNotSendFlg;
	}
	/**
	 * @return Returns the limitOnSession.
	 */
	public int getLimitOnSession() {
		return limitOnSession;
	}
	/**
	 * @param limitOnSession The limitOnSession to set.
	 */
	public void setLimitOnSession(int limitOnSession) {
		this.limitOnSession = limitOnSession;
	}
	/**
	 * @return Returns the currentHost.
	 */
	public String getCurrentDomain() {
		return currentDomain;
	}
	/**
	 * @param currentHost The currentHost to set.
	 */
	public void setCurrentDomain(String currentDomain) {
		this.currentDomain = currentDomain;
	}
	/**
	 * @return Returns the hostStatusMap.
	 */
	public Map getDomainStatusMap() {
		return domainStatusMap;
	}
	/**
	 * @param hostStatusMap The hostStatusMap to set.
	 */
	public void setDomainStatusMap(Map domainStatusMap) {
		this.domainStatusMap = domainStatusMap;
	}
	
	/**
	 * @param hostStatusMap The hostStatusMap to set.
	 */
	public MailStatus getCurrentMailStatus() {
		MailStatus mailStatus = null;
		if( domainStatusMap != null ){
			mailStatus = (MailStatus)domainStatusMap.get( currentDomain );
		}
		return mailStatus;
	}
	
}
