/*
 * @(#)Fxp.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.ftp;

import com.jscape.util.StringTools;
import com.jscape.util.regex.JDKMatcherFactory;
import com.jscape.util.regex.Matcher;
import com.jscape.util.regex.MatcherFactory;

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


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

    private static final MatcherFactory REGEX_FACTORY = new JDKMatcherFactory();

    private Vector listeners = new Vector();

    /**
     * Creates a new Fxp instance
     */
    public Fxp() {
    }

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

        synchronized (this) {
            this.listeners = listeners;
        }
    }

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

        Vector listeners = (Vector) this.listeners.clone();
        listeners.removeElement(listener);

        synchronized (this) {
            this.listeners = listeners;
        }
    }


    /**
     * 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(Ftp source, Ftp 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(Ftp source, Ftp 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(Ftp source, Ftp destination, String filename)
            throws FtpException {
        // establish connection if needed
        connect(source, destination);

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

        // issue PASV command to source server
        String pasvr = source.issueCommand("PASV");

        StringTokenizer st = new StringTokenizer(pasvr);
        for (int i = 0; i < 4; ++i) {
            st.nextToken();
        }

        String pasvnumber = st.nextToken();
        pasvnumber = pasvnumber.substring(1, pasvnumber.lastIndexOf(")"));

        // issue PORT command to dest server
        destination.issueCommand("PORT " + pasvnumber);
        FxpRetr srcThread = new FxpRetr(this, source, destination, filename);
        srcThread.start();
        FxpStor dstThread = new FxpStor(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);
    }

    /**
     * Transfer files matching regular expression from source FTP server to destination FTP server.
     * For example, to transfer files ending with extension .txt use the regular expression ".*\\.txt".
     *
     * @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(Ftp source, Ftp destination, String filter)
            throws FtpException {
        Matcher regex = createRegex(filter);

        for (Enumeration files = source.getDirListing(); files.hasMoreElements(); ) {
            FtpFile file = (FtpFile) files.nextElement();
            String filename = file.getFilename();

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


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

        for (int i = 0; i < this.listeners.size(); ++i) {
            ((FxpListener) this.listeners.elementAt(i)).fxpStart(event);
        }
    }

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

        for (int i = 0; i < this.listeners.size(); ++i) {
            ((FxpListener) this.listeners.elementAt(i)).fxpEnd(event);
        }
    }


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

        for (int i = 0; i < this.listeners.size(); ++i) {
            ((FxpListener) this.listeners.elementAt(i)).fxpFailed(event);
        }
    }

    private Matcher createRegex(String filter)
            throws FtpException {
        try {
            return StringTools.isValidString(filter)
                   ? REGEX_FACTORY.matcherFor(filter)
                   : Matcher.POSITIVE;
        } catch (Exception e) {
            throw new FtpException(e);
        }
    }
}
