/*
    Extensions to ExtJS
    
    Aparajita Fishman
    aparajita@aparajita.com
*/

Ext.namespace('Ext.ux.form');

Ext.ux.form.SearchField = Ext.extend(Ext.form.TwinTriggerField,
{
	validationEvent: false,
	validateOnBlur: false,
	trigger1Class: 'x-form-clear-trigger',
	trigger2Class: 'x-form-search-trigger',
	hideTrigger1: true,
	width: 180,
	hasSearch: false,
	paramName: 'query',
	minLength: 1,
	trigger1Reloads: true,

	initComponent : function()
	{
		if (!this.store.baseParams)
			this.store.baseParams = {};
		
		Ext.ux.form.SearchField.superclass.initComponent.call(this);
		this.on('specialkey', function(f, e)
		{
			if (e.getKey() == e.ENTER)
				this.onTrigger2Click();
		}, this);
	},
	
	onTrigger1Click : function()
	{
		if (this.hasSearch)
		{
			this.store.baseParams[this.paramName] = '';
			
			if (this.trigger1Reloads)
				this.store.reload({params: {start: 0}});
			else			
				this.store.removeAll();
			
			this.el.dom.value = '';
			this.triggers[0].hide();
			this.hasSearch = false;
			this.focus();
		}
	},

	onTrigger2Click : function()
	{
		var v = this.getRawValue();

		if (v.length < 1)
		{
			this.onTrigger1Click();
			return;
		}

		if (v.length < this.minLength)
		{
			Ext.Msg.alert('Invalid Search', 'You must enter a minimum of ' + this.minLength + ' characters.');
			return;
		}
		
		this.store.baseParams[this.paramName] = v;
		var o = {start: 0};
		this.store.reload({params:o});
		this.hasSearch = true;
		this.triggers[0].show();
		this.focus();
	}
});


Ext.ux.data = 
{
    handleJsonStoreException: function(dataProxy, type, action, options, response, arg)
    {
        if (Ext.isString(arg) && arg === 'timeout')
            window.location = App.timeoutFunction();
    },
    
    jsonFromResponse: function(response)
	{
        var json = null;

        // See if we have a JSON response. If so,
    	// the first non-whitespace character should be '{' or '['.
    	if (/^\s*[{[]/.test(response.responseText))
    	{
    		try
    		{
    			json = Ext.util.JSON.decode(response.responseText);
    		}
    		catch (e)
    		{
    			json = null;
    		}
    	}

    	if (json && Ext.type(json.result) === 'string')
    	{
    	    switch (json.result)
    	    {
    	        case 'timeout':
    	            App.timeoutFunction();
    	            break;
    	    }
    	}
    	
    	return json;
    }
};

Ext.ux.data.Updater = function()
{	
	function _updateRecord(button, text, options)
	{
		if (button === 'yes' || button === 'ok')
		{
		    if (options.showProgress)
    			Ext.ux.Msg.wait(options.title || '', options.progressMsg || 'Please wait...');
		
			var opts = Ext.apply({}, options);
			Ext.apply(opts, options.request);
			Ext.apply(opts, {
				mode: 'submit',
				success: null,
				failure: null,
				callback: Ext.ux.data.Updater.checkActionResult
			});
		
			Ext.Ajax.request(opts);
		}
	}
	
	return {
		
		sessionTimeout: 10,
		
			
		/*
			Convenience function for updating records. Takes the following options:
		
			confirmOptions : Object		If present, is passed to Ext.Msg.show
			recordName : String			A textual name for the object being updated
			title : String				Title for the progress message dialog
			showProgress: Boolean       If false or not passed, no progress dialog is displayed
			progressMsg : String		Used as the progress message while the request is pending
			keepProgress: Boolean       If true, the progress dialog will remain after the request completes
			request : Object			Passed to Ajax.request. The callbacks should be defined here.
										The callbacks will be called with the parameters 
										(success : Boolean, json : Object, userData : Object)
			scope : Object				The scope used by all callbacks, including the request
			successMsg : String			If present, an alert with this message is displayed on success
			failureMsg : String			If present, an alert with this message is displayed on failure
			userData : Mixed			Extra data that can be passed to the request callbacks
		*/
		update: function(options)
		{
			if (options.confirmOptions)
			{
				var confirmOptions = options.confirmOptions;
				confirmOptions.fn = _updateRecord.createDelegate(options.scope || this, [options], true);
				Ext.Msg.show(confirmOptions);
			}
			else
			{
				_updateRecord('ok', '', options);
			}
		},
	
		checkActionResult: function(options, success, response)
		{
			if (options.keepProgress !== true)
				Ext.ux.Msg.hide();	// Hide the progress dialog if it's still there
		
			var errMsg = null;
		
			if (success)
			{
				var json = Ext.ux.data.jsonFromResponse(response);
			
				if (json)
				{				
					// If there is a closer, do it now
					Ext.ux.Util.close(options.closer);
				
					if (Ext.type(json.result) === 'string')
					{
						var callSuccess = function(json, options)
						{
							if (Ext.type(options.request.success) === 'function')
								options.request.success.call(this, true, json, options.userData);
					
							if (Ext.type(options.request.callback) === 'function')
								options.request.callback.call(this, true, json, options.userData);
						}.createDelegate(options.scope || this, [json, options]);
                
                        var callFailure = function(json, options)
                        {
                            if (Ext.type(options.request.failure) === 'function')
                                options.request.failure.call(this, false, json, options.userData);
                    
                            if (Ext.type(options.request.callback) === 'function')
                                options.request.callback.call(this, true, json, options.userData);
                        }.createDelegate(options.scope || this, [json, options]);
                
                        var callTimeout = function(json, options)
                        {
                            if (Ext.type(options.timeout) === 'function')
                                options.timeout.call(this, json, options.userData);
                    
                            if (Ext.type(options.request.callback) === 'function')
                                options.request.callback.call(this, true, json, options.userData);
                        }.createDelegate(options.scope || this, [json, options]);
				
						var name = options.recordName ? 'The ' + options.recordName : 'That record';
				
						switch (json.result)
						{
							case 'success':
								if (options.successMsg)
								{
									Ext.ux.Msg.alertInfo(
										'', options.successMsg,
										callSuccess, 
										options.scope);
								}
								else
									callSuccess();
								break;
					
							case 'locked':
								errMsg = name + ' is currently being edited by someone else. Please try again later.';
								success = false;
								break;
						
							case 'conflict':
								errMsg = name + ' was modified by someone else while you were editing. Please try again later.';
								success = false;
								break;
					
							case 'timeout':
							    if (options.timeout)
							        callTimeout();
							    else
    								Ext.ux.data.Updater.showTimeout();
								return false;
						
							case 'missing':
								errMsg = name + ' cannot be found in the database. It may have been deleted.';
								success = false;
								break;
						
							default:
								success = false;
						
								if (json.message)
									errMsg = json.message;
								break;
						}
					}
					else
						success = false;
				}
				else
				{
					// Response is not JSON. Unless the mode is 'direct' an error occurred.
					if (options.mode != 'direct')
						success = false;
				}
			}
		
			if (!success)
			{
				Ext.ux.Msg.alertError(
					'Error', 
					errMsg || options.failureMsg || 'An error occurred. Please try again later.',
					callFailure,
					options.scope || this);
			}
		
			return success;
		},
	
		showTimeout: function(closer)
		{
			Ext.ux.data.Updater.timedOut = true;
			Ext.ux.Util.close(closer);
		
			Ext.Msg.show(
			{
				title: 'Session Timeout',
				width: 500,
				closable: false,
				cls: 'static',
				icon: Ext.Msg.INFO,
				modal: true,
				msg: timeoutTpl.apply({ name: App.name, timeout: Ext.ux.data.Updater.sessionTimeout }),
			
				buttons: { ok: 'Login', cancel: 'No thanks' },
			
				fn: function(button)
				{
					if (button === 'ok')
						App.loginFunction();
					else
						App.logoutFunction();
				}
			});
		}
	};
}();


/****************************************************
	ux.data.JsonReader
	
	Subclass of JsonReader that looks for a 'timeout'
	property in the response JSON. If it exists and
	is true, then the timeout handler is invoked.
****************************************************/
Ext.ux.data.JsonReader = function(mode, meta, recordType)
{
	this.uxMode = mode;
	meta = meta || {};
	recordType = recordType || {};
	Ext.ux.data.JsonReader.superclass.constructor.call(this, meta, recordType);
};

Ext.extend(Ext.ux.data.JsonReader, Ext.data.JsonReader,
{
	readRecords : function(o)
	{
		var timeout = o.result === 'timeout',
		    result = { success: !timeout };
		
		switch (this.uxMode)
		{
			case 'select':
				if (!timeout)				
					return Ext.ux.data.JsonReader.superclass.readRecords.apply(this, arguments);
				else
					throw 'timeout';
				
			case 'load':
				/*
					When using a custom reader for a load action, the result is expected
					to be in the format:
					
					success: true,
					records: [{
						field1: 'value1',
						field2: 'value2'
					}]
					
					But an object called 'data' is the documented interface for field values 
					when no reader is specified. Clients on the back end shouldn't know or care
					if a custom reader is being used, so we transform the 'data' field into 'records'.
				*/
				if (timeout)
					throw 'timeout';
				
				result.records = o.result == 'success' ? [{data: o.data}] : {};
				return result;
				
			case 'submit':				
				/*
					When using a custom reader for a submit action, an error result is expected
					in the format:
					
					success: false,
					records: [{
						data: {
							id: 'field1_id',
							msg: 'Field1 is not valid.'
						}
					},
					{
						data: {
							id: 'field2_id',
							msg: 'Field2 is not valid either.'
						}
					}]
					
					The documented interface is:
					
					success: false,
					errors: {
						field1_id: 'Field1 is not valid.',
						field2_id: 'Field2 is not valid either.'
					}
					
					We want to hide this from clients, so we transform the 'errors' field
					into the 'records' array. If the client returned a result code
					other than success, the first record is:
					
					{
						__<result code>__: ''
					}
				*/
				
				if (o.result == 'success')
				{
					result.records = [];
					
					for (var field in o.data)
					{
						if (o.data.hasOwnProperty(field))
						{
							result.records.push(
							{
								data:
								{ 
									id: "'" + field + "'", 
									msg: o.data[field] 
								}
							});
						}
					}
				}
				else
				{
					result.records = [
					{
						data: 
						{
							id: String.format("'__{0}__'", o.result),
							msg: ''
						}
					}];
				}
				
				return result;
				
			default:
				throw 'timeout';	// bail
		}
	}	
});


/****************************************************
	ux.data.JsonStore
	
	Subclass of JsonStore that looks for timeouts
****************************************************/
Ext.ux.data.JsonStore = function(config)
{
    Ext.ux.data.JsonStore.superclass.constructor.call(this, Ext.apply(config, 
    {
        proxy: !config.data ? new Ext.data.HttpProxy({url: config.url}) : undefined,
        reader: new Ext.ux.data.JsonReader('select', config, config.fields)
    }));
};

Ext.extend(Ext.ux.data.JsonStore, Ext.data.Store);


Ext.ux.Msg =
{	
	waitId: 0,
	
	
	alertInfo: function(title, prompt, callback, scope, animEl)
	{
		Ext.ux.Msg.alert(title, prompt, Ext.Msg.INFO, callback, scope, animEl);
	},
	
	
	alertWarning: function(title, prompt, callback, scope, animEl)
	{
		Ext.ux.Msg.alert(title, prompt, Ext.Msg.WARNING, callback, scope, animEl);
	},
	
	
	alertError: function(title, prompt, callback, scope, animEl)
	{
		Ext.ux.Msg.alert(title, prompt, Ext.Msg.ERROR, callback, scope, animEl);
	},
	
	
	alert: function(title, prompt, icon, callback, scope, animEl)
	{
		Ext.Msg.show(
		{
			title: title,
			msg: prompt,
			buttons: Ext.Msg.OK,
			icon: icon,
			animEl: animEl,
			fn: callback ? callback.createDelegate(scope || this) : null
		});
	},
	
	
	wait: function(title, msg, progressText, interval, animEl)
	{
		Ext.Msg.show(
		{
			title: title || App.name,
			msg: msg,
			progressText: progressText || '',
			width: 400,
			wait: true,
			waitConfig: { interval: interval ? interval : 200 },
			animEl: animEl
		});
		
		// Add a unique identifier to this invocation of wait().
		// This is useful in hide().
		++Ext.ux.Msg.waitId;
		Ext.Msg.getDialog().waitId = Ext.ux.Msg.waitId;
	},
	
	
	/*
		When hiding a progress dialog, it's nice to complete the progress to
		100%, wait a half second, then close it completely.
	*/
	hide: function(waitId)
	{
		if (!Ext.Msg.isVisible())
			return;
		
		// First make sure it actually is a progress dialog
		var dlg = Ext.Msg.getDialog();
		var progress = dlg.getEl().child('.x-progress-wrap');
		
		if (!progress || !progress.isVisible())
		{
			// If we were passed a waitId, that means we wanted to close a progress dialog
			// which has been replaced by an alert during the 500ms delay. In that case
			// don't close the alert!
			if (Ext.type(waitId) != 'number')
				Ext.Msg.hide();
		}
		
		// If we were passed a waitId and it matches the current progress dialog, close it.
		else if (Ext.type(waitId) == 'number' && waitId === dlg.waitId)
			Ext.Msg.hide();
		else
		{
			progress = Ext.getCmp(progress.id);
		
			if (progress.isWaiting())
			{
				// This is a hack, I'm relying on internal structures
				progress.waitTimer.onStop = null; 
				Ext.TaskMgr.stop(progress.waitTimer);
				progress.waitTimer = null;
			}
		
			Ext.Msg.updateProgress(1);
		
			// Now wait half a second, and then hide the dialog
			Ext.ux.Msg.hide.defer(500, Ext.ux.Msg, [dlg.waitId]);
		}
	}
};


/*
    We use this object instead of of Ext.Updater so that we can intercept
    the response and see if there was a timeout.
*/
Ext.ux.AjaxWindow = function(config)
{
    var conf = {};
    
    Ext.apply(conf, config, { listeners: {} });
    
    if (conf.autoLoad)
    {
        this.url = conf.autoLoad;
        conf.autoLoad = null;
    }
    else
        this.url = conf.url;
        
    Ext.apply(conf.listeners,
    {
        close: function()
        {
            if (this.list)
                this.list.enable();
        },
        
        scope: this
    });
    
    Ext.ux.AjaxWindow.superclass.constructor.call(this, conf);
    
    Ext.Ajax.request(
    {
        url: this.url,
        success: this.onSuccess,
        failure: this.onFailure,
        scope: this
    });
};

Ext.extend(Ext.ux.AjaxWindow, Ext.Window,
{    
    onSuccess: function(response)
    {
		
        // Check to see if we got a JSON response with a timeout message
        var json = Ext.ux.data.jsonFromResponse(response);
        
        if (!json)
        {
            this.show();
			this.update(response.responseText, false, etraxx.makeButtons)
            //if (Ext.isIE6 || Ext.isIE7 || Ext.isIE8)
            //   curvyCorners.scanStyles();
            etraxx.updateToolbar(this)
			etraxx.makeFormFields()
			
        }
    },
    
    onFailure: function(response)
    {
        // FIXME: probably should redirect somewhere
    }
});

	
Ext.ux.Util =
{
    trimRe: /^\s+|\s+$/g,
    
    
	getButtonWindow: function(button)
	{
		var id = button.getEl().findParent('div.x-window', 2700).id;
		return Ext.getCmp(id);
	},
	
	
	getSelectedOptions: function(select)
	{
		var selected = [];
		var options = select.options;
		
		for (var i = 0; i < options.length; ++i)
		{
			if (options[i].selected)
				selected.push(i);
		}
		
		return selected;
	},
	
	
	getSelectValue: function(id)
	{
	    var select = Ext.getDom(id);
	    
	    return select.options[select.selectedIndex].value;
	},
	
	
	addArguments: function(args)
	{
		var a = Array.prototype.slice.call(args, 0);
		
		for (var i = 1; i < arguments.length; ++i)
			a.push(arguments[i]);
		
		return a;
	},
	
	
	close: function(closer)
	{
		var type = Ext.type(closer);
		
		switch (type)
		{
			case 'string':
				var w = Ext.getCmp(closer);
				
				if (w)
					w.close();
				break;
				
			case 'function':
				closer();
				break;
				
			case 'object':
				closer.close();
				break;
				
			default:
				break;
		}
	}
};


Ext.apply(Date.prototype,
{
    skipWeekend: function()
    {
        var dayOfWeek = this.getDay(),
            adjust = 0;
        
        if (dayOfWeek == 6)
            adjust = 2;
        else if (dayOfWeek == 0)
            adjust = 1;
            
        if (adjust)
            this.setDate(this.getDate() + adjust);
            
        return this;
    },
    
    addBusinessDays: function(days)
    {
        var d = this.clone();
        
        d.skipWeekend();
        
        for (var i = days; days; --days)
        {
            d.setDate(d.getDate() + 1);
            d.skipWeekend();
        }
        
        return d;
    }
});


Ext.ux.VTypes = {
    wholeNumber: function(val)
    {
        return /^\d+$/.test(val);
    },
    
    wholeNumberText: locale.string('text.required-only-numbers.js'),
    wholeNumberMask: /[\d]/
};


Ext.ux.initExt = function()
{
    var bd = document.body || document.getElementsByTagName('body')[0];
    
    if (!bd)
        return;
    
    if (Ext.isWindows)
        Ext.fly(bd).addClass('ext-win');
        
    Ext.apply(Ext.form.VTypes, Ext.ux.VTypes);
}

Ext.onReady(Ext.ux.initExt);

