/******************************************************************************
 * Product: Adempiere ERP & CRM Smart Business Solution                       *
 * Copyright (C) 1999-2006 ComPiere, Inc. All Rights Reserved.                *
 * This program is free software; you can redistribute it and/or modify it    *
 * under the terms version 2 of the GNU General Public License as published   *
 * by the Free Software Foundation. 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 General Public License for more details.                       *
 * You should have received a copy of the GNU General Public License along    *
 * with this program; if not, write to the Free Software Foundation, Inc.,    *
 * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.                     *
 * For the text or an alternative of this public license, you may reach us    *
 * ComPiere, Inc., 2620 Augustine Dr. #245, Santa Clara, CA 95054, USA        *
 * or via info@compiere.org or http://www.compiere.org/license.html           *
 *****************************************************************************/
package org.compiere.process;

import java.io.IOException;
import java.nio.charset.Charset;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Level;

import javax.mail.BodyPart;
import javax.mail.Flags;
import javax.mail.Folder;
import javax.mail.Message;
import javax.mail.MessagingException;
import javax.mail.Session;
import javax.mail.Store;

import org.adempiere.exceptions.AdempiereException;
import org.compiere.model.MAttachment;
import org.compiere.model.MColumn;
import org.compiere.model.MProcessPara;
import org.compiere.model.MRequest;
import org.compiere.model.MRequestType;
import org.compiere.model.MUser;
import org.compiere.util.DB;
import org.compiere.util.EmailSrv;
import org.compiere.util.EmailSrv.EmailContent;
import org.compiere.util.EmailSrv.ProcessEmailHandle;
import org.compiere.util.Msg;
import org.compiere.util.Trx;

/**
 *	Request Email Processor
 *	
 *  @author Carlos Ruiz based on initial work by Jorg Janke - sponsored by DigitalArmour
 *  @version $Id: RequestEMailProcessor.java,v 1.2 2006/10/23 06:01:20 cruiz Exp $
 *  hieplq:separate email process to other class for easy re-use and do IDEMPIERE-2244
 *  
 *  IMAPHost format: {imap|imaps}://[IMAPHostURL]:[Port] example: imaps://imap.gmail.com:993
 */
@org.adempiere.base.annotation.Process
public class RequestEMailProcessor extends SvrProcess implements ProcessEmailHandle
{
	protected String	p_IMAPHost = null;
	protected int		p_IMAPPort = 143;
	protected String	p_IMAPUser = null;
	protected String	p_IMAPPwd = null;
	protected String	p_RequestFolder = null;
	protected String	p_InboxFolder = null;
	protected Boolean	p_NestInbox = true;
	protected String	p_ErrorFolder = null;
	protected Boolean 	isSSL = null;
	protected int C_BPartner_ID = 0;
	protected int AD_User_ID = 0;
	protected int AD_Role_ID = 0;
	protected int SalesRep_ID = 0;
	protected int R_RequestType_ID = 0;
	protected String p_DefaultPriority = null;
	protected String p_DefaultConfidentiality = null;
	protected String p_HTMLAttachmentType = "H";

	protected int noProcessed = 0;
	protected int noRequest = 0;
	protected int noError = 0;
	/**	Session				*/
	protected Session 	m_session = null;
	/**	Store				*/
	protected Store 		m_store = null;
	/**	Process Error				*/
	protected static final int		ERROR = 0;
	/**	Process Request				*/
	protected static final int		REQUEST = 1;
	
	protected Folder errorFolder;
	protected Folder requestFolder;
	protected List<Folder> lsFolderProcess = new ArrayList<Folder>();
	/**
	 *  Prepare - e.g., get Parameters.
	 */
	protected void prepare()
	{
		ProcessInfoParameter[] para = getParameter();
		for (int i = 0; i < para.length; i++)
		{
			String name = para[i].getParameterName();
			if (para[i].getParameter() == null)
				;
			else if (name.equals("p_IMAPHost"))
				p_IMAPHost = ((String)para[i].getParameter());
			else if (name.equals("p_IMAPUser"))
				p_IMAPUser = ((String)para[i].getParameter());
			else if (name.equals("p_IMAPPwd"))
				p_IMAPPwd = ((String)para[i].getParameter());
			else if (name.equals("p_RequestFolder"))
				p_RequestFolder = ((String)para[i].getParameter());
			else if (name.equals("p_InboxFolder"))
				p_InboxFolder = ((String)para[i].getParameter());
			else if (name.equals("p_ErrorFolder"))
				p_ErrorFolder = ((String)para[i].getParameter());
			else if (name.equals("C_BPartner_ID"))
				C_BPartner_ID = para[i].getParameterAsInt();
			else if (name.equals("AD_User_ID"))
				AD_User_ID = para[i].getParameterAsInt();
			else if (name.equals("AD_Role_ID"))
				AD_Role_ID = para[i].getParameterAsInt();
			else if (name.equals("SalesRep_ID"))
				SalesRep_ID = para[i].getParameterAsInt();
			else if (name.equals("R_RequestType_ID"))
				R_RequestType_ID = para[i].getParameterAsInt();
			else if (name.equals("p_DefaultPriority"))
				p_DefaultPriority = ((String)para[i].getParameter());
			else if (name.equals("p_DefaultConfidentiality"))
				p_DefaultConfidentiality = ((String)para[i].getParameter());
			else if (name.equals("p_NestInbox"))
				p_NestInbox = "Y".equalsIgnoreCase(para[i].getParameter().toString());
			else if (name.equals("HTMLAttachmentType"))
				p_HTMLAttachmentType = para[i].getParameterAsString();
			else
				MProcessPara.validateUnknownParameter(getProcessInfo().getAD_Process_ID(), para[i]);
		}
		
		if(p_HTMLAttachmentType == null)
			p_HTMLAttachmentType = "H";
		
	}	//	prepare

	/**
	 *  Perform process.
	 *  @return Message (clear text)
	 *  @throws Exception if not successful
	 */
	protected String doIt() throws Exception
	{
		parseParameter();
		
		EmailSrv emailSrv = new EmailSrv(p_IMAPHost, p_IMAPUser, p_IMAPPwd, p_IMAPPort, isSSL);
		
		checkInputParameter (emailSrv);		
		
		EmailSrv.readEmailFolder(emailSrv, p_InboxFolder, p_NestInbox, this);
		
		StringBuilder msgreturn = new StringBuilder("processInBox - Total=").append(noProcessed) 
				.append(" - Requests=").append(noRequest)
				.append(" - Errors=").append(noError);
		return msgreturn.toString();
	}	//	doIt
	
	protected void parseParameter() {
		// === check for ssl input parameter ===
		int imapProtocolIndex = p_IMAPHost.lastIndexOf("://");
		
		if(imapProtocolIndex > 0) {
			String str_Protocol = p_IMAPHost.substring(0, imapProtocolIndex);
			if(str_Protocol.toLowerCase().equals("imaps"))
				isSSL  = true;
			else if(str_Protocol.toLowerCase().equals("imap"))
				isSSL = false;
			else
				log.warning("Unrecognized protocol - " + str_Protocol);
			
			if(isSSL != null)	// Remove Imap Protocol
				p_IMAPHost = p_IMAPHost.substring(imapProtocolIndex + 3, p_IMAPHost.length());
		}

		// === check input parameter === 
		int portStartIndex = p_IMAPHost.lastIndexOf(":");
		if (portStartIndex > 0){
			String strPort = p_IMAPHost.substring(portStartIndex + 1, p_IMAPHost.length());
			p_IMAPHost = p_IMAPHost.substring(0, portStartIndex);
			try{
				p_IMAPPort = Integer.parseInt(strPort);				
			}catch (Exception ex){
				throw new AdempiereException("Error format port : " + strPort);
			}			
		}else if (p_IMAPHost.startsWith("imap.gmail.com")){
			p_IMAPPort = 993;
		} else if(portStartIndex <= 0 && isSSL != null && isSSL) {
			p_IMAPPort = 993;	// Default Port for IMAPS protocol
		}
	}
	
	protected void checkInputParameter (EmailSrv emailSrv) throws MessagingException, Exception {
		if (log.isLoggable(Level.INFO)) log.info("doIt - IMAPHost=" + p_IMAPHost +
					   " IMAPPort=" + p_IMAPPort  +
				       " IMAPUser=" + p_IMAPUser  +
				       " RequestFolder=" + p_RequestFolder +
				       " InboxFolder=" + p_InboxFolder +
				       " ErrorFolder=" + p_ErrorFolder);
		
		if (R_RequestType_ID < 1 && MRequestType.getDefault(getCtx()) == null){
			throw new AdempiereException("request type: must define a default request type, or select one in request type field");
			
		}
		
		requestFolder = EmailSrv.getFolder(emailSrv.getMailStore(), p_RequestFolder, p_NestInbox, true);
		errorFolder = EmailSrv.getFolder(emailSrv.getMailStore(), p_ErrorFolder, p_NestInbox, true);
		
		lsFolderProcess.add(requestFolder);
		lsFolderProcess.add(errorFolder);
	}

	@Override
	public boolean checkEmailHeader(EmailContent emailHeader, Message emailRaw) throws MessagingException {
		boolean isCancel = emailHeader.fromAddress.size() == 0;
		if (isCancel){
			errorFolder.appendMessages(new Message[]{emailRaw});
			noError++;
		}		
		return isCancel;
	}
	
	@Override
	public void processEmailContent(EmailContent emailHeader, Message emailRaw, Store mailStore, Folder mailFolder) throws MessagingException, IOException {
		Trx trxRequest = null;
		try {			
			trxRequest = Trx.get(Trx.createTrxName("SvrProcess-makerequest"), true);
			trxRequest.setDisplayName(getClass().getName()+"_processEmailContent");
			trxRequest.start();
			
			createRequest(emailHeader, trxRequest.getTrxName());
			emailRaw.setFlag(Flags.Flag.SEEN, true);
			emailRaw.setFlag(Flags.Flag.ANSWERED, true);
			requestFolder.appendMessages(new Message[]{emailRaw});
			// log movement
			if (log.isLoggable(Level.INFO)) log.info("message " + emailHeader.subject + " moved to " + p_RequestFolder + " folder");
			if (log.isLoggable(Level.INFO)) log.info("message info: Sent -> " + emailHeader.sentDate + " From -> " + emailHeader.fromAddress.get(0).toString());
			// Delete in InBox
			emailRaw.setFlag(Flags.Flag.DELETED, true);
			@SuppressWarnings("unused")
			Message[] deleted = mailFolder.expunge();
			noRequest++;
			trxRequest.commit(true);
		} catch (Exception e) {
			trxRequest.rollback();			
			if (log.isLoggable(Level.INFO)) log.info("message " + emailHeader.subject + " threw error");
			e.printStackTrace();
			if (e instanceof AdempiereException){
				throw (AdempiereException)e;
			}else if (e instanceof MessagingException){
				throw (MessagingException)e;
			}else{
				throw new AdempiereException (e.getMessage());
			}
		}finally{
			if (trxRequest != null){
				trxRequest.close();
				trxRequest = null;
			}
		}
		
	}
	
	
	/**
	 * 	Create request
	 * @param emailContent
	 * @param trxName
	 * @throws Exception
	 */
	protected void createRequest(EmailContent emailContent, String trxName) throws Exception {
		// Assign from variable
		String fromAddress = emailContent.fromAddress.get(0);		
		
		int maxlen = MColumn.get(getCtx(), MRequest.Table_Name, MRequest.COLUMNNAME_DocumentNo).getFieldLength();
		String documentNo = emailContent.messageID;
		
		if (documentNo.length() > maxlen)
			documentNo = documentNo.substring(0,30);
		
		// Review if the e-mail was already created, comparing Message-ID+From+body
		int retValuedup = 0;
		String sqldup = "SELECT R_Request_ID FROM R_Request "
			 + "WHERE AD_Client_ID = ? "
			 + "AND DocumentNo = ? "
			 + "AND StartDate = ?";
		PreparedStatement pstmtdup = null;
		ResultSet rsdup = null;
		try 
		{
			pstmtdup = DB.prepareStatement (sqldup, null);
			pstmtdup.setInt(1, getAD_Client_ID());
			pstmtdup.setString(2, documentNo);
			pstmtdup.setTimestamp(3, new Timestamp(emailContent.sentDate.getTime()));
			rsdup = pstmtdup.executeQuery ();
			if (rsdup.next ())
				retValuedup = rsdup.getInt(1);
		} catch (SQLException e) 
		{
			throw e;
		}
		finally
		{
			DB.close (rsdup,pstmtdup);
			rsdup = null;pstmtdup = null;
		}
		if (retValuedup > 0) {
			if (log.isLoggable(Level.INFO)) log.info("request already existed for msg -> " + emailContent.subject);
			return;
		}
		
		// Analyze subject if Re: find the original request by subject + e-mail and add an action
		int request_upd = 0;
		String sqlupd = "SELECT r_request_id "
			 + "  FROM r_request "
			 + " WHERE ad_client_id = ? "
			 + "   AND summary LIKE 'FROM: ' || ? || '%' "
			 + "   AND (   documentno = "
			 + "              SUBSTR "
			 + "                 (?, "
			 + "                  INSTR "
			 + "                      (?, "
			 + "                       '<' "
			 + "                      ) "
			 + "                 ) "
			 + "        OR (    ? LIKE 'Re: %' "
			 + "            AND summary = "
			 + "                      'FROM: ' "
			 + "                   || ? "
			 + "                   || CHR (10) "
			 + "                   || SUBSTR (?, 5) "
			 + "           ) "
			 + "       ) ";
		PreparedStatement pstmtupd = null;
		ResultSet rsupd = null;
		try 
		{
			pstmtupd = DB.prepareStatement (sqlupd, null);
			pstmtupd.setInt(1, getAD_Client_ID());
			pstmtupd.setString(2, fromAddress);
			pstmtupd.setString(3, emailContent.subject);
			pstmtupd.setString(4, emailContent.subject);
			pstmtupd.setString(5, emailContent.subject);
			pstmtupd.setString(6, fromAddress);
			pstmtupd.setString(7, emailContent.subject);
			rsupd = pstmtupd.executeQuery ();
			if (rsupd.next ())
				request_upd = rsupd.getInt(1);
		} 
		catch (SQLException e) 
		{
			throw e;
		}
		finally
		{
			DB.close(rsupd,pstmtupd);
			rsupd = null;pstmtupd = null;
		}
		if (request_upd > 0) {
			if (log.isLoggable(Level.INFO)) log.info("msg -> " + emailContent.subject + " is an answer for req " + request_upd);
			updateRequest(request_upd, emailContent, trxName);
			return;
		}
		
		MRequest req = new MRequest(getCtx(), 0, trxName);
		// Subject as summary
		StringBuilder msgreq = new StringBuilder("FROM: ").append(fromAddress).append("\n").append(emailContent.subject);
		req.setSummary(msgreq.toString());
		// Body as result
		//TODO:1. improve to when email only html content, convert it to text and set here
		//TODO:2. improve to add control display html at form, no need open attach to see
		msgreq = new StringBuilder("FROM: ") .append(emailContent.fromAddress.get(0)).append("\n").append(emailContent.getTextContent());
		req.setResult(msgreq.toString());
		// Message-ID as documentNo
		req.setDocumentNo(documentNo);

		// Default request type for this process
		if (R_RequestType_ID > 0)
			req.setR_RequestType_ID(R_RequestType_ID);
		else
			req.setR_RequestType_ID();
			
		// set Default sales representative 
		if (SalesRep_ID > 0)
			req.setSalesRep_ID(SalesRep_ID);
		
		// set Default role
		if (AD_Role_ID > 0)
			req.setAD_Role_ID(AD_Role_ID);

		// Look for user via e-mail		
		int retValueu = -1;
		String sqlu = "SELECT ad_user_id "
			+ "  FROM ad_user "
			+ " WHERE UPPER (email) = UPPER (?) "
			+ "   AND ad_client_id = ?";
		PreparedStatement pstmtu = null;
		ResultSet rsu = null;
		try 
		{
			pstmtu = DB.prepareStatement (sqlu, null);
			pstmtu.setString(1, fromAddress);
			pstmtu.setInt(2, getAD_Client_ID());
			rsu = pstmtu.executeQuery ();
			if (rsu.next ())
				retValueu = rsu.getInt(1);
		}
		catch(SQLException e)
		{
			throw e;
		}
		finally
		{
			DB.close (rsu,pstmtu);
			rsu = null;pstmtu = null;
		}
		if (retValueu > 0) {
			req.setAD_User_ID(retValueu);
		} else {
			// set default user
			if (AD_User_ID > 0)
				req.setAD_User_ID(AD_User_ID);
		}
		
		// Look BP
		if (req.getAD_User_ID() > 0) {
			MUser us = new MUser(getCtx(), req.getAD_User_ID(), trxName);
			if (us.getC_BPartner_ID() > 0)
				req.setC_BPartner_ID(us.getC_BPartner_ID());
		}
		if (req.getC_BPartner_ID() <= 0 && C_BPartner_ID > 0) {
			// set default business partner
			req.setC_BPartner_ID(C_BPartner_ID);
		}
		
		// Set start date as sent date of e-mail
		req.setStartDate(new Timestamp(emailContent.sentDate.getTime()));
		
		// defaults priority Medium, confidentiality partner
		if (p_DefaultConfidentiality != null) {
			req.setConfidentialType (p_DefaultConfidentiality);
			req.setConfidentialTypeEntry (p_DefaultConfidentiality);
		}
		if (p_DefaultPriority != null) {
			req.setPriority(p_DefaultPriority);
			req.setPriorityUser(p_DefaultPriority);
		}
		
		req.saveEx(trxName);
		addLog(req.getR_Request_ID(), null, null, Msg.parseTranslation(getCtx(), "@Added@ @R_Request_ID@ ") + req.getDocumentNo(), MRequest.Table_ID, req.getR_Request_ID());
		
		if (log.isLoggable(Level.INFO)) log.info("created request " + req.getR_Request_ID() + " from msg -> " + emailContent.subject);
		
		if("H".equals(p_HTMLAttachmentType)) {
			String htmlContent = emailContent.getHtmlContent(true);
			if (htmlContent != null){
				MAttachment attach = req.createAttachment();
				
				attach.addEntry(emailContent.subject + ".html", emailContent.getHtmlContent(true).getBytes(Charset.forName("UTF-8")));
				attach.saveEx(trxName);
			}
		} else if("I".equals(p_HTMLAttachmentType)) {
			ArrayList<BodyPart> imagesList = emailContent.getHTMLImageBodyParts();
			if(imagesList != null) {
				for(BodyPart image: imagesList) {
					MAttachment attach = req.createAttachment();
					
					attach.addEntry(image.getFileName(), EmailSrv.getBinaryData(image));
					attach.saveEx(trxName);
				}
			}
		}
				
		for (BodyPart attachFile : emailContent.lsAttachPart){
			MAttachment attach = req.createAttachment();
			attach.addEntry(attachFile.getFileName(), EmailSrv.getBinaryData(attachFile));
			attach.saveEx(trxName);
		}
		
	}
	
	protected void updateRequest(int request_upd, EmailContent emailContent, String trxName) throws MessagingException, SQLException {
		MRequest requp = new MRequest(getCtx(), request_upd, trxName);
		// Body as result
		
		StringBuilder msgreq = new StringBuilder("FROM: ").append(emailContent.fromAddress.get(0)).append("\n").append(emailContent.getTextContent());
		requp.setResult(msgreq.toString());
		requp.saveEx(trxName);
		addLog(requp.getR_Request_ID(), null, null, Msg.parseTranslation(getCtx(), "@Updated@ @R_Request_ID@ ") + requp.getDocumentNo(), MRequest.Table_ID, requp.getR_Request_ID());
	}

	@Override
	public void processEmailError(EmailContent emailHeader, Message emailRaw,
			Store mailStore, Folder mailFolder) throws MessagingException {
		noError++;
		errorFolder.appendMessages(new Message[]{emailRaw});
	}

	@Override
	public List<Folder> getListFolder() {
		return lsFolderProcess;
	}

}	//	RequestEMailProcessor
