/*
 * Copyright (C) 2021 favdb
 *
 * 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.entity;

import i18n.I18N;
import java.io.File;
import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import org.hibernate.Session;
import storybook.ctrl.Ctrl;
import static storybook.model.EntityUtil.findEntities;
import storybook.model.Model;
import storybook.model.book.Book;
import static storybook.model.book.Book.TYPE.CHAPTER;
import static storybook.model.book.Book.TYPE.ITEM;
import static storybook.model.book.Book.TYPE.ITEMLINK;
import static storybook.model.book.Book.TYPE.LOCATION;
import static storybook.model.book.Book.TYPE.PART;
import static storybook.model.book.Book.TYPE.PERSON;
import static storybook.model.book.Book.TYPE.PLOT;
import static storybook.model.book.Book.TYPE.RELATION;
import static storybook.model.book.Book.TYPE.STRAND;
import static storybook.model.book.Book.TYPE.TAG;
import static storybook.model.book.Book.TYPE.TAGLINK;
import storybook.model.hbn.dao.ChapterDAO;
import storybook.model.hbn.dao.SceneDAO;
import storybook.model.hbn.dao.TaglinkDAO;
import storybook.tools.DateUtil;
import storybook.tools.LOG;
import storybook.tools.ListUtil;
import storybook.tools.Period;
import storybook.tools.SbDuration;
import storybook.tools.comparator.ObjectComparator;
import storybook.tools.file.XEditorFile;
import storybook.ui.MainFrame;
import storybook.ui.dialog.ExceptionDlg;

/**
 * Scene utilities
 *
 * @author favdb
 */
public class Scenes {

	private static final String TT = "SceneUtil";

	private Scenes() {
		// empty
	}

	/**
	 * create a Scene and initialize the Strand and the Chapter
	 *
	 * @param strand
	 * @param chapter
	 * @return
	 */
	public static Scene create(Strand strand, Chapter chapter) {
		Scene scene = new Scene();
		scene.setStrand(strand);
		scene.setStatus(1);
		scene.setChapter(chapter);
		scene.setSceneno(1);
		scene.setDate(null);
		scene.setTitle(I18N.getMsg("scene") + " 1");
		scene.setSummary("<p></p>");
		scene.setNotes("");
		return scene;
	}

	/**
	 * chage the status of a given scene with the new value
	 *
	 * @param mainFrame
	 * @param scene
	 * @param status
	 */
	public static void changeStatus(MainFrame mainFrame, Scene scene, int status) {
		scene.setStatus(status);
		mainFrame.getBookController().updateEntity(scene);
	}

	/**
	 * get list of Scenes which have a fixed date
	 *
	 * @param mainFrame
	 * @return
	 */
	public static List<Scene> getWithDates(MainFrame mainFrame) {
		//LOG.trace(TT+".getWithDates(mainFrame)");
		if (mainFrame == null) {
			return new ArrayList<>();
		}
		Model model = mainFrame.getBookModel();
		Session session = model.beginTransaction();
		SceneDAO sceneDao = new SceneDAO(session);
		List<Scene> scenes = sceneDao.findAll();
		model.commit();
		if (scenes.isEmpty()) {
			return scenes;
		}
		if (!scenes.get(0).hasScenets()) {
			scenes.get(0).setScenets(new Timestamp(new Date().getTime()));
		}
		int i = 0;
		for (Scene scene : scenes) {
			if (scene.hasRelativescene()) {
				Date date = null;
				for (int j = i; j >= 0; j--) {
					Scene s = scenes.get(j);
					if (s.getId().equals(scene.getRelativesceneid())) {
						date = s.getDate();
						if (date != null) {
							if (scene.getRelativetime().isEmpty()
							   || SbDuration.isZero(scene.getRelativetime())) {
								date = DateUtil.add(date, s.getSbDuration());
							} else {
								date = DateUtil.add(s.getDate(), scene.getRelativetime());
							}
							break;
						}
					}
				}
				scene.setDate(date);
			} else if (!scene.hasScenets() && i > 0) {
				Scene s = scenes.get(i - 1);
				if (s.hasDate()) {
					scene.setDate(DateUtil.add(s.getDate(), s.getSbDuration()));
				}
			}
			i++;
		}
		return scenes;
	}

	/**
	 * find all Scenes
	 *
	 * @param mainFrame
	 * @return
	 */
	public static List<Scene> find(MainFrame mainFrame) {
		List<Scene> scenes = new ArrayList<>();
		if (mainFrame == null) {
			return scenes;
		}
		Model model = mainFrame.getBookModel();
		Session session = model.beginTransaction();
		SceneDAO dao = new SceneDAO(session);
		scenes = dao.findAll();
		model.commit();
		return scenes;
	}

	/**
	 * find Scenes linked to the given Entity
	 *
	 * @param mainFrame
	 * @param entity
	 * @return
	 */
	public static List<Scene> findBy(MainFrame mainFrame, AbstractEntity entity) {
		//LOG.trace(TT + ".findBy(mainFrame, entity=" + LOG.trace(entity) + ")");
		Model model = mainFrame.getBookModel();
		Session session = model.beginTransaction();
		SceneDAO dao = new SceneDAO(session);
		List<Scene> scenes = new ArrayList<>();
		Date date1 = null, date2 = null;
		switch (Book.getTYPE(entity)) {
			case CHAPTER:
				scenes = dao.findByChapter((Chapter) entity);
				break;
			case ITEM:
				scenes = dao.findByItem((Item) entity);
				break;
			case ITEMLINK:
				Itemlink itemlink = (Itemlink) entity;
				if (itemlink.hasStartScene()) {
					date1 = itemlink.getStartScene().getDate();
				}
				if (itemlink.hasEndScene()) {
					date2 = itemlink.getEndScene().getDate();
				}
				if (date1 != null && date2 != null) {
					scenes = dao.findByPeriod(new Period(date1, date2));
				}
				break;
			case LOCATION:
				scenes = dao.findByLocationLink((Location) entity);
				break;
			case PART:
				scenes = dao.findByPart((Part) entity);
				break;
			case PERSON:
				scenes = dao.findByPerson((Person) entity);
				break;
			case PLOT:
				scenes = dao.findByPlot((Plot) entity);
				break;
			case RELATION:
				Relationship relation = (Relationship) entity;
				if (relation.hasStartScene()) {
					scenes.add(relation.getStartScene());
				}
				if (relation.hasEndScene()) {
					scenes.add(relation.getEndScene());
				}
				break;
			case STRAND:
				scenes = dao.findByStrands((Strand) entity);
				scenes.addAll(dao.findByStrand((Strand) entity));
				break;
			case TAG:
				Tag tag = (Tag) entity;
				TaglinkDAO tagDao = new TaglinkDAO(session);
				List<Taglink> taglinks = tagDao.findByTag(tag);
				for (Taglink tl : taglinks) {
					if (tl.hasStartScene() && tl.getStartScene().hasScenets()) {
						date1 = tl.getStartScene().getDate();
					}
					if (tl.hasEndScene() && tl.getEndScene().hasScenets()) {
						date2 = tl.getEndScene().getDate();
					}
					if (date1 != null && date2 != null) {
						List<Scene> s = dao.findByDate(date1, date2);
						scenes.addAll(s);
					}
				}
				break;
			case TAGLINK:
				Taglink taglink = (Taglink) entity;
				if (taglink.hasStartScene() && taglink.hasEndScene()) {
					date1 = taglink.getStartScene().getDate();
					date2 = taglink.getEndScene().getDate();
					if (date1 != null && date2 != null) {
						scenes = dao.findByPeriod(new Period(date1, date2));
					}
				}
				break;
			default:
				break;
		}
		model.commit();
		return scenes;
	}

	/**
	 * find Scenes list beetwen starting Scene and ending Scene
	 *
	 * @param mainFrame
	 * @param start
	 * @param end
	 * @return
	 */
	public static List<Scene> find(MainFrame mainFrame, Scene start, Scene end) {
		List<Scene> ret = new ArrayList<>();
		if (start == null) {
			return (ret);
		}
		List<Scene> scenes = find(mainFrame);
		if (end == null) {
			int n = scenes.indexOf(start);
			if (n > 0) {
				ret.add(scenes.get(n - 1));
			}
			if (n < scenes.size() - 1) {
				ret.add(scenes.get(n + 1));
			}
			return ret;
		}
		return (scenes.subList(scenes.indexOf(start), scenes.indexOf(end)));
	}

	/**
	 * find a Scene with the given ID
	 *
	 * @param mainFrame
	 * @param id
	 * @return
	 */
	public static Scene getById(MainFrame mainFrame, Long id) {
		@SuppressWarnings("unchecked")
		List<Scene> scenes = findEntities(mainFrame, Book.TYPE.SCENE);
		for (Scene s : scenes) {
			if (s.getId().equals(id)) {
				return s;
			}
		}
		return null;
	}

	/**
	 * find all distinct dates of scenes
	 *
	 * @param scenes
	 * @return
	 */
	@SuppressWarnings("unchecked")
	public static List<Date> findDateDistinct(List<Scene> scenes) {
		//LOG.trace(TT + ".findDistinctDates(part " + LOG.trace(part) + ")");
		List<Date> dates = new ArrayList<>();
		if (scenes == null || scenes.isEmpty()) {
			return dates;
		}
		for (Scene scene : scenes) {
			if (scene.hasScenets()) {
				dates.add(scene.getDate());
			}
		}
		dates = ListUtil.removeNullAndDuplicates(dates);
		Collections.sort(dates, new ObjectComparator());
		return dates;
	}

	/**
	 * get the first date of Scenes
	 *
	 * @param mainFrame
	 * @return
	 */
	public static Date findDateFirst(MainFrame mainFrame) {
		Model model = mainFrame.getBookModel();
		Session session = model.beginTransaction();
		SceneDAO dao = new SceneDAO(session);
		Date date = dao.findFirstDate();
		model.commit();
		return date;
	}

	/**
	 * get last date of Scenes
	 *
	 * @param mainFrame
	 * @return
	 */
	public static Date findDateLast(MainFrame mainFrame) {
		Model model = mainFrame.getBookModel();
		Session session = model.beginTransaction();
		SceneDAO dao = new SceneDAO(session);
		Date date = dao.findLastDate();
		model.commit();
		return date;
	}

	/**
	 * renumber all scenes
	 *
	 * @param mainFrame
	 */
	public static void renumber(MainFrame mainFrame) {
		//LOG.trace(TT + ".renumber(mainFrame)");
		if (mainFrame.getPref().sceneIsRenumAuto()) {
			renumberAuto(mainFrame, null);
		} else {
			renumberInc(mainFrame, null, 1, mainFrame.getPref().sceneGetRenumInc());
		}
		List<Chapter> chapters = Chapters.find(mainFrame, null);
		int start = 1;
		for (Chapter chapter : chapters) {
			if (mainFrame.getPref().sceneIsRenumAuto()) {
				renumberAuto(mainFrame, chapter);
			} else {
				start = renumberInc(mainFrame, chapter, start, mainFrame.getPref().sceneGetRenumInc());
				if (mainFrame.getPref().sceneIsRenumByChapter()) {
					start = 1;
				}
			}
		}
		mainFrame.setUpdated();
		mainFrame.cursorSetDefault();
	}

	/**
	 * renumber all scenes of a given chapter
	 *
	 * @param mainFrame
	 * @param chapter
	 */
	public static void renumber(MainFrame mainFrame, Chapter chapter) {
		//LOG.trace(TT + ".renumber(mainFrame, chapter=" + LOG.trace(chapter) + ")");
		try {
			if (mainFrame.getPref().sceneIsRenumAuto()) {
				renumberAuto(mainFrame, chapter);
			} else {
				renumberInc(mainFrame, chapter, 1, mainFrame.getPref().sceneGetRenumInc());
			}
			mainFrame.setUpdated();
		} catch (Exception e) {
			ExceptionDlg.show(TT
			   + ".renumber(mainFrame, chapter=" + LOG.trace(chapter) + ") Exception", e);
		}
	}

	/**
	 * automatic renumber scenes of a chapter
	 *
	 * @param mainFrame
	 * @param chapter
	 */
	public static void renumberAuto(MainFrame mainFrame, Chapter chapter) {
		//LOG.trace(TT + ".renumberAuto(mainFrame, chapter=" + LOG.trace(chapter) + ")");
		Model model = mainFrame.getBookModel();
		Session session = model.beginTransaction();
		SceneDAO dao = new SceneDAO(session);
		List<Scene> scenes = dao.findByChapter(chapter);
		int partNumber = 0;
		int chapterNumber = 0;
		if (chapter != null) {
			partNumber = (chapter.hasPart() ? chapter.getPart().getNumber() * 100000 : 0);
			chapterNumber = chapter.getChapterno() * 1000;
		}
		if (chapterNumber > 99000) {
			chapterNumber = 99000;
		}
		int sceneNumber = 1;
		int newNum;
		int inc = 1;
		for (Scene scene : scenes) {
			newNum = partNumber + chapterNumber + (sceneNumber * 10);
			scene.setSceneno(newNum);
			session.update(scene);
			sceneNumber += inc;
		}
		model.commit();
		model.fireAgainScenes();
		mainFrame.setUpdated();
	}

	/**
	 * incremental renumber scenes of a chapter
	 *
	 * @param mainFrame
	 * @param chapter
	 * @param start: starting number
	 * @param inc: increment
	 * @return last number used
	 */
	public static int renumberInc(MainFrame mainFrame, Chapter chapter, int start, int inc) {
		//LOG.trace(TT + ".renumberInc(mainFrame, chapter=" + LOG.trace(chapter)
		//+ ", start=" + start + ", inc=" + inc + ")");
		Model model = mainFrame.getBookModel();
		Session session = model.beginTransaction();
		SceneDAO dao = new SceneDAO(session);
		List<Scene> scenes = dao.findByChapter(chapter);
		int sceneNumber = start;
		for (Scene scene : scenes) {
			scene.setSceneno(sceneNumber);
			session.update(scene);
			sceneNumber += inc;
		}
		model.commit();
		model.fireAgainScenes();
		mainFrame.setUpdated();
		return sceneNumber;
	}

	/**
	 * renumber scenes by time
	 *
	 * @param mainFrame
	 * @param chapter
	 */
	public static void renumberByTime(MainFrame mainFrame, Chapter chapter) {
		Model model = mainFrame.getBookModel();
		Ctrl ctrl = mainFrame.getBookController();
		Session session = model.beginTransaction();
		ChapterDAO dao = new ChapterDAO(session);
		List<Scene> scenes = dao.findScenesOrderByTimestamp(chapter);
		session.close();
		int counter = 1;
		for (Scene scene : scenes) {
			scene.setSceneno(counter);
			ctrl.updateEntity(scene);
			++counter;
		}
	}

	/**
	 * get the next number for a scene in the given chapter
	 *
	 * @param mainFrame
	 * @param chapter
	 * @return
	 */
	public static int getNextNumber(MainFrame mainFrame, Chapter chapter) {
		//LOG.trace(TT + ".getNextNumber(mainFrame, chapter=" + LOG.trace(chapter) + ")");
		int number = Scenes.findBy(mainFrame, chapter).size();
		if (mainFrame.getPref().sceneIsRenumAuto()) {
			if (chapter != null) {
				@SuppressWarnings("null")
				String snum = String.format("%02d%02d%02d0",
				   (chapter.hasPart() ? chapter.getPart().getNumber() : 0),
				   chapter.getChapterno(),
				   number + 1);
				return Integer.parseInt(snum);
			}
			return (number + 1) * 10;
		} else {
			if (!mainFrame.getPref().sceneIsRenumByChapter()) {
				number = find(mainFrame).size();
			}
			return number + mainFrame.getPref().sceneGetRenumInc();
		}
	}

	public static String scenarioKey(String type, int i) {
		//LOG.trace(TT+".findKey(type=" + type + ", i=" + i + ")");
		String key = "scenario." + type + "." + String.format("%02d", i);
		try {
			String msg = I18N.getMsg(key);
			if (msg.equals("!" + key + "!")) {
				return ("");
			}
			return (msg);
		} catch (Exception ex) {
			return ("");
		}
	}

	/**
	 * launch external editor
	 *
	 * @param mainFrame
	 * @param scene
	 */
	public static void launchExternal(MainFrame mainFrame, Scene scene) {
		String name = XEditorFile.getFilePath(mainFrame, scene);
		if (name == null || name.isEmpty()) {
			name = XEditorFile.getDefaultFilePath(mainFrame, scene.getName());
		}
		if (name.isEmpty()) {
			return;
		}
		File file = new File(name);
		if (!file.exists()) {
			file = XEditorFile.createFile(mainFrame, name);
			if (file == null) {
				return;
			}
			scene.setOdf(name);
		}
		XEditorFile.launchExternal(file.getAbsolutePath());
	}

	/**
	 * get the last used Scene number
	 *
	 * @param mainFrame
	 * @return
	 */
	public static int getLastNumber(MainFrame mainFrame) {
		@SuppressWarnings("unchecked")
		List<Scene> lst = findEntities(mainFrame, Book.TYPE.SCENE);
		int n = 0;
		for (Scene scene : lst) {
			if ((scene.getSceneno() != null) && (scene.getSceneno() > n)) {
				n = scene.getSceneno();
			}
		}
		return n;
	}

}
