# Resource class

path = require 'path'
_ = require 'lodash'
Promise = require 'bluebird'
http = require 'http'

Request = require './request' # This is incorrect
Response = require './response'


RegExp.quote = (str) ->
  str.replace /([.?*+^$[\]\\(){}|-])/g, "\\$1"

module.exports = class Resource
  constructor: (options) ->
    @resources = []
    @methods = {}
    @uses = []

    @options = options or {}

  # Define /end/ function for a method on this resource
  method: (method, uses..., fn) ->
    m = method.toUpperCase()
    if @methods[m]?
      throw new Error "Duplicate #{m} handler set!"
    @methods[m] = uses.concat [fn]
    this # Chaining


  # Add a method to run for ALL request (in/passing) this resource
  use: (fn) ->
    @uses.push fn
    this # Chaining

  # Define a new resource point
  resource: (args...) ->
    # If it is a RegExp, do other things
    if args[0] instanceof RegExp
      [uriRegExp, parameters] = args

      parameters ?= []
      if parameters not instanceof Array
        parameters = [parameters]

    # Else, generate the regexp and parameters from url
    else
      if not args[0]? or args[0] is ''
        throw new Error "First argument of .resource must be a non-empty string!"

      uri = path.join args...
      # Remove slash in front
      if uri.indexOf('/') is 0
        uri = uri.slice 1

      # Split all seperate segments
      parameters = []
      PARAMREG = /([^/]+)/

      uri = '^\\/' + uri.split('/').map (segment) ->
        if segment.indexOf(':') is 0 # Parameter
          parameters.push segment.slice(1)
          PARAMREG.source
        else # Just a string literal
          RegExp.quote segment
      .join('\\/') # Join with escaped slashes
      uriRegExp = new RegExp uri

    resource = new Resource
    @resources.push
      resource: resource
      uriRegExp: uriRegExp
      paramNames: parameters
    resource

  # Alias for ::resource
  res: @::resource


  # Get request from listening or from a parent resource
  _bubble: (req, response) ->
    # Enable/Disable hall if this resource is supposed to
    for key,value of @options
      response.options[key] = value

    url = req.url.match(/^([^?]*)\??.*$/)[1]
    #console.log '\nGot a request:', req

    promise = Promise.bind(response).return(req)
    @uses.forEach (use) ->
      promise = promise.then(use).return(req)

    promise.then =>
      # Check resources for a match
      matching = _.find @resources, (resource) ->
        url.match(resource.uriRegExp)?

      # If no match, error!
      if not matching?
        # Check if it could be for this resource
        if ['', '/'].indexOf(url) isnt -1
          # Request is for this resource!
          if not @methods[req.method]?
            throw new Error "Method not allowed!"

          promise = Promise.bind(response)
          @methods[req.method].forEach (fn) ->
            promise = promise.return(req).then(fn)
          return promise

        # If not, DIE!
        throw new Error "Resource not found!"

      match = url.match matching.uriRegExp # Yes, I know

      # Get the params
      params = req.params ||= {} # Get the already set params (or make a new empty object)
      for paramname, key in matching.paramNames
        params[paramname] = match[key+1]

      # Update the url
      req.url = url.slice match[0].length

      # Let it bubble up!
      matching.resource._bubble req, response


  # First bubble in a chain, does some extra stuff
  _forgeRequest: (req) ->
    req._url = req.url ?= '/' # Save origional url
    req.method ?= 'GET'

    # TODO: Check for existance of the resource:method first
    response = new Response req, @debug # Placeholder for the real response
    response.setBody Promise.try(@_bubble, [req, response], this)

  # Set a certain option in the resource.
  # Subresources may overwrite this, as this also may overwrite super resources.
  set: (option, value) ->
    if not value?
      delete @options[option]
    else
      @options[option] = value


  # Change this resource into a server
  listen: (args...) ->
    @_server = http.createServer (req, res) =>
      # Go up the tree, all the way
      @_forgeRequest(req).then (response) ->
        response.applyTo res
    .listen args...

['get', 'post', 'put', 'delete'].forEach (method) ->
  Resource::[method] = (args...) ->
    @method method, args...





# PLUGINS, WILL MOVE THEM TO THEIR OWN FILE
formidable = require 'formidable'
Resource.multipartBodyParser = (opts) ->
  opts ?= {}

  (req) ->
    if req.contentType isnt "multipart/form-data" # or (req.getContentLength() is 0 and not req.isChunked())
      return

    form = new formidable.IncomingForm()
    ###
    form.keepExtensions = (if opts.keepExtensions then true else false)
    form.onPart = onPart = (part) ->
      if part.filename and options.multipartFileHandler
        options.multipartFileHandler part, req
      else if not part.filename and options.multipartHandler
        options.multipartHandler part, req
      else
        form.handlePart part
    ###
    new Promise (yell, cry) ->
      form.parse req, (err, fields, files) ->
        if err?
          return cry err
        yell [fields, files]

    .spread (fields, files) ->
      req.body = fields
      req.files = files

    .catch (err) ->
      #new BadRequestError(err.message)
      throw err

querystring = require("querystring")

Resource.formBodyParser = ->
  (req) ->
    if req.contentType isnt 'application/x-www-form-urlencoded'
      return

    req.loadBody().then (body) ->
      params = querystring.parse body.toString()
      #req._body = req.body
      req.body = params
    .catch (e) ->
      #throw new errors.InvalidContentError(e.message)
      throw new Error "Invalid content"


Resource.jsonBodyParser = ->
  (req) ->
    if req.contentType isnt 'application/json'
      return

    if ['POST', 'PUT'].indexOf(req.method) is -1
      return

    req.loadBody().then (body) ->
      try
        req.body = JSON.parse body
      catch e
        console.log 'JSON ERROR :\'(', e

Resource.bodyParser = ->
  json = Resource.jsonBodyParser()
  multipart = Resource.multipartBodyParser()
  form = Resource.formBodyParser()

  (req) ->
    Promise.all [
      form(req),
      json(req),
      multipart(req)
    ]

Resource.authorizationParser = (options) ->
  (req) ->
    # Fill to prevent /nullpoint/ errors
    req.authorization = {}

    unless req.headers.authorization
      return

    [scheme, credentials] = req.headers.authorization.split " ", 2
    if not credentials?
      throw new Error "HTTP:400 Invalid Authorization header!"

    # Fill the authorization
    req.authorization =
      scheme: scheme.toLowerCase()
      credentials: credentials
      raw: scheme.toLowerCase() + " " + credentials

    # Long ago, there was a code to parse Basic auth.
    # But then the basic auth was to basic, so it went home.
    # The code had no use anymore, and commited suicide.
    # Rest in peace, basic auth parse code.
