package com.jware.util.mail;

import java.util.Calendar;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.regex.Pattern;

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

/**
 * メール送信基盤クラス。
 */
public class MassMailSender {

	private final int MAX_RETRY_COUNT = 3;
	private final long NORMAL_MAXWAITING = 120*60*1000L;
	private final long ABNORMAL_MAXWAITING = 240*60*1000L;
	private final int DEFAULT_POOL_SIZE = 5;
	private final long SAVING_INTERVAL = 60*1000L;
	private final Log log = LogFactory.getLog( this.getClass() );

	private MailResourceUtility myResources = null;
	private MailPersistentManager persistentManager = null;
	private long mailSavedTime = 0;
	
	public MassMailSender(){
		super();
		myResources = new MailResourceUtility();
	}

    public boolean execute( BaseMailData mailData ) {

    	boolean result = false;
    	if( mailData == null ) {
    		return result;
    	}
    	mailData.setProcessor( myResources.getResourceString("MailSendingProcessor") );
    	mailData.setPreInsertTitle( myResources.getResourceString("mail.Title.PreInsert") );
    	mailData.setPostInsertTitle( myResources.getResourceString("mail.Title.PostInsert") );

    	mailSavedTime = Calendar.getInstance().getTimeInMillis();
    	mailData.setStatus( 1 );  // sending status
        int threadPoolSize = 0;
        try {
        	threadPoolSize = Integer.parseInt( myResources.getResourceString("ThreadPoolSize") );
        } catch( Exception e ) {
        	threadPoolSize = DEFAULT_POOL_SIZE;
        }
        if( threadPoolSize <= 0 ) {
        	threadPoolSize = DEFAULT_POOL_SIZE;
        }
        Map threadPool = new HashMap( );
        Map abnormalThreadPool = new HashMap( );
    	
    	Map hostAddressMap = new HashMap();
    	Map hostStatusMap = mailData.getHostStatusMap();
    	if( hostStatusMap == null ){
    		hostStatusMap = new HashMap();
    	}
    	
    	List toList = mailData.getToList();
    	if( toList != null ) {
    		for( int i=0; i<toList.size(); i++ ) {
    			
    			String address = (String)toList.get(i);
    			String domain = MXLoockup.lookupDomain( address );
    			if( domain != null ){
	    			List addressList = (List)hostAddressMap.get(domain);
	    			if( addressList == null ){
	    				addressList = new LinkedList();
	    				hostAddressMap.put( domain, addressList );
					}
	    			addressList.add( address );
    			}
    		}
    	}
    	
    	List ccList = mailData.getCcList();
    	if( ccList != null ) {
    		for( int i=0; i<ccList.size(); i++ ) {
    			
    			String address = (String)ccList.get(i);
    			String domain = MXLoockup.lookupDomain( address );
    			if( domain != null ){
	    			List addressList = (List)hostAddressMap.get(domain);
	    			if( addressList == null ){
	    				addressList = new LinkedList();
	    				hostAddressMap.put( domain, addressList );
					}
	    			addressList.add( address );
    			}
    		}
    	}

    	List bccList = mailData.getBccList();
    	if( bccList != null ) {
    		for( int i=0; i<bccList.size(); i++ ) {
    			
    			String address = (String)bccList.get(i);
    			String domain = MXLoockup.lookupDomain( address );
    			if( domain != null ){
	    			List addressList = (List)hostAddressMap.get(domain);
	    			if( addressList == null ){
	    				addressList = new LinkedList();
	    				hostAddressMap.put( domain, addressList );
					}
	    			addressList.add( address );
    			}
    		}
    	}
    	
    	if( mailData instanceof MassMailData ) {
    		BaseMailData beforMailData = ((MassMailData)mailData).getBeforeMail();
	    	MassMailSender beforMailSender = new MassMailSender();
	    	beforMailSender.execute( beforMailData );
    	}

		Iterator domainIterator = hostAddressMap.keySet().iterator();
		while( domainIterator.hasNext() ) {
			
            String domain = (String)domainIterator.next();
            List addressList = (List)hostAddressMap.get(domain);
            
            String host = MXLoockup.lookupHost( domain );
            if( host != null ) {
            	
	            MailStatus mailStatus = (MailStatus)hostStatusMap.get(host);
	            if( mailStatus == null ){
	            	mailStatus = new MailStatus();
	            }
	            mailStatus.setMailHost( host );
	            mailStatus.setMailDomain( domain );
	            mailStatus.setStartTime( Calendar.getInstance().getTime() );
	            
	            MailResourceUtility mailResourceUtility = new MailResourceUtility();
	            String domainConstraint = mailResourceUtility.getResourceString( domain );
	            if( domainConstraint != null ) {
	            	String[] constraints = domainConstraint.split( "," ); 
	            	int maxSendable = -1;
	            	if( constraints.length > 0 ){
		            	try{
		            		maxSendable = Integer.parseInt( constraints[0].trim() );
		            	} catch( Exception e ) {
		            		maxSendable = -1;
		            	}
	            	}
	            	int limitInterval = 0;
	            	if( constraints.length > 1 ){
		            	try{
		            		limitInterval = Integer.parseInt( constraints[1].trim() );
		            	} catch( Exception e ) {
		            		limitInterval = 0;
		            	}
	            	}
	            	mailStatus.setMaxSendable( maxSendable );
	            	mailStatus.setLimitInterval( limitInterval );
	            	if( limitInterval != 0 && maxSendable != -1 ){
	            		mailStatus.setAbnormalFlg( true );
	            	}
	            }
	            
        		mailStatus.setCompleteFlg( false );
	            hostStatusMap.put( domain, mailStatus );
	
            }
		}
    	
		mailData.setHostStatusMap( hostStatusMap );
		
		Iterator hostIterator = hostStatusMap.keySet().iterator();
		while( hostIterator.hasNext() ) {
			
            String domain = (String)hostIterator.next();
            MailStatus mailStatus = (MailStatus)hostStatusMap.get(domain);
            List addressList = (List)hostAddressMap.get(domain);
            
            BaseMailSender mailSender = new BaseMailSender();
            configMailSender( mailSender );
            mailSender.setCurrentDomain( domain );
            mailSender.setDomainStatusMap( hostStatusMap );
            mailSender.setCcBccNotSendFlg( true );
            
            List mailDataList = new LinkedList();
            
            for( int i=0; i<addressList.size(); i++ ) {
            	String address = (String)addressList.get(i);
            	List newToList = new LinkedList();
            	newToList.add( address );
            	
            	BaseMailData newMailData = new BaseMailData();
            	newMailData.setFrom( mailData.getFrom() );
            	newMailData.setToList( newToList );
            	newMailData.setCcList( mailData.getCcList() );
            	newMailData.setBccList( mailData.getBccList() );
            	newMailData.setSubject( mailData.getSubject() );
            	newMailData.setContentTitle( mailData.getContentTitle() );

        		newMailData.setContent( mailData.getContent() );
        		newMailData.setAttachments( mailData.getAttachments() );

        		newMailData.setMailType( mailData.getMailType() );
            	newMailData.setMailFlag( mailData.getMailFlag() );
            	newMailData.setCreateDate( mailData.getCreateDate() );
            	newMailData.setAddressNameMap( mailData.getAddressNameMap() );

        		newMailData.setInsertTitleFlg( mailData.isInsertTitleFlg() );
        		newMailData.setPreInsertTitle( mailData.getPreInsertTitle() );
        		newMailData.setPostInsertTitle( mailData.getPostInsertTitle() );
            	
            	mailDataList.add( newMailData );
            }
            mailSender.setMailDataList( mailDataList );

            boolean kickedTheThread = false;
            while ( !kickedTheThread ) {

            	if( mailStatus.isAbnormalFlg()  ){
            		kickedTheThread = true;
		            String threadName = "AbnormalMailSender"+Calendar.getInstance().getTimeInMillis();
		    		ThreadGroup threadGroup = Thread.currentThread().getThreadGroup();
		    		MailSenderThread mailSenderThread = new MailSenderThread(threadGroup, threadName);
		    		mailSenderThread.setMailSender( mailSender );
		    		mailSenderThread.start();
		    		abnormalThreadPool.put( threadName, mailSenderThread );
            	} else if( threadPool.size() < threadPoolSize ){
            		kickedTheThread = true;
		            String threadName = "MailSender"+Calendar.getInstance().getTimeInMillis();
		    		ThreadGroup threadGroup = Thread.currentThread().getThreadGroup();
		    		MailSenderThread mailSenderThread = new MailSenderThread(threadGroup, threadName);
		    		mailSenderThread.setMailSender( mailSender );
		    		mailSenderThread.start();
		    		threadPool.put( threadName, mailSenderThread );
            	}
            	if( !kickedTheThread ){
	            	Iterator threadIterator = threadPool.keySet().iterator();
	            	while( threadIterator.hasNext() ){
	            		String threadName = (String)threadIterator.next();
		            	MailSenderThread tempThread = (MailSenderThread)threadPool.get(threadName);
		            	if( !MailSenderThread.isTheThreadRunning( threadName) ){
		            		threadPool.remove( threadName );
		            		break;
		            	} else {
		            		long currentTime = Calendar.getInstance().getTimeInMillis();
		            		long threadTime = Calendar.getInstance().getTimeInMillis();
		            		try {
		            			threadTime = Long.parseLong( threadName.substring( "MailSender".length() )); 
		            		} catch( Exception e ){
		            			threadTime = Calendar.getInstance().getTimeInMillis();
		            		}
		            		currentTime = currentTime - NORMAL_MAXWAITING;
		            		if( currentTime > threadTime ){
			            		threadPool.remove( threadName );
			            		tempThread.interrupt();
			            		tempThread.destroy();
			            		break;
		            		}
		            	}
		            }
            	}
            	
            	long currentTime = Calendar.getInstance().getTimeInMillis();
            	if( persistentManager != null && currentTime > mailSavedTime + SAVING_INTERVAL ) {
            		persistentManager.save( mailData );
            		mailSavedTime = currentTime;
            	}
            }            
		}    	

        // normalThread Waiting and retry for Failed loop
        do {
        	
        	if( threadPool.size() < threadPoolSize ){
	    		hostIterator = hostStatusMap.keySet().iterator();
	    		while( hostIterator.hasNext() ) {
	    			
	                String host = (String)hostIterator.next();
	                MailStatus mailStatus = (MailStatus)hostStatusMap.get(host);
	                if( mailStatus!= null && mailStatus.isCompleteFlg()
	                		&& mailStatus.getSendFailed() > 0
	                		&& mailStatus.getRetryCount() < MAX_RETRY_COUNT ) {
                		mailStatus.setSendFailed( 0 );
                        List addressList = mailStatus.getFailedList();
                        
                		mailStatus.setCompleteFlg( false );
                		mailStatus.setSendFailed( 0 );
                        BaseMailSender mailSender = new BaseMailSender();
                        configMailSender( mailSender );
                        mailSender.setCurrentDomain( host );
                        mailSender.setDomainStatusMap( hostStatusMap );
                        mailSender.setCcBccNotSendFlg( true );
                        
                        List mailDataList = new LinkedList();
                        
                        for( int i=0; i<addressList.size(); i++ ) {
                        	String address = (String)addressList.get(i);
                        	List newToList = new LinkedList();
                        	newToList.add( address );
                        	
                        	BaseMailData newMailData = new BaseMailData();
                        	newMailData.setFrom( mailData.getFrom() );
                        	newMailData.setToList( newToList );
                        	newMailData.setCcList( mailData.getCcList() );
                        	newMailData.setBccList( mailData.getBccList() );
                        	newMailData.setSubject( mailData.getSubject() );
                        	if( mailData instanceof MassMailData ) {
                        		newMailData.setContent( ((MassMailData)mailData).getContent(address) );
                        		newMailData.setAttachments( ((MassMailData)mailData).getAttachments(address) );
                        	} else {
                        		newMailData.setContent( mailData.getContent() );
                        		newMailData.setAttachments( mailData.getAttachments() );
                        	}
                        	newMailData.setMailType( mailData.getMailType() );
                        	newMailData.setMailFlag( mailData.getMailFlag() );
                        	newMailData.setCreateDate( mailData.getCreateDate() );
                        	newMailData.setAddressNameMap( mailData.getAddressNameMap() );
                        	
                        	mailDataList.add( newMailData );
                        }
                        mailSender.setMailDataList( mailDataList );

                        if( mailStatus.isAbnormalFlg() ){
	    		            String threadName = "AbnormalMailSender"+Calendar.getInstance().getTimeInMillis();
	    		    		ThreadGroup threadGroup = Thread.currentThread().getThreadGroup();
	    		    		MailSenderThread mailSenderThread = new MailSenderThread(threadGroup, threadName);
	    		    		mailSenderThread.setMailSender( mailSender );
	    		    		mailSenderThread.start();
	    		    		abnormalThreadPool.put( threadName, mailSenderThread );
                        } else {
	    		            String threadName = "MailSender"+Calendar.getInstance().getTimeInMillis();
	    		    		ThreadGroup threadGroup = Thread.currentThread().getThreadGroup();
	    		    		MailSenderThread mailSenderThread = new MailSenderThread(threadGroup, threadName);
	    		    		mailSenderThread.setMailSender( mailSender );
	    		    		mailSenderThread.start();
	    		    		threadPool.put( threadName, mailSenderThread );
                        }
                	}
                }
    		}        	
        	
        	Iterator threadIterator = threadPool.keySet().iterator();
        	while( threadIterator.hasNext() ){
        		String threadName = (String)threadIterator.next();
            	MailSenderThread tempThread = (MailSenderThread)threadPool.get(threadName);
            	if( !MailSenderThread.isTheThreadRunning( threadName) ){
            		threadPool.remove( threadName );
            		break;
            	} else {
            		long currentTime = Calendar.getInstance().getTimeInMillis();
            		long threadTime = Calendar.getInstance().getTimeInMillis();
            		try {
            			threadTime = Long.parseLong( threadName.substring( "MailSender".length() )); 
            		} catch( Exception e ){
            			threadTime = Calendar.getInstance().getTimeInMillis();
            		}
            		currentTime = currentTime - NORMAL_MAXWAITING;
            		if( currentTime > threadTime ){
	            		threadPool.remove( threadName );
	            		tempThread.interrupt();
	            		//tempThread.destroy();
	            		break;
            		}
            	}
            }
        	
        	long currentTime = Calendar.getInstance().getTimeInMillis();
        	if( persistentManager != null && (currentTime > mailSavedTime + SAVING_INTERVAL) ) {
        		persistentManager.save( mailData );
        		mailSavedTime = currentTime;
        	}
        	
        } while ( threadPool.size() > 0 );           
        
        // AbnormalThread Waiting loop
        do {
        	
    		hostIterator = hostStatusMap.keySet().iterator();
    		while( hostIterator.hasNext() ) {
    			
                String host = (String)hostIterator.next();
                MailStatus mailStatus = (MailStatus)hostStatusMap.get(host);
                if( mailStatus!= null && mailStatus.isCompleteFlg() 
                		&& mailStatus.isAbnormalFlg() == true
                		&& mailStatus.getSendFailed() > 0
                		&& mailStatus.getRetryCount() < MAX_RETRY_COUNT ) {
            		mailStatus.setSendFailed( 0 );
                    List addressList = mailStatus.getFailedList();
                    
            		mailStatus.setCompleteFlg( false );
            		mailStatus.setSendFailed( 0 );
                    BaseMailSender mailSender = new BaseMailSender();
                    configMailSender( mailSender );
                    mailSender.setCurrentDomain( host );
                    mailSender.setDomainStatusMap( hostStatusMap );
                    mailSender.setCcBccNotSendFlg( true );
                    
                    List mailDataList = new LinkedList();
                    
                    for( int i=0; i<addressList.size(); i++ ) {
                    	String address = (String)addressList.get(i);
                    	List newToList = new LinkedList();
                    	newToList.add( address );
                    	
                    	BaseMailData newMailData = new BaseMailData();
                    	newMailData.setFrom( mailData.getFrom() );
                    	newMailData.setToList( newToList );
                    	newMailData.setCcList( mailData.getCcList() );
                    	newMailData.setBccList( mailData.getBccList() );
                    	newMailData.setSubject( mailData.getSubject() );
                    	if( mailData instanceof MassMailData ) {
                    		newMailData.setContent( ((MassMailData)mailData).getContent(address) );
                    		newMailData.setAttachments( ((MassMailData)mailData).getAttachments(address) );
                    	} else {
                    		newMailData.setContent( mailData.getContent() );
                    		newMailData.setAttachments( mailData.getAttachments() );
                    	}
                    	newMailData.setMailType( mailData.getMailType() );
                    	newMailData.setMailFlag( mailData.getMailFlag() );
                    	newMailData.setCreateDate( mailData.getCreateDate() );
                    	newMailData.setAddressNameMap( mailData.getAddressNameMap() );
                    	
                    	mailDataList.add( newMailData );
                    }
                    mailSender.setMailDataList( mailDataList );

		            String threadName = "AbnormalMailSender"+Calendar.getInstance().getTimeInMillis();
		    		ThreadGroup threadGroup = Thread.currentThread().getThreadGroup();
		    		MailSenderThread mailSenderThread = new MailSenderThread(threadGroup, threadName);
		    		mailSenderThread.setMailSender( mailSender );
		    		mailSenderThread.start();
		    		abnormalThreadPool.put( threadName, mailSenderThread );
            	}
            }
        	
        	Iterator threadIterator = abnormalThreadPool.keySet().iterator();
        	while( threadIterator.hasNext() ){
        		String threadName = (String)threadIterator.next();
            	MailSenderThread tempThread = (MailSenderThread)abnormalThreadPool.get(threadName);
            	if( !MailSenderThread.isTheThreadRunning( threadName) ){
            		abnormalThreadPool.remove( threadName );
            		break;
            	} else {
            		long currentTime = Calendar.getInstance().getTimeInMillis();
            		long threadTime = Calendar.getInstance().getTimeInMillis();
            		try {
            			threadTime = Long.parseLong( threadName.substring( "AbnormalMailSender".length() )); 
            		} catch( Exception e ){
            			threadTime = Calendar.getInstance().getTimeInMillis();
            		}
            		currentTime = currentTime - ABNORMAL_MAXWAITING;
            		if( currentTime > threadTime ){
            			abnormalThreadPool.remove( threadName );
	            		tempThread.interrupt();
	            		//tempThread.destroy();
	            		break;
            		}
            	}
            }
        	
        	long currentTime = Calendar.getInstance().getTimeInMillis();
        	if( persistentManager != null && (currentTime > mailSavedTime + SAVING_INTERVAL) ) {
        		persistentManager.save( mailData );
        		mailSavedTime = currentTime;
        	}
        	
        } while ( abnormalThreadPool.size() > 0 );           

        if( mailData instanceof MassMailData ) {
    		BaseMailData afterMailData = ((MassMailData)mailData).getAfterMail();
	    	MassMailSender afterMailSender = new MassMailSender();
	    	afterMailSender.execute( afterMailData );
    	}

    	mailData.setStatus( 2 );  // complete status;
    	mailData.setEndDate( Calendar.getInstance().getTime());
    	if( persistentManager != null ) {
    		persistentManager.save( mailData );
    	}
    	
    	return result;
    }

    public void configMailSender( BaseMailSender mailSender ) {
    	
    	if( mailSender != null ){
    		
    		mailSender.setSmtpDirectFlg( isSmtpDirectFlg() );
    		mailSender.setPopBeforeSmtp( isPopBeforeSmtp() );
    		mailSender.setShiftJISFlg( isShiftJISFlg() );
    		
    		mailSender.setServerOsEncode(myResources.getResourceString("ServerOsEncode"));
    		mailSender.setSmtpEncode(myResources.getResourceString("SmtpEncode"));
    		mailSender.setSmtpServerAddress(myResources.getResourceString("SmtpServerAddress"));
    		mailSender.setPopServerAddress(myResources.getResourceString("PopServerAddress"));
    		mailSender.setPopAccessUserName(myResources.getResourceString("PopAccessUserName"));
    		mailSender.setPopAccessPassword(myResources.getResourceString("PopAccessPassword"));
    		mailSender.setBccMailAdddress(myResources.getResourceString("BccMailAdddress"));
    		mailSender.setClientOsEncode(myResources.getResourceString("ClientOsEncode"));
    		mailSender.setFileEncodingType(myResources.getResourceString("FileEncodingType"));
    		mailSender.setFileDistination(myResources.getResourceString("FileDistination"));

    	}
    	
    }
    
    /**
	 * SmtpDirectFlgのチェックを設定ファイルより読み込む
	 * @return boolean
	 */
	public boolean isSmtpDirectFlg(){
		if(Pattern.matches("[Yy][Ee][Ss]", 
		myResources.getResourceString("SmtpDirectFlg"))){
			return true;
		}
		return false;
	}

    /**
	 * PopBeforeSmtpのチェックを設定ファイルより読み込む
	 * @return boolean
	 */
	public boolean isPopBeforeSmtp(){
		if(Pattern.matches("[Yy][Ee][Ss]", 
				myResources.getResourceString("PopBeforSmtp"))){
				return true;
		}
		return false;
	}

	/**
	 * ShiftJISFlgのチェックを設定ファイルより読み込む
	 * @return boolean
	 */
	public boolean isShiftJISFlg(){
		if(Pattern.matches("[Yy][Ee][Ss]", 
				myResources.getResourceString("ShiftJISFlg"))){
				return true;
		}
		return false;
	}

	/**
	 * @return Returns the persistentManager.
	 */
	public MailPersistentManager getPersistentManager() {
		return persistentManager;
	}
	/**
	 * @param persistentManager The persistentManager to set.
	 */
	public void setPersistentManager(MailPersistentManager persistentManager) {
		this.persistentManager = persistentManager;
	}
}
