defineProperty = Object.defineProperty
getDescriptor = Object.getOwnPropertyDescriptor
import [browserOnly] helpers-changeEvent.coffee
import [browserOnly] helpers-requiresDomDescriptorFix.coffee
import [browserOnly] helpers-windowPropsToIgnore.coffee

setValueNoop = (v, publisher)-> @updateAllSubs(publisher or @)

genID = ()-> ''+(++currentID)

genObj = ()-> Object.create(null)

genProxiedInterface = (isSub, completeCallback)-> (subject, customOptions, saveOptions)->
	SimplyBind(subject, customOptions, saveOptions, isSub, completeCallback)

genSelfUpdater = (binding, fetchValue)->
	binding.selfUpdater or
	binding.selfUpdater = new Binding ()->
		if fetchValue then binding.setValue(binding.fetchDirectValue(), binding, true) else binding.updateAllSubs(binding)
	, 'Func', {}

# ==== Checks =================================================================================
targetIncludes = (target, item)-> target and target.indexOf(item) isnt -1

checkIf =
	isDefined: (subject)-> subject isnt undefined
	
	isArray: (subject)-> subject instanceof Array
	
	isObject: (subject)-> typeof subject is 'object' and subject # 2nd check is to test against 'null' values

	isString: (subject)-> typeof subject is 'string'
	
	isNumber: (subject)-> typeof subject is 'number'
	
	isFunction: (subject)-> typeof subject is 'function'

	isBindingInterface: (subject)-> subject instanceof BindingInterface
	
	import [browserOnly] helpers-checkIf.DOM.coffee




# ==== Descriptor Modification =================================================================================
fetchDescriptor = (object, property, isProto)->
	descriptor = getDescriptor(object, property)
	if descriptor
		descriptor.configurable = true if isProto
		return descriptor
	
	else if objectProto=Object.getPrototypeOf(object)
		return fetchDescriptor(objectProto, property, true)


convertToLive = (bindingInstance, object, onlyArrayMethods)->
	_ = bindingInstance
	_.origDescriptor = fetchDescriptor(object, _.property) if not _.origDescriptor

	if onlyArrayMethods
		arrayMutatorMethods.forEach (method)-> # Using forEach because we need a closure here
			defineProperty object, method, 
				configurable: true
				value: ()->
					result = Array::[method].apply object, arguments
					_.updateAllSubs(_)
					return result

	else
		if _.type is 'Proxy'
			origFn = _.origFn = _.value
			context = object
			_.value = result:null, args:null

			if checkIf.isFunction(origFn)
				slice = [].slice
				getterValue = proxyFn = ()-> 
					args = slice.call(arguments)
					_.value.args = args = if _.selfTransform then _.selfTransform(args) else args
					_.value.result = result = origFn.apply(context, args)
					_.updateAllSubs(_)
					return result
				
				defineProperty object, _.property, 
					configurable: _.isLiveProp = true
					get: ()-> getterValue
					set: (newValue)->
						if not checkIf.isFunction(newValue)
							getterValue = newValue

						else if newValue isnt origFn
							origFn = _.origFn = newValue	if newValue isnt proxyFn
							getterValue = proxyFn			if getterValue isnt proxyFn
						
						return

			

		# 'ObjectProp' or 'Array' type bindings
		else import [browserOnly] helpers-convertToLive-avoidDomTypes.coffee
			propertyDescriptor = _.origDescriptor or dummyPropertyDescriptor
			_.origGetter = propertyDescriptor.get.bind(object) if propertyDescriptor.get
			_.origSetter = propertyDescriptor.set.bind(object) if propertyDescriptor.set
			shouldWriteLiveProp = propertyDescriptor.configurable
			import [browserOnly] helpers-convertToLive-StylingConstructorCheck.coffee
			import [browserOnly] helpers-convertToLive-WebkitDomDescriptorFix.coffee
			
			if shouldWriteLiveProp
				typeIsArray = _.type is 'Array'
				shouldIndicateUpdateIsFromSelf = not _.origSetter and not typeIsArray
				
				defineProperty object, _.property,
					configurable: _.isLiveProp = true
					enumerable: propertyDescriptor.enumerable
					get: _.origGetter or ()-> _.value
					set: (newValue)-> _.setValue(newValue, _, shouldIndicateUpdateIsFromSelf); return

			
				if typeIsArray
					convertToLive(_, object[_.property], true)

	return





convertToReg = (bindingInstance, object, onlyArrayMethods)->
	if onlyArrayMethods
		delete object[method] for method in arrayMutatorMethods
	else
		_ = bindingInstance
		newDescriptor = _.origDescriptor
		newDescriptor.value = (_.origFn or _.value) unless newDescriptor.set or newDescriptor.get
		defineProperty object, _.property, newDescriptor




# ==== Object cloning =================================================================================
cloneObject = (object)->
	clone = genObj()
	clone[key] = object[key] for key of object
	return clone

extendState = (base, stateToInherit)->
	stateMapping = Object.keys(stateToInherit)
	base[key] = stateToInherit[key] for key in stateMapping
	return







# ==== Binding Cache =================================================================================
cache =	
	get: (object, isFunction, selector, isMultiChoice)->
		if isFunction
			return boundInstances[object._sb_ID]
		else
			import [browserOnly] helpers-cache.get.DOMChoice_group.coffee
			if object._sb_map and object._sb_map[selector]
				return boundInstances[ object._sb_map[selector] ]


	set: (B, isFunction)-> # B ==== Binding Object
		if isFunction
			defineProperty B.object, '_sb_ID', {'configurable':true, 'value':B.ID}

		else
			selector = B.selector

			if B.object._sb_map
				B.object._sb_map[selector] = B.ID
			else
				propsMap = {}
				propsMap[selector] = B.ID
				
				defineProperty B.object, '_sb_map', {'configurable':true, 'value':propsMap}
		return

















# ==== Placeholders =================================================================================
escapeRegEx = /[.*+?^${}()|[\]\\]/g
pholderRegEx = pholderRegExSplit = null

setPholderRegEx = ()->
	start = settings.placeholder[0].replace(escapeRegEx, '\\$&')
	end = settings.placeholder[1].replace(escapeRegEx, '\\$&')
	middle = "[^#{end}]+"
	pholderRegEx = new RegExp("#{start}(#{middle})#{end}", 'g')
	pholderRegExSplit = new RegExp("#{start}#{middle}#{end}", 'g')
	return

setPholderRegEx() # Create the regEx on init



applyPlaceholders = (contexts, values, indexMap)->
	output = ''
	for contextPart,index in contexts
		output += contextPart
		output += values[indexMap[index]] if indexMap[index]
	
	return output

import [browserOnly] helpers-scanTextNodesPlaceholders.coffee








# ==== Errors + Warnings =================================================================================
throwError = (errorName)->
	throw new Error 'SimplyBind: '+(errors[errorName] or errorName)

throwWarning = (warningName, depth)-> unless settings.silent
	errSource = getErrSource(depth)
	warn = errors[warningName]
	warn += "\n\n"+errSource
	console.warn('SimplyBind: '+warn)
	return

throwErrorBadArg = (arg)->
	throwError "Invalid argument/s (#{arg})", true
	return

getErrSource = (depth)->
	((new Error).stack or '')
		.split('\n')
		.slice(depth+3)
		.join('\n')






