# -----------------------------------------------------------------------------
# Author: Alexander Kravets <alex@slatestudio.com>,
#         Slate Studio (http://www.slatestudio.com)
#
# Coding Guide:
#   https://github.com/thoughtbot/guides/tree/master/style/coffeescript
# -----------------------------------------------------------------------------

# -----------------------------------------------------------------------------
# REST ARRAY STORE
# -----------------------------------------------------------------------------
#
# Config options:
#   pagination  - enable pagination for resource index, default `true`
#   searchable  - enable resource search, default `false`
#   urlParams   - additional parameter to be included into request
#   sortBy      - sort objects by field
#   sortReverse - reverse sorted objects
#
# Public methods:
#   loadObject
#   load
#   reset
#   search
#   push
#   update
#   remove
#
# -----------------------------------------------------------------------------
class @RestArrayStore extends ArrayStore

  # PRIVATE ===============================================

  _initialize_store: ->
    @dataFetchLock  = false
    @lastPageLoaded = false

    @searchable     = @config.searchable ? false
    @searchQuery    = ''

    @pagination     = @config.pagination ? true
    @nextPage       = 1
    @objectsPerPage = chr.itemsPerPageRequest ? 20

    @requestParams ?=
      page:    'page'
      perPage: 'perPage'
      search:  'search'

    @_configure_store()


  _configure_store: ->
    @ajaxConfig = {}


  # generate rest url for resource
  _resource_url: (type, id) ->
    objectPath = if id then "/#{ id }" else ''
    "#{ @config.path }#{ objectPath }"


  _request_url: (type, id) ->
    url = @_resource_url(type, id)

    if @config.urlParams
      extraParamsString = $.param(@config.urlParams)
      url = "#{ url }?#{ extraParamsString }"

    return url


  # do requests to database api
  _ajax: (type, id, data, success, error) ->
    options = $.extend @ajaxConfig,
      url:  @_request_url(type, id)
      type: type
      data: data
      success: (data, textStatus, jqXHR) =>
        success?(data)
        setTimeout ( => @dataFetchLock = false ), 50
      error: (jqXHR, textStatus, errorThrown ) =>
        error?(jqXHR.responseJSON)
        @dataFetchLock = false

    @dataFetchLock = true
    $.ajax options


  # check how this works with sorting enabled
  _sync_with_data_objects: (objects) ->
    if objects.length == 0 then return @_reset_data()
    if @_data.length  == 0 then return ( @_add_data_object(o) for o in objects )

    objectsMap = {}
    (o = @_normalize_object_id(o) ; objectsMap[o._id] = o) for o in objects

    objectIds     = $.map objects, (o) -> o._id
    dataObjectIds = $.map @_data,  (o) -> o._id

    addObjectIds        = $(objectIds).not(dataObjectIds).get()
    updateDataObjectIds = $(objectIds).not(addObjectIds).get()
    removeDataObjectIds = $(dataObjectIds).not(objectIds).get()

    for id in removeDataObjectIds
      @_remove_data_object(id)

    for id in addObjectIds
      @_add_data_object(objectsMap[id])

    for id in updateDataObjectIds
      @_update_data_object(id, objectsMap[id])


  # update next page counter and check if the last page was loaded
  _update_next_page: (data) ->
    if @pagination
      if data.length > 0
        @lastPageLoaded = true

        if data.length == @objectsPerPage
          @nextPage += 1
          @lastPageLoaded = false

      else
        @lastPageLoaded = true


  _is_pagination_edge_case: ->
    ( @pagination && @lastPageLoaded == false )


  _reload_current_page: (callbacks) ->
    @nextPage -= 1
    @load(true, callbacks)


  # PUBLIC ================================================

  # load a single object
  loadObject: (id, callbacks={}) ->
    callbacks.onSuccess ?= $.noop
    callbacks.onError   ?= $.noop

    @_ajax 'GET', id, {}, ((data) =>
      callbacks.onSuccess(data)
    ), callbacks.onError


  # load next page objects from database and trigger 'objects_added' event
  load: (sync=false, callbacks={}) ->
    callbacks.onSuccess ?= $.noop
    callbacks.onError   ?= $.noop

    params = {}

    if @pagination
      params[@requestParams.page]    = @nextPage
      params[@requestParams.perPage] = @objectsPerPage

    if @searchable && @searchQuery.length > 0
      params[@requestParams.search]  = @searchQuery

    params = $.param(params)

    @_ajax 'GET', null, params, ((data) =>
      @_update_next_page(data)

      if sync
        @_sync_with_data_objects(data)
      else
        @_add_data_object(o) for o in data

      callbacks.onSuccess(data)

      $(this).trigger('objects_added', { objects: data })
    ), -> chr.showError('Error while loading data, application error 500.')


  # reset data and load again first page
  reset: (@searchQuery='') ->
    @lastPageLoaded = false
    @nextPage       = 1
    @load(true)


  # load search results first page
  search: (searchQuery) ->
    @reset(searchQuery)


  # add new object
  push: (serializedFormObject, callbacks={}) ->
    callbacks.onSuccess ?= $.noop
    callbacks.onError   ?= $.noop

    obj = @_parse_form_object(serializedFormObject)

    @_ajax 'POST', null, obj, ((data) =>
      d = @_add_data_object(data)

      if @_is_pagination_edge_case()
        if d.position >= (@nextPage - 1) * @objectsPerPage
          # if object added to the end of the list remove it
          @_remove_data_object(d.object._id)

      callbacks.onSuccess(data)

    ), callbacks.onError


  # update objects attributes
  update: (id, serializedFormObject, callbacks={}) ->
    callbacks.onSuccess ?= $.noop
    callbacks.onError   ?= $.noop

    obj = @_parse_form_object(serializedFormObject)

    @_ajax 'PUT', id, obj, ((data) =>
      d = @_update_data_object(id, data)

      if @_is_pagination_edge_case() && d.positionHasChanged
        if d.position >= (@nextPage - 1) * @objectsPerPage - 1
          # if object added to the end of the list reload page to
          # sync last item on the page
          @_reload_current_page(callbacks)

      else
        callbacks.onSuccess(data)

    ), callbacks.onError


  # delete object
  remove: (id, callbacks={}) ->
    callbacks.onSuccess ?= $.noop
    callbacks.onError   ?= $.noop

    @_ajax 'DELETE', id, {}, ( =>
      @_remove_data_object(id)

      if @_is_pagination_edge_case()
        # after item delete reload page to sync last item on the page
        @_reload_current_page(callbacks)

      else
        callbacks.onSuccess()

    ), callbacks.onError