/**
 * Copyright (c) 2004-2005, Regents of the University of California
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * Redistributions of source code must retain the above copyright notice,
 * this list of conditions and the following disclaimer.
 *
 * Redistributions in binary form must reproduce the above copyright
 * notice, this list of conditions and the following disclaimer in the
 * documentation and/or other materials provided with the distribution.
 *
 * Neither the name of the University of California, Los Angeles nor the
 * names of its contributors may be used to endorse or promote products
 * derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

package avrora.gui;

import avrora.Defaults;
import avrora.core.LoadableProgram;
import avrora.sim.Simulation;
import avrora.sim.platform.PlatformFactory;
import cck.util.Option;
import cck.util.Util;
import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.io.File;
import java.io.Serializable;
import java.util.*;


/**
 * From a high level view, this class handles "input to the simulator," 
 * which entails entering files that can be loaded as program as well as 
 * specifiying options.  Node locations and monitor selection are NOT
 * handeled by this class.  This class is now only utilized by the
 * menu bar and there are no visual elements besides dialog boxes that this 
 * class owns.
 */
public class ManageSimInput {

    File currentFileAllInfo;
    JLabel currentFile;

    JButton openFile;

    JFileChooser fc;
    int stageForFileSelection; //we display different screens depending on where the user is
    JButton nextButton;
    JButton backButton;

    JDialog setOptionsDialog;
    LinkedList<Serializable> optionsDialogValues; //holds the current values we are setting in the options dialog
    JButton optionsDialogUpdate;

    JDialog fileSelectionDialog;
    JButton fileSelectionDialogUpdate;
    SpinnerNumberModel numOfNodesSpinner;


    /**
     * This is the "constructor" for this class.  It inits all the dialog boxes where 
     * appropiate
     * @return An object of ManageSimInput which can then be used to input to sim
     */
    public static ManageSimInput createManageSimInput() {
        ManageSimInput thesetup = new ManageSimInput();

        //Set up file chooser box...so if user clicks open file it opens a filechooser
        thesetup.fc = new JFileChooser();

        return thesetup;
    }

    /**
     * This creates the dialog box that asks for sim options
     * Called by SimMenuBar
     */
    public void createSetOptionsDialog() {
        //Make sure we have nice window decorations.
        //initLookAndFeel();
        JDialog.setDefaultLookAndFeelDecorated(true);

        setOptionsDialog = new JDialog(AvroraGui.instance.masterFrame, "Set Simulator Options");

        //Now we create a JPanel that will be linked to the dialog
        JPanel internalPanel = new JPanel();
        internalPanel.setOpaque(true);
        internalPanel.setLayout(new BorderLayout());

        //Let's add a title banner
        JLabel dialogBanner = new JLabel();
        dialogBanner.setText("Close window to cancel");
        internalPanel.add(dialogBanner, BorderLayout.NORTH);

        JPanel belowBannerPanel = new JPanel();
        belowBannerPanel.setLayout(new GridLayout(AvroraGui.instance.getOptionList().size(), 1));

        //Now we go to VisualAction and grab a list of the options
        //that we can set
        optionsDialogValues = new LinkedList<Serializable>();
        for (Option currentOption : AvroraGui.instance.getOptionList()) {
            //NOTE: YOU COULD DETECT SPECIAL CASE HERE (e.g. if(option.getName().equals("MY SPECIAL OPTION"));
            belowBannerPanel.add(addOption(currentOption));
        }

        //Border of 30 on the outside
        belowBannerPanel.setBorder(BorderFactory.createEmptyBorder(30, 30, 30, 30));

        internalPanel.add(belowBannerPanel, BorderLayout.CENTER);

        //Add the "Update" button
        optionsDialogUpdate = new JButton();
        optionsDialogUpdate.setText("Update");
        optionsDialogUpdate.setToolTipText("Click to update the simulator options");
        optionsDialogUpdate.addActionListener(AvroraGui.instance);
        internalPanel.add(optionsDialogUpdate, BorderLayout.SOUTH);
        internalPanel.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));

        //Add the panel to the dialog
        setOptionsDialog.setContentPane(internalPanel);

        //Sizes things appropiatly
        setOptionsDialog.pack();

    }

    /**
     * This creates the dialog box that adds files ("nodes") to the simulator
     */
    public void createFileSelectionDialog() {

        stageForFileSelection = 0; //so we start on the first page.


        //We should init all the stuff here
        //The stuff that is init below is NOT necessarily displayed
        //at this time
        //button that holds "Open File"
        openFile = new JButton();
        openFile.setToolTipText("Get a simulation file");
        openFile.setMaximumSize(new Dimension(75, 40));
        openFile.addActionListener(AvroraGui.instance);
        openFile.setText("Select File");

        //The label that displays/stores the file name
        currentFile = new JLabel("<SELECT A FILE>");
        currentFile.setHorizontalAlignment(JLabel.CENTER);
        //Set border so its not too close to the button

        //Set up file chooser box...so if user clicks open file it opens a filechooser
        fc = new JFileChooser();

        //Set up the spinner
        numOfNodesSpinner = new SpinnerNumberModel();
        numOfNodesSpinner.setValue(new Integer(1));
        numOfNodesSpinner.setMinimum(new Integer(1));
        numOfNodesSpinner.setStepSize(new Integer(1));
        numOfNodesSpinner.addChangeListener(AvroraGui.instance);

        //Add the Update Button
        fileSelectionDialogUpdate = new JButton();
        fileSelectionDialogUpdate.setText("Update");
        fileSelectionDialogUpdate.setToolTipText("Click to add the node(s) to the sim");
        fileSelectionDialogUpdate.addActionListener(AvroraGui.instance);

        //Add the next button
        nextButton = new JButton();
        nextButton.setText("NEXT>>");
        nextButton.addActionListener(AvroraGui.instance);

        //Add the back button
        backButton = new JButton();
        backButton.setText("<<BACK");
        backButton.addActionListener(AvroraGui.instance);

        //We should reset the "currentFile" data
        currentFileAllInfo = null;

        //Make sure we have nice window decorations.
        //initLookAndFeel();
        JDialog.setDefaultLookAndFeelDecorated(true);
        fileSelectionDialog = new JDialog(AvroraGui.instance.masterFrame, "Add Nodes to Simulation");

        updateFileSelectionDialog();
    }

    private void updateFileSelectionDialog() {

        //Now we create a JPanel that will be linked to the dialog
        JPanel internalPanel = new JPanel();
        internalPanel.setOpaque(true);
        internalPanel.setLayout(new BorderLayout());

        JPanel belowBannerPanel = new JPanel();
        JPanel navigationPanel = new JPanel();

        //so the top is the same no matter what...
        //depending on what page is displayed, that determines the next step
        if (stageForFileSelection == 0) {
            //Let's add a title banner
            JLabel dialogBanner = new JLabel();
            dialogBanner.setText("Step 1: Specify File");
            internalPanel.add(dialogBanner, BorderLayout.NORTH);

            //Create the actual layout
            belowBannerPanel.setLayout(new BorderLayout());
            JPanel tempPanel = new JPanel();
            tempPanel.add(openFile);
            belowBannerPanel.add(tempPanel, BorderLayout.NORTH);
            belowBannerPanel.add(currentFile, BorderLayout.SOUTH);

            //We want the navigation panel to read "NEXT>>"
            navigationPanel.setLayout(new BorderLayout());
            navigationPanel.add(nextButton, BorderLayout.EAST);

        } else if (stageForFileSelection == 1) {
            //Let's add a title banner
            JLabel dialogBanner = new JLabel();
            dialogBanner.setText("Step 2: Specify Number of Nodes");
            internalPanel.add(dialogBanner, BorderLayout.NORTH);

            belowBannerPanel.setLayout(new BorderLayout());
            belowBannerPanel.add(currentFile, BorderLayout.NORTH);
            JLabel userInstr = new JLabel("Select the number of duplicate nodes based upon selected file");
            currentFile.setHorizontalAlignment(JLabel.CENTER);
            belowBannerPanel.add(userInstr, BorderLayout.CENTER);
            JSpinner tempSpin = new JSpinner(numOfNodesSpinner);
            JPanel tempPanel2 = new JPanel();
            tempPanel2.add(tempSpin);
            belowBannerPanel.add(tempPanel2, BorderLayout.SOUTH);

            navigationPanel.setLayout(new BorderLayout());
            JPanel tempPanel = new JPanel();
            tempPanel.setLayout(new FlowLayout());
            tempPanel.add(backButton);
            tempPanel.add(fileSelectionDialogUpdate);
            navigationPanel.add(tempPanel, BorderLayout.EAST);
        }

        internalPanel.add(belowBannerPanel, BorderLayout.CENTER);
        internalPanel.add(navigationPanel, BorderLayout.SOUTH);

        internalPanel.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));

        //Set reasonable size
        internalPanel.setPreferredSize(new Dimension(400, 150));

        //Add the panel to the dialog
        fileSelectionDialog.setContentPane(internalPanel);

        //Sizes things appropiatly
        fileSelectionDialog.pack();
        fileSelectionDialog.validate();

    }


    /**
     * Called when first init the dialog box..it actually adds the look
     * of the option to the box
     * Each "type" of option has a special case here: that is, if it's a boolean option
     * we provide it with a checkbox; if it's a numeric option we provide it with a numeric spinner
     *
     * @param theOption The Option that we need to create a visual widget for
     *
     * @return A visual widget that can be displayed (it will vary depending on option type)
     */
    private Component addOption(Option theOption) {
        if (theOption instanceof Option.Bool) {
            JCheckBox theCheckBox = new JCheckBox(theOption.getName());
            theCheckBox.setSelected(((Option.Bool) theOption).get());
            theCheckBox.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
            optionsDialogValues.add(theCheckBox);

            //Tool tip setup
            //Couldn't get a method that was worth anything to work here
            //The problem is that you can only print help to the terminal...you
            //can't access an option's help string....it's protected.  Any way around it
            //would just be a hack...maybe look into changing the Option class if this feature is important
            //would just need to add a "getHelpString()" function
            //theCheckBox.setToolTipText(OptionExtend.getDescription(theOption));

            return theCheckBox;
        } else if (theOption instanceof Option.Double) {
            JPanel outerShell = new JPanel();
            outerShell.setLayout(new BorderLayout());
            //This spinner module allows the user to use up and down arrows to change seconds values
            SpinnerNumberModel theSpinner = new SpinnerNumberModel();
            theSpinner.setValue(new Double(((Option.Double) theOption).get()));

            JLabel spinnerTitle = new JLabel();
            spinnerTitle.setText(theOption.getName() + " ");

            outerShell.add(spinnerTitle, BorderLayout.WEST);
            outerShell.add(new JSpinner(theSpinner), BorderLayout.CENTER);
            outerShell.setPreferredSize(new Dimension(150, 30));
            outerShell.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
            optionsDialogValues.add(theSpinner);
            return outerShell;
        } else if (theOption instanceof Option.Str) {
            JPanel outerShell = new JPanel();
            outerShell.setLayout(new BorderLayout());
            JTextField theText = new JTextField();
            theText.setText(((Option.Str) theOption).get());

            JLabel textBoxTitle = new JLabel();
            textBoxTitle.setText(theOption.getName() + " ");

            outerShell.add(textBoxTitle, BorderLayout.WEST);
            outerShell.add(theText, BorderLayout.CENTER);
            outerShell.setPreferredSize(new Dimension(150, 30));
            outerShell.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
            optionsDialogValues.add(theText);
            return outerShell;
        }
        return null;
    }

    /**
     * This actually is called once Update is pressed - it updates the
     * option's value
     *
     * @param theOption The option that will physically be changed
     * @param theComponent The visual widget, passed so we know the new option value
     */
    private void updateOption(Option theOption, Object theComponent) {
        if (theOption instanceof Option.Bool) {
            //If bool, then it's a checkbox
            theOption.set(Boolean.toString(((JCheckBox) theComponent).isSelected()));
        } else if (theOption instanceof Option.Double) {
            //first get the spinnner model
            SpinnerNumberModel tempSpinnerModel = (SpinnerNumberModel)theComponent;
            //frikin types
            theOption.set(Double.toString(tempSpinnerModel.getNumber().doubleValue()));
        } else if (theOption instanceof Option.Str) {
            //it's a JTextField
            JTextField theText = (JTextField)theComponent;
            theOption.set(theText.getText());
        }

    }

    /**
     * This function sees if an event was caused by
     * this panel.  If so, it reacts to it by calling other methods.
     *
     * @return true if indeed this class caused the event
     */
    public boolean checkAndDispatch(ActionEvent e) {
        //if open file button was pushed...load file chooser

        if (e.getSource() == optionsDialogUpdate) {
            return optionsUpdate();
        } else if (e.getSource() == openFile) {
            return openFileUpdate();
        } else if (e.getSource() == fileSelectionDialogUpdate) {
            return fileSelectionUpdate();
        } else if (e.getSource() == nextButton) {
            return nextButtonUpdate();
        } else if (e.getSource() == backButton) {
            return backButtonUpdate();
        } else {
            return false; //this module did not cause the action;
        }
    }

    private boolean openFileUpdate() {
        int returnVal = fc.showOpenDialog(null); //will center with screen, not window

        if (returnVal == JFileChooser.APPROVE_OPTION) {
            currentFileAllInfo = fc.getSelectedFile();
        } else {
            //User cancelled the open file request, really we just do nothing
        }
        return true;
    }

    // unused
/*    private boolean setOptionsUpdate() {
        createSetOptionsDialog();
        setOptionsDialog.setLocationRelativeTo(null); //center on screen
        setOptionsDialog.setVisible(true);
        return true;
    }
*/
    private boolean optionsUpdate() {
        ListIterator<Serializable> componentIter = optionsDialogValues.listIterator(0);

        for (Option currentOption : AvroraGui.instance.getOptionList()) {
            //NOTE: YOU COULD DETECT SPECIAL CASE HERE (e.g. if(option.getName().equals("MY SPECIAL OPTION"));

            updateOption(currentOption, componentIter.next());
        }
        //Destroy window
        setOptionsDialog.setVisible(false); //so really, this doesn't destroy it, but when the button is pushed
        //again, it will just re-create the dialog using the VisualAction values
        //This is slightly messy...the dialog stays around and then
        //get regenerated upon another button push


        return true;
    }

    private boolean backButtonUpdate() {
        stageForFileSelection = 0;
        updateFileSelectionDialog();
        return true;
    }

    private boolean nextButtonUpdate() {
        if (currentFileAllInfo == null) {
            return true;
        }

        stageForFileSelection = 1;
        updateFileSelectionDialog();
        return true;
    }

    private boolean fileSelectionUpdate() {
        //We don't want to add nodes if the user never
        //actually selected a file
        if (currentFileAllInfo == null) {
            return true;
        }

        LoadableProgram pp = new LoadableProgram(currentFileAllInfo);
        try {
            pp.load();
        } catch ( Exception e ) {
            // TODO: display exception loading file
            throw Util.failure(e.toString());
        }
        PlatformFactory pf = Defaults.getPlatform("mica2");
        Simulation s = AvroraGui.instance.getSimulation();

        int max = ((Integer) numOfNodesSpinner.getValue()).intValue();
        for (int i = 0; i < max; i++) {
            //add the stuff to the sim
            s.createNode(pf, pp);
        }

        //We should redraw the table
        AvroraGui.instance.topologyBox.createSimpleAirTable();

        //And get rid of dialog box
        fileSelectionDialog.setVisible(false);
        return true;
    }
}
