/**
 * 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.Options;

import javax.swing.Box;
import javax.swing.ImageIcon;
import javax.swing.JPanel;
import javax.swing.BoxLayout;
import javax.swing.ListModel;
import javax.swing.JScrollPane;
import javax.swing.JList;
import javax.swing.JButton;
import javax.swing.BorderFactory;
import javax.swing.ListSelectionModel;
import javax.swing.border.TitledBorder;
import javax.swing.event.ListDataEvent;
import javax.swing.event.ListDataListener;

import org.lyra.Ledger;
import org.lyra.Lyra;
import org.lyra.Database.Comptroller;
import org.lyra.Interface.Constants;
import org.lyra.Interface.HostCreateDialog;

import java.awt.Font;
import java.awt.Color;
import java.awt.Window;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;


/**
 * The management of host entries can be managed from this panel.  Provides
 * the necessary user-interface elements to permit a user to remove and 
 * create a new entry.
 * <p>
 * @author Martin Foster
 * @version 1.0.4, %Revision, 147%, %LastChangedDate, 06/11/11 4:57PM%
 */
public class HostsPanel extends JPanel {

	// Serial Conundrum
	private static final long serialVersionUID = 1L;
	
	/*
	 * USER INTERFACE ELEMENTS
	 */
	
	// Insertion and Removal Buttons
	private JPanel panelControl = null;
	private JButton buttonInsert = null;
	private JButton buttonRemove = null;
	
	// Listing of Current Elements
	private JScrollPane scrollCurrent = null;
	private JList listCurrent = null;

	
	/*
	 * PACKAGE INSTANCE
	 */
	private Map<String,String> Lang;
	
	private Ledger Logger;
	private Comptroller Manager;
	
	private Window Parent;
	
	
	/*
	 * CLASS INSTANCES
	 */
	HostListModel hostModel;
	Interact Interface;
	
	
	/**
	 * This is the default constructor.  Used exclusively for visual editors
	 * and will establish the necessary baseline to display but not provide
	 * interactivity.
	 */
	@SuppressWarnings("serial")
	public HostsPanel() {
		
		// Super Class Handling
		super();
		
		/*
		 * 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>(){
			{
				put("Options.Interface.Theme","Java Theme");
			}
		};
		
		// Generate Baseline
		initFrame();
	}
	// End of Constructor Class

	
	/**
	 * Primary application constructor which is passed all of the necessary
	 * elements in order to display settings and handle interaction.
	 * <p>
	 * @param logger Ledger instances for debugging and information.
	 * @param manager Database access manager instance.
	 * @param lang Language map for independent language elements.
	 * @param parent Parent window to block when using dialogs.
	 */
	public HostsPanel(Ledger logger, Comptroller manager, Map<String,String> lang, Window parent) {
		
		// Have super class do its thing
		super();
		
		// Pass up
		this.Logger  = logger;
		this.Manager = manager;
		this.Lang    = lang;
		
		// Pass up parent
		this.Parent  = parent;
		
		// Create Instance
		Interface = new Interact();
		
		// Initialise the Dialog
		initFrame();
	 }
	// End of Constructor Class
	
	
	/**
	 * This method initialises this.  Establishes the baseline and implements
	 * the components.
	 */
	private void initFrame() {

		// Establish Layout
		this.setLayout(new BoxLayout(this, BoxLayout.X_AXIS));
		
		// Adjust Border
		this.setBorder(BorderFactory.createTitledBorder(null, Lang.get("Options.Host.Group.Main"), TitledBorder.DEFAULT_JUSTIFICATION, TitledBorder.DEFAULT_POSITION, new Font("Dialog", Font.BOLD, 12), new Color(51, 51, 51)));

		// Implement Components
		this.add(getPanelControl(), null);
		this.add(getScrollCurrent(), null);
	}
	// End of initFrame Method


	/**
	 * This method initialises scrollCurrent.  Enables scrolling of the JList
	 * element containing all known hosts.	
	 * <p>
	 * @return javax.swing.JScrollPane	
	 */
	private JScrollPane getScrollCurrent() {
		
		// Check for Existence
		if (scrollCurrent == null) {
			
			// Create Instance
			scrollCurrent = new JScrollPane();
			
			// Always have scroll bar
			scrollCurrent.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS);

			// Add in List for scrolling
			scrollCurrent.setViewportView(getListCurrent());
		}
		
		// Return Instance
		return scrollCurrent;
	}
	// End of getScrollCurrent method

	
	/**
	 * This method initialises listCurrent.  Current listing of all hosts
	 * tracked by the database.	
	 * <p>
	 * @return javax.swing.JList	
	 */
	private JList getListCurrent() {
		
		// Check for Existence
		if (listCurrent == null) {
			
			// Create Instance
			listCurrent = new JList();
			
			// Adjust Selection Model 
			listCurrent.setSelectionMode(
				ListSelectionModel.SINGLE_INTERVAL_SELECTION
			  );
		}
		
		// Return Instance
		return listCurrent;
	}
	// End of getListCurrent Method

	
	/**
	 * This method initialises panelControl.  Contains the two elements that
	 * allow for the management of hosts, the remove and addition elements.	
	 * <p> 	
	 * @return javax.swing.JPanel	
	 */
	private JPanel getPanelControl() {
		
		// Check for Existence
		if (panelControl == null) {
			
			// Create Instance
			panelControl = new JPanel();
			
			// Establish Layout
			panelControl.setLayout(new BoxLayout(getPanelControl(), BoxLayout.Y_AXIS));

			// Spacer and Insertion
			panelControl.add(Box.createVerticalGlue());
			panelControl.add(getButtonInsert(), null);
			
			// Spacer and Removal
			panelControl.add(Box.createVerticalStrut(Constants.getNormalGap()));
			panelControl.add(getButtonRemove(), null);
			
			// Additional Spacer
			panelControl.add(Box.createVerticalGlue());
		}
		
		// Return Instance
		return panelControl;
	}
	// End of getPanelControl Method

	
	/**
	 * This method initialises buttonInsert.  As the name implies this
	 * provides the capability to create a new host.	
	 * 	<p>
	 * @return javax.swing.JButton	
	 */
	private JButton getButtonInsert() {
		
		// Check for Existence
		if (buttonInsert == null) {
			
			// Create new Instance
			buttonInsert = new JButton();
			
			// Language and Icons
			buttonInsert.setToolTipText(Lang.get("Options.Host.Insert.Tip"));
			buttonInsert.setIcon(new ImageIcon(
					getClass().getResource(
							Lyra.getIconByMap("Button-Addition"))));
			
			// Identifier and Listener
			buttonInsert.setName("HOST.INSERT");
			buttonInsert.addActionListener(Interface);
		}
		
		// Return Instance
		return buttonInsert;
	}
	// End of getButtonInsert Method

	
	/**
	 * This method initialises buttonRemove.  Permits the removal of hosts
	 * from the database.	
	 * <p>
	 * @return javax.swing.JButton	
	 */
	private JButton getButtonRemove() {
		
		// Check for Existence
		if (buttonRemove == null) {
			
			// Create Instance
			buttonRemove = new JButton();
			
			// Language and Icon
			buttonRemove.setToolTipText(Lang.get("Options.Host.Remove.Tip"));
			buttonRemove.setIcon(new ImageIcon(
					getClass().getResource(
							Lyra.getIconByMap("Button-Remove"))));

			// Identifier and Listener
			buttonRemove.setName("HOST.REMOVE");
			buttonRemove.addActionListener(Interface);
		}
		
		// Return Instance
		return buttonRemove;
	}
	// End of getButtonRemove Method
	
	
	/**
	 * Returns the state of changes as requested by the user.  Will in turn be
	 * used to make the actual changes in the parent class. 
	 * <p>
	 * @return List of Maps which contains the host information picture.
	 */
	public List<Map<String,String>> getState() {
		
		// Proper return
		if (hostModel != null)
			return hostModel.getModelList();
		
		// Default return
		return null;
	}
	// End of getState Method
	
	
	/**
	 * Establishes the default values for the JList component.  Can be
	 * called upon multiple times to update from the database.
	 */
	public void setState() {

		// Pass through
		Interface.setState();
	}
	// End of setState Method
	
	
	/**
	 * Implementation of list model which makes use of a List of Map instances
	 * that can be used to track changes to the host list without directly
	 * affecting changes to the database.
	 * <p>
	 * @author Martin Foster
	 */
	@SuppressWarnings("unused")
	private class HostListModel implements ListModel {

		// Container List
		private List<Map<String,String>> myList;
		
		// List Container
		private List<ListDataListener> myListener 
			= new LinkedList<ListDataListener>();
		
		// Class Logger Identifier
    	private static final String CLASS = "org.lyra.Interface.Options.HostPanel.Interact";

		
		/**
		 * Alternate constructor method.  Does not contain any information 
		 * when creating the model.
		 */
		public HostListModel() {
			
			// Generate an empty list
			myList = new LinkedList<Map<String,String>>();
		}
		// End of Constructor Class
		
		
		/**
		 * Base constructor method, will take a list of all known hosts with
		 * all attributes.  This constructor can be interfaced directly with
		 * the <i>org.lyra.Database.Comptroller</i> class.
		 * <p>
		 * @param list Complete list of all hosts and attributes.
		 * @see Comptroller
		 */
		public HostListModel(List<Map<String,String>> list) {
			
			// Pass up the List
			myList = list;
		}
		// End of Constructor Class
		
	
		/**
		 * Returns the currently stored list in the model.
		 * <p>
		 * @return List of Maps containing host state.
		 */
		public List<Map<String,String>> getModelList() {
			
			// Return Element
			return myList;
		}
		// End of getModelList
		

		/* (non-Javadoc)
		 * @see javax.swing.ListModel#addListDataListener(javax.swing.event.ListDataListener)
		 */
		public void addListDataListener(ListDataListener l) {

			// Add a listener
			myListener.add(l);
		}
		// End of addListDataListener Method


		/* (non-Javadoc)
		 * @see javax.swing.ListModel#getElementAt(int)
		 */
		public Object getElementAt(int index) {

			/*
			 * A decision was made to forego the creation of a rendering
			 * class in addition to a data model.  As a result this model
			 * will not return the actual data type, but instead will return
			 * a formatted string.
			 */
			
			// Extract Map
			Map<String,String> row = myList.get(index);
			
			// What key should we use
			String key = (row.get("Type").equals("REMOVE"))
				? "Options.Host.Format.Remove"
				: "Options.Host.Format.Normal";
			
			
			// Return Formatted String
			return Lang.get(key)
						.replaceAll("MNAME", row.get("Identifier"))
						.replaceAll("MHOST", row.get("Host"))
						.replaceAll("MPORT", row.get("Port"));
		}
		// End of getElementAt Method

		
		/* (non-Javadoc)
		 * @see javax.swing.ListModel#getSize()
		 */
		public int getSize() {

			// Simple Pass-Through
			return myList.size();
		}
		// End of getSize Method


		/* (non-Javadoc)
		 * @see javax.swing.ListModel#removeListDataListener(javax.swing.event.ListDataListener)
		 */
		public void removeListDataListener(ListDataListener l) {

			// Remove a listener
			myListener.remove(l);
		}
		// End of removeListDataListener Method
	
		
		/**
		 * Add in a row of new host information into the list.
		 * <p>
		 * @param row Map containing host information.
		 */
		public void insertRow(Map<String,String> row) {
		
			// The new index entry
			int index = myList.size();
			
			
			/*
			 * We mark the new entry as being new.  That way when we process
			 * the list we can make appropriate choices.
			 */
			row.put("Type","ADD");
			
			// Add in element
			myList.add(row);
			
			// Log change
			Logger.Info(
				CLASS,
				"insertRow",
				Lang.get("Options.Log.Host.Insert")
					.replaceAll("MNAME", row.get("Identifier"))
					.replaceAll("MHOST", row.get("Host"))
					.replaceAll("MPORT", row.get("Port"))
			  );
			
			// Indicate changes
			fireContentsChanged(ListDataEvent.INTERVAL_ADDED, index, index);
		}
		// End of insertRow Method
		
		
		/**
		 * Remove a row in the data model.
		 * <p>
		 * @param index Index of row to remove.
		 */
		public void removeRow(int index) {
			
			// Safety
			if ((index < 0) && (index >= myList.size()))
				return;
			
			/*
			 * Now that we are sure that the index is within a safe range
			 * we can mark the removal of the entry and then notify users.
			 */
			
			// Remove singular entry
			Map<String,String> row = myList.get(index);
			
			// Establish this element for removal
			row.put("Type","REMOVE");
			
			// Log change
			Logger.Info(
				CLASS,
				"removeRow",
				Lang.get("Options.Log.Host.Remove")
					.replaceAll("MNAME", row.get("Identifier"))
					.replaceAll("MHOST", row.get("Host"))
					.replaceAll("MPORT", row.get("Port"))
			  );
			
			// Indicate changes
			fireContentsChanged(ListDataEvent.CONTENTS_CHANGED, index, index);
		}
		// End of removeRow Method
		
		
		/**
		 * Remove a range of rows in the data model.
		 * <p>
		 * @param start Start index to remove.
		 * @param end End index to remove.
		 */
		public void removeRows(int start, int end) {
			
			// Safety Procedures
			if (start > end)          return;
			if (start < 0)            return;
			if (end >= myList.size()) return;
			
			/*
			 * Now that we are sure that the index is within a safe range
			 * we can mark the removal of the entry and then notify users.
			 */
			
			// Cycle through elements and remove
			for (int count=start; count <= end; count++) {
				
				// Remove singular entry
				Map<String,String> row = myList.get(count);
				
				// Establish this element for removal
				row.put("Type","REMOVE");
				
				// Log change
				Logger.Info(
					CLASS,
					"removeRows",
					Lang.get("Options.Log.Host.Remove")
						.replaceAll("MNAME", row.get("Identifier"))
						.replaceAll("MHOST", row.get("Host"))
						.replaceAll("MPORT", row.get("Port"))
				  );
			}
			
			// Indicate changes
			fireContentsChanged(ListDataEvent.CONTENTS_CHANGED, start, end);
		}
		// End of removeRows Method
		
		
		/**
		 * Notifies various listeners that a change has taken place.
		 * <p>
		 * @param type An integer specifying CONTENTS_CHANGED, INTERVAL_ADDED, or INTERVAL_REMOVED
		 * @param start An integer specifying the bottom of a range
		 * @param end An integer specifying the top of a range
		 */
		private void fireContentsChanged(int type, int start, int end) {
			
			// Create new Data Event
			ListDataEvent event = new ListDataEvent(this, type, start, end);
					
			/*
			 * We cycle through the listing in reverse order in order to
			 * ensure that there are no problems if a listener removes itself
			 * from the list while this process is being triggered.
			 */
			for (int count=myListener.size()-1; count >= 0; count--) {
				
				// Extract Element
				ListDataListener target = myListener.get(count);
				
				// Elements were updated
				if (type == ListDataEvent.INTERVAL_ADDED)
					target.intervalAdded(event);

				// Otherwise just a change
				else target.contentsChanged(event);
			}
		}
		// End of fireContentsChanged Method
	}
	// End of HostListModel Class
	
	
	/**
	 * The code separation from the user interface.   This Class handles all
	 * of the complexities of making the interface interactive.
	 * <p>
	 * @author Martin Foster
	 */
	private class Interact implements ActionListener {

		/**
		 * Base constructor for the class.  Nothing more then a stubb method
		 * at the moment. 
		 */
		public Interact() {}
		
		
		/**
		 * Establishes the default values for the JList component.  Can be
		 * called upon multiple times to update from the database.
		 */
		public void setState() {
			
			/*
			 * This section is only designed to run if an instance of
			 * Comptroller is in existence.  Otherwise an empty default will
			 * be created as a result.
			 */
			if (Manager != null) {

				// Create Instance
				hostModel = new HostListModel(
						Manager.getHostComplete());
				
				// Replace model with our own
				getListCurrent().setModel(hostModel);
			}
		}
		// End of setState Method
		

		/* (non-Javadoc)
		 * @see java.awt.event.ActionListener#actionPerformed(java.awt.event.ActionEvent)
		 */
		public void actionPerformed(ActionEvent evt) {
			
			// We only deal with instances of JButton
			if (evt.getSource() instanceof JButton) {
				
				// Extract Element
				String ident = ((JButton)evt.getSource()).getName();
				
				/*
				 * Additions require the use of the HostCreateDialog which
				 * will prompt the creation of a host as it occurs when no host
				 * exists.
				 */
				if (ident.equals(getButtonInsert().getName())) {
					
					// Extract new host
					Map<String,String> host = 
						HostCreateDialog.getHostDetails(Logger, Lang, Parent);
					
					// Insert new host if we got a value back
					if (host.size() > 0)
						hostModel.insertRow(host);
				}
				
				
				/*
				 * Removal is not an instant operation, this will simply mark
				 * the hosts for removal making the Accept and Cancel buttons
				 * in the holder class relevant.
				 */
				else if (ident.equals(getButtonRemove().getName())) {
					
					// Index Range
					int[] indices = getListCurrent().getSelectedIndices();
					
					/*
					 * These would indicate a multiple host selection for
					 * removal.
					 */
					if (indices.length > 1) {
						
						// Get the beginning and end range
						int start = indices[0];
						int end   = indices[indices.length - 1];
						
						// Remove the range
						hostModel.removeRows(start, end);
					}
					
					/*
					 * Single row removal will make use of the first element
					 * only.  Note that anything less then one will be ignore
					 * as there is nothing to select for removal.
					 */
					else if (indices.length == 1) {
						
						// Remove the range
						hostModel.removeRow(indices[0]);
					}
				}
			}
		}
		// End of actionPerformed Method
		
	}
	// End of Interact Method
}
