'use strict'

##
# Controller used in the prices edition page
##
Application.Controllers.controller "EditPricingController", ["$scope", "$state", '$uibModal', '$filter', 'TrainingsPricing', 'Credit', 'Pricing', 'Plan', 'Coupon', 'plans', 'groups', 'growl', 'machinesPricesPromise', 'Price', 'dialogs', 'trainingsPricingsPromise', 'trainingsPromise', 'machineCreditsPromise', 'machinesPromise', 'trainingCreditsPromise', 'couponsPromise', 'spacesPromise', 'spacesPricesPromise', 'spacesCreditsPromise', '_t'
, ($scope, $state, $uibModal, $filter, TrainingsPricing, Credit, Pricing, Plan, Coupon, plans, groups, growl, machinesPricesPromise, Price, dialogs, trainingsPricingsPromise, trainingsPromise, machineCreditsPromise, machinesPromise, trainingCreditsPromise, couponsPromise, spacesPromise, spacesPricesPromise, spacesCreditsPromise, _t) ->

  ### PUBLIC SCOPE ###
  ## List of machines prices (not considering any plan)
  $scope.machinesPrices = machinesPricesPromise

  ## List of trainings pricing
  $scope.trainingsPricings = trainingsPricingsPromise

  ## List of available subscriptions plans (eg. student/month, PME/year ...)
  $scope.plans = plans

  ## List of groups (eg. normal, student ...)
  $scope.groups = groups

  ## Associate free machine hours with subscriptions
  $scope.machineCredits = machineCreditsPromise

  ## Array of associations (plan <-> training)
  $scope.trainingCredits = trainingCreditsPromise

  ## Associate a plan with all its trainings ids
  $scope.trainingCreditsGroups = {}

  ## List of trainings
  $scope.trainings = trainingsPromise

  ## List of machines
  $scope.machines = machinesPromise

  ## List of coupons
  $scope.coupons = couponsPromise

  ## List of spaces
  $scope.spaces = spacesPromise

  ## Associate free space hours with subscriptions
  $scope.spaceCredits = spacesCreditsPromise

  ## List of spaces prices (not considering any plan)
  $scope.spacesPrices = spacesPricesPromise

  ## The plans list ordering. Default: by group
  $scope.orderPlans = 'group_id'

  ## Status of the drop-down menu in Credits tab
  $scope.status =
    isopen: false



  $scope.findTrainingsPricing = (trainingsPricings, trainingId, groupId)->
    for trainingsPricing in trainingsPricings
      if trainingsPricing.training_id == trainingId and trainingsPricing.group_id == groupId
        return trainingsPricing


  $scope.updateTrainingsPricing = (data, trainingsPricing)->
    if data?
      TrainingsPricing.update({ id: trainingsPricing.id }, { trainings_pricing: { amount: data } }).$promise
    else
      _t('pricing.please_specify_a_number')

  ##
  # Retrieve a plan from its given identifier and returns it
  # @param id {number} plan ID
  # @returns {Object} Plan, inherits from $resource
  ##
  $scope.getPlanFromId = (id) ->
    for plan in $scope.plans
      if plan.id == parseInt(id)
        return plan



  ##
  # Retrieve a group from its given identifier and returns it
  # @param id {number} group ID
  # @returns {Object} Group, inherits from $resource
  ##
  $scope.getGroupFromId = (groups, id) ->
    for group in groups
      if group.id == parseInt(id)
        return group


  ##
  # Returns a human readable string of named trainings, according to the provided array.
  # $scope.trainings may contains the full list of training. The returned string will only contains the trainings
  # whom ID are given in the provided parameter
  # @param trainings {Array<number>} trainings IDs array
  ##
  $scope.showTrainings = (trainings) ->
    unless angular.isArray(trainings) and trainings.length > 0
      return _t('pricing.none')

    selected = []
    angular.forEach $scope.trainings, (t) ->
      if trainings.indexOf(t.id) >= 0
        selected.push t.name
    return if selected.length then selected.join(' | ') else _t('pricing.none')



  ##
  # Validation callback when editing training's credits. Save the changes.
  # @param newdata {Object} training and associated plans
  # @param planId {number|string} plan id
  ##
  $scope.saveTrainingCredits = (newdata, planId) ->
    # save the number of credits
    Plan.update {id: planId},
      training_credit_nb: newdata.training_credits
    , angular.noop() # do nothing in case of success
    , (error) ->
       growl.error(_t('pricing.an_error_occurred_while_saving_the_number_of_credits'))

    # save the associated trainings
    angular.forEach $scope.trainingCreditsGroups, (original, key) ->
      if parseInt(key) == parseInt(planId) # we've got the original data
        if original.join('_') != newdata.training_ids.join('_') # if any changes
          # iterate through the previous credits to remove
          angular.forEach original, (oldTrainingId) ->
            if newdata.training_ids.indexOf(oldTrainingId) == -1
              tc = findTrainingCredit(oldTrainingId, planId)
              if tc
                tc.$delete {}
                , ->
                  $scope.trainingCredits.splice($scope.trainingCredits.indexOf(tc), 1)
                  $scope.trainingCreditsGroups[planId].splice($scope.trainingCreditsGroups[planId].indexOf(tc.id), 1)
                , (error) ->
                  growl.error(_t('pricing.an_error_occurred_while_deleting_credit_with_the_TRAINING', {TRAINING:tc.creditable.name}))
              else
                growl.error(_t('pricing.an_error_occurred_unable_to_find_the_credit_to_revoke'))

          # iterate through the new credits to add
          angular.forEach newdata.training_ids, (newTrainingId) ->
            if original.indexOf(newTrainingId) == -1
              Credit.save
                credit:
                  creditable_id: newTrainingId
                  creditable_type: 'Training'
                  plan_id: planId
              , (newTc) -> # success
                $scope.trainingCredits.push(newTc)
                $scope.trainingCreditsGroups[newTc.plan_id].push(newTc.creditable_id)
              , (error) -> # failed
                  training = getTrainingFromId(newTrainingId)
                  growl.error(_t('pricing.an_error_occurred_while_creating_credit_with_the_TRAINING', {TRAINING: training.name}))
                  console.error(error)




  ##
  # Cancel the current training credit modification
  # @param rowform {Object} see http://vitalets.github.io/angular-xeditable/
  ##
  $scope.cancelTrainingCredit = (rowform) ->
    rowform.$cancel()


  ##
  # Create a new empty entry in the $scope.machineCredits array
  # @param e {Object} see https://docs.angularjs.org/guide/expression#-event-
  ##
  $scope.addMachineCredit = (e)->
    e.preventDefault()
    e.stopPropagation()
    $scope.inserted =
      creditable_type: 'Machine'
    $scope.machineCredits.push($scope.inserted)
    $scope.status.isopen = !$scope.status.isopen



  ##
  # In the Credits tab, while editing a machine credit row, select the current machine from the
  # drop-down list of machines as the current item.
  # @param credit {Object} credit object, inherited from $resource
  ##
  $scope.showCreditableName = (credit) ->
    selected = _t('pricing.not_set')
    if credit and credit.creditable_id
      if credit.creditable_type == 'Machine'
        angular.forEach $scope.machines, (m)->
          if m.id == credit.creditable_id
            selected = m.name + ' ( id. ' + m.id + ' )'
      else if credit.creditable_type == 'Space'
        angular.forEach $scope.spaces, (s)->
          if s.id == credit.creditable_id
            selected = s.name
    return selected



  ##
  # Validation callback when editing machine's credits. Save the changes.
  # This will prevent the creation of two credits associating the same machine and plan.
  # @param data {Object} machine, associated plan and number of credit hours.
  # @param [id] {number} credit id for edition, create a new credit object if not provided
  ##
  $scope.saveMachineCredit = (data, id) ->
    for mc in $scope.machineCredits
      if mc.plan_id == data.plan_id and mc.creditable_id == data.creditable_id and (id == null or mc.id != id)
        growl.error(_t('pricing.error_a_credit_linking_this_machine_with_that_subscription_already_exists'))
        unless id
          $scope.machineCredits.pop()
        return false

    if id?
      Credit.update {id: id}, credit: data, ->
        growl.success(_t('pricing.changes_have_been_successfully_saved'))
    else
      data.creditable_type = 'Machine'
      Credit.save
        credit: data
      , (resp) ->
        $scope.machineCredits[$scope.machineCredits.length-1].id = resp.id
        growl.success(_t('pricing.credit_was_successfully_saved'))


  ##
  # Removes the newly inserted but not saved machine credit / Cancel the current machine credit modification
  # @param rowform {Object} see http://vitalets.github.io/angular-xeditable/
  # @param index {number} credit index in the $scope.machineCredits array
  ##
  $scope.cancelMachineCredit = (rowform, index) ->
    if $scope.machineCredits[index].id?
      rowform.$cancel()
    else
      $scope.machineCredits.splice(index, 1)



  ##
  #  Deletes the machine credit at the specified index
  # @param index {number} machine credit index in the $scope.machineCredits array
  ##
  $scope.removeMachineCredit = (index) ->
    Credit.delete $scope.machineCredits[index]
    $scope.machineCredits.splice(index, 1)



  ##
  # Create a new empty entry in the $scope.spaceCredits array
  # @param e {Object} see https://docs.angularjs.org/guide/expression#-event-
  ##
  $scope.addSpaceCredit = (e)->
    e.preventDefault()
    e.stopPropagation()
    $scope.inserted =
      creditable_type: 'Space'
    $scope.spaceCredits.push($scope.inserted)
    $scope.status.isopen = !$scope.status.isopen



  ##
  # Validation callback when editing space's credits. Save the changes.
  # This will prevent the creation of two credits associated with the same space and plan.
  # @param data {Object} space, associated plan and number of credit hours.
  # @param [id] {number} credit id for edition, create a new credit object if not provided
  ##
  $scope.saveSpaceCredit = (data, id) ->
    for sc in $scope.spaceCredits
      if sc.plan_id == data.plan_id and sc.creditable_id == data.creditable_id and (id == null or sc.id != id)
        growl.error(_t('pricing.error_a_credit_linking_this_space_with_that_subscription_already_exists'))
        unless id
          $scope.spaceCredits.pop()
        return false

    if id?
      Credit.update {id: id}, credit: data, ->
        growl.success(_t('pricing.changes_have_been_successfully_saved'))
    else
      data.creditable_type = 'Space'
      Credit.save
        credit: data
      , (resp) ->
        $scope.spaceCredits[$scope.spaceCredits.length - 1].id = resp.id
        growl.success(_t('pricing.credit_was_successfully_saved'))



  ##
  # Removes the newly inserted but not saved space credit / Cancel the current space credit modification
  # @param rowform {Object} see http://vitalets.github.io/angular-xeditable/
  # @param index {number} credit index in the $scope.spaceCredits array
  ##
  $scope.cancelSpaceCredit = (rowform, index) ->
    if $scope.spaceCredits[index].id?
      rowform.$cancel()
    else
      $scope.spaceCredits.splice(index, 1)



  ##
  # Deletes the space credit at the specified index
  # @param index {number} space credit index in the $scope.spaceCredits array
  ##
  $scope.removeSpaceCredit = (index) ->
    Credit.delete $scope.spaceCredits[index]
    $scope.spaceCredits.splice(index, 1)



  ##
  # If the plan does not have a type, return a default value for display purposes
  # @param type {string|undefined|null} plan's type (eg. 'partner')
  # @returns {string}
  ##
  $scope.getPlanType = (type) ->
    if type == 'PartnerPlan'
      return _t('pricing.partner')
    else return _t('pricing.standard')

  ##
  # Change the plans ordering criterion to the one provided
  # @param orderBy {string} ordering criterion
  ##
  $scope.setOrderPlans = (orderBy) ->
    if $scope.orderPlans == orderBy
      $scope.orderPlans = '-'+orderBy
    else
      $scope.orderPlans = orderBy

  ##
  # Retrieve a price from prices array by a machineId and a groupId
  ##
  $scope.findPriceBy = (prices, machineId, groupId)->
    for price in prices
      if price.priceable_id == machineId and price.group_id == groupId
        return price

  ##
  # update a price for a machine and a group, not considering any plan
  ##
  $scope.updatePrice = (data, price)->
    if data?
      Price.update({ id: price.id }, { price: { amount: data } }).$promise
    else
      _t('pricing.please_specify_a_number')

  ##
  # Delete the specified subcription plan
  # @param id {number} plan id
  ##
  $scope.deletePlan = (plans, id) ->
    if typeof id != 'number'
      console.error('[EditPricingController::deletePlan] Error: invalid id parameter')
    else
      # open a confirmation dialog
      dialogs.confirm
        resolve:
          object: ->
            title: _t('pricing.confirmation_required')
            msg: _t('pricing.do_you_really_want_to_delete_this_subscription_plan')
      , ->
        # the admin has confirmed, delete the plan
        Plan.delete {id: id}, (res) ->
          growl.success(_t('pricing.subscription_plan_was_successfully_deleted'))
          $scope.plans.splice(findItemIdxById(plans, id), 1)

        , (error) ->
          console.error('[EditPricingController::deletePlan] Error: '+error.statusText) if error.statusText
          growl.error(_t('pricing.unable_to_delete_the_specified_subscription_an_error_occurred'))



  ##
  # Generate a string identifying the given plan by literal humain-readable name
  # @param plan {Object} Plan object, as recovered from GET /api/plan/:id
  # @param groups {Array} List of Groups objects, as recovered from GET /api/groups
  # @param short {boolean} If true, the generated name will contains the group slug, otherwise the group full name
  # will be included.
  # @returns {String}
  ##
  $scope.humanReadablePlanName = (plan, groups, short)->
    "#{$filter('humanReadablePlanName')(plan, groups, short)}"



  ##
  # Delete a coupon from the server's database and, in case of success, from the list in memory
  # @param coupons {Array<Object>} should be called with $scope.coupons
  # @param id {number} ID of the coupon to delete
  ##
  $scope.deleteCoupon = (coupons, id) ->
    if typeof id != 'number'
      console.error('[EditPricingController::deleteCoupon] Error: invalid id parameter')
    else
      # open a confirmation dialog
      dialogs.confirm
        resolve:
          object: ->
            title: _t('pricing.confirmation_required')
            msg: _t('pricing.do_you_really_want_to_delete_this_coupon')
      , ->
        # the admin has confirmed, delete the coupon
        Coupon.delete {id: id}, (res) ->
          growl.success(_t('coupon_was_successfully_deleted'))
          $scope.coupons.splice(findItemIdxById(coupons, id), 1)

        , (error) ->
          console.error('[EditPricingController::deleteCoupon] Error: '+error.statusText) if error.statusText
          if error.status == 422
            growl.error(_t('pricing.unable_to_delete_the_specified_coupon_already_in_use'))
          else
            growl.error(_t('pricing.unable_to_delete_the_specified_coupon_an_unexpected_error_occurred'))



  ##
  # Open a modal allowing to select an user and send him the details of the provided coupon
  # @param coupon {Object} The coupon to send
  ##
  $scope.sendCouponToUser = (coupon) ->
    $uibModal.open
      templateUrl: '<%= asset_path "admin/pricing/sendCoupon.html" %>'
      resolve:
        coupon: -> coupon
      size: 'md'
      controller: ['$scope', '$uibModalInstance', 'Coupon', 'coupon', '_t', ($scope, $uibModalInstance, Coupon, coupon, _t) ->

        ## Member, receiver of the coupon
        $scope.ctrl =
          member: null

        ## Details of the coupon to send
        $scope.coupon = coupon

        ## Callback to validate sending of the coupon
        $scope.ok = ->
          Coupon.send {coupon_code: coupon.code, user_id: $scope.ctrl.member.id}, (res) ->
            growl.success(_t('pricing.coupon_successfully_sent_to_USER', {USER: $scope.ctrl.member.name}))
            $uibModalInstance.close({user_id: $scope.ctrl.member.id})
          , (err) ->
            growl.error(_t('pricing.an_error_occurred_unable_to_send_the_coupon'))

        ## Callback to close the modal and cancel the sending process
        $scope.cancel = ->
          $uibModalInstance.dismiss('cancel')
      ]


  ### PRIVATE SCOPE ###

  ##
  # Kind of constructor: these actions will be realized first when the controller is loaded
  ##
  initialize = ->

    $scope.trainingCreditsGroups = groupCreditsByPlan($scope.trainingCredits)

    ## adds empty array for plan which hasn't any credits yet
    for plan in $scope.plans
      unless $scope.trainingCreditsGroups[plan.id]?
        $scope.trainingCreditsGroups[plan.id] = []



  ##
  # Retrieve an item index by its ID from the given array of objects
  # @param items {Array<{id:number}>}
  # @param id {number}
  # @returns {number} item index in the provided array
  ##
  findItemIdxById = (items, id)->
    (items.map (item)->
      item.id
    ).indexOf(id)



  ##
  # Group the given credits array into a map associating the plan ID with its associated trainings/machines
  # @return {Object} the association map
  ##
  groupCreditsByPlan = (credits) ->
    creditsMap = {}
    angular.forEach credits, (c) ->
      unless creditsMap[c.plan_id]
        creditsMap[c.plan_id] = []
      creditsMap[c.plan_id].push(c.creditable_id)
    creditsMap



  ##
  # Iterate through $scope.traininfCredits to find the credit matching the given criterion
  # @param trainingId {number|string} training ID
  # @param planId {number|string} plan ID
  ##
  findTrainingCredit = (trainingId, planId) ->
    trainingId = parseInt(trainingId)
    planId = parseInt(planId)

    for credit in $scope.trainingCredits
      if credit.plan_id == planId and credit.creditable_id == trainingId
        return credit


  ##
  # Retrieve a training from its given identifier and returns it
  # @param id {number} training ID
  # @returns {Object} Training inherited from $resource
  ##
  getTrainingFromId = (id) ->
    for training in $scope.trainings
      if training.id == parseInt(id)
        return training


  ## !!! MUST BE CALLED AT THE END of the controller
  initialize()
]
