//----------------------------------------------------------------------------
//
// Module:      FesiFtp.java      
//
// Description: FesiFTP main program and GUI
//
// FESI Copyright (c) Jean-Marc Lugrin, 2000
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public
// License as published by the Free Software Foundation; either
// version 2 of the License, or (at your option) any later version.

// 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
// Lesser General Public License for more details.

// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
//----------------------------------------------------------------------------


// Possible enhancements:
//     removeMacro()
//     Noop, site, syst commands
//     listMacros, doMacro, doExit
//     onExit
//     abort xfer (from menu)
//     promptOK msg box
//     promptList box (for file selection)
//     All operations as macros, redefinable by user
//     "do" execution routines with optional prompt depending on parameters
//     asynchronous mode
//     getConnectionObject for user extensions
//     menu shortcuts
//
// Correction history
//    1.0 - 19 July 1998
//            Initial stable release
//
import java.awt.*;
import java.awt.event.*;
import java.io.*;

import com.oroinc.net.ftp.*;
import FESI.jslib.*;

/**
 * Provide an FTP client which is programmable via EcmaScript
 */
public class FesiFTP extends Frame implements FTPInterface {
    
    private TextArea textArea = null; // Used to log messages
    
    private String eol = System.getProperty("line.separator", "\n");

    // Current values
    private String server = null;
    private String username = null;
    private String password = null;
    
    // The controller (in the model-view-CONTROLLER sense)   
    private FTPController controller = null;
    
    // If true, a script is in progress - Must be changed by setBusy only !
    private boolean busyState = false;
    
    // The FTP connection
    private FTPClient ftp =  new FTPClient();

    // The local pseudo connection
    private LocalContext localContext;

    // Menus to insert new commands
    private MenuBar menubar;
    private Menu macroMenu;
    
    // The EcmaScript interpreter for macros
    private JSGlobalObject global = null;


    /**
     * Check if currently connected
     *
     * @return true if connected    
     */
    private boolean isConnected () {
        return ftp != null & ftp.isConnected();
    }
    

    /**
     * Append the FTP replies to the message log
     *
     */
    private void appendReplyStrings() {
        String[] replies = ftp.getReplyStrings();
        for (int i = 0; i<replies.length; i++) {
            textArea.append(replies[i] + eol);
        }
    }
    

    /**
     * log a message to the log window
     *
     * @param   text  
     */
     public void logMessage(String text) {
         textArea.append(text);
     }
     

    /**
     * log an error (prompt as a dialog with confirmation)
     *
     * @param   text  
     */
     public void logError(String text) {
         if (text==null) {
             text = "?? Missing error text - internal error ??";
         }
         MessageBox mb = new MessageBox(this, findCenter (), "Error", text);
         mb.show();
     }
     

    /**
     * Request a string parameter to the user. Returns
     * null if cancelled.
     *
     * @param   requestTitle  
     * @param   requestText  
     * @param   defaultAnswer  
     * @return  The reply or null
     */
    public String requestDialog (
                    String requestTitle, 
                    String requestText,
                    String defaultAnswer) {
         RequestDialog rd = new RequestDialog(this, findCenter (),
                        requestTitle,requestText, defaultAnswer);
         rd.show();
         return rd.reply;
    }
    

    /**
     * Request the connection information from the user. Returns
     * null if cancelled.
     *
     * @param   defaultServer  
     * @param   defaultUsername  
     * @param   defaultPassword  
     * @return  A ConnectionInfo or null
     */
    public ConnectionInfo requestConnectionInfo(String defaultServer,
                                                String defaultUsername,
                                                String defaultPassword) {
        ConnectDialog cd = new ConnectDialog(this, findCenter (),
                    defaultServer, defaultUsername, defaultPassword);
        cd.show();
        if (cd.server==null || cd.server.trim().equals("")) {
            return null;
        } else {
            return new ConnectionInfo(cd.server, cd.username, cd.password);
        }
    }
    

    /**
     * Add a macro with the specified name to the macro menu
     *
     * @param   name  The name of the macro
     * @param   text  The macro text
     */
    public void addMacro(String name, String text) {
        MenuItem miMacro = new MenuItem(name);
        final String macroText = text;
        final String macroName = name;
        miMacro.addActionListener( new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                if (!busyState) {
                    setBusy(true);
                     try {
                        Object result = global.eval(macroText); // Basic evaluation
                        // Object result = global.evalAsFunction(input); // Basic evaluation
                        if (result!=null) textArea.append(result.toString() + eol);
                    } catch (JSException ex) {
                        textArea.append("Error evaluating macro '" + macroName + "': " + ex.getMessage() + eol);
                    }
                    setBusy(false);
                }
            }
        });
        macroMenu.add(miMacro);

    }


    /**
     * Main program
     *
     * @param   args  Arguments (ignored)
     */
    public static void main (String args[]) {
        new FesiFTP();
    }
    
    

    /**
     * Create the FesiFTP object (a window)
     *
     */
    public FesiFTP() {
        super("FESI FTP");
        
        this.addWindowListener( new WindowAdapter() {
            public void windowClosing(WindowEvent e) {
                System.exit(0);
            }
        });
        
        try {
            localContext = new LocalContext();
        } catch (IOException e) {
            e.printStackTrace();
            System.exit(-1);
        }

        menubar = new MenuBar();         // Create a menubar.
        this.setMenuBar(menubar);                   // Add menubar to window.
    
        // File menu
        Menu fileMenu = new Menu("File");
        MenuItem miExit = new MenuItem("Exit");
        miExit.addActionListener( new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                if (!busyState) System.exit(0);
            }
        });
        fileMenu.add(miExit);
        
        menubar.add(fileMenu);                     // Add the menu to the menubar.
        
        // FTP menu
        Menu ftpMenu = new Menu("Remote");
        MenuItem miConnect = new MenuItem("Connect");
        miConnect.addActionListener( new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                if (!busyState) {
                    boolean mode = controller.changeExceptionMode(false);
                    setBusy(true);
                    controller.doConnect();
                    setBusy(false);
                    controller.setExceptionMode(mode);
                }
            }
        });
        ftpMenu.add(miConnect);
        
        MenuItem miDisconnect = new MenuItem("Disconnect");
        miDisconnect.addActionListener( new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                if (!busyState) {
                    boolean mode = controller.changeExceptionMode(false);
                    setBusy(true);
                    controller.doDisconnect();
                    setBusy(false);
                    controller.setExceptionMode(mode);
                }
            }
        });
        ftpMenu.add(miDisconnect);
        
        MenuItem miRPwd = new MenuItem("Print remote directory");
        miRPwd.addActionListener( new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                if (!busyState) {
                    boolean mode = controller.changeExceptionMode(false);
                    setBusy(true);
                    controller.doRemotePrintWorkingDirectory();
                    setBusy(false);
                    controller.setExceptionMode(mode);
                }
            }
        });
        ftpMenu.add(miRPwd);
        
        MenuItem miRCwd = new MenuItem("Change remote directory ...");
        miRCwd.addActionListener( new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                if (!busyState) {
                    boolean mode = controller.changeExceptionMode(false);
                    setBusy(true);
                    controller.doRemoteChangeWorkingDirectory();
                    setBusy(false);
                    controller.setExceptionMode(mode);
                }
            }
        });
        ftpMenu.add(miRCwd);
        
        MenuItem miRCpd = new MenuItem("Up remote directory");
        miRCpd.addActionListener( new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                if (!busyState) {
                    boolean mode = controller.changeExceptionMode(false);
                    setBusy(true);
                    controller.doRemoteChangeToParentDirectory();
                    setBusy(false);
                    controller.setExceptionMode(mode);
                }
            }
        });
        ftpMenu.add(miRCpd);
        
        MenuItem miRList = new MenuItem("List remote directory");
        miRList.addActionListener( new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                if (!busyState) {
                    boolean mode = controller.changeExceptionMode(false);
                    setBusy(true);
                    controller.doRemoteListDirectory();
                    setBusy(false);
                    controller.setExceptionMode(mode);
                }
            }
        });
        ftpMenu.add(miRList);
        
        MenuItem miRMakeDir = new MenuItem("Make remote directory");
        miRMakeDir.addActionListener( new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                if (!busyState) {
                    boolean mode = controller.changeExceptionMode(false);
                    setBusy(true);
                    controller.doRemoteMakeDirectory();
                    setBusy(false);
                    controller.setExceptionMode(mode);
                }
            }
        });
        ftpMenu.add(miRMakeDir);

        MenuItem miRRenFile = new MenuItem("Rename remote file");
        miRRenFile.addActionListener( new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                if (!busyState) {
                    boolean mode = controller.changeExceptionMode(false);
                    setBusy(true);
                    controller.doRemoteRenameFile();
                    setBusy(false);
                    controller.setExceptionMode(mode);
                }
            }
        });
        ftpMenu.add(miRRenFile);

        MenuItem miRDelFile = new MenuItem("Delete remote file");
        miRDelFile.addActionListener( new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                if (!busyState) {
                    boolean mode = controller.changeExceptionMode(false);
                    setBusy(true);
                    controller.doRemoteDeleteFile();
                    setBusy(false);
                    controller.setExceptionMode(mode);
                }
            }
        });
        ftpMenu.add(miRDelFile);

        MenuItem miRRmDir = new MenuItem("Remove remote directory");
        miRRmDir.addActionListener( new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                if (!busyState) {
                    boolean mode = controller.changeExceptionMode(false);
                    setBusy(true);
                    controller.doRemoteRemoveDirectory();
                    setBusy(false);
                    controller.setExceptionMode(mode);
                }
            }
        });
        ftpMenu.add(miRRmDir);

        menubar.add(ftpMenu);                     // Add the menu to the menubar.


        // Local menu
        Menu localMenu = new Menu("Local");
        
        MenuItem miLPwd = new MenuItem(" local directory");
        miLPwd.addActionListener( new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                if (!busyState) {
                    boolean mode = controller.changeExceptionMode(false);
                    setBusy(true);
                    controller.doLocalPrintWorkingDirectory();
                    setBusy(false);
                    controller.setExceptionMode(mode);
                }
            }
        });
        localMenu.add(miLPwd);
        
        MenuItem miLCwd = new MenuItem("Change local directory ...");
        miLCwd.addActionListener( new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                if (!busyState) {
                    boolean mode = controller.changeExceptionMode(false);
                    setBusy(true);
                    controller.doLocalChangeWorkingDirectory();
                    setBusy(false);
                    controller.setExceptionMode(mode);
                }
            }
        });
        localMenu.add(miLCwd);
        
        MenuItem miLCpd = new MenuItem("Up local directory");
        miLCpd.addActionListener( new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                if (!busyState) {
                    boolean mode = controller.changeExceptionMode(false);
                    setBusy(true);
                    controller.doLocalChangeToParentDirectory();
                    setBusy(false);
                    controller.setExceptionMode(mode);
                }
            }
        });
        localMenu.add(miLCpd);

        MenuItem miLList = new MenuItem("List local directory");
        miLList.addActionListener( new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                if (!busyState) {
                    boolean mode = controller.changeExceptionMode(false);
                    setBusy(true);
                    controller.doLocalListDirectory();
                    setBusy(false);
                    controller.setExceptionMode(mode);
                }
            }
        });
        localMenu.add(miLList);

        MenuItem miLMakeDir = new MenuItem("Make local directory");
        miLMakeDir.addActionListener( new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                if (!busyState) {
                    boolean mode = controller.changeExceptionMode(false);
                    setBusy(true);
                    controller.doLocalMakeDirectory();
                    setBusy(false);
                    controller.setExceptionMode(mode);
                }
            }
        });
        localMenu.add(miLMakeDir);

        MenuItem miLRenFile = new MenuItem("Rename local file");
        miLRenFile.addActionListener( new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                if (!busyState) {
                    boolean mode = controller.changeExceptionMode(false);
                    setBusy(true);
                    controller.doLocalRenameFile();
                    setBusy(false);
                    controller.setExceptionMode(mode);
                }
            }
        });
        localMenu.add(miLRenFile);

        MenuItem miLDelFile = new MenuItem("Delete local file");
        miLDelFile.addActionListener( new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                if (!busyState) {
                    boolean mode = controller.changeExceptionMode(false);
                    setBusy(true);
                    controller.doLocalDeleteFile();
                    setBusy(false);
                    controller.setExceptionMode(mode);
                }
            }
        });
        localMenu.add(miLDelFile);

        MenuItem miLLRmDir = new MenuItem("Remove local directory");
        miLLRmDir.addActionListener( new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                if (!busyState) {
                    boolean mode = controller.changeExceptionMode(false);
                    setBusy(true);
                    controller.doLocalRemoveDirectory();
                    setBusy(false);
                    controller.setExceptionMode(mode);
                }
            }
        });
        localMenu.add(miLLRmDir);

        menubar.add(localMenu);                     // Add the menu to the menubar.

        // Xfer menu
        Menu xferMenu = new Menu("Transfer");

        MenuItem miGetText = new MenuItem("Get text file");
        miGetText.addActionListener( new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                if (!busyState) {
                    boolean mode = controller.changeExceptionMode(false);
                    setBusy(true);
                    controller.doGetTextFile();
                    setBusy(false);
                    controller.setExceptionMode(mode);
                }
            }
        });
        xferMenu.add(miGetText);
        
        MenuItem miPutText = new MenuItem("Store text file");
        miPutText.addActionListener( new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                if (!busyState) {
                    boolean mode = controller.changeExceptionMode(false);
                    setBusy(true);
                    controller.doSendTextFile();
                    setBusy(false);
                    controller.setExceptionMode(mode);
                }
            }
        });
        xferMenu.add(miPutText);

        MenuItem miGetBinary = new MenuItem("Get binary file");
        miGetBinary.addActionListener( new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                if (!busyState) {
                    boolean mode = controller.changeExceptionMode(false);
                    setBusy(true);
                    controller.doGetBinaryFile();
                    setBusy(false);
                    controller.setExceptionMode(mode);
                }
            }
        });
        xferMenu.add(miGetBinary);
        
        MenuItem miPutBinary = new MenuItem("Store binary file");
        miPutBinary.addActionListener( new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                if (!busyState) {
                    boolean mode = controller.changeExceptionMode(false);
                    setBusy(true);
                    controller.doSendBinaryFile();
                    setBusy(false);
                    controller.setExceptionMode(mode);
                }
            }
        });
        xferMenu.add(miPutBinary);
  
        menubar.add(xferMenu);                     // Add the menu to the menubar.
 
        // Macro menu
        macroMenu = new Menu("Macros");
        menubar.add(macroMenu);                     // Add the menu to the menubar.

    
        // Text log area
        textArea = new TextArea("", 24, 80);
        textArea.setFont(new Font("MonoSpaced", Font.PLAIN, 12));
        textArea.setEditable(false);
        this.add("Center", textArea);
        
        // setSize(600, 300);                     // Set the window size.
        this.pack();                              // Will force size of text area
        Dimension    dimScreen = getToolkit ().getScreenSize ();
        Dimension    dimWindow = getSize (); 
        setLocation (dimScreen.width/2 - dimWindow.width/2, // Center screen
                         dimScreen.height/2 - dimWindow.height/2);
                             
        setBusy(true);                // To wait until initialization is complete
        this.show();                                // Pop the window up.
             
             
        // Setup controller
        controller = new FTPController(this);

        // Announce        
        textArea.append("FesiFTP - Version 1.0 - 19-July-1998" + eol);
        
            
        // Build the interpreter with common extensions
        String[] extensions = new String[] {"FESI.Extensions.BasicIOw",
                                            "FESI.Extensions.FileIO",
                                            "FESI.Extensions.JavaAccess",
                                            "FESI.Extensions.Database"
                                            };
        try {
            global = JSUtil.makeEvaluator(extensions);
            global.setMember("ftp",controller);
            global.eval("function joinAll(a){var x=''; for (var i in a) {x+=a[i]}}");
            global.eval("function write() {ftp.logMessage(joinAll(arguments));}");
            global.eval("function writeln() {ftp.logMessageEol(joinAll(arguments));}");
            
            // Load the autoLoad file if needed
            File autoload = new File("autoload.esftp");
            if (autoload.exists()) {
                
                textArea.append("Loading autoload.esftp..." + eol);
                 
                // Create the stream to interpret
                String wholeFile = "";
                String input = null;
                FileReader inr = null;
                try { 
                    inr = new FileReader(autoload);
                } catch (FileNotFoundException e) {
                    textArea.append("Error: Cannot open autoload.esftp" + eol); 
                    wholeFile = null; // as error indicator                   
                }
                // Evaluate and  if any result
                try {
                    Object result = global.eval(inr,"File autoload.esftp"); // Basic evaluation
                    // Object result = global.evalAsFunction(input); // Basic evaluation
                    if (result!=null) textArea.append(result.toString() + eol);
                } catch (JSException e) {
                    textArea.append("Error evaluating autoload.esftp: " + e.toString() + eol);
                }
                try {
                    inr.close();
                } catch (IOException ignore) {
                }

            } else {
                textArea.append("No file " + System.getProperty("user.dir", ".") +
                                     File.separator + "autoload.esftp" + eol);
            }
        } catch (JSException e) {
            textArea.append("Cannot initialize FESI");
            textArea.append("EcmaScript exception: " + e.getMessage() + eol);
        }
        setBusy(false);
    }
    
    
    

    /**
     * Find the center of this frame
     *
     * @return  The center point   
     */
    private Point findCenter ()
    {
        return (new Point ((getLocation ().x + (getSize ().width / 2)), 
                           (getLocation ().y + (getSize ().height / 2))));        
    }


    /**
     * Set or clear the busy flag, changing the cursor as well
     *
     * @param   state true if busy, false if not 
     */
    private void setBusy(boolean state) {
        this.busyState = busyState;
        if (state) {
            setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
            textArea.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
            //menuBar.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
        } else {
            setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
            textArea.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
            //menuBar.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
        }
    }
}