package com.acme.actors.model.mydb;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Hashtable;
import java.util.List;
import java.util.UUID;

import com.acme.actors.model.Actor;
import com.acme.actors.model.DataStore;
import com.acme.actors.model.ObjectPropertyChangeEvent;
import com.acme.logging.Log;

/**
 * Simple in-memory implementation of the {@link DataStore} interface
 */
public class MyDataStore implements DataStore {
	private Hashtable<String,Actor> m_actorsById=new Hashtable<String,Actor>();
	private List<Actor> m_actorsByLastname=new ArrayList<Actor>();
	private LastNameComparator m_lastNameComparator=new LastNameComparator();
	
	public MyDataStore() {
		//fill the database with some dummy entries
		initDb();
	}
	
	private void initDb() {
		//Add content to the in-memory datastore
		addActorToDb("Sean", "Connery", "James Bond");
		addActorToDb("Daniel", "Craig", "James Bond");
		addActorToDb("Catherine", "Zeta-Jones", "Ocean's Twelve");
		addActorToDb("Tobey", "Maguire", "Spiderman");
		addActorToDb("Kirsten", "Dunst", "Spiderman");
		addActorToDb("John", "Travolta", "Pulp Fiction");
		addActorToDb("Bruce", "Willis", "Die Hard");
		addActorToDb("Christopher", "Lee", "Dracula");
		addActorToDb("Patrick", "Stewart", "Star Trek");
		addActorToDb("Charlton", "Heston", "Planet of the Apes");
	}
	
	private void addActorToDb(String firstName, String lastName, String comment) {
		String newId=UUID.randomUUID().toString();
		
		Actor newActor=new Actor();
		newActor.setId(newId);
		newActor.setFirstname(firstName);
		newActor.setLastname(lastName);
		newActor.setComment(comment);
		newActor.resetChanges();
		
		addActorToDb(newActor);
	}
	
	private void addActorToDb(Actor p) {
		m_actorsById.put(p.getId(), p);
		
		resortActor(p, true);
	}
	
	/**
	 * Inserts the Actor into an internal list, ordered by lastname.
	 * 
	 * @param actor Actor to be inserted
	 * @param isNew <code>false</code> to remove the actor from the list (e.g. if the last name has been changed)
	 */
	private void resortActor(Actor actor, boolean isNew) {
		if (!isNew)
			m_actorsByLastname.remove(actor);
		
		//search the new position of the entry in the list
		int pos=Collections.binarySearch(m_actorsByLastname, actor, m_lastNameComparator);
		if (pos < 0) {
			m_actorsByLastname.add(-pos-1, actor);
		}
		else {
			m_actorsByLastname.add(pos, actor);
		}
	}
	
	public Actor createActor() {
		String newId=UUID.randomUUID().toString();
		
		Actor newActor=new Actor();
		newActor.setId(newId);
		return newActor;
	}

	public void updateActor(Actor changedActor) {
		if (!changedActor.hasChanges()) {
			Log.getInstance().println("Updating "+changedActor+": No changes");
			return;
		}
		Log.getInstance().println("Updating "+changedActor+": Changes detected");
		
		String id=changedActor.getId();
		Actor existingActor=m_actorsById.get(id);
		if (existingActor!=null) {
			applyChanges(existingActor, changedActor);
			resortActor(existingActor, false);
		}
		else {
			addActorToDb(new Actor(changedActor));
		}
		changedActor.resetChanges();
	}
	
	/**
	 * This method transfers the data changes in one Actor object to the other
	 * Actor object.
	 * 
	 * @param dbActor Persistent data object
	 * @param changedActor Object with data changes to be transferred
	 */
	private void applyChanges(Actor dbActor, Actor changedActor) {
		Log.getInstance().println("Start: Processing changes for object "+dbActor.toString());
		ObjectPropertyChangeEvent[] changes=changedActor.getChanges();
		
		for (int i=0; i<changes.length; i++) {
			ObjectPropertyChangeEvent currChange=changes[i];
			String currFieldId=currChange.getFieldId();
			if (Actor.FLD_ACTOR_FIRSTNAME.equals(currFieldId)) {
				Log.getInstance().println("Firstname changed, old="+currChange.getOldValue()+", new="+currChange.getNewValue());
				dbActor.setFirstname(changedActor.getFirstname());
			}
			else if (Actor.FLD_ACTOR_LASTNAME.equals(currFieldId)) {
				Log.getInstance().println("Lastname changed, old="+currChange.getOldValue()+", new="+currChange.getNewValue());
				dbActor.setLastname(changedActor.getLastname());
			}
			else if (Actor.FLD_ACTOR_COMMENT.equals(currFieldId)) {
				Log.getInstance().println("Comment changed, old="+currChange.getOldValue()+", new="+currChange.getNewValue());
				dbActor.setComment(changedActor.getComment());
			}
		}
		Log.getInstance().println("Done: Processing changes");
	}
	
	public void deleteActor(String id) {
		Actor Actor=m_actorsById.get(id);
		if (Actor!=null) {
			Log.getInstance().println("Deleting "+Actor);
			m_actorsById.remove(id);
			m_actorsByLastname.remove(Actor);
		}
		else {
			Log.getInstance().println("Unable to delete Actor with id "+id+". Actor not found in database");
		}
	}

	public Actor findActorById(String id) {
		Actor p=m_actorsById.get(id);
		if (p!=null) {
			Actor actorCopy=new Actor(p);
			return actorCopy;
		}
		else
			return null;
	}

	public List<Actor> getActorsByLastname() {
		ArrayList<Actor> retList=new ArrayList<Actor>();
		for (Actor currActor : m_actorsByLastname) {
			retList.add(new Actor(currActor));
		}
		
		return Collections.unmodifiableList(retList);
	}
	
	/**
	 * {@link java.util.Comparator} implementation that sorts the Actor instances
	 * by last name
	 */
	private static class LastNameComparator implements Comparator<Actor> {
		public int compare(Actor p1, Actor p2) {
			String lastName1=p1.getLastname()==null ? "" : p1.getLastname();
			String lastName2=p2.getLastname()==null ? "" : p2.getLastname();
			
			return lastName1.compareTo(lastName2);
		}
	}

}
