/* Form extension */
Form.Selection = Class.create({
	
	initialize: function(element)
	{
		if (document.selection)
		{
			element.focus();
			
			// The current selection
			var range = document.selection.createRange();
			
			// We'll use this as a 'dummy'
			var stored_range = range.duplicate();
			
			// Select all text
			stored_range.moveToElementText(element);
			
			// Now move 'dummy' end point to end point of original range
			stored_range.setEndPoint('EndToEnd', range);
			
			// Now we can calculate start and end points
			element.selectionStart = stored_range.text.length - range.text.length;
			element.selectionEnd = element.selectionStart + range.text.length;
		}

		this.element = element;
		this.start = element.selectionStart;
		this.end = element.selectionEnd;
	},
	
	setValue: function(txt)
	{
		var scrollPos = this.element.scrollTop;
		var v = this.element.value;
		
		this.element.value = v.substring(0, this.start) + txt + v.substring(this.end);
		
		var start = this.start;
		var end = this.start + txt.length;
		
		this.element.focus();
			
		if (this.element.setSelectionRange)	
		{		
			this.element.setSelectionRange(start, end);	
		}	
		else if (this.element.createTextRange) 
		{		
			var range = this.element.createTextRange();		
			range.moveStart('character', start);		
			range.moveEnd('character', end - this.element.value.length);		
			range.select();
		}
		
		this.element.scrollTop = scrollPos;
	},
	
	getValue: function()
	{
		return this.element.value.substring(this.start, this.end);
	},
	
	surround: function(prefix, suffix)
	{
		var v = this.getValue();
		
		if (v.match(/ $/))
		{
			v = v.substring(0, v.length - 1);
			suffix += " ";	
		}
		
		this.setValue(prefix + v + suffix);
	}
});

Form.Element.getSelection = function(element)
{
	return new Form.Selection(element);
};

Form.Element.getSelectedText = function(element) {
		
	if (document.selection) 
	{
		// focus the element
        element.focus();
        
        // return its selection
        return document.selection.createRange().text;
        
    } 
    else if (element.selectionStart || element.selectionStart == '0') 
    {
    	// focus the element
        element.focus();
        
        // get the start and end position of the selection
        var startPos = element.selectionStart;
        var endPos = element.selectionEnd;
        
        // return the selection
        return element.value.substring(startPos, endPos);
    }
    
    // there was no selection
    else 
    {
        return '';
    }
}

Form.Element.setSelectedText = function(element, text) {
	
	if (document.selection) 
	{
		element.focus();
        var sel = document.selection.createRange();
        
        if (sel.text.length > 0) {
        	sel.text = text;
        	return;
        }
    } 
    else if (element.selectionStart || element.selectionStart == '0') 
    {
    	element.focus();
        var startPos = element.selectionStart;
        var endPos = element.selectionEnd;
        element.value = 
        	element.value.substring(0, startPos) 
        	+ text 
        	+ element.value.substring(endPos, element.value.length);
        	
        return;
    } 
    
    // nothing else worked
    element.value += text;
}

/* Array extension */

Array.prototype.remove = function(element) {
	
	var done = false;
	var found = false;
	
    for (var i = 0; i < this.length; i++) 
    {
    	if (this[i] == element) 
    	{
    		for (var j = i; j < this.length - 1; j++) {
    			this[j] = this[j + 1];
    		}
    		
    		this.length--;
    		done = true;
    	}
    	
    	if (done)
    		break;
    }
}

/* Rectangle class */
var Rectangle = Class.create();

Rectangle.prototype = {
	
	initialize: function(point, dimension) {
		
		if (! point)
			this.point = new Point();
		else
			this.point = point;
				
		if (! dimension)
			this.dimension = new Dimension();
		else 
			this.dimension = dimension;
	}
	
	,getPoint: function() {
		return this.point;
	}
	
	,getDimension: function() {
		return this.dimension;
	}
	
	,getX: function() {
		return this.point.getX();
	}
	
	,getY: function() {
		return this.point.getY();
	}
	
	,getWidth: function() {
		return this.dimension.getWidth();
	}
	
	,getHeight: function() {
		return this.dimension.getHeight();
	}
	
	,containsPoint: function(point) {
		return (
			point.getX() >= this.getX() 
			&& point.getX() <= this.getX() + this.getWidth()
			&& point.getY() >= this.getY() 
			&& point.getY() <= this.getY() + this.getHeight()
		);
	}
};

Rectangle.createFromElement = function(element) {
	var point = Point.createFromElement(element);
	var dimension = Dimension.createFromElement(element);
	
	return new Rectangle(point, dimension);
}

var Point = Class.create();

Point.prototype = {
	
	y: 0
	,x: 0
	
	,initialize: function(x, y) {
		if (x)
			this.x = x;
			
		if (y)
			this.y = y;
	}
	
	,getX: function() {
		return this.x;
	}
	
	,getY: function() {
		return this.y;
	}
	
	,substract: function(point) {
		this.x -= point.x;
		this.y -= point.y;
	}
	
	,isNull: function()
	{
		return this.x == 0 && this.y == 0;
	}
	
	,toString: function() {
		return this.x + ',' + this.y;
	}
	
	,toGPoint: function() {
		return new GPoint(this.x, this.y);
	}
	
	,toGLatLng: function() {
		return new GLatLng(this.y, this.x);
	}
	
	/* Latitude methods */
	,getLatString: function() {
		var quarter = (this.y >= 0) ? 'N' : 'S';
		var hours = Math.abs(this.y);
		var minutes = (hours % 1) * 60;
		var seconds = (minutes % 1) * 60;
		
		return Math.floor(hours) + '°'
			+ Math.floor(minutes) + '\''
			+ Math.floor(seconds) + '"'
			+ quarter;
	}
	
	,getLatDegrees: function() {
		var hours = Math.abs(this.y);
		return Math.floor(hours);
	}
	
	,getLngMinutes: function() {
		var hours = Math.abs(this.y);
		var minutes = (hours % 1) * 60;
		return Math.floor(minutes);
	}
	
	,getLatSeconds: function() {
		var hours = Math.abs(this.y);
		var minutes = (hours % 1) * 60;
		var seconds = (minutes % 1) * 60;
		return Math.floor(seconds);	
	}
	
	/* Longitude methods */
	,getLngString: function() {
		var quarter = (this.x >= 0) ? 'E' : 'W';
		var hours = Math.abs(this.x);
		var minutes = (hours % 1) * 60;
		var seconds = (minutes % 1) * 60;
		
		return Math.floor(hours) + '°'
			+ Math.floor(minutes) + '\''
			+ Math.floor(seconds) + '"'
			+ quarter;
	}
	
	,getLngDegrees: function() {
		var hours = Math.abs(this.x);
		return Math.floor(hours);
	}
	
	,getLngMinutes: function() {
		var hours = Math.abs(this.x);
		var minutes = (hours % 1) * 60;
		return Math.floor(minutes);
	}
	
	,getLngSeconds: function() {
		var hours = Math.abs(this.x);
		var minutes = (hours % 1) * 60;
		var seconds = (minutes % 1) * 60;
		return Math.floor(seconds);	
	}
}


Point.createFromGPoint = function(point) {
	return new Point(point.x, point.y);
}

Point.createFromGLatLng = function(point) {
	return new Point(point.lng(), point.lat());
}

Point.createFromString = function(str) {
	if (! str.length)
		return null;
		
	var parts = str.split(',');
	
	if (parts.length != 2)
		return null;
		
	return new Point(parts[0], parts[1]);
}

Point.createFromElement = function(element) {
	var p = Position.cumulativeOffset(element);
	return new Point(p[0], p[1]);
}

var Dimension = Class.create();

Dimension.prototype = {
	
	width: 0
	,height: 0
	
	,initialize: function(width, height) {
		if (width)
			this.width = width;
			
		if (height)
			this.height = height;
	}
	
	,getWidth: function() {
		return this.width;
	}
	
	,getHeight: function() {
		return this.height;
	}
	
	,containsPoint: function(point) {
		return (
			point.getX() >= 0
			&& point.getX() <= this.width
			&& point.getY() >= 0
			&& point.getY() <= this.height
		);
	}
}

Dimension.createFromElement = function(element) {
	return new Dimension(
		element.offsetWidth
		,element.offsetHeight
	);
}

Object.extend(String.prototype, {
	ljust: function(size, padstr) {
		var source = this;
		if (! padstr) 
			padstr = ' ';
		
		var padSize = size - source.length;
		
		if (padSize < 1)
			return source;
			
		return padstr.times(padSize) + source;
	}
	
	,rjust: function(size, padstr) {
		var source = this;
		if (! padstr) padstr = ' ';
		
		var padSize = size - source.length;
		
		if (padSize < 1)
			return source;
			
		return source + padstr.times(padSize);
	}
	
	,ltrim: function(chars) {
    	chars = chars || "\\s";
    	return this.replace(new RegExp("^[" + chars + "]+", "g"), "");
	}
	
	,rtrim: function(chars)
	{
		chars = chars || "\\s";
    	return this.replace(new RegExp("[" + chars + "]+$", "g"), "");
	}
	
	,trim: function(chars)
	{
		return this.ltrim(chars).rtrim(chars);
	}
});

/**
 * Date extensions
 */
Date.prototype.getDayOfWeek = function() {
	return (this.getDay() + 6) % 7;
}

var DateUtil = Class.create();
			
DateUtil.NULL_SQL_DATE_TIME = '0000-00-00 00:00:00';
DateUtil.NULL_SQL_DATE = '0000-00-00';

DateUtil.parse = function (dateStr) {
	
	var date;
	date = DateUtil.parseSqlDate(dateStr);
	
	if (date != null)
		return date;
		
	return null;
}

DateUtil.parseSqlDate = function(str) {
	var match;
	var date = new Date(0);

	// match MM-DD-YYYY
	if ((match = new RegExp('^([0-9]{4})\-([0-9]{1,2})\-([0-9]{1,2})$').exec(str)) != null) 
	{
		date.setDate(1);
		date.setMonth(parseInt(match[2], 10) - 1);
		date.setYear(parseInt(match[1], 10));
		date.setDate(parseInt(match[3], 10));
		
	// match YYYY-MM-DD hh:mm:ss
	} 
	else if ((match = new RegExp('^([0-9]{4})\-([0-9]{1,2})\-([0-9]{1,2}) ([0-9]{1,2})\:([0-9]{1,2})\:([0-9]{1,2})$').exec(str)) != null) 
	{
		date.setDate(1);
		date.setSeconds(parseInt(match[6], 10));
		date.setMinutes(parseInt(match[5], 10));
		date.setHours(parseInt(match[4], 10));
		
		date.setMonth(parseInt(match[2], 10) - 1);
		date.setYear(parseInt(match[1], 10));
		date.setDate(parseInt(match[3], 10));
	}
	else
	{
		return null;
	}
	
	return date;
}

DateUtil.toSqlDate = function(date) {
	return date.getFullYear().toString().ljust(2, '0')
		+ '-' + (date.getMonth() + 1).toString().ljust(2, '0')
		+ '-' + date.getDate().toString().ljust(2, '0');
}

DateUtil.toSqlDateTime = function(date) {
	
	return DateUtil.toSqlDate(date) 
		+ ' ' + date.getHours().toString().ljust(2, '0') 
		+ ':' + date.getMinutes().toString().ljust(2, '0') 
		+ ':' + date.getSeconds().toString().ljust(2, '0');
}

var Color = {
	
	isValidHexColor: function(hex)
	{
		return /^\#[a-fA-F0-9]{6}$/.test(hex);
	}
	
	,hexToRgb: function(hex_string, default_) 
	{
	    if (default_ == undefined)
	    {
	        default_ = null;
	    }
	
	    if (hex_string.substr(0, 1) == '#')
	    {
	        hex_string = hex_string.substr(1);
	    }
	    
	    var r;
	    var g;
	    var b;
	    if (hex_string.length == 3)
	    {
	        r = hex_string.substr(0, 1);
	        r += r;
	        g = hex_string.substr(1, 1);
	        g += g;
	        b = hex_string.substr(2, 1);
	        b += b;
	    }
	    else if (hex_string.length == 6)
	    {
	        r = hex_string.substr(0, 2);
	        g = hex_string.substr(2, 2);
	        b = hex_string.substr(4, 2);
	    }
	    else
	    {
	        return default_;
	    }
	    
	    r = parseInt(r, 16);
	    g = parseInt(g, 16);
	    b = parseInt(b, 16);
	    if (isNaN(r) || isNaN(g) || isNaN(b))
	    {
	        return default_;
	    }
	    else
	    {
	        return {r: r / 255, g: g / 255, b: b / 255};
	    }
	},
	
	rgbToHex: function(r, g, b, includeHash)
	{
	    r = Math.round(r * 255);
	    g = Math.round(g * 255);
	    b = Math.round(b * 255);
	    if (includeHash == undefined)
	    {
	        includeHash = true;
	    }
	    
	    r = r.toString(16);
	    if (r.length == 1)
	    {
	        r = '0' + r;
	    }
	    g = g.toString(16);
	    if (g.length == 1)
	    {
	        g = '0' + g;
	    }
	    b = b.toString(16);
	    if (b.length == 1)
	    {
	        b = '0' + b;
	    }
	    return ((includeHash ? '#' : '') + r + g + b).toUpperCase();
	},
	
	
	hsvToRgb: function(hue, saturation, value)
	{
	    var red;
	    var green;
	    var blue;
	    
	    if (value == 0.0)
	    {
	        red = 0;
	        green = 0;
	        blue = 0;
	    }
	    else
	    {
	        var i = Math.floor(hue * 6);
	        var f = (hue * 6) - i;
	        var p = value * (1 - saturation);
	        var q = value * (1 - (saturation * f));
	        var t = value * (1 - (saturation * (1 - f)));
	        
	        switch (i)
	        {
	            case 1: red = q; green = value; blue = p; break;
	            case 2: red = p; green = value; blue = t; break;
	            case 3: red = p; green = q; blue = value; break;
	            case 4: red = t; green = p; blue = value; break;
	            case 5: red = value; green = p; blue = q; break;
	            case 6: // fall through
	            case 0: red = value; green = t; blue = p; break;
	        }
	    }
	    
	    return {r: red, g: green, b: blue};
	},
	
	rgbToHsv: function(red, green, blue)
	{
	    var max = Math.max(Math.max(red, green), blue);
	    var min = Math.min(Math.min(red, green), blue);
	    var hue;
	    var saturation;
	    var value = max;
	    
	    if (min == max)
	    {
	        hue = 0;
	        saturation = 0;
	    }
	    else
	    {
	        var delta = (max - min);
	        saturation = delta / max;
	        if (red == max)
	        {
	            hue = (green - blue) / delta;
	        }
	        else if (green == max)
	        {
	            hue = 2 + ((blue - red) / delta);
	        }
	        else
	        {
	            hue = 4 + ((red - green) / delta);
	        }
	        hue /= 6;
	        if (hue < 0)
	        {
	            hue += 1;
	        }
	        if (hue > 1)
	        {
	            hue -= 1;
	        }
	    }
	    
	    return {
	        h: hue,
	        s: saturation,
	        v: value
	    };
	}
}
