(->
	# Prepare
	$ = jQuery = window.jQuery
	console = window.console
	nowpadCommon = window.nowpadCommon
	List = nowpadCommon.List

	# Check Browser
	if $.browser.msie or typeof console is 'undefined' or typeof console.log is 'undefined'
		throw Error('Your browser is not supported yet')
	
	# Classes
	class Element
		# Requirements
		element: null
		elementType: null

		# Constructor
		constructor: (@element) ->
			# Determine type
			@elementType =
				if @element.getSession?
					'ace'
				else if @element instanceof jQuery
					'jquery'
				else if @element instanceof window.Element
					'native'
				else
					throw new Error 'Unknown element type'
		
		# Active
		active: (value) ->
			# Prepare
			result = false

			# Apply?
			if value? and value is true
				switch @elementType
					when 'ace'
						@element.textInput.getElement().focus()

					when 'jquery'
						@element.get(0).focus()
					
					when 'native'
						@element.focus()
				
				result = true
			
			# Fetch
			else
				switch @elementType
					when 'ace'
						result = @element.textInput.getElement() is document.activeElement
					
					when 'jquery'
						result = @element.find(document.activeElement).add(@element.filter(document.activeElement)).length isnt 0
					
					when 'native'
						result = @element is document.activeElement
			
			# Return
			result
		
		# Value
		value: (value) ->
			# Apply
			if value?
				switch @elementType
					when 'ace'
						@element.getSession().setValue value
					when 'jquery'
						@element.val value
					when 'native'
						@element.value = value
			# Fetch
			else
				switch @elementType
					when 'ace'
						@element.getSession().getValue()
					when 'jquery'
						@element.val()
					when 'native'
						@element.value
		
		# Selection Range
		selectionRange: (selectionRange,content) ->
			# Apply
			if selectionRange?
				switch @elementType
					when 'ace'
						# Calculate
						session = @element.getSession()
						content = content||session.getValue()
						startString = content.substring(0,selectionRange.selectionStart)
						endString = content.substring(0,selectionRange.selectionEnd)
						startSplit = startString.split('\n')
						endSplit = endString.split('\n')
						startRow = if startSplit.length then startSplit.length-1 else 0
						endRow = if endSplit.length then endSplit.length-1 else 0
						startColumn = startSplit[startRow].length
						endColumn = endSplit[endRow].length

						# Apply
						session.selection.setSelectionRange
							start:
								row: startRow
								column: startColumn
							end:
								row: endRow
								column: endColumn
					
					when 'jquery'
						element = @element.get 0
						element.selectionStart = selectionRange.selectionStart
						element.selectionEnd = selectionRange.selectionEnd
					
					when 'native'
						@element.selectionStart = selectionRange.selectionStart
						@element.selectionEnd = selectionRange.selectionEnd
			
			# Fetch
			else
				switch @elementType
					when 'ace'
						# Prepare
						lines = @element.getSession().doc.getAllLines()
						range = @element.getSelectionRange()
						selectionStart = 0
						selectionEnd = 0
						i = 0

						# Calculate
						while i < lines.length and i <= range.end.row
							# Selection Start
							if i is range.start.row
								selectionStart += range.start.column;
							else if i < range.start.row
								selectionStart += lines[i].length+1
							
							# Selection End
							if i is range.end.row
								selectionEnd += range.end.column;
							else if i <= range.end.row
								selectionEnd += lines[i].length+1;

							# Increment
							++i
						
						# Result
						result =
							selectionStart: selectionStart
							selectionEnd: selectionEnd
					
					when 'jquery'
						element = @element.get 0
						# Result
						result =
							selectionStart: el.selectionStart
							selectionEnd: el.selectionEnd

					when 'native'
						# Result
						result =
							selectionStart: @element.selectionStart
							selectionEnd: @element.selectionEnd
		
		# Select a line
		line: (line) ->
			if line
				switch @elementType
					when 'ace'
						@element.gotoLine line
		
		# Add CSS Class
		addClass: (name) ->
			switch @elementType
				when 'jquery'
					@element.addClass name
		
		# Remove CSS Class
		removeClass: (name) ->
			switch @elementType
				when 'jquery'
					@element.removeClass name
		
		# Change Event
		change: (callback) ->
			switch @elementType
				when 'ace'
					@element.getSession().on 'change', callback
				when 'jquery'
					@element.keyup callback
				when 'native'
					@element.addEventListener 'keyup', callback, false
	

	class Pad
		# Requirements
		id: null
		element: null
		documentId: null

		# Synchronisation
		lastSyncedValue: ''
		lastCurrentValue: ''
		lastSyncedState: false
		newSyncedState: false
		newSyncedStates: []
		selectionRange: 
			selectionStart: 0
			selectionEnd: 0
		
		# Misc
		timer: false
		timerDelay: 200
		isTyping: false
		inRequest: false
		timeoutDelay: 1500

		# Constructor
		constructor: ({@id,element,@documentId}) ->
			# Construct element
			@element = new Element element

			# Send off initial sync
			window.now.nowpad_valueSyncDocument @documentId, (state,value,delay) =>
				@lastCurrentValue = @lastSyncedValue = value
				@lastSyncedState = state
				@timerDelay = delay
				@element.value value
				window.setTimeout(
					=>
						@element.line 1
					500
				)
			
			# Bind to change event
			@element.change =>
				@resetTimer()

		# Sync notify
		syncNotify: (state) ->
			if state isnt @lastSyncedState
				@resetTimer()
		
		# Delay notify
		delayNotify: (delay) ->
			if delay isnt @timerDelay
				@timerDelay = delay

		# Timer Reset
		resetTimer: ->
			# Clear
			@clearTimer()
			@isTyping = true

			# Initialise
			@timer = window.setTimeout(
				=>
					@isTyping = false
					@request()
				@timerDelay
			)
		
		# Timer Clear
		clearTimer: ->
			if @timer
				window.clearTimeout @timer
				@timer = false
		
		# Request
		request: ->
			# Check
			unless nowpad.ready
				return false
			
			# Lock
			if @inRequest
				return false
			@inRequest = true

			# Update
			@sync()

			# Feedback
			@element.addClass 'sync'

			# Timeout function used before server requests
			timeoutCallback = =>
				# Unlock
				window.now.nowpad_unlockDocument @documentId

				# Feedback
				@element.removeClass 'sync'
				@inRequest = false
			
			# Grab a lock
			timeoutInterval = window.setTimeout timeoutCallback, @timeoutDelay
			window.now.nowpad_lockDocument @documentId, (lockSuccess) =>
				window.clearTimeout timeoutInterval

				# Success
				if lockSuccess
					# We got the lock

					# Update the current value
					@lastCurrentValue = @element.value()
					@selectionRange = @element.selectionRange()

					# Fetch our patch
					patch = nowpadCommon.createPatch @lastSyncedValue, @lastCurrentValue

					# Sync
					timeoutInterval = window.setTimeout timeoutCallback, @timeoutDelay
					window.now.nowpad_patchSyncDocument @documentId, @lastSyncedState, patch, (_states,_state) =>
						clearTimeout timeoutInterval

						# Log
						console.log 'Sync response: ', {_state,_states}

						# Updates?
						if _states.length isnt 0
							# Synchronise
							@newSyncedStates = _states
							@newSyncedState = _state

							# Apply the changes when the user has stopped typing
							if @isTyping
								@resetTimer()
							else
								@sync()
						
						# Unlock
						window.now.nowpad_unlockDocument @documentId

						# Feedback
						@element.removeClass 'sync'
						@inRequest = false
				
				# Failure
				else
					# We didn't get the lock

					# Feedback
					@element.removeClass 'sync'
					@inRequest = false

					# Try again later
					@resetTimer()
		
		# Synchronise the value between the client and server
		sync: ->
			# Check if there is something to do
			if @newSyncedStates.length is 0
				return false
			
			# Local Values
			lastCurrentValue = @lastCurrentValue
			newCurrentValue = @element.value()

			# Synced Values
			lastSyncedState = @lastSyncedState
			newSyncedStates = @newSyncedStates
			newSyncedState = @newSyncedState
			newSyncedValue = @lastSyncedValue

			# Range
			oldSelectionRange = #clone
				selectionStart: @selectionRange.selectionStart
				selectionEnd: @selectionRange.selectionEnd
			
			# Apply synced patches
			for state in newSyncedStates
				# Apply Patch
				console.log 'remote:', state
				newSyncedValue = @applyPatch state, newSyncedValue
			
			# Compare local changes
			if lastCurrentValue && (lastCurrentValue isnt newCurrentValue)
				# Generate and apply the patch to synced changes
				state =
					patch: nowpadCommon.createPatch lastCurrentValue||'', newCurrentValue
					clientId: nowpad.clientId
				console.log('local:', state);
				newCurrentValue = @applyPatch(state,newSyncedValue);
			else
				# Apply synced changes
				newCurrentValue = newSyncedValue
			
			# Has Changes?
			if @element.value() isnt newCurrentValue
				# Updated selection range
				newSelectionRange = @element.selectionRange()

				# Apply changes
				@element.value newCurrentValue

				# Determine newest selection change
				selectionStartDifference = (newSelectionRange.selectionStart-oldSelectionRange.selectionStart)
				selectionEndDifference = (newSelectionRange.selectionEnd-oldSelectionRange.selectionEnd)
				
				# Apply selection difference
				@selectionRange.selectionStart += selectionStartDifference
				@selectionRange.selectionEnd += selectionEndDifference

				# Log
				console.log(
					[newSelectionRange.selectionStart,oldSelectionRange.selectionStart],
					[newSelectionRange.selectionEnd,oldSelectionRange.selectionEnd],
					[selectionStartDifference,selectionEndDifference],
					[@selectionRange.selectionStart,@selectionRange.selectionEnd]
				)

				# Apply cursor
				if @element.active()
					console.log 'applying cursor:', @selectionRange
					@element.selectionRange @selectionRange, newCurrentValue
				else
					console.log 'skipping cursor for element:', @element
			
			# Apply ssync changes
			@newSyncedStates = []
			@newSyncedState = false
			@lastSyncedState = newSyncedState
			@lastSyncedValue = newSyncedValue
			@lastCurrentValue = newSyncedValue
		
		# Apply a patch to our state
		applyPatch: (_state,_value) ->
			# Sync Value
			patchResult = nowpadCommon.applyPatch(
				_state.patch
				_value
				@selectionRange
			)
			patchValue = patchResult.value

			# Sync and Apply Cursor
			if _value isnt patchValue and _state.clientId isnt nowpad.clientId
				# Log
				console.log 'updating cursor:',
					range: @selectionRange
					valuesDifferent: _value isnt patchValue
					clientDifferent: _state.clientId isnt nowpad.clientId
				# Apply
				@selectionRange = patchResult.selectionRange
			else
				# Log
				console.log 'ignored cursor:',
					range: @selectionRange
					valuesDifferent: _value isnt patchValue
					clientDifferent: _state.clientId isnt nowpad.clientId
			
			# Done
			return patchValue
	
	# NowPad
	nowpad = window.nowpad = {
		# Variables
		ready: false
		clientId: null
		client:
			id: null
			info: {}
		pads: new List()
		pendingInstances: []

		# Initialise
		init: ->
			# Wait for now
			window.now.ready ->
				# Handshake
				window.now.nowpad_handshake(
					# Sync notify
					(documentId, state) ->
						console.log 'Sync notify called: ', documentId, state
						nowpad.pads.forEach (pad) ->
							if pad.documentId is documentId
								pad.syncNotify state
					# Delay notify
					(documentId, delay) ->
						nowpad.pads.forEach (pad) ->
							if pad.documentId is documentId
								pad.delayNotify delay
					# Callback
					(clientId) ->
						nowpad.clientId = clientId
						nowpad.ready = true
						for instance in nowpad.pendingInstances
							nowpad.createInstance instance.config, instance.callback
				)

		# Create Instance
		createInstance: (config,callback) ->
			if @ready
				padId = nowpad.pads.generateId()
				config.id = padId
				pad = new Pad(config)
				@pads.add(pad)
				if callback then callback(pad)
			else
				@pendingInstances.push {config,callback}
	}

	# Initialise
	nowpad.init()
	
	# jQuery Extension
	jQuery.fn.nowpad = (documentId) ->
		$this = $(this)
		nowpad.createInstance(
			element: $this
			documentId: documentId || $this.data('documentId') || 'empty'
		)
		return $this

)()