/*
 * Copyright: (c) TBEE.ORG
 * Version:   $Revision: 1.10 $
 * Modified:  $Date: 2007/08/08 15:10:25 $
 * By:        $Author: toeukpap $
 */
package nl.reinders.bm;

import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeSupport;
import java.beans.PropertyVetoException;
import java.beans.VetoableChangeListener;
import java.beans.VetoableChangeSupport;
import java.util.GregorianCalendar;
import java.util.List;

import javax.persistence.MappedSuperclass;
import javax.persistence.PostLoad;
import javax.persistence.PostPersist;
import javax.persistence.PostUpdate;
import javax.persistence.PrePersist;
import javax.persistence.PreUpdate;
import javax.persistence.Transient;

import org.tbee.util.PropertyChangeProvider;
import org.tbee.util.ThreadUtil;
import org.tbee.util.jpa.EntityManagerChangeCount;

@MappedSuperclass
abstract public class AbstractBean
implements PropertyChangeProvider
{
	
	// ===============================================================================================
	// PropertyChange

	/** PropertyChange */
	public PropertyChangeListener[] getPropertyChangeListeners() { if (iPropertyChangeSupport == null) return new PropertyChangeListener[]{}; return iPropertyChangeSupport.getPropertyChangeListeners(); } 
	synchronized public void addPropertyChangeListener(PropertyChangeListener o) { if (iPropertyChangeSupport == null) iPropertyChangeSupport = new PropertyChangeSupport(this); iPropertyChangeSupport.addPropertyChangeListener(o); }
	public void removePropertyChangeListener(PropertyChangeListener o) { if (iPropertyChangeSupport == null) return; iPropertyChangeSupport.removePropertyChangeListener(o); }
	public PropertyChangeListener[] getPropertyChangeListeners(String propertyName) { if (iPropertyChangeSupport == null) return new PropertyChangeListener[]{}; return iPropertyChangeSupport.getPropertyChangeListeners(propertyName); } 
	synchronized public void addPropertyChangeListener(String propertyName, PropertyChangeListener o) { if (iPropertyChangeSupport == null) iPropertyChangeSupport = new PropertyChangeSupport(this); iPropertyChangeSupport.addPropertyChangeListener(propertyName, o); }
	public void removePropertyChangeListener(String propertyName, PropertyChangeListener o) { if (iPropertyChangeSupport == null) return; iPropertyChangeSupport.removePropertyChangeListener(propertyName, o); }
	public void firePropertyChange(String name, Object before, Object after)
	{ 
		// do the property change
		if (iPropertyChangeSupport != null) iPropertyChangeSupport.firePropertyChange(name, before, after);
		
		// if propery really changed, update the dwh fields and mark as changed 
		if (name != PENDING_CHANGES_PROPERTY_ID && !"dwhmodified".equalsIgnoreCase(name) && !"dwhby".equalsIgnoreCase(name))
		{
			// We could use the PCS to determine if a property was actually changed,
			// but that would always create a PCS collection and a listener for each bean.
			// This is more effictient. 
			if ( (before == null && after == null) || (before != null && after != null && before.equals(after)) ) return;
			
			// property was changed
			setPendingChanges(true); // mark this instance as having pending changes
			setDwhmodified(new GregorianCalendar()); // update the modified date for the datawarehouse
			Integer lUserId = getGlobalDwhby(); // get the current logged in user (this may have to change if the BM is used differently in a user-per-thread setup)
			if (lUserId != null) setDwhby( lUserId ); // update who made this change for the datawarehouse
		}
	}
	@Transient transient private PropertyChangeSupport iPropertyChangeSupport = null;


	// ===============================================================================================
	// VetoableChange

	/** VetoableChange */
	synchronized public void addVetoableChangeListener(VetoableChangeListener o) { if (iVetoableChangeSupport == null) iVetoableChangeSupport = new VetoableChangeSupport(this); iVetoableChangeSupport.addVetoableChangeListener(o); }
	public void removeVetoableChangeListener(VetoableChangeListener o) { if (iVetoableChangeSupport == null) return; iVetoableChangeSupport.removeVetoableChangeListener(o); }
	public void fireVetoableChange(String name, Object before, Object after)
	throws PropertyVetoException
	{ 
		if (iVetoableChangeSupport == null) return;
		iVetoableChangeSupport.fireVetoableChange(name, before, after);
	}
	@Transient transient private VetoableChangeSupport iVetoableChangeSupport = null;

	
	// ===============================================================================================
	// PendingChanges

	/** PendingChanges */
	public boolean hasPendingChanges() { return iPendingChanges; }
	public void setPendingChanges(boolean value)
	{		
		boolean lValue = iPendingChanges;
		iPendingChanges = value;
		firePropertyChange(PENDING_CHANGES_PROPERTY_ID, lValue, value);
		
		// status changes, correct the ChangeCounter
		if (value != lValue)
		{
			if (value) EntityManagerChangeCount.incrementChangeCount();
			else EntityManagerChangeCount.decreaseChangeCount();
		}
	}
	@Transient transient private boolean iPendingChanges = false;
	final static public String PENDING_CHANGES_PROPERTY_ID = "PendingChanges";
	final static public Class PENDING_CHANGES_PROPERTY_CLASS = Integer.class;
    
	
	// ===============================================================================================
	// Datawarehouse

	// placeholders, should be overriden but this is not forced to allow none DWH entities
	//abstract public void setDwhmodified(java.util.Calendar value);
	public void setDwhmodified(java.util.Calendar value) {}
	public void setDwhby(Integer value) {}

	// GlobalDwhby 
	static public Integer getGlobalDwhby() 
	{
		Integer lDwhby = null;
		if (lDwhby == null) lDwhby = (Integer)ThreadUtil.get(Thread.currentThread(), LOGGED_IN_USER_NR);
		if (lDwhby == null) lDwhby = (Integer)ThreadUtil.get(Thread.currentThread().getThreadGroup(), LOGGED_IN_USER_NR);
		if (lDwhby == null) lDwhby = cGlobalDwhby;
		return lDwhby;
	}
	static public void setGlobalDwhby(Integer id) { cGlobalDwhby = id; }
	@Transient transient static Integer cGlobalDwhby = null;
	static public void setGlobalDwhby(Integer id, Thread t) { ThreadUtil.store(t, LOGGED_IN_USER_NR, id); }
	static public void setGlobalDwhby(Integer id, ThreadGroup t) { ThreadUtil.store(t, LOGGED_IN_USER_NR, id); }
	@Transient transient final static private String LOGGED_IN_USER_NR = "LOGGED_IN_USER_NR";

	// ===============================================================================================
	// events
	
	/* 
	 * Reset the pending changes flag upon loading from or updating the database.
	 * - it resets when loading after the constructor, so setting defaults does not leave the flag to TRUE
	 * - however, creating a new object instead of loading still requires this call
	 * - it resets after the object is updated in the database
	 */
	
	@PostLoad
	public void postLoad()
	{
		setPendingChanges(false);
	}
	
	@PrePersist @PreUpdate
	public void preSave()
	{
		if (cGlobalDwhby == null) throw new IllegalStateException("Cannot set Dwhby, because GlobalDwhby is not set.");
	}
	
	@PostPersist @PostUpdate
	public void postSave()
	{
		// for the DataChangeListener: add this class as having changed 
		EntityManagerChangeCount.addDataChange(this.getClass().getSimpleName()); 
		setPendingChanges(false);
	}
	
	// placeholders, should be overriden but this is not forced to allow none DWH entities
	//abstract public Integer getLazylock();
	//abstract public void setLazylock(Integer x);
	public Integer getLazylock() { return null; }
	public void setLazylock(Integer x) {}
	

	// ===============================================================================================
	// progress
	// For business processes implemented in the BM: a callback setup to update the GUI.
	
	public interface ProgressListener
	{
		public void init(int min, int max);
		public void progress(int current);
		public void done();
	}
	static public abstract class ProgressListenerDefault implements ProgressListener
	{
		public void init(int min, int max) {}
		abstract public void progress(int current);
		public void done() {}
	}


	// ===============================================================================================
	// convenience
	
	/**
	 * Small utility method to add null to a list (for comboboxes, etc)
	 */
	static public List addNull(List list)
	{
		list.add(null);
		return list;
	}
}
