/**
  * Copyright (c) 2011, Martin Foster
 * 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 Ethereal-Realms.org 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 HOLDER 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 org.lyra.Interface;

import javax.swing.JPanel;

import java.awt.BorderLayout;
import java.awt.Window;

import javax.swing.Box;
import javax.swing.JDialog;
import javax.swing.BoxLayout;
import javax.swing.JLabel;
import javax.swing.JCheckBox;
import javax.swing.JButton;
import javax.swing.ImageIcon;
import javax.swing.JTextArea;

import org.lyra.Ledger;
import org.lyra.Lyra;
import org.lyra.Database.Comptroller;
import org.lyra.Listener.WindowSizeListener;
import org.lyra.Widgets.ResizeIcon;

import java.awt.Insets;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.WindowEvent;
import java.awt.event.WindowListener;
import java.util.HashMap;
import java.util.Map;

/**
 * Basic user interface allowing the user to confirm if they want to terminate
 * execution.   Also permits the user to make this feature disappear unless
 * they explicitly want it.
 * <p>
 * With this class the constructors are hidden outside of the class itself as
 * we make use of a Static function to create the instance and extract the
 * response from the user:
 * <p>
 * <code>
 *   if (CloseApplicationDialog.getVerdict) {
 *      ... Close the application
 *   }
 * </code>
 * <p>
 * @author Martin Foster
 * @version 1.0.1, %Revision, 81%, %LastChangedDate, 03/02/11 3:47PM%
 */
public class CloseApplicationDialog extends JDialog implements Runnable {

	// Serial requirements
	private static final long serialVersionUID = 1L;
	
	// User Interface Elements
	private JPanel jContentPane = null;
	private JPanel panelAction = null;
	private JPanel panelMain = null;
	private JPanel panelPrompt = null;
	private JPanel panelStatus = null;
	private JCheckBox checkPrompt = null;
	private JLabel labelPrompt = null;
	private JLabel iconResize = null;
	private JButton buttonAccept = null;
	private JButton buttonCancel = null;
	private JTextArea areaNotice = null;

	/*
	 * PACKAGE INSTANCES
	 */
	
	// Language element
	private Map<String,String> Lang;
	
	// Thread
	private Thread Waitor;
	
	// Instance
	private Comptroller Manager;
	private Ledger Logger;
	
	// Returning value
	private boolean Process = true;
	private static boolean Return = true;
	
	
	/*
	 * CLASS INSTANCE
	 */
	private Interact Action;
	
	
	/**
	 * Visual Editor constructor which is normally never called directly
	 * due to a lack of internationalisation. 
	 * <p>
	 * @param owner Parent frame.
	 */
	@SuppressWarnings("serial")
	private CloseApplicationDialog(Window owner) {
		
		// Let the inherited class do its work
		super(owner);
		

		/*
		 * Under normal circumstances this section will be passed on from the
		 * database.   However Visual Editors do not have this ability to we
		 * create a HashMap that can be used either way.
		 */
		Lang = new HashMap<String,String>(){
			{
				// Titles and Sections
	        	put("Close.Window.Title","Exit Lyra?");
	        	
	        	// Section Identifiers
	        	put("Close.Message.Notice","Nice message here...");
	        	put("Close.Message.Prompt","Prompt next time?");

	        	// Generic features found elsewhere
	        	put("Button.Cancel","Cancel");
	        	put("Button.Accept","Accept");
			}
		};
		
		// Initialise look and feel
		initDialog(owner);
	}
	// End of Constructor Class

	
	/**
	 * Primary constructor used by the application.  Will provide logging and
	 * language independent display.
	 * <p>
	 * @param logger Ledger instance for logging actions.
	 * @param manager Comptroller instance for establishing settings.
	 * @param lang Map containing language elements.
	 * @param owner Frame to block.
	 */
	private CloseApplicationDialog(Ledger logger, Comptroller manager, Map<String,String> lang, Window owner) {
		
		// Let the inherited class do its work
		super(owner);
		
		// Pass up
		Logger = logger;
		Manager = manager;
		Lang = lang;
		
		// Create instance
		Action = new Interact(this);
		
		// Initialise look and feel
		initDialog(owner);
	}
	// End of Constructor Class
	

	/**
	 * Element which will deal with the creation of the class instance and
	 * extracting the information prior to returning it to the calling program.
	 * <p>
	 * @param logger Ledger instance for logging actions.
	 * @param manager Comptroller instance for establishing settings.
	 * @param lang Map containing language elements.
	 * @param parent Frame to block.
	 * @return boolean confirming user choice.
	 */
	static public boolean getVerdict(Ledger logger, Comptroller manager, Map<String,String> lang, Window parent) {

		// Create a new instance and make visible
    	CloseApplicationDialog cd = new CloseApplicationDialog(logger,manager,lang,parent);

    	// Create a new thread that will wait for a completed Host entry
		cd.Waitor = new Thread(cd);
		cd.Waitor.start();
		
		// Set the display to be visible
    	cd.setVisible(true);
		
    	// Wait out for our needs
		try {
			// Join in and wait for the results
			cd.Waitor.join();
		} 
		
		// Something went wrong so just pass on the defaults
		catch (InterruptedException e) {}
		
		// Return element
		return Return;
	}
	// End of getVerdict Method
	
	
	/**
	 * This method will wait out until the HashMap is defined.  If not it will
	 * sleep until interrupted.
	 */
	public void run() {
		
		/*
		 * We essentially wait until such a time that the user has opted to 
		 * do something.  Otherwise we have an arbitrarily large sleep period
		 * as to prevent unnecessary cycles.
		 */
		while (Process) {
			try {
				// Nothing for us to do sleep
				Thread.sleep(300000);
			} 
			
			/*
			 * Another component has interrupted us from our sleep.  This
			 * should mean the end of the cycle.
			 */
			catch (InterruptedException e) {}
		}
	}
	// End of Run Method
	
	
	/**
	 * This method initialises the user elements along with the look and
	 * feel of the interface.   Permits the use of multiple constructor but
	 * a single drawing element.
	 * <p>
	 * @param owner Frame attached to this endeavour.
	 */
	private void initDialog(Window owner) {

		// Set the title of the dialog
		this.setTitle(Lang.get("Close.Window.Title"));
		
		// Adjust Icon
		this.setIconImage(Constants.getIcon());
		
		// Block the application
		this.setModal(true);
		
		// Insert User Elements
		this.setContentPane(getJContentPane());
		
		// Default button
		getRootPane().setDefaultButton(getButtonAccept());

		// Size according to needs
		this.pack();
		
		// Set position
		this.setLocationRelativeTo(owner);
		
		// Window listener
		this.addWindowListener(Action);
	}
	// End of initDialog Method
	

	/**
	 * This method initialises jContentPane.
	 * <p>
	 * @return javax.swing.JPanel
	 */
	private JPanel getJContentPane() {
		
		// Check for existence
		if (jContentPane == null) {
			
			// Create new instance and set layout
			jContentPane = new JPanel();
			jContentPane.setLayout(new BorderLayout());

			// Insert Elements
			jContentPane.add(getPanelMain(), BorderLayout.CENTER);
			jContentPane.add(getPanelStatus(), BorderLayout.SOUTH);
		}
		
		// Return element
		return jContentPane;
	}
	// End of getJContentPane
	
	
	/**
	 * This method initialises panelMain.
	 * <p>
	 * @return javax.swing.JPanel
	 */
	private JPanel getPanelMain() {
		
		// Check for existence
		if (panelMain == null) {
			
			// Create new instance and set layout
			panelMain = new JPanel();
			panelMain.setLayout(new BoxLayout(getPanelMain(), BoxLayout.Y_AXIS));

			
			/*
			 * Add in the specific user elements.  Normally BorderLayout is
			 * used.  However in this case a BoxLayout was preferred.
			 */
			
			// Notice and Spacer 
			panelMain.add(getAreaNotice(), null);
			panelMain.add(Box.createVerticalStrut(Constants.getSmallGap()));
			
			// Prompt Panel and Spacer
			panelMain.add(getPanelPrompt(), null);
			panelMain.add(Box.createVerticalStrut(Constants.getSectionGap()));
			
			// Action Panel
			panelMain.add(getPanelAction(), null);
			
		}
		
		// Return element
		return panelMain;
	}
	// End of getPanelMain
	

	/**
	 * This method initialises panelPrompt.	
	 * <p>
	 * @return javax.swing.JPanel	
	 */
	private JPanel getPanelPrompt() {
		
		// Check for existence
		if (panelPrompt == null) {

			// Create instance and set layout
			panelPrompt = new JPanel();
			panelPrompt.setLayout(new BoxLayout(getPanelPrompt(), BoxLayout.X_AXIS));

			
			/*
			 * The effect of using the last HorizontalGlue element should force
			 * a left justification.
			 */ 
			
			// Check box and Spacer
			panelPrompt.add(Box.createHorizontalStrut(Constants.getSmallGap()));
			panelPrompt.add(getCheckPrompt(), null);
			panelPrompt.add(Box.createHorizontalStrut(Constants.getSmallGap()));
			
			// Prompt and Spacer
			panelPrompt.add(getLabelPrompt(), null);
			panelPrompt.add(Box.createHorizontalGlue());
		}
		
		// Return Instance
		return panelPrompt;
	}
	// End of getPanelPrompt Method
	

	/**
	 * This method initialises panelAction.	
	 * <p>
	 * @return javax.swing.JPanel	
	 */
	private JPanel getPanelAction() {
		
		// Check for Instance
		if (panelAction == null) {
			
			// Create new instance and establish layout
			panelAction = new JPanel();
			panelAction.setLayout(new BoxLayout(getPanelAction(), BoxLayout.X_AXIS));

			/*
			 * The user of two HorizontalGlue elements should force the buttons
			 * to the centre.
			 */
			
			// Spacer and Accept Button
			panelAction.add(Box.createHorizontalGlue());
			panelAction.add(Box.createHorizontalStrut(Constants.getIndentGap()));
			panelAction.add(getButtonAccept(), null);
			
			// Add in Spacer
			panelAction.add(Box.createHorizontalStrut(Constants.getSectionGap()));
			
			// Cancel Button and Spacer
			panelAction.add(getButtonCancel(), null);
			panelAction.add(Box.createHorizontalStrut(Constants.getIndentGap()));
			panelAction.add(Box.createHorizontalGlue());
		}
		
		// Return Instance
		return panelAction;
	}
	// End of getPanelAction Method

	
	/**
	 * This method initialises checkPrompt.	
	 * <p>	
	 * @return javax.swing.JCheckBox	
	 */
	private JCheckBox getCheckPrompt() {
		
		// Check for Existence
		if (checkPrompt == null) {
			
			// Create Instance
			checkPrompt = new JCheckBox();
			
			// Checked by default
			checkPrompt.setSelected(true);
		}
		
		// Return Instance
		return checkPrompt;
	}
	// End of getCheckPrompt Method
	

	/**
	 * This method initialises labelPrompt.	
	 * <p>
	 * @return javax.swing.JLabel	
	 */
	private JLabel getLabelPrompt() {
		
		// Check for existence
		if (labelPrompt == null) {
			
			// Create instance
			labelPrompt = new JLabel();
			
			// Establish language elements
			labelPrompt.setText(Lang.get("Close.Message.Prompt"));
		}
		
		// Return instance
		return labelPrompt;
	}
	// End of getLabelPrompt Method
	
	
	/**
	 * This method initialises buttonAccept	
	 * <p>	
	 * @return javax.swing.JButton	
	 */
	private JButton getButtonAccept() {
		if (buttonAccept == null) {
			
			// Create Instance
			buttonAccept = new JButton();
			
			// Set language and icon
			buttonAccept.setText(Lang.get("Button.Accept"));
			buttonAccept.setIcon(new ImageIcon(
					Lyra.getIconByMap("Button-Accept")));
			
			// Identifier and Listener
			buttonAccept.setName("ACCEPT");
			buttonAccept.addActionListener(Action);
		}
		
		// Return Instance
		return buttonAccept;
	}
	// End of getButtonAccept Method
	

	/**
	 * This method initialises buttonCancel.	
	 * <p> 	
	 * @return javax.swing.JButton	
	 */
	private JButton getButtonCancel() {
		
		// Check for Instance
		if (buttonCancel == null) {
			
			// Create Instance
			buttonCancel = new JButton();
			
			// Language and Image
			buttonCancel.setText(Lang.get("Button.Cancel"));
			buttonCancel.setIcon(new ImageIcon(
					Lyra.getIconByMap("Button-Cancel")));

			// Identifier and Listener
			buttonCancel.setName("CANCEL");
			buttonCancel.addActionListener(Action);
		}
		
		// Return Instance
		return buttonCancel;
	}
	// End of getButtonListner Method
	
	
	/**
	 * This method initialises areaNotice.	Here we will use line count to
	 * reserve spacing and no use of wrapping.  This permits a more direct
	 * control over the look and feel.
	 * <p>
	 * @return javax.swing.JTextArea	
	 */
	private JTextArea getAreaNotice() {
		
		// Check for Existence
		if (areaNotice == null) {
			
			// Create Instance
			areaNotice = new JTextArea();

			// Prevent user from interacting with this
			areaNotice.setEditable(false);

			// Establish Language and Wrapping
			areaNotice.setText(Lang.get("Close.Message.Notice"));
			
			// Adjust the font style
			areaNotice.setFont(new JLabel().getFont());
			areaNotice.setRows(areaNotice.getLineCount());

			// Look and feel elements
			areaNotice.setMargin(new Insets(5, 5, 5, 5));
			areaNotice.setOpaque(false);
			
			// Set Identifier
			areaNotice.setName("NOTICE");
		}
		
		// Return Instance
		return areaNotice;
	}
	// End of getAreaNotice Method

	
	/**
	 * This method initialises panelStatus	
	 * <p>
	 * @return javax.swing.JPanel	
	 */
	private JPanel getPanelStatus() {
		
		// Determine existence
		if (panelStatus == null) {

			// Create instance
			panelStatus = new JPanel();
			
			// Establish layout
			panelStatus.setLayout(new BoxLayout(getPanelStatus(), BoxLayout.X_AXIS));
			
			// Add in resizing label/icon
			panelStatus.add(Box.createHorizontalGlue());
			panelStatus.add(getIconResize(), null);
		}
		
		// Return instance
		return panelStatus;
	}
	// End of getPanelStatus
	
	
	/**
	 * This method initialises iconResize
	 * <p>
	 * @return javax.swing.JLabel
	 */
	private JLabel getIconResize() {
		
		// Verify existence of element
		if (iconResize == null) {

			// Create and establish baseline
			iconResize = new JLabel(new ResizeIcon());
			iconResize.setOpaque(false);
			
			/*
			 * Making this icon do something of use is one of the core reasons
			 * why it was added to the display itself.  Here we create an 
			 * instance and add the listener, since it is not used anywhere else
			 * we don't worry out creating an instance earlier.
			 */
			WindowSizeListener sizer = new WindowSizeListener(this);
			
			// Add in listener elements
			iconResize.addMouseListener(sizer);
			iconResize.addMouseMotionListener(sizer);
		}
		
		// Return label
		return iconResize;
	}
	// End of getIconResize Method
	
	
	
	/**
	 * Implements the most basic logic elements.   Since they are not readily
	 * employed anywhere else they are included in a sub-class in order to 
	 * separate code and display elements.
	 * <p>
	 * @author Martin Foster
	 */
	private class Interact implements ActionListener, WindowListener {

		// Instance of Frame
		JDialog myFrame;
		
		// Class Logger Identifier
    	private static final String CLASS = "org.lyra.Interface.CloseApplicationDialog.Interact";

    	
    	/**
    	 * Default constructor for the class.   Will enable us to control the
    	 * Frame more easily.
    	 * <p>
    	 * @param source
    	 */
    	public Interact(JDialog source) {
    		// Pass up
    		this.myFrame = source;
    		
    	}
    	// End of Constructor Method
    	
		
		/* (non-Javadoc)
		 * @see java.awt.event.ActionListener#actionPerformed(java.awt.event.ActionEvent)
		 */
		public void actionPerformed(ActionEvent evt) {
			
			// Safety Catch
			if (evt.getSource() instanceof JButton) {
				
				// Extract instance of button
				JButton trigger = (JButton)evt.getSource();
				
				// User wishes to leave
				if (trigger.getName().equals("ACCEPT")) {
				
					/*
					 * We need to see what the user wants the next time he sees
					 * this prompt.   If the user disables the prompt check box
					 * we will disable that setting.
					 */
					if (!getCheckPrompt().isSelected()) {
						
						// Log the action
						Logger.Info(
							CLASS, 
							"actionListner",
							Lang.get("Close.Log.Prompt")
						  );
						
						// Update database
						Manager.updatePrompt();
					}
					
					// Log the action
					Logger.Info(
						CLASS, 
						"actionListner",
						Lang.get("Close.Log.Accept")
					  );
					
					/*
					 * This is straight forward disable the processing boolean
					 * so that we can continue with this.
					 */
					Return  = true;
					Process = false;
				}
				
				// User changed his mind
				else if (trigger.getName().equals("CANCEL")) {
					
					// Log the action
					Logger.Info(
						CLASS, 
						"actionListner",
						Lang.get("Close.Log.Cancel")
					  );
					
					/*
					 * The default return state is the exit the application.
					 * So we change that before affecting processing.
					 */
					Return  = false;
					Process = false;
				}
				
				// Finish up nicely
				if (!Process) {
					
					// Interrupt the waiting thread
					Waitor.interrupt();
					
					// Close off the frame
					myFrame.setVisible(false);
					myFrame.dispose();
				}
			}
		} 
		// End of actionPerformed Method
		
		
    	/*
    	 * WINDOW BASED EVENTS
    	 */
		
		/* (non-Javadoc)
		 * @see java.awt.event.WindowListener#windowClosing(java.awt.event.WindowEvent)
		 */
		public void windowClosing(WindowEvent evt) {
			
			/*
			 * Closing the window has the same effect as pressing the Cancel
			 * button.   So we set the conditions appropriately.
			 */
			if (Process) {
			
				// Log the action
				Logger.Info(
						CLASS, 
						"actionListner",
						Lang.get("Close.Log.Cancel")
				  );

				// Adjust values
				Return = false;
				Process = false;
				
				// Interrupt thread
				Waitor.interrupt();
			}
		}
		// End of windowClosing Method

		
		/* (non-Javadoc)
		 * @see java.awt.event.WindowListener#windowClosed(java.awt.event.WindowEvent)
		 */
		public void windowClosed(WindowEvent evt) {}
		
    	
		/* (non-Javadoc)
		 * @see java.awt.event.WindowListener#windowActivated(java.awt.event.WindowEvent)
		 */
		public void windowActivated(WindowEvent evt) {}


		/* (non-Javadoc)
		 * @see java.awt.event.WindowListener#windowDeactivated(java.awt.event.WindowEvent)
		 */
		public void windowDeactivated(WindowEvent evt) {}


		/* (non-Javadoc)
		 * @see java.awt.event.WindowListener#windowDeiconified(java.awt.event.WindowEvent)
		 */
		public void windowDeiconified(WindowEvent evt) {}


		/* (non-Javadoc)
		 * @see java.awt.event.WindowListener#windowIconified(java.awt.event.WindowEvent)
		 */
		public void windowIconified(WindowEvent evt) {}


		/* (non-Javadoc)
		 * @see java.awt.event.WindowListener#windowOpened(java.awt.event.WindowEvent)
		 */
		public void windowOpened(WindowEvent evt) {}
	}
	// End of Interact Class
}
