package is.karlmenn.feedr.util;

import is.karlmenn.feedr.data.Transaction;

import com.webobjects.eocontrol.*;
import com.webobjects.foundation.*;

import er.extensions.eof.*;

/**
 * Writes information about EOF transactions to a log/table, creating an audit trail.
 * 
 * @author Hugi Þórðarson
 */

public class Spy {

	/**
	 * Codes for the basic actions that can be performed on EOs (written to the log).
	 */
	private static final String ACTION_INSERT = "I";
	private static final String ACTION_UPDATE = "U";
	private static final String ACTION_DELETE = "D";

	/**
	 * A key set in the userInfo() of EOEditingContexts 
	 * where transactions should not be logged.
	 */
	private static final String DO_NOT_LOG = "DO_NOT_LOG_TRANSACTIONS_IN_THIS_EC";

	/**
	 * A key set in the userInfo() of EOEditingContexts
	 * to mark what user is making the modifications.
	 */
	private static final String USERNAME_KEY = "HI_THERE_I_AM_THE_USERNAME";

	/**
	 * Logging happens in this EC.
	 */
	private EOEditingContext _loggingEC;

	/**
	 * Since this is currently a singleton, we need a variable to hold that instance.
	 */
	private static Spy _defaultSpy;

	/**
	 * It's a singleton.
	 * 
	 * We set the logging editing context so that transactions in it are not logged
	 * (logging transactions in the loggingEC would result in a rather embarrassing loop).
	 */
	private Spy() {
		_loggingEC = ERXEC.newEditingContext();
		_loggingEC.setUserInfoForKey( true, DO_NOT_LOG );
	}

	/**
	 * Creates our default transaction manager. 
	 */
	public static Spy defaultSpy() {
		if( _defaultSpy == null ) {
			_defaultSpy = new Spy();
		}

		return _defaultSpy;
	}

	/**
	 * This method is invoked each time changes are saved in *any* EC in the application.
	 * 
	 * Logs all transactions (inserts, updates and deletes).
	 */
	public void handleSaveChangesInEditingContext( NSNotification notification ) {
		EOEditingContext ec = (EOEditingContext)notification.object();
		Object shouldNotLog = ec.userInfoForKey( DO_NOT_LOG );

		if( shouldNotLog == null ) {

			// Inserted objects
			for( EOEnterpriseObject eo : ec.insertedObjects() ) {
				createAndInsertTransactionForEO( ACTION_INSERT, eo );
			}

			// Updated objects		
			for( EOEnterpriseObject eo : ec.updatedObjects() ) {
				createAndInsertTransactionForEO( ACTION_UPDATE, eo );
			}

			// Deleted objects
			for( EOEnterpriseObject eo : ec.deletedObjects() ) {
				createAndInsertTransactionForEO( ACTION_DELETE, eo );
			}

			_loggingEC.saveChanges();
		}
	}

	/**
	 * Creates a single transaction record.
	 * 
	 * @param action Denotes the type of action ("I" for insert, "U" for update, "D" for deletion).
	 * @param eo the EOEnterpriseObject the action is performed on.
	 */
	private Transaction createAndInsertTransactionForEO( String action, EOEnterpriseObject eo ) {
		Transaction t = new Transaction();
		_loggingEC.insertObject( t );
		t.setDate( new NSTimestamp() );
		t.setUserName( eo.editingContext().userInfoForKey( USERNAME_KEY ) );
		t.setBefore( NSPropertyListSerialization.stringFromPropertyList( ((ERXGenericRecord)eo).committedSnapshot() ) );
		t.setAfter( NSPropertyListSerialization.stringFromPropertyList( ((ERXGenericRecord)eo).changesFromCommittedSnapshot() ) );
		t.setAction( action );
		t.setEntityNameString( eo.entityName() );
		t.setObjectID( new Integer( ((ERXGenericRecord)eo).primaryKey() ) );
		return t;
	}

	/**
	 * Registers the transaction manager so it starts listening and watching transactions.
	 */
	public static void register() {
		NSSelector<Spy> saveSelector = new NSSelector<Spy>( "handleSaveChangesInEditingContext", new Class[] { NSNotification.class } );
		NSNotificationCenter.defaultCenter().addObserver( defaultSpy(), saveSelector, ERXEC.EditingContextWillSaveChangesNotification, null );
	}
}