/* synthesis_module.js
 * A Gears worker that is kicked off once and then runs continuously in the background in support of the indendex.com application.
 * It handles messages from the UI or from other workers, calling synthesis functions.
 * This module is called by meisosis, akin and (recursively) synthesis modules. 
 * It calls upon Db and synthesis modules. 
 * 
 * Note: Synthesis related functions that are run in the foreground (not by the synthesis worker) are appended to this file.
 */

function synthesis_module(wp) {
/* constructor
 */ 			
	return this.run_(wp); // kick off worker
}

synthesis_module.prototype.run_ = 	function(wp){
	/* the code to run on the worker  
	 */
	try {
		var synthScript = 
		'google.gears.workerPool.onmessage = ' + String(this.synthWorker) 		+ '; ' +
		'var module 				=  "synthesis"; ' 							+	// module global
		'var get_count_conts 		= ' +	String(this.get_count_conts) 		+';' +
		'var step_out 				= ' +	String(this.step_out) 				+';' +
		'var method_of_synthesis	= ' +	String(this.method_of_synthesis) 	+';' +
		'var invert 				= ' +	String(this.invert) 				+';' +
// jssha256 start
		'var SHA256_init 			= ' +	String(this.SHA256_init) 			+';' +
		'var SHA256_write 			= ' +	String(this.SHA256_write) 			+';' +
		'var SHA256_finalize 		= ' +	String(this.SHA256_finalize) 		+';' +
		'var SHA256_sigma0 			= ' +	String(this.SHA256_sigma0) 			+';' +
		'var SHA256_sigma1 			= ' +	String(this.SHA256_sigma1) 			+';' +
		'var SHA256_Sigma0 			= ' +	String(this.SHA256_Sigma0) 			+';' +
		'var SHA256_Sigma1 			= ' +	String(this.SHA256_Sigma1) 			+';' +
		'var SHA256_Ch 				= ' +	String(this.SHA256_Ch) 				+';' +
		'var SHA256_Maj 			= ' +	String(this.SHA256_Maj) 			+';' +
		'var SHA256_Hash_Word_Block = ' +	String(this.SHA256_Hash_Word_Block) +';' +
		'var SHA256_Hash_Byte_Block = ' +	String(this.SHA256_Hash_Byte_Block) +';' +
// jssha256 end
		'var string_to_array 		= ' +	String(this.string_to_array) 		+';' +
		'var decary_to_boolary	 	= ' + 	String(this.decary_to_boolary) 		+';' +
// module shared code in mod_lib.js
		'var lib_set_mod_state 		= ' +	String(lib_set_mod_state) 			+';'+
		'var lib_insert_binding 	= ' + 	String(lib_insert_binding) 			+';' +
		'var lib_insert_conts 		= ' + 	String(lib_insert_conts) 			+';' +
		'var lib_activate_binding 	= ' + 	String(lib_activate_binding) 		+';' +
		'var lib_caller	 			= ' + 	String(lib_caller) 					+';' +
		'var lib_debug	 			= ' + 	String(lib_debug) 					+';';
				
		var SynWorkerId = wp.createWorker(synthScript);		
		return {ID: SynWorkerId, Script: synthScript};	
	}
	catch(e){
	}
}   

synthesis_module.prototype.synthWorker = function(a, b, message) {

	var wp 					= google.gears.workerPool; 	
	var conts				= new Array;
	var modparent 			= message.sender;	// module static 
	var synthScript 		= message.body[1];	// module static 
	var wkrthrottle			= message.body[2];	// module static 

	try {

		// comment: initialise transaction state from message to a module variable
		mv = lib_set_mod_state (message, modparent, synthScript, wkrthrottle);

		// comment: reject a transaction if not meant for synth module 
		if (mv.dest.module !== 'synthesis') {
			lib_caller (mv, 'sy: invalid module name '+mv.dest.module, 'error', mv.init.parent);
			return;
		}

	// comment: handle return or ad hoc message from another module		
		lib_debug (mv.ba, mv.from.action, mv.from.status, 'synthesis: receiving instruction '+mv.dest.module+' '+mv.from.module+' '+mv.from.action+' '+mv.from.status)

	 switch (mv.from.status) {
	 	case 'exception':
			throw (mv.dest.message);
	 	break;
	 	case 'error':
			throw ('synth error' +mv.dest.message);
	 	break;
	 	case 'success':			
			lib_debug (mv.ba, mv.from.action, mv.from.status, 'synthesis. status=success');
			switch (mv.from.module) {
				case 'batch':
					lib_debug (mv.ba, mv.from.action, mv.from.status, 'synthesis. insert_binding');
					lib_insert_binding(mv); // new binding
				break;											
				case 'synthesis':
					switch (mv.from.action) {
						case 'step_out':
							lib_debug (mv.ba, mv.from.action, mv.from.status, "returning. step_out");
							step_out(mv);
						break;
						default: throw ('unknown action. ' + mv.from.action); 	break;
					}
				break;
				case 'db':
					lib_debug (mv.ba, mv.from.action, mv.from.status, 'synthesis. return from db');
					switch (mv.from.action) {
						case 'insert_binding': // comment: Db process successfully inserted a binding
							mv.sy.conts = method_of_synthesis(mv, mv.sy.mos_breadth, mv.sy.oos);
							lib_insert_conts(mv);
							lib_debug (mv.ba, mv.from.action, 'success', "requested ins conts "+mv.sy.oos);
							if (mv.sy.steps-- >= 0) {
								mv.sy.lot = 1;
								mv.sy.last_lot_conts = invert(conts);
								step_out(mv);
							}
						break;
						case 'insert_conts': // comment: Db successfully inserted one lot of conts
							lib_debug (mv.ba, mv.from.action, mv.from.status, "synthesis. insert_conts. mv.dest.ID " + mv.dest.ID + " binding " + mv.sy.binding_id + " lot " + mv.sy.lot);
							// how many contents have been inserted so far for this ID? Activate when full.
							if (get_count_conts(wp, mv, mv.sy.binding_id) === Math.pow(2, mv.sy.breadth)) {
								lib_activate_binding(mv, true); // activate the binding whose conts is now full
								lib_debug (mv.ba, mv.from.action, mv.from.status, " activated " + mv.sy.binding_id + " after lot " + mv.sy.lot);
							}
							else lib_debug (mv.ba, mv.from.action, mv.from.status, " keep on counting" );
						break;
						case 'activate_binding': // comment: Db process successfully activated a binding
							mv.dest.module = 'batch'
							lib_caller(mv, 'binding activated ' + mv.sy.binding_id, 'success', mv.ba.bawID);
							lib_debug (mv.ba, mv.from.action, mv.from.status, 'synthesis: done activated binding ' + mv.sy.binding_id + ' notify ' + mv.init.parent);
						break;								
//					default: throw ('unknown action. ' + mv.dest.action);	break;
				}
//				default: throw ('unknown module. ' + mv.from.module);			break;
			}
//			default: throw (' unknown status: '+mv.from.action + '. status: '+  mv.from.status);			
	 	};	

		// comment: handle errors from this worker
		wp.onerror = function( err ){
			lib_debug (mv.ba, mv.from.action, mv.from.status, 'synthesis. synthWorker error' +err);
		};
	}
	catch (e) { lib_debug (mv.ba, 'synthWorker', 'exception', e); }
	finally {
		delete wp; 	
		delete conts;
	}
};

synthesis_module.prototype.get_count_conts = 	function(wp, mv, binding_id) {
/* count contents of active binding. Select on ID.
 * Used after every lot insertion.  
 */
	lib_debug (mv.ba, mv.from.action, mv.from.status, 'start get_count_conts '+binding_id);
	
	var dbgcc 		= google.gears.factory.create('beta.database');
	try {
		dbgcc.open(mv.cu.name);
		var select_string = 	
		' select count(pos) '+
		' from contents c, binding ab where c.id = ? and ab.id = c.id ';										
		var rsgcc = dbgcc.execute(select_string , [binding_id]);
	lib_debug (mv.ba, mv.from.action, mv.from.status, 'done get_count_conts '+binding_id);
		return rsgcc.field(0);
	} 
	catch (e) { lib_debug (mv.ba, 'get_count_contsx', 'exception', e); }
	finally {
		rsgcc.close();	
		dbgcc.close();
	}
};

synthesis_module.prototype.step_out = function(mv){
	/* Expand a synthetic binding to a breadth in the range :
	 Mos_breadth:8:256		Mos_breadth+1:9:512 	Mos_breadth+2:10:1024  .... 	Max_breadth:16:64K
	 For step-out (expansion), use previously created lot of contents array as new input to Method of Synthesis (mos).
	 Interim results are iteratively reused according to the Pattern of Sytnthesis to make new cTag of required breadth.
	 */

	var wp 			= google.gears.workerPool; 	
	var lotlength 	= Math.pow(2, mv.sy.mos_breadth);
 	var conts 		= new Array (lotlength);
	var lot 		= mv.sy.lot;
	mv.from.action 	= 'step_out';

	try {

		while (mv.sy.steps >= 0) { // comment : process current lot
			lib_debug (mv.ba, mv.from.action, mv.from.status, "step_out. ID=" + mv.sy.binding_id + "  sywID=" + mv.sy.sywID + " step:" + mv.sy.steps + " lot:" + lot + " syw_base_lot:" +mv.sy.syw_base_lot);
			mv.sy.conts = method_of_synthesis(mv, mv.sy.mos_breadth, mv.sy.last_lot_conts);
			lib_insert_conts(mv);
			lib_debug (mv.ba, mv.from.action, 'success', "requested ins conts "+mv.sy.conts);

			if (mv.sy.steps-- >= 0) { // comment : look ahead. Is another step due ?
				// comment : after inversion of previous lot of contents, spawn or reuse a worker to process it. 
				mv.dest.module 		= 'synthesis';
				mv.from.module 		= 'synthesis';
				mv.dest.action 		= 'step_out';
				mv.from.action 		= 'step_out';
				mv.from.status 		= 'success';
				
				var nlot 				= lot * 2 + 1;
			/* Comment: throttle back new workers, to mitigate monopolisation of system resources.
			* Also, to mitigate stability problems with too large a number of simultaneous Gears workers.
			*/
			lib_debug (mv.ba, mv.from.action, 'success', "test wkrthrottle "+nlot+ ' ' +mv.sy.syw_base_lot+ ' '+mv.ba.wkrthrottle+' ' +mv.ba.synthScript);
				if (nlot  > mv.sy.syw_base_lot * Math.pow(2, mv.ba.wkrthrottle )) {
					mv.sy.sywID 		= wp.createWorker(mv.ba.synthScript);
			lib_debug (mv.ba, mv.from.action, 'success', "new synth worker "+mv.sy.sywID);

					mv.sy.syw_base_lot 	= nlot;
					// comment : kick off a new worker 
				}
				else {} // comment : come back in to this worker
				mv.sy.last_lot_conts = invert(conts);
				mv.sy.lot 			= nlot;
				wp.sendMessage([mv, mv.ba.synthScript, mv.ba.wkrthrottle], mv.sy.sywID);			
			}
			lot *= 2;
			mv.sy.last_lot_conts	= conts;
			mv.sy.lot 	 			= lot;
		}
	} 

	catch (e) { lib_debug (mv.ba, 'step_out', 'exception', e); }
	finally {
		delete wp; 	
		delete lotlength;
 		delete conts;
	}
}

synthesis_module.prototype.method_of_synthesis =  function(mv, mos_breadth, oos) {
/* SHA-256 Algorithm was selected as the initial method of synthetic conception for its properties :
 * - variable length input
 * - fixed length 256-bit output. (ties with minimum breadth of Synthetic cTag is 8)
 * - proven quality of distribution of ones and zeros in hash
 * - implemented on many different platforms including javascript
 * 
 *   This implementation of SHA-256 (jssha256) was selected for Akinity demo for its properties :
 * - Javascript
 * - GPL
 * - accuracy has purportedly been verified against a standard test set (not checked myself)
 *
 * N.B. SHA256 is a non-symmetric hash. 
 * Future versions of Akinity may specify synthetic cTags to be constructed using a symmetric hash algorithm. 
 * And publish public key(s).
 * It is not an explicit requirement that this version of Akinity cTag should present industrial-strength security. 
 *   Whether or not strong irreversibility is actually delivered by jssha256 is therefore moot. 
 */
/* jssha256 version 0.1  -  Copyright 2006 B. Poettering
 *
 *  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 2 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, write to the Free Software
 *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
 *  02111-1307 USA
 */

 try {
	var res;
	var boolary 	= [];	

// begin jssha256 
	SHA256_init();
	SHA256_write(oos);
	res = SHA256_finalize();
// end jssha256 

	boolary = decary_to_boolary(res, mos_breadth);
	return boolary 
	} 
	catch (e) { lib_debug (mv.ba, 'method_of_synthesis', 'exception', e); }
 	finally {
 		delete res;
		delete boolary;
 	}
}

synthesis_module.prototype.string_to_array =  function (str) {
// local function
  var len = str.length;
  var res = new Array(len);
  for(var i = 0; i < len; i++)
    res[i] = str.charCodeAt(i);
  return res;
}

synthesis_module.prototype.decary_to_boolary = function (decary, mos_breadth) { 
// local function
// convert input byte array to an array of 256 (pseudo) booleans
// this function needs improving
var lotlength 	= Math.pow(2, mos_breadth);
var boolarr = new Array(lotlength);
  for (var i = 0; i < decary.length; i++)   {
  		for (var j = 0; j<8; j++) 		{
  			boolarr[(i+1)*8 - (j+1)] = Math.floor(decary[i] / (Math.pow(2,j))) % 2;
		}
  }
  return boolarr;
};

synthesis_module.prototype.invert = function(origary) {
// local function
 var arylen = origary.length;
 var invary = new Array (arylen);
 var i = arylen -1;
 
	while (i >= 0) {
		invary[i] = !origary[i];
		i--;
	}
	return invary;
}

synthesis_module.prototype.SHA256_init =  function() {
	SHA256_H = new Array(0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a, 0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19);
	SHA256_buf = new Array();
	SHA256_len = 0;
}

synthesis_module.prototype.SHA256_write = function (msg) {
  if (typeof(msg) == "string")
    SHA256_buf = SHA256_buf.concat(string_to_array(msg));
  else
    SHA256_buf = SHA256_buf.concat(msg);
  for(var i = 0; i + 64 <= SHA256_buf.length; i += 64)
    SHA256_Hash_Byte_Block(SHA256_H, SHA256_buf.slice(i, i + 64));
  SHA256_buf = SHA256_buf.slice(i);
  SHA256_len += msg.length;
}

synthesis_module.prototype.SHA256_finalize =  function () {
  SHA256_buf[SHA256_buf.length] = 0x80;

  if (SHA256_buf.length > 64 - 8) {
    for(var i = SHA256_buf.length; i < 64; i++)
      SHA256_buf[i] = 0;
    SHA256_Hash_Byte_Block(SHA256_H, SHA256_buf);
    SHA256_buf.length = 0;
  }

  for(var i = SHA256_buf.length; i < 64 - 5; i++)
    SHA256_buf[i] = 0;
  SHA256_buf[59] = (SHA256_len >>> 29) & 0xff;
  SHA256_buf[60] = (SHA256_len >>> 21) & 0xff;
  SHA256_buf[61] = (SHA256_len >>> 13) & 0xff;
  SHA256_buf[62] = (SHA256_len >>> 5) & 0xff;
  SHA256_buf[63] = (SHA256_len << 3) & 0xff;
  SHA256_Hash_Byte_Block(SHA256_H, SHA256_buf);

  var res = new Array(32);
  for(var i = 0; i < 8; i++) {
    res[4 * i + 0] = SHA256_H[i] >>> 24;
    res[4 * i + 1] = (SHA256_H[i] >> 16) & 0xff;
    res[4 * i + 2] = (SHA256_H[i] >> 8) & 0xff;
    res[4 * i + 3] = SHA256_H[i] & 0xff;
  }

  delete SHA256_H;
  delete SHA256_buf;
  delete SHA256_len;
  return res;
}

synthesis_module.prototype.SHA256_sigma0 = function (x) {
  return ((x >>> 7) | (x << 25)) ^ ((x >>> 18) | (x << 14)) ^ (x >>> 3);
}

synthesis_module.prototype.SHA256_sigma1 = function (x) {
  return ((x >>> 17) | (x << 15)) ^ ((x >>> 19) | (x << 13)) ^ (x >>> 10);
}

synthesis_module.prototype.SHA256_Sigma0 = function (x) {
  return ((x >>> 2) | (x << 30)) ^ ((x >>> 13) | (x << 19)) ^ 
    ((x >>> 22) | (x << 10));
}

synthesis_module.prototype.SHA256_Sigma1 = function (x) {
  return ((x >>> 6) | (x << 26)) ^ ((x >>> 11) | (x << 21)) ^ 
    ((x >>> 25) | (x << 7));
}

synthesis_module.prototype.SHA256_Ch = function (x, y, z) {
  return z ^ (x & (y ^ z));
}

synthesis_module.prototype.SHA256_Maj = function (x, y, z) {
  return (x & y) ^ (z & (x ^ y));
}

synthesis_module.prototype.SHA256_Hash_Word_Block = function (H, W) {
SHA256_K = new Array(
  0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 
  0x923f82a4, 0xab1c5ed5, 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 
  0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174, 0xe49b69c1, 0xefbe4786, 
  0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da, 
  0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 
  0x06ca6351, 0x14292967, 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 
  0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85, 0xa2bfe8a1, 0xa81a664b, 
  0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070, 
  0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 
  0x5b9cca4f, 0x682e6ff3, 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 
  0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2 
);
  for(var i = 16; i < 64; i++)
    W[i] = (SHA256_sigma1(W[i - 2]) +  W[i - 7] + 
      SHA256_sigma0(W[i - 15]) + W[i - 16]) & 0xffffffff;
  var state = new Array().concat(H);
  for(var i = 0; i < 64; i++) {
    var T1 = state[7] + SHA256_Sigma1(state[4]) + 
      SHA256_Ch(state[4], state[5], state[6]) + SHA256_K[i] + W[i];
    var T2 = SHA256_Sigma0(state[0]) + SHA256_Maj(state[0], state[1], state[2]);
    state.pop();
    state.unshift((T1 + T2) & 0xffffffff);
    state[4] = (state[4] + T1) & 0xffffffff;
  }
  for(var i = 0; i < 8; i++)
    H[i] = (H[i] + state[i]) & 0xffffffff;
}

synthesis_module.prototype.SHA256_Hash_Byte_Block = function (H, w) {
  var W = new Array(16);
  for(var i = 0; i < 16; i++)
    W[i] = w[4 * i + 0] << 24 | w[4 * i + 1] << 16 | 
      w[4 * i + 2] << 8 | w[4 * i + 3];
  SHA256_Hash_Word_Block(H, W);
}

/* Foreground functions
 * 
 */
	function synthesis(culture, organelle, breadth){
		var create_dt 		= new Date().getTime();
		var bi 	= [];
		var seq = 0;
		bi[seq]	= set_syn(seq+1, organelle, organelle, breadth); 
		var batch = set_batch (true, true, bi[seq]);
		do_batch(batch);
	}

/* Drop it. Use batch module for all syn / mei
 * 
 
	function synthesis(culture, organelle, breadth){
		var create_dt 		= new Date().getTime();
		var mos_breadth	= +document.getElementById("ConcMosBreadth").value;	// Akinity v.0.1 specifies Method of synthesis is SHA-256. Therefore mos_breadth=8
		var maxBreadth 	= +document.getElementById("ConcMaxBreadth").value;	// Akinity v.0.1 specified
		var Z_breadth 	= +breadth || +document.getElementById("ConcDefBreadth").value||11;
		
		var action;
		var status;
		var oos 			= organelle ;

		try {
			// validation
			if (organelle === "") throw ('Text entry field is null');			
			if (clean_text(organelle) !== true) throw ('Invalid character');
			if (Z_breadth > maxBreadth) throw ("Breadth-step error: target breadth "+ Z_breadth +" - exceeds limit "+ maxBreadth);
			if (Z_breadth < mos_breadth) throw ("definedBreadth-step error: Invalid target breadth - too low");

			// comment: fire up the main synth worker (if needed) and store its ID in a global Text field
	        if(synworker === 0) synworker = new synthesis_module(wp);
	
			var mv = init_mv();	// create module variable structue and default values
			mv.sy.oos =oos;
			mv.sy.description =oos;
			mv.sy.create_dt = create_dt;
			mv.dest = {
				module: 'synthesis',
				action: 'initiate',
//				status: 'success',
				message: null,
				ID: synworker.ID }
			mv.cu.name = culture;
			trx_caller (wp, mv, mv.dest.ID, synworker.Script, wkrthrottle, mv.dest.ID);
		}
		catch(e){
			alert ("synthesis error: " +e)	;
		}
	}
 


*/

