###!
Resourcy v1.1.0

Resourcy is an unobtrusive RESTful adapter for jquery-ujs and Rails.

Documentation and other useful information can be found at
https://github.com/jejacks0n/resourcy

Copyright (c) 2012 Jeremy Jackson

https://raw.github.com/jejacks0n/resourcy/master/LICENSE
###

# resource list
resources = {}

# used to describe routes
methods = ['get', 'put', 'post', 'delete']
actions = ['index', 'create', 'new', 'edit', 'show', 'update', 'delete']
descriptions =
  plural: [['get', ''], ['post', ''], ['get', '/new'], ['get', '/:id/edit'], ['get', '/:id'], ['put', '/:id'], ['delete', '/:id']]
  singular: [null, ['post', ''], ['get', '/new'], ['get', '/edit'], ['get', ''], ['put', ''], ['delete', '']]

parseUrl = (url) ->
  url = url.match(/^((http[s]?|ftp):\/\/)?(((.+)@)?([^:\/\?#\s]+)(:(\d+))?)?(\/?[^\?#\.]+)?(\.([^\?#]+))?(\?([^#]*))?(#(.*))?$/i) or []
  path = url[9]?.match(/(\/.*)\/+(\w+)$/i) or []
  return scheme: url[2], credentials: url[5], host: url[6], port: url[8], path: url[9], action: path[2] or '', format: url[11], query: url[13], hash: url[15]

createResource = (path, singular = false, defaults = {}) =>
  return resources[path] if resources[path]
  return resources[path] =
    path: new RegExp("^#{(if path[0] is '/' then path else "/#{path}").replace(/:\w+/ig, '(\\w+)')}/?(\\w+)?/?(\\w+)?/?($|\\?|\\.|#)", 'i')
    pathvars: (path.match(/:(\w+)/ig) or []).join('|').replace(/:/g, '').split('|') # todo: this seems wonky
    actions: {}
    defaults: defaults
    singular: singular
    name: path.substr(path.lastIndexOf('/') + 1)
    options: (defaults = {}) ->
      @defaults = defaults
      return @
    add: (action, callback) ->
      object = {}
      if typeof(action) is 'string' then object[action] = callback else object = action
      for action, callback of object
        [method, action] = action.split(':')
        addCallback.call(@, method, action, callback)
      return @
    remove: (action) ->
      [method, action] = action.split(':')
      if action then delete(@actions[method][action]) else delete(@actions[method])
      return @
    removeAll: ->
      @actions = {}
      return @
    describe: ->
      routes = []
      for method in methods
        for action of @actions[method]
          routes.push("#{path}/#{action} #{method.toUpperCase()} => #{@name}##{action}")
      [start, desc] = if @singular then [1, descriptions.singular] else [0, descriptions.plural]
      for i in [start..actions.length - 1]
        if @actions[actions[i]]
          routes.push("#{path}#{desc[i][1]} #{desc[i][0].toUpperCase()} => #{@name}##{actions[i]}")
      return routes

addCallback = (method, action, callback) ->
  errorMsg = "The #{([method, action].join(':').replace(/:$/, ''))} action already exists on the '#{@name}' resource. Try removing it first."
  if action
    @actions[method] ||= {}
    throw errorMsg if @actions[method][action]
    @actions[method][action] = callback
  else
    throw errorMsg if @actions[method]
    throw "Adding index to '#{@name}' isn't possible (singular resource)." if @singular and method is 'index'
    @actions[method] = callback

handleRequest = (method, url, options, original, optionsHandler) ->
  method = method.toLowerCase()
  {path, action} = urlParts = parseUrl(url)
  defaults = {}

  proceeded = false
  proceed = (opts) ->
    proceeded = true
    return original(url, optionsHandler(options or {}, opts or {}, defaults))

  for key, resource of resources
    continue unless matches = path.match(resource.path)
    if callback = determineCallback(resource, action, method, matches[matches.length - 2], matches[matches.length - 3])
      defaults = resource.defaults
      vars = {}
      vars[pathvar] = matches[index + 1] for pathvar, index in resource.pathvars
      result = callback(proceed, vars, urlParts)
    return proceed(result) if result != false and !proceeded
    return

  return original(url, options)

determineCallback = (resource, action, method, matchAction, matchIdOrAction) ->
  switch method
    when 'get'
      return resource.actions.get[action] if resource.actions.get?[action]
      switch (if matchIdOrAction then action else '')
        when '' then return (if resource.singular then resource.actions.show else resource.actions.index)
        when 'new' then return resource.actions.new
        when 'edit' then return resource.actions.edit
        else return resource.actions.show unless matchAction
    when 'put' then return resource.actions.put?[action] or (resource.actions.update unless matchAction)
    when 'post' then return resource.actions.post?[action] or (resource.actions.create unless matchIdOrAction)
    when 'delete' then return resource.actions.delete?[action] or (resource.actions.destroy unless matchAction)


@Resourcy =
  removeAll: -> resources = {}
  handleRequest: handleRequest
  noConflict: @Resourcy?.noConflict or -> delete(Resourcy)

  resources: (path, defaults = {}, actions = {}) -> return createResource(path, false, defaults).add(actions)
  resource: (path, defaults = {}, actions = {}) -> return createResource(path, true, defaults).add(actions)
  routes: ->
    routes = {}
    routes[resource.name] = resource.describe() for path, resource of resources
    return routes