/////////////////////////////////////////////////////////////////////////
//
// © Institute for Software Science, University of Vienna, 2004
//
// Copyright in this software belongs to Institute for Software Science, 
// University of Vienna, Nordbergstrasse 15/C/3, 1090 Vienna, Austria
//
// This software may not be used, sold, licensed, transferred, copied
// or reproduced in whole or in part in any manner or form or in or
// on any media by any person other than in accordance with the terms
// of the Licence Agreement supplied with the software, or otherwise
// without the prior written consent of the copyright owners.
//
// This software is distributed WITHOUT ANY WARRANTY, without even the
// implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
// PURPOSE, except where stated in the Licence Agreement supplied with
// the software.
//
//	Created By :		Gerhard Engelbrecht
//	Created Date :		2004/04/20
//	Created for Project:	GEMSS
//
////////////////////////////////////////////////////////////////////////
//
// Dependencies: None
//
/////////////////////////////////////////////////////////////////////////
//
//	Last commit info:	$Author: gerry $
//					$Date: 2004/08/11 16:32:31 $
//					$Revision: 1.2 $
//
/////////////////////////////////////////////////////////////////////////

package at.ac.univie.iss.service.state;

import java.io.EOFException;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.util.Iterator;
import java.util.Vector;
import java.util.logging.Level;
import java.util.logging.Logger;

import at.ac.univie.iss.service.global.ServiceInfo;

/**
*	The <code>ThreadLockedRepositoryImpl</code> is an implementation of the
*	<code>DetailsRepository</code> interface. The details repository holds
*	information about clients and their states and optional information about
*	the QoS state.<p>
*	This <code>ThreadLockedRepositoryImpl</code> solves the synchronization 
*	problem occuring when multiple clients/threads accessing the repository by
*	locking the resources with Java's thread locking mechanism.
*
*	@author		Gerhard Engelbrecht
*/
final public class ThreadLockedRepositoryImpl implements DetailsRepository {

	private static Logger logger = null;

	private File repositoryFile = null;
	private boolean locked = false;

	private ThreadLockedRepositoryImpl() {}

    /**
    *	Creates a new and empty details repository in the given file.
    */
	public ThreadLockedRepositoryImpl(File repositoryFile) throws Exception {

		logger = ServiceInfo.getLogger("details");
		
		this.repositoryFile = repositoryFile;
		
		if (repositoryFile.exists() && repositoryFile.delete()) {
			
			logger.log(Level.FINER, "Old repository file deleted.\n");
		}
		logger.log(Level.FINER, "Repository (" + repositoryFile + ") initialized.\n");
	}


	private synchronized void lock() {
	
		while (locked) {
			try {
				wait();
			}
			catch (InterruptedException interruptedException) {
			}
		}
		locked = true;
		notify();
	}

	private synchronized void unlock() {
	
		while (!locked) {
			try {
				wait();
			}
			catch (InterruptedException interruptedException) {
			}
		}
		locked = false;
		notify();
	}
	

	public Details getDetails(String cid) {
	
		logger.log(Level.FINER, "Started.\n");
		
		if (!repositoryFile.exists()) {
			
			logger.log(Level.FINER, "Repository does not exist, returning null.\n");
			return null;
		}

		Details returnDetails = null;
		int count = 0;
	
		try {
			this.lock();
			logger.log(Level.FINER, "Repository locked.\n");

			FileInputStream fileInputStream = new FileInputStream(repositoryFile);
			ObjectInputStream inputStream = new ObjectInputStream(fileInputStream);
			
			boolean eof = false;
			Details details = null;
			
			while (!eof) {
			
				try {
					details = (Details)inputStream.readObject();
					count++;

					logger.log(Level.FINEST, "Read Details object:\n" + details + "\n");

					IdDetails idDetails = (IdDetails)details.getDetails(StateConstants.IDDETAILS);
	
					if (cid.equals(idDetails.getCId())) {
						
						logger.log(Level.FINEST, "Found given cid in detail repository.\n");
						returnDetails = details;
						eof = true;
					}
				}
				catch (EOFException eofException) {
	
					logger.log(Level.FINER, "End of file (Read " + count + " details objects).\n");
					eof = true;
				}
				catch (Exception readException) {

					logger.log(Level.WARNING, "A read error occured!\n", readException);
					eof = true;
				}
			}
			inputStream.close();
			this.unlock();
			logger.log(Level.FINER, "Repository unlocked.\n");
		}
		catch (Exception exception) {

			this.unlock();
			logger.log(Level.SEVERE, "An error occured (see details below).\n", exception);
		}
		logger.log(Level.FINER, "Returning:\n" + returnDetails + "\n");
		return returnDetails;
	}

	public void setDetails(Details details) {
	
		logger.log(Level.FINER, "Started.\n");
		
		IdDetails idDetails = (IdDetails)details.getDetails(StateConstants.IDDETAILS);
		boolean replaced = false;
		int count = 0;

		Vector detailsFromFile = new Vector();
	
		try {
			this.lock();
			logger.log(Level.FINER, "Repository locked.\n");
			
			try {
				FileInputStream fileInputStream = new FileInputStream(repositoryFile);
				ObjectInputStream inputStream = new ObjectInputStream(fileInputStream);
				
				
				boolean eof = false;
				Details readDetails = null;
				
				while (!eof) {
				
					try {
						readDetails = (Details)inputStream.readObject();
						count++;
	
						logger.log(Level.FINEST, "Read Details object:\n" + readDetails + "\n");
	
						IdDetails readIdDetails = (IdDetails)readDetails.getDetails(StateConstants.IDDETAILS);
		
						if (idDetails.getCId().equals(readIdDetails.getCId())) {
							
							logger.log(Level.FINEST, "Replaced read details with given Details object:\n" + details + "\n");
							detailsFromFile.add(details);
							replaced = true;
						}
						else {
							detailsFromFile.add(readDetails);
						}							
					}
					catch (EOFException eofException) {
	
						logger.log(Level.FINER, "End of file (Read " + count + " details objects).\n");
						eof = true;
					}
					catch (Exception readException) {
	
						logger.log(Level.WARNING, "A read error occured!\n", readException);
						eof = true;
					}
				}
				inputStream.close();
			}
			catch (FileNotFoundException fnfException) {
				logger.log(Level.FINER, "Repository file not found.\n");
			}
			if (!replaced) {

				// if not replaced: add given details

				logger.log(Level.FINEST, "Given details CId not found in DB, added given Details object:\n" + details + "\n");
				detailsFromFile.add(details);
			}

			FileOutputStream fileOutputStream = new FileOutputStream(repositoryFile);
			ObjectOutputStream outputStream = new ObjectOutputStream(fileOutputStream);
			boolean error = false;
			
			Details writeDetails = null;

			for (count = 0; ((count < detailsFromFile.size()) && (!error)); count++) {
			
				try {
					writeDetails = (Details)detailsFromFile.get(count);

					logger.log(Level.FINEST, "Write Details object:\n" + writeDetails + "\n");

					outputStream.writeObject(writeDetails);
				}
				catch (Exception writeException) {
					logger.log(Level.SEVERE, "A write error occured!\n", writeException);
					error = true;
				}
			}
			outputStream.close();
			logger.log(Level.FINER, "Wrote " + (count + 1) + " details objects.\n");
		
			this.unlock();
		
			logger.log(Level.FINER, "Repository unlocked.\n");
		}
		catch (Exception exception) {
			this.unlock();
			logger.log(Level.SEVERE, "An error occured (see details below).\n", exception);
		}
		logger.log(Level.FINER, "Finished.\n");
	}

	public Details removeDetails(String cid) {
	
		logger.log(Level.FINER, "Started.\n");

		Vector detailsFromFile = new Vector();
		int count = 0;
	
		try {
			this.lock();
			logger.log(Level.FINER, "Repository locked.\n");
			
			FileInputStream fileInputStream = new FileInputStream(repositoryFile);
			ObjectInputStream inputStream = new ObjectInputStream(fileInputStream);
			
			boolean eof = false;
			Details readDetails = null;
			
			while (!eof) {
			
				try {
					readDetails = (Details)inputStream.readObject();
					count++;

					logger.log(Level.FINEST, "Read Details object:\n" + readDetails + "\n");

					IdDetails idDetails = (IdDetails)readDetails.getDetails(StateConstants.IDDETAILS);
	
					if (!cid.equals(idDetails.getCId())) {
						
						detailsFromFile.add(readDetails);
					}
				}
				catch (EOFException eofException) {
	
					logger.log(Level.FINER, "End of file (Read " + count + " details objects).\n");
					eof = true;
				}
				catch (Exception readException) {

					logger.log(Level.WARNING, "A read error occured!\n", readException);
					eof = true;
				}
			}
			
			inputStream.close();			

			FileOutputStream fileOutputStream = new FileOutputStream(repositoryFile);
			ObjectOutputStream outputStream = new ObjectOutputStream(fileOutputStream);

			boolean error = false;
			Details writeDetails = null;

			for (count = 0; ((count < detailsFromFile.size()) && (!error)); count++) {
			
				try {
					writeDetails = (Details)detailsFromFile.get(count);

					logger.log(Level.FINEST, "Write Details object:\n" + writeDetails + "\n");

					outputStream.writeObject(writeDetails);
				}
				catch (Exception writeException) {
					logger.log(Level.SEVERE, "A write error occured!\n", writeException);
					error = true;
				}
			}
			outputStream.flush();
			outputStream.close();
			logger.log(Level.FINER, "Wrote " + (count + 1) + " details objects.\n");

			this.unlock();
			logger.log(Level.FINER, "Repository unlocked.\n");
		}
		catch (Exception exception) {
			this.unlock();
			logger.log(Level.SEVERE, "An error occured (see details below).\n", exception);
		}
		logger.log(Level.FINER, "Finished.\n");

		return null;
	}

	public synchronized String toString() {
	
		StringBuffer buffer = new StringBuffer();
		
		buffer.append("[Details repository: ");

		try {
			this.lock();
			FileInputStream fileInputStream = new FileInputStream(repositoryFile);
			ObjectInputStream inputStream = new ObjectInputStream(fileInputStream);
			
			boolean eof = false;
			Details details = null;
			int count = 0;
			
			while (!eof) {
			
				try {
					details = (Details)inputStream.readObject();
					
					if (count > 0) {
						buffer.append(",\n");
					}
					buffer.append("" + count + ". " + details);
				}
				catch (EOFException eofException) {
	
					logger.log(Level.FINER, "End of file.\n");
					eof = true;
				}
				catch (Exception readException) {

					logger.log(Level.WARNING, "A read error occured!\n", readException);
					eof = true;
				}
				count++;
			}
			inputStream.close();	

			this.unlock();
		}
		catch (Exception exception) {

			this.unlock();
			logger.log(Level.SEVERE, "An error occured (see details below).\n", exception);
		}
		buffer.append("]");

		return buffer.toString();
	}
	
}