/*
 *  Draco Econometrics
 *  Copyright (C) 2006-2007 Approximatrix, LLC
 *  http://www.approximatrix.com
 *  
 *  This program is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 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 General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package com.approximatrix.draco.main.ui;

import javax.swing.JFileChooser;
import javax.swing.filechooser.FileFilter;
import javax.swing.JFrame;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.UIManager;
import javax.swing.JDesktopPane;
import javax.swing.JInternalFrame;
import javax.swing.ImageIcon;

import java.awt.Window;
import java.awt.Point;
import java.awt.event.KeyListener;
import java.awt.event.KeyEvent;
import javax.swing.event.InternalFrameListener;
import javax.swing.event.InternalFrameEvent;

import java.io.File;
import java.util.ArrayList;
import java.util.List;
import java.awt.Component;

import ca.guydavis.swing.desktop.CascadingWindowPositioner;

import com.approximatrix.draco.application.ApplicationManager;
import com.approximatrix.draco.application.ChildFrame;
import com.approximatrix.draco.application.ChildFrameRegistrar;
import com.approximatrix.draco.datamodel.StatTableModel;
import com.approximatrix.draco.io.DracoSaver;
import com.approximatrix.draco.io.GeneralLoadWorksheet;
import com.approximatrix.draco.io.SaveDracoSQL;
import com.approximatrix.draco.io.SaveEASY;
import com.approximatrix.draco.io.filters.*;
import com.approximatrix.draco.io.ui.ImportData;
import com.approximatrix.draco.general.ui.JDesktopBackgroundPane;


import java.awt.BorderLayout;
import java.beans.PropertyVetoException;

/*
 * Created on Jan 24, 2005
 *
 * TODO To change the template for this generated file go to
 * Window - Preferences - Java - Code Style - Code Templates
 */

/**
 * @author armstrongj
 * 
 * TODO To change the template for this generated type comment go to
 * Window - Preferences - Java - Code Style - Code Templates
 */
public class MainWindow extends JFrame implements KeyListener, InternalFrameListener, WorksheetHandler {
	
	private static final int START_WIDTH = 800;
	private static final int START_HEIGHT = 600;
	
	private static final String PROGRAM_NAME = ApplicationManager.APP_NAME;
	
	private static final String NO_FILENAME_SPECIFIED = "Untitled";

	/** The icon to be displayed for this child window */
	private static final String PATH_TO_ICON = "/com/approximatrix/draco/main/ui/images/icons/d-32.png";
	
	/** The desktop pane that contains all our child frames */
	private JDesktopPane desktop = null;
	
	/** The main data display window for this MainWindow object */
	private DataDialog datadialog = null;

	private DynamicMainMenuBar mainMenuBar = null;
	
	//private StatTableModel user_data = null;
	
	private String current_filename = NO_FILENAME_SPECIFIED;
	
	public boolean started_from_main = false;

	/** The main window's toolbar implementing the fancy dynamic behaviour */
	private DynamicToolbar mainWindowToolBar = null;
	
	/** Positioner for handling the positioning of newly created windows */
	private CascadingWindowPositioner positioner = null;
	
	/** Allows starting draco directly from the MainWindow class.  This is
	 * not recommended since the ApplicationManager static class should be
	 * used for all program startup.
	 * 
	 * @param args command line arguments that are summarily ignored
	 */
	public static void main(String[] args) {
		try {
			UIManager.setLookAndFeel(
					UIManager.getSystemLookAndFeelClassName());
		} catch (Exception e) { 
			System.out.println("Change of LNF failed...");
		}

		MainWindow application = new MainWindow();
		application.started_from_main = true;
	}
	
	/**
	 * This is the default constructor
	 */
	public MainWindow() {
		super();
		initialize();

		datadialog = (DataDialog)this.openWindow(new DataDialog(new StatTableModel()));
		
	}

	/** Constructor that generates a new window based on a 2D array
	 * of double data that is converted into this objects primary
	 * StatTableModel.
	 * 
	 * @param data the 2D array of data to use
	 * @param column_names the names of the columns in data
	 */
	public MainWindow(double[][] data, String[] column_names) {
		this();
		// Generate a default model
		this.setWindowTableModel(new StatTableModel(data,column_names));
	}

	/** Constructor that generates a new window based on a passed
	 * StatTableModel
	 * @param model a valid, complete StatTableModel
	 */
	public MainWindow(StatTableModel model) {
		this();
		this.setWindowTableModel(model);	
	}

	public MainWindow(File toLoad) {
		this();
		GeneralLoadWorksheet.LoadFile(toLoad.getAbsolutePath(), this, true);
	}
	
	/**
	 * This method initializes this
	 * 
	 * @return void
	 */
	private void initialize() {
		this.setDefaultCloseOperation(javax.swing.WindowConstants.DISPOSE_ON_CLOSE);

		this.updateTitle();
		this.setName("main_window");
		this.setJMenuBar(getMainMenuBar());

		this.setLocationByPlatform(true);
		//this.setLocationRelativeTo(null);

		this.setSize(START_WIDTH, START_HEIGHT);
		//this.setContentPane(getJContentPane());
		
		this.setVisible(true);
		
		JPanel panel = new JPanel();
		panel.setLayout(new BorderLayout());
		this.setContentPane(panel);
		
		// Must set this as the default value - the window
		// listeners below will handle closing the window
		this.setDefaultCloseOperation(DO_NOTHING_ON_CLOSE);
		
		// Start up the toolbar
		mainWindowToolBar = new DynamicToolbar(this);
		panel.add(mainWindowToolBar,BorderLayout.NORTH);
		
		// Start the desktop pane
		//getDesktop();
		panel.add(getDesktop(), BorderLayout.CENTER);
		
		// Start the positioner for new windows
		positioner = new CascadingWindowPositioner(getDesktop());
		
		this.addWindowListener(new java.awt.event.WindowAdapter() { 
			public void windowClosing(java.awt.event.WindowEvent e) {    
				closeWindow();				
			}
		});
		this.addWindowListener(new java.awt.event.WindowAdapter() { 
			public void windowIconified(java.awt.event.WindowEvent e) {    
				//taskbar.hideAll();
				//taskbar.setMainWindowMinimized();
			}
		});
		this.addWindowListener(new java.awt.event.WindowAdapter() { 
			public void windowDeiconified(java.awt.event.WindowEvent e) {    
				//taskbar.setMainWindowRestored();
			}
		});
		this.addWindowListener(new java.awt.event.WindowAdapter() { 
			public void windowActivated(java.awt.event.WindowEvent e) {    
				//taskbar.setMainWindowRestored();				
			}
		});
		this.addWindowListener(new java.awt.event.WindowAdapter() { 
			public void windowGainedFocus(java.awt.event.WindowEvent e) {    
				//taskbar.setMainWindowRestored();
				Window[] children = e.getWindow().getOwnedWindows();
				if(children != null) {
					for(int j=1;j<children.length;j++) {
						System.out.println("ToBack");
						children[j].toBack();
					}
				}
				//e.getWindow().toFront();
			}
		});

		// Set up the window manager
		//taskbar = new StatWindowManager(this);

		// Set up any popup menus (not implemented)
		//row_column_popup_menu = new JPopupMenu();

		this.setIconImage(new ImageIcon(this.getClass().getResource(PATH_TO_ICON)).getImage());
				
	}
	
	/**
	 * This method initializes DynamicMainMenuBar used in
	 * the main window.  The method is public because the
	 * toolbar needs access to functions performed by some
	 * menu options.
	 * 	
	 * @return an initialized DynamicMainMenuBar
	 */    
	public DynamicMainMenuBar getMainMenuBar() {
		if (mainMenuBar == null) {
			mainMenuBar = new DynamicMainMenuBar(this);
		}
		return mainMenuBar;
	}

	/** Signals that plugins have changed, and passes the message on to the
	 * data dialog.
	 */
	public void notifyPluginsUpdated() {
		this.datadialog.notifyPluginsUpdated();
	}
	
	/** Forces this MainWindow to close.  This routine does not
	 * necessarily shut down the application (that is handled by
	 * the ApplicationManager).
	 */
	public void closeWindow() {
		
		int rc = JOptionPane.NO_OPTION;
		if(!this.isInitialWindow())
			rc = JOptionPane.showConfirmDialog(this, "Save file before exiting?",
											   "Close "+ApplicationManager.APP_NAME+" Worksheet",
											   JOptionPane.YES_NO_CANCEL_OPTION);
		
		switch(rc) {
			case JOptionPane.YES_OPTION:
				this.saveFile();
			case JOptionPane.NO_OPTION:
				ApplicationManager.CloseWindow(this);
				if(started_from_main) {
					System.exit(0);
				} else { 
					dispose();
				}
				break;
			default:
				return;
		}
	}

	/* (non-Javadoc)
	 * @see com.approximatrix.draco.main.ui.WorksheetHandler#setWindowTableModel(com.approximatrix.draco.datamodel.StatTableModel)
	 */
	public void setWindowTableModel(StatTableModel model) {
		datadialog.setDataModel(model);
		this.setCurrentFilename(NO_FILENAME_SPECIFIED);
		updateTitle();
		
		model.fireTableStructureChanged();
		model.fireTableDataChanged();
	}

	/* (non-Javadoc)
	 * @see com.approximatrix.draco.main.ui.WorksheetHandler#setWindowTableModel(com.approximatrix.draco.datamodel.StatTableModel, java.lang.String)
	 */
	public void setWindowTableModel(StatTableModel model, String filename) {
		this.setWindowTableModel(model);
		this.setCurrentFilename(filename);
		updateTitle();
	}
	
	/* (non-Javadoc)
	 * @see com.approximatrix.draco.main.ui.WorksheetHandler#getWindowTableModel()
	 */
	public StatTableModel getWindowTableModel() {
		return datadialog.getDataModel();		
	}

	/** Forces this MainWindow object to display an data import dialog
	 * 
	 * @param time_series true if a time series is being imported, false if panel data
	 */
	public void forceImportWindow(boolean time_series) {
		this.openWindow(new ImportData(time_series));
	}

	/** Saves a file with a given filename
	 *
	 */
	public void saveNeedFilename() {
		JFileChooser chooser = new JFileChooser();
		if(current_filename != null) {
			chooser.setCurrentDirectory(new java.io.File(current_filename));
		}
		chooser.setDialogTitle("Save Worksheet As...");
		chooser.setDialogType(JFileChooser.SAVE_DIALOG);
		chooser.addChoosableFileFilter(new ODSFilter());
		chooser.addChoosableFileFilter(new EASYFilter());
		chooser.addChoosableFileFilter(new SQLFilter());
		
		if (chooser.showSaveDialog(chooser) == JFileChooser.APPROVE_OPTION) { 
			current_filename = chooser.getSelectedFile().toString();

			FileFilter chosen = chooser.getFileFilter();

			if(!chosen.accept(new File(current_filename))) {
				String desired_extension = null;
				if(chosen.getClass() == EASYFilter.class) {
					desired_extension = EASYFilter.EXTENSION;
				} else if(chosen.getClass() == SQLFilter.class) {
						desired_extension = SQLFilter.EXTENSION;
				} else if(chosen.getClass() == ODSFilter.class) {
					desired_extension = ODSFilter.EXTENSION;
				}
				current_filename = current_filename+"."+desired_extension;
			}

			//user_data.loadfile(chooser.getSelectedFile());
			updateTitle();
			saveFile(current_filename,true);
		}

	}

	/** Saves the current worksheet to the file current_file
	 * 
	 */
	public void saveFile(String fname, boolean allow_quicksave) {
		if(fname == null) return;
		
		DracoSaver saver = null;
		if(new EASYFilter().accept(new File(fname))) {
			SaveEASY easySaver = new SaveEASY(fname,this);
			easySaver.setQuicksaveOnly(false);
			easySaver.setAllowQuicksave(false);
			saver = easySaver;
		} else
			saver = new SaveDracoSQL(fname, this);
			
		saver.save();
		if(!saver.stepSuccess()) {
			if(saver.getError() != null)
				JOptionPane.showMessageDialog(MainWindow.this,saver.getError(),ApplicationManager.APP_NAME+" Error",JOptionPane.ERROR_MESSAGE);
			else
				JOptionPane.showMessageDialog(MainWindow.this,"Problem saving file...",ApplicationManager.APP_NAME+" Error",JOptionPane.ERROR_MESSAGE);		
		}
	}

	/** Saves the current worksheet to the file current_filename
	 * 
	 */
	public void saveFile() {
		if(current_filename.compareTo(NO_FILENAME_SPECIFIED) == 0)
			saveNeedFilename();
		else
			saveFile(current_filename, true);
	}
	
	/** Forces this window to update the title
	 */
	private void updateTitle() {
		String just_name = new File(current_filename).getName();
		if(just_name == null)
			just_name = current_filename;
		this.setTitle(PROGRAM_NAME + " - " + just_name);
	}

	public void keyTyped(KeyEvent e) {

	}

	public void keyPressed(KeyEvent e) {

	}

	/** Captures the copy, cut, and paste keys, passing their occurences
	 * into the menu object, which has the proper copy and paste facilities.
	 * 
	 */
	public void keyReleased(KeyEvent e) {
		if(e.isControlDown()) {
			switch(e.getKeyCode()) {
			case KeyEvent.VK_COPY:
				getMainMenuBar().signalCopy();
				//System.out.println("Copied");
				break;
			case KeyEvent.VK_PASTE:
				getMainMenuBar().signalPaste();
				break;
			case KeyEvent.VK_CUT:
				getMainMenuBar().signalCut();
				break;
			}
		}
	}
	
	/** Returns the desktop pane for this MainWindow object
	 * 
	 * @return the current desktop pane
	 */
	public JDesktopPane getDesktop() {
		if(desktop == null) {
		
		    if(ApplicationManager.ENABLE_BKGD)
    		    desktop = new JDesktopBackgroundPane();
    		else
    		    desktop = new JDesktopPane();
    		    
			//desktop = new JDesktopPane();
			//if(ApplicationManager.ENABLE_BKGD) 
			//	desktop.setBorder(new CenteredBackgroundBorder());
		}
		return desktop;
	}
	
	/* (non-Javadoc)
	 * @see com.approximatrix.draco.main.ui.ChildFrameRegistrar#openWindow(com.approximatrix.draco.application.ChildFrame)
	 */
	public ChildFrame openWindow(ChildFrame new_window) {
		
		// Determine the position
		Point position = positioner.getPosition(new_window, getActiveFrames());
		new_window.setLocation(position);
		
		// Now open the window
		getDesktop().add(new_window);
		
		// Set the focus...
		//new_window.addFocusListener(this);
		new_window.addInternalFrameListener(this);
		
		// Now focus the window
		new_window.requestFocus();
		
		// Update the dialogs menu...
		
		new_window.setVisible(true);
		return new_window;
	}
	
	public boolean isInitialWindow() {
		return this.getWindowTableModel().isInitialModel() && (getDesktop().getAllFrames() != null || getDesktop().getAllFrames().length <=1); 
	}
	
	/** Returns a List of currently non-iconified frames on the desktop
	 * 
	 * @return the frames on the desktop
	 */
	public List<JInternalFrame> getActiveFrames() {
		List<JInternalFrame> frames = new ArrayList<JInternalFrame>();
		for (JInternalFrame frame : this.desktop.getAllFrames()) {
			//if (frame.isVisible() && !frame.isClosed() && !frame.isIcon()) {
			//    frames.add(frame);
			//}
			if(!frame.isIcon()) {
				frames.add(frame);
			}
		}
		return frames;
	}
	
	
	/** Returns the current filename in use by Draco
	 * 
	 * @return a String containing the full filename, including path
	 */
	public String getCurrentFilename() {
		return current_filename;
	}
	
	/** Sets the current filename to the specified String.  This routine
	 * does not actually load the file, just stores the name in this object.
	 * 
	 * @param filename the current filename with full path
	 */
	public void setCurrentFilename(String filename) {
		this.current_filename = filename;
	}
	
	/** Hides the current data tables while a model is being loaded
	 * so that no Swing exceptions occur while the data is being
	 * constructed.
	 * 
	 * @param value false to block swing updates, true to allow them
	 */
	public void setUpdatableTable(boolean value) {
		if(datadialog != null)
			datadialog.setUpdatableTable(value);
	}
	
	/* (non-Javadoc)
	 * @see com.approximatrix.draco.main.ui.ChildFrameRegistrar#getWindowsByClass(java.lang.Class)
	 */
	public Object[] getWindowsByClass(Class value) {
		Component[] frames = getDesktop().getAllFrames();
		if(frames == null)
			return null;
		
		ArrayList results = new ArrayList();
		for(int i=0;i<frames.length;i++) {
			if(value.isAssignableFrom(frames[i].getClass())) {
				results.add(frames[i]);
			}
		}
		
		if(results.size() == 0) return null;
		
		Object[] to_return = new Object[results.size()];
		
		return results.toArray(to_return);
	}
	
	/** Updates any constants dialogs that may be open.  Should be called
	 * after any constant is created.
	 *
	 */
	public void updateConstantsDialog() {
		// Update any ConstantsDialog windows
		Object[] cframe = getWindowsByClass(ConstantsDialog.class);
		if(cframe != null) {
			for(int i=0;i<cframe.length;i++)
				((ConstantsDialog)cframe[i]).fireConstantsChange();
		}
	}
	
	/** Hides or shows the main desktop pane based on the boolean passed in.
	 * The routine is necessary to block drawing of the desktop windows when
	 * loading a new worksheet.
	 * 
	 * @param value true to stop all desktop drawing, false otherwise
	 */
	public void setVisibleDesktop(boolean value) {
		if(!value && desktop != null)
			this.getContentPane().remove(desktop);
		else if(desktop != null) {
			this.getContentPane().add(desktop,BorderLayout.CENTER);
		}
		this.getContentPane().repaint();
	}
	
	/** Destroys all child windows in this main window (except the data window)
	 * in preparation for loading a new worksheet.
	 *
	 */
	public void closeAllChildren() {
		if(desktop != null) return;
		
		JInternalFrame[] frames = getDesktop().getAllFrames();
		for(int i=0;i<frames.length;i++) {
			if(frames[i] != datadialog && frames[i] != null) 
				frames[i].dispose();
		}
	}
	
	/** Brings the specified ChildFrame to the front, and makes sure the
	 * window is not iconified.
	 * 
	 * @param window the ChildFrame to bring to front
	 */
	public void toFront(ChildFrame window) {
		if(window == null) return;
		if(window.isIcon()) {
			try {
				window.setIcon(false);
			} catch(PropertyVetoException pve) {
				System.err.println("Can't de-iconify: "+window.getTitle());
				pve.printStackTrace();
			}
		}
		//window.requestFocus();
		//desktop.setSelectedFrame(window);
		getDesktop().getDesktopManager().activateFrame(window);
	}

	/** Triggered when an internal frame is activated (e.g. brought to the
	 * front) in the desktop pane.  This event causes the toolbar and menus
	 * to update immediately.
	 * 
	 * @see javax.swing.event.InternalFrameListener#internalFrameActivated(javax.swing.event.InternalFrameEvent)
	 */
	public void internalFrameActivated(InternalFrameEvent e) {
		Object window = e.getSource();
		if(ChildFrame.class.isAssignableFrom(window.getClass())) {
			this.getMainMenuBar().setFocusedChild((ChildFrame)window);
			this.mainWindowToolBar.setFocusedChild((ChildFrame)window);
		}
	}

	/** Unused but present to complete the InternalFrameListener interface
	 * 
	 * @see javax.swing.event.InternalFrameListener#internalFrameClosed(javax.swing.event.InternalFrameEvent)
	 */
	public void internalFrameClosed(InternalFrameEvent e) {

	}

	/** Unused but present to complete the InternalFrameListener interface
	 * 
	 * @see javax.swing.event.InternalFrameListener#internalFrameClosing(javax.swing.event.InternalFrameEvent)
	 */
	public void internalFrameClosing(InternalFrameEvent e) {
		Object window = e.getSource();
		if(ChildFrame.class.isAssignableFrom(window.getClass())) {
			this.getMainMenuBar().setFocusedChild(null);
			this.mainWindowToolBar.setFocusedChild(null);
		}
	}

	/** Unused but present to complete the InternalFrameListener interface
	 * 
	 * @see javax.swing.event.InternalFrameListener#internalFrameDeactivated(javax.swing.event.InternalFrameEvent)
	 */
	public void internalFrameDeactivated(InternalFrameEvent e) {

	}

	/** Unused but present to complete the InternalFrameListener interface
	 * 
	 * @see javax.swing.event.InternalFrameListener#internalFrameDeiconified(javax.swing.event.InternalFrameEvent)
	 */
	public void internalFrameDeiconified(InternalFrameEvent e) {

	}

	/** Unused but present to complete the InternalFrameListener interface
	 * 
	 * @see javax.swing.event.InternalFrameListener#internalFrameIconified(javax.swing.event.InternalFrameEvent)
	 */
	public void internalFrameIconified(InternalFrameEvent e) {

	}

	/** Unused but present to complete the InternalFrameListener interface
	 * 
	 * @see javax.swing.event.InternalFrameListener#internalFrameOpened(javax.swing.event.InternalFrameEvent)
	 */
	public void internalFrameOpened(InternalFrameEvent e) {
		
	}
	
}  //  @jve:decl-index=0:visual-constraint="77,11"
