Delegator = require 'dom-delegator'
extend    = require 'xtend'
immupdate = require 'immupdate'

# ~ export virtual-dom and h, just because it is cheap
module.exports['virtual-dom'] = require 'virtual-dom'
module.exports.h              = require 'virtual-dom/h'
# /

# ~ event handlers
module.exports.sendEvent = require 'value-event/event'
module.exports.sendValue = require 'value-event/value'
module.exports.sendClick = require 'value-event/click'
module.exports.sendSubmit = require 'value-event/submit'
module.exports.sendChange = require 'value-event/change'
module.exports.sendKey = require 'value-event/key'
module.exports.sendDetail = (require 'value-event/base-event') (ev, broadcast) ->
  detail = ev._rawEvent.detail
  data = extend detail, this.data
  broadcast data
# /

# ~ state factory
class TalioState
  type: 'TalioState'
  constructor: (@state) ->
  silentlyUpdate: ->
    u = immupdate.bind @, @state
    @state = u.apply @, arguments
  change: ->
    @silentlyUpdate.apply @, arguments
    @cb(@state) if @cb
  subscribe: (cb) -> @cb = cb
  itself: -> @state
  get: (prop) ->
    ret = @state
    try
      for degree in prop.split '.'
        ret = ret[degree]
    catch e
    return ret
module.exports.StateFactory = (dict) -> new TalioState dict
# / state factory

# ~ mainloop
raf = require('raf')
TypedError = require('error/typed')
InvalidUpdateInRender = TypedError(
  type: 'talio.invalid.update.in-render'
  message: 'talio: Unexpected update occurred in loop.\n' + 'We are currently rendering a view, ' + 'you can\'t change state right now.\n' + 'The diff is: {stringDiff}.\n' + 'SUGGESTED FIX: find the state mutation in your view ' + 'or rendering function and remove it.\n' + 'The view should not have any side effects.\n'
  diff: null
  stringDiff: null)

mainloop = (initialState, view, channels, opts) ->
  opts = opts or {}
  currentState = initialState
  create = opts.create
  diff = opts.diff
  patch = opts.patch
  redrawScheduled = false

  tree = opts.initialTree or view(currentState, channels)
  target = opts.target or create(tree, opts)
  inRenderingTransaction = false
  currentState = null

  update = (state) ->
    if inRenderingTransaction
      throw InvalidUpdateInRender(
        diff: state._diff
        stringDiff: JSON.stringify(state._diff))
    if currentState == null and !redrawScheduled
      redrawScheduled = true
      raf redraw

    currentState = state
    return

  redraw = ->
    redrawScheduled = false
    return if currentState == null
    inRenderingTransaction = true
    try
      newTree = view(currentState, channels)
    catch e
      console.error "We had a problem while rendering the tree with the following state:", currentState
      console.debug "Aborting the render."
      inRenderingTransaction = false
      newTree = tree
      console.debug e.stack

    if opts.createOnly
      inRenderingTransaction = false
      create newTree, opts
    else
      patches = diff(tree, newTree, opts)
      inRenderingTransaction = false
      target = patch(target, patches, opts)
    tree = newTree
    currentState = null
    return

  return {
    target: target
    update: update
  }
# / mainloop

# ~ the function that starts everything
module.exports.Delegator = Delegator
module.exports.delegator = Delegator()
module.exports.run = (domnode, vrender, handlers, BaseState) ->
  # create a blank state if none provided
  if not BaseState or BaseState.type != 'TalioState'
    BaseState = new TalioState {}

  # allocate handlers in the dom-delegator for the supplied channels
  createChannel = (acc, name) ->
    acc[name] = Delegator.allocateHandle(
      handlers[name].bind(handlers, BaseState)
    )
    return acc
  channels = Object.keys(handlers).reduce createChannel, {}

  theloop = mainloop(BaseState.itself(), vrender, channels,
    diff: require 'virtual-dom/vtree/diff'
    patch: require 'virtual-dom/vdom/patch'
    create: require 'virtual-dom/vdom/create-element'
  )
  domnode.appendChild theloop.target
  BaseState.subscribe (state) -> theloop.update state
# /
