Source: protocol.js

/**Json protocol helper to support jclient.
 * All JBody and JHelper static helpers are here. */
class Protocol {
	static opts(options) {
		if (options) {
			if (options.noNull !== undefined)
				Protocol.valOptions.noNull = options.noNull === true || options.noNull === 'true';
			if (options.noBoolean !== undefined)
				Protocol.valOptions.noBoolean = options.noBoolean === true || options.noBoolean === 'true';
		}
	}

	/**Format login request message.
	 * @param {string} uid
	 * @param {string} tk64
	 * @param {string} iv64
	 * @return login request message
	 */
	static formatSessionLogin (uid, tk64, iv64) {
		var body = new SessionReq(uid, tk64, iv64);
		body.a = 'login';
		return new JMessage(Protocol.Port.session, null, body);
	}

	/**Format a query request object, including all information for construct a "select" statement.
	 * @param  {string} tabl  from table
	 * @param  {string} alias
	 * @return {object} fromatter to build request object
	static formatQueryReq (tbl, alias) {
		return new QueryReq(this, tbl, alias);
	}
	 */

	static formatHeader (ssInf) {
		return new JHeader(ssInf.ssid, ssInf.uid);
	}

	static rs2arr (rs) {
		var cols = [];
		var rows = [];
		rs.forEach((r, rx) => {
			if (rx === 0) {
				cols = r;
			}
			else {
				rw = {};
				r.forEach((c, cx) => {
					rw[cols[cx]] = c;
				})
				rows.push(rw);
			}
		});

		return rows;
	}

	static nv2cell (nv) {
		return [nv.name, nv.value];
	}

	static nvs2row (nvs) {
		var row = [];
		if (nvs) {
			for (var ix = 0; ix < nvs.length; ix++)
				row.push(this.nv2cell(nvs[ix]));
		}
		return row;
	}

	/** convert [{name, value}, ...] to [n1, n2, ...]
	 * @param {Array} [n-v, ...]
	 * @return {Array} [n1, n2, ...]
	 */
	static nvs2cols(nvArr) {
		var cols = [];
		if (nvArr) {
			for (var ix = 0; ix < nvArr.length; ix++) {
				if (nvArr[ix].name) {
					cols.push(nvArr[ix].name);
				}
				else
					cols.push(ix);
			}
		}
		return cols;
	}

	/** convert [[{name, value}]] to [[[name, value]]]
	 * @param {Array} 2d array of n-v pairs
	 * @return {Array} 3d array that can be used by server as nv rows
	 */
	static nvs2rows(nvs) {
		var rows = [];
		if(nvs) {
			for (var ix = 0; ix < nvs.length; ix++)
				// Ix.nvn = 0; Ix.nvv = 1
				// rows.push([nvs[ix].name, nvs[ix].value]);
				rows.push(this.nvs2row(nvs[ix]));
		}
		return rows;
	}
} ;

/**Static methods of Protocol */
{
	Protocol.CRUD = {c: 'I', r: 'R', u: 'U', d: 'D'};

	Protocol.Port = {	heartbeat: "ping.serv", echo: "echo.serv", session: "login.serv",
						query: "r.serv", update: "u.serv",
						insert: "c.serv", delete: "d.serv",
						dataset: "ds.serv", stree: "s-tree.serv" };

	Protocol.MsgCode = {ok: "ok", exSession: "exSession", exSemantic: "exSemantic",
						exIo: "exIo", exTransct: "exTransct", exDA: "exDA",
						exGeneral: "exGeneral"};

	Protocol.cfg  = {	ssInfo: "ss-k", };

	Protocol.valOptions = {};
}

/** Regex helper */
class Jregex  {
	/**Add single quotes to str, if not yet.
	 * @param {string} str
	 * @return {string} quoted */
	static quote(str) {
		if (str === undefined || str === null )
			str = "''";
		else if (typeof str === "string" && str.substring(0, 1) !== "'"
			&& str.substring(0, 2) != "''")
			return "'" + str + "'";
	}

	static isblank(s) {
		return s === undefined || s === null
			|| (typeof s === 'string' && s.trim() === '');
	}
}

class JMessage {
	constructor (port, header, body) {
		this.version = "1.0";
		this.seq = Math.round(Math.random() * 1000);

		// string options, like no-null: true for asking server replace null with ''.
		this.opts = Protocol.valOptions;

		/**Protocol.Port property name, use this name to get port url */
		this.port = port; // for robustness?
		var prts = Protocol.Port;
		var msg = this;
		Object.getOwnPropertyNames(prts).forEach(function(val, idx, array) {
			if (prts[val] === port) {
				// console.log(val + ' -> ' + obj[val]);
				msg.port = val;
				return false;
			}
		});

		if (header)
			this.header = header;
		else this.header = {};

		this.body = [];
		// this.body.push(body.parentMsg(this));
		this.body.push(body);
	}

	/** A short cut for body[0].post()
	 * @param {UpdateReq} pst post sql request
	 * @return {UpdateReq} the  first request body[0].post returned value.*/
	post(pst) {
		if (this.body !== undefined && this.body.length > 0)
			return this.body[0].post(pst);
	}
}

class JHeader {
	constructor (ssid, userId) {
		this.ssid = ssid;
		this.uid = userId;
	}

	/**Set user action (for DB log on DB transaction)
	 * @param {object} act {funcId, remarks, cate, cmd}
	 */
	userAct (act) {
		this.usrAct = [];
		this.usrAct.push(act.funcId);
		this.usrAct.push(act.cate);
		this.usrAct.push(act.cmd);
		this.usrAct.push(act.remarks);
	}
}

class UserReq {
	constructor (conn, tabl) {
		this.conn = conn;
		this.tabl = tabl
		this.data = {};
	}

	get(prop) {
		return this.data[prop];
	}

	set(prop, v) {
		this.data[prop] = v;
		return this;
	}

	/**set a.<br>
	 * a() can only been called once.
	 * @param {string} a
	 * @return {UserReq} this */
	a(a) {
		this.a = a;
		return this;
	}
}

class SessionReq {
	constructor (uid, token, iv) {
		this.uid = uid;
		this.token = token;
		this.iv = iv;
	}
}

class QueryReq {
	constructor (conn, tabl, alias, pageInf) {
		this.conn = conn;
		// this.query = query;
		this.mtabl = tabl;
		this.mAlias = alias;
		this.exprs = [];
		this.joins = [];
		this.where = [];

		if (pageInf)
			this.page(pageInf.size, pageInf.page);
	}

	page (size, idx) {
		this.page = idx;
		this.pgSize = size;
		return this;
	}

	/**add joins to the query request object
	 * @param {string} jt join type, example: "j" for inner join, "l" for left outer join
	 * @param {string} t table, example: "a_roles"
	 * @param {string} a alias, example: "r"
	 * @param {string} on on conditions, example: "r.roleId = mtbl.roleId and mtbl.flag={@varName}"
	 * Variables are replaced at client side (by js)
	 * @return this query object
	 */
	join (jt, t, a, on) {
		// parse "j:tbl:alias [conds]"
		this.joins.push([jt, t, a, on]);
		return this;
	}

	j (tbl, alias, conds) {
		return this.join("j", tbl, alias, conds);
	}

	l (tbl, alias, conds) {
		return this.join("l", tbl, alias, conds);
	}

	r (tbl, alias, conds) {
		return this.join("r", tbl, alias, conds);
	}

	joinss (js) {
		if (js !== undefined && js.length !== undefined)
			for (var ix = 0; ix < js.length; ix++)
				this.join(js[ix].t, js[ix].tabl, js[ix].as, js[ix].on);
		return this;
	}

	expr (exp, as) {
		//this.exprs.push({expr: exp, as: as});
		this.exprs.push([exp, as]);
		return this;
	}

	exprss (exps) {
		if (exps !== undefined && exps.length !== undefined)
			for (var ix = 0; ix < exps.length; ix++)
				if (exps[ix].exp !== undefined)
					this.expr(exps[ix].exp, exps[ix].as);
				else if (exps[ix].length !== undefined)
					this.expr(exps[ix][0], exps[ix][1]);
				else {
					console.error('Can not parse expr:');
					console.error(exps[ix]);
					console.error('Correct Format:')
					console.error('[exp, as]');
				}
		return this;
	}

	whereCond (logic, loper, roper) {
		if (Array.isArray(logic))
			this.where = this.where.concat(logic);
		else if (logic !== undefined)
			this.where.push([logic, loper, roper]);
		return this;
	}

	orderby (tabl, col, asc) {
		if (this.orders === undefined)
			this.orders = [];
		this.orders.push([tabl, col, asc]);
		return this;
	}

	orderbys (cols) {
		if (Array.isArray(cols)) {
			for (var ix = 0; ix < cols.length; ix++) {
				if (Array.isArray(cols[ix])) {
					if (this.orders === undefined)
						this.orders = [];
					this.orders.push(cols[ix]);
				}
				else {
					this.orderby(cols[ix].tabl, cols[ix].col, cols[ix].asc);
				}

			}
		}
		else console.log('QueryReq#orderbys() - argument is not an array.', cols);
		return this;
	}

	groupby (expr) {
		// console.warn("groupby() is still to be implemented");
		if (this.groups === undefined)
			this.groups = [];
		this.groups.push(expr);
		return this;
	}

	groupbys (exprss) {
		// console.warn("groupby() is still to be implemented");
		if (Array.isArray(exprss)) {
			for (var ix = 0; ix < exprss.length; ix++) {
				this.groupby(exprss[ix]);
			}
		}
		else if (exprss != undefined) {
            console.log('QueryReq#groupbys() - argument is not an array.', exprss);
        }
		return this;
	}

	commit () {
		var hd = this.formatHeader();
		// return { header: hd, tabls: froms, exprs: expr, conds: cond, orders: order, group: groupings};
		return {header: hd,
				body: [{a: "R",
						exprs: this.exprs,
						f: this.mtbl,
						j: this.joinings,
						conds: this.cond,
						orders: this.order,
						group: this.groupings}]};
	}
}

class UpdateReq {
	constructor (conn, tabl, pk) {
		this.conn = conn;
		this.mtabl = tabl;
		this.nvs = [];
		this.where = [];
		if (Array.isArray(pk))
			this.where.push(pk);
		else if (typeof pk === "object")
		 	if (pk.pk !== undefined)
				this.where.push([pk.pk, pk.v]);
			else console.error("UpdateReq: Can't understand pk: ", pk);
	}

	/** add n-v
	 * @param {string} n
	 * @param {object} v
	 * TODO what about v is QueryReq ?
	 * @return {UpdateReq} this
	 */
	nv (n, v) {
		if (Array.isArray(n)) {
			this.nvs = this.nvs.concat(Protocol.nvs2row(n));
		}
		else {
			this.nvs.push([n, v]);
		}
		return this;
	}

	whereCond (logic, loper, roper) {
		if (Array.isArray(logic))
			this.where = this.where.concat(logic);
		else if (logic !== undefined)
			this.where.push([logic, loper, roper]);
		return this;
	}

	/**Wrapper of #wherecodn(), will take rop as consts and add "''".<br>
	 * whereCond(logic, lop, Jregex.quote(rop));
	 * @param logic logic operator
	 * @param lop left operand
	 * @param rop right operand
	 * @return {UpdateReq} this */
	where_ (logic, lop, rop) {
		return this.whereCond(logic, lop, Jregex.quote(rop));
	}

	/**Add post operation
	 * @param {UpdateReq | InsertReq} pst post request
	 * @return {UpdateReq} this */
	post (pst) {
		if (pst === undefined) {
			console.warn('You really wanna an undefined post operation?');
			return this;
		}
		else if (typeof pst.version === 'string' && typeof pst.seq === 'number')
			console.warn('You pobably adding a JMessage as post operation? It should only be JBody(s).');

		if (this.postUpds === undefined) {
			this.postUpds = [];
		}
		if (Array.isArray(pst)) {
			this.postUpds = this.postUpds.concat(pst);
		}
		else {
			this.postUpds.push(pst);
		}
		return this;
	}
}

class DeleteReq extends UpdateReq {
	constructor (conn, tabl, pk) {
		super (conn, tabl, pk);
		this.a = Protocol.CRUD.d;
	}
}

class InsertReq extends UpdateReq {
	constructor (conn, tabl) {
		super (conn, tabl);
		this.a = Protocol.CRUD.c;
	}

	columns (cols) {
		if (this.cols === undefined)
			this.cols = [];
		if (Array.isArray(cols)){
			this.cols = this.cols.concat(cols);
		}
		else this.cols.push(cols);
	}

	/**Override Update.nv() - insert is using valus() for nvss.
	 * @param {string} n
	 * @param {string} v
	 * @return {InsertReq} this*/
	nv (n, v) {
		this.valus(n, v);
	}

	/**Set inserting value(s).
	 * Becareful about function name - 'values' shall be reserved.
	 * @param {string|Array} n_row field name or n-v array<br>
	 * n_row can be typically return of jqueyr serializeArray, a.k.a [{name, value}, ...].<br>
	 * Note:<br>
	 * 1. Only one row on each calling.<br>
	 * 2. Don't use both n-v mode and row mode, can't handle that.
	 * @param {string} v value if n_row is name. Optional.
	 * @return {InsertReq} this
	 */
	valus (n_row, v) {
		if (this.nvss === undefined)
			this.nvss = [];

		var warned = false;
		if (Array.isArray(n_row)) {
			if (Array.isArray(n_row[0])) {
				// already a 2-d array
				if (Array.isArray(n_row[0][0]) && !warned) {
					console.warn('InsertReq is trying to handle multi rows in on value call, it is wrong. You must use InsertReq.nvRows(rows) instead.',
							n_row);
					warned = true;
					this.nvss = this.nvss.concat(n_row);
				}
				else {this.nvss = this.nvss.concat([n_row]);}
			}
			else {
				// guess as a n-v array
				if (this.cols === undefined)
					this.columns(Protocol.nvs2cols(n_row));
				this.nvss = this.nvss.concat([Protocol.nvs2row(n_row)]);
			}
		}
		else if (typeof n_row === 'string'){
			this.columns(n_row);
			if (this.nvss.length == 0) {
				this.nvss.push([[n_row, v]]);
			}
			else {
				this.nvss[0].push([n_row, v]);
			}
		}
		else console.error('Error when setting n-v with', n_row, v,
			'Check the type of n - only string for column, or n is an array represeting a row\'s n-vs!');
		return this;
	}

	nvRows(rows) {
		if (Array.isArray(rows)) {
			for (var ix = 0; ix < rows.length && Array.isArray(rows[ix]); ix++) {
				this.valus(rows[ix]);
			}
		}
	}
}
///////////////// io.odysz.semantic.ext ////////////////////////////////////////
/** define t that can be understood by stree.serv */
const stree_t = {
	/** load dataset configured and format into tree with semantics defined by sk. */
	sqltree: 'sqltree',
	/** Reformat the tree structure - reformat the 'fullpath', from the root */
	retree: 'retree',
	/** Reformat the forest structure - reformat the 'fullpath', for the entire table */
	reforest: 'reforest',
	/** Query with client provided QueryReq object, and format the result into tree. */
	query: ''};

class DatasetCfg extends QueryReq {
	/**@param {string} conn JDBC connection id, configured at server/WEB-INF/connects.xml
	 * @param {string} sk semantic key configured in WEB-INF/dataset.xml
	 * @param {stree_t} t function branch tag (JBody#a).
	 * Can be only one of stree_t.sqltree, stree_t.retree, stree_t.reforest, stree_t.query
	 * @param {object} args arguments to be formatted to sql args.
	 * @param {string} maintbl if t is null or undefined, use this to replace maintbl in super (QueryReq), other than let it = sk.
	 * @param {string} alias if t is null or undefined, use this to replace alias in super (QueryReq).
	 */
	constructor (conn, sk, t, args, maintbl, alias) {
		super(conn, Jregex.isblank(t) ? maintbl : sk, alias);

		this.conn = conn;
		this.sk = sk;
		this.sqlArgs = args;

		this._t(t);
		this.checkt(t);
	}

	get geTreeSemtcs() { return this.trSmtcs; }

	/**Set tree semantics<br>
	 * You are recommended not to use this except your are the semantic-* developer.
	 * @param {TreeSemantics} semtcs */
	treeSemtcs(semtcs) {
		this.trSmtcs = semtcs;
		return this;
	}

	_t(ask) {
		if (typeof sk === 'string' && sk.length > 0 && ask !== stree_t.sqltree) {
			console.warn('DatasetReq.a is ignored for sk is defined.', sk);
			this.a = stree_t.sqltree;
		}
		else {
			this.a = ask;
			this.checkt(ask);
		}
	}

	args(args) {
		if (this.sqlArgs === undefined)
			this.sqlArgs = [];

		if (typeof args === 'string')
			this.sqlArgs = this.sqlArgs.concat([args]);
		else if (Array.isArray(args))
			this.sqlArgs = args;
		else console.error('sql args is not an arry: ', args);
			this.sqlArgs = this.sqlArgs.concat(args);
		return this;
	}

	/** Check is t can be undertood by s-tree.serv
	 * @param {string} t*/
	checkt(t) {
		// if (t !== stree_t.sqltree && t !== stree_t.retree && t !== stree_t.reforest) {
		if (t !== undefined && !stree_t.hasOwnProperty(t)) {
			console.warn("DatasetCfg.t won't been understood by server:", t, "Should be one of", stree_t);
		}
	}
}

///////////////// END //////////////////////////////////////////////////////////
export {Jregex, Protocol, JMessage, JHeader, UserReq, SessionReq, QueryReq, UpdateReq, DeleteReq, InsertReq, DatasetCfg, stree_t}