package de.uka.ipd.consensus.impl;

import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.Properties;

import javax.swing.event.EventListenerList;

import org.tm4j.tologx.QueryEvaluator;
import org.tm4j.tologx.QueryEvaluatorFactory;
import org.tm4j.tologx.TologResultsSet;

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.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.user.UserManager;

/**
 * TODO
 * @author Thomas Much
 * @version 2006-06-14
 * @deprecated slow... SQLRatingManagerImpl is used instead
 *
 */
public class TM4JRatingManagerImpl extends AbstractConsensusFoundationModule implements RatingManager {

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


	private EventListenerList listenerList = new EventListenerList();

	private org.tm4j.topicmap.Topic ratingTopicType;
	private org.tm4j.topicmap.Topic rateableTopicType;

	private boolean running = false;


	public TM4JRatingManagerImpl() {
		throw new UnsupportedOperationException("DO NOT USE - INCOMPLETE AND SLOW IMPLEMENTATION");
	}

	
	public void init(Properties config, LoggingManager logging, UserManager users, OntologyManager ontology) throws Exception {
		running = true;
		
		OntologyManagerImpl omi = (OntologyManagerImpl)ontology;

		ratingTopicType   = omi.getOrCreateTopicType("Rating","ConsensusFoundation rating",omi.getInternalTopicType(),null);
		rateableTopicType = omi.getOrCreateTopicType("Rateable","ConsensusFoundation rateable",omi.getInternalTopicType(),null);

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


	public void initCompleted(Properties config, ConsensusFoundationManager cfm) throws Exception {
		super.initCompleted(config, cfm);
		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 {

		OntologyManagerImpl omi = (OntologyManagerImpl)this.getOntologyManager();

		org.tm4j.topicmap.Topic tmTopic;
		TM4JRatingImpl ri;

		try {
			tmTopic = omi.createNewTopic( null, null, this.getRatingTopicType() );

			omi.createNewCreatedByAssociation( creator, tmTopic );

			ri = (TM4JRatingImpl)TM4JObjectWrapper.wrap( omi, tmTopic );

			ri.initValue( value );
		}
		catch (Exception e) {
			throw new RatingManagerException(e);
		}

		return ri;
	}


	protected Rateable getRateable(TM4JRatingImpl rating) {
		if (rating != null) {
			OntologyManagerImpl omi = (OntologyManagerImpl)this.getOntologyManager();
			// get all associations in which the wrapped topic plays a role
			// (should be "created-by", and if the rating is assigned to a user/topic/association, at least one other)
			Collection ca = omi.getTopicMap().getUtils().getAssociations( rating.getWrappedTopic() );
			if (ca != null) {
				Iterator ita = ca.iterator();
				while (ita.hasNext()) {
					org.tm4j.topicmap.Association tmAssociation = (org.tm4j.topicmap.Association)ita.next();
					// are there any members of role "rating"? (either none or exactly one)
					Collection cm = omi.getMembersOfRole( tmAssociation, this.getRatingTopicType() );
					if ((cm != null) && (cm.size() > 0)) {
						// yes, we've found the right association
						if (tmAssociation.isOfType( this.getRatingTopicType() )) {
							// the association is an internal association for topic/user ratings
							// (the rated topic or user is the only player of member with role "rateable"
							org.tm4j.topicmap.Topic tmTopic = omi.getFirstPlayer( tmAssociation, this.getRateableTopicType() );
							Object obj = TM4JObjectWrapper.wrap( omi, tmTopic );
							if (obj instanceof Rateable) {
								return (Rateable)obj;
							}
						}
						else {
							// this is a rating for the association itself, so return it:
							return TM4JObjectWrapper.wrap( omi, tmAssociation );
						}
					}
				}
			}
		}
		return null;		
	}


	private void addRating(Rateable rated, Rating rating, org.tm4j.topicmap.Topic wrappedTopic) throws RatingManagerException {
		if (rating.getRateable() != null) {
			throw new RatingManagerRatingInUseException( rating + " is already associated with " + rating.getRateable() );
		}

		try {
			OntologyManagerImpl omi = (OntologyManagerImpl)this.getOntologyManager();
			org.tm4j.topicmap.Association association = omi.getFirstAssociation( wrappedTopic, this.getRatingTopicType() );

			org.tm4j.topicmap.Member memberRating = null;

			if (association == null) {
				association = omi.getTopicMap().createAssociation( null, null, this.getRatingTopicType(), null );
				
				omi.createSourceLocator( association );

				org.tm4j.topicmap.Member memberRateable = association.createMember(null);

				omi.createSourceLocator( memberRateable );

				memberRateable.setRoleSpec( this.getRateableTopicType() );
				memberRateable.addPlayer( wrappedTopic );

				// TODO: Assoziation-erzeugen in spezielle Methoden auslagern?
			}
			else {
				Collection members = omi.getMembersOfRole( association, this.getRatingTopicType() );

				if (members != null) {
					Iterator itm = members.iterator();
					if (itm.hasNext()) {
						memberRating = (org.tm4j.topicmap.Member)itm.next();
					}
				}
			}

			if (memberRating == null) {
				memberRating = association.createMember(null);
				
				omi.createSourceLocator( memberRating );
				
				memberRating.setRoleSpec( this.getRatingTopicType() );
			}
			
			memberRating.addPlayer( ((TM4JRatingImpl)rating).getWrappedTopic() );

			this.fireRatingSet( rating, rated );
		}
		catch (Exception e) {
			throw new RatingManagerException(e);
		}
	}


	public void addRating(Topic rated, Rating rating) throws RatingManagerException,DynamicRightsManagerException {
		User creator = rating.getCreator();
		if (this.getRating(rated,creator) != null) {
			throw new RatingManagerDuplicateRatingException( creator + " has already a rating associated with " + rated );
		}
		
		this.addRating( rated, rating, ((TopicImpl)rated).getWrappedTopic() );
	}


	public void addRating(User rated, Rating rating) throws RatingManagerException,DynamicRightsManagerException {
		User creator = rating.getCreator();
		if (this.getRating(rated,creator) != null) {
			throw new RatingManagerDuplicateRatingException( creator + " has already a rating associated with " + rated );
		}
		
		this.addRating( rated, rating, ((UserImpl)rated).getWrappedTopic() );
	}


	public void addRating(Attribute rated, Rating rating) throws RatingManagerException,DynamicRightsManagerException {
		User creator = rating.getCreator();
		if (this.getRating(rated,creator) != null) {
			throw new RatingManagerDuplicateRatingException( creator + " has already a rating associated with " + rated );
		}

		if (rating.getRateable() != null) {
			throw new RatingManagerRatingInUseException( rating + " is already associated with " + rating.getRateable() );
		}

		// TODO

		this.fireRatingSet(rating,rated);
	}
	
	
	public void addRating(Association rated, Rating rating) throws RatingManagerException,DynamicRightsManagerException {
		User creator = rating.getCreator();
		if (this.getRating(rated,creator) != null) {
			throw new RatingManagerDuplicateRatingException( creator + " has already a rating associated with " + rated );
		}

		if (rating.getRateable() != null) {
			throw new RatingManagerRatingInUseException( rating + " is already associated with " + rating.getRateable() );
		}

		OntologyManagerImpl omi = (OntologyManagerImpl)this.getOntologyManager();

		org.tm4j.topicmap.Association association = ((AssociationImpl)rated).getWrappedAssociation();

		if (association.isOfType( this.getRatingTopicType() )) {
			throw new RatingManagerDuplicateRatingException( "Internal rating association cannot be rated: " + rated );
		}

		try {
			org.tm4j.topicmap.Member memberRating = null;
			
			Collection members = omi.getMembersOfRole( association, this.getRatingTopicType() );

			if (members != null) {
				Iterator itm = members.iterator();
				if (itm.hasNext()) {
					memberRating = (org.tm4j.topicmap.Member)itm.next();
				}
			}
			
			if (memberRating == null) {
				memberRating = association.createMember(null);
				
				omi.createSourceLocator( memberRating );
				
				memberRating.setRoleSpec( this.getRatingTopicType() );
			}

			memberRating.addPlayer( ((TM4JRatingImpl)rating).getWrappedTopic() );

			this.fireRatingSet(rating,rated);
		}
		catch (Exception e) {
			throw new RatingManagerException(e);
		}
	}


	public void removeRating(User remover, Rating rating) throws RatingManagerException,DynamicRightsManagerException {
		if (rating.isHistorical()) return;
		// TODO
		// TODO: fireRatingRemoved
	}
	
	
	private Rating getRating(Collection ratings, User rater) {
		if ((ratings != null) && (ratings.size() > 0)) {
			Iterator it = ratings.iterator();
			while (it.hasNext()) {
				Rating r = (Rating)it.next();
				if (rater.equals( r.getCreator() )) {
					return r;
				}
			}
		}
		return null;
	}


	private double getAverageRatingValue(Collection c) {
		int size = c.size();
		if (size > 0) {
			double summe = Rating.NEUTRAL;
			Iterator it = c.iterator();
			while (it.hasNext()) {
				Rating r = (Rating)it.next();
				summe += r.getValue();
			}
			return summe / size;
		}
		else {
			return Rating.NEUTRAL;
		}
	}


	public Collection getRatings(Topic topic) {
		OntologyManagerImpl omi = (OntologyManagerImpl)this.getOntologyManager();
		Collection players = omi.getPlayers( ((TopicImpl)topic).getWrappedTopic(), this.getRatingTopicType(), this.getRatingTopicType() );
		return TM4JCollectionWrapperSet.getInstance( omi, players );
	}


	public Rating getRating(Topic topic, User rater) {
		Collection c = this.getRatings( topic );
		return this.getRating( c, rater );
	}


	public Collection getRatings(User user) {
		OntologyManagerImpl omi = (OntologyManagerImpl)this.getOntologyManager();
		Collection players = omi.getPlayers( ((UserImpl)user).getWrappedTopic(), this.getRatingTopicType(), this.getRatingTopicType() );
		return TM4JCollectionWrapperSet.getInstance( omi, players );
	}
	
	
	public Rating getRating(User user, User rater) {
		Collection c = this.getRatings( user );
		return this.getRating( c, rater );
	}


	public Collection getRatings(Attribute attribute) {
		// TODO
		return Collections.EMPTY_SET;
	}
	
	
	public Rating getRating(Attribute attribute, User rater) {
		Collection c = this.getRatings( attribute );
		return this.getRating( c, rater );
	}


	public Collection getRatings(Association association) {
		OntologyManagerImpl omi = (OntologyManagerImpl)this.getOntologyManager();
		Collection players = omi.getPlayers( ((AssociationImpl)association).getWrappedAssociation(), this.getRatingTopicType() );
		return TM4JCollectionWrapperSet.getInstance( omi, players );
	}


	public Rating getRating(Association association, User rater) {
		Collection c = this.getRatings( association );
		return this.getRating( c, rater );
	}


	public int getRatingsCount(Association association) {
		return this.getRatings(association).size();
	}


	public int getRatingsCount(Attribute attribute) {
		return this.getRatings(attribute).size();
	}


	public int getRatingsCount(Topic topic) {
		return this.getRatings(topic).size();
	}


	public int getRatingsCount(User user) {
		return this.getRatings(user).size();
	}


	public double getAverageRatingValue(Association association) {
		Collection c = this.getRatings( association );
		return this.getAverageRatingValue( c );
	}


	public double getAverageRatingValue(Attribute attribute) {
		Collection c = this.getRatings( attribute );
		return this.getAverageRatingValue( c );
	}


	public double getAverageRatingValue(Topic topic) {
		Collection c = this.getRatings( topic );
		return this.getAverageRatingValue( c );
	}


	public double getAverageRatingValue(User user) {
		Collection c = this.getRatings( user );
		return this.getAverageRatingValue( c );
	}


	public Collection getUsersOwnRatings(User user) {
		OntologyManagerImpl omi = (OntologyManagerImpl)this.getOntologyManager();
		Object txa = null;
		try {
			final String query = "select $T from role-player($R1," + ((UserImpl)user).getId() + "),type($R1," + omi.getUserTopicType().getID()
							+ "),type($A," + omi.getCreatedbyTopicType().getID() + "),direct-instance-of($T," + this.getRatingTopicType().getID()
							+ "),association-role($A,$R1),association-role($A,$R2),role-player($R2,$T)?";
			txa = omi.openTransaction(user);
			QueryEvaluator qe = QueryEvaluatorFactory.newQueryEvaluator( omi.getTopicMap() );
			TologResultsSet rs = qe.execute( query );
			return TM4JTologResultWrapperSet.getInstance( omi, rs );
		}
		catch (Exception e) {
			this.getLoggingManager().error("Error reading user's own ratings: " + e);
			return Collections.EMPTY_SET;
		}
		finally {
			omi.abortTransaction( user, txa );
		}
	}


	public double getUsersAverageOwnRatingsValue(User user) {
		Collection c = this.getUsersOwnRatings( user );
		return this.getAverageRatingValue( c );
	}


	public int getUsersOwnRatingsCount(User user) {
		return this.getUsersOwnRatings(user).size();
	}

	
	public void deleteRatings(Topic topic) throws RatingManagerException {
		// TODO
	}
	
	
	public void deleteRatings(Attribute attribute) throws RatingManagerException {
		// TODO
	}
	
	
	public void deleteRatings(Association association) throws RatingManagerException {
		// TODO
	}
	
	
	public void deleteRatings(User user) throws RatingManagerException {
		// TODO
	}


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

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

	
	protected 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);
		}
	}

	
	protected 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 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 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);
	         }
	     }
	 }

	
	protected org.tm4j.topicmap.Topic getRatingTopicType() {
		return ratingTopicType;
	}
	
	
	protected org.tm4j.topicmap.Topic getRateableTopicType() {
		return rateableTopicType;
	}

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