/*
Storybook: Open Source software for novelists and authors.
Copyright (C) 2008 - 2012 Martin Mustun

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
package storybook.model.hbn.dao;

import com.googlecode.genericdao.dao.hibernate.GenericDAO;
import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import org.hibernate.Criteria;
import org.hibernate.HibernateException;
import org.hibernate.Query;
import org.hibernate.Session;
import org.hibernate.criterion.Restrictions;
import storybook.model.Model;
import storybook.model.hbn.entity.Chapter;
import storybook.model.hbn.entity.Item;
import storybook.model.hbn.entity.Location;
import storybook.model.hbn.entity.Part;
import storybook.model.hbn.entity.Person;
import storybook.model.hbn.entity.Plot;
import storybook.model.hbn.entity.Scene;
import storybook.model.hbn.entity.Strand;
import storybook.tools.DateUtil;
import storybook.tools.LOG;
import storybook.tools.ListUtil;
import storybook.tools.Period;
import storybook.tools.comparator.ObjectComparator;
import storybook.ui.MainFrame;

public class SceneDAO extends _GenericDAO<Scene, Long>
		implements SceneDAOInterface {

	private static final String TT = "SceneDAOImpl";

	public static final String SELECT_SCENE = "select s from Scene as s";
	public static final String SELECT_COUNT = "select count(s) from Scene as s";
	public static final String SELECT_DISTINCT = "select distinct cast(s.scene_ts as DATE), s.scene_ts from scene s";
	public static final String ORDER_BY_TS = " order by s.scene_ts";

	public SceneDAO() {
		super();
	}

	public SceneDAO(Session session) {
		super(session);
	}

	public Date findFirstDate() {
		List<Date> dates = findDistinctDates();
		if (dates.isEmpty()) {
			return new Date();
		}
		return dates.get(0);
	}

	public Date findLastDate() {
		List<Date> dates = findDistinctDates();
		if (dates.isEmpty()) {
			return new Date();
		}
		return dates.get(dates.size() - 1);
	}

	@SuppressWarnings("unchecked")
	@Override
	public List<Scene> findAll() {
		Query query = session
				.createQuery(SELECT_SCENE
						+ " left join s.chapter as ch"
						+ " order by ch.chapterno, s.sceneno");
		return (List<Scene>) query.list();
	}

	@SuppressWarnings("unchecked")
	public List<Scene> findByPart(Part part) {
		Query query = session.createQuery(SELECT_SCENE
				+ " inner join s.chapter as chapter"
				+ " inner join chapter.part as part"
				+ " where chapter.part=:part"
				+ " order by part.number, chapter.chapterno, s.sceneno");
		query.setEntity(DAOutil.PART, part);
		return (List<Scene>) query.list();
	}

	@SuppressWarnings("unchecked")
	public List<Scene> findByStatus(int state) {
		Criteria crit = session.createCriteria(Scene.class);
		crit.add(Restrictions.eq(DAOutil.STATUS, state));
		return (List<Scene>) crit.list();
	}

	@SuppressWarnings("unchecked")
	public List<Scene> findStateInProgress() {
		Criteria crit = session.createCriteria(Scene.class);
		crit.add(Restrictions.ne(DAOutil.STATUS, 5));
		return (List<Scene>) crit.list();
	}

	@SuppressWarnings("unchecked")
	public List<Scene> findScenesWithRelativeSceneId() {
		Criteria crit = session.createCriteria(Scene.class);
		crit.add(Restrictions.isNotNull("relativesceneid"));
		return (List<Scene>) crit.list();
	}

	@SuppressWarnings("unchecked")
	public List<Scene> findScenesWithRelativeSceneId(Scene scene) {
		Criteria crit = session.createCriteria(Scene.class);
		crit.add(Restrictions.eq("relativesceneid", scene.getId()));
		return (List<Scene>) crit.list();
	}

	public long countByPerson(Person person) {
		Query query = session.createQuery(
				SELECT_COUNT
				+ " join s.persons as p"
				+ " where p=:person");
		query.setEntity(DAOutil.PERSON, person);
		return (Long) query.uniqueResult();
	}

	public long countByItem(Item item) {
		Query query = session.createQuery(
				SELECT_COUNT
				+ " join s.items as l"
				+ " where l=:item");
		query.setEntity(DAOutil.ITEM, item);
		return (Long) query.uniqueResult();
	}

	public long countByLocation(Location location) {
		Query query = session.createQuery(
				SELECT_COUNT
				+ " join s.locations as l"
				+ " where l=:location");
		query.setEntity(DAOutil.LOCATION, location);
		return (Long) query.uniqueResult();
	}

	public Scene getRelativeScene(Scene scene) {
		return (Scene) session.get(Scene.class, scene.getRelativesceneid());
	}

	public List<Date> findDistinctDates() {
		return findDistinctDates(null);
	}

	@SuppressWarnings("unchecked")
	public List<Date> findDistinctDatesByStrand(Strand strand) {
		List<Date> dates = new ArrayList<>();
		if (strand == null) {
			return dates;
		}
		// native SQL
		String sql = SELECT_DISTINCT
				+ " where s.strand_id=:strand_id"
				+ ORDER_BY_TS;
		Query query = session.createSQLQuery(sql);
		try {
			query.setParameter("strand_id", strand.getId());
		} catch (HibernateException ex) {
			LOG.err(TT + ".findDistinctDatesByStrand \n" + sql + "\n nothing selected");
			return (new ArrayList<>());
		}
		List<Object> ret = query.list();
		for (int i = 0; i < ret.size(); ++i) {
			Object[] oa = (Object[]) ret.get(i);
			dates.add((Date) oa[0]);
		}
		dates = ListUtil.removeNullAndDuplicates(dates);
		Collections.sort(dates, new ObjectComparator());
		return dates;
	}

	@SuppressWarnings("unchecked")
	public List<Date> findDistinctDates(Part part) {
		//LOG.trace(TT + ".findDistinctDates(part " + AbstractEntity.trace(part) + ")");
		List<Date> dates = new ArrayList<>();
		List<Scene> scenes = findAll();
		for (Scene scene : scenes) {
			if (scene.hasRelativescene()) {
				Date date = null;
				for (Scene s : scenes) {
					if (s.getId().equals(scene.getRelativesceneid())) {
						date = s.getDate();
						if (date == null) {
							break;
						}
						if (!scene.getRelativetime().isEmpty()) {
							date = DateUtil.add(date, s.getSbDuration());
						} else {
							date = DateUtil.add(date, scene.getRelativetime());
						}
						break;
					}
				}
				scene.setDate(date);
				if (date != null) {
					dates.add(date);
				}
			} else if (scene.hasScenets()) {
				dates.add(scene.getDate());
			}

		}
		dates = ListUtil.removeNullAndDuplicates(dates);
		Collections.sort(dates, new ObjectComparator());
		return dates;
	}

	@SuppressWarnings("unchecked")
	public List<Scene> findByDate(Date date) {
		if (date == null) {
			return new ArrayList<>();
		}
		Query query = session.createQuery(SELECT_SCENE
				+ " where s.scene_ts"
				+ " between :tsStart and :tsEnd"
				+ " order by s.scene_ts, s.sceneno");
		Timestamp tsStart = new Timestamp(date.getTime());
		date = DateUtil.addDays(date, 1);
		date = DateUtil.addMilliseconds(date, -1);
		Timestamp tsEnd = new Timestamp(date.getTime());
		query.setTimestamp("tsStart", tsStart);
		query.setTimestamp("tsEnd", tsEnd);
		return (List<Scene>) query.list();
	}

	public List<Scene> findByDate(Date date1, Date date2) {
		List<Scene> scenes = findAll();
		List<Scene> scene1 = findByDate(date1);
		List<Scene> scene2 = findByDate(date2);
		List<Scene> r = new ArrayList<>();
		if (scene1.isEmpty() || scene2.isEmpty()) {
			return (r);
		}
		int s1 = scenes.indexOf(scene1.get(0));
		int s2 = scenes.indexOf(scene2.get(0));
		return scenes.subList(s1, s2);
	}

	@SuppressWarnings("unchecked")
	public List<Scene> findByPerson(Person person) {
		if (person == null) {
			return new ArrayList<>();
		}
		Query query = session.createQuery(SELECT_SCENE
				+ " join s.persons as p"
				+ " where p=:person");
		query.setParameter("person", person);
		return query.list();
	}

	@SuppressWarnings("unchecked")
	public List<Scene> findByPlot(Plot plot) {
		if (plot == null) {
			return new ArrayList<>();
		}
		Query query = session.createQuery(SELECT_SCENE
				+ " join s.plots as p"
				+ " where p=:plot");
		query.setParameter(DAOutil.PLOT, plot);
		return query.list();
	}

	@SuppressWarnings("unchecked")
	public List<Scene> findByLocationLink(Location location) {
		if (location == null) {
			return new ArrayList<>();
		}
		Query query = session.createQuery(SELECT_SCENE
				+ " join s.locations as l"
				+ " where l=:location");
		query.setParameter(DAOutil.LOCATION, location);
		return query.list();
	}

	@SuppressWarnings("unchecked")
	public List<Scene> findByItem(Item item) {
		if (item == null) {
			return new ArrayList<>();
		}
		Query query = session.createQuery(SELECT_SCENE
				+ " join s.items as l"
				+ " where l=:item");
		query.setParameter(DAOutil.ITEM, item);
		return query.list();
	}

	@SuppressWarnings("unchecked")
	public List<Scene> findByPeriod(Period period) {
		List<Scene> ret = new ArrayList<>();
		if (period == null) {
			return (ret);
		}
		List<Scene> scenes = findAll();
		for (Scene scene : scenes) {
			if (scene.hasScenets() && period.isInside(scene.getDate())) {
				ret.add(scene);
			}
		}
		return ret;
	}

	@SuppressWarnings("unchecked")
	public List<Scene> findByStrands(Strand strand) {
		if (strand == null) {
			return new ArrayList<>();
		}
		Query query = session.createQuery(SELECT_SCENE
				+ " join s.strands as st"
				+ " where st=:strand");
		query.setParameter(DAOutil.STRAND, strand);
		return query.list();
	}

	@SuppressWarnings("unchecked")
	public List<Scene> findByStrand(Strand strand) {
		if (strand == null) {
			return new ArrayList<>();
		}
		Query query = session.createQuery(SELECT_SCENE
				+ " where s.strand=:strand");
		query.setParameter(DAOutil.STRAND, strand);
		return query.list();
	}

	@SuppressWarnings("unchecked")
	public List<Scene> findByStrandAndDate(Strand strand, Date date) {
		if (date == null || strand == null) {
			return new ArrayList<>();
		}
		Query query = session
				.createQuery(SELECT_SCENE
						+ " left join s.chapter as ch"
						+ " where s.scene_ts equals :tsStart and s.strand=:strand"
						+ " order by s.scene_ts, ch.chapterno, s.sceneno");
		Timestamp tsStart = new Timestamp(date.getTime());
		query.setTimestamp("tsStart", tsStart);
		query.setEntity(DAOutil.STRAND, strand);
		List<Scene> ret = query.list();
		List<Scene> scenes = findScenesWithRelativeSceneId();
		for (Scene scene : scenes) {
			if (scene.getStrand() != null && !scene.getStrand().getId().equals(strand.getId())) {
				continue;
			}
			Scene relativeScene = (Scene) session.get(Scene.class, scene.getRelativesceneid());
			Date sceneDate = scene.getRelativedate(relativeScene);
			if (sceneDate == null) {
				continue;
			}
			if (sceneDate.compareTo(date) != 0) {
				continue;
			}
			ret.add(scene);
		}
		return ret;
	}

	public List<Scene> findByChapter(Chapter chapter) {
		if (chapter == null) {
			return new ArrayList<>();
		}
		ChapterDAO dao = new ChapterDAO(session);
		return dao.findScenes(chapter);
	}

	@SuppressWarnings("unchecked")
	public List<Person> findNarrators() {
		List<Scene> ret = findAll();
		List<Person> narrators = new ArrayList<>();
		for (Scene s : ret) {
			narrators.add(s.getNarrator());
		}
		narrators = ListUtil.setUnique(narrators);
		return narrators;
	}

	@SuppressWarnings("unchecked")
	public List<Scene> findByNarrator(Person narrator) {
		if (narrator == null) {
			return new ArrayList<>();
		}
		Query query = session.createQuery(SELECT_SCENE
				+ " where s.narrator=:narrator");
		query.setParameter("narrator", narrator);
		return query.list();
	}

	public static List<Person> findNarrators(MainFrame mainFrame) {
		Model model = mainFrame.getBookModel();
		Session session = model.beginTransaction();
		SceneDAO sceneDAO = new SceneDAO(session);
		List<Person> persons = sceneDAO.findNarrators();
		model.commit();
		return persons;
	}

	@SuppressWarnings("unchecked")
	public List<Scene> findScenesToExport() {
		Query query = session.createQuery(SELECT_SCENE
				+ " inner join s.chapter as chapter"
				+ " inner join chapter.part as part"
				+ " order by part.number, chapter.chapterno, s.sceneno");
		return query.list();
	}

}

interface SceneDAOInterface extends GenericDAO<Scene, Long> {
}
