/*
 * This file is part of muCommander, http://www.mucommander.com
 *
 * muCommander is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 3 of the License, or
 * (at your option) any later version.
 *
 * muCommander 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, see <http://www.gnu.org/licenses/>.
 */

package com.mucommander.job.impl;

import com.mucommander.commons.file.AbstractFile;
import com.mucommander.commons.file.MimeTypes;
import com.mucommander.commons.file.util.FileSet;
import com.mucommander.commons.io.StreamUtils;
import com.mucommander.commons.io.base64.Base64OutputStream;
import com.mucommander.conf.MuConfigurations;
import com.mucommander.conf.MuPreference;
import com.mucommander.conf.MuPreferences;
import com.mucommander.job.FileJobState;
import com.mucommander.text.Translator;
import com.mucommander.ui.dialog.file.ProgressDialog;
import com.mucommander.ui.main.MainFrame;

import java.io.*;
import java.net.Socket;
import java.util.List;
import java.util.StringTokenizer;
import java.util.Vector;


/**
 * This job sends one or several files by email.
 *
 * @author Maxence Bernard
 */
public class SendMailJob extends TransferFileJob {

    /** True after connection to mail server has been established */
    private boolean connectedToMailServer;

    /** Error dialog title */
    private String errorDialogTitle;
	
	
    /////////////////////
    // Mail parameters //
    /////////////////////

    /** Email recipient(s) */
    private String recipientString;
    /** Email subject */
    private String mailSubject;
    /** Email body */
    private String mailBody;

    /** SMTP server */
    private String mailServer;
    /** From name */
    private String fromName;
    /** From address */
    private String fromAddress;
	
    /** Email boundary string, delimits the end of the body and attachments */
    private String boundary;

    /** Connection variable */
    private BufferedReader in;
    /** OutputStream to the SMTP server */
    private OutputStream out;
    /** Base64OutputStream to the SMTP server */
    private Base64OutputStream out64;
    /** Socket connection to the SMTP server */
    private Socket socket;

	
    private final static String CLOSE_TEXT = Translator.get("close");
    private final static int CLOSE_ACTION = 11;
	
	
    public SendMailJob(ProgressDialog progressDialog, MainFrame mainFrame, FileSet filesToSend, String recipientString, String mailSubject, String mailBody) {
        super(progressDialog, mainFrame, filesToSend);

        this.boundary = "mucommander"+System.currentTimeMillis();
        this.recipientString = recipientString;
        this.mailSubject = mailSubject;
        this.mailBody = mailBody+"\n\n"+"Sent by muCommander - http://www.mucommander.com\n";

        this.mailServer = MuConfigurations.getPreferences().getVariable(MuPreference.SMTP_SERVER);
        this.fromName = MuConfigurations.getPreferences().getVariable(MuPreference.MAIL_SENDER_NAME);
        this.fromAddress = MuConfigurations.getPreferences().getVariable(MuPreference.MAIL_SENDER_ADDRESS);
    
        this.errorDialogTitle = Translator.get("email_dialog.error_title");
    }

    /**
     * Returns true if mail preferences have been set.
     */
    public static boolean mailPreferencesSet() {
        return MuConfigurations.getPreferences().isVariableSet(MuPreference.SMTP_SERVER)
            && MuConfigurations.getPreferences().isVariableSet(MuPreference.MAIL_SENDER_NAME)
            && MuConfigurations.getPreferences().isVariableSet(MuPreference.MAIL_SENDER_ADDRESS);
    }


    /**
     * Shows an error dialog with a single action : close, and stops the job.
     */
    private void showErrorDialog(String message) {
        showErrorDialog(errorDialogTitle, message, new String[]{CLOSE_TEXT}, new int[]{CLOSE_ACTION});
        interrupt();
    }
	
	
    /////////////////////////////////////////////
    // Methods taking care of sending the mail //
    /////////////////////////////////////////////

    private void openConnection() throws IOException {
        this.socket = new Socket(mailServer, MuConfigurations.getPreferences().getVariable(MuPreference.SMTP_PORT, MuPreferences.DEFAULT_SMTP_PORT));
        this.in = new BufferedReader(new InputStreamReader(socket.getInputStream(), "UTF-8"));
        this.out = socket.getOutputStream();
        this.out64 = new Base64OutputStream(out, true);
		
        this.connectedToMailServer = true;
    }

    private void sendBody() throws IOException {
        // here you are supposed to send your username
        readWriteLine("HELO muCommander");
        // warning : some mail server validate the sender address and will fail is an invalid
        // address is provided
        readWriteLine("MAIL FROM: "+fromAddress);
		
        List<String> recipients = new Vector<String>();
        recipientString = splitRecipientString(recipientString, recipients);
        int nbRecipients = recipients.size();
        for(int i=0; i<nbRecipients; i++)
            readWriteLine("RCPT TO: <"+recipients.get(i)+">" );
        readWriteLine("DATA");
        writeLine("MIME-Version: 1.0");
        writeLine("Subject: "+this.mailSubject);
        writeLine("From: "+this.fromName+" <"+this.fromAddress+">");
        writeLine("To: "+recipientString);
        writeLine("Content-Type: multipart/mixed; boundary=\"" + boundary +"\"");
        writeLine("\r\n--" + boundary);

        // Send the body
        //        writeLine( "Content-Type: text/plain; charset=\"us-ascii\"\r\n");
        writeLine("Content-Type: text/plain; charset=\"utf-8\"\r\n");
        writeLine(this.mailBody+"\r\n\r\n");
        writeLine("\r\n--" +  boundary );        
    }
    

    /**
     * Parses the specified string, replaces delimiter characters if needed and adds recipients  (String instances) to the given Vector.
     *
     * @param recipientsStr String containing one or several recipients that need to be separated by ',' and/or ';' characters.
     */
    private String splitRecipientString(String recipientsStr, List<String> recipients) {

        // /!\ this piece of code is far from being bullet proof but I'm too lazy now to rewrite it
        StringBuilder newRecipientsSb = new StringBuilder();
        StringTokenizer st = new StringTokenizer(recipientsStr, ",;");
        String rec;
        int pos1, pos2;
        while(st.hasMoreTokens()) {
            rec = st.nextToken().trim();
            if((pos1=rec.indexOf('<'))!=-1 && (pos2=rec.indexOf('>', pos1+1))!=-1)
                recipients.add(rec.substring(pos1+1, pos2));
            else
                recipients.add(rec);

            newRecipientsSb.append(rec);
            if(st.hasMoreTokens())
                newRecipientsSb.append(", ");
        }
		
        return newRecipientsSb.toString();
    }
	
	
    /**
     * Send file as attachment encoded in Base64, and returns true if file was successfully
     * and completely transferred.
     */ 
    private void sendAttachment(AbstractFile file) throws IOException {
        InputStream fileIn = null;
        try {
            // Send MIME type of attachment file
            String mimeType = MimeTypes.getMimeType(file);
            // Default mime type
            if(mimeType==null)
                mimeType = "application/octet-stream";
            writeLine("Content-Type:"+mimeType+"; name="+file.getName());
            writeLine("Content-Disposition: attachment;filename=\""+file.getName()+"\"");
            writeLine("Content-transfer-encoding: base64\r\n");
            fileIn = setCurrentInputStream(file.getInputStream());
            
            // Write file to socket
            StreamUtils.copyStream(fileIn, out64);
	
            // Writes padding bytes without closing the stream.
            out64.writePadding();
	
            writeLine("\r\n--" + boundary);
        }
        finally {
            if(fileIn!=null)
                fileIn.close();
        }
    }
	
    private void sayGoodBye() throws IOException {
        writeLine("\r\n\r\n--" + boundary + "--\r\n");
        readWriteLine(".");
        readWriteLine("QUIT");
    }


    private void closeConnection() {
        try {
            socket.close();
            in.close();
            out64.close();
        }
        catch(Exception e){
        }
    }
    
    private void readWriteLine(String s) throws IOException {
        out.write((s + "\r\n").getBytes("UTF-8"));
        in.readLine();
    }

    private void writeLine(String s) throws IOException {
        out.write((s + "\r\n").getBytes("UTF-8"));
    }


    ////////////////////////////////////
    // TransferFileJob implementation //
    ////////////////////////////////////

    @Override
    protected boolean processFile(AbstractFile file, Object recurseParams) {
        if (getState() == FileJobState.INTERRUPTED)
            return false;

        // Send file attachment
        try {
            sendAttachment(file);
        }
        catch(IOException e) {
            showErrorDialog(Translator.get("email.send_file_error", file.getName()));
            return false;
        }

        // If this was the last file, notify the mail server that the mail is over
        if(getCurrentFileIndex()==getNbFiles()-1) {
            try {
                // Say goodbye to the server
                sayGoodBye();
            }
            catch(IOException e) {
                showErrorDialog(Translator.get("email.goodbye_failed"));
                return false;
            }
        }

        return true;
    }

    @Override
    protected boolean hasFolderChanged(AbstractFile folder) {
        // This job does not modify anything
        return false;
    }


    ///////////////////////
    // Overridden method //
    ///////////////////////

    /**
     * This method is called when this job starts, before the first call to {@link #processFile(AbstractFile,Object) processFile()} is made.
     * This method here does nothing but it can be overridden by subclasses to perform some first-time initializations.
     */
    @Override
    protected void jobStarted() {
        super.jobStarted();

        // Open socket connection to the mail server, and say hello
        try {
            openConnection();
        }
        catch(IOException e) {
            showErrorDialog(Translator.get("email.server_unavailable", mailServer));
        }

        if (getState() == FileJobState.INTERRUPTED)
            return;

        // Send mail body
        try {
            sendBody();
        }
        catch(IOException e) {
            showErrorDialog(Translator.get("email.connection_closed"));
        }
    }


    /**
     * Method overridden to close connection to the mail server.
     */
    @Override
    protected void jobStopped() {
        super.jobStopped();

        // Close the connection
        closeConnection();
    }

    @Override
    public String getStatusString() {
        if(connectedToMailServer)
            return Translator.get("email.sending_file", getCurrentFilename());
        else
            return Translator.get("email.connecting_to_server", mailServer);
    }
}
