/**
 * Copyright (c) 2011, Martin Foster  <martin@ethereal-realms.org>
 * 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.Host;

import java.lang.Exception;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

import org.lyra.Ledger;

/**
 * This class will notify various clients on updates originating from the 
 * host itself.  In order to create segregation the Updater will run in its
 * own thread and using another connection to the host.
 * <p>
 * @author Martin Foster
 * @version 1.0.1, %Revision, 99%, %LastChangedDate, 17/03/11 8:17PM%
 * @see org.lyra.Host.UpdateEvent
 * @see org.lyra.Host.UpdateListener
 * @see org.lyra.Host.Terminus
 */
public class Updater implements Runnable{

	// Container for all listeners
	List<UpdateListener> Clients = new LinkedList<UpdateListener>();
	List<UpdateListener> Core    = new LinkedList<UpdateListener>();
	
	// Package Instances
	Ledger   Logger;
	Terminus Host;
	Status   Information;
	
	// Language element
	Map<String,String> Lang;
	
	// Class generating updates
	private static final String CLASS = "org.lyra.Host.Updater";

	// Continuance
	private boolean Process = true;
	
	
	/**
	 * Core constructor which will should be making use of its own instance
	 * of Host
	 * <p>
	 * @param logger Ledger instance to track updates in log.
	 * @param details Host connection details.
	 * @param lang Language map.
	 */
	public Updater(Ledger logger, Map<String,String> details, Map<String,String> lang) {
		
		// Pass instances up
		this.Logger = logger;
		this.Lang   = lang;
		
		// Create an instance
		this.Host   = new Terminus(
			Logger,
			details.get("Host"), 
			details.get("Port"), 
			details.get("Pass")
		  ); 
	}
	// End of Constructor Method
	
	
	/**
	 * There is no sense in continuing with the process unless we can actually
	 * perform our task.  So we will make sure the connection is valid.
	 * <p>
	 * @return Instance of status enabling the application to extract information.
	 */
	public Status startUpdate() {
		
		// Connect to the host and ensure it works
		if (Host.Connect()) {
			
			/*
			 * Create Status instance which once tied to this second process
			 * will prevent chances of conflicts when keeping track of
			 * updates.
			 */
			Information = new Status(Logger, Host);
			
			
			/*
			 * Start a new process and have everything run in the background
			 * that way we can retrieve new updates and update the instances.
			 */
			Thread process = new Thread(this);
			process.start();
		}
		
		// Return instance regardless of definition
		return Information;
	}
	// End of startUpdate Method
	
	
	/**
	 * External process ability to kill off the thread cleanly and without issue
	 * used primarily for when the application is closing. 
	 */
	public void stopUpdate() {
		
		// Log the failure
		Logger.Info(
			CLASS,
			"stopUpdate",
			Lang.get("Updater.Log.Close")
		  );
		
		// Prevent future processing
		Process = false;
		
		/*
		 * We will attempt to close the connection properly even though
		 * it will most probably be ignored.
		 */
		Host.writeBatch("Status.NoWait");
		
		/*
		 * Close off our instance of host.  This should free up resources and
		 * prevent any additional problems.
		 */
		Host.Disconnect();
	}
	// End of stopUpdate Method
	
	
	/* (non-Javadoc)
	 * @see java.lang.Runnable#run()
	 */
	public void run() {

		// Log the update
		Logger.Info(
			CLASS,
			"run",
			Lang.get("Updater.Log.Initial")
		  );
		
		/*
		 * This thread will loop until such a time that it is killed or that
		 * we make a decision to kill the process.
		 */
		while (Process) {
			
			try {

				// Request updates as the arrive
				Host.writeBatch("Status.Wait");
				
				// Read the second column only
				List<String> lines = Host.readList(1);
				
				// Create an instance of a new event
				UpdateEvent update = 
					new UpdateEvent(this, lines.toArray(new String[0]));
				
				// Fire the event off an notify users
				fireUpdateRequired(update);
			} 
			
			/*
			 * An input/output error probably means that we have an issue with
			 * the server and we should just close that connection.
			 */
			catch (Exception e) {

				// Log the failure
				Logger.Info(
					CLASS,
					"run",
					Lang.get("Updater.Log.Error")
				  );
				
				/*
				 * We attempt to recover the connection.  If that fails then 
				 * we will have to give up.
				 */
				Process = Host.Recover(e);
			}
		}
	}
	// End of run Method

	
	/*
	 * LISTENER INTERFACE
	 * 
	 * Elements that are related to the notification and addition of client 
	 * listeners will be found below.
	 */
	
	/**
	 * Adds an <i>UpdateListener</i> to the updater, a core client will
	 * always be executed first.  Establishing baselines prior to the
	 * application clients.
	 * <p>
	 * @param l Class implementing the UpdateListener interface.
	 */
	public void addCoreListener(UpdateListener l) {
		
		// Add to the collection
		Core.add(l);
	}
	// End of addCoreListener Event
	
	
	/**
	 * Removes an <i>UpdateListener</i> from the updater, a core client will
	 * always be executed first.  Establishing baselines prior to the
	 * application clients.
	 * <p>
	 * @param l Instance of class implementing the UpdateListener interface.
	 */
	public void removeCoreListener(UpdateListener l) {
		
		// Remove from Collection
		Core.remove(l);
	}
	// End of removeCoreListener Method


	/**
	 * Returns an array of all the <i>UpdateListeners</i> added to this class
	 * with <i>addCoreListener()</i>, a core client will always be executed 
	 * first.  Establishing baselines prior to the application clients.
	 * <p>
	 * @return All of the UpdateListeners added or an empty array if no listeners have been added.
	 */
	public UpdateListener[] getCoreListeners() {
		
		// Have the collection do all the work
		return Core.toArray(new UpdateListener[0]);
	}
	// End of getUpdateListeners Method
	
	
	/**
	 * Adds an <i>UpdateListener</i> to the updater.
	 * <p>
	 * @param l Class implementing the UpdateListener interface.
	 */
	public void addUpdateListener(UpdateListener l) {
		
		// Add to the collection
		Clients.add(l);
	}
	// End of addUpdateListener Event
	
	
	/**
	 * Removes an <i>UpdateListener</i> from the updater.
	 * <p>
	 * @param l Instance of class implementing the UpdateListener interface.
	 */
	public void removeUpdateListener(UpdateListener l) {
		
		// Remove from Collection
		Clients.remove(l);
	}
	// End of removeUpdateListener Method


	/**
	 * Returns an array of all the <i>UpdateListeners</i> added to this class
	 * with <i>addUpdateListener()</i>.
	 * <p>
	 * @return All of the UpdateListeners added or an empty array if no listeners have been added.
	 */
	public UpdateListener[] getUpdateListeners() {
		
		// Have the collection do all the work
		return Clients.toArray(new UpdateListener[0]);
	}
	// End of getUpdateListeners Method
	
	
	/**
	 * Notifies all listeners that have registered interest for notification 
	 * on this event type. The event instance is lazily created using the 
	 * event parameter. 
	 * <p>
	 * @param update The UpdateEvent object.
	 */
	protected void fireUpdateRequired(UpdateEvent update) {
		
		// Log the failure
		Logger.Info(
			CLASS,
			"fireUpdateRequired",
			Lang.get("Updater.Log.Fire")
				.replaceAll("MEVENT", update.toString())
		  );
		
		
		/*
		 * Information elements are tied directly to this process.  So we may
		 * as well ensure they get updated prior.
		 */
		Information.updateByEvent(update);
		
		/*
		 * We loop through in reverse order in order to avoid situations where
		 * a Listener removes itself from this component and actually changes
		 * the structure of the collection.  In reverse order, positions will
		 * not be affected by a removal.
		 */
		
		/*
		 * Core clients are typically instances of the <i>org.lyra.Host</i> 
		 * group and require updates prior to regular application clients.
		 */
		for (int count = Core.size()-1; count >= 0; count--)
			Core.get(count).hostUpdated(update);

		
		/*
		 * With the core clients done their business, these clients will be
		 * able to update using valid information.
		 */
		for (int count = Clients.size()-1; count >= 0; count--)
			Clients.get(count).hostUpdated(update);
	}
	// End of fireUpdateRequired Method
}
