
package reservationManager;
import java.util.Hashtable;
import java.lang.NullPointerException;


/**
 * This class implements all the functionality of the reservation manager that 
 * allows users to reserve some ammount of the resource, while negotiating about it.
 * This ammount of resource will be kept aside untill the reservation either:
 * 		* retracted by one of the sides
 * 		* expires because of lack of communication (DOS protection)
 * 		* is commited
 * 
 * During the negotiation every side can retract this reservations, and in such case 
 * the resources will be freed, and the reservation removed.  
 * 
 * Every saved reservation is controlled by a watchdog thread, that will remove it when a 
 * timeout expires. This timeout can be extended during the communication between the two sides
 * By renewing the reservation.
 * 
 * Once an agreement was reached the reservation is commited by both of the sides.
 * The resource is allocated for the asked period of time and at the end of this period
 * No more renewals are allowed - So the user who once used the resource wont be able to
 * keep it to himself 
 */
public class ReservationManager {
	private Hashtable dict = new Hashtable();
	private Resource resource = null;
	private int reservationID = 1;
	
	/**
	 * Builds a new instance of the db handling the resource
	 * @param resource resource to be handled
	 */
	public ReservationManager(int resourceAmmount){
		this.resource = new Resource(resourceAmmount);
	}
	
	/**
	 * returns every time new ID for a reservation
	 * @return every time new ID for a reservation
	 */
	protected synchronized int getReservationID(){
		return reservationID++;
	}
	
	/**
	 * This function reserves the asked ammount of the internal resource
	 * and saved the reservation request in a special self cleaning DB.
	 * After a given timeout the reservation will be deleted (unless it was removed)
	 * and the reserved ammount of the resource will be returned to the pool
	 * @param howMuch how much to reserve
	 * @return the reservation number, or -1 if there was not enough resource
	 */
	public synchronized int reserveResource(int howMuch){
		try{
			this.resource.useResource(howMuch);			
		}
		catch (Exception ex) {
            return -1;
        }
		int id = getReservationID();
		this.put(new Integer(id),howMuch);
		System.out.println("created reservation of "+howMuch+" with id "+id);
		return id;
	}
	
	/**
	 * Returns the ammount of the resource that is available 
	 * to reserve at the moment
	 * @return the ammount of the resource
	 */
	public synchronized int getAvailableResource(){
		return this.resource.getAmount();
	}
	
	/**
	 * retract a priviously made reservation, all the occupied
	 * resources are freed, and the reservation is removed
	 * @param id reservation id
	 */
	public synchronized void retractReservation(int id){
		try{
			this.remove(new Integer(id));
			System.out.println("retracted reservation "+id);
		}
		catch (NullPointerException ex){
			System.out.println("Couldnt remove "+id+" allready expired");
		}
	}
	
	/**
	 * Allows the user to commit a previously made reservation.
	 * the resource is then allocated to him for the asked period of time
	 * and after this period the resource is freed
	 * and the reservation is removed
	 * @param id reservation id
	 * @param seconds num of seconds that the resource will be in use
	 * @return true,if could allocate the resource for use 
	 */
	public synchronized boolean commitReservation(int id,int seconds){
		try{
			DBEntry entry = (DBEntry)dict.get(new Integer(id));
			System.out.println("commited reservation "+id);
			entry.timer.setInitialDelay(seconds*1000);
			entry.timer.restart();
			//set that the resource is already in use
			//wont be able to renew it
			entry.timer.setInUse();
		}
		catch (Exception e){
			System.out.println("Couldnt commit "+id+" allready expired");
			return false;
		}
		return true;
	}
	
	/**
	 * Renew the reservation - reset the timer of expiry of the reservation.
	 * When the timer expires it will be removed and the resource freed
	 * 
	 * If the reservation was previously commited this method wont return an eror, but the reservation wont
	 * be renewd either, because commited reservations cant be commited
	 * @param id id of the reservation
	 * @return true if could renew the reservation
	 */
	public synchronized boolean renewReservation(int id){
		try{
			this.renew(new Integer(id));
			System.out.println("renewed reservation "+id);
		}
		catch (NullPointerException ex){
			System.out.println("Couldnt renew "+id+" allready expired");
			return false;
		}
		return true;
	}
	
	/**
	 * returns the data that was saved in the asked reservation
	 * @param id reservation id
	 * @return data that was saved in the asked reservation
	 */
	public synchronized int getReservation(int id){
		int data = -1;
		try{
			DBEntry entry = (DBEntry)dict.get(new Integer(id));
			data = entry.data;
		}
		catch (NullPointerException ex){
			System.out.println("Couldnt read "+id+" allready expired");
		}
		return data;
	}
	
	/**
	 * inserts new entry in the DB
	 * and creates a thread that controlls it, and will erase the entry when 
	 * it had timeout
	 * @param key key that will be used in the dict
	 * @param data data that will be inserted in dict[key]
	 */
	protected synchronized void put(Object key, int data){
		clearingTimer timer = new clearingTimer(this.dict,key,resource);
		this.dict.put(key, new DBEntry(data, timer));
		timer.start();
	}
	
	/**
	 * removes the entry that corresponds to the asked key
	 * @param key key of the entry to remove
	 * @throws NullPointerException when there is no such entry in the DB
	 */
	protected synchronized void remove(Object key){
		DBEntry entry = (DBEntry)dict.get(key);
		//also will return the resource
		entry.timer.stop(false); //without printing 	
		//dict.remove(key); - no need the stopping of the timer
		//removed it
	}
	
	/**
	 * checks if the given key is in the DB
	 * @param key key of the entry
	 * @return true if the key is in the DB
	 */
	protected synchronized boolean contains(Object key){
		return dict.contains(key);
	}
	
	/**
	 * Gets the data for the key
	 * @param key key of the entry to renew
	 * @return data that was stored for that key
	 * @throws NullPointerException when there is no such entry in the DB
	 */
	protected synchronized int get(Object key) throws NullPointerException{
		DBEntry entry = (DBEntry)dict.get(key);
		return entry.data;
	}
	
	/**
	 * resets the timer that erases the entry on expiry
	 * @param key key of the etry to renew
	 * @throws NullPointerException when there is no such entry in the DB
	 */
	protected synchronized void renew(Object key) throws NullPointerException{
		DBEntry entry = (DBEntry)dict.get(key);
		entry.timer.restart();
	}
	
	/**
	 * Return the entries saved in the DB as an array
	 * @return the entries saved in the DB as an array
	 */
	public synchronized int[] getReservations(){
				
		Object[] tuplesArray = dict.values().toArray();
		int[] dataArray = new int[tuplesArray.length];
		
		for (int i=0;i< tuplesArray.length; i++)
			dataArray[i]=((DBEntry)tuplesArray[i]).data;

		return dataArray;
	}
	
	/**
	 * returns the number of currently known reservations
	 * @return the number of currently known reservations
	 */
	public synchronized int getNumReservations(){
		return dict.values().toArray().length;
	}
	
	public static void printDB(ReservationManager db){
		int[] dataArray = db.getReservations();
		System.out.print("[");
		for (int i=0;i<dataArray.length;i++)
			System.out.print(dataArray[i]+", ");
		System.out.println("]");
	}
	
	public static void main(String[] args) {
		ReservationManager db = new ReservationManager(100);
		db.put("1",1);
		ReservationManager.printDB(db);
		db.put("2",2);
		db.put("3",3);
		ReservationManager.printDB(db);
		ReservationManager.printDB(db);
		ReservationManager.printDB(db);
		ReservationManager.printDB(db);
		ReservationManager.printDB(db);
	}
}

class DBEntry {
	/**
	 * Creates a new tuple for db entry
	 * @param my_data the data in the entry
	 * @param pointer pointer to the controlling thread
	 */
	public DBEntry(int data, clearingTimer timer){
		this.data = data;
		this.timer=timer;
	}
	
	public int data;
	public clearingTimer timer = null;
}

