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

import api.shef.ShefEditor;
import i18n.I18N;
import java.awt.event.ActionListener;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import javax.swing.JButton;
import org.hibernate.Session;
import org.w3c.dom.Node;
import resources.icons.ICONS;
import resources.icons.IconUtil;
import storybook.App;
import storybook.ctrl.Ctrl;
import storybook.model.DB.DATA;
import storybook.model.EntityUtil;
import storybook.model.Model;
import storybook.model.book.Book;
import storybook.model.hbn.dao.EndnoteDAO;
import storybook.shortcut.Shortcuts;
import storybook.tools.html.Html;
import storybook.tools.xml.XmlKey.XK;
import storybook.tools.xml.XmlUtil;
import storybook.ui.MainFrame;

public class Endnote extends AbstractEntity {

	public static final String TT = "Endnote";

	public enum TYPE {
		ENDNOTE, COMMENT;

		@Override
		public String toString() {
			return name().toLowerCase();
		}
	}

	private Integer number = -1;
	private Scene scene = null;
	private String sort = "";
	private Integer type = TYPE.ENDNOTE.ordinal();

	public Endnote() {
		super(Book.TYPE.ENDNOTE, "010");
	}

	public Endnote(ResultSet rs) {
		super(Book.TYPE.ENDNOTE, "010", rs);
		try {
			type = rs.getInt("type");
			number = rs.getInt("number");
			scene = rs.getObject("scene", Scene.class);
		} catch (SQLException ex) {
			//empty
		}
	}

	public Endnote(int type, Scene scene, Integer number) {
		super(Book.TYPE.ENDNOTE, "010");
		this.type = type;
		this.scene = scene;
		this.number = number;
	}

	public Endnote(int type, Scene scene, Integer number, String notes) {
		this(type, scene, number);
		setNotes(notes);
	}

	public Endnote(TYPE type, Scene scene, Integer number, String notes) {
		this(type.ordinal(), scene, number, notes);
	}

	@SuppressWarnings("OverridableMethodCallInConstructor")
	public Endnote(int type, Scene scene, Integer number, String notes, int index) {
		this(type, scene, number, notes);
		this.type = type;
		setSort(index);
	}

	public Integer getType() {
		return getMinMax(type, 0, 1);
	}

	public String getTypeLib() {
		return I18N.getMsg("endnote.type_" + TYPE.values()[getType()].toString());
	}

	public void setType(TYPE type) {
		this.type = type.ordinal();
	}

	public void setType(Integer number) {
		this.type = (number == null ? 0 : number);
	}

	public Integer getNumber() {
		return number;
	}

	public void setNumber(Integer number) {
		this.number = (number == null ? -1 : number);
		setName(String.format("%s %02d", getTypeLib(), number));
	}

	public Scene getScene() {
		return scene;
	}

	public void setScene(Scene entity) {
		this.scene = entity;
	}

	public String getSort() {
		return sort;
	}

	public void setSort(String value) {
		this.sort = value;
	}

	/**
	 * compute default sort value format is: pp.cc.ss.ttttt where: - pp is part number, 99 if not -
	 * cc is chapter number, 99 if not - ss is scene number, mandatory - ttttt is text index of the
	 * endnote
	 *
	 * @param loc
	 */
	public void setSort(int loc) {
		int nchapter = 99, npart = 99;
		if (getScene().hasChapter()) {
			nchapter = getScene().getChapter().getChapterno();
			if (getScene().getChapter().hasPart()) {
				npart = getScene().getChapter().getPart().getNumber();
			}
		}
		this.sort = String.format("%02d.%02d.%03d.%05d",
		   npart,
		   nchapter,
		   scene.getSceneno(),
		   loc);
	}

	@Override
	public String toDetail(Integer detailed) {
		StringBuilder b = new StringBuilder();
		b.append(super.toDetailHeader(detailed));
		b.append(getInfo(2, DATA.ENDNOTE_TYPE, getType() + " (" + getTypeLib() + ")"));
		b.append(getInfo(2, DATA.ENDNOTE_NUMBER, getNumber()));
		b.append(getInfo(2, DATA.ENDNOTE_SCENE, getScene()));
		if (App.isDev()) {
			b.append(getInfo(2, DATA.SORT, getSort()));
		}
		b.append(super.toDetailFooter(detailed));
		return b.toString();
	}

	@Override
	public String toCsv(String quoteStart, String quoteEnd, String separator) {
		StringBuilder b = new StringBuilder();
		b.append(quoteStart).append(getClean(id)).append(quoteEnd).append(separator);
		b.append(quoteStart).append(getClean(getName())).append(quoteEnd).append(separator);
		b.append(quoteStart).append(getClean(type)).append(quoteEnd).append(separator);
		b.append(quoteStart).append(getClean(number)).append(quoteEnd).append(separator);
		b.append(quoteStart).append(getClean(scene)).append(quoteEnd).append(separator);
		b.append(quoteStart).append(getClean(getNotes())).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 toXml() {
		StringBuilder b = new StringBuilder(toXmlBeg());
		b.append(XmlUtil.setAttribute(2, XK.TYPE, getClean(type)))
		   .append(XmlUtil.setAttribute(0, XK.NUMBER, getClean(number)))
		   .append(XmlUtil.setAttribute(0, XK.SCENE, getClean(scene)))
		   .append(XmlUtil.setAttribute(0, XK.SORT, getClean(sort)))
		   .append(">\n");
		b.append(toXmlEnd());
		return b.toString();
	}

	public static Endnote fromXml(MainFrame mainFrame, Node node) {
		//LOG.trace(TT + ".fromXml(mainFrame, node=" + node.getTextContent() + ")");
		Endnote p = new Endnote();
		fromXmlBeg(node, p);
		p.setType(XmlUtil.getInteger(node, XK.TYPE));
		p.setNumber(XmlUtil.getInteger(node, XK.NUMBER));
		p.setSort(XmlUtil.getString(node, XK.SORT));
		p.setScene((Scene) EntityUtil
		   .findEntityById(mainFrame, Book.TYPE.SCENE, XmlUtil.getLong(node, XK.SCENE)));
		fromXmlEnd(node, p);
		return p;
	}

	@Override
	@SuppressWarnings("EqualsWhichDoesntCheckParameterClass")
	public boolean equals(Object obj) {
		if (!super.equals(obj)) {
			return false;
		}
		Endnote test = (Endnote) obj;
		boolean ret = true;
		ret = ret && equalsIntegerNullValue(getType(), test.getType());
		ret = ret && equalsIntegerNullValue(getNumber(), test.getNumber());
		ret = ret && equalsObjectNullValue(getScene(), test.getScene());
		ret = ret && equalsStringNullValue(getNotes(), test.getNotes());
		return ret;
	}

	@Override
	public int hashCode() {
		return hashPlus(super.hashCode(), number, scene, sort);
	}

	/**
	 * get the link to the endnote from scene text
	 * <a href="#endnote_001" name="innote_001"><small><sup>&#160;(1)</sup></small></a>
	 *
	 * @param dir : for export, the directory of the endnote.html
	 * @return
	 */
	public String getLinkToEndnote(String dir) {
		return linkTo(dir, this);
	}

	/**
	 * get link from the endnote to the scene where it is
	 *
	 * @param dir
	 * @return
	 */
	public String getLinkToScene(String dir) {
		StringBuilder b = new StringBuilder();
		String snumber = String.format("_%03d", id);
		b.append(String.format("<a href=\"%s#innote%s\"", dir, snumber))
		   .append(String.format(" name=\"endnote_%s\">", snumber))
		   .append(String.format("%d. </a>", number));
		return b.toString();
	}

	/**
	 * ************************
	 */
	//***************************
	//** Utilities for Endnote **
	//***************************
	//***************************
	/**
	 * find an Endnote of the given type
	 *
	 * @param mainFrame
	 * @param type
	 *
	 * @return
	 */
	public static List<Endnote> find(MainFrame mainFrame, TYPE type) {
		//LOG.trace(TT + ".find(mainFrame, type=" + type + ")");
		Model model = mainFrame.getBookModel();
		Session session = model.beginTransaction();
		List<Endnote> list = new EndnoteDAO(session).findAllBySort(type.ordinal());
		model.commit();
		return list;
	}

	/**
	 * find an Endnotes sorted by Scene of the given type
	 *
	 * @param mainFrame
	 * @param type
	 *
	 * @return
	 */
	public static List<Scene> findByScene(MainFrame mainFrame, TYPE type) {
		//LOG.trace(TT + ".find(mainFrame, type=" + type + ")");
		Model model = mainFrame.getBookModel();
		Session session = model.beginTransaction();
		List<Scene> list = new EndnoteDAO(session).findScenes(type.ordinal());
		model.commit();
		return list;
	}

	/**
	 * find all Endnotes of the given type in the given Scene
	 *
	 * @param mainFrame
	 * @param type
	 * @param scene
	 *
	 * @return
	 */
	public static List<Endnote> find(MainFrame mainFrame, TYPE type, Scene scene) {
		List<Endnote> list = find(mainFrame, type);
		if (scene == null) {
			return list;
		}
		List<Endnote> endnotes = new ArrayList<>();
		for (Object obj : list) {
			Endnote en = (Endnote) obj;
			if (en.getType() == type.ordinal()
			   && scene.equals(en.getScene())) {
				endnotes.add(en);
			}
		}
		return endnotes;
	}

	/**
	 * find an Endnote of the given number in the given Scene
	 *
	 * @param mainFrame
	 * @param type
	 * @param scene
	 * @param number
	 * @return
	 */
	public static Endnote find(MainFrame mainFrame, TYPE type, Scene scene, int number) {
		for (Object obj : find(mainFrame, type)) {
			Endnote en = (Endnote) obj;
			if (en.getType() == type.ordinal()
			   && scene.equals(en.getScene())
			   && number == en.getNumber()) {
				return en;
			}
		}
		return null;
	}

	/**
	 * remove all given type Endnote in the given Scene
	 *
	 * @param mainFrame
	 * @param type
	 * @param scene
	 */
	public static void removeEndnotes(MainFrame mainFrame, TYPE type, Scene scene) {
		//LOG.trace(TT + ".removeEndnotes(mainFrame, scene=" + AbstractEntity.trace(scene) + ")");
		for (Endnote en : find(mainFrame, type, scene)) {
			removeEndnote(mainFrame, (Endnote) en);
		}
	}

	/**
	 * remove the given Endnote
	 *
	 * @param mainFrame
	 * @param endnote
	 */
	public static void removeEndnote(MainFrame mainFrame, Endnote endnote) {
		//LOG.trace(TT + ".removeEndnote(mainFrame, endnote=" + AbstractEntity.trace(endnote) + ")");
		Scene scene = endnote.getScene();
		if (scene != null) {
			String link = endnote.getLinkToEndnote("");
			if (!scene.getSummary().isEmpty()) {
				scene.setSummary(scene.getSummary().replace(link, ""));
				mainFrame.getBookModel().SCENE_Update(scene);
			}
		}
		mainFrame.getBookModel().ENTITY_Delete(endnote);
	}

	/**
	 * renumber the Endnotes of the given type
	 *
	 * @param mainFrame
	 * @param type
	 * @return
	 */
	public static boolean renumber(MainFrame mainFrame, Integer type) {
		//LOG.trace(TT + ".renumber(mainFrame)");
		mainFrame.cursorSetWaiting();
		boolean rc = false;
		Ctrl ctrl = mainFrame.getBookController();
		Model model = mainFrame.getBookModel();
		Session session = model.beginTransaction();
		List<Endnote> endnotes = new EndnoteDAO(session).findAllBySort(type);
		model.commit();
		int num = 1;
		List<Endnote> toRemove = new ArrayList<>();
		for (Endnote en : endnotes) {
			Scene scene = en.getScene();
			if (scene != null) {
				if (!scene.getSummary().contains(en.getLinkToEndnote(""))) {
					mainFrame.getBookModel().ENTITY_Delete(en);
					continue;
				}
				if (en.getNumber() != num) {
					String oldLink = en.getLinkToEndnote("");
					en.setNumber(num);
					if (type == TYPE.ENDNOTE.ordinal()) {
						//replacing link only for Endnote
						String newLink = en.getLinkToEndnote("");
						String text = scene.getSummary().replace(oldLink, newLink);
						scene.setSummary(text);
						ctrl.updateEntity(scene);
					}
					ctrl.updateEntity(en);
					rc = true;
				}
				num++;
			} else {
				toRemove.add(en);
			}
		}
		if (!toRemove.isEmpty()) {
			for (Endnote en : toRemove) {
				ctrl.deleteEntity(en);
			}
		}
		mainFrame.cursorSetDefault();
		return rc;
	}

	/**
	 * create an Endnoteof the given type in the given text
	 *
	 * @param mainFrame
	 * @param type
	 * @param scene
	 * @param htTexte the ShefEditor where to insert the Endnote
	 *
	 * @return
	 */
	public static Endnote createEndnote(MainFrame mainFrame, TYPE type,
	   Scene scene, ShefEditor htTexte) {
		int num = 1;
		for (Endnote en : find(mainFrame, type, scene)) {
			num = Math.max(num, ((Endnote) en).getNumber() + 1);
		}
		Endnote en = new Endnote(type.ordinal(), scene, num);
		en.setSort(htTexte.wysEditorGet().getWysEditor().getCaretPosition());
		if (mainFrame.showEditorAsDialog(en)) {
			return null;
		}
		en = Endnote.find(mainFrame, type, scene, num);
		String link = linkTo("", en);
		htTexte.insertText(link);
		return en;
	}

	/**
	 * remove all Endnotes of the given type
	 *
	 * @param mainFrame
	 * @param type
	 */
	public static void removeAll(MainFrame mainFrame, TYPE type) {
		//LOG.trace(TT + ".removeAll(mainFrame, type="
		//+ (type == 0 ? "endnote" : "comment") + ")");
		for (Scene scene : Scenes.find(mainFrame)) {
			removeEndnotes(mainFrame, type, scene);
		}
	}

	/**
	 * resort the Endnotes of the given type, not for Comments
	 *
	 * @param mainFrame
	 * @param type
	 */
	public static void resort(MainFrame mainFrame, TYPE type) {
		if (type == TYPE.ENDNOTE) {
			List<Scene> scenes = findByScene(mainFrame, type);
			for (Scene scene : scenes) {
				resort(mainFrame, type, scene);
			}
		}
	}

	/**
	 * resort the Endnotes of the given type in the given Scene, not for Comments
	 *
	 * @param mainFrame
	 * @param type
	 * @param scene
	 */
	public static void resort(MainFrame mainFrame, TYPE type, Scene scene) {
		if (type == TYPE.ENDNOTE) {
			String text = Html.htmlToText(scene.getSummary());
			List<Endnote> endnotes = find(mainFrame, type, scene);
			for (Endnote en : endnotes) {
				int idx = text.indexOf("[" + en.getNumber().toString() + "]") + 1;
				en.setSort(idx);
				mainFrame.getBookController().updateEntity(en);
			}
		}
	}

	/**
	 * get the JButton to show the list of Endnotes
	 *
	 * @param act the action for button clicked
	 *
	 * @return
	 */
	public static JButton getButtonShow(ActionListener... act) {
		JButton bt = new JButton(IconUtil.getIconSmall(ICONS.K.SORT));
		bt.setName("endnote_show");
		bt.setMnemonic('X');
		bt.setToolTipText(Shortcuts.getTooltips("shef", "endnote_show"));
		if (act != null && act.length > 0) {
			bt.addActionListener(act[0]);
		}
		return bt;
	}

	/**
	 * get a JButton for adding an Endnote
	 *
	 * @param act the action for button clicked
	 *
	 * @return
	 */
	public static JButton getButtonAdd(ActionListener... act) {
		JButton bt = new JButton(IconUtil.getIconSmall(ICONS.K.CHAR_ENDNOTE));
		bt.setName("endnote_add");
		bt.setMnemonic('W');
		bt.setToolTipText(Shortcuts.getTooltips("shef", "endnote_add"));
		if (act != null && act.length > 0) {
			bt.addActionListener(act[0]);
		}
		return bt;
	}

	/**
	 * get the next number to create
	 *
	 * @param endnotes
	 * @return
	 */
	public static Integer getNextNumber(List endnotes) {
		int n = 0;
		for (Object c : endnotes) {
			if (((Endnote) c).number > n) {
				n = ((Endnote) c).number;
			}
		}
		return (n + 1);
	}

	/**
	 * get the first Endnote of given type
	 *
	 * @param mainFrame
	 * @param scene
	 * @param type
	 * @return
	 */
	public static int findFirst(MainFrame mainFrame, Scene scene, TYPE type) {
		List endnotes = find(mainFrame, type, scene);
		if (!endnotes.isEmpty()) {
			return ((Endnote) endnotes.get(0)).getNumber();
		}
		return find(mainFrame, type).size() + 1;
	}

	/**
	 * find the Endnote by number for the given type
	 *
	 * @param endnotes
	 * @param n for the number to find
	 * @param type
	 * @return
	 */
	public static Endnote findNumber(List endnotes, int n, int type) {
		if (endnotes == null || endnotes.isEmpty()) {
			return (null);
		}
		for (Object c : endnotes) {
			if (((Endnote) c).type == type && ((Endnote) c).number == n) {
				return (Endnote) c;
			}
		}
		return (null);
	}

	/**
	 * Link to the endnote from text like
	 * <a class="endnote" href="#endnote_001" name="innote_001"><small><sup>(1)</sup></small></a>
	 *
	 * @param file : real name of the endnote.html file
	 * @param endnote
	 *
	 * @return
	 */
	public static String linkTo(String file, Endnote endnote) {
		return linkTo(file, endnote.getId(), endnote.getNumber(), endnote.getType());
	}

	/**
	 * get a String which link to the endnote from text
	 * <a class="endnote" href="#endnote_001" name="innote_001"><small><sup>(1)</sup></small></a>
	 *
	 * @param file : real name of the endnote.html file
	 * @param id of the endnote
	 * @param number
	 * @param type of Endnote
	 *
	 * @return
	 */
	public static String linkTo(String file, Long id, int number, int type) {
		if (type < 0) {
			type = 0;
		}
		if (type > 1) {
			type = 1;
		}
		String snumber = String.format("_%03d", id);
		String ntype = "endnote";
		String enclose = "()";
		if (type == 1) {
			ntype = "comment";
			enclose = "[]";
		}
		String shref = String.format("%s#" + ntype + "%s", file, snumber);
		String sname = String.format("in" + ntype + "%s", snumber);
		String stext = String.format("%c%d%c", enclose.charAt(0), number, enclose.charAt(1));
		String sclass = " class=\"" + ntype + "\"";
		return Html.intoA(sname, shref, stext, sclass);
	}

	/**
	 * get a String which link to the scene from the Endnote like
	 * <a href="#endnote_001" name="innote_001">NN. text note</a>
	 *
	 * @param file : real name of the origin file
	 * @param endnote
	 * @return
	 */
	public static String linkFrom(String file, Endnote endnote) {
		return linkFrom(file,
		   endnote.getId(),
		   endnote.getNumber(),
		   endnote.getType());
	}

	/**
	 * get a String which link to the scene from the Endnote like
	 * <a href="#endnote_001" name="innote_001">NN.text note</a>
	 *
	 * @param file : real name of the origin file
	 * @param id of the Endnote
	 * @param number
	 * @param type of the Endnote
	 *
	 * @return
	 */
	public static String linkFrom(String file, Long id, int number, int type) {
		if (type < 0) {
			type = 0;
		}
		if (type > 1) {
			type = 1;
		}
		String libin = "innote";
		String libend = "endnote";
		if (type == 1) {
			libin = "incomment";
			libend = "comment";
		}
		String sid = String.format("_%03d", id);
		StringBuilder b = new StringBuilder();
		String shref = String.format("%s#%s%s", file, libin, sid);
		String sname = String.format("%s%s", libend, sid);
		String text = String.format("[%d]", number);
		b.append(Html.intoA(sname, shref, text, "class=\"" + libend + "\""));
		return b.toString();
	}

	/**
	 * check if the Endnote is in the Scene text
	 *
	 * @return true if yes
	 */
	public boolean isInText() {
		return (scene == null ? false : scene.getSummary().contains(getLinkToEndnote("")));
	}

	/**
	 * get the default columns for table
	 *
	 * @return
	 */
	public static List<String> getDefColumns() {
		List<String> list = AbstractEntity.getDefColumns(Book.TYPE.ENDNOTE);
		return list;
	}

	/**
	 * get the table format to check/resize
	 *
	 * @return
	 */
	public static List<String> getTable() {
		List<String> ls = new ArrayList<>();
		String tableName = "endnote";
		AbstractEntity.getTable(tableName, ls);
		ls.add(tableName + ",number,Integer,0");
		ls.add(tableName + ",type,Integer,0");
		ls.add(tableName + ",sort,String,256");
		ls.add(tableName + ",scene_id,Integer,0");
		return ls;
	}

}
