package de.uka.ipd.consensus.impl;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.Properties;

import javax.naming.Context;
import javax.naming.InitialContext;
import javax.sql.DataSource;
import javax.swing.event.EventListenerList;

import de.uka.ipd.consensus.foundation.AbstractConsensusFoundationModule;
import de.uka.ipd.consensus.foundation.ConsensusFoundationManager;
import de.uka.ipd.consensus.foundation.logging.LoggingManager;
import de.uka.ipd.consensus.foundation.ontology.OntologyManager;
import de.uka.ipd.consensus.foundation.rating.RatingEvent;
import de.uka.ipd.consensus.foundation.rating.RatingListener;
import de.uka.ipd.consensus.foundation.rating.RatingManager;
import de.uka.ipd.consensus.foundation.rating.RatingManagerDuplicateRatingException;
import de.uka.ipd.consensus.foundation.rating.RatingManagerException;
import de.uka.ipd.consensus.foundation.rating.RatingManagerRatingInUseException;
import de.uka.ipd.consensus.foundation.rights.DynamicRightsManager;
import de.uka.ipd.consensus.foundation.rights.DynamicRightsManagerException;
import de.uka.ipd.consensus.foundation.schema.Association;
import de.uka.ipd.consensus.foundation.schema.Attribute;
import de.uka.ipd.consensus.foundation.schema.Concept;
import de.uka.ipd.consensus.foundation.schema.Rateable;
import de.uka.ipd.consensus.foundation.schema.Rating;
import de.uka.ipd.consensus.foundation.schema.Topic;
import de.uka.ipd.consensus.foundation.schema.User;
import de.uka.ipd.consensus.foundation.schema.Version;
import de.uka.ipd.consensus.foundation.schema.Versionable;
import de.uka.ipd.consensus.foundation.user.UserManager;

/**
 * 
 * TODO
 * @author Thomas Much
 * @version 2006-06-14
 */
public class SQLRatingManagerImpl extends AbstractConsensusFoundationModule implements RatingManager {

	public static final String NO      = "NO";
	public static final String YES     = "YES";
	public static final String NEUTRAL = "NEUTRAL";

	private static final String RATINGS_TABLE      = "CFRATINGS";
	private static final String HISTORY_TABLE      = "CFRATINGHIST";
	private static final String STATS_TABLE        = "CFRATINGSTAT";
	private static final String RATINGS_DATASOURCE = "jdbc/datenquelle";

	private static final int    RATEABLE_TYPE_CONCEPT = 'C';
	private static final int    RATEABLE_TYPE_USER    = 'U';

	private EventListenerList    listenerList = new EventListenerList();
	private boolean              running      = false;
	private DataSource           pool;
	private DynamicRightsManager drm;


	public void init(Properties config, LoggingManager logging, UserManager users, OntologyManager ontology) throws Exception {
		running = true;
		
		Context init = new InitialContext( config );
		Context env  = (Context)init.lookup("java:comp/env");
		
		String source = config.getProperty("cfimpl.ratingdatasource");
		
		if ((source == null) || (source.length() == 0)) {
			source = RATINGS_DATASOURCE;
		}

		pool = (DataSource)env.lookup(source);
		
		if (Boolean.valueOf(config.getProperty("cfimpl.initdatabase")).booleanValue()) {
			initDatabase();
		}

		logging.debug("RatingManager init done.");
	}


	public void initCompleted(Properties config, ConsensusFoundationManager cfm) throws Exception {
		super.initCompleted(config, cfm);
		this.drm = cfm.getDynamicRightsManager();
		cfm.getLoggingManager().debug("RatingManager initCompleted done.");
	}

	
	public void shutdown() {
		if (running) {
			running = false;
			getLoggingManager().debug("RatingManager shutdown.");
		}
	}

	
	public boolean changesAllowed() {
		return true;
	}

	
	public String ratingValueToString(double value) {
		if (value < -0.5) {
			return NO;
		}
		else if (value > 0.5) {
			return YES;
		}
		else {
			return NEUTRAL;
		}
	}


	public double stringToRatingValue(String str) {
		if (str.equalsIgnoreCase( YES )) {
			return Rating.YES;
		}
		else if (str.equalsIgnoreCase( NO )) {
			return Rating.NO;
		}
		else {
			return Rating.NEUTRAL;
		}
	}


	public Rating createRating(User creator, double value) throws RatingManagerException,DynamicRightsManagerException {
		if (this.drm != null) {
			this.drm.checkCreateRating( creator );
		}

		Connection con      = null;
		int        id       = -1;
		long       date     = new Date().getTime();
		String     username = creator.getUsername();

		try {
			con = pool.getConnection();
			
			con.setReadOnly(false);

			PreparedStatement ps = con.prepareStatement( "INSERT INTO " + RATINGS_TABLE + " (USERNAME,VALUE,RATINGDATE) VALUES (?,?,?)", Statement.RETURN_GENERATED_KEYS );
			
			ps.setString( 1, username );
			ps.setDouble( 2, value );
			ps.setLong( 3, date );

			ps.execute();
			
			ResultSet rs = ps.getGeneratedKeys();
			
			if (rs.next()) {
				id = rs.getInt( 1 );
			}
			else {
				throw new RatingManagerException("no auto-generated id");
			}
			
			rs.close();
			ps.close();
		}
		catch (Exception e) {
			throw new RatingManagerException(e);
		}
		finally {
			if (con != null) {
				try {
					con.close();
				}
				catch (Exception e) {
					throw new RatingManagerException(e);
				}
			}
		}

		return new SQLRatingImpl( this, id, creator, username, value, date );
	}


	private Rating getRating(User user, Rateable rateable, String rateableid) {
		assert (rateable != null);

		if (user == null) return null;
		
		String username = user.getUsername();
		
		if ((username == null) || (username.length() == 0)) return null;
		if ((rateableid == null) || (rateableid.length() == 0)) return null;
		
		Connection con = null;
		Rating     r   = null;
		
		try {
			con = pool.getConnection();
			
			con.setReadOnly(true);

			PreparedStatement ps = con.prepareStatement("SELECT ID,VALUE,COMMENT,RATINGDATE,RATEDDATE FROM " + RATINGS_TABLE + " WHERE USERNAME=? AND RATEABLEID=?");

			ps.setString( 1, username );
			ps.setString( 2, rateableid );
			
			ResultSet rs = ps.executeQuery();
			
			if (rs.next()) {
				int    id           = rs.getInt( 1 );
				double value        = rs.getDouble( 2 );
				String comment      = rs.getString( 3 );
				long   date         = rs.getLong( 4 );
				long   rateddate    = rs.getLong( 5 );

				r = new SQLRatingImpl( this, id, user, username, rateable, value, comment, date, rateddate );
			}
			
			rs.close();
			ps.close();
		}
		catch (Exception e) {
			this.getLoggingManager().error("Error reading rating: " + e);
		}
		finally {
			if (con != null) {
				try {
					con.close();
				}
				catch (Exception e) {
					this.getLoggingManager().error("Error reading rating: " + e);
				}
			}
		}
		
		return r;
	}


	private Collection getRatings(Rateable rateable, String rateableid) {
		assert (rateable != null);

		if ((rateableid == null) || (rateableid.length() == 0)) return Collections.EMPTY_SET;
		
		Connection con  = null;
		List       list = new ArrayList();
		
		try {
			con = pool.getConnection();
			
			con.setReadOnly(true);

			PreparedStatement ps = con.prepareStatement("SELECT ID,USERNAME,VALUE,COMMENT,RATINGDATE,RATEDDATE FROM " + RATINGS_TABLE + " WHERE RATEABLEID=? ORDER BY RATINGDATE DESC");

			ps.setString( 1, rateableid );
			
			ResultSet rs = ps.executeQuery();
			
			while (rs.next()) {
				int    id           = rs.getInt( 1 );
				String username     = rs.getString( 2 );
				double value        = rs.getDouble( 3 );
				String comment      = rs.getString( 4 );
				long   date         = rs.getLong( 5 );
				long   rateddate    = rs.getLong( 6 );
				
				Rating r = new SQLRatingImpl( this, id, username, rateable, value, comment, date, rateddate );
				
				list.add( r );
			}
			
			rs.close();
			ps.close();
		}
		catch (Exception e) {
			this.getLoggingManager().error("Error reading ratings: " + e);
		}
		finally {
			if (con != null) {
				try {
					con.close();
				}
				catch (Exception e) {
					this.getLoggingManager().error("Error reading ratings: " + e);
				}
			}
		}

		return Collections.unmodifiableCollection( list );
	}


	protected Collection getHistory(SQLRatingImpl rating) {
		assert (rating != null);
		
		Connection con  = null;
		List       list = new ArrayList();

		int      id       = rating.getId();
		String   username = rating.getUsername();
		User     creator  = rating.getCreator();
		Rateable rateable = rating.getInternalRateable();
		
		try {
			con = pool.getConnection();
			
			con.setReadOnly(true);

			PreparedStatement ps = con.prepareStatement("SELECT ID,RATINGID,VALUE,COMMENT,RATINGDATE,RATEDDATE FROM " + HISTORY_TABLE + " WHERE RATINGID=? ORDER BY RATINGDATE DESC");
			
			ps.setInt( 1, id );
			
			ResultSet rs = ps.executeQuery();
			
			while (rs.next()) {
				double value        = rs.getDouble( 3 );
				String comment      = rs.getString( 4 );
				long   date         = rs.getLong( 5 );
				long   rateddate    = rs.getLong( 6 );
				
				Rating r = new SQLRatingImpl( this, id, creator, username, rateable, value, comment, date, rateddate, true );
				
				list.add( r );
			}
			
			rs.close();
			ps.close();
		}
		catch (Exception e) {
			this.getLoggingManager().error("Error reading rating history: " + e);
		}
		finally {
			if (con != null) {
				try {
					con.close();
				}
				catch (Exception e) {
					this.getLoggingManager().error("Error reading rating history: " + e);
				}
			}
		}

		return Collections.unmodifiableCollection( list );
	}
	
	
	private int getRatingsCount(String rateableid) {
		if ((rateableid == null) || (rateableid.length() == 0)) return 0;

		Connection con   = null;
		int        count = 0;
		
		try {
			con = pool.getConnection();
			
			con.setReadOnly(true);

			PreparedStatement ps = con.prepareStatement("SELECT RATEABLEID,RATINGCOUNT FROM " + STATS_TABLE + " WHERE RATEABLEID=?");
			
			ps.setString( 1, rateableid );
			
			ResultSet rs = ps.executeQuery();
			
			if (rs.next()) {
				count = rs.getInt( 2 );
			}
			
			rs.close();
			ps.close();
		}
		catch (Exception e) {
			this.getLoggingManager().error("Error reading ratings count: " + e);
		}
		finally {
			if (con != null) {
				try {
					con.close();
				}
				catch (Exception e) {
					this.getLoggingManager().error("Error reading ratings count: " + e);
				}
			}
		}
		
		return count;
	}
	
	
	private double getAverageRatingValue(String rateableid) {
		if ((rateableid == null) || (rateableid.length() == 0)) return Rating.NEUTRAL;

		Connection con   = null;
		double     value = Rating.NEUTRAL;
		
		try {
			con = pool.getConnection();
			
			con.setReadOnly(true);

			PreparedStatement ps = con.prepareStatement("SELECT RATEABLEID,AVGVALUE FROM " + STATS_TABLE + " WHERE RATEABLEID=?");
			
			ps.setString( 1, rateableid );
			
			ResultSet rs = ps.executeQuery();
			
			if (rs.next()) {
				value = rs.getDouble( 2 );
			}
			
			rs.close();
			ps.close();
		}
		catch (Exception e) {
			this.getLoggingManager().error("Error reading average rating value: " + e);
		}
		finally {
			if (con != null) {
				try {
					con.close();
				}
				catch (Exception e) {
					this.getLoggingManager().error("Error reading average rating value: " + e);
				}
			}
		}
		
		return value;
	}


	private void addRating(SQLRatingImpl rating, Rateable rateable, String rateableid, int rateableType) throws RatingManagerException,DynamicRightsManagerException {
		assert (rateable != null);
		
		if (rating == null) return;

		if (this.drm != null) {
			this.drm.checkAddRating( rating, rateable );
		}
		
		if (rating.isHistorical()) {
			throw new RatingManagerRatingInUseException( " Historical rating " + rating + " cannot be added again" );
		}

		User creator = rating.getCreator();

		if (creator == null) return;
		
		String username = creator.getUsername();
		
		if ((username == null) || (username.length() == 0)) return;
		if ((rateableid == null) || (rateableid.length() == 0)) return;

		if (rating.getInternalRateable() != null) {
			throw new RatingManagerRatingInUseException( rating + " is already associated with " + rating.getRateable() );
		}
		
		Connection con = null;
		
		try {
			con = pool.getConnection();
			
			con.setReadOnly(true);

			PreparedStatement ps = con.prepareStatement("SELECT ID FROM " + RATINGS_TABLE + " WHERE USERNAME=? AND RATEABLEID=?");

			ps.setString( 1, username );
			ps.setString( 2, rateableid );
			
			ResultSet rs = ps.executeQuery();
			
			if (rs.next()) {
				throw new RatingManagerDuplicateRatingException( creator + " has already a rating associated with " + rateable );
			}
			
			rs.close();
			ps.close();

			con.setReadOnly(false);
			
			long rateddate = 0L;
			if (rateable instanceof Versionable) {
				// Users aren't Versionable
				Version ver = ((Versionable)rateable).getVersion();
				if (ver != null) {
					rateddate = ver.getDate().getTime();
				}
			}

			ps = con.prepareStatement("UPDATE " + RATINGS_TABLE + " SET RATEABLEID=?,RATEABLETYPE=?,RATEDDATE=? WHERE ID=?");
			
			ps.setString( 1, rateableid );
			ps.setInt( 2, rateableType );
			ps.setLong( 3, rateddate );
			ps.setInt( 4, ((SQLRatingImpl)rating).getId() );

			ps.execute();
			ps.close();
			
			this.updateRatingStats( con, rateableid );
			
			((SQLRatingImpl)rating).setRateable( rateable );

			this.fireRatingSet( rating, rateable );
		}
		catch (RatingManagerException e) {
			throw e;
		}
		catch (Exception e) {
			throw new RatingManagerException(e);
		}
		finally {
			if (con != null) {
				try {
					con.close();
				}
				catch (Exception e) {
					throw new RatingManagerException(e);
				}
			}
		}
	}


	private void updateRatingStats( Connection con, String rateableid ) throws Exception {
		con.setReadOnly(true);

		PreparedStatement ps = con.prepareStatement("SELECT COUNT(ID),AVG(VALUE) FROM " + RATINGS_TABLE + " WHERE RATEABLEID=?");

		ps.setString( 1, rateableid );
		
		ResultSet rs = ps.executeQuery();
		
		int    count;
		double avg;

		if (rs.next()) {
			count = rs.getInt( 1 );
			avg   = rs.getDouble( 2 );
		}
		else {
			count = 0;
			avg   = Rating.NEUTRAL;
		}
		
		rs.close();
		ps.close();

		con.setReadOnly(false);

		ps = con.prepareStatement("UPDATE " + STATS_TABLE + " SET AVGVALUE=?,RATINGCOUNT=? WHERE RATEABLEID=?");
		
		ps.setDouble( 1, avg );
		ps.setInt( 2, count );
		ps.setString( 3, rateableid );
		
		int updates = ps.executeUpdate();
		
		ps.close();
		
		if (updates == 0) {
			// first rating for rateable; no row yet, create a new one:
			ps = con.prepareStatement("INSERT INTO " + STATS_TABLE + " (RATEABLEID,AVGVALUE,RATINGCOUNT) VALUES (?,?,?)");
			
			ps.setString( 1, rateableid );
			ps.setDouble( 2, avg );
			ps.setInt( 3, count );
			
			ps.execute();
			ps.close();
		}
	}


	protected void changeRatingValue(SQLRatingImpl rating, double newval, double oldval, long newdate, long olddate, long newrateddate, long oldrateddate, String oldcomment ) {
		assert (rating != null);
		
		Rateable rateable = rating.getInternalRateable();

		Connection con = null;
		
		try {
			con = pool.getConnection();

			con.setReadOnly(false);
			
			if (rateable == null) {
				// only update newval and newdate, leave comment as it is

				PreparedStatement ps = con.prepareStatement("UPDATE " + RATINGS_TABLE + " SET VALUE=?,RATINGDATE=? WHERE ID=?");
				
				ps.setDouble( 1, newval );
				ps.setLong( 2, newdate );
				ps.setInt( 3, rating.getId() );
				
				ps.execute();
				ps.close();
			}
			else {
				// copy current values to history

				PreparedStatement ps = con.prepareStatement("INSERT INTO " + HISTORY_TABLE + " (RATINGID,VALUE,COMMENT,RATINGDATE,RATEDDATE) VALUES (?,?,?,?,?)");
				
				ps.setInt( 1, rating.getId() );
				ps.setDouble( 2, oldval );
				ps.setString( 3, oldcomment );
				ps.setLong( 4, olddate );
				ps.setLong( 5, oldrateddate );

				ps.execute();
				ps.close();
				
				// update newval and newdate, reset comment
				
				ps = con.prepareStatement("UPDATE " + RATINGS_TABLE + " SET VALUE=?,COMMENT=?,RATINGDATE=?,RATEDDATE=? WHERE ID=?");

				ps.setDouble( 1, newval );
				ps.setString( 2, null );
				ps.setLong( 3, newdate );
				ps.setLong( 4, newrateddate );
				ps.setInt( 5, rating.getId() );
				
				ps.execute();
				ps.close();

				this.updateRatingStats( con, rateable.getId() );
				
				this.fireRatingChanged( rating, rateable, oldval );
			}
		}
		catch (Exception e) {
			this.getLoggingManager().error("Error changing rating value: " + e);
		}
		finally {
			if (con != null) {
				try {
					con.close();
				}
				catch (Exception e) {
					this.getLoggingManager().error("Error changing rating value: " + e);
				}
			}
		}
	}


	protected void changeRatingComment(SQLRatingImpl rating, String comment) {
		assert (rating != null);

		Connection con = null;
		
		try {
			con = pool.getConnection();
			
			con.setReadOnly(false);

			PreparedStatement ps = con.prepareStatement("UPDATE " + RATINGS_TABLE + " SET COMMENT=? WHERE ID=?");
			
			ps.setString( 1, comment );
			ps.setInt( 2, rating.getId() );

			ps.execute();
			ps.close();
		}
		catch (Exception e) {
			this.getLoggingManager().error("Error changing rating comment: " + e);
		}
		finally {
			if (con != null) {
				try {
					con.close();
				}
				catch (Exception e) {
					this.getLoggingManager().error("Error changing rating comment: " + e);
				}
			}
		}
	}


	/**
	 * returns all (TODO) user's ratings, newest first
	 */
	public Collection getUsersOwnRatings(User user) {
		// TODO: Anzahl/Offset übergeben?
		if (user == null) return Collections.EMPTY_SET;
		
		String username = user.getUsername();
		
		Connection con  = null;
		List       list = new ArrayList();
		
		try {
			con = pool.getConnection();
			
			con.setReadOnly(true);

			PreparedStatement ps = con.prepareStatement("SELECT ID,RATEABLEID,RATEABLETYPE,VALUE,COMMENT,RATINGDATE,RATEDDATE FROM " + RATINGS_TABLE + " WHERE USERNAME=? ORDER BY RATINGDATE DESC");

			ps.setString( 1, username );
			
			ResultSet rs = ps.executeQuery();

			while (rs.next()) {
				int    id           = rs.getInt( 1 );
				String rateableid   = rs.getString( 2 );
				int    rateableType = rs.getInt( 3 );
				double value        = rs.getDouble( 4 );
				String comment      = rs.getString( 5 );
				long   date         = rs.getLong( 6 );
				long   rateddate    = rs.getLong( 7 );
				
				Rateable rateable;
				
				if (rateableType == RATEABLE_TYPE_USER) {
					rateable = this.getUserManager().getUserById( rateableid );
				}
				else {
					rateable = this.getOntologyManager().getQueryEngine().getConceptById( user, rateableid );
				}
				
				Rating r = new SQLRatingImpl( this, id, user, username, rateable, value, comment, date, rateddate );
				
				list.add( r );
			}
			
			rs.close();
			ps.close();
		}
		catch (Exception e) {
			this.getLoggingManager().error("Error reading own ratings: " + e);
		}
		finally {
			if (con != null) {
				try {
					con.close();
				}
				catch (Exception e) {
					this.getLoggingManager().error("Error reading own ratings: " + e);
				}
			}
		}

		return Collections.unmodifiableCollection( list );
	}


	public double getUsersAverageOwnRatingsValue(User user) {
		if (user == null) return Rating.NEUTRAL;

		String username = user.getUsername();

		Connection con     = null;
		double     average = Rating.NEUTRAL;
		
		try {
			con = pool.getConnection();
			
			con.setReadOnly(true);

			PreparedStatement ps = con.prepareStatement("SELECT AVG(VALUE) FROM " + RATINGS_TABLE + " WHERE USERNAME=?");

			ps.setString( 1, username );
			
			ResultSet rs = ps.executeQuery();

			if (rs.next()) {
				average = rs.getDouble( 1 );
			}
			
			rs.close();
			ps.close();
		}
		catch (Exception e) {
			this.getLoggingManager().error("Error reading own ratings avg value: " + e);
		}
		finally {
			if (con != null) {
				try {
					con.close();
				}
				catch (Exception e) {
					this.getLoggingManager().error("Error reading own ratings avg value: " + e);
				}
			}
		}

		return average;
	}


	public int getUsersOwnRatingsCount(User user) {
		if (user == null) return 0;

		String username = user.getUsername();

		Connection con   = null;
		int        count = 0;
		
		try {
			con = pool.getConnection();
			
			con.setReadOnly(true);

			PreparedStatement ps = con.prepareStatement("SELECT COUNT(ID) FROM " + RATINGS_TABLE + " WHERE USERNAME=?");

			ps.setString( 1, username );
			
			ResultSet rs = ps.executeQuery();

			if (rs.next()) {
				count = rs.getInt( 1 );
			}
			
			rs.close();
			ps.close();
		}
		catch (Exception e) {
			this.getLoggingManager().error("Error reading own ratings count: " + e);
		}
		finally {
			if (con != null) {
				try {
					con.close();
				}
				catch (Exception e) {
					this.getLoggingManager().error("Error reading own ratings count: " + e);
				}
			}
		}

		return count;
	}


	public void removeRating(User remover, Rating rating) throws RatingManagerException,DynamicRightsManagerException {
		if (rating.isHistorical()) return;

		SQLRatingImpl ri = (SQLRatingImpl)rating;

		Rateable rateable = ri.getInternalRateable();

		if (rateable == null) return;

		if (this.drm != null) {
			this.drm.checkRemoveRating( remover, rating );
		}

		Connection con = null;

		try {
			con = pool.getConnection();
			
			con.setReadOnly( false );

			PreparedStatement ps = con.prepareStatement("DELETE FROM " + RATINGS_TABLE + " WHERE ID=?");
			
			ps.setInt( 1, ri.getId() );
			
			ps.execute();
			ps.close();
			
			ps = con.prepareStatement("DELETE FROM " + HISTORY_TABLE + " WHERE RATINGID=?");

			ps.setInt( 1, ri.getId() );
			
			ps.execute();
			ps.close();

			this.updateRatingStats( con, rateable.getId() );

			ri.setHistorical( true );
			ri.setRateable( null );

			this.fireRatingRemoved( rating, rateable );
		}
		catch (Exception e) {
			throw new RatingManagerException(e);
		}
		finally {
			if (con != null) {
				try {
					con.close();
				}
				catch (Exception e) {
					throw new RatingManagerException(e);
				}
			}
		}			
	}
	
	
	private void deleteRatings(String rateableid) throws RatingManagerException {
		Connection con = null;

		try {
			con = pool.getConnection();
			
			con.setReadOnly( false );

			// TODO: aus HISTORY_TABLE löschen... dafür alle passenden IDs aus RATINGS_TABLE raussuchen und zuordnen... oder rateableid in History speichern?

			PreparedStatement ps = con.prepareStatement("DELETE FROM " + RATINGS_TABLE + " WHERE RATEABLEID=?");
			
			ps.setString( 1, rateableid );
			
			ps.execute();
			ps.close();
			
			ps = con.prepareStatement("DELETE FROM " + STATS_TABLE + " WHERE RATEABLEID=?");
			
			ps.setString( 1, rateableid );
			
			ps.execute();
			ps.close();
		}
		catch (Exception e) {
			throw new RatingManagerException(e);
		}
		finally {
			if (con != null) {
				try {
					con.close();
				}
				catch (Exception e) {
					throw new RatingManagerException(e);
				}
			}
		}			
	}


	public Rating getRating(Topic topic, User rater) {
		if (topic != null) {
			return this.getRating( rater, topic, topic.getId() );
		}
		else {
			return null;
		}
	}

	
	public Rating getRating(Attribute attribute, User rater) {
		if (attribute != null) {
			return this.getRating( rater, attribute, attribute.getId() );
		}
		else {
			return null;
		}
	}

	
	public Rating getRating(Association association, User rater) {
		if (association != null) {
			return this.getRating( rater, association, association.getId() );
		}
		else {
			return null;
		}
	}

	
	public Rating getRating(User user, User rater) {
		if (user != null) {
			return this.getRating( rater, user, user.getId() );
		}
		else {
			return null;
		}
	}


	public Collection getRatings(Topic topic) {
		if (topic != null) {
			return this.getRatings( topic, topic.getId() );
		}
		else {
			return Collections.EMPTY_SET;
		}
	}

	
	public Collection getRatings(Attribute attribute) {
		if (attribute != null) {
			return this.getRatings( attribute, attribute.getId() );
		}
		else {
			return Collections.EMPTY_SET;
		}
	}

	
	public Collection getRatings(Association association) {
		if (association != null) {
			return this.getRatings( association, association.getId() );
		}
		else {
			return Collections.EMPTY_SET;
		}
	}

	
	public Collection getRatings(User user) {
		if (user != null) {
			return this.getRatings( user, user.getId() );
		}
		else {
			return Collections.EMPTY_SET;
		}
	}
	
	
	public int getRatingsCount(Association association) {
		if (association != null) {
			return this.getRatingsCount( association.getId() );
		}
		else {
			return 0;
		}
	}

	
	public int getRatingsCount(Attribute attribute) {
		if (attribute != null) {
			return this.getRatingsCount( attribute.getId() );
		}
		else {
			return 0;
		}
	}


	public int getRatingsCount(Topic topic) {
		if (topic != null) {
			return this.getRatingsCount( topic.getId() );
		}
		else {
			return 0;
		}
	}

	
	public int getRatingsCount(User user) {
		if (user != null) {
			return this.getRatingsCount( user.getId() );
		}
		else {
			return 0;
		}
	}
	

	public double getAverageRatingValue(Association association) {
		if (association != null) {
			return this.getAverageRatingValue( association.getId() );
		}
		else {
			return Rating.NEUTRAL;
		}
	}

	
	public double getAverageRatingValue(Attribute attribute) {
		if (attribute != null) {
			return this.getAverageRatingValue( attribute.getId() );
		}
		else {
			return Rating.NEUTRAL;
		}
	}

	
	public double getAverageRatingValue(Topic topic) {
		if (topic != null) {
			return this.getAverageRatingValue( topic.getId() );
		}
		else {
			return Rating.NEUTRAL;
		}
	}

	
	public double getAverageRatingValue(User user) {
		if (user != null) {
			return this.getAverageRatingValue( user.getId() );
		}
		else {
			return Rating.NEUTRAL;
		}
	}

	
	public void addRating(Topic rated, Rating rating) throws RatingManagerException,DynamicRightsManagerException {
		if (rated != null) {
			this.addRating( (SQLRatingImpl)rating, rated, rated.getId(), RATEABLE_TYPE_CONCEPT );
		}
	}


	public void addRating(Attribute rated, Rating rating) throws RatingManagerException,DynamicRightsManagerException {
		if (rated != null) {
			this.addRating( (SQLRatingImpl)rating, rated, rated.getId(), RATEABLE_TYPE_CONCEPT );
		}
	}

	
	public void addRating(Association rated, Rating rating) throws RatingManagerException,DynamicRightsManagerException {
		if (rated != null) {
			this.addRating( (SQLRatingImpl)rating, rated, rated.getId(), RATEABLE_TYPE_CONCEPT );
		}
	}


	public void addRating(User rated, Rating rating) throws RatingManagerException,DynamicRightsManagerException {
		if (rated != null) {
			this.addRating( (SQLRatingImpl)rating, rated, rated.getId(), RATEABLE_TYPE_USER );
		}
	}

	
	public void deleteRatings(Topic topic) throws RatingManagerException {
		if (topic != null) {
			this.deleteRatings( topic.getId() );
		}
	}
	
	
	public void deleteRatings(Attribute attribute) throws RatingManagerException {
		if (attribute != null) {
			this.deleteRatings( attribute.getId() );
		}
	}
	
	
	public void deleteRatings(Association association) throws RatingManagerException {
		if (association != null) {
			this.deleteRatings( association.getId() );
		}
	}
	
	
	public void deleteRatings(User user) throws RatingManagerException {
		if (user != null) {
			this.deleteRatings( user.getId() );
		}
	}


	public void addRatingListener(RatingListener listener) {
		listenerList.add(RatingListener.class, listener);
	}

	
	public void removeRatingListener(RatingListener listener) {
		listenerList.remove(RatingListener.class, listener);
	}

	
	private void fireRatingSet(Rating source, Rateable rated) {
		if (rated instanceof Concept) {
			this.fireConceptRatingSet(source,source.getCreator(),(Concept)rated);
		}
		else if (rated instanceof User) {
			this.fireUserRatingSet(source,source.getCreator(),(User)rated);
		}
	}


	private void fireRatingChanged(Rating source, Rateable rated, double oldRatingValue) {
		if (rated instanceof Concept) {
			this.fireConceptRatingChanged(source,source.getCreator(),(Concept)rated,oldRatingValue);
		}
		else if (rated instanceof User) {
			this.fireUserRatingChanged(source,source.getCreator(),(User)rated,oldRatingValue);
		}
	}


	private void fireRatingRemoved(Rating source, Rateable rated) {
		if (rated instanceof Concept) {
			this.fireConceptRatingRemoved(source,source.getCreator(),(Concept)rated);
		}
		else if (rated instanceof User) {
			this.fireUserRatingRemoved(source,source.getCreator(),(User)rated);
		}
	}

	
	private void fireConceptRatingSet(Rating source, User rater, Concept ratedConcept) {
		 RatingEvent event = null;
	     Object[] listeners = listenerList.getListenerList();
	     for (int i = listeners.length-2; i>=0; i-=2) {
	         if (listeners[i]==RatingListener.class) {
	             if (event == null) event = new RatingEvent(getConsensusFoundationManager(),source,rater,ratedConcept);
	             ((RatingListener)listeners[i+1]).conceptRatingSet(event);
	         }
	     }
	 }

	 
	private void fireConceptRatingChanged(Rating source, User rater, Concept ratedConcept, double oldRatingValue) {
		 RatingEvent event = null;
	     Object[] listeners = listenerList.getListenerList();
	     for (int i = listeners.length-2; i>=0; i-=2) {
	         if (listeners[i]==RatingListener.class) {
	             if (event == null) event = new RatingEvent(getConsensusFoundationManager(),source,rater,ratedConcept,oldRatingValue);
	             ((RatingListener)listeners[i+1]).conceptRatingChanged(event);
	         }
	     }
	 }

	
	private void fireConceptRatingRemoved(Rating source, User rater, Concept ratedConcept) {
		 RatingEvent event = null;
	     Object[] listeners = listenerList.getListenerList();
	     for (int i = listeners.length-2; i>=0; i-=2) {
	         if (listeners[i]==RatingListener.class) {
	             if (event == null) event = new RatingEvent(getConsensusFoundationManager(),source,rater,ratedConcept);
	             ((RatingListener)listeners[i+1]).conceptRatingRemoved(event);
	         }
	     }
	 }

	 
	private void fireUserRatingSet(Rating source, User rater, User ratedUser) {
		 RatingEvent event = null;
	     Object[] listeners = listenerList.getListenerList();
	     for (int i = listeners.length-2; i>=0; i-=2) {
	         if (listeners[i]==RatingListener.class) {
	             if (event == null) event = new RatingEvent(getConsensusFoundationManager(),source,rater,ratedUser);
	             ((RatingListener)listeners[i+1]).userRatingSet(event);
	         }
	     }
	 }

	 
	private void fireUserRatingChanged(Rating source, User rater, User ratedUser, double oldRatingValue) {
		 RatingEvent event = null;
	     Object[] listeners = listenerList.getListenerList();
	     for (int i = listeners.length-2; i>=0; i-=2) {
	         if (listeners[i]==RatingListener.class) {
	             if (event == null) event = new RatingEvent(getConsensusFoundationManager(),source,rater,ratedUser,oldRatingValue);
	             ((RatingListener)listeners[i+1]).userRatingChanged(event);
	         }
	     }
	 }

	
	private void fireUserRatingRemoved(Rating source, User rater, User ratedUser) {
		 RatingEvent event = null;
	     Object[] listeners = listenerList.getListenerList();
	     for (int i = listeners.length-2; i>=0; i-=2) {
	         if (listeners[i]==RatingListener.class) {
	             if (event == null) event = new RatingEvent(getConsensusFoundationManager(),source,rater,ratedUser);
	             ((RatingListener)listeners[i+1]).userRatingRemoved(event);
	         }
	     }
	 }

	
	private void initDatabase() throws Exception {
		Connection con = null;

		try {
			con = pool.getConnection();
			
			con.setReadOnly(false);
			
			Statement stmt = con.createStatement();

			stmt.execute("CREATE TABLE IF NOT EXISTS `" + RATINGS_TABLE + "` (\n"
					+ "`ID` INT NOT NULL PRIMARY KEY AUTO_INCREMENT,\n"
					+ "`USERNAME` VARCHAR(255),\n"
					+ "`RATEABLEID` VARCHAR(255),\n"
					+ "`RATEABLETYPE` INT,\n"
					+ "`VALUE` DOUBLE,\n"
					+ "`COMMENT` VARCHAR(255),\n"
					+ "`RATINGDATE` BIGINT,\n"
					+ "`RATEDDATE` BIGINT\n"
					+ ")\n"
					+ "ENGINE = MYISAM\n"
					+ "CHARACTER SET utf8;");

			stmt.execute("CREATE TABLE IF NOT EXISTS `" + HISTORY_TABLE + "` (\n"
					+ "`ID` INT NOT NULL PRIMARY KEY AUTO_INCREMENT,\n"
					+ "`RATINGID` INT NOT NULL,\n"
					+ "`VALUE` DOUBLE,\n"
					+ "`COMMENT` VARCHAR(255),\n"
					+ "`RATINGDATE` BIGINT,\n"
					+ "`RATEDDATE` BIGINT\n"
					+ ")\n"
					+ "ENGINE = MYISAM\n"
					+ "CHARACTER SET utf8;");
			
			stmt.execute("CREATE TABLE IF NOT EXISTS `" + STATS_TABLE + "` (\n"
					+ "`RATEABLEID` VARCHAR(255) NOT NULL PRIMARY KEY,\n"
					+ "`AVGVALUE` DOUBLE,\n"
					+ "`RATINGCOUNT` INT\n"
					+ ")\n"
					+ "ENGINE = MYISAM\n"
					+ "CHARACTER SET utf8;");
			
			stmt.close();
		}
		finally {
			if (con != null) {
				con.close();
			}
		}
	}


	public String getVersion() {
		return "2006-06-14 tm (" + this.getClass().getName() + ")";
	}

}
