// The primary function is computeDelta(). It returns a Delta Entry
// based on the entry you pass in (typically the Work Entry).
//
// How To Use:
// -----------
// To use, simply have your AL load this Script as a Global Prolog. 
// then create a Script component in your AL at a suitable spot
// where you drive the contents of work (or some other Entry)
// through delta computation:
//
//      var deltaEntry = deltaEngine.computeDelta(work, "cdm:Signature");
//
// The computeDelta() method supports several optional parameters as well:
//
//      deltaEngine.computeDelta(entry, keyAttribute, autoCommit, resetSnapshots, deltaTableName)  
//
// So, if you also wanted to erase all snapshots in the table called "MY_DELTA", 
// and have to manually commit snapshots by calling deltaEngine.commit():
//
//      var deltaEntry = deltaEngine.computeDelta(work, "cdm:Signature", false, true, "MY_DELTA");
// 
// As a JSON object, this library only occupies a single variable in your
// script context, which is nice when you use the Debugger's Watch List feature.
//
// The parameters to computeDelta are:
//     entry       - The Entry object (e.g. work) that you want to compute
//                         changes for based on its snapshot from the previous run.
//                         Mandatory. 
//     keyAttribute   - Name of the Attribute in the entry that 
//                         uniquely identifies this record (e.g. uid).
//                         Note that if no individual Attribute value is 
//                         unique in your input data, then you should create 
//                         one that is by combining several.
//                         Mandatory. This must be the name of an Attribute in the
//                         entry passed as the first parameter.
//     autoCommit     - If true then each new snapshot is committed immediately
//                         after it has been returned by the computeDelta() call.
//                         A value of false means that you will script this call
//
//                             deltaEngine.commit(); 
//
//                         when you are ready to acknowledge that a change
//                         has been successfully handled (e.g. another SC).
//                         Optional. Autocommit is true by default.
//     resetSnapshots - if true will cause snapshot db to be re-created during 
//                        the first computeDelta() call.
//                         Optional. You us a value of true for this argument if you
//                         want to delete all snapshots and create a new delta
//                         baseline. The Snapshot table is automatically created
//                         the first time you call computeDelta(), regardless of
//                         the value sent for this parameter.
//     deltaTableName - Unique name for this snapshot db table for this AL.
//                         Optional. A default value based on AL and Component names
//
var deltaEngine = {
  debug : false, // for debug output
  
  version : "2.1",
  
  deltaConn : null,
  linkCrit : null,
  snapshotEntry : null,
  newSnapshotEntry : null,
  deltaEntry : null,
  deltaTableName : null,
  keyAttributeName : null,
  resetSnapshots : null,
  prevDeltaTableNames : null,
  
   
  // Here is the main function. You only have to send the first two parameters.
  // All the rest have default values, as described in the comments at the start
  // of this script.
  //
  computeDelta : function(entry, keyAttribute, autoCommit, resetSnapshots, deltaTableName) {
   
    if (typeof(autoCommit) != "undefined" && !autoCommit)
       autoCommit = false
    else
       autoCommit = true;
		
    if (typeof(resetSnapshots) == "undefined" || !resetSnapshots)
	resetSnapshots = false
    else
        resetSnapshots = true;

    this.keyAttributeName = keyAttribute;
    this.autoCommit = autoCommit;
    this.resetSnapshots = resetSnapshots;
 
    if (typeof(deltaTableName) == "undefined" || !deltaTableName) {
      var alName = task.getShortName();
      var compName = thisConnector.getName();
      deltaTableName = "TDI_" + alName.substring(0,Math.min(alName.length, 5)) 
                              + "_" + compName.substring(0,Math.min(compName.length, 5))
    }
   
    this.deltaTableName = deltaTableName;

    if (!this.deltaConn) {
	this.initializeConnector(this.deltaTableName); 
    }    

    if(this.prevDeltaTableNames == null){
	this.prevDeltaTableNames = new java.util.ArrayList();
    }	

    if(!this.prevDeltaTableNames.contains(this.deltaTableName)) {
	this.createDeltaTable(this.resetSnapshots);	
	this.prevDeltaTableNames.add(this.deltaTableName);
    }    


    this.linkCrit = this.makeLinkCrit(entry, keyAttribute);
    this.snapshotEntry = this.deltaConn.findEntry(this.linkCrit);
  
    if (this.snapshotEntry)
      this.snapshotEntry = com.ibm.di.store.StoreFactory.deserializeObject(this.snapshotEntry.getObject("Entry"));
  
    if (this.debug) {
	task.logmsg("");
        task.logmsg("@@Old snapshot: " + this.snapshotEntry);
    }
  
    var uniqueKey = entry.getString(keyAttribute);
    if (!uniqueKey) {
       task.logmsg("**ERROR - unique key (" + keyAttribute + ") not found in entry");
       task.dumpEntry(entry)
       return null;
    }
    
    if (!this.snapshotEntry) {
      this.deltaEntry = system.newEntry();
      this.deltaEntry.merge(entry);
      this.deltaEntry.setOperation("add");
    } else {
      // Else compute the delta
      this.deltaEntry = com.ibm.di.entry.DeltaEntry.getDeltaEntry(entry, this.snapshotEntry);
    }

    if (this.deltaEntry.size() > 0 && this.deltaEntry.getOperation() != "generic") {
       // Save the new snapshot
       if (this.debug)
	  task.logmsg("@@ saving snapshot...");
       this.newSnapshotEntry = system.newEntry();
       this.newSnapshotEntry.setAttribute("ID", entry.getString(keyAttribute));
       this.newSnapshotEntry.setAttribute("Entry", com.ibm.di.store.StoreFactory.serializeObject(entry));
  
       if (this.deltaEntry.getOperation() == "modify") { // snapshot exists already
          if (this.debug)
	     task.logmsg("@@ modifying snapshot: " + this.newSnapshotEntry);
          this.deltaConn.modEntry(this.newSnapshotEntry, this.linkCrit)
       } else if (this.deltatEntry.getOperation() == "add") { // new snapshot
          if (this.debug)
	     task.logmsg("@@ adding new snapshot: " + this.newSnapshotEntry);
          this.deltaConn.putEntry(this.newSnapshotEntry);
       }
    }
  
    // And commit the change
    if (this.autoCommit == true)
       this.commit();

    if (this.debug)
       task.logmsg("@@finished");
  
    return this.deltaEntry;
  },
  
  makeLinkCrit : function(entry, keyAttribute) {
    var linkCrit = new com.ibm.di.server.SearchCriteria();
    linkCrit.addCriteria("ID", 
                          com.ibm.di.server.SearchCriteria.EXACT, 
                          entry.getString(keyAttribute));
    return linkCrit;
  },
  
  commit : function() {
    if (this.deltaConn)	{
      if (this.debug)
        task.logmsg("@@Commiting snapshot changes...");
      this.deltaConn.commit();
    }
  },

  initializeConnector : function(deltaTableName) {
    // Now see if there is a JDBC Connector already set up
    this.deltaConn = system.loadConnector("ibmdi.JDBC");
    this.deltaConn.setParam("jdbcTable", deltaTableName);
    this.deltaConn.setParam("jdbcCommit","Manual");
    this.setParam(this.deltaConn, "jdbcSource", "com.ibm.di.store.database");
    this.setParam(this.deltaConn, "jdbcDriver", "com.ibm.di.store.jdbc.driver");
    this.setParam(this.deltaConn, "jdbcLogin", "com.ibm.di.store.jdbc.user");
    this.setParam(this.deltaConn, "jdbcPassword", "com.ibm.di.store.jdbc.password");
    this.deltaConn.initialize(null);   
    return this.deltaConn;
  },
  
  // Sets the specified parameter of the connector using
  // the named property value, throwing an exception if
  // this property is not defined.
  //
  setParam : function(connector, paramName, propName) {
    var propVal = system.getTDIProperty(propName);
  
    if (this.debug)
      task.logmsg("@@--> setParam(" + paramName + ", " + propName + "='" + propVal + "')");
  
    if (propVal == null)
      throw "**ERROR - required property not defined: " + propName;
  
    connector.setParam(paramName, propVal);
  },


  deleteSnapshots : function() {
    var sql = "DROP TABLE " + this.deltaTableName;
    
    if (this.debug)
       task.logmsg("@@Deleting table: " + sql);
        
    res = this.deltaConn.execSQL(sql);
      
    if (res)
      task.logmsg("** Error deleting Delta Table: " + res);
  },

  createDeltaTable : function(resetSnapshots) {
    var sql = "DROP TABLE " + this.deltaTableName;
    
    if (resetSnapshots)
      this.deleteSnapshots();
      
	  sql = system.getTDIProperty("com.ibm.di.store.create.delta.store");
    sql = sql.replaceAll(com.ibm.di.store.StoreFactory.REGEX, com.ibm.di.store.StoreFactory.VARCHAR_LENGTH);
    sql = sql.replaceAll("\\{0\\}", this.deltaTableName);
    sql = sql.replaceAll("\\{UNIQUE\\}|\\{unique\\}", java.lang.Long.toHexString(java.lang.System.currentTimeMillis()));
  
    var parts = system.splitString(sql, ";");
  
    if (this.debug)
      task.logmsg("@@Creating table: " + parts[0] + "\n");
  
    res = this.deltaConn.execSQL(parts[0]);
  
    if (!res && parts[1]) {
      if (this.debug)
        task.logmsg("@@Adding index: " + parts[1] + "\n");
  
      res = this.deltaConn.execSQL(parts[1]);
    }
  }

}

