handle message errors

handle message errors

    _self = this

    if console?.log?
     LOG = console.log.bind console
    else
     LOG = -> null

    if console?.error?
     ERROR_LOG = console.error.bind console
    else
     ERROR_LOG = -> null

    POLL_TYPE =
     progress: true

##Response class

    class Response
     constructor: (data, port, options) ->
      @id = data.id
      @port = port
      @options = options
      @queue = []
      @fresh = true

     progress: (progress, data, callback) ->
      @queue.push
       method: 'progress'
       data: data
       options: {progress: progress}
       callback: callback
      @_handleQueue()

     success: (data) ->
      @queue.push method: 'success', data: data, options: {}
      @_handleQueue()

     fail: (data) ->
      @queue.push method: 'fail', data: data, options: {}
      @_handleQueue()

     setOptions: (options) ->
      @options = options
      @fresh = true
      @_handleQueue()

     _multipleResponse: ->
      responseList = []
      callbacks = []
      for d in @queue
       responseList.push
        status: d.method
        data: d.data
        options: d.options
       if d.callback?
        callbacks.push d.callback

      done = ->
       for callback in callbacks
        callback()

      if @port.isStreaming
       @port.respondMultiple this, responseList, @options, done
      else if @fresh
       @port.respondMultiple this, responseList, @options, done
       @fresh = false

      @queue = []

     _handleQueue: ->
      return unless @queue.length > 0
      return if not @queue.isStreaming and not @fresh

      if @queue.length > 1
       return @_multipleResponse()

      d = @queue[0]

      if @port.isStreaming
       @port.respond this, d.method, d.data, d.options, @options, d.callback
      else if @fresh
       @port.respond this, d.method, d.data, d.options, @options, d.callback
       @fresh = false

      @queue = []

##Call class

    class Call
     constructor: (@id, @method, @data, @callbacks, @options) -> null

     handle: (data, options) ->
      if not @callbacks[options.status]?
       return if options.status is 'progress'
       #Handled by caller
       throw new Error "No callback registered #{@method} #{options.status}"
      self = this
      setTimeout (-> self.callbacks[options.status] data, options), 0

      if POLL_TYPE[options.status]?
       return false
      else
       return true

##Port base class

    class Port
     constructor: ->
      @handlers = {}
      @callsCache = {}
      @callsCounter = 0
      @id = parseInt Math.random() * 1000
      @responses = {}
      @wrappers =
       send: []
       respond: []
       handleCall: []
       handleResponse: []

     isStreaming: true

     onCallError: (msg, options) ->
      callsCache = @callsCache
      @callsCache = {}
      for id, call of callsCache
       if not call.callbacks.fail?
        ERROR_LOG 'fail callback not registered', call.method, call.data
       else
        call.callbacks.fail error: 'connectionError', msg: msg, options: options, {}

      @errorCallback msg, options

     onHandleError: (msg, data, options) ->
      @errorCallback msg, data
      response = new Response data, this, options
      response.fail msg

     errorCallback: (msg, options) ->
      ERROR_LOG msg, options

     wrap: (wrapper) ->
      for key, f of wrapper
       if @wrappers[key]?
        @wrappers[key].push f
       else
        this[key] = f

###Send RPC call

     send: (method, data, callbacks, options = {}) ->
      if (typeof callbacks) is 'function'
       callbacks =
        success: callbacks

      for f in @wrappers.send
       return unless f.apply this, [method, data, callbacks, options]

      params = @_createCall method, data, callbacks, options
      @_send params, callbacks

###Respond to a RPC call

     respond: (response, status, data, options = {}, portOptions = {}, callback = null) ->
      if not POLL_TYPE[status]?
       delete @responses[response.id]

      for f in @wrappers.respond
       return unless f.apply this, [response, status, data, options, portOptions]

      @_respond (@_createResponse response, status, data, options), portOptions, callback

     respondMultiple: (response, list, portOptions = {}, callback = null) ->
      for d in list
       if not POLL_TYPE[d.status]?
        delete @responses[response.id]
        break

      data = []
      for d in list
       cancel = false
       d.options ?= {}
       for f in @wrappers.respond
        r = f.apply this, [response, d.status, d.data, d.options, portOptions]
        if not r
         cancel = true
         break

        continue if cancel

       data.push @_createResponse response, d.status, d.data, d.options

      return if data.length is 0

      data =
       type: 'list'
       list: data

      @_respond data, portOptions, callback

###Create Call object
This is a private function

     _createCall: (method, data, callbacks, options) ->
      call = new Call "#{@id}-#{@callsCounter}", method, data, callbacks, options
      @callsCounter++
      @callsCache[call.id] = call

      params =
       type: 'call'
       id: call.id
       method: call.method
       data: call.data

      params[k] = v for k, v of options
      return params

###Create Response object

     _createResponse: (response, status, data, options) ->
      params =
       type: 'response'
       id: response.id
       status: status
       data: data

      params[k] = v for k, v of options

      return params

###Add handler

     on: (method, callback) ->
      @handlers[method] = callback

###Handle incoming message

     _handleMessage: (data, options, last = true) ->
      switch data.type
       when 'list'
        for d, i in data.list
         @_handleMessage d, options, (last and i + 1 is data.list.length)
       when 'response'
        try
         @_handleResponse data, options, last
        catch e
         @onerror? e
       when 'call'
        try
         @_handleCall data, options, last
        catch e
         @onerror? e
       when 'poll'
        try
         @_handlePoll data, options, last
        catch e
         @onerror? e

     _handleCall: (data, options) ->
      for f in @wrappers.handleCall
       return unless f.apply this, arguments
      if not @handlers[data.method]?
       @onHandleError "Unknown method: #{data.method}", data, options
       return
      @responses[data.id] = new Response data, this, options
      @handlers[data.method] data.data, data, @responses[data.id]

     _handleResponse: (data, options) ->
      for f in @wrappers.handleResponse
       return unless f.apply this, arguments
      if not @callsCache[data.id]?
       #Cannot reply
       @errorCallback "Response without call: #{data.id}", data
       return
      try
       if @callsCache[data.id].handle data.data, data
        delete @callsCache[data.id]
      catch e
       @errorCallback e.message, data
       delete @callsCache[data.id]

     _handlePoll: (data, options) ->
      @onHandleError "Poll not implemented", data, options

##WorkerPort class
Used for browser and worker

    class WorkerPort extends Port
     constructor: (worker) ->
      super()
      @worker = worker
      @worker.onmessage = @_onMessage.bind this
      #@worker.onerror = @onCallError.bind this

     _send: (data) ->
      @worker.postMessage data

     _respond: (data, options, callback) ->
      @worker.postMessage data
      callback?()

     _onMessage: (e) ->
      data = e.data
      @_handleMessage data

##FramePort class
Used for browser and worker

    class FramePort extends Port
     constructor: (source, dest) ->
      super()
      @source = source
      @dest = dest
      @source.addEventListener 'message', @_onMessage.bind this

     _send: (data) ->
      @dest.postMessage data, '*'

     _respond: (data, options, callback) ->
      @dest.postMessage data, '*'
      callback?()

     _onMessage: (e) ->
      data = e.data
      @_handleMessage data

##SocketPort class

    class SocketPort extends Port
     constructor: (socket) ->
      super()
      @socket = socket
      @socket.on 'message', @_onMessage.bind this

     _send: (data) ->  @socket.emit 'message', data
     _respond: (data, options, callback) ->
      @worker.emit 'message', data
      callback?()

     _onMessage: (e) ->
      data = e.data
      @_handleMessage data

##ServerSocketPort class

    class ServerSocketPort extends Port
     constructor: (server) ->
      super()
      @server = server
      @server.on 'connection', @_onConnection.bind this

     _onConnection: (socket) ->
      new SocketPort handlers: @handlers, socket

##AJAX class

    class AjaxHttpPort extends Port
     constructor: (options) ->
      super()
      @protocol = options.protocol
      @host = options.host
      @port = options.port
      @path = options.path ? '/'
      @url = @path
      if @protocol?
       if @host? and not @port?
        @url = "#{@protocol}://#{@host}#{@path}"
       else if @host? and @port?
        @url = "#{@protocol}://#{@host}:#{@port}#{@path}"
      else
       if @host? and not @port?
        @url = "//#{@host}#{@path}"
       else if @host? and @port?
        @url = "//#{@host}:#{@port}#{@path}"

     isStreaming: false

     _onRequest: (xhr) ->
      return unless xhr.readyState is 4
      status = xhr.status
      if ((not status and xhr.responseText? and xhr.responseText != '') or
          (status >= 200 and status < 300) or
          (status is 304))
       try
        jsonData = JSON.parse xhr.responseText
       catch e
        @onCallError 'ParseError', e
        return

       @_handleMessage jsonData, xhr: xhr
      else
       @onCallError 'Cannot connect to server'

     _respond: (data, options, callback) ->
      @errorCallback 'AJAX cannot respond', data
      callback?()

     _send: (data) ->
      data = JSON.stringify data
      xhr = new XMLHttpRequest
      xhr.open 'POST', @url
      xhr.onreadystatechange = =>
       @_onRequest xhr
      xhr.setRequestHeader 'Accept', 'application/json'
      #xhr.setRequestHeader 'Content-Type', 'application/json'
      xhr.send data

     _handleResponse: (data, options, last = true) ->
      for f in @wrappers.handleResponse
       return unless f.apply this, arguments
      if not @callsCache[data.id]?
       @errorCallback "Response without call: #{data.id}", data
       return
      call = @callsCache[data.id]
      try
       if call.handle data.data, data
        delete @callsCache[data.id]
       else if last
        params =
         type: 'poll'
         id: call.id
        params[k] = v for k, v of call.options
        @_send params
      catch e
       @errorCallback e.message, data
       delete @callsCache[data.id]

##NodeHttpPort class

    class NodeHttpPort extends Port
     constructor: (options, http) ->
      super()
      @host = options.host ? 'localhost'
      @port = options.port ? 80
      @path = options.path ? '/'
      @http = http
      @_createHttpOptions()

     isStreaming: false

     _createHttpOptions: ->
      @httpOptions =
       hostname: @host
       port: @port
       path: @path
       method: 'POST'
       agent: false
       headers:
        accept: 'application/json'
       'content-type': 'application/json'

     _onRequest: (res) ->
      data = ''
      #LOG 'STATUS: ' + res.statusCode
      #LOG 'HEADERS: ' + JSON.stringify res.headers
      res.setEncoding 'utf8'
      res.on 'data', (chunk) ->
       data += chunk
      #LOG 'result', res
      res.on 'end', =>
       try
        jsonData = JSON.parse data
       catch e
        @onCallError 'ParseError', e
        return

       @_handleMessage jsonData, response: res

     _respond: (data, options, callback) ->
      data = JSON.stringify data
      res = options.response
      res.setHeader 'content-length', Buffer.byteLength data, 'utf8'
      if callback?
       res.once 'finish', ->
        callback()
       res.once 'close', ->
        callback()

      res.write data
      res.end()

     _send: (data, callbacks) ->
      data = JSON.stringify data
      options = @httpOptions
      options.headers['content-length'] = Buffer.byteLength data, 'utf8'

      req = @http.request options, @_onRequest.bind this
      delete options.headers['content-length']
      req.on 'error', (e) ->
       callbacks.fail? e

      req.write data
      req.end()

     _handleResponse: (data, options, last = true) ->
      for f in @wrappers.handleResponse
       return unless f.apply this, arguments
      if not @callsCache[data.id]?
       @errorCallback "Response without call: #{data.id}", data
       return
      call = @callsCache[data.id]
      if not call.handle data.data, data
       return if not last
       params =
        type: 'poll'
        id: call.id
       params[k] = v for k, v of call.options
       @_send params, call.callbacks

##NodeHttpServerPort class

    class NodeHttpServerPort extends Port
     constructor: (options, http, zlib = null) ->
      super()
      @port = options.port
      @http = http
      @zlib = zlib
      @allowOrigin = options.allowOrigin

     isStreaming: false

     _onRequest: (req, res) ->
      data = ''
      res.setHeader 'content-type', 'application/json'

      req.on 'data', (chunk) ->
       data += chunk
      req.on 'end', =>
       try
        jsonData = JSON.parse data
       catch e
        @errorCallback 'ParseError', e
        return

       @_handleMessage jsonData, response: res, request: req

     _respond: (data, options, callback) ->
      if @allowOrigin?
       options.response.setHeader 'Access-Control-Allow-Origin', @allowOrigin

      accept = options.request.headers['accept-encoding']
      accept ?= ''
      if not @zlib?
       accept = ''

      data = JSON.stringify data
      buffer = new Buffer data, 'utf8'
      if accept.match /\bdeflate\b/
       options.response.setHeader 'content-encoding', 'deflate'
       @zlib.deflate buffer, (err, result) =>
        if err?
         return @errorCallback 'DeflateError', e
        @_sendBuffer result, options.response, callback
      else if accept.match /\bgzip\b/
       options.response.setHeader 'content-encoding', 'gzip'
       @zlib.gzip buffer, (err, result) =>
        if err?
         return @errorCallback 'GZipeError', e
        @_sendBuffer result, options.response, callback
      else
       @_sendBuffer buffer, options.response, callback

     _sendBuffer: (buf, res, callback) ->
       res.setHeader 'content-length', buf.length
       if callback?
        res.once 'finish', ->
         callback()
        res.once 'close', ->
         callback()

       res.write buf
       res.end()

     listen: ->
      @server = @http.createServer @_onRequest.bind this
      @server.listen @port

     _handlePoll: (data, options) ->
      for f in @wrappers.handleCall
       return unless f.apply this, arguments
      if not @responses[data.id]?
       @onHandleError "Poll without response: #{data.id}", data, options
       return
      @responses[data.id].setOptions options

##NodeHttpsServerPort class

    class NodeHttpsServerPort extends NodeHttpServerPort
     constructor: (options, http) ->
      super options, http
      @_key = options.key
      @_cert = options.cert

     listen: ->
      options =
       key: @_key
       cert: @_cert

      @server = @http.createServer options, @_onRequest.bind this
      @server.listen @port

#IO Module

    IO =
     addPort: (name, port) ->
      IO[name] = port

     ports:
      WorkerPort: WorkerPort
      FramePort: FramePort
      SocketPort: SocketPort
      AjaxHttpPort: AjaxHttpPort
      NodeHttpPort: NodeHttpPort
      NodeHttpServerPort: NodeHttpServerPort
      NodeHttpsServerPort: NodeHttpsServerPort
      ServerSocketPort: ServerSocketPort

     Helpers:
      progress: (from, to, func) ->
       (progress, data) ->
        func? progress * (to - from) + from, data

     setup_____: (options) ->
      options.workers ?= []
      switch options.platform
       when 'ui'
        for worker in options.workers
         IO.addPort worker.name, new WorkerPort worker.js
        for socket in options.sockets
         IO.addPort socket.name, new SocketPort socket.url
       when 'node'
        for socket in options.listenSockets
         IO.addPort socket.name, new SocketListenPort socket.port
        for socket in options.sockets
         IO.addPort socket.name, new SocketPort socket.url
       when 'worker'
        IO.addPort 'UI', new WorkerListenPort _self

    if exports?
     module.exports = IO
    else
     _self.IO = IO

    _self = this

    if console?.log?
     LOG = console.log.bind console
    else
     LOG = -> null

    if console?.error?
     ERROR_LOG = console.error.bind console
    else
     ERROR_LOG = -> null

    POLL_TYPE =
     progress: true

##Response class

    class Response
     constructor: (data, port, options) ->
      @id = data.id
      @port = port
      @options = options
      @queue = []
      @fresh = true

     progress: (progress, data, callback) ->
      @queue.push
       method: 'progress'
       data: data
       options: {progress: progress}
       callback: callback
      @_handleQueue()

     success: (data) ->
      @queue.push method: 'success', data: data, options: {}
      @_handleQueue()

     fail: (data) ->
      @queue.push method: 'fail', data: data, options: {}
      @_handleQueue()

     setOptions: (options) ->
      @options = options
      @fresh = true
      @_handleQueue()

     _multipleResponse: ->
      responseList = []
      callbacks = []
      for d in @queue
       responseList.push
        status: d.method
        data: d.data
        options: d.options
       if d.callback?
        callbacks.push d.callback

      done = ->
       for callback in callbacks
        callback()

      if @port.isStreaming
       @port.respondMultiple this, responseList, @options, done
      else if @fresh
       @port.respondMultiple this, responseList, @options, done
       @fresh = false

      @queue = []

     _handleQueue: ->
      return unless @queue.length > 0
      return if not @queue.isStreaming and not @fresh

      if @queue.length > 1
       return @_multipleResponse()

      d = @queue[0]

      if @port.isStreaming
       @port.respond this, d.method, d.data, d.options, @options, d.callback
      else if @fresh
       @port.respond this, d.method, d.data, d.options, @options, d.callback
       @fresh = false

      @queue = []

##Call class

    class Call
     constructor: (@id, @method, @data, @callbacks, @options) -> null

     handle: (data, options) ->
      if not @callbacks[options.status]?
       return if options.status is 'progress'
       #Handled by caller
       throw new Error "No callback registered #{@method} #{options.status}"
      self = this
      setTimeout (-> self.callbacks[options.status] data, options), 0

      if POLL_TYPE[options.status]?
       return false
      else
       return true

##Port base class

    class Port
     constructor: ->
      @handlers = {}
      @callsCache = {}
      @callsCounter = 0
      @id = parseInt Math.random() * 1000
      @responses = {}
      @wrappers =
       send: []
       respond: []
       handleCall: []
       handleResponse: []

     isStreaming: true

     onCallError: (msg, options) ->
      callsCache = @callsCache
      @callsCache = {}
      for id, call of callsCache
       if not call.callbacks.fail?
        ERROR_LOG 'fail callback not registered', call.method, call.data
       else
        call.callbacks.fail error: 'connectionError', msg: msg, options: options, {}

      @errorCallback msg, options

     onHandleError: (msg, data, options) ->
      @errorCallback msg, data
      response = new Response data, this, options
      response.fail msg

     errorCallback: (msg, options) ->
      ERROR_LOG msg, options

     wrap: (wrapper) ->
      for key, f of wrapper
       if @wrappers[key]?
        @wrappers[key].push f
       else
        this[key] = f

###Send RPC call

     send: (method, data, callbacks, options = {}) ->
      if (typeof callbacks) is 'function'
       callbacks =
        success: callbacks

      for f in @wrappers.send
       return unless f.apply this, [method, data, callbacks, options]

      params = @_createCall method, data, callbacks, options
      @_send params, callbacks

###Respond to a RPC call

     respond: (response, status, data, options = {}, portOptions = {}, callback = null) ->
      if not POLL_TYPE[status]?
       delete @responses[response.id]

      for f in @wrappers.respond
       return unless f.apply this, [response, status, data, options, portOptions]

      @_respond (@_createResponse response, status, data, options), portOptions, callback

     respondMultiple: (response, list, portOptions = {}, callback = null) ->
      for d in list
       if not POLL_TYPE[d.status]?
        delete @responses[response.id]
        break

      data = []
      for d in list
       cancel = false
       d.options ?= {}
       for f in @wrappers.respond
        r = f.apply this, [response, d.status, d.data, d.options, portOptions]
        if not r
         cancel = true
         break

        continue if cancel

       data.push @_createResponse response, d.status, d.data, d.options

      return if data.length is 0

      data =
       type: 'list'
       list: data

      @_respond data, portOptions, callback

###Create Call object
This is a private function

     _createCall: (method, data, callbacks, options) ->
      call = new Call "#{@id}-#{@callsCounter}", method, data, callbacks, options
      @callsCounter++
      @callsCache[call.id] = call

      params =
       type: 'call'
       id: call.id
       method: call.method
       data: call.data

      params[k] = v for k, v of options
      return params

###Create Response object

     _createResponse: (response, status, data, options) ->
      params =
       type: 'response'
       id: response.id
       status: status
       data: data

      params[k] = v for k, v of options

      return params

###Add handler

     on: (method, callback) ->
      @handlers[method] = callback

###Handle incoming message

     _handleMessage: (data, options, last = true) ->
      switch data.type
       when 'list'
        for d, i in data.list
         @_handleMessage d, options, (last and i + 1 is data.list.length)
       when 'response'
        @_handleResponse data, options, last
       when 'call'
        @_handleCall data, options, last
       when 'poll'
        @_handlePoll data, options, last

     _handleCall: (data, options) ->
      for f in @wrappers.handleCall
       return unless f.apply this, arguments
      if not @handlers[data.method]?
       @onHandleError "Unknown method: #{data.method}", data, options
       return
      @responses[data.id] = new Response data, this, options
      @handlers[data.method] data.data, data, @responses[data.id]

     _handleResponse: (data, options) ->
      for f in @wrappers.handleResponse
       return unless f.apply this, arguments
      if not @callsCache[data.id]?
       #Cannot reply
       @errorCallback "Response without call: #{data.id}", data
       return
      try
       if @callsCache[data.id].handle data.data, data
        delete @callsCache[data.id]
      catch e
       @errorCallback e.message, data
       delete @callsCache[data.id]

     _handlePoll: (data, options) ->
      @onHandleError "Poll not implemented", data, options

##WorkerPort class
Used for browser and worker

    class WorkerPort extends Port
     constructor: (worker) ->
      super()
      @worker = worker
      @worker.onmessage = @_onMessage.bind this
      #@worker.onerror = @onCallError.bind this

     _send: (data) ->
      @worker.postMessage data

     _respond: (data, options, callback) ->
      @worker.postMessage data
      callback?()

     _onMessage: (e) ->
      data = e.data
      @_handleMessage data

##FramePort class
Used for browser and worker

    class FramePort extends Port
     constructor: (source, dest) ->
      super()
      @source = source
      @dest = dest
      @source.addEventListener 'message', @_onMessage.bind this

     _send: (data) ->
      @dest.postMessage data, '*'

     _respond: (data, options, callback) ->
      @dest.postMessage data, '*'
      callback?()

     _onMessage: (e) ->
      data = e.data
      @_handleMessage data

##SocketPort class

    class SocketPort extends Port
     constructor: (socket) ->
      super()
      @socket = socket
      @socket.on 'message', @_onMessage.bind this

     _send: (data) ->  @socket.emit 'message', data
     _respond: (data, options, callback) ->
      @worker.emit 'message', data
      callback?()

     _onMessage: (e) ->
      data = e.data
      @_handleMessage data

##ServerSocketPort class

    class ServerSocketPort extends Port
     constructor: (server) ->
      super()
      @server = server
      @server.on 'connection', @_onConnection.bind this

     _onConnection: (socket) ->
      new SocketPort handlers: @handlers, socket

##AJAX class

    class AjaxHttpPort extends Port
     constructor: (options) ->
      super()
      @protocol = options.protocol
      @host = options.host
      @port = options.port
      @path = options.path ? '/'
      @url = @path
      if @protocol?
       if @host? and not @port?
        @url = "#{@protocol}://#{@host}#{@path}"
       else if @host? and @port?
        @url = "#{@protocol}://#{@host}:#{@port}#{@path}"
      else
       if @host? and not @port?
        @url = "//#{@host}#{@path}"
       else if @host? and @port?
        @url = "//#{@host}:#{@port}#{@path}"

     isStreaming: false

     _onRequest: (xhr) ->
      return unless xhr.readyState is 4
      status = xhr.status
      if ((not status and xhr.responseText? and xhr.responseText != '') or
          (status >= 200 and status < 300) or
          (status is 304))
       try
        jsonData = JSON.parse xhr.responseText
       catch e
        @onCallError 'ParseError', e
        return

       @_handleMessage jsonData, xhr: xhr
      else
       @onCallError 'Cannot connect to server'

     _respond: (data, options, callback) ->
      @errorCallback 'AJAX cannot respond', data
      callback?()

     _send: (data) ->
      data = JSON.stringify data
      xhr = new XMLHttpRequest
      xhr.open 'POST', @url
      xhr.onreadystatechange = =>
       @_onRequest xhr
      xhr.setRequestHeader 'Accept', 'application/json'
      #xhr.setRequestHeader 'Content-Type', 'application/json'
      xhr.send data

     _handleResponse: (data, options, last = true) ->
      for f in @wrappers.handleResponse
       return unless f.apply this, arguments
      if not @callsCache[data.id]?
       @errorCallback "Response without call: #{data.id}", data
       return
      call = @callsCache[data.id]
      try
       if call.handle data.data, data
        delete @callsCache[data.id]
       else if last
        params =
         type: 'poll'
         id: call.id
        params[k] = v for k, v of call.options
        @_send params
      catch e
       @errorCallback e.message, data
       delete @callsCache[data.id]

##NodeHttpPort class

    class NodeHttpPort extends Port
     constructor: (options, http) ->
      super()
      @host = options.host ? 'localhost'
      @port = options.port ? 80
      @path = options.path ? '/'
      @http = http
      @_createHttpOptions()

     isStreaming: false

     _createHttpOptions: ->
      @httpOptions =
       hostname: @host
       port: @port
       path: @path
       method: 'POST'
       agent: false
       headers:
        accept: 'application/json'
       'content-type': 'application/json'

     _onRequest: (res) ->
      data = ''
      #LOG 'STATUS: ' + res.statusCode
      #LOG 'HEADERS: ' + JSON.stringify res.headers
      res.setEncoding 'utf8'
      res.on 'data', (chunk) ->
       data += chunk
      #LOG 'result', res
      res.on 'end', =>
       try
        jsonData = JSON.parse data
       catch e
        @onCallError 'ParseError', e
        return

       @_handleMessage jsonData, response: res

     _respond: (data, options, callback) ->
      data = JSON.stringify data
      res = options.response
      res.setHeader 'content-length', Buffer.byteLength data, 'utf8'
      if callback?
       res.once 'finish', ->
        callback()
       res.once 'close', ->
        callback()

      res.write data
      res.end()

     _send: (data, callbacks) ->
      data = JSON.stringify data
      options = @httpOptions
      options.headers['content-length'] = Buffer.byteLength data, 'utf8'

      req = @http.request options, @_onRequest.bind this
      delete options.headers['content-length']
      req.on 'error', (e) ->
       callbacks.fail? e

      req.write data
      req.end()

     _handleResponse: (data, options, last = true) ->
      for f in @wrappers.handleResponse
       return unless f.apply this, arguments
      if not @callsCache[data.id]?
       @errorCallback "Response without call: #{data.id}", data
       return
      call = @callsCache[data.id]
      if not call.handle data.data, data
       return if not last
       params =
        type: 'poll'
        id: call.id
       params[k] = v for k, v of call.options
       @_send params, call.callbacks

##NodeHttpServerPort class

    class NodeHttpServerPort extends Port
     constructor: (options, http, zlib = null) ->
      super()
      @port = options.port
      @http = http
      @zlib = zlib
      @allowOrigin = options.allowOrigin

     isStreaming: false

     _onRequest: (req, res) ->
      data = ''
      res.setHeader 'content-type', 'application/json'

      req.on 'data', (chunk) ->
       data += chunk
      req.on 'end', =>
       try
        jsonData = JSON.parse data
       catch e
        @errorCallback 'ParseError', e
        return

       @_handleMessage jsonData, response: res, request: req

     _respond: (data, options, callback) ->
      if @allowOrigin?
       options.response.setHeader 'Access-Control-Allow-Origin', @allowOrigin

      accept = options.request.headers['accept-encoding']
      accept ?= ''
      if not @zlib?
       accept = ''

      data = JSON.stringify data
      buffer = new Buffer data, 'utf8'
      if accept.match /\bdeflate\b/
       options.response.setHeader 'content-encoding', 'deflate'
       @zlib.deflate buffer, (err, result) =>
        if err?
         return @errorCallback 'DeflateError', e
        @_sendBuffer result, options.response, callback
      else if accept.match /\bgzip\b/
       options.response.setHeader 'content-encoding', 'gzip'
       @zlib.gzip buffer, (err, result) =>
        if err?
         return @errorCallback 'GZipeError', e
        @_sendBuffer result, options.response, callback
      else
       @_sendBuffer buffer, options.response, callback

     _sendBuffer: (buf, res, callback) ->
       res.setHeader 'content-length', buf.length
       if callback?
        res.once 'finish', ->
         callback()
        res.once 'close', ->
         callback()

       res.write buf
       res.end()

     listen: ->
      @server = @http.createServer @_onRequest.bind this
      @server.listen @port

     _handlePoll: (data, options) ->
      for f in @wrappers.handleCall
       return unless f.apply this, arguments
      if not @responses[data.id]?
       @onHandleError "Poll without response: #{data.id}", data, options
       return
      @responses[data.id].setOptions options

##NodeHttpsServerPort class

    class NodeHttpsServerPort extends NodeHttpServerPort
     constructor: (options, http) ->
      super options, http
      @_key = options.key
      @_cert = options.cert

     listen: ->
      options =
       key: @_key
       cert: @_cert

      @server = @http.createServer options, @_onRequest.bind this
      @server.listen @port

#IO Module

    IO =
     addPort: (name, port) ->
      IO[name] = port

     ports:
      WorkerPort: WorkerPort
      FramePort: FramePort
      SocketPort: SocketPort
      AjaxHttpPort: AjaxHttpPort
      NodeHttpPort: NodeHttpPort
      NodeHttpServerPort: NodeHttpServerPort
      NodeHttpsServerPort: NodeHttpsServerPort
      ServerSocketPort: ServerSocketPort

     Helpers:
      progress: (from, to, func) ->
       (progress, data) ->
        func? progress * (to - from) + from, data

     setup_____: (options) ->
      options.workers ?= []
      switch options.platform
       when 'ui'
        for worker in options.workers
         IO.addPort worker.name, new WorkerPort worker.js
        for socket in options.sockets
         IO.addPort socket.name, new SocketPort socket.url
       when 'node'
        for socket in options.listenSockets
         IO.addPort socket.name, new SocketListenPort socket.port
        for socket in options.sockets
         IO.addPort socket.name, new SocketPort socket.url
       when 'worker'
        IO.addPort 'UI', new WorkerListenPort _self

    if exports?
     module.exports = IO
    else
     _self.IO = IO


micro-opt: test against value access, not key lookup

Should only use `key of` (`in`) to differentiate against possibility of
`key: undefined` — `obj[key]` is highly optimized (15-20x faster) in v8

## [StateExpression](#state-expression)

A **state expression** is a data structure that formalizes a definition of
a state’s contents.

States are declared by calling the module’s exported `state()` function and
passing it a descriptive plain object map. This input may be expressed in a
shorthand format, in which case it is rewritten into an unambiguous long form
that is used internally to create `State` instances.

    State::Expression = class StateExpression

      { isNumber, isPlainObject, assign, edit, clone, invert } = O
      { NORMAL } = STATE_ATTRIBUTES

      attributeMap = do ->
        for key, value of object = assign STATE_ATTRIBUTE_MODIFIERS
          object[ key ] = key.toUpperCase()
        object

      attributeFlags = do ->
        for key, value of object = invert STATE_ATTRIBUTES
          object[ key ] = value.toLowerCase()
        object

      categoryMap  = assign STATE_EXPRESSION_CATEGORIES
      eventTypes   = assign STATE_EVENT_TYPES
      guardActions = assign GUARD_ACTIONS

### [Constructor](#state-expression--constructor)

      constructor: ( attributes, map ) ->
        if typeof attributes is 'string' then map or = {}
        else unless map
          map = attributes; attributes = undefined

        map = interpret map unless map instanceof StateExpression
        edit 'deep all', this, map

        if attributes?
          attributes = encodeAttributes attributes unless isNumber attributes
        else { attributes } = map if map

        @attributes = attributes or NORMAL

### [Private functions](#state-expression--private)

#### [interpret](#state-expression--private--interpret)

Transforms a plain object map into a well-formed `StateExpression`, making the
appropriate type inferences for any shorthand notation encountered.

      interpret = ( map ) ->

Start with a null-valued map keyed with the category names.

        result = assign STATE_EXPRESSION_CATEGORIES, null

        for own key, value of map

If `value` is just a reference to the exported `state` function, interpret that
as an empty state expression.

          value = new StateExpression if value is state

###### Categorization

**Priority 1:** Do a nominative type match for explicit expression instances.

          category =
            if value instanceof StateExpression then 'states'
            else if value instanceof TransitionExpression then 'transitions'
          if category
            item = result[ category ] or = {}
            item[ key ] = value

**Priority 2:** Recognize an explicitly named category object.

          else if key of result and value
            result[ key ] = clone result[ key ], value

**Priority 3:** Use keys and value types to infer implicit categorization.

          else
            category =
              if eventTypes[ key ]? or typeof value is 'string'
                'events'
              else if guardActions[ key ]?
                'guards'
              else if typeof value is 'function' or ( type = value?.type ) and
                  ( type is 'state-bound-function' or
                    type is 'state-fixed-function' )
                'methods'
              else if value is NIL or isPlainObject value
                'states'
            if category
              item = result[ category ] or = {}
              item[ key ] = value

###### Coersion

Event values are coerced into an array.

        for own key, value of object = result.events
          if typeof value is 'function' or typeof value is 'string'
            object[ key ] = [ value ]

Guards are represented as an object keyed by selector, so non-object values are
coerced into a single-element object with the value keyed to the wildcard
selector `*`.

        for own key, value of object = result.guards
          object[ key ] = '*': value unless isPlainObject value

Transition values must be a `TransitionExpression`.

        for own key, value of object = result.transitions
          unless value is NIL or value instanceof TransitionExpression
            object[ key ] = new TransitionExpression value

State values must resolve to a `StateExpression`. They may be supplied as a
plain object, or as a live `State` instance, which is automatically `express`ed
to a formal `StateExpression`.

        for own key, value of object = result.states
          if value instanceof State
            object[ key ] = value.express true
          else unless value is NIL or value instanceof StateExpression
            object[ key ] = new StateExpression value

        result

### [Class methods](#state-expression--class-methods)

#### [encodeAttributes](#state-expression--class--encode-attributes)

Returns the bit-field integer represented by the provided set of `attributes`.

      @encodeAttributes = encodeAttributes = ( attributes ) ->
        attributes = assign attributes if typeof attributes is 'string'
        result = NORMAL
        for own key, value of attributes when key of attributeMap
          result |= STATE_ATTRIBUTES[ attributeMap[ key ] ]
        result

#### [decodeAttributes](#state-expression--class--decode-attributes)

Returns the space-delimited set of attribute names represented by the provided
bit-field integer `number`.

      @decodeAttributes = decodeAttributes = ( number ) ->
        ( value for key, value of attributeFlags when number & key ).join ' '

#### [untype](#state-expression--class-methods--untype)

Returns the `StateExpression` provided by `expr` as a plain-`Object`.

      @untype = untype = ( expr ) ->
        result = {}
        result[ key ] = value for own key, value of expr
        s[ name ] = untype subexpr for name, subexpr of s = result.states
        result

## [StateExpression](#state-expression)

A **state expression** is a data structure that formalizes a definition of
a state’s contents.

States are declared by calling the module’s exported `state()` function and
passing it a descriptive plain object map. This input may be expressed in a
shorthand format, in which case it is rewritten into an unambiguous long form
that is used internally to create `State` instances.

    State::Expression = class StateExpression

      { isNumber, isPlainObject, assign, edit, clone, invert } = O
      { NORMAL } = STATE_ATTRIBUTES

      attributeMap = do ->
        for key, value of object = assign STATE_ATTRIBUTE_MODIFIERS
          object[ key ] = key.toUpperCase()
        object

      attributeFlags = do ->
        for key, value of object = invert STATE_ATTRIBUTES
          object[ key ] = value.toLowerCase()
        object

      categoryMap  = assign STATE_EXPRESSION_CATEGORIES
      eventTypes   = assign STATE_EVENT_TYPES
      guardActions = assign GUARD_ACTIONS

### [Constructor](#state-expression--constructor)

      constructor: ( attributes, map ) ->
        if typeof attributes is 'string' then map or = {}
        else unless map
          map = attributes; attributes = undefined

        map = interpret map unless map instanceof StateExpression
        edit 'deep all', this, map

        if attributes?
          attributes = encodeAttributes attributes unless isNumber attributes
        else { attributes } = map if map

        @attributes = attributes or NORMAL

### [Private functions](#state-expression--private)

#### [interpret](#state-expression--private--interpret)

Transforms a plain object map into a well-formed `StateExpression`, making the
appropriate type inferences for any shorthand notation encountered.

      interpret = ( map ) ->

Start with a null-valued map keyed with the category names.

        result = assign STATE_EXPRESSION_CATEGORIES, null

        for own key, value of map

If `value` is just a reference to the exported `state` function, interpret that
as an empty state expression.

          value = new StateExpression if value is state

###### Categorization

**Priority 1:** Do a nominative type match for explicit expression instances.

          category =
            if value instanceof StateExpression then 'states'
            else if value instanceof TransitionExpression then 'transitions'
          if category
            item = result[ category ] or = {}
            item[ key ] = value

**Priority 2:** Recognize an explicitly named category object.

          else if key of result and value
            result[ key ] = clone result[ key ], value

**Priority 3:** Use keys and value types to infer implicit categorization.

          else
            category =
              if key of eventTypes or typeof value is 'string'
                'events'
              else if key of guardActions
                'guards'
              else if typeof value is 'function' or ( type = value?.type ) and
                  ( type is 'state-bound-function' or
                    type is 'state-fixed-function' )
                'methods'
              else if value is NIL or isPlainObject value
                'states'
            if category
              item = result[ category ] or = {}
              item[ key ] = value

###### Coersion

Event values are coerced into an array.

        for own key, value of object = result.events
          if typeof value is 'function' or typeof value is 'string'
            object[ key ] = [ value ]

Guards are represented as an object keyed by selector, so non-object values are
coerced into a single-element object with the value keyed to the wildcard
selector `*`.

        for own key, value of object = result.guards
          object[ key ] = '*': value unless isPlainObject value

Transition values must be a `TransitionExpression`.

        for own key, value of object = result.transitions
          unless value is NIL or value instanceof TransitionExpression
            object[ key ] = new TransitionExpression value

State values must resolve to a `StateExpression`. They may be supplied as a
plain object, or as a live `State` instance, which is automatically `express`ed
to a formal `StateExpression`.

        for own key, value of object = result.states
          if value instanceof State
            object[ key ] = value.express true
          else unless value is NIL or value instanceof StateExpression
            object[ key ] = new StateExpression value

        result

### [Class methods](#state-expression--class-methods)

#### [encodeAttributes](#state-expression--class--encode-attributes)

Returns the bit-field integer represented by the provided set of `attributes`.

      @encodeAttributes = encodeAttributes = ( attributes ) ->
        attributes = assign attributes if typeof attributes is 'string'
        result = NORMAL
        for own key, value of attributes when key of attributeMap
          result |= STATE_ATTRIBUTES[ attributeMap[ key ] ]
        result

#### [decodeAttributes](#state-expression--class--decode-attributes)

Returns the space-delimited set of attribute names represented by the provided
bit-field integer `number`.

      @decodeAttributes = decodeAttributes = ( number ) ->
        ( value for key, value of attributeFlags when number & key ).join ' '

#### [untype](#state-expression--class-methods--untype)

Returns the `StateExpression` provided by `expr` as a plain-`Object`.

      @untype = untype = ( expr ) ->
        result = {}
        result[ key ] = value for own key, value of expr
        s[ name ] = untype subexpr for name, subexpr of s = result.states
        result


form submit and error handling

form submit and error handling

# Form processing

*Define a directive for Angular forms, and a service to cache and look them
up later.*

    _ = require 'lodash'
    i8n = require 'inflection'

    pkgman = require 'pkgman'

    exports.pkgmanRegister = (registrar) ->

#### Implements hook `shrubAngularDirective`.

      registrar.registerHook 'shrubAngularDirective', -> [
        '$compile', '$exceptionHandler', '$injector', '$log', '$q', 'shrub-form', 'shrub-require'
        ($compile, $exceptionHandler, $injector, $log, $q, formService, require) ->

          link: (scope, element, attrs) ->

Keep a reference to the form scope, if the form attribute value changes, it'll
need to be rebuilt.

###### TODO: It shouldn't be trashed as it is now, it should be moved over to a new scope non-destructively.

            formScope = null
            scope.$watch attrs.form, (form) ->
              return unless form?

              form.key ?= attrs.form

Get the form's current values.

              form.values = ->
                values = {}

                for name, field of form.fields
                  widget = formService.widgets[field.type]
                  if widget.extractValues?
                    widget.extractValues field, values, formService
                  else
                    values[field.name] = field.value

                return values

Normalize form submits into an array.

              form.submits = [form.submits] unless angular.isArray form.submits

Build a submit function which will be bound to ngSubmit.

              (scope['$shrubSubmit'] ?= {})[form.key] = ($event) ->

Call all the form submission handlers.

                try

                  values = form.values()

                  promises = for submit in form.submits
                    submit values, form, $event

                  $q.all promises

                catch error
                  $exceptionHandler error

#### Invoke hook `shrubFormAlter`.

              pkgman.invokeFlat 'shrubFormAlter', form

#### Invoke hook `shrubFormFormKeyAlter`.

###### TODO: Multiline

              pkgman.invokeFlat "shrubForm#{pkgman.normalizePath form.key, true}Alter", form

Create the form element.

              $form = angular.element '<form />'
              $form.addClass form.key

Default method to POST.

              $form.attr 'method', attrs.method ? 'POST'
              $form.attr 'data-ng-submit', "$shrubSubmit['#{form.key}']($event)"

Build the form fields.

              for name, field of form.fields

Look up the widget definition and warn if it doesn't exist.

                unless (widget = formService.widgets[field.type])?

###### TODO: Multiline

                  $log.warn "Form `#{form.key}` contains non-existent field type `#{field.type}`!"
                  continue

Default name to the key.

                field.name ?= name

Helper function to synchronize the field and model value.

                do (field) -> field.syncModel = (scope) ->
                  self = this

                  return unless self.model?

                  self.$modelDereg?()
                  self.$modelDereg = null

                  self.$valueDereg?()
                  self.$valueDereg = null

                  if self.model isnt 'field.value'

                    self.$valueDereg = scope.$watch 'field.value', (value) ->
                      scope.$eval "#{self.model} = value", value: value

                    self.$modelDereg = scope.$watch self.model, (value) ->
                      self.value = value

                  return

                $form.append """

    <div
      data-#{widget.directive}
      data-field="#{attrs.form}.fields['#{name}']"
      data-form="#{attrs.form}"
    ></div><span> </span>

    """

Add hidden form key to allow server-side interception/processing.

              $formKeyElement = angular.element '<input type="hidden" />'
              $formKeyElement.attr name: 'formKey', value: form.key
              $form.append $formKeyElement

Remove any old stuff.

              if formScope
                formScope.$destroy()
                element.find('form').remove()

Insert and compile the form element.

              element.append $form
              $compile($form) formScope = scope.$new()

Register the form in the system.

              formService.cache form.key, formScope, $form

      ]

#### Implements hook `shrubAngularService`.

      registrar.registerHook 'shrubAngularService', -> [

        ->

          service = forms: {}, widgets: {}

## form.cache

* (string) `key` - The form key.
* (Scope) `scope` - The form's Angular scope.
* (Element) `element` - The form's jqLite element.

*Cache a form for later lookup.*

          service.cache = (key, scope, element) ->
            service.forms[key] = scope: scope, element: element

#### Invoke hook `shrubFormWidgets`.

          for formWidgets in pkgman.invokeFlat 'shrubFormWidgets'
            formWidgets = [formWidgets] unless _.isArray formWidgets

            for formWidget in formWidgets
              continue unless formWidget.directive?
              service.widgets[formWidget.type] = formWidget

          service

      ]

      registrar.recur [
        'widget/checkbox'
        'widget/checkboxes'
        'widget/group'
        'widget/hidden'
        'widget/markup'
        'widget/radio'
        'widget/radios'
        'widget/submit'
        'widget/select'
        'widget/text'
        'widget/textarea'
      ]

# Form processing

*Define a directive for Angular forms, and a service to cache and look them
up later.*

    _ = require 'lodash'
    i8n = require 'inflection'

    pkgman = require 'pkgman'

    exports.pkgmanRegister = (registrar) ->

#### Implements hook `shrubAngularDirective`.

      registrar.registerHook 'shrubAngularDirective', -> [
        '$compile', '$injector', '$log', '$q', 'shrub-form', 'shrub-require'
        ($compile, $injector, $log, $q, formService, require) ->

          link: (scope, element, attrs) ->

Keep a reference to the form scope, if the form attribute value changes, it'll
need to be rebuilt.

###### TODO: It shouldn't be trashed as it is now, it should be moved over to a new scope non-destructively.

            formScope = null
            scope.$watch attrs.form, (form) ->
              return unless form?

              form.key ?= attrs.form

Normalize form submits into an array.

              form.submits = [form.submits] unless angular.isArray form.submits

Build a submit function which will be bound to ngSubmit.

              (scope['$shrubSubmit'] ?= {})[form.key] = ($event) ->

Gather all the field values.

                values = {}
                for name, field of form.fields

                  widget = formService.widgets[field.type]
                  if widget.extractValues?
                    widget.extractValues field, values, formService
                  else
                    values[field.name] = field.value

Call all the form submission handlers.

                $q.all (submit values, form, $event for submit in form.submits)

#### Invoke hook `shrubFormAlter`.

              pkgman.invokeFlat 'shrubFormAlter', form

#### Invoke hook `shrubFormFormKeyAlter`.

###### TODO: Multiline

              pkgman.invokeFlat "shrubForm#{pkgman.normalizePath form.key, true}Alter", form

Create the form element.

              $form = angular.element '<form />'
              $form.addClass form.key

Default method to POST.

              $form.attr 'method', attrs.method ? 'POST'
              $form.attr 'data-ng-submit', "$shrubSubmit['#{form.key}']($event)"

Build the form fields.

              for name, field of form.fields

Look up the widget definition and warn if it doesn't exist.

                unless (widget = formService.widgets[field.type])?

###### TODO: Multiline

                  $log.warn "Form `#{form.key}` contains non-existent field type `#{field.type}`!"
                  continue

Default name to the key.

                field.name ?= name

Helper function to synchronize the field and model value.

                do (field) -> field.syncModel = (scope) ->
                  self = this

                  return unless self.model?

                  self.$modelDereg?()
                  self.$modelDereg = null

                  self.$valueDereg?()
                  self.$valueDereg = null

                  if self.model isnt 'field.value'

                    self.$valueDereg = scope.$watch 'field.value', (value) ->
                      scope.$eval "#{self.model} = value", value: value

                    self.$modelDereg = scope.$watch self.model, (value) ->
                      self.value = value

                  return

                $form.append """

    <div
      data-#{widget.directive}
      data-field="#{attrs.form}.fields['#{name}']"
      data-form="#{attrs.form}"
    ></div><span> </span>

    """

Add hidden form key to allow server-side interception/processing.

              $formKeyElement = angular.element '<input type="hidden" />'
              $formKeyElement.attr name: 'formKey', value: form.key
              $form.append $formKeyElement

Remove any old stuff.

              if formScope
                formScope.$destroy()
                element.find('form').remove()

Insert and compile the form element.

              element.append $form
              $compile($form) formScope = scope.$new()

Register the form in the system.

              formService.cache form.key, formScope, $form

      ]

#### Implements hook `shrubAngularService`.

      registrar.registerHook 'shrubAngularService', -> [

        ->

          service = forms: {}, widgets: {}

## form.cache

* (string) `key` - The form key.
* (Scope) `scope` - The form's Angular scope.
* (Element) `element` - The form's jqLite element.

*Cache a form for later lookup.*

          service.cache = (key, scope, element) ->
            service.forms[key] = scope: scope, element: element

#### Invoke hook `shrubFormWidgets`.

          for formWidgets in pkgman.invokeFlat 'shrubFormWidgets'
            formWidgets = [formWidgets] unless _.isArray formWidgets

            for formWidget in formWidgets
              continue unless formWidget.directive?
              service.widgets[formWidget.type] = formWidget

          service

      ]

      registrar.recur [
        'widget/checkbox'
        'widget/checkboxes'
        'widget/group'
        'widget/hidden'
        'widget/markup'
        'widget/radio'
        'widget/radios'
        'widget/submit'
        'widget/select'
        'widget/text'
        'widget/textarea'
      ]


ensure property.error only emits to root

# Property - controller of Object properties

The `Property` class is the *secretive shadowy* element that governs
`Object` behavior and are bound to the `Object` via
`Object.defineProperty`. It acts like a shadow `Proxy/Reflector` to
the `Object` instance and provides tight control via the
`Getter/Setter` interfaces.

The `Property` instances attach themselves to the `Object[Symbol.for('property')]` property
and are rarely accessed directly - but they are the **fundamental**
actors that *actualize* YANG schema compliance into ordinary JS
objects.

Below are list of properties available to every instance of `Property`:

property | type | mapping | description
--- | --- | --- | ---
name   | string | direct | name of the property
schema | object | direct | a schema instance (usually [Yang](./src/yang.listcoffee))
state  | object | direct | *private* object holding internal state
container | object | access(state) | reference to object containing this property
configurable | boolean | getter(state) | defines whether this property can be redefined
enumerable   | boolean | getter(state) | defines whether this property is enumerable
content | any | computed | getter/setter for `state.value`
context | object | computed | dynamically generated using [context](./src/context.coffee)
root  | [Property](./src/property.litcoffee) | computed | dynamically returns the root Property instance
props | array(Property) | computed | returns children Property instances
key   | string/number | computed | conditionally returns unique key for Property if a list item
path  | [XPath](./src/xpath.coffee) | computed | dynamically generate XPath for this Property from root

## Class Property

    debug    = require('debug')('yang:property')
    delegate = require 'delegates'
    equal    = require('deep-equal')
    Emitter  = require('events').EventEmitter
    context  = require './context'
    XPath    = require './xpath'
    kProp    = Symbol.for('property')

    class Property
      @property: (prop, desc) ->
        Object.defineProperty @prototype, prop, desc

      constructor: (@name, @schema={}) ->
        unless this instanceof Property then return new Property arguments...

        @state =
          value: undefined
          parent: null
          container: null
          configurable: true
          enumerable: @binding?
          mutable: @schema.config?.valueOf() isnt false
          attached: false
          changed: false
          changes: new Set

        Object.setPrototypeOf @state, Emitter.prototype

        @schema.kind ?= 'anydata'

        # Bind the get/set functions to call with 'this' bound to this
        # Property instance.  This is needed since native Object
        # Getter/Setter uses the Object itself as 'this'
        @set = @set.bind this
        @get = @get.bind this
        # expose the BoundThis for the set/get
        @set.bound = @get.bound = this

        # soft freeze this instance
        Object.preventExtensions this

      debug: -> debug @uri, arguments...

      delegate @prototype, 'state'
        .method 'once'
        .method 'on'
        .access 'container'
        .access 'parent'
        .getter 'configurable'
        .getter 'enumerable'
        .getter 'mutable'
        .getter 'prev'
        .getter 'attached'

      delegate @prototype, 'schema'
        .getter 'kind'
        .getter 'type'
        .getter 'default'
        .getter 'binding'

### Computed Properties

      @property 'content',
        get: -> @state.value
        set: (value) -> @set value, { force: true, suppress: true }

      @property 'active',
        get: -> @enumerable or @binding?

      @property 'changed',
        get: -> @state.changed

      @property 'change',
        get: -> @content

      @property 'context',
        get: ->
          ctx = Object.create(context)
          ctx.state = {}
          ctx.property = this
          Object.preventExtensions ctx
          return ctx

      @property 'root',
        get: ->
          return this if @kind is 'module'
          root = switch
            when @parent is this then this
            when @parent instanceof Property then @parent.root
            else this
          @state.path = undefined unless @state.root is root
          return @state.root = root

      @property 'children',
        get: ->
          return [] unless @content instanceof Object
          children = []
          for own k of @content
            desc = Object.getOwnPropertyDescriptor(@content, k)
            children.push desc.get.bound if desc?.get?.bound instanceof Property
          return children

      @property 'path',
        get: ->
          if this is @root
            entity = switch
              when @kind is 'module' then '/'
              else '.'
            return XPath.parse entity, @schema
          @state.path ?= @parent.path.clone().append @name
          return @state.path

      @property 'uri',
        get: -> [ @parent?.uri, @name ].join '/'

## Instance-level methods

      clone: ->
        @debug "[clone] cloning with #{@children.length} properties"
        copy = (new @constructor @name, @schema)
        copy.state[k] = v for k, v of @state
        return copy

      clean: ->
        if @changed
          child.clean() for child in @children
          @state.changes.clear()
          @state.changed = false

      emit: (event) ->
        @state.emit arguments...
        unless this is @root
          @debug "[emit] '#{event}' to '#{@root.name}'"
          @root.emit arguments...

### join (obj, ctx)

This call is the primary mechanism via which the `Property` instance
attaches itself to the provided target `obj`. It defines itself in the
target `obj` via `Object.defineProperty`.

      join: (obj, ctx={}) ->
        return obj unless obj instanceof Object
        { property: parent, state: opts } = ctx
        opts ?= { replace: false, suppress: false, force: false }

        detached = true unless @container?
        @container = obj
        @parent = parent ? obj[kProp]

        # if joining for the first time, apply existing data unless explicit replace
        if detached and opts.replace isnt true
          @debug "[join] applying existing data for #{@name} to:"
          @debug obj
          opts.suppress = true
          @set obj[@name], opts

        # TODO: should produce meaningful warning?
        try Object.defineProperty obj, @name, this
        @state.attached = true
        @debug "[join] attached into #{obj.constructor.name} container"
        return obj

### get (pattern)

This is the main `Getter` for the target object's property value. When
called with optional `pattern` it will perform an internal
[find](#find-xpath) operation to traverse/locate that value being
requested instead of returning its own `@content`.

It also provides special handling based on different types of
`@content` currently held.

When `@content` is a function, it will call it with the current
`@context` instance as the bound context for the function being
called.

      get: (pattern) -> switch
        when pattern?
          try match = @find pattern
          return unless match? and match.length
          switch
            when match.length is 1 then match[0].content
            when match.length > 1  then match.map (x) -> x.content
            else undefined
        when @binding?
          try return @binding.call @context
          catch e
            throw @error "issue executing registered function binding during get(): #{e.message}", e
        else @content

### set (value)

This is the main `Setter` for the target object's property value.  It
utilizes internal `@schema` attribute if available to enforce schema
validations.

      set: (value, opts={}) ->
        { force = false, suppress = false, actor } = opts
        @clean()
        @debug "[set] enter with:"
        @debug value
        #@debug opts

        return this if value? and equal(value, @content)
        unless @mutable or not value? or force
          throw @error "cannot set data on read-only (config false) element"
        return @remove opts if value is null and @kind isnt 'leaf'

        @debug "[set] applying schema..."
        ctx = @context.with(opts).with(suppress: true)
        value = switch
          when not @mutable
            @schema.validate value, ctx
          when @schema.apply?
            @schema.apply value, ctx
          else value
        return this if value instanceof Error

        @state.prev = @state.value
        @state.enumerable = value? or @binding?

        if @binding?.length is 1 and not force
          try @binding.call ctx, value
          catch e
            @debug e
            throw @error "issue executing registered function binding during set(): #{e.message}", e
        else
          @state.value = value

        # TODO: do we need this block?
        if @container?.hasOwnProperty @name
          Object.defineProperty @container, @name,
            configurable: @state.configurable
            enumerable: @state.enumerable

        @state.changed = true
        @emit 'update', this, actor unless suppress
        @debug "[set] completed"
        return this

### merge (value)

Performs a granular merge of `value` into existing `@content` if
available, otherwise performs [set](#set-value) operation.

      merge: (value, opts={}) ->
        opts.merge = true
        opts.replace ?= true
        @set value, opts

### remove

The reverse of [join](#join-obj), it will detach itself from the
`@container` parent object.

      remove: (opts={}) ->
        { suppress, actor } = opts
        @state.enumerable = false
        @state.value = null
        return this unless @container?
        Object.defineProperty @container, @name, enumerable: false

        @emit 'update', this, actor unless suppress
        return this

### find (pattern)

This helper routine can be used to allow traversal to other elements
in the data tree from the relative location of the current `Property`
instance. It returns matching `Property` instances based on the
provided `pattern` in the form of XPATH or YPATH.

It is internally used via [get](#get) and generally used inside
controller logic bound inside the [Yang expression](./yang.litcoffee)
as well as event handler listening on [Model](./model.litcoffee)
events.

It *always* returns an array (empty to denote no match) unless it
encounters an error, in which case it will throw an Error.

      find: (pattern='.', opts={}) ->
        @debug "[find] #{pattern}"
        unless pattern instanceof XPath
          if /^\.\.\//.test(pattern) and @parent?
            return @parent.find pattern.replace(/^\.\.\//, ''), opts
          if /^\//.test(pattern) and this isnt @root
            return @root.find pattern, opts
          pattern = XPath.parse pattern, @schema

        @debug "[find] using #{pattern}"
        if opts.root or not @container? or pattern.tag not in [ '/', '..' ]
          @debug "[find] apply #{pattern}"
          pattern.apply(@content).props ? []
        else switch
          when pattern.tag is '/'  and @parent? then @parent.find pattern, opts
          when pattern.tag is '..' and @parent? then @parent.find pattern.xpath, opts
          else []

### in (pattern)

A convenience routine to locate one or more matching Property
instances based on `pattern` (XPATH or YPATH) from this Model.

      in: (pattern) ->
        try props = @find pattern
        return unless props? and props.length
        return switch
          when props.length > 1 then props
          else props[0]

### error (msg)

Provides more contextual error message pertaining to the Property instance.

      error: (err, ctx) ->
        unless err instanceof Error
          err = new Error err
        err.name = 'PropertyError'
        err.path = @path
        err.prop = this
        err.context = ctx
        @root.emit 'error', err
        return err

### inspect

      inspect: ->
        return {
          name:   @schema.tag ? @name
          kind:   @schema.kind
          xpath:  @path.toString()
          schema: @schema.toJSON? tag: false, extended: true
          active: @active
          readonly: not @mutable
        }

### toJSON

This call creates a new copy of the current `Property.content`
completely detached/unbound to the underlying data schema. It's main
utility is to represent the current data state for subsequent
serialization/transmission. It accepts optional argument `tag` which
when called with `true` will tag the produced object with the current
property's `@name`.

      toJSON: (tag=false) ->
        copy = (src, ctx) ->
          return unless src? and typeof src isnt 'function'
          return if ctx? and ctx.kind is 'anydata'
          if typeof src is 'object'
            prop = src[kProp]
            try res = new src.constructor
            catch then res = {}
            for own k, v of src when typeof v isnt 'function'
              res[k] = copy v, prop?.in(k)
            return res
          src.constructor.call src, src
        value = copy @get()
        if tag
          name = @schema.datakey ? @name
          "#{name}": value
        else value

## Export Property Class

    module.exports = Property

# Property - controller of Object properties

The `Property` class is the *secretive shadowy* element that governs
`Object` behavior and are bound to the `Object` via
`Object.defineProperty`. It acts like a shadow `Proxy/Reflector` to
the `Object` instance and provides tight control via the
`Getter/Setter` interfaces.

The `Property` instances attach themselves to the `Object[Symbol.for('property')]` property
and are rarely accessed directly - but they are the **fundamental**
actors that *actualize* YANG schema compliance into ordinary JS
objects.

Below are list of properties available to every instance of `Property`:

property | type | mapping | description
--- | --- | --- | ---
name   | string | direct | name of the property
schema | object | direct | a schema instance (usually [Yang](./src/yang.listcoffee))
state  | object | direct | *private* object holding internal state
container | object | access(state) | reference to object containing this property
configurable | boolean | getter(state) | defines whether this property can be redefined
enumerable   | boolean | getter(state) | defines whether this property is enumerable
content | any | computed | getter/setter for `state.value`
context | object | computed | dynamically generated using [context](./src/context.coffee)
root  | [Property](./src/property.litcoffee) | computed | dynamically returns the root Property instance
props | array(Property) | computed | returns children Property instances
key   | string/number | computed | conditionally returns unique key for Property if a list item
path  | [XPath](./src/xpath.coffee) | computed | dynamically generate XPath for this Property from root

## Class Property

    debug    = require('debug')('yang:property')
    delegate = require 'delegates'
    equal    = require('deep-equal')
    Emitter  = require('events').EventEmitter
    context  = require './context'
    XPath    = require './xpath'
    kProp    = Symbol.for('property')

    class Property
      @property: (prop, desc) ->
        Object.defineProperty @prototype, prop, desc

      constructor: (@name, @schema={}) ->
        unless this instanceof Property then return new Property arguments...

        @state =
          value: undefined
          parent: null
          container: null
          configurable: true
          enumerable: @binding?
          mutable: @schema.config?.valueOf() isnt false
          attached: false
          changed: false
          changes: new Set

        Object.setPrototypeOf @state, Emitter.prototype

        @schema.kind ?= 'anydata'

        # Bind the get/set functions to call with 'this' bound to this
        # Property instance.  This is needed since native Object
        # Getter/Setter uses the Object itself as 'this'
        @set = @set.bind this
        @get = @get.bind this
        # expose the BoundThis for the set/get
        @set.bound = @get.bound = this

        # soft freeze this instance
        Object.preventExtensions this

      debug: -> debug @uri, arguments...

      delegate @prototype, 'state'
        .method 'once'
        .method 'on'
        .access 'container'
        .access 'parent'
        .getter 'configurable'
        .getter 'enumerable'
        .getter 'mutable'
        .getter 'prev'
        .getter 'attached'

      delegate @prototype, 'schema'
        .getter 'kind'
        .getter 'type'
        .getter 'default'
        .getter 'binding'

### Computed Properties

      @property 'content',
        get: -> @state.value
        set: (value) -> @set value, { force: true, suppress: true }

      @property 'active',
        get: -> @enumerable or @binding?

      @property 'changed',
        get: -> @state.changed

      @property 'change',
        get: -> @content

      @property 'context',
        get: ->
          ctx = Object.create(context)
          ctx.state = {}
          ctx.property = this
          Object.preventExtensions ctx
          return ctx

      @property 'root',
        get: ->
          return this if @kind is 'module'
          root = switch
            when @parent is this then this
            when @parent instanceof Property then @parent.root
            else this
          @state.path = undefined unless @state.root is root
          return @state.root = root

      @property 'children',
        get: ->
          return [] unless @content instanceof Object
          children = []
          for own k of @content
            desc = Object.getOwnPropertyDescriptor(@content, k)
            children.push desc.get.bound if desc?.get?.bound instanceof Property
          return children

      @property 'path',
        get: ->
          if this is @root
            entity = switch
              when @kind is 'module' then '/'
              else '.'
            return XPath.parse entity, @schema
          @state.path ?= @parent.path.clone().append @name
          return @state.path

      @property 'uri',
        get: -> [ @parent?.uri, @name ].join '/'

## Instance-level methods

      clone: ->
        @debug "[clone] cloning with #{@children.length} properties"
        copy = (new @constructor @name, @schema)
        copy.state[k] = v for k, v of @state
        return copy

      clean: ->
        if @changed
          child.clean() for child in @children
          @state.changes.clear()
          @state.changed = false

      emit: (event) ->
        @state.emit arguments...
        unless this is @root
          @debug "[emit] '#{event}' to '#{@root.name}'"
          @root.emit arguments...

### join (obj, ctx)

This call is the primary mechanism via which the `Property` instance
attaches itself to the provided target `obj`. It defines itself in the
target `obj` via `Object.defineProperty`.

      join: (obj, ctx={}) ->
        return obj unless obj instanceof Object
        { property: parent, state: opts } = ctx
        opts ?= { replace: false, suppress: false, force: false }

        detached = true unless @container?
        @container = obj
        @parent = parent ? obj[kProp]

        # if joining for the first time, apply existing data unless explicit replace
        if detached and opts.replace isnt true
          @debug "[join] applying existing data for #{@name} to:"
          @debug obj
          opts.suppress = true
          @set obj[@name], opts

        # TODO: should produce meaningful warning?
        try Object.defineProperty obj, @name, this
        @state.attached = true
        @debug "[join] attached into #{obj.constructor.name} container"
        return obj

### get (pattern)

This is the main `Getter` for the target object's property value. When
called with optional `pattern` it will perform an internal
[find](#find-xpath) operation to traverse/locate that value being
requested instead of returning its own `@content`.

It also provides special handling based on different types of
`@content` currently held.

When `@content` is a function, it will call it with the current
`@context` instance as the bound context for the function being
called.

      get: (pattern) -> switch
        when pattern?
          try match = @find pattern
          return unless match? and match.length
          switch
            when match.length is 1 then match[0].content
            when match.length > 1  then match.map (x) -> x.content
            else undefined
        when @binding?
          try return @binding.call @context
          catch e
            throw @error "issue executing registered function binding during get(): #{e.message}", e
        else @content

### set (value)

This is the main `Setter` for the target object's property value.  It
utilizes internal `@schema` attribute if available to enforce schema
validations.

      set: (value, opts={}) ->
        { force = false, suppress = false, actor } = opts
        @clean()
        @debug "[set] enter with:"
        @debug value
        #@debug opts

        return this if value? and equal(value, @content)
        unless @mutable or not value? or force
          throw @error "cannot set data on read-only (config false) element"
        return @remove opts if value is null and @kind isnt 'leaf'

        @debug "[set] applying schema..."
        ctx = @context.with(opts).with(suppress: true)
        value = switch
          when not @mutable
            @schema.validate value, ctx
          when @schema.apply?
            @schema.apply value, ctx
          else value
        return this if value instanceof Error

        @state.prev = @state.value
        @state.enumerable = value? or @binding?

        if @binding?.length is 1 and not force
          try @binding.call ctx, value
          catch e
            @debug e
            throw @error "issue executing registered function binding during set(): #{e.message}", e
        else
          @state.value = value

        # TODO: do we need this block?
        if @container?.hasOwnProperty @name
          Object.defineProperty @container, @name,
            configurable: @state.configurable
            enumerable: @state.enumerable

        @state.changed = true
        @emit 'update', this, actor unless suppress
        @debug "[set] completed"
        return this

### merge (value)

Performs a granular merge of `value` into existing `@content` if
available, otherwise performs [set](#set-value) operation.

      merge: (value, opts={}) ->
        opts.merge = true
        opts.replace ?= true
        @set value, opts

### remove

The reverse of [join](#join-obj), it will detach itself from the
`@container` parent object.

      remove: (opts={}) ->
        { suppress, actor } = opts
        @state.enumerable = false
        @state.value = null
        return this unless @container?
        Object.defineProperty @container, @name, enumerable: false

        @emit 'update', this, actor unless suppress
        return this

### find (pattern)

This helper routine can be used to allow traversal to other elements
in the data tree from the relative location of the current `Property`
instance. It returns matching `Property` instances based on the
provided `pattern` in the form of XPATH or YPATH.

It is internally used via [get](#get) and generally used inside
controller logic bound inside the [Yang expression](./yang.litcoffee)
as well as event handler listening on [Model](./model.litcoffee)
events.

It *always* returns an array (empty to denote no match) unless it
encounters an error, in which case it will throw an Error.

      find: (pattern='.', opts={}) ->
        @debug "[find] #{pattern}"
        unless pattern instanceof XPath
          if /^\.\.\//.test(pattern) and @parent?
            return @parent.find pattern.replace(/^\.\.\//, ''), opts
          if /^\//.test(pattern) and this isnt @root
            return @root.find pattern, opts
          pattern = XPath.parse pattern, @schema

        @debug "[find] using #{pattern}"
        if opts.root or not @container? or pattern.tag not in [ '/', '..' ]
          @debug "[find] apply #{pattern}"
          pattern.apply(@content).props ? []
        else switch
          when pattern.tag is '/'  and @parent? then @parent.find pattern, opts
          when pattern.tag is '..' and @parent? then @parent.find pattern.xpath, opts
          else []

### in (pattern)

A convenience routine to locate one or more matching Property
instances based on `pattern` (XPATH or YPATH) from this Model.

      in: (pattern) ->
        try props = @find pattern
        return unless props? and props.length
        return switch
          when props.length > 1 then props
          else props[0]

### error (msg)

Provides more contextual error message pertaining to the Property instance.

      error: (err, ctx) ->
        unless err instanceof Error
          err = new Error err
        err.name = 'PropertyError'
        err.path = @path
        err.prop = this
        err.context = ctx
        @emit 'error', err
        return err

### inspect

      inspect: ->
        return {
          name:   @schema.tag ? @name
          kind:   @schema.kind
          xpath:  @path.toString()
          schema: @schema.toJSON? tag: false, extended: true
          active: @active
          readonly: not @mutable
        }

### toJSON

This call creates a new copy of the current `Property.content`
completely detached/unbound to the underlying data schema. It's main
utility is to represent the current data state for subsequent
serialization/transmission. It accepts optional argument `tag` which
when called with `true` will tag the produced object with the current
property's `@name`.

      toJSON: (tag=false) ->
        copy = (src, ctx) ->
          return unless src? and typeof src isnt 'function'
          return if ctx? and ctx.kind is 'anydata'
          if typeof src is 'object'
            prop = src[kProp]
            try res = new src.constructor
            catch then res = {}
            for own k, v of src when typeof v isnt 'function'
              res[k] = copy v, prop?.in(k)
            return res
          src.constructor.call src, src
        value = copy @get()
        if tag
          name = @schema.datakey ? @name
          "#{name}": value
        else value

## Export Property Class

    module.exports = Property