package eu.dnetlib.functionality.rating.app;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import org.hibernate.Session;
import eu.dnetlib.domain.functionality.Rating;
import eu.dnetlib.functionality.rating.dao.RatingDAO;
import eu.dnetlib.functionality.rating.dao.RatingDAOException;
import eu.dnetlib.utils.hibernate.HibernateUtil;

/**
 * This class implements rating DAO using Hibernate.
 * @author thanos@di.uoa.gr
 *
 */
public class RatingDAOImpl implements RatingDAO {
	private static final String GET_TOP_DOCUMENTS_QUERY = "SELECT documentId, AVG(score) FROM Rating GROUP BY documentId ORDER BY AVG(score) DESC";
	private static final String GET_TOP_RATINGS_QUERY = "FROM Rating ORDER BY score DESC";
	private static final String RATE_QUERY = "FROM Rating WHERE userId = ? AND documentId = ?";
	private static final String SEARCH_BY_DOCUMENT_QUERY = "FROM Rating WHERE documentId = ? ORDER BY score DESC";
	private static final String SEARCH_BY_USER_QUERY = "FROM Rating WHERE userId = ? ORDER BY score DESC";
	
	private HibernateUtil hibernateUtil; 
	
	/**
	 * Create a new rating DAO implementation.
	 *
	 */
	public RatingDAOImpl() {}
	
	public void setHibernateUtil(HibernateUtil hibernateUtil) {
		this.hibernateUtil = hibernateUtil;
	}
	
	@SuppressWarnings("unchecked")
	@Override
	public List<Rating> getTopDocuments(int limit) throws RatingDAOException {
		List<Rating> result = new ArrayList<Rating>();
		try {
			Session session = hibernateUtil.getSession();
			session.beginTransaction();
			Iterator<Object []> iterator = ((List<Object []>) session.createQuery(GET_TOP_DOCUMENTS_QUERY).setMaxResults(limit).list()).iterator();
			while (iterator.hasNext()) {
				Object [] row = (Object []) iterator.next();
				result.add(new Rating((String) row[0], ((Double) row[1]).floatValue()));
			}
			session.getTransaction().commit();
			return result;
		} catch (Exception e) {
			throw new RatingDAOException("error retrieving top " + limit + " documents", e);
		}
	}
	
	@SuppressWarnings("unchecked")
	@Override
	public List<Rating> getTopRatings(int limit) throws RatingDAOException {
		try {
			Session session = hibernateUtil.getSession();
			session.beginTransaction();
			List<Rating> result = (List<Rating>) session.createQuery(GET_TOP_RATINGS_QUERY).setMaxResults(limit).list();
			session.getTransaction().commit();
			return result;
		} catch (Exception e) {
			throw new RatingDAOException("error retrieving top " + limit + " ratings", e);
		}
	}

	@Override
	public void rate(Rating rating) throws RatingDAOException {
		try {
			Session session = hibernateUtil.getSession();
			session.beginTransaction();
			Rating previousRating = Rating.class.cast(session.createQuery(RATE_QUERY).setString(0, rating.getUserId()).setString(1, rating.getDocumentId()).uniqueResult());
			if (previousRating == null) // previous rating does not exist; create new
				session.save(rating);
			else // user has already rated the document; just update score
				previousRating.setScore(rating.getScore());
			session.getTransaction().commit();
		} catch (Exception e) {
			throw new RatingDAOException("error rating document " + rating.getDocumentId() + " for user " + rating.getUserId(), e);
		}
	}
	
	@SuppressWarnings("unchecked")
	@Override
	public List<Rating> searchByDocument(String documentId) throws RatingDAOException {
		try {
			Session session = hibernateUtil.getSession();
			session.beginTransaction();
			List<Rating> result = (List<Rating>) session.createQuery(SEARCH_BY_DOCUMENT_QUERY).setString(0, documentId).list();
			session.getTransaction().commit();
			return result;
		} catch (Exception e) {
			throw new RatingDAOException("error retrieving ratings for document " + documentId, e);
		}
	}

	@SuppressWarnings("unchecked")
	@Override
	public List<Rating> searchByUser(String userId) throws RatingDAOException {
		try {
			Session session = hibernateUtil.getSession();
			session.beginTransaction();
			List<Rating> ratings = (List<Rating>) session.createQuery(SEARCH_BY_USER_QUERY).setString(0, userId).list();
			List<Rating> result = new ArrayList<Rating>();
			for (Rating rating : ratings)
				result.add(new Rating(rating.getUserId(), rating.getDocumentId(), rating.getScore()));
			session.getTransaction().commit();
			return result;
		} catch (Exception e) {
			throw new RatingDAOException("error retrieving ratings for user " + userId, e);
		}
	}
}
