/*
 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.db.scene;

import i18n.I18N;
import java.awt.Color;
import java.io.File;
import java.sql.Timestamp;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import javax.swing.Icon;
import org.w3c.dom.Node;
import storybook.App;
import storybook.db.DB.DATA;
import storybook.db.EntityUtil;
import storybook.db.abs.AbstractEntity;
import storybook.db.book.Book;
import storybook.db.chapter.Chapter;
import storybook.db.item.Item;
import storybook.db.location.Location;
import storybook.db.person.Person;
import storybook.db.plot.Plot;
import storybook.db.status.Status;
import storybook.db.strand.Strand;
import storybook.db.tag.Tag;
import storybook.tools.DateUtil;
import storybook.tools.Markdown;
import storybook.tools.SbDuration;
import storybook.tools.TextUtil;
import storybook.tools.html.Html;
import storybook.tools.swing.ColorUtil;
import storybook.tools.xml.XmlKey.XK;
import storybook.tools.xml.XmlUtil;
import static storybook.tools.xml.XmlUtil.*;
import storybook.tools.zip.ZipXml;
import storybook.ui.MainFrame;

/**
 * Scene generated by hbm2java
 *
 * @hibernate.class table="SCENE"
 */
public class Scene extends AbstractEntity {

	private static final String STD_FMT = "%s.%02d";

	private Chapter chapter;
	private Long chapter_id = -1L;
	private Strand strand;
	private Long strand_id = -1L;
	private Integer sceneno = 0;
	private Timestamp scenets = null;
	//TODO calendar
	//private String sbDate;
	private String title = "";
	private String summary = "<p></p>";
	private Integer status = 0;// means DUMMY status
	private Integer intensity = 1;
	private String duration = "";
	private Integer relativeDateDifference = 0;
	private String relativetime = "";
	private Long relativesceneid = -1L;
	private String odf = "";
	private Boolean informative = false;

	private List<Person> persons = new ArrayList<>();
	private List<Long> persons_id = new ArrayList<>();
	private List<Item> items = new ArrayList<>();
	private List<Long> items_id = new ArrayList<>();
	private List<Tag> tags = new ArrayList<>();
	private List<Long> tags_id = new ArrayList<>();
	private List<Location> locations = new ArrayList<>();
	private List<Long> locations_id = new ArrayList<>();
	private List<Plot> plots = new ArrayList<>();
	private List<Long> plots_id = new ArrayList<>();
	private List<Strand> strands = new ArrayList<>();
	private List<Long> strands_id = new ArrayList<>();
	private Person narrator = null;
	private Long narrator_id = -1L;

	//scenario
	public String scenario_pitch = "";
	public Integer scenario_moment = 0,
	   //0=NONE, 1=DAY, 2=AT DAWN, 3=MORNING, 4=MIDDAY,
	   //5=AFTERNOON, 6=SUNSET, 7=NIGHT, 8=EVENING, 9=LATER
	   scenario_loc = 0, //0=NONE,1=INT.,2=EXT.
	   scenario_start = 0, //0=NONE,1=CUT,2=FADE IN
	   scenario_end = 0, //0=NONE,1=CUT,2=FADE OUT,3=DISSOLVE TO
	   scenario_stage = 0 //0=none, else the index number of the stage
	   ;

	@SuppressWarnings("OverridableMethodCallInConstructor")
	public Scene() {
		super(Book.TYPE.SCENE, "111");
		//setDate(new Date());
		setSummary("");
		setNotes("");
		setDescription("");
		setAssistant("");
		scenario_pitch = "";
		scenario_moment = 0;
		scenario_loc = 0;
		scenario_start = 0;
		scenario_end = 0;
		scenario_stage = 0;
	}

	@SuppressWarnings("OverridableMethodCallInConstructor")
	public Scene(MainFrame mainFrame) {
		this();
		if (mainFrame != null) {
			switch (mainFrame.project.book.getSceneDateInit()) {
				case 0:
					setDate(null);
					break;
				case 1:
					setDate(mainFrame.lastDateGet());
					break;
				case 2:
					setDate(new Date());
					break;
			}
		}
	}

	@SuppressWarnings("OverridableMethodCallInConstructor")
	public Scene(Integer number, String title, Chapter chapter, Strand strand) {
		this();
		setName(title);
		setChapter(chapter);
		setStrand(strand);
		setSceneno(number);
		setStatus(1);
	}

	@SuppressWarnings("OverridableMethodCallInConstructor")
	public Scene(Chapter chapter, Strand strand, Integer number, Timestamp date,
	   String title, String summary, Integer status, String duration,
	   Integer relativeDateDifference, Long relativesceneid,
	   String notes, Boolean informative, String odf) {
		this();
		setName(title);
		setChapter(chapter);
		setStrand(strand);
		setSceneno(number);
		setScenets(date);
		setTitle(title);
		setSummary(summary);
		setStatus(status);
		setRelativeDateDifference(relativeDateDifference);
		setRelativesceneid(relativesceneid);
		setDuration(duration);
		setNotes(notes);
		setInformative(informative);
		setOdf(odf);
	}

	public Chapter getChapter() {
		return this.chapter;
	}

	public boolean hasChapter() {
		return chapter != null;
	}

	public void setChapter(Chapter chapter) {
		this.chapter = chapter;
	}

	public void setChapter() {
		this.chapter = null;
	}

	public void setChapterId(Long value) {
		this.chapter_id = value;
	}

	public Long getChapterId() {
		return this.chapter_id;
	}

	public Strand getStrand() {
		return this.strand;
	}

	public void setStrand() {
		this.strand = null;
	}

	public void setStrand(Strand strand) {
		this.strand = strand;
	}

	public void setStrandId(Long value) {
		this.strand_id = value;
	}

	public Long getStrandId() {
		return this.strand_id;
	}

	@SuppressWarnings("null")
	public Integer getSceneno() {
		return (sceneno != null ? sceneno : -1);
	}

	public static Integer getNextNumber(List scenes) {
		int n = 0;
		for (Object c : scenes) {
			if (((Scene) c).sceneno > n) {
				n = ((Scene) c).sceneno;
			}
		}
		return (n + 1);
	}

	public boolean hasSceneno() {
		return sceneno != null;
	}

	public void setSceneno(Integer sceneno) {
		this.sceneno = sceneno;
	}

	public Timestamp getScenets() {
		return this.scenets;
	}

	public boolean hasScenets() {
		return scenets != null;
	}

	public int getDateType() {
		if (hasDate()) {
			return 1;
		}
		if (hasRelativescene()) {
			return 2;
		}
		return 0;
	}

	public Date getDate() {
		if (scenets == null) {
			return null;
		}
		return new Date(scenets.getTime());
	}

	/**
	 * return true if scene has a TS or if has a relative scene
	 *
	 * @return
	 */
	@Override
	public boolean hasDate() {
		return hasScenets() || hasRelativescene();
	}

	public void removeScenets() {
		scenets = null;
	}

	public String getDateOnly() {
		if (!hasScenets()) {
			return "";
		}
		DateFormat formatter = new SimpleDateFormat("yyyy/MM/dd");
		return formatter.format(scenets);
	}

	public String getDateStrShort() {
		if (!hasScenets()) {
			return "";
		}
		DateFormat formatter;
		if (DateUtil.isZeroTimeDate(scenets)) {
			formatter = I18N.getShortDateFormatter();
		} else {
			formatter = I18N.getDateTimeFormatter();
		}
		return formatter.format(scenets);
	}

	public String getDateStrMedium() {
		if (!hasScenets()) {
			return "";
		}
		DateFormat formatter;
		if (DateUtil.isZeroTimeDate(scenets)) {
			formatter = I18N.getMediumDateFormatter();
		} else {
			formatter = I18N.getDateTimeFormatter();
		}
		return formatter.format(scenets);
	}

	public String getDateStrLong() {
		if (!hasScenets()) {
			return "";
		}
		DateFormat formatter;
		if (DateUtil.isZeroTimeDate(scenets)) {
			formatter = I18N.getLongDateFormatter();
		} else {
			formatter = I18N.getDateTimeFormatter();
		}
		return formatter.format(scenets);
	}

	public void setDate(Date date) {
		if (date == null) {
			setScenets(null);
		} else {
			setScenets(new Timestamp(date.getTime()));
		}
	}

	public void setScenets(Timestamp ts) {
		scenets = ts;
	}

	public String getTitle() {
		if (title == null) {
			title = "";
		}
		return title;
	}

	public String getTitle(boolean truncate) {
		if (truncate) {
			return TextUtil.ellipsize(title, 30);
		}
		return title;
	}

	public void setTitle(String title) {
		this.title = title;
		setName(title);
	}

	public String getFullTitle() {
		return getFullTitle(false);
	}

	public String getFullTitle(boolean truncate) {
		return getFullTitle(false, truncate);
	}

	public String getFullTitle(boolean timestamp, boolean truncate) {
		StringBuilder buf = new StringBuilder();
		buf.append(getChapterSceneNo());
		buf.append(getTitle(truncate));
		if (timestamp && hasScenets()) {
			buf.append(" (");
			buf.append(getDateStrMedium());
			buf.append(")");
		}
		return buf.toString();
	}

	public String getTitleText(boolean truncate, int length) {
		if (title == null || title.isEmpty()) {
			return getEllipsizedText(length);
		}
		return getName() + ": " + getEllipsizedText(length);
	}

	public String getChapterSceneNo() {
		return getChapterSceneNo(true);
	}

	public String getChapterSceneNo(boolean appendColon) {
		StringBuilder buf = new StringBuilder();
		if (hasChapter()) {
			buf.append(chapter.getChapterno());
		} else {
			buf.append("x");
		}
		buf.append(".");
		if (hasSceneno()) {
			buf.append(sceneno);
		} else {
			buf.append("x");
		}
		if (appendColon) {
			buf.append(": ");
		}
		return buf.toString();
	}

	public String getChapterSceneToolTip() {
		if (!hasChapter()) {
			return "";
		}
		StringBuilder buf = new StringBuilder("<html>");
		buf.append(I18N.getColonMsg("chapter"));
		buf.append(" ").append(getChapter().toString());
		buf.append("<br>");
		if (getChapter().hasPart()) {
			buf.append(I18N.getColonMsg("part"));
			buf.append(" ").append(getChapter().getPart().toString());
			buf.append("<br>");
		}
		return buf.toString();
	}

	public String getPartChapterSceneNo() {
		String ret;
		if (!hasChapter()) {
			ret = "x.x.";
		} else {
			ret = chapter.getPart().getNumber() + "." + chapter.getChapterno() + ".";
		}
		ret += sceneno;
		return (ret);
	}

	public boolean hasEndnote(String note) {
		return (getSummary().contains(note));
	}

	public String getSummary() {
		if (summary == null) {
			return ("<p></p>");
		}
		// suppression de codes HTML
		String toRemove[] = {"html", "div", "body"};
		String text = this.summary;
		if (text.contains("<head.*</head>")) {
			text = text.replaceAll("<head.*</head>", "");
		}
		for (String str : toRemove) {
			text = text.replaceAll("<" + str + ".*>", "");
			text = text.replaceAll("</" + str + ">", "");
		}
		if (text.isEmpty()) {
			text = "<p></p>";
		}
		return (text);
	}

	public String getSummary(int length) {
		if (length == -1) {
			return summary;
		}
		return TextUtil.ellipsize(Html.htmlToText(summary), length);
	}

	/**
	 * get number of characters in the scene summary or external file
	 *
	 * @return
	 */
	public int getChars() {
		if (getOdf() != null && !getOdf().isEmpty()) {
			File f = new File(getOdf());
			if (f.exists()) {
				int nb;
				if (getOdf().endsWith("odt")) {
					nb = TextUtil.countChars(ZipXml.getDocumentText(f, "content.xml", "text:h, text:p"));
				} else if (getOdf().endsWith("docx")) {
					nb = TextUtil.countChars(ZipXml.getDocumentText(f, "word/document.xml", "w:t"));
				} else {
					nb = TextUtil.countChars(getSummary());
				}
				return nb;
			}
		}
		return TextUtil.countChars(getSummary());
	}

	/**
	 * get number of words in the scene summary or external file
	 *
	 * @return
	 */
	public int getWords() {
		if (getOdf() != null && !getOdf().isEmpty()) {
			File f = new File(getOdf());
			if (f.exists()) {
				int nb;
				if (getOdf().endsWith("odt")) {
					nb = TextUtil.countWords(ZipXml.getDocumentText(f, "content.xml", "text:h, text:p")) - 1;
				} else if (getOdf().endsWith("docx")) {
					nb = TextUtil.countWords(ZipXml.getDocumentText(f, "word/document.xml", "w:t")) - 1;
				} else {
					nb = TextUtil.countWords(getSummary());
				}
				nb = Math.max(0, nb);
				return nb;
			}
		}
		return TextUtil.countWords(getSummary());
	}

	/**
	 * get the standard CC.SS
	 *
	 * @return
	 */
	public String getCCSS() {
		int cn = 0;
		if (hasChapter()) {
			cn = getChapter().getChapterno();
		}
		String str = String.format("%02d.%02d", cn, getSceneno());
		return str;
	}

	/**
	 * get the intensity
	 *
	 * @param scenes
	 * @return
	 */
	public static int getIntensity(List<Scene> scenes) {
		int n = 0;
		for (Scene s : scenes) {
			n += s.getIntensity();
		}
		if (n == 0 || scenes.size() < 1) {
			return 1;
		}
		return Math.max(1, n / scenes.size());
	}

	/**
	 * get the text of the Scene
	 *
	 * @return
	 */
	public String getHtmlSummary() {
		if (getSummary().contains(Html.P_B)) {
			return getSummary();
		} else {
			return Markdown.toHtml(getSummary());
		}
	}

	/**
	 * get the truncated text of the Scene
	 *
	 * @param length
	 * @return
	 */
	public String getEllipsizedText(int length) {
		return TextUtil.ellipsize(getHtmlSummary(), length);
	}

	/**
	 * get the HTML text of the Scene for a multi file
	 *
	 * @param isMulti: for a multi file HTML
	 * @return
	 */
	public String getTextToHtml(boolean isMulti) {
		String txt = getSummary();
		if (isMulti) {
			txt = txt.replace("#endnote", "./endnote.html#endnote");
		}
		return txt;
	}

	/**
	 * set the summary of the Scene
	 *
	 * @param str
	 */
	public void setSummary(String str) {
		if (str == null) {
			this.summary = null;
		} else {
			this.summary = str
			   .replace("<html>", "").replace("</html>", "")
			   .replace("<head>", "").replace("</head>", "")
			   .replace("<body>", "").replace("</body>", "")
			   .replaceAll("<style.*type=\"text/css.*</style>", "");
		}
	}

	/**
	 * get the status
	 *
	 * @return
	 */
	public Integer getStatus() {
		if (status == null) {
			return 0;
		}
		if (status >= Status.STATUS.values().length) {
			status = 0;
		}
		return status;
	}

	/**
	 * get the status as an icon
	 *
	 * @return
	 */
	public Icon getStatusIcon() {
		return Status.getStatusIcon(getStatus());
	}

	/**
	 * set the status
	 *
	 * @param status
	 */
	public void setStatus(Integer status) {
		//App.trace("scene setting status to " + status);
		this.status = status;
	}

	/**
	 * set the Scene state
	 *
	 * @param state
	 */
	public void setScenestate(SceneStatus state) {
		this.status = state.getNumber();
	}

	/**
	 * get the Scene state
	 *
	 * @return
	 */
	public SceneStatus getScenestate() {
		if (status == null) {
			status = 0;
		}
		SceneStatusModel model = new SceneStatusModel();
		return (SceneStatus) model.findByNumber(this.status);
	}

	/**
	 * get the intensity
	 *
	 * @return
	 */
	public Integer getIntensity() {
		if (intensity == null) {
			return 1;
		}
		if (intensity >= 5) {
			intensity = 5;
		}
		return this.intensity;
	}

	/**
	 * get the intensity as a Color
	 *
	 * @return
	 */
	public Color getIntensityColor() {
		return IntensityPanel.getColorValue(intensity);
	}

	/**
	 * set the intensity
	 *
	 * @param intensity
	 */
	public void setIntensity(Integer intensity) {
		this.intensity = intensity;
	}

	/**
	 * set the duration
	 *
	 * @param duration
	 */
	public void setDuration(String duration) {
		this.duration = duration;
	}

	/**
	 * get the duration
	 *
	 * @return
	 */
	public String getDuration() {
		return (duration == null ? "" : duration);
	}

	/**
	 * check if a duration exists
	 *
	 * @return
	 */
	public boolean hasDuration() {
		return (duration != null && !duration.isEmpty());

	}

	/**
	 * get the duration as a SbDuration
	 *
	 * @return
	 */
	public SbDuration getSbDuration() {
		if (hasDuration()) {
			return new SbDuration(getDuration());
		} else {
			return SbDuration.getFromWords(summary);
		}
	}

	/**
	 * get the duration as a text String
	 *
	 * @return
	 */
	public String getDurationToText() {
		if (!getDuration().isEmpty()) {
			SbDuration du = new SbDuration(duration);
			return du.toText();
		}
		int words = getWords();
		if (words == 0) {
			return "";
		}
		SbDuration d = SbDuration.getFromWords(summary);
		return "(" + d.toText() + ")";
	}

	/**
	 * get the duration as a compacted text String
	 *
	 * @return
	 */
	public String getTextDuration() {
		if ((duration == null || duration.isEmpty()) && !summary.isEmpty()) {
			return "[" + SbDuration.computeFromWords(summary) + "]";
		}
		return getDuration();
	}

	/**
	 * get the relative difference date
	 *
	 * @return
	 */
	public Integer getRelativeDateDifference() {
		return this.relativeDateDifference;
	}

	/**
	 * set the relative difference date
	 *
	 * @param relativeDateDifference
	 */
	public void setRelativeDateDifference(Integer relativeDateDifference) {
		this.relativeDateDifference = relativeDateDifference;
	}

	/**
	 * get the relative time
	 *
	 * @return
	 */
	public String getRelativetime() {
		return (this.relativetime == null ? "" : this.relativetime);
	}

	/**
	 * set the relative time
	 *
	 * @param rel
	 */
	public void setRelativetime(String rel) {
		this.relativetime = rel;
	}

	public String getRelativetimeToText() {
		if (getRelativetime().isEmpty() || SbDuration.isZero(duration)) {
			return "<" + SbDuration.getFromWords(getSummary()).toText() + ">";
		} else {
			SbDuration dur = new SbDuration(getRelativetime());
			return dur.toText();
		}
	}

	/**
	 * get the relative date
	 *
	 * @param relativeScene
	 * @return
	 */
	public Date getRelativedate(Scene relativeScene) {
		if (relativeScene == null) {
			return null;
		}
		Date date = relativeScene.getDate();
		if ((date == null) || (this.relativetime == null || this.relativetime.isEmpty())) {
			return null;
		}
		return DateUtil.add(date, relativetime);
	}

	/**
	 * get the relative SceneId
	 *
	 * @return
	 */
	public Long getRelativesceneid() {
		if (this.relativesceneid == null) {
			return -1L;
		}
		return this.relativesceneid;
	}

	/**
	 * set the relative SceneId
	 *
	 * @param relativeSceneId
	 */
	public void setRelativesceneid(Long relativeSceneId) {
		this.relativesceneid = relativeSceneId;
	}

	/**
	 * set the relative SceneId
	 *
	 * @param relativeScene
	 */
	public void setRelativesceneid(Scene relativeScene) {
		if (relativeScene == null) {
			return;
		}
		setRelativesceneid(relativeScene.id);
	}

	/**
	 * check if a relative Scene exists
	 *
	 * @return
	 */
	public boolean hasRelativescene() {
		return this.relativesceneid != -1L;
	}

	/**
	 * remove the relative SceneId
	 */
	public void removeRelativescene() {
		relativesceneid = null;
		relativetime = "";
		relativeDateDifference = null;
	}

	/**
	 * get the list of Person
	 *
	 * @return
	 */
	public List<Person> getPersons() {
		if (persons == null) {
			return (new ArrayList<>());
		}
		return persons;
	}

	/**
	 * get the list of Person as a String
	 *
	 * @return
	 */
	@SuppressWarnings("unchecked")
	public String getPersonList() {
		return (EntityUtil.getNames(persons));
	}

	/**
	 * set the list of Persons
	 *
	 * @param persons
	 */
	public void setPersons(List<Person> persons) {
		this.persons = persons;
	}

	public void setPersonsId(List<Long> values) {
		this.persons_id.addAll(values);
	}

	public List<Long> getPersonsId() {
		return persons_id;
	}

	/**
	 * get the list of Items
	 *
	 * @return
	 */
	public List<Item> getItems() {
		if (items == null) {
			return (new ArrayList<>());
		}
		return items;
	}

	/**
	 * set the list of Items
	 *
	 * @param items
	 */
	public void setItems(List<Item> items) {
		this.items = items;
	}

	public void setItemsId(List<Long> values) {
		this.items_id.addAll(values);
	}

	public List<Long> getItemsId() {
		return this.items_id;
	}

	/**
	 * get the list of Locations
	 *
	 * @return
	 */
	public List<Location> getLocations() {
		if (locations == null) {
			return (new ArrayList<>());
		}
		return locations;
	}

	/**
	 * get the list of Locations as a String
	 *
	 * @return
	 */
	@SuppressWarnings("unchecked")
	public String getLocationList() {
		return (EntityUtil.getNames(locations));
	}

	/**
	 * set the list of Locations
	 *
	 * @param locations
	 */
	public void setLocations(List<Location> locations) {
		this.locations = locations;
	}

	public void setLocationsId(List<Long> values) {
		this.locations_id.addAll(values);
	}

	public List<Long> getLocationsId() {
		return this.locations_id;
	}

	public List<Strand> getStrands() {
		if (strands == null) {
			return (new ArrayList<>());
		}
		return strands;
	}

	@SuppressWarnings("unchecked")
	public String getStrandsList() {
		return (EntityUtil.getNames(strands));
	}

	public void setStrands(List<Strand> strands) {
		this.strands = strands;
	}

	public void setStrandsId(List<Long> values) {
		this.strands_id.addAll(values);
	}

	public List<Long> getStrandsId() {
		return this.strands_id;
	}

	/**
	 * get the list of Items
	 *
	 * @return
	 */
	public List<Tag> getTags() {
		if (tags == null) {
			return (new ArrayList<>());
		}
		return tags;
	}

	/**
	 * set the list of Items
	 *
	 * @param tags
	 */
	public void setTags(List<Tag> tags) {
		this.tags = tags;
	}

	public void setTagsId(List<Long> values) {
		this.tags_id.addAll(values);
	}

	public List<Long> getTagsId() {
		return this.tags_id;
	}

	public Boolean getInformative() {
		if (informative == null) {
			return false;
		}
		return informative;
	}

	public void setInformative(Boolean informative) {
		this.informative = informative;
	}

	public Person getNarrator() {
		return narrator;
	}

	public void setNarrator(Person narrator) {
		this.narrator = narrator;
	}

	public void setNarratorId(Long value) {
		this.narrator_id = value;
	}

	public Long getNarratorId() {
		return this.narrator_id;
	}

	public void setScenario_stage(Integer stage) {
		this.scenario_stage = stage;
	}

	public Integer getScenario_stage() {
		if (scenario_stage == null) {
			scenario_stage = 0;
		}
		return (this.scenario_stage);
	}

	public void setScenario_pitch(String pitch) {
		this.scenario_pitch = pitch;
	}

	public String getScenario_pitch() {
		return (scenario_pitch == null ? "" : scenario_pitch);
	}

	public void setScenario_moment(Integer moment) {
		this.scenario_moment = moment;
	}

	public Integer getScenario_moment() {
		if (scenario_moment == null) {
			scenario_moment = 0;
		}
		return (scenario_moment);
	}

	public String getScenario_momentTxt() {
		if (scenario_moment == null) {
			return "";
		}
		return Scenes.scenarioKey("moment", getScenario_moment());
	}

	public void setScenario_loc(Integer loc) {
		this.scenario_loc = loc;
	}

	public Integer getScenario_loc() {
		if (scenario_loc == null) {
			scenario_loc = 0;
		}
		return (scenario_loc);
	}

	public String getScenario_locTxt() {
		if (scenario_loc == null) {
			return "0";
		}
		return (scenario_loc == 1 ? I18N.getMsg("scenario.loc.int") : I18N.getMsg("scenario.loc.ext"));
	}

	public void setScenario_start(Integer start) {
		this.scenario_start = start;
	}

	public Integer getScenario_start() {
		if (scenario_start == null) {
			scenario_start = 0;
		}
		return (scenario_start);
	}

	public String getScenario_startTxt() {
		if (scenario_start == null) {
			return "";
		}
		return Scenes.scenarioKey("in", getScenario_start());
	}

	public void setScenario_end(Integer end) {
		this.scenario_end = end;
	}

	public Integer getScenario_end() {
		if (scenario_end == null) {
			scenario_end = 0;
		}
		return (scenario_end);
	}

	public String getScenario_endTxt() {
		if (scenario_start == null) {
			return "";
		}
		return Scenes.scenarioKey("out", getScenario_start());
	}

	@Override
	public String toString() {
		if (isTransient()) {
			return "";
		}
		return (getFullTitle(false, true) + (this.hasNotes() ? "*" : ""));
	}

	@Override
	public String toCsv(String quoteStart, String quoteEnd, String separator) {
		StringBuilder b = new StringBuilder();
		b.append(quoteStart).append(getClean(this)).append(quoteEnd).append(separator);
		b.append(quoteStart).append(getClean(getName())).append(quoteEnd).append(separator);
		b.append(quoteStart).append(getClean(getChapter())).append(quoteEnd).append(separator);
		b.append(quoteStart).append(getClean(getStrand())).append(quoteEnd).append(separator);
		b.append(quoteStart).append(getClean(getSceneno())).append(quoteEnd).append(separator);
		b.append(quoteStart).append(getClean(getScenets())).append(quoteEnd).append(separator);
		b.append(quoteStart).append(getClean(getStatus())).append(quoteEnd).append(separator);
		b.append(quoteStart).append(getClean(getInformative())).append(quoteEnd).append(separator);
		b.append(quoteStart).append(getClean(getDuration())).append(quoteEnd).append(separator);
		b.append(quoteStart).append(getClean(getIntensity())).append(quoteEnd).append(separator);
		b.append(quoteStart).append(getClean(getRelativetime())).append(quoteEnd).append(separator);
		b.append(quoteStart).append(getClean(getRelativesceneid())).append(quoteEnd).append(separator);
		b.append(quoteStart).append(getNarrator()).append(quoteEnd).append(separator);
		b.append(quoteStart).append(getClean(getOdf())).append(quoteEnd).append(separator);
		b.append(quoteStart);
		for (Person p : getPersons()) {
			b.append(p.getId().toString()).append("/");
		}
		b.append(quoteEnd).append(separator);
		b.append(quoteStart);
		for (Item p : getItems()) {
			b.append(p.getId().toString()).append("/");
		}
		b.append(quoteEnd).append(separator);
		b.append(quoteStart);
		for (Location p : getLocations()) {
			b.append(p.getId().toString()).append("/");
		}
		b.append(quoteEnd).append(separator);
		b.append(quoteStart);
		for (Strand p : getStrands()) {
			b.append(p.getId().toString()).append("/");
		}
		b.append(quoteEnd).append(separator);
		b.append(quoteStart);
		for (Plot p : getPlots()) {
			b.append(p.getId().toString()).append("/");
		}
		b.append(quoteEnd).append(separator);
		b.append(quoteStart).append(getClean(getSummary())).append(quoteEnd).append(separator);
		b.append(quoteStart).append(getClean(getDescription())).append(quoteStart).append("\n");
		b.append(quoteStart).append(getClean(getNotes())).append(quoteStart).append("\n");
		b.append(quoteStart).append(getClean(getScenario_pitch())).append(quoteStart).append("\n");
		b.append(quoteStart).append(getClean(getScenario_loc())).append(quoteStart).append("\n");
		b.append(quoteStart).append(getClean(getScenario_moment())).append(quoteStart).append("\n");
		b.append(quoteStart).append(getClean(getScenario_start())).append(quoteStart).append("\n");
		b.append(quoteStart).append(getClean(getScenario_end())).append(quoteStart).append("\n");
		return (b.toString());
	}

	@Override
	public String toHtml() {
		return (toCsv("<td>", "</td>", "\n"));
	}

	@Override
	public String toText() {
		return (toCsv("", "", "\t"));
	}

	@Override
	public String toDetail(Integer detailed) {
		//LOG.trace(TT+".toDetail(detailed=" + (detailed ? "true" : "false") + ")");
		StringBuilder b = new StringBuilder();
		String str;
		b.append(super.toDetailHeader(detailed));
		b.append(getInfo(2, DATA.NUMBER, getSceneno()));
		if (getStatus() > 0 || detailed > 0) {
			b.append(getInfo(detailed, DATA.STATUS, Status.getHtmlValue(getStatus())));
		}
		if (getIntensity() > 1 || detailed > 0) {
			IntensityPanel vi = new IntensityPanel(getIntensity());
			b.append(getInfo(detailed, DATA.INTENSITY, vi.getHtmlValue()));
		}
		b.append(getInfo(2, DATA.CHAPTER, getChapter()));
		if (getStrand() != null) {
			b.append(getInfo(detailed, DATA.STRAND, getClean(getStrand()) + " " + getStrand().getHTMLColor()));
		} else {
			b.append(getInfo(detailed, DATA.STRAND, I18N.getMsg("empty")));
		}
		b.append(getInfo(detailed, DATA.INFORMATIVE, getInformative()));
		b.append(getInfo(detailed, DATA.DATE, getClean(getScenets())));
		str = (getDuration().isEmpty() ? "" : getDurationToText());
		if (!str.isEmpty() || detailed > 1) {
			b.append(getInfo(detailed, DATA.DURATION, str));
		}
		if (getNarrator() != null || detailed > 1) {
			b.append(getInfo(detailed, DATA.SCENE_NARRATOR, getNarrator()));
		}
		if (App.getAssistant().isExists("scene")
		   && (!getScenariostage().isEmpty() || detailed > 1)) {
			b.append(getInfo(detailed, DATA.SCENARIO_STAGE, getScenariostage()));
		}
		//scenario
		if (!getScenario_pitch().isEmpty()
		   || getScenario_loc() != 0
		   || getScenario_moment() != 0
		   || getScenario_start() != 0
		   || getScenario_end() != 0) {
			StringBuilder bx = new StringBuilder();
			bx.append(Html.TABLE_B).append(Html.TR_B);
			bx.append(getInfo(detailed, DATA.SCENARIO_PITCH, getScenario_pitch()));
			bx.append(getInfo(detailed, DATA.SCENARIO_LOC, getScenario_locTxt()));
			bx.append(getInfo(detailed, DATA.SCENARIO_MOMENT, getScenario_momentTxt()));
			bx.append(getInfo(detailed, DATA.SCENARIO_START, getScenario_startTxt()));
			bx.append(getInfo(detailed, DATA.SCENARIO_END, getScenario_endTxt()));
			bx.append(Html.TR_E).append(Html.TABLE_E);
			b.append(getInfo(detailed, "scenario", bx.toString()));
		}
		if (!getPersons().isEmpty() || detailed > 1) {
			b.append(getInfo(detailed, DATA.PERSONS, getPersonList()));
		}
		if (!getItems().isEmpty() || detailed > 1) {
			b.append(getInfo(detailed, DATA.ITEMS, getItemList()));
		}
		if (!getLocations().isEmpty() || detailed > 1) {
			b.append(getInfo(detailed, DATA.LOCATIONS, getLocationList()));
		}
		if (!getPlots().isEmpty() || detailed > 1) {
			b.append(getInfo(detailed, DATA.PLOTS, getPlotsList()));
		}
		if (!getStrands().isEmpty() || detailed > 1) {
			b.append(getInfo(detailed, DATA.STRANDS, getStrandsList()));
		}
		if (getRelativesceneid() > -1L) {
			SbDuration dx = new SbDuration(getRelativetime());
			str = getClean(getRelativesceneid()) + " ";
			if (!dx.toText().isEmpty()) {
				str = str + Html.intoB(I18N.getMsg("scene.relativetime")) + " "
				   + dx.toText(I18N.getMsg("duration.initiales"));
			}
		}
		b.append(getInfo(detailed, "scene.relative", str));
		//xeditor
		b.append(getInfo(detailed, "file", getOdf()));
		if (getSummary().contains(Html.P_E)) {
			b.append(getInfo(detailed, DATA.SCENE_SUMMARY, getSummary()));
		} else {
			b.append(getInfo(detailed, DATA.SCENE_SUMMARY, Markdown.toHtml(getSummary())));
		}
		b.append(super.toDetailFooter(detailed));
		return (b.toString());
	}

	@Override
	public String toXml() {
		StringBuilder b = new StringBuilder();
		b.append("<scene ");
		b.append(xmlCommon());
		b.append(XmlUtil.setAttribute(8, XK.NUMBER, getSceneno()));
		b.append(XmlUtil.setAttribute(0, XK.STATUS, getStatus()));
		b.append(XmlUtil.setAttribute(0, XK.INTENSITY, getIntensity()));
		b.append(XmlUtil.setAttribute(8, XK.DATE, getScenets()));
		b.append(XmlUtil.setAttribute(0, XK.DURATION, getDuration()));
		if (getInformative()) {
			b.append(XmlUtil.setAttribute(0, XK.INFORMATIVE, getInformative()));
		}
		b.append(XmlUtil.setAttribute(0, XK.CHAPTER, getChapter()));
		b.append(XmlUtil.setAttribute(0, XK.STRAND, getStrand()));
		b.append(XmlUtil.setAttribute(0, XK.NARRATOR, getNarrator()));
		String x = getScenario_stage().toString() + getScenario_pitch() + getScenario_loc().toString()
		   + getScenario_moment() + getScenario_end().toString();
		x = x.replace("0", "");
		if (!x.isEmpty()) {
			b.append(XmlUtil.setAttribute(8, XK.STAGE, getScenario_stage()));
			b.append(XmlUtil.setAttribute(0, XK.PITCH, getScenario_pitch()));
			b.append(XmlUtil.setAttribute(0, XK.LOC, getScenario_loc()));
			b.append(XmlUtil.setAttribute(0, XK.MOMENT, getScenario_moment()));
			b.append(XmlUtil.setAttribute(0, XK.START, getScenario_start()));
			b.append(XmlUtil.setAttribute(0, XK.END, getScenario_end()));
		}
		b.append(XmlUtil.setAttribute(8, XK.PERSONS, EntityUtil.getIds(persons)));
		b.append(XmlUtil.setAttribute(8, XK.ITEMS, EntityUtil.getIds(items)));
		b.append(XmlUtil.setAttribute(8, XK.LOCATIONS, EntityUtil.getIds(locations)));
		b.append(XmlUtil.setAttribute(8, XK.STRANDS, EntityUtil.getIds(strands)));
		b.append(XmlUtil.setAttribute(8, XK.PLOTS, EntityUtil.getIds(plots)));
		if (relativesceneid != -1L) {
			b.append(XmlUtil.setAttribute(8, XK.RELATIVESCENE, getRelativesceneid()));
			b.append(XmlUtil.setAttribute(0, XK.RELATIVETIME, getRelativetime()));
		}
		b.append(XmlUtil.setAttribute(8, XK.FILE, getOdf()));
		b.append(">\n");
		b.append(toXmlMeta(2, XK.TEXT, this.getSummary(), true));
		b.append(toXmlEnd());
		return (b.toString());
	}

	public String toStoryboard() {
		StringBuilder b = new StringBuilder();
		b.append("<shot \n");
		b.append(XmlUtil.setAttribute(0, XK.NAME, String.format("%03d/001", this.getSceneno())));
		b.append(XmlUtil.setAttribute(0, XK.NUMBER, this.getSceneno().toString()));
		b.append(XmlUtil.setAttribute(0, XK.DURATION, this.getDuration()));
		b.append(XmlUtil.setAttribute(0, XK.PITCH, getClean(getScenario_pitch())));
		b.append(XmlUtil.setAttribute(0, XK.LOC, getClean(getScenario_loc())));
		b.append(XmlUtil.setAttribute(0, XK.MOMENT, getClean(getScenario_moment())));
		b.append(XmlUtil.setAttribute(0, XK.START, getClean(getScenario_start())));
		b.append(XmlUtil.setAttribute(0, XK.END, getClean(getScenario_end())));
		b.append(">\n");
		b.append(toXmlEnd());
		return (b.toString());
	}

	public static Scene fromXml(Node node) {
		//LOG.trace("Scene.fromXml(node=" + Xml.nodeToString(node) + ")");
		Scene p = new Scene();
		fromXmlBeg(node, p);
		p.setSceneno(XmlUtil.getInteger(node, XK.NUMBER));
		String ts = XmlUtil.getString(node, XK.DATE);
		if (!ts.isEmpty()) {
			p.setScenets(Timestamp.valueOf(ts));
		}
		p.setChapterId(getLong(node, XK.CHAPTER));
		p.setStrandId(getLong(node, XK.STRAND));
		p.setStatus(getInteger(node, XK.STATUS));
		p.setIntensity(getInteger(node, XK.INTENSITY));
		p.setDuration(getString(node, XK.DURATION));
		p.setRelativesceneid(getLong(node, XK.RELATIVESCENE));
		p.setRelativetime(getString(node, XK.RELATIVETIME));
		p.setScenario_pitch(getString(node, XK.PITCH));
		p.setScenario_loc(getInteger(node, XK.LOC));
		p.setScenario_moment(getInteger(node, XK.MOMENT));
		p.setScenario_start(getInteger(node, XK.START));
		p.setScenario_end(getInteger(node, XK.END));
		p.setOdf(getString(node, XK.FILE));
		p.setItemsId(getLongList(node, XK.ITEMS));
		p.setLocationsId(getLongList(node, XK.LOCATIONS));
		p.setPersonsId(getLongList(node, XK.PERSONS));
		p.setPlotsId(getLongList(node, XK.PLOTS));
		p.setStrandsId(getLongList(node, XK.STRANDS));
		p.setTagsId(getLongList(node, XK.TAGS));
		p.setSummary(getText(node, XK.TEXT));
		fromXmlEnd(node, p);
		return p;
	}

	@Override
	@SuppressWarnings("EqualsWhichDoesntCheckParameterClass")
	public boolean equals(Object obj) {
		if (!(obj instanceof Scene)) {
			return false;
		}
		Scene test = (Scene) obj;

		if (id != null ? !id.equals(test.id) : test.id != null) {
			return false;
		}
		boolean ret = equalsStringNullValue(getName(), test.getName());
		ret = ret && equalsIntegerNullValue(sceneno, test.getSceneno());
		ret = ret && equalsIntegerNullValue(status, test.getStatus());
		ret = ret && equalsIntegerNullValue(intensity, test.getIntensity());
		ret = ret && equalsStringNullValue(duration, test.getDuration());
		ret = ret && equalsObjectNullValue(chapter, test.getChapter());
		ret = ret && equalsObjectNullValue(strand, test.getStrand());
		if (scenets != null) {
			ret = ret && equalsTimestampNullValue(scenets, test.getScenets());
		} else if (relativesceneid != null) {
			ret = ret && equalsLongNullValue(relativesceneid, test.getRelativesceneid());
			//ret = ret && equalsIntegerNullValue(relativeDateDifference, test.getRelativeDateDifference());
			ret = ret && equalsStringNullValue(relativetime, test.getRelativetime());
		}
		ret = ret && equalsObjectNullValue(narrator, test.getNarrator());
		ret = ret && equalsStringNullValue(summary, test.getSummary());
		ret = ret && equalsBooleanNullValue(informative, test.getInformative());
		ret = ret && equalsStringNullValue(scenario_pitch, test.getScenario_pitch());
		ret = ret && equalsObjectNullValue(scenario_loc, test.getScenario_loc());
		ret = ret && equalsObjectNullValue(scenario_moment, test.getScenario_moment());
		ret = ret && equalsObjectNullValue(scenario_start, test.getScenario_start());
		ret = ret && equalsObjectNullValue(scenario_end, test.getScenario_end());
		ret = ret && equalsListNullValue(persons, test.getPersons());
		ret = ret && equalsListNullValue(locations, test.getLocations());
		ret = ret && equalsListNullValue(strands, test.getStrands());
		ret = ret && equalsListNullValue(plots, test.getPlots());
		ret = ret && equalsStringNullValue(getDescription(), test.getDescription());
		ret = ret && equalsStringNullValue(getNotes(), test.getNotes());
		return ret;
	}

	@Override
	public int hashCode() {
		return hashPlus(super.hashCode(),
		   sceneno,
		   status,
		   intensity,
		   chapter,
		   strand,
		   strands,
		   informative,
		   narrator,
		   persons,
		   items,
		   locations,
		   plots,
		   scenets,
		   relativesceneid,
		   relativetime,
		   scenario_start,
		   scenario_end,
		   scenario_loc,
		   scenario_moment,
		   scenario_stage,
		   scenario_pitch,
		   summary,
		   odf);
	}

	@Override
	public int compareTo(AbstractEntity en) {
		return getCCSS().compareTo(((Scene) en).getCCSS());
	}

	public String getOdf() {
		return (odf == null ? "" : odf);
	}

	public void setOdf(String nf) {
		odf = nf;
	}

	public List<Plot> getPlots() {
		return (plots);
	}

	@SuppressWarnings("unchecked")
	public String getPlotsList() {
		return (EntityUtil.getNames(plots));
	}

	public void setPlots(List<Plot> plots) {
		this.plots = plots;
	}

	public void setPlotsId(List<Long> values) {
		this.plots_id.addAll(values);
	}

	public List<Long> getPlotsId() {
		return this.plots_id;
	}

	@SuppressWarnings("unchecked")
	public String getItemList() {
		return (EntityUtil.getNames(items));
	}

	public static Scene find(List<Scene> list, String str) {
		for (Scene elem : list) {
			if (elem.getName().equals(str)) {
				return (elem);
			}
		}
		return (null);
	}

	public static Scene find(List<Scene> list, Long id) {
		for (Scene elem : list) {
			if (elem.id.equals(id)) {
				return (elem);
			}
		}
		return (null);
	}

	public static Scene findNumber(List scenes, Integer n) {
		for (Object s : scenes) {
			if (((Scene) s).getSceneno().equals(n)) {
				return ((Scene) s);
			}
		}
		return (null);
	}

	public String getScenarioHeader(boolean isScenario) {
		if (!isScenario) {
			return "";
		}
		StringBuilder b = new StringBuilder();
		b.append("<p>");
		if (scenario_loc > 0) {
			b.append(" ");
			b.append(I18N.getMsg(String.format(STD_FMT, "scenario.loc", scenario_loc)));
		} else {
			b.append(" ");
			b.append(I18N.getMsg(String.format(STD_FMT, "scenario.loc", 2)));
		}
		if (scenario_moment > 0) {
			b.append(" ");
			b.append(I18N.getMsg(String.format(STD_FMT, "scenario.moment", scenario_moment)));
		}
		if (locations != null && !locations.isEmpty()) {
			b.append(" - ");
			b.append(locations.get(0).getName().toUpperCase());
		}
		if (scenario_start > 0) {
			b.append(" - ");
			b.append(I18N.getMsg(String.format(STD_FMT, "scenario.in", scenario_start)));
		}
		b.append("</p>");
		return (b.toString());
	}

	public String getScenarioFooter() {
		StringBuilder b = new StringBuilder();
		b.append("<p style=\"text-align:right;\">");
		if (scenario_end > 0) {
			b.append(I18N.getMsg(String.format(STD_FMT, "scenario.out", scenario_end)));
		}
		b.append("</p>");
		return (b.toString());
	}

	public String getScenariopitch() {
		return getScenario_pitch();
	}

	public String getScenariostage() {
		return ScenarioCodes.getStage(getScenario_stage());
	}

	public String getScenarioloc() {
		return ScenarioCodes.getLoc(getScenario_loc());
	}

	public String getScenariomoment() {
		return ScenarioCodes.getMom(getScenario_moment());
	}

	public String getScenariostart() {
		return ScenarioCodes.findScenarioKey("scenario.in", getScenario_start());
	}

	public String getScenarioend() {
		return ScenarioCodes.findScenarioKey("scenario.out", getScenario_end());
	}

	public static List<String> getDefColumns() {
		List<String> list = AbstractEntity.getDefColumns(Book.TYPE.SCENE);
		list.add("title, 2048");
		list.add("summary, 32768");
		list.add("scenario_pitch, 1024");
		list.add("odf, 256");
		return (list);
	}

	public static List<String> getTable() {
		List<String> ls = new ArrayList<>();
		String tableName = "scene";
		AbstractEntity.getTable(tableName, ls);
		ls.add(tableName + ",chapter_id,Integer,0");
		ls.add(tableName + ",strand_id,Integer,0");
		ls.add(tableName + ",sceneno,Integer,0");
		ls.add(tableName + ",scene_ts,Time,0");
		ls.add(tableName + ",title,String,2048");
		ls.add(tableName + ",summary,String,32768");
		ls.add(tableName + ",status,Integer,0");
		ls.add(tableName + ",intensity,Integer,0");
		ls.add(tableName + ",duration,String,32");
		ls.add(tableName + ",relative_scene_id,Integer,0");
		ls.add(tableName + ",relative_date_timee,String,255");
		//ls.add(tableName + ",relative_date_difference,Integer,0");
		ls.add(tableName + ",informative,Boolean,0");
		ls.add(tableName + ",scenario_pitch,String,1024");
		ls.add(tableName + ",scenario_stage,Integer,0");
		ls.add(tableName + ",scenario_start,Integer,0");
		ls.add(tableName + ",scenario_end,Integer,0");
		ls.add(tableName + ",scenario_moment,Integer,0");
		ls.add(tableName + ",scenario_loc,Integer,0");
		ls.add(tableName + ",persons,Table.Person,0");
		ls.add(tableName + ",locations,Table.Location,0");
		ls.add(tableName + ",items,Table.Item,0");
		ls.add(tableName + ",strands,Table.Strand,0");
		ls.add(tableName + ",plots,Table.Plot,0");
		ls.add(tableName + ",odf,String,256");
		ls.add(tableName + ",narrator_id,Integer,0");
		return (ls);
	}

	/**
	 * get strands list in HTML beautify
	 *
	 * @return
	 */
	public String getStrandsHtml() {
		StringBuilder b = new StringBuilder(Html.HTML_B);
		b.append(Html.BODY_B);
		b.append("<table border=\"0\" cellpadding=\"0\" cellspacing=\"1\">");
		for (Strand s : strands) {
			b.append(Html.TR_B);
			b.append(Html.intoTD("█", "color=\"" + ColorUtil.getHTML(s.getJColor()) + "\""));
			b.append(Html.intoTD("&nbsp;"));
			b.append(Html.intoTD(s.getName()));
			b.append(Html.TR_E);
		}
		b.append(Html.TABLE_E);
		b.append(Html.BODY_E);
		b.append(Html.HTML_E);
		return b.toString();
	}

	/**
	 * get the identification like PP.CC.SS where:
	 * <ul>
	 * <li>PP is the Part ID (may be 00 if there is no Part)</li>
	 * <li>CC is the Chapter ID (may be 00 if there is no Chapter)</li>
	 * <li>SS is the Scene ID</li>
	 * </ul>
	 *
	 * @return
	 */
	public String getIdent() {
		return String.format("S%03d", getId());
	}

	/**
	 * get a HTML file name for the scene
	 *
	 * @param scene
	 * @param bookTitle: empty seems it is a unique HTMLfile
	 * @return
	 */
	public static String getFilename(Scene scene, String bookTitle) {
		if (bookTitle.isEmpty()) {
			return "";
		}
		return String.format("%s-%s.html", bookTitle, scene.getIdent());
	}

	@Override
	public void changeHtmlLinks(String path) {
		super.changeHtmlLinks(path);
		setSummary(Html.changeLinks(path, getSummary()));
	}

}
