###
 * 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
###

# Base class for all Buttons. Yeahhhh
#
#TODO document this in class...
#	role: ui-role
#	disabled: true, false
#
#
#Button.DOM: the actual button object
#Button.disable: disable button
#Button.enable: enable button
CUI.Template.loadTemplateText(require('./Button.html'));
CUI.Template.loadTemplateText(require('./Button_ng.html'));

class CUI.Button extends CUI.DOMElement

	@defaults:
		confirm_ok: "Ok"
		confirm_icon: "question"
		confirm_cancel: "Cancel"
		confirm_title: "Confirmation"
		disabled_css_class: "cui-disabled"
		loading_css_class: "cui-loading"
		active_css_class: "cui-active"

	#Construct a new CUI.Button.
	#
	# @param [Object] options for button creation
	# @option options [String] size controls the size of the button.
	#   "mini", small button.
	#   "normal", medium size button.
	#   "big", big sized button.
	#   "bigger", bigger sized button.
	# @option options [String] appearance controls the style or appearance of the button.
	#   "flat", button has no border and inherits its background color from its parent div.
	#   "normal", standard button with border and its own background color.
	#   "link", standard button without border and a underlined text.
	#   "important", emphasized button , to show the user that the button is important.

	constructor: (opts) ->

		super(opts)

		if @_tooltip
			if @_tooltip.text or @_tooltip.content
				@__tooltipOpts = @_tooltip

		@__has_left = true
		@__has_right = true
		@__has_center = true

		tname = @getTemplateName()
		# getTemplateName, also sets has_left / has_right

		@__box = new CUI.Template
			name: tname
			map:
				left: if @__has_left then ".cui-button-left" else undefined
				center: if @__has_center then ".cui-button-center" else undefined
				visual: if CUI.__ng__ then ".cui-button-visual" else undefined
				right: if @__has_right then ".cui-button-right" else undefined

		@registerTemplate(@__box)

		@__active = null
		@__disabled = false
		@__loading = false
		@__hidden = false
		@__txt =  null

		@addClass("cui-button-button")

		if CUI.util.isString(@__tooltipOpts?.text)
			@setAria("label", @__tooltipOpts?.text)
			@__hasAriaLabel = true
		else
			@__hasAriaLabel = false

		CUI.dom.setAttribute(@DOM, "tabindex", @_tabindex)

		if not @_attr?.role
			CUI.dom.setAttribute(@DOM, "role", @_role)

		if not @_left or @_left == true
			if @_icon
				CUI.util.assert(CUI.util.isUndef(@_icon_left), "new #{@__cls}", "opts.icon conflicts with opts.icon_left", opts: @opts)
				icon_left = @_icon
			else
				icon_left = @_icon_left

			if icon_left
				CUI.util.assert(not @_icon_active and not @_icon_inactive, "new CUI.Button", "opts.icon_active or opts.icon_inactive cannot be set together with opts.icon or opts.icon_left", opts: @opts)
				@setIcon(icon_left)
		else
			@append(@_left, "left")

		if not @_right
			if @_icon_right
				@setIconRight(@_icon_right)
			else if @_menu and @_icon_right != false
				@addClass("cui-button--has-caret")

				if @_menu_parent
					@setIconRight("fa-angle-right")
				else
					@setIconRight("fa-angle-down")

		else if @_right != true
			@append(@_right, "right")

		@setSize(@_size)

		if @_appearance
			@addClass("cui-button-appearance-"+@_appearance)

		if @_primary
			@addClass("cui-button--primary")

		if @_center
			@append(@_center, "center")
		else if @_text
			@setText(@_text)

		if @_disabled and (@_disabled == true or @_disabled.call(@, @))
			@disable()

		if @_loading and (@_loading == true or @_loading.call(@, @))
			@setLoading(true)

		if @_hidden and (@_hidden == true or @_hidden.call(@, @))
			@hide()

		if @_active == true
			@activate(initial_activate: true)
		else if @_active == false or @_switch #switch default is active=false TODO initial_activate: true == bug!?
			@deactivate(initial_activate: true)
		else
			@__setState()

		@__radio_allow_null = @_radio_allow_null

		if @_radio
			CUI.util.assert(CUI.util.isUndef(@_switch), "new CUI.Button", "opts.switch conflicts with opts.radio.", opts: @opts)
			if @_radio == true
				@__radio = "radio--"+@getUniqueId()
			else
				@__radio = @_radio
		else if not CUI.util.isNull(@_switch)
			@__radio = "switch--"+@getUniqueId()
			@__radio_allow_null = true

		if @__radio
			CUI.util.assert(not @_attr?.radio, "new CUI.Button", "opts.radio conflicts with opts.attr.radio", opts: @opts)
			CUI.dom.setAttribute(@DOM, "radio", @__radio)

		@setGroup(@_group)

		if @_menu
			@__menu_opts = {}
			itemList_opts = {}

			# rescue options for menu and separate them
			# from itemlist
			for k, v of @_menu
				switch k
					when "onShow", "onHide"
						continue
					when "class", "backdrop", "onPosition", "placement", "placements", "pointer"
						@__menu_opts[k] = v
					else
						itemList_opts[k] = v

			if not CUI.util.isEmpty(@_class)
				if @__menu_opts.class
					@__menu_opts.class += " "+@_class
				else
					@__menu_opts.class = @_class

			if @_menu.itemList
				@__menu_opts.itemList = @_menu.itemList
			else
				@__menu_opts.itemList = itemList_opts

			@__menu_opts.element = @

			if not @__menu_opts.hasOwnProperty("use_element_width_as_min_width")
				if not @_menu_parent
					@__menu_opts.use_element_width_as_min_width = true

			@__menu_opts.onHide = =>
				@_menu.onHide?()

			@__menu_opts.onShow = =>
				@_menu.onShow?()

			if not @__menu_opts.hasOwnProperty("backdrop")
				@__menu_opts.backdrop = policy: "click-thru"

			if not @__menu_opts.backdrop.hasOwnProperty("blur") and
				@_menu_parent?.getOpt("backdrop")?.blur
					if @_menu_on_hover
						@__menu_opts.backdrop =
							policy: "click-thru"
							blur: true
					else
						@__menu_opts.backdrop.blur = true

			if @_menu_parent
				@__menu_opts.parent_menu = @_menu_parent


		CUI.Events.listen
			type: "keydown"
			node: @DOM
			capture: true
			call: (ev) =>
				if ev.hasModifierKey()
					return

				if ev.keyCode() in [13, 32]
					#space / return
					@onClickAction(ev)
					ev.stop()
					return

				if ev.keyCode() == 27
					# blur button
					@DOM.blur()
					ev.stop()
					return

				el = null

				right = =>
					el = CUI.dom.findNextVisibleElement(@DOM, "[tabindex]")

				left = =>
					el = CUI.dom.findPreviousVisibleElement(@DOM, "[tabindex]")

				switch ev.keyCode()
					when 39 # right cursor
						right()
					when 40 # down cursor
						right()
					when 37 # left cursor
						left()
					when 38 # up cursor
						left()

				if el
					el.focus()
					ev.stop()

				return

		CUI.Events.listen
			type: CUI.Button.clickTypesPrevent[@_click_type]
			node: @DOM
			call: (ev) =>
				ev.preventDefault()
				# ev.stopPropagation()
				return

		CUI.Events.listen
			type: CUI.Button.clickTypes[@_click_type]
			node: @DOM
			call: (ev) =>

				if CUI.globalDrag
					# ev.stop()
					return

				if ev.getButton() != 0 and not ev.getType().startsWith("touch")
					# ev.stop()
					return

				ev.stopPropagation()
				@onClickAction(ev)
				return

		if @_menu_on_hover
			CUI.Button.menu_timeout = null

			menu_stop_hide = =>
				if not CUI.Button.menu_timeout
					return

				CUI.clearTimeout(CUI.Button.menu_timeout)
				CUI.Button.menu_timeout = null

			menu_start_hide = (ev, ms=700) =>
				menu_stop_hide()
				# we set a timeout, if during the time
				# the focus enters the menu, we cancel the timeout
				CUI.Button.menu_timeout = CUI.setTimeout
					ms: ms
					call: =>
						@getMenu().hide(ev)

		if @_menu_on_hover or @__tooltipOpts or @_onMouseenter
			CUI.Events.listen
				type: "mouseenter"
				node: @DOM
				call: (ev) =>

					if CUI.globalDrag
						return

					@_onMouseenter?(ev)
					if ev.isImmediatePropagationStopped()
						return

					if @__tooltipOpts
						@__initTooltip()
						@getTooltip().showTimeout().start()

					if @_menu_on_hover
						menu = @getMenu()
						menu_stop_hide()

						if not @__disabled and menu.hasItems(ev)

							menu_shown = CUI.dom.data(CUI.dom.find(".cui-button--hover-menu")[0], "element")
							if menu_shown and menu_shown != menu
								menu_shown.hide(ev)

							CUI.dom.addClass(menu.DOM, "cui-button--hover-menu")

							CUI.Events.ignore
								instance: @
								node: menu

							CUI.Events.listen
								type: "mouseenter"
								node: menu
								instance: @
								only_once: true
								call: =>
									menu_stop_hide()

							CUI.Events.listen
								type: "mouseleave"
								node: menu
								instance: @
								only_once: true
								call: =>
									menu_start_hide(ev)

							menu.show(ev)

					return

		CUI.Events.listen
			type: "mouseleave"
			node: @DOM
			call: (ev) =>
				# @__prevent_btn_click = false

				if CUI.globalDrag
					return

				@_onMouseleave?(ev)

				if @_menu_on_hover
					menu_start_hide(ev, 100)

				return

	setSize: (size) ->

		remove = []
		for cls in @DOM.classList
			if cls.startsWith("cui-button-size")
				remove.push(cls)

		for cls in remove
			@DOM.classList.remove(cls)

		if size
			@DOM.classList.add("cui-button-size-"+size)
		@

	onClickAction: (ev) ->
		if @__disabled # or ev.button != 0
			ev.preventDefault()
			return

		@getTooltip()?.hide(ev)

		if @__radio
			if @__radio_allow_null
				@toggle({}, ev)
			else
				@activate({}, ev)

		if @hasMenu() and
			# not (ev.ctrlKey or ev.shiftKey or ev.altKey or ev.metaKey) and
			not @_menu_on_hover and
			@getMenu().hasItems(ev)
				@getMenu().show(ev)

				# in some contexts (like FileUploadButton), this
				# is necessary, so we stop the file upload
				# to open
				#
				ev.preventDefault()
				return

		if ev.isImmediatePropagationStopped()
			return

		CUI.Events.trigger
			type: "cui-button-click"
			node: @
			info:
				event: ev

		if ev.isImmediatePropagationStopped() or not @_onClick
			return

		@_onClick.call(@, ev, @)

	initOpts: ->
		super()
		@addOpts
			tabindex:
				default: 0
				check: (v) ->
					CUI.util.isInteger(v) or v == false
			size:
				check: ["mini","normal","big","bigger"]
			appearance:
				check: ["link","flat","normal","important","transparent-border"]
			primary:
				mandatory: true
				default: false
				check: Boolean
			onClick:
				check: Function
			click_type:
				default: "click" # "touchend"
				mandatory: true
				check: (v) ->
					!!CUI.Button.clickTypes[v]

			text:
				check: String
			tooltip:
				check: "PlainObject"
			disabled:
				default: false
				check: (v) ->
					CUI.util.isBoolean(v) or CUI.util.isFunction(v)
			loading:
				default: false
				check: (v) ->
					CUI.util.isBoolean(v) or CUI.util.isFunction(v)
			active_css_class:
				default: CUI.defaults.class.Button.defaults.active_css_class
				check: String
			left:
				check: (v) ->
					if v == true
						return true

					(CUI.util.isElement(v) or
						v instanceof CUI.Element or
						CUI.util.isString(v)) and
						not @_icon and
						not @_icon_left and
						not @_icon_active and
						not @_icon_inactive
			right:
				check: (v) ->
					(v == true or CUI.util.isContent(v)) and not @_icon_right
			center:
				check: (v) ->
					CUI.util.isContent(v) or CUI.util.isString(v)
			icon:
				check: (v) ->
					v instanceof CUI.Icon or CUI.util.isString(v)
			icon_left:
				check: (v) ->
					v instanceof CUI.Icon or CUI.util.isString(v)
			icon_right:
				check: (v) ->
					v instanceof CUI.Icon or CUI.util.isString(v) or v == false
			icon_active:
				check: (v) ->
					v instanceof CUI.Icon or CUI.util.isString(v)
			icon_inactive:
				check: (v) ->
					v instanceof CUI.Icon or CUI.util.isString(v)
			text_active:
				check: String
			text_inactive:
				check: String
			value: {}
			name:
				check: String
			hidden:
				check: (v) ->
					CUI.util.isBoolean(v) or CUI.util.isFunction(v)
			menu:
				check: "PlainObject"
			menu_on_hover:
				check: Boolean
			menu_parent:
				check: CUI.Menu
			onActivate:
				check: Function
			onDeactivate:
				check: Function
			onMouseenter:
				check: Function
			onMouseleave:
				check: Function

			# if set, this button belongs
			# to a group of buttons
			# on click, the active state of
			# this button will be set and unset
			# on the others
			radio:
				check: (v) ->
					CUI.util.isString(v) or v == true
			# whether to allow de-select
			# on radio buttons
			radio_allow_null:
				check: Boolean
			switch:
				check: Boolean
			active:
				check: Boolean
			# set to false to skip running onActivate and onDeactivate
			# callbacks on initial activate/deactivate when the button is
			# created
			activate_initial:
				default: true
				check: Boolean
			#group can be used for buttonbars to specify a group css style
			group:
				check: String
			role:
				default: "button"
				mandatory: true
				check: String

	# return icon for string
	__getIcon: (icon) ->
		if not icon
			null
		else if icon instanceof CUI.Icon
			icon
		else
			new CUI.Icon(icon: icon)

	readOpts: ->

		if @opts.switch
			CUI.util.assert(CUI.util.isUndef(@opts.radio_allow_null), "new CUI.Button", "opts.switch cannot be used together with opts.radio_allow_null", opts: @opts)

		super()

		if @_left
			CUI.util.assert(@_left == true or not (@_icon_active or @_icon_inactive or @_icon), "new CUI.Button", "opts.left != true cannot be used togeter with opts.icon*", opts: @opts)


	getCenter: ->
		return @__box.map.center;

	__getTemplateName: ->
		if @_icon or @_icon_left or @_icon_active or @_icon_inactive or @_left
			@__has_left = true
		else
			@__has_left = false

		if @_icon_right or (@_menu and @_icon_right != false) or @_right
			@__has_right = true
		else
			@__has_right = false

		if @__has_left and
			CUI.util.isUndef(@_text) and
			CUI.util.isUndef(@_center) and
			CUI.util.isUndef(@_text_active) and
			CUI.util.isUndef(@_text_inactive) and
			CUI.__ng__
				@__has_center = false

		if @__has_left and @__has_right
			return "button"
		else if @__has_left
			if @__has_center
				return "button-left-center"
			else
				return "button-left"
		else if @__has_right
			return "button-center-right"
		else
			return "button-center"

	getTemplateName: ->
		if CUI.__ng__
			@__getTemplateName() + "-ng"
		else
			@__getTemplateName()

	getValue: ->
		@_value

	getElementForLayer: ->
		return @__box.map.visual

	getRadioButtons: ->
		if not @__radio
			return []
		@__getButtons("radio", @__radio)

	getGroupButtons: ->
		if not @getGroup()
			return []
		@__getButtons("button-group", @getGroup())

	# returns other buttons
	__getButtons: (key, value) ->
		parents = CUI.dom.parents(@DOM, ".cui-buttonbar,.cui-form-table,.cui-item-list-body,.cui-layer")

		if parents.length == 0
			# buttons are not grouped by anything, so we
			# have no other buttons, so we use the top level element
			parents = CUI.dom.parents(@DOM)

		if parents.length > 0
			docElem = parents[parents.length-1]
		else
			return []

		(CUI.dom.data(c, "element") for c in CUI.dom.matchSelector(docElem, ".cui-button[#{key}=\"#{value}\"]"))


	hasMenu: ->
		!!@__menu_opts

	hasLeft: ->
		@__has_left

	getMenu: ->
		if not @hasMenu()
			return

		if @__menu
			@__menu
		else
			@__menu = new CUI.Menu(@__menu_opts)

	menuSetActiveIdx: (idx) ->
		if @__menu
			@__menu.setActiveIdx(idx)
		else
			@__menu_opts.itemList.active_item_idx = idx
		@

	getMenuRootButton: ->
		if @_menu_parent
			return @_menu_parent.getButton()?.getMenuRootButton()
		else if @hasMenu()
			return @
		else
			null

	#TODO rename to toggleActiveState
	toggle: (flags={}, event) ->
		@setActive(not @__active, flags, event)

	setActive: (active, flags={}, event) ->
		if active
			@activate(flags, event)
		else
			@deactivate(flags, event)

	__callOnGroup: (call, flags, event) ->

		group = @getGroup()
		if not group or not event?.hasModifierKey() or flags.ignore_ctrl
			return

		flags.ignore_ctrl = true
		if not event.altKey()
			started = true
		else
			started = false

		for btn in @getGroupButtons()
			if btn == @
				started = true
				continue
			if not started
				continue
			btn[call](flags, event)
		return

	activate: (flags={}, event) ->
		# console.error "activate", flags, @getUniqueId(), @__active, @_activate_initial

		activate = =>
			@addClass(@_active_css_class)
			@setAria("pressed", true)
			@__setState()
			@__callOnGroup("activate", flags, event)
			return

		if @_activate_initial == false and flags.initial_activate
			@__active = true
			activate()
			return @

		if @__active == true and CUI.util.isEmptyObject(flags)
			return @

		if @__radio
			_flags =
				prior_activate: @
				initial_activate: flags.initial_activate

			for btn, idx in @getRadioButtons()
				if btn == @ or not btn.isActive()
					continue

				# don't send the event here, since this happens
				# code driven
				btn.deactivate(_flags)

		@__active = true
		ret = @_onActivate?(@, flags, event)
		if CUI.util.isPromise(ret)
			ret.done(activate).fail =>
				@__active = false
			return ret

		activate()
		@


	deactivate: (flags={}, event) ->
		# console.error "deactivate", flags, @getUniqueId(), @__active, @_activate_initial, @_icon_inactive

		deactivate = =>
			@removeClass(@_active_css_class)
			@setAria("pressed", false)
			@__setState()
			@__callOnGroup("deactivate", flags, event)
			return

		if @_activate_initial == false and flags.initial_activate
			@__active = false
			deactivate()
			return @

		if @__active == false and CUI.util.isEmptyObject(flags)
			return @

		@__active = false
		ret = @_onDeactivate?(@, flags, event)
		if CUI.util.isPromise(ret)
			ret.done(deactivate).fail =>
				@__active = true
			return ret

		deactivate()
		@

	setIconRight: (icon=null) ->
		@setIcon(icon, "right")

	setIcon: (icon=null, _key="left") ->
		key = "__icon_"+_key

		if icon == ""
			@[key] = ""
		else
			@[key] = @__getIcon(icon)

		CUI.util.assert(@[key] == null or @[key] == "" or @[key] instanceof CUI.Icon, "CUI.Button.setIcon", "icon needs to be instance of Icon", icon: icon)

		if @[key] == null
			@empty(_key)
		else if @[key] == ""
			@replace(CUI.dom.element("SPAN"), _key)
		else
			@replace(@[key], _key)
		@

	startSpinner: ->
		CUI.util.assert(@__has_left, "CUI.Button.startSpinner", "No space for Icon found, make sure the Button was created with opts.left set.", opts: @opts)
		if @__hasSpinner
			return

		@__iconBeforeSpinner = @getIcon()
		@__hasSpinner = true
		@setIcon("spinner")
		@

	stopSpinner: ->
		@setIcon(@__iconBeforeSpinner)
		@__hasSpinner = false
		@__iconBeforeSpinner = null
		@

	getIcon: ->
		@__icon_left

	getIconRight: ->
		@__icon_right

	__setState: ->
		@__setIconState()
		@__setTextState()

	__setIconState: ->
		if not (@_icon_active or @_icon_inactive)
			return @

		if @isActive()
			if not @_icon_active
				@setIcon("")
			else
				@setIcon(@_icon_active)
		else
			if not @_icon_inactive
				@setIcon("")
			else
				@setIcon(@_icon_inactive)
		@

	__setTextState: ->
		if not (@_text_active or @_text_inactive)
			return @

		if @isActive()
			if not CUI.util.isNull(@_text_active)
				@setText(@_text_active)
		else
			if not CUI.util.isNull(@_text_inactive)
				@setText(@_text_inactive)
		@


	isActive: ->
		!!@__active

	isDisabled: ->
		@__disabled

	isEnabled: ->
		not @__disabled

	setEnabled: (enabled) ->
		if enabled
			@enable()
		else
			@disable()

	setLoading: (on_off) ->
		if on_off
			CUI.dom.addClass(@DOM, CUI.defaults.class.Button.defaults.loading_css_class)
			@__loading = true
		else
			CUI.dom.removeClass(@DOM, CUI.defaults.class.Button.defaults.loading_css_class)
			@__loading = false

	isLoading: ->
		@__loading

	disable: ->
		CUI.dom.addClass(@DOM, CUI.defaults.class.Button.defaults.disabled_css_class)
		CUI.dom.removeAttribute(@DOM, "tabindex")
		@__disabled = true
		@

	enable: ->
		CUI.dom.removeClass(@DOM, CUI.defaults.class.Button.defaults.disabled_css_class)
		CUI.dom.setAttribute(@DOM, "tabindex", @_tabindex)
		@__disabled = false
		@

	setText: (@__txt) ->
		if CUI.util.isEmpty(@__txt)
			@__txt = ''

		span = CUI.dom.text(@__txt)
		if not @__hasAriaLabel
			span.id = "button-text-"+@getUniqueId()
			@setAria("labelledby", span.id)
		@replace(span, "center")

	setTextMaxChars: (max_chars) ->
		CUI.dom.setAttribute(@getCenter().firstChild, "data-max-chars", max_chars)

	getText: ->
		@__txt

	getGroup: ->
		@__group

	setGroup: (@__group) ->
		if @__group
			CUI.dom.setAttribute(@DOM, "button-group", @__group)
		else
			CUI.dom.removeAttribute(@DOM, "button-group")

	__initTooltip: ->
		if @__tooltip and not @__tooltip.isDestroyed()
			return @

		tt_opts = CUI.util.copyObject(@__tooltipOpts)

		tt_opts.element ?= @DOM

		# make sure the tooltip does not register any listeners
		for k in ["on_hover", "on_click"]
			CUI.util.assert(not tt_opts.hasOwnProperty(k), "CUI.Button.__initTooltip", "opts.tooltip cannot contain #{k}.", opts: @opts)
			tt_opts[k] = false

		@__tooltip = new CUI.Tooltip(tt_opts)
		@

	getTooltip: ->
		@__tooltip

	isShown: ->
		not @isHidden()

	isHidden: ->
		@__hidden

	destroy: ->
		# console.debug "destroying button", @__uniqueId, @getText()
		@__menu?.destroy()
		@__menu = null
		@__tooltip?.destroy()
		@__tooltip = null
		super()

	show: ->
		@__hidden = false
		CUI.dom.removeClass(@DOM, "cui-button-hidden")
		CUI.dom.showElement(@DOM)
		CUI.Events.trigger
			type: "show"
			node: @DOM

	hide: ->
		@__hidden = true
		CUI.dom.addClass(@DOM, "cui-button-hidden")
		CUI.dom.hideElement(@DOM)
		CUI.Events.trigger
			type: "hide"
			node: @DOM

	@clickTypes:
		click: ['click']
		mouseup: ['mouseup']
		dblclick: ['dblclick']

	@clickTypesPrevent:
		click: ['dblclick', 'mousedown']
		mouseup: ['mouseup', 'mousedown']
		dblclick: ['click', 'mousedown']

CUI.defaults.class.Button = CUI.Button

CUI.Events.registerEvent
	type: ["show", "hide", "cui-button-click"]
	bubble: true
