###
 * coffeescript-ui - Coffeescript User Interface System (CUI)
 * Copyright (c) 2013 - 2016 Programmfabrik GmbH
 * MIT Licence
 * https://github.com/programmfabrik/coffeescript-ui, http://www.coffeescript-ui.org
###

class CUI.SimpleForm extends CUI.DataField

	initOpts: ->
		super()
		@addOpts
			fields:
				mandatory: true
				check: (v) ->
					CUI.util.isFunction(v) or CUI.util.isArray(v)
			header:
				check: Array
			# true: all fields horizontal
			# int: n fields horizontal
			horizontal:
				default: false
				check: (v) ->
					CUI.util.isBoolean(v) or (CUI.util.isInteger(v) and v > 0)
			appearance:
				default: "normal"
				mandatory: true
				check: ["normal","separators"]
			render_as_grid:
				default: false
				mandatory: true
				check: Boolean
			blockLevelOffset:
				check: "Integer"
				default: 0

	readOpts: ->
		super()
		if @_horizontal == 1
			@__horizontal = null
		else
			@__horizontal = @_horizontal

		if @_form?.checkbox
			# the form has a checkbox (for form context)
			CUI.util.assert(CUI.util.isPlainObject(@_form.checkbox, "new CUI.Form", "opts.form.checkbox needs to be PlainObject.", opts: @opts))
			CUI.util.assert(@_name, "new CUI.Form", "opts.form.checkbox requires opts.name to be set.", opts: @opts)
			CUI.util.assert(not @_form.checkbox.data, "new CUI.Form", "opts.form.checkbox cannot have 'data' set.", opts: @opts)
			CUI.util.assert(not @_form.checkbox.name, "new CUI.Form", "opts.form.checkbox cannot have 'name' set.", opts: @opts)

			cb_opts = CUI.util.copyObject(@_form.checkbox, true)

			cb_opts.data = @__checkbox_data = checkbox: false
			cb_opts.name = "checkbox"

			onActivate = cb_opts.onActivate

			cb_opts.onActivate = (cb, flags, ev) =>
				onActivate?(cb, flags, ev)
				if not ev.ctrlKey()
					return

				for cb_el in CUI.dom.matchSelector(@DOM, ".cui-checkbox")
					CUI.dom.data(cb_el).element.activate()

				return

			@__checkbox = new CUI.Checkbox(cb_opts).start()
			CUI.Events.listen
				type: "data-changed"
				node: @__checkbox
				call: =>
					if not @__checkbox_set_data
						return
					if @__checkbox_data.checkbox
						@__checkbox_set_data[@_name] = @__checkbox_form_data
					else
						delete(@__checkbox_set_data[@_name])

		else
			@checkbox = null

		@initLayout()


	initLayout: ->

	getCheckbox: ->
		@__checkbox


	__createFields: ->
		fs = []
		fields = @getArrayFromOpt("fields")
		for field, idx in fields
			if not field
				continue

			if CUI.util.isFunction(field)
				_field = CUI.DataField.new(field(@))
			else
				_field = CUI.DataField.new(field)

			_field.setForm(@)
			fs.push(_field)
		fs

	getNameOpt: ->
		name:
			check: String

	init: ->
		@__initUndo()
		@initFields()
		# @setFormDepth()

	initFields: ->
		@__fields = @__createFields()
		@__fields

	reload: ->
		@initFields()
		@callOnOthers("unsetData")
		@callOnOthers("setData", @__data)
		super()

	displayValue: ->
		if @__checkbox
			@__checkbox.displayValue()
		super()

	getParentData: ->
		@__parent_data or @__data

	setData: (data) ->
		if @_name and @__checkbox
			CUI.util.assert(not CUI.util.isFunction(data), "Form.setData", "opts.data cannot be set by Function when data is managed by opts.form.checkbox.", opts: @opts)

		if @_name and not CUI.util.isFunction(data)
			# console.debug "init data ", @_name, data, 1
			#

			if @__checkbox
				@__checkbox_set_data = data
				if data[@_name]
					@__checkbox_form_data = data[@_name]
					@__checkbox_data.checkbox = true
					# @show()
				else
					@__checkbox_form_data = {}
					@__checkbox_data.checkbox = false
					# @hide()

				super(@__checkbox_form_data)

			else
				if CUI.util.isUndef(data[@_name])
					data[@_name] = {}

				@__parent_data = data
				super(data[@_name])
		else
			super(data)

		# sometimes fields depend on data, we need to see if
		# that is the case
		# these functions do this:
		#
		# fields: ->
		#    if not df.getData()
		#       return []
		#    else
		#       return [...]
		#
		# it can happen, like in FormPopover, that fields
		# are not yet initialized
		#
		if CUI.util.isFunction(@_fields) and @__fields and @__fields.length == 0
			# console.debug "fields depends on data...", @__data
			@initFields()
			@callOnOthers("setData", @__data)
		@

	# this hides a form row, if all
	# datafields in it are hidden
	__setRowVisibility: (tr) ->
		df = CUI.dom.data(tr, "data-field")
		if not df
			console.warn("Form.__setRowVisibility", "data-field not found", df, @)
			return

		for _f in df.getAllDataFields()
			if not _f.isHidden()
				CUI.dom.showElement(tr)
				return

		CUI.dom.hideElement(tr)
		return

	render: ->
		# we need fields rendered before we render
		# us, so that <label for=...> can work, just
		# to be safe, for "ng" only until we know
		# it does not break anything
		super()
		@renderTable()
		if not @hasContentForAppend() or @__checkbox?.getValue() == false
			CUI.dom.hideElement(@DOM)
		else
			CUI.dom.showElement(@DOM)
		@

	getLayout: ->
		@

	renderTable: ->

		@getLayout().empty()
		add_listener = (node) =>
			CUI.Events.listen
				node: node
				type: "form-check-row-visibility"
				call: (ev) =>
					tr = CUI.dom.closest(ev.getNode(), ".cui-form-tr,.cui-form-block,.cui-form-row")
					ev.stopPropagation()
					if tr
						@__setRowVisibility(tr)
					return

		# form_depth = @getFormDepth()

		table = null
		table_has_left = null

		append = (stuff, to) =>
			if not to
				add_listener(stuff)
				@appendToContainer(stuff)
			else if stuff
				to.appendChild(stuff)
			return

		get_append = (v, info=@) =>
			if v instanceof CUI.Form
				v.DOM
			else if CUI.util.isPlainObject(v) # assume a label constructor
				# new CUI.Label(v).DOM
				new CUI.MultilineLabel(v).DOM
			else if CUI.util.isString(v)
				# new CUI.Label(text: v).DOM
				new CUI.MultilineLabel(text: v).DOM
			else if v?.DOM
				v.DOM
			else if CUI.util.isFunction(v)
				get_append(v(info))
			else if CUI.util.isEmpty(v)
				null
			else
				v

		get_label = (field, register = false) =>
			lbl = field._form?.label
			if not lbl
				return

			if CUI.util.isString(lbl)
				label = CUI.dom.element("label")
				label.textContent = lbl
				field.registerLabel(label)
				return label

			return get_append(lbl)

		fields = @getFields()
		len = fields.length
		field_idx = -1

		field_has_left = (_field) =>

			fopts = _field?._form or {}
			if fopts.label
				return true

			if fopts.use_field_as_label
				console.error("Form: use_field_as_label is obsolete in \"ng\" design", @)
				return true

			return false

		render_next_field = =>
			field_idx = field_idx + 1
			if field_idx == len
				# we are done
				return

			field = fields[field_idx]
			hint_div = null
			grid = field._form?.grid

			if field._form and (not CUI.util.isNull(field._form.hint) or field._form.right)

				add_hint_div = =>
					if hint_div
						return
					hint_div = CUI.dom.element("DIV", class: "cui-form-hint", "data-for-field": field.getUniqueId())

				if not CUI.util.isNull(field._form.hint)
					add_hint_div()
					if CUI.util.isString(field._form.hint)
						hint_div.appendChild(new CUI.Label(class: "cui-form-hint-label", icon: field._form.hint_icon, text: field._form.hint, multiline: true, markdown: true).DOM)
					else
						CUI.dom.append(hint_div, field._form.hint)

				if field._form.right
					add_hint_div()

					console.error("Form.renderTable: form.right is deprecated. Use 'hint' instead. Form:", @, "Field:", field, "Field#", field_idx)
					# append deprecated stuff to the hint div
					# you should use ".hint" instead
					append(get_append(field._form.right), hint_div)

			if field.renderAsBlock()
				level = @getBlockLevelOffset() + 1

				if level > 3
					level = 3

				if field instanceof CUI.Form
					cb = field.getCheckbox()
				else
					cb = null

				if cb
					do (cb, field) =>
						CUI.Events.listen
							type: "data-changed"
							node: cb
							call: =>
								if cb.getValue()
									CUI.dom.addClass(blk.DOM, "cui-form-block--checkbox-checked")
									CUI.dom.showElement(field.DOM)
								else
									CUI.dom.removeClass(blk.DOM, "cui-form-block--checkbox-checked")
									CUI.dom.hideElement(field.DOM)
					left_side = cb
				else
					left_side = get_label(field)

					if left_side?.nodeName == "LABEL"
						left_side.classList.add("cui-block-title")

				blk = new CUI.Block
					padded: false
					maximize: @__maximize
					maximize_horizontal: @__maximize_horizontal
					maximize_vertical: @__maximize_vertical
					class: "cui-form-block"
					level: level
					header: left_side
					content: [
						get_append(field)
						hint_div
					]

				if cb
					CUI.dom.addClass(blk.DOM, "cui-form-block--has-checkbox")

					if cb.getValue()
						CUI.dom.addClass(blk.DOM, "cui-form-block--checkbox-checked")
					else
						CUI.dom.removeClass(blk.DOM, "cui-form-block--checkbox-checked")

				append(blk)

				# used to set row visibility
				CUI.dom.data(blk.DOM, "data-field", field)

				@__setRowVisibility(blk.DOM)

				table = null
				table_has_left = null
				render_next_field()
				return

			if not table
				# check if next subform has a left side
				#
				has_left = false
				for idx in [field_idx...len] by 1
					if field_has_left(fields[idx])
						has_left = true
						break

				if not has_left
					table = CUI.dom.element("DIV", class: "cui-form-container")
					table_has_left = false
				else
					table = CUI.dom.element("DIV", class: "cui-form-table")
					table_has_left = true

				if @__horizontal
					CUI.dom.addClass(table, "cui-form--horizontal")

				# CUI.dom.setAttribute(table, "cui-form-depth", form_depth)
				append(table)

			name = field.getName()
			classes = []
			if name
				classes.push("cui-form-field-name--"+name)

			if field instanceof CUI.Select
				classes.push("cui-form-field-type--select")
			else if field instanceof CUI.Checkbox
				classes.push("cui-form-field-type--checkbox")
			else if field instanceof CUI.Input
				classes.push("cui-form-field-type--input")

			if table_has_left
				tr = CUI.dom.element("DIV", class: "cui-form-tr "+classes.join(" "), "data-for-field": field.getUniqueId())

				td = CUI.dom.element("DIV", class: "cui-form-td cui-form-key")
				append(get_label(field, true), td)
				tr.appendChild(td)

				td = CUI.dom.element("DIV", class: "cui-form-td cui-form-value")
				append(get_append(field), td)
				append(hint_div, td)
				tr.appendChild(td)

				# used to set row visibility
				CUI.dom.data(tr, "data-field", field)

				@__setRowVisibility(tr)

				if grid
					tr.setAttribute("data-cui-grid", grid)

				table.appendChild(tr)
			else
				ff = field._form
				if ff
					if ff.maximize
						classes.push("cui-maximize")

					if (ff.maximize and ff.maximize_horizontal != false) or
						ff.maximize_horizontal == true
							classes.push("cui-maximize-horizontal")

					if (ff.maximize and ff.maximize_vertical != false) or
						ff.maximize_vertical == true
							classes.push("cui-maximize-vertical")

				row = CUI.dom.element("DIV", class: "cui-form-row "+classes.join(" "), "data-for-field": field.getUniqueId())
				row.appendChild(get_append(field))
				append(get_append(field), row)
				append(hint_div, row)

				# used to set row visibility
				CUI.dom.data(row, "data-field", field)

				@__setRowVisibility(row)

				if grid
					row.setAttribute("data-cui-grid", grid)

				table.appendChild(row)

			render_next_field()

		render_next_field()
		return

	renderAsBlock: ->
		if @getCheckbox()
			return true

		if @_render_as_block == false
			return false

		if @_render_as_block == true
			return true

		fields = @getFields()
		for f in fields
			if f._form?.label
				return true

			if f instanceof CUI.Form
				if f.renderAsBlock()
					return true

		return false

	hasContentForAppend: ->
		if @__fields?.length > 0
			true
		else
			false

	appendToContainer: (stuff) ->
		@append(stuff)

	onDataChanged: (ev, info) ->
		super(ev, info)

		if not info.element
			return

		# console.debug "Form data-changed", @getData()

		if info.action in ["goto", "reset"]
			return

		@__undo.log[++@__undo.idx] =
			name: info.element.getName()
			undo_idx: info.undo_idx
			action: info.action

		@__undo.log.splice(@__undo.idx+1)
		return

	hasUserData: (data) ->
		for f in @getFields("hasUserData")
			if f.hasUserData(data)
				return true
		return false

	remove: ->
		super()

	__initUndo: ->
		if @__undo
			return

		@__undo =
			log: []
			idx: -1


	getLog: ->
		log = []
		for l in @__undo.log
			log.push("#{l.name} #{l.action} #{l.undo_idx}")
		log.join("\n")

	undo: ->
		if @__undo.idx == -1
			return false

		if @__undo.idx == 0
			# we dont exactly know which
			# field we need to tell to
			for df in @getFields()
				df.goto(@__undo.idx--)
			return true

		l = @__undo.log[--@__undo.idx]
		# console.debug ""+@getRootForm(), l.name, @getRootForm().getFieldsByName(l.name)
		for f in @getRootForm().getFieldsByName(l.name)
			f.goto(l.undo_idx)
		return true

	redo: ->
		if @__undo.idx == @__undo.log.length - 1
			return false

		l = @__undo.log[++@__undo.idx]
		for f in @getRootForm().getFieldsByName(l.name)
			f.goto(l.undo_idx)
		return true

	getFieldByIdx: (idx) ->
		CUI.util.assert(CUI.util.isInteger(idx) and idx >= 0, "#{@__cls}.getFieldByIdx", "idx must be Integer.", idx: idx)
		@getFields("getFieldByIdx")[idx]

	updateHint: (field_name, hint, trigger_resize = true) ->
		field = @getFieldsByName(field_name)[0]
		if not field
			console.error("Form.updateHint:", field_name, "not found.")
			return

		els = CUI.dom.matchSelector(@getLayout().DOM, ".cui-form-hint[data-for-field='"+field.getUniqueId()+"'] > .cui-form-hint-label")
		if els.length != 1
			console.error("Form.updateHint:", field_name, "not found in DOM.")
			return

		CUI.dom.data(els[0]).element.setText(hint)

		if trigger_resize
			CUI.Events.trigger
				type: "content-resize"
				node: els[0]
		@

	__setClassOnField: (field_name, cls, add_remove) ->
		for field in @getFieldsByName(field_name)
			row = CUI.dom.closest(field.DOM, "[data-for-field]")
			if not row
				continue

			if add_remove
				CUI.dom.addClass(row, cls)
			else
				CUI.dom.removeClass(row, cls)
		@

	addClassToField: (field_name, cls) ->
		@__setClassOnField(field_name, cls, true)

	removeClassFromField: (field_name, cls) ->
		@__setClassOnField(field_name, cls, false)

	getFieldsByName: (name, found_fields = []) ->
		CUI.util.assert(CUI.util.isString(name), "#{@__cls}.getFieldsByName", "name must be String.", name: name)

		# console.debug @dataFields, @, typeof(@getFields)

		for _field in @getFields("getFieldsByName")
			# console.debug _field, CUI.util.getObjectClass(_field), name, _field.getName
			if _field.getName() == name
				found_fields.push(_field)

			if _field instanceof CUI.Form
				_field.getFieldsByName(name, found_fields)


		# if found_fields.length == 0
		#	console.warn("#{@__cls}.getFieldsByName: No field found matching \"#{name}\".")

		return found_fields

	getFields: (func) ->
		# console.debug "form get fields", @__fields
		@__fields

	isDataField: ->
		if @_name
			return true
		else
			return false

	hasData: ->
		false

	getValue: ->
		# console.debug "getValue FORM", @_name, @__data
		if not @_name
			data = {}
			for field in @getDataFields()
				if k = field.getName()
					data[k] = field.getValue()
			return data
		else
			# console.debug "getValue HENK", @_name, @__data
			data = CUI.util.copyObject(@__data, true)
			delete(data._undo)
			return data

	isChanged: ->
		for _field in @getFields()
			if _field.isChanged()
				return true
		return false

	getBlockLevelOffset: ->

		if @getForm()
			parent = (@getForm().getBlockLevelOffset?() or 0) + 1
		else
			parent = 0

		return @_blockLevelOffset + parent

	setBlockLevelOffset: (@_blockLevelOffset) ->
		return @_blockLevelOffset

	destroy: ->
		if @table
			@unregisterTableListeners()
			CUI.dom.remove(@table)
			@table = null
		super()


CUI.Events.registerEvent
	type: "form-check-row-visibility"
	bubble: true
