class App.UI.Form
  constructor: (opts = {}) ->
    @formId = opts.id
    @obj = opts.for
    @initObj = if opts.initObj? and opts.initObj then true else false
    @delegator = opts.delegator
    @callbackSuccess = opts.callbackSuccess
    @callbackFailure = opts.callbackFailure
    @callbackActive = opts.callbackActive
    @form = this._findForm()
    @submit = @form.find ':submit'
    @submitVal = @submit.val()
    @errorsShowHideDuration = if opts.errorsShowHideDuration? then opts.errorsShowHideDuration else null  # 200
    @hideErrorFunc = if opts.hideErrorFunc? then opts.hideErrorFunc else null  # 'slideUp'
    @showErrorFunc = if opts.showErrorFunc? then opts.showErrorFunc else null  # 'slideDown'

  getObj: -> @obj

  render: ->
    if @initObj
      this._assignAttribs()
    else
      this.fill()
    this._handle()

  fill: (attr = null) ->
    return null if not @obj?
    return null if not @obj.constructor.attributes?
    attributes = {}
    if attr?
      attributes[attr] = null
    else
      attributes = @obj.constructor.attributes
    for name, _ of attributes
      remoteName = @obj.getAttrRemoteName name
      formEl = @form.find("[data-attr=#{remoteName}]").find "input,textarea,select"
      if formEl.length is 1
        formEl.val @obj[name]
        continue
      if formEl.first().attr("type") isnt "hidden" and formEl.last().attr('type') isnt "checkbox"
        continue
      formEl.last().prop 'checked', !formEl.last().is(":checked")

  _findForm: ->
    return $("##{@formId}") if @formId?
    if @obj?
      objName = @obj.getIdentity().toLowerCase()
      if @obj.id? then $("#edit_#{objName}_#{@obj.id}") else $("#new_#{objName}")

  _handle: ->
    @form.on 'submit', (e) =>
      e.preventDefault()
      return if not this._canBeSubmitted()
      if not @obj?
        this._submitForm()
        return
      this._assignAttribs()
      this._hideErrors()
      if @obj.isInvalid()
        this._delayedRenderErrors()
        @delegator[@callbackFailure]() if @callbackFailure?
        return
      this._submittingForm false
      clearForm = if @obj.id? then false else true
      @obj.save()
      .then (data) =>
        if data.success
          this._handleSuccess data, clearForm
        else
          @delegator[@callbackFailure]() if @callbackFailure?
          this._renderErrors()
      .catch (err) => this._connectionError()

  _canBeSubmitted: ->
    return false if @submit.hasClass 'active'
    return false if @submit.hasClass 'success'
    return false if @submit.hasClass 'failure'
    true

  _submitForm: ->
    this._submittingForm()
    url = @form.attr('action') + '.json'
    jqxhr = $.post url, @form.serialize()
    jqxhr.always => @submit.removeClass('active').blur()
    jqxhr.fail => this._connectionError()
    jqxhr.done (data) =>
      if data.success
        this._handleSuccess data, @form.attr("method") is "POST"
      else
        this._renderErrors data.errors

  _handleSuccess: (data, clearForm = true) ->
    val = data.flash?.success ? "Success"
    @submit.removeClass("active").addClass('success').val val
    if data.access_token?
      App.Env.loco.getWire().setToken data.access_token
    if @callbackSuccess?
      if data.data?
        @delegator[@callbackSuccess](data.data)
      else
        @delegator[@callbackSuccess]()
      return
    setTimeout =>
      @submit.removeClass('success').val @submitVal
      selector = ":not([data-loco-not-clear=true])"
      if clearForm
        @form.find("input:not([type='submit'])#{selector}, textarea#{selector}").val ''
    , 5000

  _delayedRenderErrors: ->
    if @errorsShowHideDuration?
      setTimeout =>
        this._renderErrors()
      , @errorsShowHideDuration
    else
      this._renderErrors()

  _renderErrors: (remoteErrors = null) ->
    return if @obj? and not @obj.errors?
    return if not @obj? and not remoteErrors?
    data = if remoteErrors? then remoteErrors else @obj.errors
    for attrib, errors of data
      remoteName = if @obj? then @obj.getAttrRemoteName(attrib) else attrib
      if remoteName? and attrib isnt "base"
        # be aware of invalid elements's nesting e.g. "div" inside of "p"
        @form.find("[data-attr=#{remoteName}]").find(".errors[data-for=#{remoteName}]").text errors[0]
        continue
      if attrib is "base" and errors.length > 0
        if $(".errors[data-for='base']").length is 1
          $(".errors[data-for='base']").text errors[0]
        else
          @submit.val errors[0]
    if @submit.val() is @submitVal or @submit.val() is "Sending..."
      @submit.val "Invalid data"
    @submit.removeClass("active").addClass 'failure'
    this._showErrors()
    setTimeout =>
      @submit.removeClass('failure').val @submitVal
      @form.find('input.invalid, textarea.invalid, select.invalid').removeClass 'invalid'
    , 1000

  _assignAttribs: ->
    return null if not @obj.constructor.attributes?
    for name, _ of @obj.constructor.attributes
      remoteName = @obj.getAttrRemoteName name
      formEl = @form.find("[data-attr=#{remoteName}]").find "input,textarea,select"
      if formEl.length is 1
        @obj[name] = formEl.val()
        continue
      if formEl.first().attr("type") isnt "hidden" and formEl.last().attr('type') isnt "checkbox"
        continue
      if formEl.last().is ":checked"
        @obj[name] = formEl.last().val()
      else
        @obj[name] = formEl.first().val()

  _hideErrors: ->
    @form.find('.errors').each (index, e) =>
      if $(e).text().trim().length > 0
        $(e).text ""
        if @hideErrorFunc? and @errorsShowHideDuration?
          $(e).velocity @hideErrorFunc, @errorsShowHideDuration
        else
          $(e).hide()

  _showErrors: ->
    @form.find('.errors').each (index, e) =>
      if $(e).text().trim().length > 0
        if @showErrorFunc? and @errorsShowHideDuration?
          $(e).velocity @showErrorFunc, @errorsShowHideDuration
        else
          $(e).show()

  _submittingForm: (hideErrors = true) ->
    @submit.removeClass('success').removeClass('failure').addClass('active').val "Sending..."
    @delegator[@callbackActive]() if @callbackActive?
    this._hideErrors() if hideErrors

  _connectionError: ->
    @submit.removeClass('active').addClass('failure').val 'Connection Error'
    setTimeout =>
      @submit.removeClass('failure').val @submitVal
    , 3000