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

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.channels.FileChannel;
import java.nio.channels.NonReadableChannelException;
import java.nio.channels.NonWritableChannelException;
import java.util.Map;

import org.lyra.Database.Comptroller;


/**
 * Operations is a class which will deal with file operations required of
 * the application.  In the cases of hostCreate and hostRemove method also
 * mix in database operations and confirmation.
 * <p>
 * @author Martin Foster
 * @version 1.0.4, %Revision, 147%, %LastChangedDate, 06/11/11 4:57PM%
 */
public class Operations {

	/*
	 * CONSTANTS
	 */
	
	// Logger identifier
	private static final String CLASS = "org.lyra.Operations";

	// Buffer Identifier
	private static final int BUFFER = 8192;
	
	/*
	 * PACAKE INSTANCE
	 */
	private Ledger Logger;
	
	
	/**
	 * Primary constructor for class, requires the logger element to be
	 * complete.
	 * <p>
	 * @param logger Instance of the Ledger class.
	 */
	public Operations(Ledger logger) {
		
		// Pass up
		this.Logger = logger;
		
	}
	// End of Constructor Class
	
	/**
	 * Wrapper for a series of instructions which should create a quick and
	 * efficient file copy structure.
	 * <p>
	 * @param source File source.
	 * @param target File target.
	 * @return Boolean indicating a structure.
	 */
	public boolean fileCopy(File source, File target) {
		try {
			// Create file handles
			FileChannel in = new FileInputStream(source).getChannel();
			FileChannel out = new FileOutputStream(target).getChannel();
		
			// Fast copy
			in.transferTo(0, (int)in.size(), out);
		
			// Close out handles
			in.close();
			out.close();
		
			// All went well
			return true;
		}
		
		// File does not exist, is a directory
		catch (FileNotFoundException e) {  
			Logger.Severe(CLASS, "fileCopy", e);
		}
		
		// Class on an input/output error
		catch (IOException e) {  
			Logger.Severe(CLASS, "fileCopy", e);
		}
		
		// If this channel was not opened for reading 
		catch (NonReadableChannelException e) {  
			Logger.Severe(CLASS, "fileCopy", e);
		}
		
		// If the target channel was not opened for writing 
		catch (NonWritableChannelException e) {  
			Logger.Severe(CLASS, "fileCopy", e);
		}
		
		// Something went wrong
		return false;
	}
	// End of fileCopy Process

	
	/**
	 * Wrapper for a series of instructions which should create a quick and
	 * efficient file copy structure.
	 * <p>
	 * @param source String Java Archive resource.
	 * @param target File target.
	 * @return Boolean indicating a structure.
	 */
	public  boolean jarCopy(String source, File target) {

		try {
			
			// Open and output handle
			InputStream is = getClass().getResourceAsStream(source);
			OutputStream os = new FileOutputStream(target);
			
			// Create buffer
			byte[] buffer = new byte[BUFFER];
			
			// Length counter
			int length;
			
			// Copy to buffer and write out
			while ((length = is.read(buffer)) > 0)
			    os.write(buffer, 0, length);

			// Close handles
			os.close();
			is.close();

		
			// All went well
			return true;
		}
		
		// File does not exist, is a directory
		catch (FileNotFoundException e) {  
			Logger.Severe(CLASS, "jarCopy", e);
		}
		
		// Class on an input/output error
		catch (IOException e) {  
			Logger.Severe(CLASS, "jarCopy", e);
		}
		
		// If this channel was not opened for reading 
		catch (NonReadableChannelException e) {  
			Logger.Severe(CLASS, "jarCopy", e);
		}
		
		// If the target channel was not opened for writing 
		catch (NonWritableChannelException e) {  
			Logger.Severe(CLASS, "jarCopy", e);
		}
		
		return false;
	}
	// End of fileCopy Process
	
	
	
	/**
	 * Wrapper for a series of instructions which should create a quick and
	 * efficient file copy structure.
	 * <p>
	 * @param stream Stream containing contents.
	 * @param target File target.
	 * @return Boolean indicating a structure.
	 */
	public boolean fileWrite(InputStream stream, File target) {
		try {
			
			// Buffering size
			byte[] buffer = new byte[BUFFER];
			
			// Open the file
			FileOutputStream fs = new FileOutputStream(target);
		
			// Create file handles
			BufferedInputStream in   = new BufferedInputStream(stream);
			BufferedOutputStream out = new BufferedOutputStream(fs);
		
			// Size captured
			int size;
			
			// Loop through and pass on buffer
			while ((size = in.read(buffer)) != -1 ) {
				out.write(buffer, 0, size);
			}
		
			// Force out all remaining information
			out.flush();
			
			// Close out handles
			in.close();
			out.close();
		
			// All went well
			return true;
		}
		
		// File does not exist, is a directory
		catch (FileNotFoundException e) {  
			Logger.Severe(CLASS, "fileWrite", e);
		}
		
		// Class on an input/output error
		catch (IOException e) {  
			Logger.Severe(CLASS, "fileWrite", e);
		}
		
		// If this channel was not opened for reading 
		catch (NonReadableChannelException e) {  
			Logger.Severe(CLASS, "fileWrite", e);
		}
		
		// If the target channel was not opened for writing 
		catch (NonWritableChannelException e) {  
			Logger.Severe(CLASS, "fileWrite", e);
		}
		
		// Something went wrong
		return false;
	}
	// End of fileWrite Process
	
	
	/**
	 * Host creation requires the transfer of a base database template and
	 * changes to the database itself.   Without success on both this operation
	 * cannot proceed.
	 * <p>
	 * @param manager Database comptroller instance.
	 * @param lang Map with language elements.
	 * @param details Map indicating host details.
	 * @return boolean to indicate success of operation.
	 */
	public boolean hostCreate(
			Comptroller manager, 
			Map<String,String> lang,
			Map<String,String> details) {

		/*
		 * Details are not empty which means we are going to commit to the
		 * database.  If the settings fail, that is not really our problem.
		 */
		if (details.size() > 0) {

			// Generate the filename component
			String filename = details.get("Identifier");
			
			// Filename Safety
			filename = filename.replaceAll("[\\s\\W]+","_");
			
			// Append extension
			filename = filename + ".db3";

			// Create file entry
			File destination = new File(Lyra.rootUser + Lyra.HOST + filename);
			
			// Confirm the does not already exist
			if (!destination.isFile()) {
				
				// Copy over the blank template to the new location
				jarCopy(
					Lyra.DB_SOURCE + Lyra.HOST + Lyra.TEMPLATE,
					destination
				  );
				
				// Update Hash
				details.put("Filename", filename);
				

				/*
				 * The database update is one of the more critical operations
				 * in this section.  Without successful operation in the 
				 * database insertion then we have no choice but to backtrack
				 * changes.
				 */
				boolean success = manager.insertHost(
					details.get("Identifier"),
					details.get("Host"),
					details.get("Port"),
					details.get("Password"),
					details.get("Filename")
				  );
				
				// Has the operation succeeded
				if (success) {
					
					// Log creation
					Logger.Info(
						CLASS, 
						"hostCreate", 
						lang.get("Host.Insert")
							.replaceAll("MHOST", details.get("Identifier"))
					  );
					
					// Everything worked as necessary
					return true;
				}
				
				// Operation failed retracted
				else {
					
					// Remove the newly created file
					if (destination.isFile())
						destination.delete();
				}
			}
		}
		
		// The operation has failed
		return false;
	}
	// End of hostCreate Method
	
	
	/**
	 * Host removal requires the removal of the separate database file as well
	 * as the associated database entry.
	 * <p>
	 * @param manager Database comptroller instance.
	 * @param lang Map with language elements.
	 * @param details Map indicating host details.
	 * @return boolean to indicate success of operation.
	 */
	public boolean hostRemove(
			Comptroller manager, 
			Map<String,String> lang,
			Map<String,String> details) {

		/*
		 * For safety reasons we will not allow the currently active host
		 * from being removed.  In order to prevent fault it will return as
		 * successful however not complete the operation.
		 */
		if (details.get("Identifier").equals(Lyra.activeHost))
			return true;
		
		/*
		 * Depending on the source of the removal information the item
		 * requested for removal may have never been inserted in the first
		 * place.  Hence the check.
		 */
		if (details.containsKey("Filename")) {

			// Recover the file destination
			File destination = new File(Lyra.rootUser + Lyra.HOST + details.get("Filename"));

			
			/*
			 * The database update is one of the more critical operations
			 * in this section.  We will only remove the files once it has
			 * been successfully completed.
			 */
			boolean success = manager.deleteHost(details.get("Identifier"));
			
			// Has the operation succeeded
			if (success) {
				
				// Remove the file
				if (destination.isFile())
					destination.delete();
				
				// Log creation
				Logger.Info(
					CLASS, 
					"hostRemove", 
					lang.get("Host.Remove")
						.replaceAll("MHOST", details.get("Identifier"))
				  );
				
				// Everything worked as necessary
				return true;
			}
		}
		
		// The operation has failed
		return false;
	}
	// End of hostRemove Method
	
	
	/**
	 * Extracts the dotted file extension from a provided string.  
	 * <p> 
	 * @param name Filename.
	 * @return String with extension.
	 */
	public static String extractFileExtension(String name) {
		
		// Where is the last . in the file
		int dot = name.lastIndexOf('.');
		
		/*
		 * A dot at the first position would be a hidden file and not an
		 * extension.  In such cases we return an empty string.
		 */
		return (dot > 0 && dot < name.length())
		  ? name.substring(dot)
		  : "";
	}
	// End of extractFileExtension Method
	
	
	/**
	 * Extracts the dotted file extension from a provided File instance.  
	 * <p> 
	 * @param path File instance.
	 * @return String with extension.
	 */
	public static String extractFileExtension(File path) {
		
		// Pass Through
		return extractFileExtension(path.getName());
	}
	// End of extractFileExtension Method
}
