class TurboGraft.Remote

constructor: (@opts, form, target) ->

  @initiator = form || target

  @actualRequestType = if @opts.httpRequestType?.toLowerCase() == 'get' then 'GET' else 'POST'
  @useNativeEncoding = @opts.useNativeEncoding

  @formData = @createPayload(form)

  @refreshOnSuccess       = @opts.refreshOnSuccess.split(" ")       if @opts.refreshOnSuccess
  @refreshOnSuccessExcept = @opts.refreshOnSuccessExcept.split(" ") if @opts.refreshOnSuccessExcept
  @refreshOnError         = @opts.refreshOnError.split(" ")         if @opts.refreshOnError
  @refreshOnErrorExcept   = @opts.refreshOnErrorExcept.split(" ")   if @opts.refreshOnErrorExcept

  xhr = new XMLHttpRequest
  if @actualRequestType == 'GET'
    url = if @formData then @opts.httpUrl + "?#{@formData}" else @opts.httpUrl
    xhr.open(@actualRequestType, url, true)
  else
    xhr.open(@actualRequestType, @opts.httpUrl, true)
  xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest')
  xhr.setRequestHeader('Accept', 'text/html, application/xhtml+xml, application/xml')
  xhr.setRequestHeader("Content-Type", @contentType) if @contentType
  xhr.setRequestHeader 'X-XHR-Referer', document.location.href

  csrfToken = CSRFToken.get().token
  xhr.setRequestHeader('X-CSRF-Token', csrfToken) if csrfToken

  triggerEventFor('turbograft:remote:init', @initiator, {xhr: xhr, initiator: @initiator})

  xhr.addEventListener 'loadstart', =>
    triggerEventFor 'turbograft:remote:start', @initiator,
      xhr: xhr

  xhr.addEventListener 'error', @onError
  xhr.addEventListener 'load', (event) =>
    if xhr.status < 400
      @onSuccess(event)
    else
      @onError(event)

  xhr.addEventListener 'loadend', =>
    @opts.done?()
    triggerEventFor 'turbograft:remote:always', @initiator,
      initiator: @initiator
      xhr: xhr

  @xhr = xhr

submit: ->
  @xhr.send(@formData)

createPayload: (form) ->
  if form
    if @useNativeEncoding || form.querySelectorAll("[type='file'][name]").length > 0
      formData = @nativeEncodeForm(form)
    else # for much smaller payloads
      formData = @uriEncodeForm(form)
  else
    formData = ''

  if formData not instanceof FormData
    @contentType = "application/x-www-form-urlencoded; charset=UTF-8"
    formData = @formAppend(formData, "_method", @opts.httpRequestType) if formData.indexOf("_method") == -1 && @opts.httpRequestType && @actualRequestType != 'GET'

  formData

formAppend: (uriEncoded, key, value) ->
  uriEncoded += "&" if uriEncoded.length
  uriEncoded += "#{encodeURIComponent(key)}=#{encodeURIComponent(value)}"

uriEncodeForm: (form) ->
  formData = ""
  @_iterateOverFormInputs form, (input) =>
    formData = @formAppend(formData, input.name, input.value)
  formData

formDataAppend: (formData, input) ->
  if input.type == 'file'
    for file in input.files
      formData.append(input.name, file)
  else
    formData.append(input.name, input.value)
  formData

nativeEncodeForm: (form) ->
  formData = new FormData
  @_iterateOverFormInputs form, (input) =>
    formData = @formDataAppend(formData, input)
  formData

_iterateOverFormInputs: (form, callback) ->
  inputs = @_enabledInputs(form)
  for input in inputs
    inputEnabled = !input.disabled
    radioOrCheck = (input.type == 'checkbox' || input.type == 'radio')

    if inputEnabled && input.name
      if (radioOrCheck && input.checked) || !radioOrCheck
        callback(input)

_enabledInputs: (form) ->
  selector = "input:not([type='reset']):not([type='button']):not([type='submit']):not([type='image']), select, textarea"
  inputs = Array::slice.call(form.querySelectorAll(selector))
  disabledNodes = Array::slice.call(TurboGraft.querySelectorAllTGAttribute(form, 'tg-remote-noserialize'))

  return inputs unless disabledNodes.length

  disabledInputs = disabledNodes
  for node in disabledNodes
    disabledInputs = disabledInputs.concat(Array::slice.call(node.querySelectorAll(selector)))

  enabledInputs = []
  for input in inputs when disabledInputs.indexOf(input) < 0
    enabledInputs.push(input)
  enabledInputs

onSuccess: (ev) =>
  @opts.success?()

  xhr = ev.target
  triggerEventFor 'turbograft:remote:success', @initiator,
    initiator: @initiator
    xhr: xhr

  if redirect = xhr.getResponseHeader('X-Next-Redirect')
    Page.visit(redirect, reload: true)
    return

  unless TurboGraft.hasTGAttribute(@initiator, 'tg-remote-norefresh')
    if @opts.fullRefresh && @refreshOnSuccess
      Page.refresh(onlyKeys: @refreshOnSuccess)
    else if @opts.fullRefresh
      Page.refresh()
    else if @refreshOnSuccess
      Page.refresh(
        response: xhr
        onlyKeys: @refreshOnSuccess
      )
    else if @refreshOnSuccessExcept
      Page.refresh(
        response: xhr
        exceptKeys: @refreshOnSuccessExcept
      )
    else
      Page.refresh(
        response: xhr
      )

onError: (ev) =>
  @opts.fail?()

  xhr = ev.target
  triggerEventFor 'turbograft:remote:fail', @initiator,
    initiator: @initiator
    xhr: xhr

  if TurboGraft.hasTGAttribute(@initiator, 'tg-remote-norefresh')
    triggerEventFor 'turbograft:remote:fail:unhandled', @initiator,
      xhr: xhr
  else
    if @opts.fullRefresh && @refreshOnError
      Page.refresh(onlyKeys: @refreshOnError)
    else if @opts.fullRefresh
      Page.refresh()
    else if @refreshOnError
      Page.refresh(
        response: xhr
        onlyKeys: @refreshOnError
      )
    else if @refreshOnErrorExcept
      Page.refresh(
        response: xhr
        exceptKeys: @refreshOnErrorExcept
      )
    else
      triggerEventFor 'turbograft:remote:fail:unhandled', @initiator,
        xhr: xhr