/*
Storybook: Open Source software for novelists and authors.
Copyright (C) 2008 - 2012 Martin Mustun, 2015 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;

import api.mig.swing.MigLayout;
import i18n.I18N;
import ideabox.IdeaxFrm;
import java.awt.Component;
import java.awt.Dimension;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JProgressBar;
import org.hibernate.Session;
import org.hibernate.exception.ConstraintViolationException;
import storybook.ctrl.ActKey;
import storybook.ctrl.Ctrl;
import static storybook.ctrl.Ctrl.PROPS.*;
import storybook.exim.EXIM;
import storybook.model.book.Book;
import static storybook.model.book.Book.TYPE.*;
import storybook.model.book.BookUtil;
import storybook.model.hbn.dao.AttributeDAO;
import storybook.model.hbn.dao.CategoryDAO;
import storybook.model.hbn.dao.ChapterDAO;
import storybook.model.hbn.dao.EndnoteDAO;
import storybook.model.hbn.dao.EpisodeDAO;
import storybook.model.hbn.dao.EventDAO;
import storybook.model.hbn.dao.GenderDAO;
import storybook.model.hbn.dao.IdeaDAO;
import storybook.model.hbn.dao.InternalDAO;
import storybook.model.hbn.dao.ItemDAO;
import storybook.model.hbn.dao.ItemlinkDAO;
import storybook.model.hbn.dao.LocationDAO;
import storybook.model.hbn.dao.MemoDAO;
import storybook.model.hbn.dao.PartDAO;
import storybook.model.hbn.dao.PersonDAO;
import storybook.model.hbn.dao.PlotDAO;
import storybook.model.hbn.dao.RelationDAO;
import storybook.model.hbn.dao.SceneDAO;
import storybook.model.hbn.dao.StrandDAO;
import storybook.model.hbn.dao.TagDAO;
import storybook.model.hbn.dao.TaglinkDAO;
import storybook.model.hbn.entity.AbstractEntity;
import storybook.model.hbn.entity.Attribute;
import storybook.model.hbn.entity.Category;
import storybook.model.hbn.entity.Challenge;
import storybook.model.hbn.entity.Chapter;
import storybook.model.hbn.entity.Endnote;
import storybook.model.hbn.entity.Episode;
import storybook.model.hbn.entity.Event;
import storybook.model.hbn.entity.Gender;
import storybook.model.hbn.entity.Idea;
import storybook.model.hbn.entity.Internal;
import storybook.model.hbn.entity.Item;
import storybook.model.hbn.entity.Itemlink;
import storybook.model.hbn.entity.Location;
import storybook.model.hbn.entity.Memo;
import storybook.model.hbn.entity.Part;
import storybook.model.hbn.entity.Person;
import storybook.model.hbn.entity.Plot;
import storybook.model.hbn.entity.Relationship;
import storybook.model.hbn.entity.Scene;
import storybook.model.hbn.entity.Scenes;
import storybook.model.hbn.entity.Strand;
import storybook.model.hbn.entity.Tag;
import storybook.model.hbn.entity.Taglink;
import storybook.model.state.SceneStatus;
import storybook.tools.DateUtil;
import storybook.tools.LOG;
import storybook.tools.swing.ColorUtil;
import storybook.tools.swing.FontUtil;
import storybook.ui.MIG;
import storybook.ui.MainFrame;
import storybook.ui.SbView;
import storybook.ui.SbView.VIEWNAME;
import storybook.ui.panel.book.BookPanel;
import storybook.ui.panel.chrono.ChronoPanel;
import storybook.ui.panel.manage.Manage;
import storybook.ui.panel.reading.ReadingPanel;

/**
 * class for the project Model
 *
 * @author martin
 *
 */
public class Model extends AbstractModel {

	private final static String TT = "Model";

	public Model(MainFrame m) {
		super(m);
		name = "book";
	}

	/**
	 * initialize the database with basics Entities for Import
	 *
	 * @param title
	 */
	public synchronized void initEntities(String title) {
		//LOG.trace(TT+".initEntities()");
		Session session = beginTransaction();
		// default strand
		session.save(new Strand(1,
		   I18N.getMsg("strand.name.init_value"),
		   I18N.getMsg("strand.abbr.init_value"),
		   ColorUtil.PALETTE.LIGHT_BLUE.getRGB(), ""));
		// default genders
		session.save(new Gender(I18N.getMsg("person.gender.male"), 6, 12, 18, 65));
		session.save(new Gender(I18N.getMsg("person.gender.female"), 6, 12, 18, 65));
		// default categories
		session.save(new Category(1, I18N.getMsg("category.central_character"), null));
		session.save(new Category(2, I18N.getMsg("category.minor_character"), null));
		commit();
	}

	/**
	 * initialize the basics Entities for a new Project
	 *
	 * @param nature
	 * @param nbParts
	 * @param nbChapters
	 * @param objective
	 */
	public synchronized void initEntities(int nature, int nbParts, int nbChapters, Date objective) {
		//LOG.trace(TT+".initEntities()");
		Session session = beginTransaction();
		// default strand
		Strand strand = new Strand(1,
		   I18N.getMsg("strand.name.init_value"),
		   I18N.getMsg("strand.abbr.init_value"),
		   ColorUtil.PALETTE.LIGHT_BLUE.getRGB(), "");
		session.save(strand);
		// default part
		for (int i = 1; i <= nbParts; i++) {
			Part part = new Part(i, String.format("%s %d", I18N.getMsg("part"), i));
			if (objective != null) {
				part.setObjectiveChars(BookUtil.computeObjectiveChars(nature, nbParts));
				part.setObjectiveTime(DateUtil.addDateTimeToTS(objective, null));
			}
			session.save(part);
		}
		// first chapter
		Part defPart = new PartDAO(session).findFirst();
		for (int i = 1; i <= nbChapters; i++) {
			Chapter chapter = new Chapter(defPart, i, String.format("%s %d", I18N.getMsg("chapter"), i));
			if (objective != null) {
				chapter.setObjectiveChars(BookUtil.computeObjectiveChars(nature, nbChapters));
				chapter.setObjectiveTime(DateUtil.addDateTimeToTS(objective, null));
			}
			session.save(chapter);
		}
		// first scene
		Chapter chapter = new ChapterDAO(session).findFirst();
		session.save(Scenes.create(strand, chapter));
		// default genders
		session.save(new Gender(I18N.getMsg("person.gender.male"), 6, 12, 18, 65));
		session.save(new Gender(I18N.getMsg("person.gender.female"), 6, 12, 18, 65));
		// default categories
		session.save(new Category(1, I18N.getMsg("category.central_character"), null));
		session.save(new Category(2, I18N.getMsg("category.minor_character"), null));

		commit();
	}

	/**
	 * test if a SQL Session for Part and Chapter is OK
	 *
	 * @param dbName
	 */
	@Override
	public synchronized void testSession(String dbName) {
		//LOG.trace(TT+".testSession(" + dbName + ")");
		try {
			sessionFactory.init(dbName);
			Session session = beginTransaction();
			// test queries for Part and Chapter
			sessionFactory.test(new PartDAO(session));
			sessionFactory.test(new ChapterDAO(session));
			commit();
			//LOG.trace(TT+".initSessions(dbName) SQL query test OK");
		} catch (Exception e) {
			LOG.log(TT + ".checkSession(dbName) *** SQL query test not OK");
		}
	}

	/**
	 * print the view, unusable because there is not print job
	 *
	 * @param view
	 */
	public void print(SbView view) {
		//empty
	}

	// common
	public void setRefresh(SbView view) {
		//LOG.trace(TT+".setRefresh(" + view.getName() + ")");
		firePropertyChange(Ctrl.PROPS.REFRESH.toString(), null, view);
		try {
			if (view.getComponentCount() == 0) {
				return;
			}
			Component comp = view.getComponent();
			if (comp instanceof ChronoPanel
			   || comp instanceof BookPanel
			   || comp instanceof Manage
			   || comp instanceof ReadingPanel) {
				// these views don't need a "fire again"
				return;
			}
			fireAgain(view);
		} catch (ArrayIndexOutOfBoundsException e) {
			// ignore
		}
	}

	/**
	 * show the options of the given view
	 *
	 * @param view
	 */
	public void showOptions(SbView view) {
		firePropertyChange(Ctrl.PROPS.SHOWOPTIONS.toString(), null, view);
	}

	/**
	 * show the given Entity into Info
	 *
	 * @param entity
	 */
	public void InfoShow(AbstractEntity entity) {
		//mainFrame.showView(VIEWNAME.INFO);
		firePropertyChange(Ctrl.PROPS.SHOWINFO.toString(), null, entity);
	}

	/**
	 * show the H2db file properties into Info
	 *
	 * @param dbFile
	 */
	public void InfoShow(H2File dbFile) {
		firePropertyChange(Ctrl.PROPS.SHOWINFO.toString(), null, dbFile);
	}

	// memoria view
	/**
	 * change layout of the Memoria
	 *
	 * @param val
	 */
	public void MemoriaLayout(Integer val) {
		firePropertyChange(Ctrl.PROPS.MEMORIA_LAYOUT.toString(), null, val);
	}

	/**
	 * show the given Entity into Memoria
	 *
	 * @param entity
	 */
	public void MemoriaShowEntity(AbstractEntity entity) {
		firePropertyChange(Ctrl.PROPS.SHOWINMEMORIA.toString(), null, entity);
	}

	/**
	 * unload the editor
	 *
	 */
	public void setUnloadEditor() {
		firePropertyChange(Ctrl.PROPS.UNLOADEDITOR.toString(), null, null);
	}

	/**
	 * change Scene filter to the given status
	 *
	 * @param state
	 */
	public void setSceneFilter(SceneStatus state) {
		firePropertyChange(Ctrl.PROPS.SCENE_FILTER_STATUS.toString(), null, state);
	}

	/**
	 * change the filter to the given Strand
	 *
	 * @param strand
	 */
	public void setStrandFilter(String strand) {
		firePropertyChange(Ctrl.PROPS.SCENE_FILTER_STRAND.toString(), null, strand);
	}

	/**
	 * print the view
	 *
	 * @param view
	 */
	public void setPrint(SbView view) {
		firePropertyChange(Ctrl.PROPS.PRINT.toString(), null, view);
	}

	/**
	 * export the view
	 *
	 * @param view
	 */
	public void setExport(SbView view) {
		EXIM.exporter(mainFrame, view);
	}

	/**
	 * ***************************
	 */
	/**
	 * chrono view *
	 */
	/**
	 * ***************************
	 */
	/**
	 * change the zoom of the Chrono
	 *
	 * @param val
	 */
	public void ChronoZoom(Integer val) {
		firePropertyChange(Ctrl.PROPS.CHRONO_ZOOM.toString(), null, val);
	}

	/**
	 * change layout direction of Chrono
	 *
	 * @param val
	 */
	public void ChronoLayoutDirection(Boolean val) {
		firePropertyChange(Ctrl.PROPS.CHRONO_LAYOUTDIRECTION.toString(), null, val);
	}

	/**
	 * show date difference in Chrono
	 *
	 * @param val : true=yes, false=no
	 */
	public void ChronoShowDateDifference(Boolean val) {
		firePropertyChange(Ctrl.PROPS.CHRONO_SHOWDATEDIFFERENCE.toString(), null, val);
	}

	/**
	 * show the given Entity into Chrono
	 *
	 * @param entity
	 */
	public void ChronoShowEntity(AbstractEntity entity) {
		firePropertyChange(Ctrl.PROPS.CHRONO_SHOWENTITY.toString(), null, entity);
	}

	//*****************************
	//** Book view               **
	//*****************************
	/**
	 * change the zoom for Book
	 *
	 * @param val : zoom level
	 */
	public void BookZoom(Integer val) {
		firePropertyChange(Ctrl.PROPS.BOOK_ZOOM.toString(), null, val);
	}

	/**
	 * show the given Entity into Book
	 *
	 * @param entity
	 */
	public void BookShowEntity(AbstractEntity entity) {
		firePropertyChange(Ctrl.PROPS.BOOK_SHOWENTITY.toString(), null, entity);
	}

	//*****************************
	//** Manage view
	//*****************************
	/**
	 * change the zoom for Manage
	 *
	 * @param val : zoom level
	 */
	public void ManageZoom(Integer val) {
		firePropertyChange(Ctrl.PROPS.MANAGE_ZOOM.toString(), null, val);
	}

	/**
	 * change number of columns in Manage
	 *
	 * @param val
	 */
	public void ManageColumns(Integer val) {
		firePropertyChange(Ctrl.PROPS.MANAGE_COLUMNS.toString(), null, val);
	}

	/**
	 * hide unassigned Scene in Manage
	 */
	public void ManageHideUnassigned() {
		firePropertyChange(Ctrl.PROPS.MANAGE_HIDEUNASSIGNED.toString(), null, null);
	}

	/**
	 * change disposition to vertical in Manage
	 */
	public void ManageVertical() {
		firePropertyChange(Ctrl.PROPS.MANAGE_VERTICAL.toString(), null, null);
	}

	/**
	 * show the given Entity into Manage
	 *
	 * @param entity
	 */
	public void ManageShowEntity(AbstractEntity entity) {
		firePropertyChange(Ctrl.PROPS.MANAGE_SHOWENTITY.toString(), null, entity);
	}

	//*****************************
	//** Reading view
	//*****************************
	/**
	 * change the zoom level in Reading
	 *
	 * @param val
	 */
	public void ReadingZoom(Integer val) {
		firePropertyChange(Ctrl.PROPS.READING_LAYOUT.toString(), null, val);
	}

	/**
	 * change the font size in Reading
	 *
	 * @param val
	 */
	public void ReadingFontSize(Integer val) {
		firePropertyChange(Ctrl.PROPS.READING_FONTSIZE.toString(), null, val);
	}

	//*****************************
	//** Storyboard view
	//*****************************
	/**
	 * change the direction for Storyboard
	 */
	public void StoryboardDirection() {
		firePropertyChange(Ctrl.PROPS.STORYBOARD_DIRECTION.toString(), null, null);
	}

	//*****************************
	//** Timeline view
	//*****************************
	/**
	 * change the zoom for Timeline
	 *
	 * @param val : zoom level
	 */
	public void TimelineZoom(Integer val) {
		firePropertyChange(Ctrl.PROPS.TIMELINE_ZOOM.toString(), null, val);
	}

	/**
	 * change options for Timeline
	 *
	 * @param val
	 */
	public void TimelineOptions(String val) {
		firePropertyChange(Ctrl.PROPS.TIMELINE_OPTIONS.toString(), null, val);
	}

	//*****************************
	//** Entities actions
	//*****************************
	/**
	 * update the given Entity
	 *
	 * @param entity
	 */
	public synchronized void ENTITY_Update(AbstractEntity entity) {
		//LOG.trace(TT+".setEntityUpdate(entity="+entity.toString()+")");
		entity.setMaj();
		switch (Book.getTYPE(entity)) {
			case ATTRIBUTE:
				ATTRIBUTE_Update((Attribute) entity);
				break;
			case CATEGORY:
				CATEGORY_Update((Category) entity);
				break;
			case CHALLENGE:
				CHALLENGE_Update((Challenge) entity);
				break;
			case CHAPTER:
				CHAPTER_Update((Chapter) entity);
				break;
			case ENDNOTE:
				ENDNOTE_Update((Endnote) entity);
				break;
			case EPISODE:
				EPISODE_Update((Episode) entity);
				break;
			case EVENT:
				EVENT_Update((Event) entity);
				break;
			case GENDER:
				GENDER_Update((Gender) entity);
				break;
			case IDEA:
				IDEA_Update((Idea) entity);
				break;
			case INTERNAL:
				INTERNAL_Update((Internal) entity);
				break;
			case ITEM:
				ITEM_Update((Item) entity);
				break;
			case ITEMLINK:
				ITEMLINK_Update((Itemlink) entity);
				break;
			case LOCATION:
				LOCATION_Update((Location) entity);
				break;
			case MEMO:
				MEMO_Update((Memo) entity);
				break;
			case PART:
				PART_Update((Part) entity);
				break;
			case PERSON:
				PERSON_Update((Person) entity);
				break;
			case PLOT:
				PLOT_Update((Plot) entity);
				break;
			case RELATION:
				RELATION_Update((Relationship) entity);
				break;
			case SCENE:
				SCENE_Update((Scene) entity);
				break;
			case STRAND:
				STRAND_Update((Strand) entity);
				break;
			case TAG:
				TAG_Update((Tag) entity);
				break;
			case TAGLINK:
				TAGLINK_Update((Taglink) entity);
				break;
			default:
				LOG.err("updateEntity type not found: " + entity.getObjType());
				break;
		}
	}

	/**
	 * create a new Entity like the given one
	 *
	 * @param entity
	 */
	public synchronized void ENTITY_New(AbstractEntity entity) {
		entity.setCreation();
		entity.setMaj();
		switch (Book.getTYPE(entity)) {
			case ATTRIBUTE:
				ATTRIBUTE_New((Attribute) entity);
				break;
			case CATEGORY:
				CATEGORY_New((Category) entity);
				break;
			case CHAPTER:
				CHAPTER_New((Chapter) entity);
				break;
			case ENDNOTE:
				ENDNOTE_New((Endnote) entity);
				break;
			case EPISODE:
				EPISODE_New((Episode) entity);
				break;
			case EVENT:
				EVENT_New((Event) entity);
				break;
			case GENDER:
				GENDER_New((Gender) entity);
				break;
			case IDEA:
				IDEA_New((Idea) entity);
				break;
			case INTERNAL:
				INTERNAL_New((Internal) entity);
				break;
			case ITEM:
				ITEM_New((Item) entity);
				break;
			case ITEMLINK:
				ITEMLINK_New((Itemlink) entity);
				break;
			case LOCATION:
				LOCATION_New((Location) entity);
				break;
			case MEMO:
				MEMO_New((Memo) entity);
				break;
			case PART:
				PART_New((Part) entity);
				break;
			case PERSON:
				PERSON_New((Person) entity);
				break;
			case PLOT:
				PLOT_New((Plot) entity);
				break;
			case RELATION:
				RELATION_New((Relationship) entity);
				break;
			case SCENE:
				SCENE_New((Scene) entity);
				break;
			case STRAND:
				STRAND_New((Strand) entity);
				break;
			case TAG:
				TAG_New((Tag) entity);
				break;
			case TAGLINK:
				TAGLINK_New((Taglink) entity);
				break;
			default:
				LOG.err("updateEntity type not found: " + entity.getObjType());
				break;
		}
	}

	/**
	 * delete the given Entity
	 *
	 * @param entity
	 * @param silent : optional
	 */
	public synchronized void ENTITY_Delete(AbstractEntity entity, boolean... silent) {
		if (entity == null) {
			return;
		}
		switch (Book.getTYPE(entity)) {
			case ATTRIBUTE:
				ATTRIBUTE_Delete((Attribute) entity);
				break;
			case CATEGORY:
				CATEGORY_Delete((Category) entity);
				break;
			case CHAPTER:
				CHAPTER_Delete((Chapter) entity);
				break;
			case ENDNOTE:
				ENDNOTE_Delete((Endnote) entity);
				break;
			case EPISODE:
				EPISODE_Delete((Episode) entity);
				break;
			case EVENT:
				EVENT_Delete((Event) entity);
				break;
			case GENDER:
				GENDER_Delete((Gender) entity);
				break;
			case IDEA:
				IDEA_Delete((Idea) entity);
				break;
			case INTERNAL:
				INTERNAL_Delete((Internal) entity);
				break;
			case ITEM:
				ITEM_Delete((Item) entity);
				break;
			case ITEMLINK:
				ITEMLINK_Delete((Itemlink) entity);
				break;
			case LOCATION:
				LOCATION_Delete((Location) entity);
				break;
			case MEMO:
				MEMO_Delete((Memo) entity);
				break;
			case PART:
				PART_Delete((Part) entity);
				break;
			case PERSON:
				PERSON_Delete((Person) entity);
				break;
			case PLOT:
				PLOT_Delete((Plot) entity);
				break;
			case RELATION:
				RELATION_Delete((Relationship) entity);
				break;
			case SCENE:
				SCENE_Delete((Scene) entity);
				break;
			case STRAND:
				STRAND_Delete((Strand) entity);
				break;
			case TAG:
				TAG_Delete((Tag) entity);
				break;
			case TAGLINK:
				TAGLINK_Delete((Taglink) entity);
				break;
			default:
				if (silent == null) {
					LOG.err("deleteEntity type not found: " + entity.getObjType());
				}
				return;
		}
		mainFrame.setUpdated();
	}

	/**
	 * delete the given Entities list ID
	 *
	 * @param type
	 * @param ids
	 */
	public synchronized void ENTITY_Delete(Book.TYPE type, ArrayList<Long> ids) {
		switch (type) {
			case ATTRIBUTE:
				Model.this.ATTRIBUTE_Delete(ids);
				break;
			case CATEGORY:
				CATEGORY_Delete(ids);
				break;
			case CHAPTER:
				Model.this.CHAPTER_Delete(ids);
				break;
			case ENDNOTE:
				ENDNOTE_Delete(ids);
				break;
			case EVENT:
				EVENT_Delete(ids);
				break;
			case GENDER:
				GENDER_Delete(ids);
				break;
			case IDEA:
				IDEA_Delete(ids);
				break;
			case INTERNAL:
				INTERNAL_Delete(ids);
				break;
			case ITEM:
				ITEM_Delete(ids);
				break;
			case ITEMLINK:
				ITEMLINK_Delete(ids);
				break;
			case LOCATION:
				LOCATION_Delete(ids);
				break;
			case MEMO:
				MEMO_Delete(ids);
				break;
			case PART:
				PART_Delete(ids);
				break;
			case PERSON:
				PERSON_Delete(ids);
				break;
			case PLOT:
				PLOT_Delete(ids);
				break;
			case RELATION:
				RELATION_Delete(ids);
				break;
			case SCENE:
				SCENE_Delete(ids);
				break;
			case STRAND:
				STRAND_Delete(ids);
				break;
			case TAG:
				TAG_Delete(ids);
				break;
			case TAGLINK:
				TAGLINK_Delete(ids);
				break;
			default:
				LOG.err("deleteEntity type not found: " + type);
				break;
		}
	}

	/**
	 * create a new Attribute like the given one
	 *
	 * @param entity
	 */
	public synchronized void ATTRIBUTE_New(Attribute entity) {
		Session session = beginTransaction();
		session.save(entity);
		commit();
		firePropertyChange(ATTRIBUTE, NEW, null, entity);
	}

	/**
	 * update the given Attribute entity
	 *
	 * @param entity
	 */
	public synchronized void ATTRIBUTE_Update(Attribute entity) {
		Session session = beginTransaction();
		AttributeDAO dao = new AttributeDAO(session);
		Attribute old = dao.find(entity.getId());
		commit();
		session = beginTransaction();
		session.update(entity);
		commit();
		firePropertyChange(ATTRIBUTE, UPDATE, old, entity);
	}

	public synchronized void ATTRIBUTE_Delete(Attribute entity) {
		if (entity.getId() == null) {
			return;
		}
		Session session = beginTransaction();
		// find linked entities
		PersonDAO dao = new PersonDAO(session);
		List<Person> persons = dao.findByAttribute(entity);
		commit();
		for (Person person : persons) {
			if (person.getAttributes().contains(entity)) {
				person.getAttributes().remove(entity);
				PERSON_Update(person);
			}
		}
		// delete attribute
		session = beginTransaction();
		session.delete(entity);
		commit();
		firePropertyChange(ATTRIBUTE, DELETE, entity, null);
	}

	public synchronized void ATTRIBUTE_Delete(ArrayList<Long> ids) {
		for (Long id : ids) {
			Session session = beginTransaction();
			AttributeDAO dao = new AttributeDAO(session);
			Attribute old = dao.find(id);
			commit();
			Model.this.ATTRIBUTE_Delete(old);
		}
	}

	//** category
	public synchronized void CATEGORY_New(Category category) {
		Session session = beginTransaction();
		session.save(category);
		commit();
		firePropertyChange(CATEGORY, NEW, null, category);
	}

	public synchronized void CATEGORY_Update(Category category) {
		Session session = beginTransaction();
		CategoryDAO dao = new CategoryDAO(session);
		Category old = dao.find(category.getId());
		commit();
		session = beginTransaction();
		session.update(category);
		commit();
		firePropertyChange(CATEGORY, UPDATE, old, category);
	}

	public synchronized void CATEGORY_Delete(Category category) {
		if (category.getId() == null) {
			return;
		}
		// set category of affected persons to "minor"
		Session session = beginTransaction();
		CategoryDAO dao = new CategoryDAO(session);
		Category minor = dao.findMinor();
		List<Person> persons = dao.findPersons(category);
		commit();
		for (Person person : persons) {
			person.setCategory(minor);
			PERSON_Update(person);
		}
		// delete category
		session = beginTransaction();
		session.delete(category);
		commit();
		firePropertyChange(CATEGORY, DELETE, category, null);
	}

	public synchronized void CATEGORY_Delete(ArrayList<Long> ids) {
		for (Long id : ids) {
			Session session = beginTransaction();
			CategoryDAO dao = new CategoryDAO(session);
			Category old = dao.find(id);
			commit();
			Model.this.CATEGORY_Delete(old);
		}
	}

	public synchronized void CATEGORY_OrderDown(Category category) {
		firePropertyChange(CATEGORY, ORDERDOWN, null, category);
	}

	public synchronized void CATEGORY_OrderUp(Category category) {
		firePropertyChange(CATEGORY, ORDERUP, null, category);
	}

	//** Challenge
	public synchronized void CHALLENGE_New(Challenge entity) {
		Session session = beginTransaction();
		session.save(entity);
		commit();
		//no fireagain
		//firePropertyChange(CHALLENGE, NEW, null, entity);
	}

	public synchronized void CHALLENGE_Update(Challenge entity) {
		// no need to read previous value of Challenge
		//Session session = beginTransaction();
		//ChallengeDAO dao = new ChallengeDAO(session);
		//Challenge old = dao.find(entity.getId());
		//commit();
		Session session = beginTransaction();
		session.update(entity);
		commit();
		//no fireagain
		//firePropertyChange(CHALLENGE, UPDATE, old, entity);
	}

	public synchronized void CHALLENGE_Delete(Challenge entity) {
		if (entity.getId() == null) {
			return;
		}
		Session session = beginTransaction();
		session.delete(entity);
		commit();
		//no fireagain
		//firePropertyChange(CHALLENGE, DELETE, entity, null);
	}

	public synchronized void CHALLENGE_Delete(ArrayList<Long> ids) {
		// no delete multi, Challenge is unic
	}

	//** chapter
	public synchronized void CHAPTER_New(Chapter entity) {
		Session session = beginTransaction();
		session.save(entity);
		commit();
		firePropertyChange(CHAPTER, NEW, null, entity);
	}

	public synchronized void CHAPTER_Update(Chapter entity) {
		Session session = beginTransaction();
		ChapterDAO dao = new ChapterDAO(session);
		Chapter old = dao.find(entity.getId());
		commit();
		session = beginTransaction();
		session.update(entity);
		commit();
		firePropertyChange(CHAPTER, UPDATE, old, entity);
	}

	@SuppressWarnings("unchecked")
	public synchronized void CHAPTER_Delete(Chapter entity) {
		if (entity.getId() == null) {
			return;
		}
		Session session = beginTransaction();
		// find scenes, set chapter to null
		ChapterDAO dao = new ChapterDAO(session);
		List<Scene> scenes = dao.findScenes(entity);
		commit();
		for (Scene scene : scenes) {
			scene.setChapter();
			SCENE_Update(scene);
		}
		// remove links in Episodes
		EPISODE_RemoveLink(entity);
		// delete chapter
		session = beginTransaction();
		session.delete(entity);
		commit();
		firePropertyChange(CHAPTER, DELETE, entity, null);
	}

	public synchronized void CHAPTER_Delete(ArrayList<Long> ids) {
		for (Long id : ids) {
			Session session = beginTransaction();
			ChapterDAO dao = new ChapterDAO(session);
			Chapter old = dao.find(id);
			commit();
			Model.this.CHAPTER_Delete(old);
		}
	}

	//** endnote
	public synchronized void ENDNOTE_New(Endnote entity) {
		//LOG.trace(TT + ".setENDNOTE_New(entity=" + entity.toString() + ")");
		Session session = beginTransaction();
		session.save(entity);
		commit();
		firePropertyChange(ENDNOTE, NEW, null, entity);
	}

	public synchronized void ENDNOTE_Update(Endnote entity) {
		Session session = beginTransaction();
		EndnoteDAO dao = new EndnoteDAO(session);
		Endnote old = dao.find(entity.getId());
		commit();
		session = beginTransaction();
		session.update(entity);
		commit();
		firePropertyChange(ENDNOTE, UPDATE, old, entity);
	}

	public synchronized void ENDNOTE_Delete(Endnote entity) {
		if (entity.getId() == null) {
			return;
		}
		Session session = beginTransaction();
		session.delete(entity);
		commit();
		firePropertyChange(ENDNOTE, DELETE, entity, null);
	}

	public synchronized void ENDNOTE_Delete(List<Long> ids) {
		for (Long id : ids) {
			Session session = beginTransaction();
			EndnoteDAO dao = new EndnoteDAO(session);
			Endnote old = dao.find(id);
			commit();
			Model.this.ENDNOTE_Delete(old);
		}
	}

	//** episode
	public synchronized void EPISODE_New(Episode entity) {
		//LOG.trace(TT + ".setEpisodeNew(entity=" + App.traceEntity(entity) + ")");
		Session session = beginTransaction();
		session.save(entity);
		commit();
		// no fire property
		//firePropertyChange(EPISODE, NEW, null, entity);
	}

	public synchronized void EPISODE_Update(Episode entity) {
		Session session = beginTransaction();
		EpisodeDAO dao = new EpisodeDAO(session);
		Episode old = dao.find(entity.getId());
		commit();
		session = beginTransaction();
		session.update(entity);
		commit();
		// no fire property
		//firePropertyChange(EPISODE, UPDATE, old, entity);
	}

	public synchronized void EPISODE_Delete(Episode entity) {
		//LOG.trace(TT + ".setEpisodeDelete(entity=" + App.traceEntity(entity) + ")");
		if (entity.getId() == null) {
			return;
		}
		Session session = beginTransaction();
		session.delete(entity);
		commit();
		// no fire property
		//firePropertyChange(EPISODE, DELETE, entity, null);
	}

	public synchronized void EPISODE_Delete(List<Long> ids) {
		// no delete multi episodes
		/*for (Long id : ids) {
			Session session = beginTransaction();
			EpisodeDAOImpl dao = new EpisodeDAOImpl(session);
			Episode old = dao.find(id);
			commit();
			setEpisodeDelete(old);
		}*/
	}

	@SuppressWarnings("unchecked")
	public synchronized void EPISODE_RemoveLink(AbstractEntity entity) {
		for (Episode episode : (List<Episode>) EntityUtil.findEntities(mainFrame, EPISODE)) {
			if (entity instanceof Chapter) {
				if (episode.hasChapter() && episode.getChapter().equals(entity)) {
					episode.setChapter(null);
					EPISODE_Update(episode);
				}
			}
			if (entity instanceof Scene) {
				if (episode.hasScene() && episode.getScene().equals(entity)) {
					episode.setScene(null);
					EPISODE_Update(episode);
				}
			}
		}
	}

	//** Event
	public synchronized void EVENT_New(Event entity) {
		Session session = beginTransaction();
		session.save(entity);
		commit();
		firePropertyChange(EVENT, NEW, null, entity);
	}

	public synchronized void EVENT_Update(Event entity) {
		Session session = beginTransaction();
		EventDAO dao = new EventDAO(session);
		Event old = dao.find(entity.getId());
		commit();
		session = beginTransaction();
		session.update(entity);
		commit();
		firePropertyChange(EVENT, UPDATE, old, entity);
	}

	public synchronized void EVENT_Delete(Event entity) {
		if (entity.getId() == null) {
			return;
		}
		Session session = beginTransaction();
		session.delete(entity);
		commit();
		firePropertyChange(EVENT, DELETE, entity, null);
	}

	public synchronized void EVENT_Delete(ArrayList<Long> ids) {
		for (Long id : ids) {
			Session session = beginTransaction();
			EventDAO dao = new EventDAO(session);
			Event old = dao.find(id);
			commit();
			Model.this.EVENT_Delete(old);
		}
	}

	//** Gender
	public synchronized void GENDER_New(Gender gender) {
		Session session = beginTransaction();
		session.save(gender);
		commit();
		firePropertyChange(GENDER, NEW, null, gender);
	}

	public synchronized void GENDER_Update(Gender gender) {
		Session session = beginTransaction();
		GenderDAO dao = new GenderDAO(session);
		Gender old = dao.find(gender.getId());
		commit();
		session = beginTransaction();
		session.update(gender);
		commit();
		firePropertyChange(GENDER, UPDATE, old, gender);
	}

	public synchronized void GENDER_Delete(Gender gender) {
		if (gender.getId() == null) {
			return;
		}
		// set gender of affected persons to "male"
		Session session = beginTransaction();
		GenderDAO dao = new GenderDAO(session);
		Gender male = dao.findMale();
		List<Person> persons = dao.findPersons(gender);
		commit();
		for (Person person : persons) {
			person.setGender(male);
			PERSON_Update(person);
		}
		// delete gender
		session = beginTransaction();
		session.delete(gender);
		commit();
		firePropertyChange(GENDER, DELETE, gender, null);
	}

	public synchronized void GENDER_Delete(ArrayList<Long> ids) {
		for (Long id : ids) {
			Session session = beginTransaction();
			GenderDAO dao = new GenderDAO(session);
			Gender old = dao.find(id);
			commit();
			Model.this.GENDER_Delete(old);
		}
	}

	//** idea
	public void setIDEA_Edit(Idea entity) {
		ENTITY_Edit((AbstractEntity) entity);
	}

	public synchronized void IDEA_New(Idea idea) {
		//LOG.trace(TT + ".IDEA_New(idea id=" + idea.getId() + ")");
		Session session = beginTransaction();
		session.save(idea);
		commit();
		firePropertyChange(IDEA, NEW, null, idea);
	}

	public synchronized void IDEA_Update(Idea idea) {
		//LOG.trace(TT + ".IDEA_Update(idea" + LOG.trace(idea) + ")");
		Session session = beginTransaction();
		IdeaDAO dao = new IdeaDAO(session);
		Idea old = dao.find(idea.getId());
		commit();
		session = beginTransaction();
		session.update(idea);
		commit();
		firePropertyChange(IDEA, UPDATE, old, idea);
		if (idea.getUuid() != null && !idea.getUuid().isEmpty()) {
			IdeaxFrm.ideaboxUpdate(mainFrame, idea);
		}
	}

	public synchronized void IDEA_Delete(Idea idea) {
		if (idea.getId() == null) {
			return;
		}
		Session session = beginTransaction();
		session.delete(idea);
		commit();
		firePropertyChange(IDEA, DELETE, idea, null);
	}

	public synchronized void IDEA_Delete(ArrayList<Long> ids) {
		for (Long id : ids) {
			Session session = beginTransaction();
			IdeaDAO dao = new IdeaDAO(session);
			Idea old = dao.find(id);
			commit();
			Model.this.IDEA_Delete(old);
		}
	}

	//** Internal
	public void INTERNAL_Edit(Internal entity) {
		ENTITY_Edit((AbstractEntity) entity);
	}

	public synchronized void INTERNAL_New(Internal entity) {
		Session session = beginTransaction();
		session.save(entity);
		commit();
		String prop = new ActKey(Book.TYPE.INTERNAL, Ctrl.PROPS.NEW).toString();
		firePropertyChange(prop, null, entity);
	}

	public synchronized void INTERNAL_Update(Internal entity) {
		Session session = beginTransaction();
		InternalDAO dao = new InternalDAO(session);
		Internal old = dao.find(entity.getId());
		commit();
		session = beginTransaction();
		session.update(entity);
		commit();
		firePropertyChange(INTERNAL, UPDATE, old, entity);
	}

	public synchronized void INTERNAL_Delete(Internal entity) {
		if (entity.getId() == null) {
			return;
		}
		Session session = beginTransaction();
		session.delete(entity);
		commit();
		firePropertyChange(INTERNAL, DELETE, entity, null);
	}

	public synchronized void INTERNAL_Delete(ArrayList<Long> ids) {
		for (Long id : ids) {
			Session session = beginTransaction();
			InternalDAO dao = new InternalDAO(session);
			Internal old = dao.find(id);
			commit();
			Model.this.INTERNAL_Delete(old);
		}
	}

	//** Itemlink
	public synchronized void ITEMLINK_New(Itemlink entity) {
		Session session = beginTransaction();
		session.save(entity);
		commit();
		firePropertyChange(ITEMLINK, NEW, null, entity);
	}

	public synchronized void ITEMLINK_Update(Itemlink entity) {
		Session session = beginTransaction();
		ItemlinkDAO dao = new ItemlinkDAO(session);
		Itemlink old = dao.find(entity.getId());
		commit();
		session = beginTransaction();
		session.update(entity);
		commit();
		firePropertyChange(ITEMLINK, UPDATE, old, entity);
	}

	public synchronized void ITEMLINK_Delete(Itemlink entity) {
		if (entity.getId() == null) {
			return;
		}
		Session session = beginTransaction();
		session.delete(entity);
		commit();
		firePropertyChange(ITEMLINK, DELETE, entity, null);
	}

	public synchronized void ITEMLINK_Delete(ArrayList<Long> ids) {
		for (Long id : ids) {
			Session session = beginTransaction();
			ItemlinkDAO dao = new ItemlinkDAO(session);
			Itemlink old = dao.find(id);
			commit();
			Model.this.ITEMLINK_Delete(old);
		}
	}

	//** Item
	public synchronized void ITEM_New(Item entity) {
		Session session = beginTransaction();
		session.save(entity);
		commit();
		firePropertyChange(ITEM, NEW, null, entity);
	}

	public synchronized void ITEM_Update(Item entity) {
		Session session = beginTransaction();
		ItemDAO dao = new ItemDAO(session);
		Item old = dao.find(entity.getId());
		commit();
		session = beginTransaction();
		session.update(entity);
		commit();
		firePropertyChange(ITEM, UPDATE, old, entity);
	}

	public synchronized void ITEM_Delete(Item item) {
		if (item.getId() == null) {
			return;
		}
		// delete item assignments
		Session session = beginTransaction();
		ItemlinkDAO dao = new ItemlinkDAO(session);
		List<Itemlink> links = dao.findByItem(item);
		commit();
		for (Itemlink link : links) {
			Model.this.ITEMLINK_Delete(link);
		}
		// delete item
		session = beginTransaction();
		session.delete(item);
		commit();
		firePropertyChange(ITEM, DELETE, item, null);
	}

	public synchronized void ITEM_Delete(ArrayList<Long> ids) {
		for (Long id : ids) {
			Session session = beginTransaction();
			ItemDAO dao = new ItemDAO(session);
			Item old = dao.find(id);
			commit();
			Model.this.ITEM_Delete(old);
		}
	}

	//** Location
	public synchronized void LOCATION_New(Location entity) {
		Session session = beginTransaction();
		session.save(entity);
		commit();
		firePropertyChange(LOCATION, NEW, null, entity);
	}

	public synchronized void LOCATION_Update(Location entity) {
		//LOG.trace(TT+".setLocationUpdate(entity=" + entity.toString() + ")");
		Session session = beginTransaction();
		LocationDAO dao = new LocationDAO(session);
		Location old = dao.find(entity.getId());
		commit();
		session = beginTransaction();
		session.update(entity);
		commit();
		firePropertyChange(LOCATION, UPDATE, old, entity);
	}

	public synchronized void LOCATION_Delete(Location entity) {
		if (entity.getId() == null) {
			return;
		}
		// delete scene links
		Session session = beginTransaction();
		SceneDAO dao = new SceneDAO(session);
		List<Scene> scenes = dao.findByLocationLink(entity);
		commit();
		for (Scene scene : scenes) {
			scene.getLocations().remove(entity);
			SCENE_Update(scene);
		}
		// delete tag / item links
		EntityUtil.deleteTagAndItemlinks(this, entity);
		// delete location
		session = beginTransaction();
		session.delete(entity);
		commit();
		firePropertyChange(LOCATION, DELETE, entity, null);
	}

	public synchronized void LOCATION_Delete(ArrayList<Long> ids) {
		for (Long id : ids) {
			Session session = beginTransaction();
			LocationDAO dao = new LocationDAO(session);
			Location old = dao.find(id);
			commit();
			Model.this.LOCATION_Delete(old);
		}
	}

	//** Memo
	public synchronized void MEMO_New(Memo entity) {
		Session session = beginTransaction();
		session.save(entity);
		commit();
		firePropertyChange(MEMO, NEW, null, entity);
	}

	public synchronized void MEMO_Update(Memo entity) {
		Session session = beginTransaction();
		MemoDAO dao = new MemoDAO(session);
		Memo old = dao.find(entity.getId());
		commit();
		session = beginTransaction();
		session.update(entity);
		commit();
		firePropertyChange(MEMO, UPDATE, old, entity);
	}

	public synchronized void MEMO_Delete(Memo entity) {
		if (entity.getId() == null) {
			return;
		}
		Session session = beginTransaction();
		session.delete(entity);
		commit();
		firePropertyChange(MEMO, DELETE, entity, null);
	}

	public synchronized void MEMO_Delete(ArrayList<Long> ids) {
		for (Long id : ids) {
			Session session = beginTransaction();
			MemoDAO dao = new MemoDAO(session);
			Memo old = dao.find(id);
			commit();
			Model.this.MEMO_Delete(old);
		}
	}

	//** Part
	public synchronized void PART_New(Part entity) {
		Session session = beginTransaction();
		session.save(entity);
		commit();
		firePropertyChange(PART, NEW, null, entity);
	}

	public synchronized void PART_Update(Part entity) {
		Session session = beginTransaction();
		PartDAO dao = new PartDAO(session);
		Part old = dao.find(entity.getId());
		commit();
		session = beginTransaction();
		session.update(entity);
		commit();
		firePropertyChange(PART, UPDATE, old, entity);
	}

	public synchronized void PART_Delete(Part entity) {
		if (entity.getId() == null) {
			return;
		}
		Session session = beginTransaction();
		// delete chapters
		PartDAO dao = new PartDAO(session);
		List<Chapter> chapters = dao.findChapters(entity);
		commit();
		for (Chapter chapter : chapters) {
			Model.this.CHAPTER_Delete(chapter);
		}
		// delete part
		session = beginTransaction();
		session.delete(entity);
		commit();
		firePropertyChange(PART, DELETE, entity, null);
	}

	public synchronized void PART_Delete(ArrayList<Long> ids) {
		for (Long id : ids) {
			Session session = beginTransaction();
			PartDAO dao = new PartDAO(session);
			Part old = dao.find(id);
			commit();
			Model.this.PART_Delete(old);
		}
	}

	public synchronized void PART_Change(Part entity) {
		firePropertyChange(PART, CHANGE, null, entity);
	}

	//** Person
	public synchronized void PERSON_New(Person entity) {
		//LOG.trace(TT+".setPersonNew(" + entity.toString() + ")");
		Session session = beginTransaction();
		session.save(entity);
		commit();
		firePropertyChange(PERSON, NEW, null, entity);
	}

	public synchronized void PERSON_Update(Person entity) {
		Session session = beginTransaction();
		PersonDAO dao = new PersonDAO(session);
		Person old = dao.find(entity.getId());
		commit();
		session = beginTransaction();
		session.update(entity);
		commit();
		firePropertyChange(PERSON, UPDATE, old, entity);
	}

	public synchronized void PERSON_Delete(Person entity) {
		if (entity.getId() == null) {
			return;
		}
		// delete scene links
		Session session = beginTransaction();
		SceneDAO dao = new SceneDAO(session);
		List<Scene> scenes = dao.findByPerson(entity);
		commit();
		for (Scene scene : scenes) {
			scene.getPersons().remove(entity);
			SCENE_Update(scene);
		}
		// delete tag / item links
		EntityUtil.deleteTagAndItemlinks(this, entity);
		// delete person
		session = beginTransaction();
		session.delete(entity);
		commit();
		firePropertyChange(PERSON, DELETE, entity, null);
	}

	public synchronized void PERSON_Delete(ArrayList<Long> ids) {
		for (Long id : ids) {
			Session session = beginTransaction();
			PersonDAO dao = new PersonDAO(session);
			Person old = dao.find(id);
			commit();
			Model.this.PERSON_Delete(old);
		}
	}

	//** Plot
	public synchronized void PLOT_New(Plot entity) {
		Session session = beginTransaction();
		session.save(entity);
		commit();
		firePropertyChange(PLOT, NEW, null, entity);
	}

	public synchronized void PLOT_Update(Plot entity) {
		Session session = beginTransaction();
		PlotDAO dao = new PlotDAO(session);
		Plot old = dao.find(entity.getId());
		commit();
		session = beginTransaction();
		session.update(entity);
		commit();
		firePropertyChange(PLOT, UPDATE, old, entity);
	}

	public synchronized void PLOT_Delete(Plot entity) {
		if (entity.getId() == null) {
			return;
		}
		// delete plot assignments
		Session session = beginTransaction();
		SceneDAO dao = new SceneDAO(session);
		List<Scene> scenes = dao.findByPlot(entity);
		commit();
		for (Scene scene : scenes) {
			scene.getPlots().remove(entity);
			SCENE_Update(scene);
		}
		// delete plot
		session = beginTransaction();
		session.delete(entity);
		commit();
		firePropertyChange(PLOT, DELETE, entity, null);
	}

	public synchronized void PLOT_Delete(ArrayList<Long> ids) {
		for (Long id : ids) {
			Session session = beginTransaction();
			PlotDAO dao = new PlotDAO(session);
			Plot old = dao.find(id);
			commit();
			Model.this.PLOT_Delete(old);
		}
	}

	//** Relation
	public synchronized void RELATION_New(Relationship entity) {
		Session session = beginTransaction();
		session.save(entity);
		commit();
		firePropertyChange(RELATION, NEW, null, entity);
	}

	public synchronized void RELATION_Update(Relationship entity) {
		Session session = beginTransaction();
		RelationDAO dao = new RelationDAO(session);
		Relationship old = dao.find(entity.getId());
		commit();
		session = beginTransaction();
		session.update(entity);
		commit();
		firePropertyChange(RELATION, UPDATE, old, entity);
	}

	public synchronized void RELATION_Delete(Relationship entity) {
		if (entity.getId() == null) {
			return;
		}
		Session session = beginTransaction();
		session.delete(entity);
		commit();
		firePropertyChange(RELATION, DELETE, entity, null);
	}

	public synchronized void RELATION_Delete(ArrayList<Long> ids) {
		for (Long id : ids) {
			Session session = beginTransaction();
			RelationDAO dao = new RelationDAO(session);
			Relationship old = dao.find(id);
			commit();
			Model.this.RELATION_Delete(old);
		}
	}

	//** Scene
	public synchronized void SCENE_New(Scene entity) {
		Session session = beginTransaction();
		session.save(entity);
		commit();
		firePropertyChange(SCENE, NEW, null, entity);
	}

	public synchronized void SCENE_Update(Scene entity) {
		//LOG.trace(TT+".setSceneUpdate(entity=" + entity.toString() + ")");
		Session session = beginTransaction();
		Scene old = (Scene) session.get(Scene.class, entity.getId());
		commit();
		session = beginTransaction();
		session.update(entity);
		commit();
		firePropertyChange(SCENE, UPDATE, old, entity);
	}

	@SuppressWarnings("unchecked")
	public synchronized void SCENE_Delete(Scene entity) {
		if (entity.getId() == null) {
			return;
		}
		// delete tag / item links
		EntityUtil.deleteTagAndItemlinks(this, entity);
		// remove relative scene of affected scenes
		Session session = beginTransaction();
		SceneDAO dao = new SceneDAO(session);
		List<Scene> scenes = dao.findScenesWithRelativeSceneId(entity);
		commit();
		for (Scene scene2 : scenes) {
			scene2.removeRelativescene();
			SCENE_Update(scene2);
		}
		// remove links in Episodes
		EPISODE_RemoveLink(entity);
		// delete scene
		session = beginTransaction();
		session.delete(entity);
		commit();
		firePropertyChange(SCENE, DELETE, entity, null);
	}

	public synchronized void SCENE_Delete(List<Long> ids) {
		for (Long id : ids) {
			Session session = beginTransaction();
			SceneDAO dao = new SceneDAO(session);
			Scene old = dao.find(id);
			commit();
			SCENE_Delete(old);
		}
	}

	//** Strand
	public synchronized void STRAND_New(Strand strand) {
		Session session = beginTransaction();
		session.save(strand);
		commit();
		firePropertyChange(STRAND, NEW, null, strand);
	}

	public synchronized void STRAND_Update(Strand strand) {
		Session session = beginTransaction();
		StrandDAO dao = new StrandDAO(session);
		Strand old = dao.find(strand.getId());
		commit();
		session = beginTransaction();
		session.update(strand);
		commit();
		firePropertyChange(STRAND, UPDATE, old, strand);
	}

	public synchronized void STRAND_Delete(Strand strand) {
		if (strand.getId() == null) {
			return;
		}
		@SuppressWarnings("unchecked")
		List<Strand> list = EntityUtil.findEntities(mainFrame, STRAND);
		Strand def = null;
		for (Strand s : list) {
			if (!s.equals(strand)) {
				def = s;
				break;
			}
		}
		try {
			// delete episode strand
			Session session = beginTransaction();
			EpisodeDAO episodeDao = new EpisodeDAO(session);
			List<Episode> episodes = episodeDao.findByStrand(strand);
			commit();
			for (Episode episode : episodes) {
				Model.this.EPISODE_Delete(episode);
			}
			// delete scene strands
			session = beginTransaction();
			SceneDAO sceneDao = new SceneDAO(session);
			List<Scene> scenes = sceneDao.findByStrands(strand);
			commit();
			for (Scene scene : scenes) {
				scene.getStrands().remove(strand);
				SCENE_Update(scene);
			}
			// delete for scenes strand
			session = beginTransaction();
			StrandDAO strandDao = new StrandDAO(session);
			scenes = strandDao.findScenes(strand);
			commit();
			for (Scene scene : scenes) {
				scene.setStrand(def);
				SCENE_Update(scene);
			}
			// delete strand
			session = beginTransaction();
			session.delete(strand);
			commit();
		} catch (ConstraintViolationException e) {
			LOG.err("Model.setDeleteStrand(" + strand.getName() + ")", e);
		}
		firePropertyChange(STRAND, DELETE, strand, null);
	}

	public synchronized void STRAND_Delete(ArrayList<Long> ids) {
		for (Long id : ids) {
			Session session = beginTransaction();
			StrandDAO dao = new StrandDAO(session);
			Strand old = dao.find(id);
			commit();
			Model.this.STRAND_Delete(old);
		}
	}

	public synchronized void STRAND_OrderUp(Strand strand) {
		firePropertyChange(STRAND, ORDERUP, null, strand);
	}

	public synchronized void STRAND_OrderDown(Strand strand) {
		firePropertyChange(STRAND, ORDERDOWN, null, strand);
	}

	//** Taglink
	public synchronized void TAGLINK_New(Taglink entity) {
		Session session = beginTransaction();
		session.save(entity);
		commit();
		firePropertyChange(TAGLINK, NEW, null, entity);
	}

	public synchronized void TAGLINK_Update(Taglink entity) {
		Session session = beginTransaction();
		TaglinkDAO dao = new TaglinkDAO(session);
		Taglink old = dao.find(entity.getId());
		commit();
		session = beginTransaction();
		session.update(entity);
		commit();
		firePropertyChange(TAGLINK, UPDATE, old, entity);
	}

	public synchronized void TAGLINK_Delete(Taglink entity) {
		if (entity.getId() == null) {
			return;
		}
		Session session = beginTransaction();
		session.delete(entity);
		commit();
		firePropertyChange(TAGLINK, DELETE, entity, null);
	}

	public synchronized void TAGLINK_Delete(ArrayList<Long> ids) {
		for (Long id : ids) {
			Session session = beginTransaction();
			TaglinkDAO dao = new TaglinkDAO(session);
			Taglink old = dao.find(id);
			commit();
			Model.this.TAGLINK_Delete(old);
		}
	}

	//** Tag
	public synchronized void TAG_New(Tag tag) {
		Session session = beginTransaction();
		session.save(tag);
		commit();
		firePropertyChange(TAG, NEW, null, tag);
	}

	public synchronized void TAG_Update(Tag tag) {
		Session session = beginTransaction();
		TagDAO dao = new TagDAO(session);
		Tag old = dao.find(tag.getId());
		commit();
		session = beginTransaction();
		session.update(tag);
		commit();
		firePropertyChange(TAG, UPDATE, old, tag);
	}

	public synchronized void TAG_Delete(Tag tag) {
		if (tag.getId() == null) {
			return;
		}
		// delete tag assignments
		Session session = beginTransaction();
		TaglinkDAO dao = new TaglinkDAO(session);
		List<Taglink> links = dao.findByTag(tag);
		commit();
		for (Taglink link : links) {
			Model.this.TAGLINK_Delete(link);
		}
		// delete tag
		session = beginTransaction();
		session.delete(tag);
		commit();
		firePropertyChange(TAG, DELETE, tag, null);
	}

	public synchronized void TAG_Delete(ArrayList<Long> ids) {
		for (Long id : ids) {
			Session session = beginTransaction();
			TagDAO dao = new TagDAO(session);
			Tag old = dao.find(id);
			commit();
			Model.this.TAG_Delete(old);
		}
	}

	/**
	 * refresh the Parts view
	 */
	public void refreshParts() {
		SbView v = mainFrame.getView(VIEWNAME.PARTS);
		if (v != null) {
			fireAgain(v);
		}
	}

	/**
	 * refresh the Chapters view
	 */
	public void refreshChapters() {
		SbView v = mainFrame.getView(VIEWNAME.CHAPTERS);
		if (v != null) {
			fireAgain(v);
		}
	}

	/**
	 * refresh the Scenes view
	 */
	public void refreshScenes() {
		SbView v = mainFrame.getView(VIEWNAME.SCENES);
		if (v != null) {
			fireAgain(v);
		}
	}

	/**
	 * fire all
	 */
	@Override
	public void fireAgain() {
		//LOG.trace(TT + ".fireAgain()");
		initProgress();
		new Thread(new ExecThread()).start();
	}

	public void fireAgain(SbView view) {
		//LOG.trace(TT + ".fireAgain(" + view.getName() + ")");
		if (view == null || !view.isLoaded()) {
			return;
		}
		switch (SbView.getVIEW(view.getName())) {
			case BOOK:
				fireAgainScenes();
				break;
			case CHRONO:
				fireAgainScenes();
				break;
			case MANAGE:
				fireAgainChapters();
				break;
			case READING:
				fireAgainChapters();
				break;
			case TIMELINE:
				fireAgainScenes();
				break;

			case ATTRIBUTES:
				fireAgainAttributes();
				break;
			case CATEGORIES:
				fireAgainCategories();
				break;
			case CHAPTERS:
				fireAgainChapters();
				break;
			case ENDNOTES:
				fireAgainEndnotes();
				break;
			case EPISODES:
				fireAgainEpisodes();
				break;
			case EVENTS:
				fireAgainEvents();
				break;
			case GENDERS:
				fireAgainGenders();
				break;
			case IDEAS:
				fireAgainIdeas();
				break;
			case INTERNALS:
				fireAgainInternals();
			case ITEMS:
				fireAgainItems();
				break;
			case ITEMLINKS:
				fireAgainItemlinks();
				break;
			case LOCATIONS:
				fireAgainLocations();
				break;
			case MEMOS:
				fireAgainMemos();
				break;
			case PARTS:
				fireAgainParts();
				break;
			case PERSONS:
				fireAgainPersons();
				break;
			case PLOTS:
				fireAgainPlots();
				break;
			case RELATIONS:
				fireAgainRelations();
				break;
			case SCENES:
				fireAgainScenes();
				break;
			case STRANDS:
				fireAgainStrands();
				break;
			case TAGS:
				fireAgainTags();
				break;
			case TAGLINKS:
				fireAgainTaglinks();
				break;
			default:
				break;
		}
	}

	public void fireAgainReading() {
		fireAgainParts();
		fireAgainChapters();
		fireAgainScenes();
	}

	private void fireAgainAttributes() {
		//LOG.trace(TT+".fireAgainAttributes()");
		firePropertyChange(ATTRIBUTE);
	}

	private void fireAgainCategories() {
		//LOG.trace(TT+".fireAgainCategories()");
		firePropertyChange(CATEGORY);
	}

	public void fireAgainChapters() {
		//LOG.trace(TT+".fireAgainChapters()");
		firePropertyChange(CHAPTER);
	}

	private void fireAgainEndnotes() {
		//LOG.trace(TT+".fireAgainEndnotes()");
		firePropertyChange(ENDNOTE);
	}

	private void fireAgainEpisodes() {
		//LOG.trace(TT+".fireAgainEndnotes()");
		firePropertyChange(EPISODE);
	}

	private void fireAgainEvents() {
		//LOG.trace(TT+".fireAgainEvents()");
		firePropertyChange(EVENT);
	}

	private void fireAgainGenders() {
		//LOG.trace(TT+".fireAgainGenders()");
		firePropertyChange(GENDER);
	}

	private void fireAgainIdeas() {
		//LOG.trace(TT+".fireAgainIdeas()");
		firePropertyChange(IDEA);
	}

	private void fireAgainInternals() {
		//LOG.trace(TT+".fireAgainInternals()");
		firePropertyChange(INTERNAL);
	}

	private void fireAgainItems() {
		//LOG.trace(TT+".fireAgainItems()");
		firePropertyChange(ITEM);
	}

	private void fireAgainItemlinks() {
		//LOG.trace(TT+".fireAgainItemlinks()");
		firePropertyChange(ITEMLINK);
	}

	private void fireAgainLocations() {
		//LOG.trace(TT+".fireAgainLocations()");
		firePropertyChange(LOCATION);
	}

	private void fireAgainMemos() {
		//LOG.trace(TT+".fireAgainMemos()");
		firePropertyChange(MEMO);
	}

	public void fireAgainParts() {
		//LOG.trace(TT+".fireAgainParts()");
		firePropertyChange(PART);
	}

	private void fireAgainPersons() {
		//LOG.trace(TT+".fireAgainPersons()");
		firePropertyChange(PERSON);
	}

	private void fireAgainPlots() {
		//LOG.trace(TT+".fireAgainPlots()");
		firePropertyChange(PLOT);
	}

	private void fireAgainRelations() {
		//LOG.trace(TT+".fireAgainRelations()");
		firePropertyChange(RELATION);
	}

	public void fireAgainScenes() {
		//LOG.trace(TT+".fireAgainScenes()");
		firePropertyChange(SCENE);
	}

	private void fireAgainStrands() {
		//LOG.trace(TT+".fireAgainStrands()");
		firePropertyChange(STRAND);
	}

	private void fireAgainTags() {
		//LOG.trace(TT+".fireAgainTags()");
		firePropertyChange(TAG);
	}

	private void fireAgainTaglinks() {
		//LOG.trace(TT+".fireAgainTaglinks()");
		firePropertyChange(TAGLINK);
	}

	private JFrame progressFrame;
	private JProgressBar progressBar;

	Book.TYPE toRefresh[] = {
		ATTRIBUTE,
		CATEGORY,
		CHAPTER,
		ENDNOTE,
		ENDNOTE,
		EPISODE,
		EVENT,
		GENDER,
		IDEA,
		ITEM,
		ITEMLINK,
		LOCATION,
		MEMO,
		PART,
		PERSON,
		PLOT,
		RELATION,
		SCENE,
		STRAND,
		TAG,
		TAGLINK
	};

	/**
	 * initialize the progress Frame
	 */
	private void initProgress() {
		progressFrame = new JFrame();
		progressFrame.setUndecorated(true);
		progressFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		JPanel pane = (JPanel) progressFrame.getContentPane();
		pane.setLayout(new MigLayout(MIG.WRAP));
		pane.add(new JLabel(I18N.getMsg("refresh")), MIG.CENTER);
		progressBar = new JProgressBar(0, toRefresh.length);
		progressBar.setMinimumSize(new Dimension(410, FontUtil.getHeight()));
		progressBar.setStringPainted(true);
		pane.add(progressBar, MIG.GROWX);
		progressFrame.pack();
		progressFrame.setLocationRelativeTo(mainFrame);
		progressFrame.setVisible(true);
	}

	public class ExecThread implements Runnable {

		public ExecThread() {
		}

		@Override
		public void run() {
			int i = 0, total = toRefresh.length;
			for (Book.TYPE type : toRefresh) {
				progressBar.setValue(i);
				firePropertyChange(type);
				i++;
				progressBar.setString(i + "/" + total
				   + " (" + I18N.getMsg(type.toString()) + ")");
			}
			progressFrame.dispose();
		}
	}

}
