/*
 * @(#)Fxps.java
 *
 * Copyright (c) 2004 JSCAPE
 * 1147 S. 53rd Pl., Mesa, Arizona, 85206, U.S.A.
 * All rights reserved.
 *
 * This software is the confidential and proprietary information of
 * JSCAPE. ("Confidential Information").  You shall not disclose such
 * Confidential Information and shall use it only in accordance with
 * the terms of the license agreement you entered into with JSCAPE.
 */
package com.jscape.inet.ftps;

import com.jscape.inet.ftp.*;

import java.util.Enumeration;
import java.util.Vector;

/**
 * Implements the functionality of a basic FXP (FTP server to FTP server transfer)
 */
public class Fxps {

    private Vector listeners = new Vector();

    /**
     * Add FXP event listener
     *
     * @param listener a FxpListener
     *
     * @see com.jscape.inet.ftp.FxpListener
     */
    @SuppressWarnings("unchecked")
    public synchronized void addFxpListener(FxpListener listener) {
        listeners.addElement(listener);
    }

    /**
     * Remove FXP event listener.
     *
     * @param listener a FxpListener
     *
     * @see com.jscape.inet.ftp.FxpListener
     */
    public synchronized void removeFxpListener(FxpListener listener) {
        if (listeners.contains(listener)) {
            listeners.removeElement(listener);
        }
    }

    /**
     * Checks if source and destination servers are connected and establishes connection if needed
     *
     * @param source      the source FTP server
     * @param destination the destination FTP server
     *
     * @throws FtpException if an I/O or FTP related error occurs
     */
    private void connect(Ftps source, Ftps destination)
            throws FtpException {
        if (!source.isConnected()) {
            source.connect();
        }

        if (!destination.isConnected()) {
            destination.connect();
        }
    }

    /**
     * Transfer directory from source FTP server to destination FTP server
     *
     * @param source      the source FTP server
     * @param destination the destination FTP server
     * @param dir         the directory name
     *
     * @throws FtpException if an I/O or FTP related error occurs
     */
    public void transferDir(Ftps source, Ftps destination, String dir)
            throws FtpException {
        // establish connection if needed
        connect(source, destination);

        // error handling
        FtpException error = null;

        // get current source directory
        String currentSourceDir = source.getDir();

        // get current destination directory
        String currentDestDir = destination.getDir();


        // set source directory
        source.setDir(dir);

        // make destination directory
        try {
            destination.makeDir(dir);
        } catch (Exception e) {
            // ignore
        }

        // set destination directory
        destination.setDir(dir);

        try {
            // enumeration thru source listing
            Enumeration listing = source.getDirListing();
            while (listing.hasMoreElements()) {
                FtpFile file = (FtpFile) listing.nextElement();
                if (!file.isDirectory()) {
                    // transfer file
                    transfer(source, destination, file.getFilename());
                } else {
                    // transfer directory
                    transferDir(source, destination, file.getFilename());
                }
            }
        } catch (FtpException fe) {
            error = fe;
        } finally {
            source.setDir(currentSourceDir);
            destination.setDir(currentDestDir);
        }

        // throw any caught error
        if (error != null) {
            throw error;
        }
    }

    /**
     * Transfer file from source FTP server to destination FTP server
     *
     * @param source      the source FTP server
     * @param destination the destination FTP server
     * @param filename    the source filename
     *
     * @throws FtpException if an I/O or FTP related error occurs
     */
    public void transfer(Ftps source, Ftps destination, String filename)
            throws FtpException {
        // establish connection if needed
        connect(source, destination);
        boolean sscn = true;

        // check to see that both source and destination support SSCN command
        try {
            source.issueCommand("SSCN");
            destination.issueCommand("SSCN");
        } catch (FtpException e) {
            sscn = false;
        }

        if (sscn) {
            source.issueCommand("SSCN ON");
        } else {
            // set protection level to clear
            source.setProtectionLevel('C');
            destination.setProtectionLevel('C');
        }

        // fire event
        fireFxpStart(source, destination, filename);

        // issue PASV command to source server
        String pasvResponse = source.issueCommand("PASV");
        String pasvnumber = pasvResponse.substring(pasvResponse.indexOf("(") + 1, pasvResponse.lastIndexOf(")"));

        // issue PORT command to dest server
        destination.issueCommand("PORT " + pasvnumber);
        FxpsRetr srcThread = new FxpsRetr(this, source, destination, filename);
        srcThread.start();
        FxpsStor dstThread = new FxpsStor(this, source, destination, filename);
        dstThread.start();

        // wait until thread is finished before returning
        while (dstThread.isAlive()) {
            try {
                Thread.sleep(100);
            } catch (Exception e) {
                // ignore
            }
        }

        // fire end event
        fireFxpEnd(source, destination, filename);

        if (sscn) {
            source.issueCommand("SSCN OFF");
        } else {
            // change protection level back to protected
            source.setProtectionLevel('P');
            destination.setProtectionLevel('P');
        }
    }

    /**
     * Transfer files matching filter from source FTP server to destination FTP server
     *
     * @param source      the source FTP server
     * @param destination the destination FTP server
     * @param filter      the file filter
     *
     * @throws FtpException if an I/O or FTP related error occurs
     */
    public void mtransfer(Ftps source, Ftps destination, String filter)
            throws FtpException {
        Enumeration files = source.getDirListing();
        while (files.hasMoreElements()) {
            FtpFile file = (FtpFile) files.nextElement();
            String filename = file.getFilename();

            if (!file.isDirectory() && matches(filename, filter)) {
                transfer(source, destination, filename);
            }
        }
    }


    /**
     * Fires <code>FxpStartEvent</code>.
     *
     * @param source      the source Ftps instance
     * @param destination the destination Ftps instance
     * @param filename    the filename
     *
     * @see com.jscape.inet.ftp.FxpStartEvent
     */
    protected void fireFxpStart(Ftps source, Ftps destination, String filename) {
        Vector thisList;
        FxpStartEvent thisEvent = new FxpStartEvent(source, destination, filename);

        // Make a copy of the list so potential changes to it by
        // other threads won't affect traversal.
        synchronized (this) {
            thisList = (Vector) listeners.clone();
        }
        // Send a FxpStartEvent to every listener.
        for (int elem = 0; elem < thisList.size(); elem++) {
            ((FxpListener) thisList.elementAt(elem)).fxpStart(thisEvent);
        }
    }

    /**
     * Fires <code>FxpEndEvent</code>.
     *
     * @param source      the source Ftps instance
     * @param destination the destination Ftps instance
     * @param filename    the filename
     *
     * @see com.jscape.inet.ftp.FxpEndEvent
     */
    protected void fireFxpEnd(Ftps source, Ftps destination, String filename) {
        Vector thisList;
        FxpEndEvent thisEvent = new FxpEndEvent(source, destination, filename);

        // Make a copy of the list so potential changes to it by
        // other threads won't affect traversal.
        synchronized (this) {
            thisList = (Vector) listeners.clone();
        }
        // Send a FxpStartEvent to every listener.
        for (int elem = 0; elem < thisList.size(); elem++) {
            ((FxpListener) thisList.elementAt(elem)).fxpEnd(thisEvent);
        }
    }


    /**
     * Fires <code>FxpFailedEvent</code>.
     *
     * @param source      the source Ftps instance
     * @param destination the destination Ftps instance
     * @param filename    the filename
     * @param ex          the exception that caused this event
     *
     * @see com.jscape.inet.ftp.FxpFailedEvent
     */
    protected void fireFxpFailed(Ftps source, Ftps destination, String filename, Exception ex) {
        Vector thisList;
        FxpFailedEvent thisEvent = new FxpFailedEvent(source, destination, filename, ex);

        // Make a copy of the list so potential changes to it by
        // other threads won't affect traversal.
        synchronized (this) {
            thisList = (Vector) listeners.clone();
        }
        // Send a FxpFailedEvent to every listener.
        for (int elem = 0; elem < thisList.size(); elem++) {
            ((FxpListener) thisList.elementAt(elem)).fxpFailed(thisEvent);
        }
    }


    /**
     * Checks if file name matches the regular expression.
     *
     * @param file chacked file name
     * @param exp  regular expression
     *
     * @return true if passed file name matches the reqular expression.
     */
    private static boolean matches(String file, String exp) {
        final String pt = "*";
        return (
                (exp.equals(pt) || file.equals(exp))
                        || (exp.startsWith(pt) && file.endsWith(exp.substring(1)))
                        || (exp.endsWith(pt)
                        && file.startsWith(
                        exp.substring(0, exp.lastIndexOf(pt)))));
    }
}
