/**
 * 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.util.ArrayList;
import java.util.List;

import org.lyra.Database.Interface;

/**
 * Patch is a class that checks to ensure that the Host and Configuration 
 * databases are up to the version of Lyra that is making use of it.  This
 * will effectively permit users to keep their settings and options. 
 * <p>
 * Implementation is rather primitive.  It will check for version
 * information and then create the changes accordingly.   Since the versions
 * are checked sequentially, a client at 1.0.0 will have no problem moving
 * to a newer by five releases variant of Lyra.
 * <p>
 * @author Martin Foster
 * @version 1.0.1, %Revision, 97%, %LastChangedDate, 13/03/11 12:30PM%
 * @since 1.0.1
 */
public class Patch {

	/*
	 * PACKAGE INSTANCES
	 */
	
	// Direct Database Handle
	private Interface Db;

	// Logging Instance
	private Ledger Logger;
	
	// Version Name
	private String Version;
	
	/*
	 * CONSTANTS
	 */
	private final static String CLASS = "org.lyra.Patch";
	

	/**
	 * Main constructor for class.
	 * <p>
	 * @param logger Ledger interface for logging.
	 * @param db Direct database handle.
	 * @param version Version information.
	 */
	private Patch(Ledger logger, Interface db, String version) {
		
		// Pass Up
		this.Logger  = logger;
		this.Db  	 = db;
		this.Version = (version != null) ? version : "0.0.0";
	}
	// End of Constructor Class

	
	/**
	 * Static function which will handle all of the database updates necessary
	 * in order to actively bring older versions of host database.  Since
	 * multiple hosts are permitted this may employed often.
	 * <p>
	 * @param logger Ledger interface for logging.
	 * @param db Direct database handle.
	 * @param version Version information.
	 */
	public static void hostPatch(Ledger logger, Interface db, String version) {
		
		// Create Instance
		Patch myPatch = new Patch(logger, db, version);
		
		// Run Confirmations
		myPatch.confirmHost();
	}
	// End of hostPatch Method
	
	
	/**
	 * Static function which will handle all of the database updates necessary
	 * in order to actively bring older versions of main database.
	 * <p>
	 * @param logger Ledger interface for logging.
	 * @param db Direct database handle.
	 * @param version Version information.
	 */
	public static void mainPatch(Ledger logger, Interface db, String version) {
		
		// Create Instance
		Patch myPatch = new Patch(logger, db, version);
		
		// Run Confirmations
		myPatch.confirmMain();
	}
	// End of mainPatch Method
	
	
	/**
	 * Lyra makes use of the same designation for all of its versions and that
	 * means the Version.Major.Minor numbering scheme.   This will permit us to
	 * compare the versions and determine if a patch must be applied.
	 * <p>
	 * @param current Current version to check.
	 * @param target Target version to compare against.
	 * @return boolean indicating elements.
	 */
	private boolean compareVersion(String current, String target) {

		// Separate the two elements
		String[] curr = current.split("\\.", 3);
		String[] tgt  = target.split("\\.", 3);
		
		/*
		 * We cycle through every element, translate to integer values and
		 * then if the current is under any value of the target
		 */
		for (int count=0; count < curr.length; count++) {

			// Return true since the target has a higher value
			if (Integer.parseInt(curr[count]) < Integer.parseInt(tgt[count]))
				return true;
			
			// Return false since the current is higher in version
			if (Integer.parseInt(curr[count]) > Integer.parseInt(tgt[count]))
				return false;
		}
			
		// Default return
		return false;
	}
	// End of compareVersion Method
	
	
	/**
	 * Looks at the version information and will perform the necessary database
	 * updates in order to ensure they go through.
	 */
	@SuppressWarnings("serial")
	private void confirmHost() {
		

		/*
		 * VERSION 1.0.1
		 * 
		 * This version introduced the Last.fm integration and operational
		 * rating of songs.
		 */
		if (compareVersion(Version, "1.0.1")) {
			
			// Information Update
			Logger.Info(CLASS, "confirmHost", "Attempt Host update to: 1.0.1");
			
			// Create a list of all necessary database changes.
			updateVersion(new ArrayList<String>(){
			  {
				  /*
				   * The hosts Song table has a new column that needs to be
				   * added in order to meet our requirements for this version
				   * so we create a new table, copy the contents into it and
				   * then rename.
				   */
				  
				  // Rename table
				  add("ALTER TABLE host.Song RENAME TO oldSong");
				  
				  // Create
				  add("CREATE TABLE host.Song (" +
						 "File        TEXT    NOT NULL, " +
						 "Artist      TEXT, " +
						 "Album       TEXT, " +
						 "Title       TEXT    NOT NULL, " +
						 "Genre       TEXT, " +
						 "Time        NUMBER  NOT NULL, " +
						 "Track       TEXT, " +
						 "Date        NUMBER, " +
						 "Rating      NUMBER, " +
						 "Art         TEXT, " +
						 "CONSTRAINT pkHost_Song PRIMARY KEY (File))");
				  
				  // Copy Contents
				  add("INSERT INTO host.Song SELECT File, Artist, Album, Title, Genre, Time, Track, Date, Rating, NULL FROM host.oldSong");
				  
				  // Drop the old table
				  add("DELETE FROM host.oldSong");
				  

				  
				  /*
				   * Now we just need to add in the final view that is required
				   * to work with ratings.
				   */
				  add("CREATE VIEW host.favRating AS " +
						    "SELECT " +
						           "File AS File, " +
						           "Rating AS Rating " +
						      "FROM Song " +
						     "WHERE Rating IS NOT NULL " + 
						       "AND Rating > 0 " +
						  "ORDER BY Rating DESC " +
				  		     "LIMIT 150 ");
				  
				  /*
				   * Adding a version identifier is the final step.  This
				   * permits us to track databases that have not been fully
				   * updated.
				   */
				  add("INSERT INTO host.Statistic (Key, Value) VALUES ('program_version','1.0.1')");
			  }
			});
		}

	}
	// End of updateHost Method
	
	
	/**
	 * Looks at the version information and will perform the necessary database
	 * updates in order to ensure they go through.
	 */
	@SuppressWarnings("serial")
	private void confirmMain() {
	
		/*
		 * VERSION 1.0.1
		 * 
		 * This version introduced the Last.fm integration and operational
		 * rating of songs.
		 */
		if (compareVersion(Version, "1.0.1")) {
			
			// Create a list of all necessary database changes.
			updateVersion(new ArrayList<String>(){
			  {
				  // Information Update
				  Logger.Info(CLASS, "confirmMain", "Attempt Main update to: 1.0.1");
				  
				  /*
				   * Straight forward single element updates that will be added
				   * over to the Setting table.  These will in turn be added to
				   * the Global map.
				   */
				  add("INSERT INTO main.Setting (\"Tag\", \"Value\", \"Comment\") VALUES ('LastDotFm.Key', 'b1f44f5d9d49d7859862e784da00685c', 'Application key identifying Lyra to the Last.fm service for integration.')");
				  add("INSERT INTO main.Setting (\"Tag\", \"Value\", \"Comment\") VALUES ('LastDotFm.Secret', 'd9af7931ed7433fe83a15deafb24b5e3', 'Singature key used in the signature component of the Last.fm requests.')");
				  add("INSERT INTO main.Setting (\"Tag\", \"Value\", \"Comment\") VALUES ('LastDotFm.Session', '0', 'Session key issued once authentication took place, which identifies the user account specifically.   Unlimited in time span for use, but can be revoked by the user.')");
				  add("INSERT INTO main.Setting (\"Tag\", \"Value\", \"Comment\") VALUES ('LastDotFm.Site', 'http://ws.audioscrobbler.com/2.0/', 'Last.fm request site address.')");
				  add("INSERT INTO main.Setting (\"Tag\", \"Value\", \"Comment\") VALUES ('LastDotFm.Authorize', 'http://www.last.fm/api/auth/', 'Users needs to authorize Lyra for integration.  This is the site address required for this authorization.')");
				  add("INSERT INTO main.Setting (\"Tag\", \"Value\", \"Comment\") VALUES ('LastDotFm.Join', 'http://www.last.fm/join/', 'Users can register to Last.fm in order to have an account established for them.')");
				  add("INSERT INTO main.Setting (\"Tag\", \"Value\", \"Comment\") VALUES ('LastDotFm.Username', '0', 'Last.fm account username.')");
				  add("INSERT INTO main.Setting (\"Tag\", \"Value\", \"Comment\") VALUES ('LastDotFm.Country', 'United States', 'Last.fm country for buy link searches.')");
				  add("INSERT INTO main.Setting (\"Tag\", \"Value\", \"Comment\") VALUES ('Program.Version', '1.0.1', 'Lyra Application Version Identifier.')");
			  }
			});
		}

	}
	// End of updateMain Method
	
	
	/**
	 * Will cycle through all of the updates and insert them into a singular
	 * transaction.
	 * <p>
	 * @param updates List containing all of the updates to complete for a version.
	 */
	private void updateVersion(List<String> updates) {
		
		// Start a new transaction
		Db.updateCommence();
		
		// Cycle through and update
		for (String update: updates)
			Db.updateSingleRows(update);
		
		// End a transaction
		Db.updateComplete();
		
		// Clean up database
		Db.Vacuum();
	}
	// End of updateVersion Method

}
