package guiDomain;
import sharedInterfaces.*;
import sharedDataInterfaces.*;
import sharedObserverInterfaces.*;

import java.io.*;
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.event.*;
import javax.swing.tree.*;
import java.text.*;
import java.util.*;


public class GuiFacade extends JFrame implements GuiFacadeInterface,
                                                 GraphPointsListener
{   // Handles on other domains
    private DataServerFacadeInterface database;
    private AbstractGraphicsFacade graph;
    private JPanel graphPanel;
    private GraphOptions options = new GraphOptions();

    // Components for the menu
    private JMenuBar menuBar;

    private JMenu fileMenu;
    private JMenuItem fileConnectItem;
    private JMenuItem fileOpenItem;
    private JMenuItem fileExitItem;
    private JMenuItem fileDisconnectItem;
    private JMenuItem fileCloseItem;

    private JMenu graphMenu;
    private JMenuItem graphOptionsItem;

    // Components for the main window
    private JLabel description;

    // Dialogs
    private ConnectDialog connectD;
    private GraphOptionsDialog graphD;
    // Initial experiment and shot
    private String initExp;
    private String initShot;
    // Connection string
    private String serverCport;

    //Tree path hash table stores selected tree paths
    //This handles multiple selections from the JTree component
    private Hashtable treePathHash = new Hashtable();

    // file tree
    private JTree tree;

    private int numCols; //Number of columns in graph panel
    private int[] rowArray; //For each column: number of rows.

    private AbstractGraphicsFacade[] graphFacades; //The array of Graph objects

    public GuiFacade()
    {
        // Construct the menu
        menuBar = new JMenuBar();
        setJMenuBar( menuBar );

        fileMenu = new JMenu( "File" );
        menuBar.add( fileMenu );

        fileConnectItem = new JMenuItem( new ConnectAction() );
        fileMenu.add( fileConnectItem );

        fileOpenItem = new JMenuItem( new OpenAction() );
        fileMenu.add( fileOpenItem );

        fileCloseItem = new JMenuItem( new CloseAction() );
        fileMenu.add( fileCloseItem );

        fileDisconnectItem = new JMenuItem( new DisconnectAction() );
        fileMenu.add( fileDisconnectItem );

        fileMenu.addSeparator();

        fileExitItem = new JMenuItem( new ExitAction() );
        fileMenu.add( fileExitItem );

	// Graph Menu
	graphMenu = new JMenu( "Graph" );
	menuBar.add( graphMenu );
	graphOptionsItem = graphMenu.add( new GraphOptionsAction() );

        description = new JLabel();

        connectD = new ConnectDialog(this, "Enter Connection Details", true);
	graphD = new GraphOptionsDialog(this,options);
    }

    public void initialise(AbstractGraphicsFacade _graph)
    {
	graph = _graph;
	graph.initialise(this);
	graph.addGraphPointsListener(this);
	graph.setGraphOptions(options);

	graphPanel = new JPanel();
        getRowColumns();
        graphPanel.setLayout(new FlexibleGridLayout(numCols, rowArray));
        int numGraphs = 0;
        for(int i = 0; i < numCols; i++)
            numGraphs+= rowArray[i];
	graphPanel.add("",graph);
        graphFacades = new AbstractGraphicsFacade[numGraphs];
        graphFacades[0] = graph;
        for(int i = 1; i < numGraphs; i++)
        {
            AbstractGraphicsFacade newGraph = graph.getGraphicsFacade();
            graphFacades[i] = newGraph;
	    newGraph.setGraphOptions(options);
	    newGraph.initialise(this);
	    newGraph.addGraphPointsListener(this);
            graphPanel.add("",newGraph);
        }

        tree=new JTree(new DefaultTreeModel(new DefaultMutableTreeNode("No experiment open")));
        tree.addTreeSelectionListener( new DataTreeSelectionListener() );
        JScrollPane treeScroll = new JScrollPane( tree );
        treeScroll.setMinimumSize(new Dimension( 150, 10 ) );

        JSplitPane rightPane = new JSplitPane( JSplitPane.VERTICAL_SPLIT,
                        new JScrollPane( description ),graphPanel);
        JSplitPane mainPane = new JSplitPane( JSplitPane.HORIZONTAL_SPLIT,
                        treeScroll, rightPane );
        getContentPane().add( mainPane, "Center" );
        this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        this.setTitle( "No experiment opened" );
        this.setBounds( 200, 200, 800, 400 );
        this.setVisible(true);

	fileConnectItem.setEnabled(true);
	fileOpenItem.setEnabled(false);
	fileCloseItem.setEnabled(false);
	fileDisconnectItem.setEnabled(false);
    }


    //Read from property file 'EScope.properties'
    //The number of columns is defined by the property label 'num_columns'
    //For column i, the number of rows is defined by the property label
    //'column_<i>.num_rows'
    private void getRowColumns()
    {
        Properties props = new Properties();
        try {
            props.load(new FileInputStream("EScope.properties"));
            numCols = Integer.parseInt(props.getProperty("num_columns"));
            rowArray = new int[numCols];
            for(int i = 0; i < numCols; i++)
            {
                rowArray[i] = Integer.parseInt(
                    props.getProperty("column_"+(i+1)+".num_rows"));
            }
        }catch(Exception exc)
        //If anything goes wrong (e.g. property file missing)
        //default to 2 columns, each with two rows
        {
            System.out.println("Exception in " +
               "MultipleGraphicsFacade.getRowColums: " + exc);
            numCols = 2;
            rowArray = new int[]{2,2};
        }
    }

    public void pointsUpdated(double x, double y, boolean inRange)
    {
	if (inRange)
	{
	    DecimalFormat decForm = new DecimalFormat("0.##E0");
            String xString = decForm.format(x);
            String yString = decForm.format(y);
            description.setText(" x= " + xString + "  y= " + yString);
        }
	else description.setText(" ");
    }


    public void applyGraphOptions()
    {
	graph.applyGraphOptions();
	graph.repaint();
    }

    public AbstractGraphicsFacade getSelectedGraph()
    {
	return graph;
    }
    public void setSelectedGraph(AbstractGraphicsFacade newGraph)
    {
	graph = newGraph;
    }

    // Private Inner Classes
    private class ExitAction extends AbstractAction
    {
        public ExitAction() {   super( "Exit" );}

        public void actionPerformed( ActionEvent event )
        {    System.exit( 0 );
        }
    }

    private class ConnectAction extends AbstractAction
    {
        public ConnectAction(){    super( "Connect ..." );}

        public void actionPerformed( ActionEvent event )
        {
	    ServerSelectDialog serverD = new ServerSelectDialog
               (GuiFacade.this, "Choose Server From List",true);
	    if (serverD.showDialog())
            {
                //Get the class name for the selected factory object
                String factoryClassName = serverD.getFactoryClassName();
                try
                {
                    DataServerFactoryInterface currFactory =
                              (DataServerFactoryInterface)
                              (Class.forName(factoryClassName).newInstance());
		    String[] args = serverD.getArgs();
                    database = currFactory.createDataServer(args);
                }
                catch(Exception exc)
                {
                    System.err.println("Cannot instantiate Data Server: "
                                                       + exc);
                    System.exit(0);
                }

	        serverCport = serverD.getServer();
		initExp = serverD.getExperiment();
		initShot = serverD.getShot();

	        if (serverCport == null || serverCport == "")
		    if (connectD.showDialog())
                             serverCport = connectD.getServer();
		if (serverCport != null && serverCport != "") //got server
	            try
	            {
                        if ( database.isConnected() )
                        {
                            database.disconnect();
                        }
                        database.connect( serverCport );
                        String newTitle = "Connected ";
                        GuiFacade.this.setTitle(newTitle + serverCport);
                        fileConnectItem.setEnabled(false);
                        fileOpenItem.setEnabled(true);
                        fileCloseItem.setEnabled(false);
                        fileDisconnectItem.setEnabled(true);
                    }
                    catch (IOException e)
                    {
                        JOptionPane.showMessageDialog(GuiFacade.this,
                                 "Failed to connect to server",
                                 "Error", JOptionPane.ERROR_MESSAGE );
                    }
	     }
	}
    }

    private class OpenAction extends AbstractAction
    {
        public OpenAction(){ super( "Open ..." );}

        public void actionPerformed( ActionEvent event )
        {
	    OpenDialog openD = new OpenDialog(GuiFacade.this,
				   "Enter experiment and shot", true,
				   initExp, initShot);
	    if (openD.showDialog())
             {
                 String experiment = openD.getExperiment();
                 int shot = openD.getShot();
                 try
                 {
                      if ( database.isOpen() )
                      {
                          database.close();
                      }
                      database.open( experiment, shot );
                      database.constructTree(tree);
                      String newTitle = " Opened experiment: "
                                   + experiment + " shot: " + shot;
                      GuiFacade.this.setTitle(newTitle);
		      fileConnectItem.setEnabled(false);
		      fileOpenItem.setEnabled(false);
		      fileCloseItem.setEnabled(true);
		      fileDisconnectItem.setEnabled(true);
                 }
                 catch (IOException e)
                 {
                     JOptionPane.showMessageDialog(GuiFacade.this,
                                                   "Failed to open " +
                                                   "experiment " + experiment +", shot " +shot + ": " +
                                                   e.getMessage(), "Error",
                                                   JOptionPane.ERROR_MESSAGE);
                 }
             }
         }
    }

    private class DisconnectAction extends AbstractAction
    {
        public DisconnectAction(){ super( "Disconnect ..." );}

        public void actionPerformed( ActionEvent event )
        {
	    try
	    {
                tree.setModel(new DefaultTreeModel(new
                    DefaultMutableTreeNode("No experiment open")));
                GuiFacade.this.resetGraphData();
                database.disconnect();
                String newTitle = "Disconnected from server";
                GuiFacade.this.setTitle(newTitle);
		fileConnectItem.setEnabled(true);
		fileOpenItem.setEnabled(false);
		fileCloseItem.setEnabled(false);
		fileDisconnectItem.setEnabled(false);
             }
            catch (IOException e)
            {
                JOptionPane.showMessageDialog(GuiFacade.this,
                                            "Failed to disconnect " +
                                                   e.getMessage(), "Error",
                                                   JOptionPane.ERROR_MESSAGE);
            }
        }
    }

    private class CloseAction extends AbstractAction
    {
        public CloseAction(){ super( "Close ..." );}

        public void actionPerformed( ActionEvent event )
        {
	    try
	    {
                tree.setModel(new DefaultTreeModel(new
                    DefaultMutableTreeNode("No experiment open")));
                GuiFacade.this.resetGraphData();
                database.close();
                String newTitle = " Database closed. Connected ";
                GuiFacade.this.setTitle(newTitle + serverCport);
		fileConnectItem.setEnabled(false);
		fileOpenItem.setEnabled(true);
		fileCloseItem.setEnabled(false);
		fileDisconnectItem.setEnabled(true);
            }
            catch (IOException e)
            {
                JOptionPane.showMessageDialog(GuiFacade.this,
                                            "Failed to close " +
                                                   e.getMessage(), "Error",
                                                   JOptionPane.ERROR_MESSAGE);
            }
        }
    }

    /**    Open the graph options dialog */
    private class GraphOptionsAction extends AbstractAction
    {
	public GraphOptionsAction(){    super( "Options" );}

	public void actionPerformed(ActionEvent event)
	{
            if (graphD == null)
                graphD = new GraphOptionsDialog(GuiFacade.this, options);
            graphD.setOptions();
            graphD.setVisible(true);
	    GuiFacade.this.applyGraphOptions();
	}
    }

    private void resetGraphData()
    {
        for(int i = 0; i < graphFacades.length; i++)
            graphFacades[i].resetGraphData();
    }


    /**    Plots the graph and displays the path of the selected tree node */
    private class DataTreeSelectionListener implements TreeSelectionListener
    {
        public void valueChanged( TreeSelectionEvent e )
        {   // Get the new selection and the corresponding data for each path
            TreePath[] paths = e.getPaths(); //'paths' contains selected and removed paths
            String shortFileAddresses = "";

	    //Find path names and update hash table of selected paths
            for(int pathIdx = 0; pathIdx < paths.length; pathIdx++)
            {
                Object[] path = paths[pathIdx].getPath();
                String shortFileAddress="";
                for ( int i = 1; i < path.length; i ++ )
                {
                    shortFileAddress = shortFileAddress + path[i].toString();
                }
		// update hash table
                if (!shortFileAddress.equals("")) // Do not plot the "TOP" node
                {
                    if(!e.isAddedPath(pathIdx)) //If the path has been removed..
                        treePathHash.remove(shortFileAddress); //Remove from the hash table
                    else
		    {  //new paths selected
                        GraphDataInterface currData = (database.getPlotData(shortFileAddress));
                        treePathHash.put(shortFileAddress, currData);//insert it
                    }
                }
            }
            //The hash table is now contains the waveforms to be displayed
            int numGraphs = treePathHash.size();
            GraphDataInterface[] graphs = new GraphDataInterface[numGraphs];
            Enumeration graphEnum = treePathHash.elements(); //waveforms
            Enumeration pathEnum = treePathHash.keys(); //path names
            shortFileAddresses = "";
            int graphIdx = 0;
            while (graphEnum.hasMoreElements())
            {
                graphs[graphIdx] = (GraphDataInterface) graphEnum.nextElement();
                shortFileAddresses += (String) pathEnum.nextElement() + " ";
                graphIdx++;
            }
            if (numGraphs > 0)
            {
                GuiFacade.this.graph.setGraphData(graphs);
            }
            GuiFacade.this.graph.repaint(); //prints a message if no data
        }
    }
}
