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 JMenu graphMenu;
    private JMenuItem graphOptionsItem;

    // Components for the main window
    private JLabel description;

    // Dialogs
    private OpenDialog openD;
    private ConnectDialog connectD;
    private GraphOptionsDialog graphD;

    //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 );

        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);
        openD = new OpenDialog(this, "Enter experiment and shot", true);
	graphD = new GraphOptionsDialog(this,options);
    }

    public void initialise(DataServerFacadeInterface _source,
			   AbstractGraphicsFacade _graph)
    {
        database = _source;

	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);
    }


    //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 )
        {
            if (connectD.showDialog())
            {
                String server = connectD.getServer();
                try
                {
                    if (database.isConnected())
                    {
                        if (database.isOpen())
                        {
                            tree.setModel(new DefaultTreeModel(new
                                DefaultMutableTreeNode("No experiment open")));
                            GuiFacade.this.resetGraphData();
                            description.setText(" ");
                            database.close();
                        }
                        database.disconnect();
                    }
                    database.connect( server );
                    String newTitle = "Connection made. No experiment opened";
                    GuiFacade.this.setTitle(newTitle);
                    }
                    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)
        {
            if (openD.showDialog())
            {
                String experiment = openD.getExperiment();
                int shot = openD.getShot();
                try
                {
                    if (database.isOpen())
                    {
                        tree.setModel(new DefaultTreeModel(new
                            DefaultMutableTreeNode("No experiment open")));
                        GuiFacade.this.resetGraphData();
                        description.setText(" ");
                        database.close();
                    }
                    database.open(experiment, shot);
                    database.constructTree(tree);
                    String newTitle = " Opened experiment: "
                        + experiment + " shot: " + shot;
                    GuiFacade.this.setTitle(newTitle);
                }
                catch (IOException e)
                {
                    JOptionPane.showMessageDialog(GuiFacade.this,
                                                  "Failed to open " +
                                                  "experiment " + experiment +
                                                  ", shot " + shot + ": " +
                                                   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
        }
    }
}
