
/*
  handles interface for showing and ??? function definitions
  
*/

/*
   takes_length takes type of argument, and returns whether
     type takes a size, like varchar(25)
*/
function takes_length(type) {
	
	switch(type)
	{
		case 'int':			case 'integer':		case 'bigint':
		case 'double precision': case 'float':	case 'double':
		case 'serial':		case 'bigserial':	case 'real':
		case 'smallint':	case 'boolean':		case 'box':
		case 'bytea':		case 'double precision': case 'date':
		case 'cidr':		case 'circle':		case 'date':
		case 'inet':		case 'line':		case 'lseg':
		case 'macaddr':		case 'money':		case 'path':
		case 'point':		case 'polygon':		
			return false;
		case 'bit':			case 'bit varying': case 'numeric':
		case 'interval':	case 'decimal':		case 'character varying':
		case 'charvar':		case 'character':	case 'text':
		case 'tsquery':		case 'tsvector':	case 'txid_snapshot':
		case 'uuid':		case 'xml':
			return true;
		default:
			return true;
	}
}

function FunctionPanel(rdbAdmin,databaseManager,sqlPanel)
{
	this.rdbAdmin = rdbAdmin;
	this.dbMgr = databaseManager;
	this.sqlPanel = sqlPanel;
	this.panelId = 'create-function-panel';
	this.mode = 'create';
	this.fname = '';
	this.maxRowId = 0;
	this.funcData = null;
	this.dirty = false;
	
	this.init_handlers = function()
	{
		var that = this;
		var $form = $('#'+this.panelId);
		$('#createNewFunctionBtn').click( function () {
			that.show('create');
		});
		$form.find('#save-function-btn').click(function() {
				that.rdbAdmin.resetMessages(); 
				that.saveFunction();
		});
		$form.find('#drop-function-btn').click(function() {
				that.rdbAdmin.resetMessages(); 
				that.dropFunction();
		});
		// create live  binding on function list struct
		$('#function-list .structure').live('click',function () {
			return that.liveShow(this);
		});
		// handlers for add and delete, up/down row butons
		$form.find('.function-del-row').live('click',function () {
				that.delRow(this);
		});
		$form.find('.function-add-row').live('click',function () {
				that.addRow(this);
		});
		$form.find('.function-row-down').live('click',function () {
				that.rowDown(this);
		});
		$form.find('.function-row-up').live('click',function () {
				that.rowUp(this);
		});
		$form.find('*:input').live('change', function () {
				that.onChange(this);
		});
		// add click handler to edit query
		$('#afunction-sql-edit').click(function () {
			var query = $('#alter-function-sql-show').html();
			that.sqlPanel.showQuery(query);
		});
	};
	
	this.liveShow = function(el)
	{
		var funcname = $(el).find('a').attr('href');
		this.show('edit',funcname);
		return false;
	};

	this.show = function(mode, fname)
	{
		var $this = this;
		this.rdbAdmin.resetMessages(); 
		if (!this.rdbAdmin.isLoggedIn()) {
			alert('please login');
			return false;
		}
		this.mode = mode;
		this.fname = fname;
		
		this.cleanParamsTable();
		if (this.mode === 'edit') {
			this.rdbAdmin.setHeading("Alter function: "+this.fname);
			this.loadFunctionDetails();
			$('#drop-function-btn').css('display','inline');
		} 
		else {
			this.rdbAdmin.setHeading("Create Function");
			$('#drop-function-btn').css('display','none');
		}
		// run onChange handler for each row, to set visibilities
		$('#'+this.panelId+' tbody tr:visible').each(function () {
			$this.onChange(this);
		});
		this.rdbAdmin.showPanel(this.panelId);
		return true;
	};
	
	this.loadFunctionDetails = function()
	{
		var $this = this;
		var $form = $('#'+this.panelId);
		//
		function withFuncDetails(json) {
			$this.funcData = json[0];
			// load argument types
			var funcName = $this.funcData[0], schema = $this.funcData[1],
				retIsSet = $this.funcData[2], volatil = $this.funcData[3],
				retType = $this.funcData[5], typeOidSet = $this.funcData[6],
				argModes = $this.funcData[7], argNames = $this.funcData[8],
				definition = $this.funcData[9];
			var typeOids = typeOidSet ? typeOidSet.split(' ') : [];
			// add return type to type list
			typeOids.push(retType);
			function withTypeInfo(tInfo) {
				// name
				$form.find('#function-name').val(funcName);
				$form.find('#old-function-name').val(funcName);
				// definition
				$form.find('#function-definition').val(definition);
				// add function params to html table
				for (var i=0; i<tInfo.length-1; i+=1) {
					var vals = { ftype: tInfo[i][1],
								 pname: argNames[i] };
					$this.addRow(null, vals);
				}
				// set return type
				$form.find('#function-ftype_999999').val(tInfo[i][1]);
				$this.types = tInfo;
			}
			// get type info, call withTypeInfo to finish
			$this.dbMgr.getTypeByOID(typeOids,withTypeInfo);
		}
		// load function details
		this.dbMgr.getFunctionsList(this.fname,withFuncDetails);
	};
	
	this.cleanParamsTable = function()
	{
		var $this = this;
		var $form = $('#'+this.panelId);
		var $body = $form.find('tbody');
		var oneRow = $body.find('tr:first').remove();
		var returnRow = $body.find('tr:last').remove();
		$body.empty()
		     .append(oneRow.hide())
			 .append(returnRow.show());
		this.maxRowId = 1;
	};

	this.setInputVisibility = function()
	{
		var $this = this;
		var $form = $('#'+this.panelId);
		var $body = $form.find('tbody');
		if (this.mode === 'edit') {
			$body.find('input:image').css('visibility','hidden');
		}
		else {
			$body.find('.function-row-up')
				 .add('.function-row-down',$body)
				 .css('visibility','visible').show();
			$body.find('tr:not(:last):last input.function-row-down')
				 .css('visibility','hidden');
			$body.find('tr:visible:first input.function-row-up')
				 .css('visibility','hidden');
		}
		$body.find('tr:visible').each(function () {
			$this.onChange(this);
		});
	};

	this.addRow = function(element, vals)
	{
		var $this = this;
		var $form = $('#'+this.panelId);
		var $body = $form.find('tbody');
		var sampleRowId = "function-param-tr_";
		var newrowID = this.maxRowId + 1;
		// clone sample row
		var $tRow = $body.find('#'+sampleRowId).clone();
		// function to add row# to end of id values
		function update_row_ids($row,i) {
			var reg = /(\S+)_(\d*)$/;
			function update_one_elem() {
				var $el = $(this);
				var id = $el.attr('id');
				if (id && id.length && reg.test(id)) { 
					id = id.replace(reg,'$1_'+i);
					$el.attr('id',id);
				}
			}
			$row.each(update_one_elem);
			$row.find('td')
			    .add('select',$row)
			    .add('input',$row)
			    .each(update_one_elem);
		}
		// reveal table row
		$tRow.css('display',''); 
		// insert new ids for selects
		update_row_ids($tRow,newrowID);
		$tRow.find('input[id^="param-name_"]').val('');
		// put data into row, if editing
		if (this.mode==='edit') {
			if (vals) {
				// populate with values
				$tRow.find('select[id^="function-param-mode_"]').val(vals.mode);
				$tRow.find('select[id^="function-ftype_"]').val(vals.ftype);
				$tRow.find('input[id^="param-name_"]').val(vals.pname);
				$tRow.find('input[id^="function-default_"]').val(vals.defaultv);
			}
		}		
		// insert new row
		var $curRow = $(element).closest('tr');
		if ($curRow.attr('id') === 'function-param-head-row') {
			// add row to the end
			$form.find('#function-param-tr_999999').before($tRow);
		}
		else {
			// add row after current
			$curRow.after($tRow);
		}
		this.setInputVisibility();
		this.maxRowId+=1;
	};
	
	this.delRow = function(element)
	{
		$(element).closest('tr').remove();
		this.setInputVisibility();
	};
	
	this.onChange = function(domel)
	{
		var $elem = $(domel);
		var $row = $elem.closest('tr');
		var $typeSelect = $row.find('td:eq(1) select');
		// if selected elem is in row...
		if ( $typeSelect.length ) {
			var idre = /^function-ftype_(\d+)$/;
			var tmpId = $typeSelect.attr('id');
			var id = idre.exec(tmpId)[1];
			// make size visible or not, depending on type
			var lenvis = takes_length($typeSelect.val()) ? 'visible' : 'hidden';
			$row.find('#function-param-length_'+id).css('visibility',lenvis);
			// if default vals allowed for this tpe
			var $defSelectVal = $row.find('td:first select').val();
			var defvis = $.inArray($defSelectVal,['IN','INOUT'])>-1 ? 'visible'
			                                                     : 'hidden'; 
			$row.find('#function-default_'+id).css('visibility',defvis);
		}
		this.updateSQLDisplay();
	};
	
	this.rowUp = function(domel)
	{
		// find ref row and row above
		var $row = $(domel).closest('tr');
		var $rowAbove = $row.prev();
		// if top row, do nothing
		if ($rowAbove.attr('id') === 'function-param-tr_') {
			return false; // we can't place first row upper :)
		}
		$row.insertBefore($rowAbove);
		this.setInputVisibility();
		return true;
	};
	
	this.rowDown = function(domel)
	{
		// find ref row and row above
		var $row = $(domel).closest('tr');
		var $rowBelow = $row.next();
		// if bottom row, do nothing
		if ($rowBelow.attr('id') === 'function-param-tr_999999') {
			return false; 
		}
		$rowBelow.after($row);
		this.setInputVisibility();
		return true;
	};
	
	this.updateSQLDisplay = function () {
		this.dirty = true;
		var sql = this.createQueryString();
		$('#'+this.panelId+' #alter-function-sql-show').html(sql);
	};

	this.createQueryString = function () {
		// create query string to manifest changes
		var $this = this;
		var $form = $('#'+this.panelId);
		var $body = $form.find('tbody');
		var funcName = $form.find('#function-name').val() || '~function~';
		var oldName = $form.find('#old-function-name').val();
		var definition = $form.find('#function-definition').val();
		function make_param_string(nm,typ,length,defaultv) {
			var data = '~nm~ ~typ~~len~~def~';
			length = length ? '('+length+')' : '';
			defaultv = defaultv ? ' DEFAULT '+defaultv : '';
			data = data.replace('~nm~',nm)
			           .replace('~typ~',typ)
					   .replace('~len~',length)
					   .replace('~def~',defaultv);
			return data;
		}
		function make_complete_def_query_string(oldName, funcName, params,
												definition, returns) {
			var paramstr = '(' + params.join(', ') + ')';
			var sql0 = '', sql, delim;
			delim = '$$';
			while ( definition.indexOf(delim) !== -1 ) {
				delim = delim.replace(/.$/,'z$');
			}
			if (oldName && oldName !== funcName) {
				sql0 = "ALTER FUNCTION " + quoteIdentifier(oldName)
				     + '('+params.join(',')+')\n'
				     + '     RENAME TO ' + quoteIdentifier(funcName) + ';\n';
			}
			sql = 'CREATE OR REPLACE FUNCTION ' + quoteIdentifier(funcName) + '\n'
				+ '     ' + paramstr + '\n'
				+ ' RETURNS ' + returns + ' AS \n'
				+ delim + definition + delim + '\n'
				+ ' LANGUAGE plpgsql;\n';
			return sql0+sql;
		}
		// traversing through parameters
		var params = [];
		$body.find('tr[id^="function-param-tr_"]').each(function(){
			// for each html row
			var $row = $(this);
			var ids = $row.attr('id');
			if ( ids === 'function-param-tr_999999' ) {
				return true; // skip if this is last row
			}
			var idre = /function-param-tr_(\d+)/;
			if ( !idre.test(ids) ) {
				// check that number found in row id, skip otherwise
				return true;
			}
			// extract data from form row
			var id = idre.exec(ids)[1];
			var pname = $row.find('#param-name_'+id).val();
			var parmtype = $row.find('#function-ftype_'+id).val();
			var length = $row.find('#function-param-length_'+id).val();
			var defaultv = $row.find('#function-default_'+id+':visible').val() || '';
			// create param string and add to params list
			params.push(make_param_string(pname,parmtype,length,defaultv));
			return true;
		});

		// get various data fields from form
		var returns = $body.find('#function-ftype_999999').val();
		var retLength = $body.find('#function-param-length_999999').val();
		if (retLength) {
			returns = returns+'('+retLength+')';
		}
		// create sql creation query from data
		var sql = make_complete_def_query_string(oldName, funcName, params,
												 definition, returns);
		return sql;
	};

	this.saveFunction = function(viewer)
	{
		var $this = this;
		var $form = $('#'+this.panelId);
		var $body = $form.find('tbody');
		var funcName = $form.find('#function-name').val();
		var oldName = $form.find('#old-function-name').val();
		var definition = $form.find('#function-definition').val();
		if ((funcName === '') || (definition === '')) {
			alert('Please fill all fields!');
			return false;
		}
		var sql = this.createQueryString();
		if (this.mode === 'edit') {
			this.fname = funcName;
		}
		// functions to handle results of query submit
		function errback (err,msg) {
			$this.rdbAdmin.showErrorMessage('<pre>'+err.toString()+': '+msg+'</pre>');
		}
		function callback(json) {
			$this.rdbAdmin.showWorkingMessage(json.status[1]);
			$('#mainPageLink').click();
		}
		// send query to engine, feed results to callback
		this.dbMgr.sqlEngine.query( { 'q' : sql,
								      'callback' : callback,
									  'errback' : errback } );
		return false;
	};
	
	this.dropFunction = function()
	{
		var sql, params = [];
		var that = this;
		if (!confirm('Are you sure?') || !this.fname) {
			return false;
		}
		// get param types w/o return type
		var ptyp = this.types.slice(0,this.types.length-1);
		for (var i in ptyp ) {
			if (ptyp.hasOwnProperty(i)) {
				params.push(this.types[i][1]);
			}
		}
		sql = "DROP FUNCTION " + quoteIdentifier(this.fname)+'('+params.join(',')+')';
		// functions to handle results of query submit
		function errback (err,msg) {
			that.rdbAdmin.showErrorMessage('<pre>'+err.toString()+': '+msg+'</pre>');
		}
		function callback(json) {
			that.rdbAdmin.showWorkingMessage(json.status[1]);
			$('#mainPageLink').click();
		}
		// send query to engine, feed results to callback
		this.dbMgr.sqlEngine.query( { 'q' : sql,
								      'callback' : callback,
									  'errback' : errback } );
		return false;
	};
}


function DatabasePanel(rdbAdmin)
{
	this.htmlTableID = 'dbpanel-functions-list';
	this.functionList = null;
	this.rdbAdmin = rdbAdmin;
	
	this.show = function()
	{
		this.rdbAdmin.showWorkingMessage('');
		this.rdbAdmin.showPanel("database-panel");
		this.rdbAdmin.setHeading("Database ");
	};
	
	this.showFunctions = function(fname)
	{
		var functions = this.sqlEngine.getFunctionsList('');
		this.functionList = functions;
		
		$('#'+this.htmlTableID).empty();
		for (var i=0; i<functions.length; i+=1) {
			$('#'+this.htmlTableID).append('<tr><td>FUNCTION</td><th><span class="btn">'+functions[i][0]+'</span></th><td><span class="btn" onclick="javascript:functionPanel.show(\'edit\',\''+functions[i][0]+'\')">Alter</span></td></tr>');
		}
	};
}


