/* Copyright 2009 Palm, Inc.  All rights reserved. */

var CharacterCounter = Class.create({	
	initialize: function(controller,platformType,textAreaElement,params) {
		var characterCounter = function() {
			var platformConst = {
				'gsm': {
					ADD_MESSAGE_HEADERS: false,
					RETURN_ONE_SEGMENT_ONLY: true,  // segmentation will happen at the TIL level for GSM
					BREAK_ON_WORD_BOUNDARIES: false
				},
				'cdma': {
					ADD_MESSAGE_HEADERS: true,
					RETURN_ONE_SEGMENT_ONLY: false,	// segmentation happens in the UI for CDMA
					BREAK_ON_WORD_BOUNDARIES: true		
				}
			}
			
			var platformData;
			
			var sceneController;
			var platformType;
			var textAreaElement;			// text area to count/limit
			var setTextFieldValueFn;
			
			var _maxLength = 0;
			var _oneSegmentMaxSize = 160;
			var _multipleSegmentMaxSize = 151;  // default will work for gsm/cdma
			var _maxparts = 1;
			
			var adjustedMaxLen = _maxLength;
			var adjustedOneSegmentMaxLen = _oneSegmentMaxSize;
			var adjustedMultipleSegmentMaxSize = _multipleSegmentMaxSize;
			
			var recheckAllTextForNonGsm = true;
			
			/*
			 * supporting 2 byte chars here always means GSM7 or UCS2
			 * CDMA case is always 1. 
			 */
			var support2ByteChars = true;

			var isOverLimit = false;		// is the current adjustedCharacterCount > maxLength
			var adjustedCharacterCount = 0; // includes holes created by word bounded segmentation
			var currentSegmentCount = 0;
			var textSavedBeforePaste;		// used to revert a paste event if the max length is exceeded
			var selectionStartSavedBeforePaste = 0;
			var selectionEndSavedBeforePaste = 0;
			var multiMessageHeaderTemplate = new Template($L("(#{currentSegmentIndex} of #{lastSegmentIndex}) "));

			var segmentCounterUI = {
				containerElement: null,
				valueElement: null
			};
			
						
			// TODO: eventually we might want to implement some cleverness that counts
			// bytes so we can accurately limit text that includes UTF-8 characters.
			// Currently this only works for ASCII text
			var getRawCharacterData = function() {
				// DO NOT strip whitespace from the end of the message.  Doing this can cause
				// problems when approaching the max size limit.  If enough spaces are be added to 
				// get past the limit, then a single character can be entered to exceed the limit.
				
				var message = textAreaElement.value;
				
				return {count:message.length, message:message};
			};

			var setOverLimit = function(isOver) {
				if (isOver) {
					isOverLimit = true;
						
					if(segmentCounterUI.valueElement)
						segmentCounterUI.valueElement.className = 'overLimit';
	
				} else {
					isOverLimit = false;
						
					if(segmentCounterUI.valueElement)
						segmentCounterUI.valueElement.className = '';					
				}				
			};
			
			/*
			 * this array is a capture of all of the high byte ascii characters integrated into GSM 7
			 */
			var extraGsm7Chars ={
			163:true,			/* 0x00A3, // POUND SIGN*/
			165:true,			/* 0x00A5, // YEN SIGN*/
			232:true,			/* 0x00E8, // LATIN SMALL LETTER E WITH GRAVE*/
			233:true,			/* 0x00E9, // LATIN SMALL LETTER E WITH ACUTE*/
			249:true,			/* 0x00F9, // LATIN SMALL LETTER U WITH GRAVE*/
			236:true,			/* 0x00EC, // LATIN SMALL LETTER I WITH GRAVE*/
			242:true,			/* 0x00F2, // LATIN SMALL LETTER O WITH GRAVE*/
			231:true,			/* 0x00E7, // LATIN SMALL LETTER C WITH CEDILLA not really GSM7, but mess router supports it*/
			199:true,			/* 0x00C7, // LATIN CAPITAL LETTER C WITH CEDILLA*/
			216:true,			/* 0x00D8, // LATIN CAPITAL LETTER O WITH STROKE*/
			248:true,			/* 0x00F8, // LATIN SMALL LETTER O WITH STROKE*/
			197:true,			/* 0x00C5, // LATIN CAPITAL LETTER A WITH RING ABOVE*/
			229:true,			/* 0x00E5, // LATIN SMALL LETTER A WITH RING ABOVE*/
			916:true,			/* 0x0394, // GREEK CAPITAL LETTER DELTA*/
			934:true,			/* 0x03A6, // GREEK CAPITAL LETTER PHI*/
			915:true,			/* 0x0393, // GREEK CAPITAL LETTER GAMMA*/
			923:true,			/* 0x039B, // GREEK CAPITAL LETTER LAMDA */
			937:true,			/* 0x03A9, // GREEK CAPITAL LETTER OMEGA*/
			928:true,			/* 0x03A0, // GREEK CAPITAL LETTER PI*/
			936:true,			/* 0x03A8, // GREEK CAPITAL LETTER PSI*/
			931:true,			/* 0x03A3, // GREEK CAPITAL LETTER SIGMA*/
			920:true,			/* 0x0398, // GREEK CAPITAL LETTER THETA*/
			926:true,			/* 0x039E, // GREEK CAPITAL LETTER XI*/
			8364:true,			/* 0x20AC, // EURO SIGN*/
			198:true,			/* 0x00C6, // LATIN CAPITAL LETTER AE*/
			230:true,			/* 0x00E6, // LATIN SMALL LETTER AE*/
			223:true,			/* 0x00DF, // LATIN SMALL LETTER SHARP S*/
			201:true,			/* 0x00C9, // LATIN CAPITAL LETTER E WITH ACUTE*/
			164:true,			/* 0x00A4, // CURRENCY SIGN*/
			161:true,			/* 0x00A1, // INVERTED EXCLAMATION MARK*/
			913:true,			/* 0x0391, // GREEK CAPITAL LETTER ALPHA*/
			914:true,			/* 0x0392, // GREEK CAPITAL LETTER BETA*/
			917:true,			/* 0x0395, // GREEK CAPITAL LETTER EPSILON*/
			919:true,			/* 0x0397, // GREEK CAPITAL LETTER ETA*/
			921:true,			/* 0x0399, // GREEK CAPITAL LETTER IOTA*/
			922:true,			/* 0x039A, // GREEK CAPITAL LETTER KAPPA*/
			924:true,			/* 0x039C, // GREEK CAPITAL LETTER MU*/
			925:true,			/* 0x039D, // GREEK CAPITAL LETTER NU*/
			927:true,			/* 0x039F, // GREEK CAPITAL LETTER OMICRON*/
			929:true,			/* 0x03A1, // GREEK CAPITAL LETTER RHO*/
			932:true,			/* 0x03A4, // GREEK CAPITAL LETTER TAU*/
			933:true,			/* 0x03A5, // GREEK CAPITAL LETTER UPSILON*/
			935:true,			/* 0x03A7, // GREEK CAPITAL LETTER CHI*/
			918:true,			/* 0x0396, // GREEK CAPITAL LETTER ZETA*/
			196:true,			/* 0x00C4, // LATIN CAPITAL LETTER A WITH DIAERESIS*/
			214:true,			/* 0x00D6, // LATIN CAPITAL LETTER O WITH DIAERESIS*/
			209:true,			/* 0x00D1, // LATIN CAPITAL LETTER N WITH TILDE*/
			220:true,			/* 0x00DC, // LATIN CAPITAL LETTER U WITH DIAERESIS*/
			167:true,			/* 0x00A7, // SECTION SIGN*/
			191:true,			/* 0x00BF, // INVERTED QUESTION MARK*/
			228:true,			/* 0x00E4, // LATIN SMALL LETTER A WITH DIAERESIS*/
			246:true,			/* 0x00F6, // LATIN SMALL LETTER O WITH DIAERESIS*/
			241:true,			/* 0x00F1, // LATIN SMALL LETTER N WITH TILDE*/
			252:true,			/* 0x00FC, // LATIN SMALL LETTER U WITH DIAERESIS*/
			224:true,			/* 0x00E0 // LATIN SMALL LETTER A WITH GRAVE*/
			};
			
			var isGsm7Char = function(charCode){

				if (charCode > 127){
					var i;
					return extraGsm7Chars[charCode];
		
				}
				
				return true;
			}
			
			
			var findNumDoubleByteGsm7Chars = function(gsm7String){
				
				var numFound = 0;
				var i;
				for (i=0; i < gsm7String.length; i+=1){
					switch(gsm7String.charCodeAt(i)){
						case 12:
						case 94:
						case 123:
						case 125:
						case 92:
						case 91:
						case 126:
						case 93: 
						case 124:
						case 8364:
							numFound += 1;
							break;
						default:
							break;
					}
				}
				
				Mojo.Log.info("found GSM7 double byte chars " + numFound);
				return numFound;
			}
			
			/**
			 * This method segments the message text so multiple SMS messages
			 * can be sent from whatever the user entered
			 * 
			 * @param {Object} message - raw message to parse - no spaces should have
			 * 							 been stripped from the beginning/end of the message
			 * @param {Object} currLen - length of the message
			 * @param {Object} isCountOnly - return the count only, not the actual segmented data
			 */
			var parseMessage = function(message,currLen,isCountOnly) {
				var result = {
					adjustedCharacterCount: 0,
					segmentCount: 0,
					segments: [],
					isOverLimit: false
				};
				
				/*
				 * something has triggered a complete recheck of the text
				 */
				Mojo.Log.info("supporting 2 bye chars " + support2ByteChars);
				/*
				 * This is only used if we are on GSM
				 */
				var usingGsm7 = true;
				if ( (support2ByteChars === true) && (recheckAllTextForNonGsm === true) ){
					
					/*
					 * NOTE we are assuming that when 2byte chars are supported we are 
					 * on GSM 7 and doing the GSM7 to UCS2 spilt
					 */
					
					Mojo.Log.info("rechecking all text");
					/*
					 * clear the recheck, we are doing it
					 */
					recheckAllTextForNonGsm = false;
					var found = false;
		
					/*
					 * loop through and look for non GSM 7
					 */
					Mojo.Log.info("message length " + message.length);
					var i;
					for (i=0; i < message.length; i+=1){
						//Mojo.Log.info("charCode " + message.charCodeAt(i));
						if (!isGsm7Char(message.charCodeAt(i))){
							Mojo.Log.info("found non GSM 7, assume 2 bytes per char");
							found = true;
							break;
						}
					}
					
					if (found){
						Mojo.Log.info("setting sizes to UCS2 defaults")
						/*
						 * lengths in are really character lengths, and in the case of UCS2
						 * the values in are relavent to GSM7 which as a packed 7 bit encoding
						 * goofies up the count when converting to UCS2. In all practicality
						 * GSM7/UCS2 is always going to boil down to
						 * GSM7 : oneSeg = 160, multipleSegMaxSize = 153, maxLen = 153 * _maxparts
						 * UCS2 : oneSeg = 70, multipleSegMaxSize = 67, maxLen = 67 * _maxparts
						 */
						//adjustedMaxLen = Math.floor(_maxLength/2);
						//adjustedMultipleSegmentMaxSize = Math.floor(_multipleSegmentMaxSize/2);
						//adjustedOneSegmentMaxLen = Math.floor(_oneSegmentMaxSize/2);
						adjustedMaxLen = 67 * _maxparts;
						adjustedMultipleSegmentMaxSize = 67;
						adjustedOneSegmentMaxLen = 70;	
						usingGsm7 = false;					
					}else{
						Mojo.Log.info("no UCS2 require chars found, setting to GSM7");
						adjustedMaxLen = _maxLength;
						adjustedMultipleSegmentMaxSize = _multipleSegmentMaxSize;
						adjustedOneSegmentMaxLen = _oneSegmentMaxSize;
						usingGsm7 = true
					}
					
				}
				
				if (platformData.BREAK_ON_WORD_BOUNDARIES ){
					/*
					 * CDMA case we split up things on clean word boundaries 
					 */

					Mojo.Log.info("break on word boundaries is true");
					
					if (adjustedMaxLen > 0 && currLen > adjustedOneSegmentMaxLen) { // more than 1 segment
						var beginning = 0;
						var end = 0;
						while (end < currLen && result.adjustedCharacterCount < adjustedMaxLen) {
							beginning = end;
							// skip any spaces that may already be at the beginning
							// of this segment
							if (message.charAt(beginning) == ' ') {
								var foundNonSpace = false;
								beginning++;
								while (!foundNonSpace && beginning < currLen) {
									if (message.charAt(beginning) == ' ') {
										beginning++;
									}
									else 
										foundNonSpace = true;
								}
							}
						
							//adjust end to point to the end of the next segment
							end = beginning + adjustedMultipleSegmentMaxSize;
						
							if (end >= currLen) { // at the end of the message
								end = currLen;
							}
							else 
								if (message.charAt(end) != ' ') { // middle of word, back up until a space is hit

									var newEnd = end;
									var foundSpace = false;
									while (!foundSpace && newEnd > beginning) {
										if (message.charAt(newEnd) == ' ') {
											foundSpace = true;
											end = newEnd;
										}
										else {
											newEnd--;
										}
									}
								}
						
							if (beginning != end) {
								if (end != currLen) 
									result.adjustedCharacterCount += adjustedMultipleSegmentMaxSize;
								else 
									result.adjustedCharacterCount += (end - beginning); //add in this segment
								result.segmentCount++;
								if (!isCountOnly) {
									result.segments.push(message.slice(beginning, end));
								}
							}
						}
						
						if(end != currLen || result.adjustedCharacterCount > adjustedMaxLen) { // didn't make it to the end, we are over the limit
							result.isOverLimit = true;
						}
					} else {
						result.adjustedCharacterCount = currLen;
						result.segmentCount = 1;
						if(!isCountOnly)
							result.segments.push(message);
					}

				} else {
					Mojo.Log.info("break on word boundaries is false");
						
					/*
					 * GSM case split up happens inside words so calc is easier
					 * no bother unless we are over half size 
					 */
					var adjustedCurrLen;
					if (usingGsm7 && (message.length > 69)){
						adjustedCurrLen = currLen + findNumDoubleByteGsm7Chars(message);
					}else{
						adjustedCurrLen = currLen;
					}
						
					if (adjustedMaxLen > 0 && adjustedCurrLen > adjustedOneSegmentMaxLen) {
						result.segmentCount = Math.ceil(adjustedCurrLen / adjustedMultipleSegmentMaxSize);
					}else{
						result.segmentCount = 1;
					}
						
					result.adjustedCharacterCount = adjustedCurrLen;
							
					if(result.adjustedCharacterCount > adjustedMaxLen) { //we are over the limit
						result.isOverLimit = true;
					}
						
					//Mojo.Log.logProperties(result, "result", false);
						
				}

		
				// add headers to the front of each message segment
				if(!isCountOnly && result.segmentCount > 1 && platformData.ADD_MESSAGE_HEADERS) {
					for(var x=0; x<=result.segmentCount-1; x++) {
						var header = multiMessageHeaderTemplate.evaluate({currentSegmentIndex:(x+1),lastSegmentIndex:result.segmentCount});
						result.segments[x] = header + result.segments[x];
					}
				}

				return result;
			};
			
			var setCurrentSegmentCount = function(newSegmentCount) {
				var displayCount;
				if (isOverLimit) {
					displayCount = 'X';
					newSegmentCount = -1;
				} else {
					displayCount = newSegmentCount;
				}			
				
				// update the segment count value if it exists
				// Don't show the count if
				if(segmentCounterUI.valueElement && newSegmentCount != currentSegmentCount) {
					if(newSegmentCount != 1)
						segmentCounterUI.valueElement.update(displayCount);
					else
						segmentCounterUI.valueElement.update('');	// this is not needed, but helps for testing
				}
				currentSegmentCount = newSegmentCount;
				// hide/show the segment container if it exists
				// TODO: it would be better if this fired off events
				if(segmentCounterUI.containerElement) {					
					if (currentSegmentCount == 1) {
						if (segmentCounterUI.containerElement.visible()) {
							segmentCounterUI.containerElement.hide();
						}
					} else {
						if (!segmentCounterUI.containerElement.visible()) {
							segmentCounterUI.containerElement.show();
						}
					}
				}
			};
			
			return {
				
				init: function(controller,platform,textElement,params) {
					// defer calculating the count because we want that to fire after the events have happened
					this.deferredResetCount = this.deferredResetCount.bind(this);
					sceneController = controller;
					platformType = platform;
					
					if(platformConst[platformType])
						platformData = platformConst[platformType];
					else
						platformData = platformConst.gsm; // if we do not have platform data, use gsm by default
					
					textAreaElement = textElement;
					
					this.setParams(params);
					
					// This method prevents anything being entered over the maxLength + makes sure the segment count is accurate
					this.handleKey = function(event) {
						
						Mojo.Log.warn("+++++keycode " + event.keyCode);
						
						var isControlChar;
						
						switch (event.keyCode){
							case 8:
							case 13:
							case 16:
							case 17:
							case 18:
							case 293:
							case 294:
							case 295:
							case 296:
							case 130:
								Mojo.Log.info("special char");
								isControlChar=true;
								break;
							default:
								Mojo.Log.info("not so special")
								isControlChar = false
								break;
							
						}
						
						
						if(adjustedMaxLen > 0 && isControlChar === false && adjustedCharacterCount >= adjustedMaxLen) {
							Event.stop(event);
							Mojo.Log.info("past limit, blocking all non special chars")
						}
						
						
					}.bind(this);
					
					// we do not have access to the text being pasted in the onPaste event
					var handlePaste = function() {
						// parse the text field after the paste to make sure everything is ok
						var rawCharacterData = getRawCharacterData();
						
						var messageData = parseMessage(rawCharacterData.message,rawCharacterData.count,true);
						// if the length after paste is larger than the maxLength, revert the paste
						if (adjustedMaxLen > 0 && messageData.isOverLimit) {
							Mojo.Log.info("Reverting paste because the new length exceeds the maxLength");
							if(setTextFieldValueFn) {
								setTextFieldValueFn(textSavedBeforePaste);
								textAreaElement.selectionStart = selectionStartSavedBeforePaste;
								textAreaElement.selectionEnd = selectionEndSavedBeforePaste;
							}
						}
						textSavedBeforePaste = '';
						selectionStartSavedBeforePaste = 0;
						selectionEndSavedBeforePaste = 0;
						this.resetCount();
					}.bind(this);
					
					this.deferredHandlePaste = function(event) {
						// grab the text before the paste happens if we need to revert
						textSavedBeforePaste = textAreaElement.value;
						selectionStartSavedBeforePaste = textAreaElement.selectionStart;
						selectionEndSavedBeforePaste = textAreaElement.selectionEnd;

						// run this when the call stack becomes empty
						handlePaste.defer();
					};
					
					
					textAreaElement.observe('keypress',this.handleKey);
					textAreaElement.observe('keydown',this.handleKey);
					textAreaElement.observe('paste', this.deferredHandlePaste);
					
				},
				
				destroy: function() {
					textAreaElement.stopObserving('keypress',this.handleKey);
					textAreaElement.stopObserving('keydown',this.handleKey);
					textAreaElement.stopObserving('paste',this.deferredHandlePaste);
				},
				
				setParams: function(params) {
					if (params) {					
						if(params.segmentCounter) {
							segmentCounterUI.containerElement = params.segmentCounter.segmentCountContainer;
							segmentCounterUI.valueElement = params.segmentCounter.segmentCountElement;
							if(params.segmentCounter.setTextFieldValueFn) {
								setTextFieldValueFn = params.segmentCounter.setTextFieldValueFn;
							}						
						}

					}					
				},
				
				setSizeParams: function(params) {
					
					Mojo.Log.info("+++++params %j ", params);
					
					var defaultParams = {
						maxPartLength: 1000,
						maxParts: 1,
						headerSize: 0,
						encodings: ""
					};
					Object.extend(defaultParams,params);
					
					if (defaultParams.maxPartLength) {
						_oneSegmentMaxSize = defaultParams.maxPartLength;
						_multipleSegmentMaxSize = (defaultParams.maxPartLength - defaultParams.headerSize);
						_maxLength = _multipleSegmentMaxSize * defaultParams.maxParts;
						_maxparts = defaultParams.maxParts;
						Mojo.Log.info("+++++encodings " + defaultParams.encodings);
						if ((params) && (!defaultParams.encodings) ||
								(defaultParams.encodings.indexOf("UCS-2") != -1) ){
							Mojo.Log.info("+++++supporting UCS2");
							support2ByteChars = true;
						}else{
							Mojo.Log.info("+++++not supporting UCS2");
							support2ByteChars = false;
						}
					} else {
						// this should never get hit. If it does, just make sure we never go past 1000 chars
						_oneSegmentMaxSize = 1000;
						_multipleSegmentMaxSize = 1000;
						_maxLength = 1000;
						_maxparts = 1;
						support2ByteChars = false;
					}
					
					adjustedOneSegmentMaxLen = _oneSegmentMaxSize;
					adjustedMultipleSegmentMaxSize = _multipleSegmentMaxSize;
					adjustedMaxLen = _maxLength;
				
					this.resetCount();
				},
				
				
				getMaxLength: function() {
					//return _maxLength;
					return adjustedMaxLen;
				},
				
				isValidLength: function() {
					return !isOverLimit;
				},
				
				resetCount: function() {
					var rawCharacterData = getRawCharacterData();
					
					if (rawCharacterData.count > 69){
						recheckAllTextForNonGsm = true;
						Mojo.Log.info("text changed, looking for char size")
					}else{
						Mojo.Log.info("text short enough that no reason to check char size");
					}
					
					var messageData = parseMessage(rawCharacterData.message,rawCharacterData.count,true);
					adjustedCharacterCount = messageData.adjustedCharacterCount;
					if (adjustedMaxLen > 0)
						setOverLimit(messageData.isOverLimit);
					setCurrentSegmentCount(messageData.segmentCount);				
				},
				
				deferredResetCount: function() {
					
					this.resetCount.defer();
				},
				
				getSegmentData: function() {
					var rawCharacterData = getRawCharacterData();
					var parsedMessageData = parseMessage(rawCharacterData.message,rawCharacterData.count,false);
					
					// The TIL will segment the message for GSM, just send one big message
					if(platformData.RETURN_ONE_SEGMENT_ONLY) {
						parsedMessageData.segmentCount = 1;
						parsedMessageData.segments = [rawCharacterData.message];
					}
					return parsedMessageData;		
				}
			}
		}();
		characterCounter.init(controller,platformType,textAreaElement,params);
		Object.extend(this,characterCounter);
	}
});