_ = require('lodash')
utils = require('../../common/js/utils.coffee')
yasen_client = require('./yasen_cache_client.coffee')
sorter = require('./sorter.coffee')
filter = require('./filter.coffee')
seller = require('./seller.coffee')
bench = require('../../common/js/bench.coffee')
throttler = require('./request_throttler.coffee')


class RequestProcessor
  first_tickets_arrived_timeout: 9000

  _merge_proposals: (old_proposals, terms) ->
    proposals = old_proposals or []
    for key, proposal of terms
      proposal.gate_id = key
      insert_to = _.sortedIndex(proposals, proposal, (proposal) ->
        # sort by price and then by productivity
        productivity = @gates[proposal.gate_id].productivity or 0
        parseFloat(proposal.unified_price) - (productivity / 1000)
      , @)
      proposals.splice(insert_to, 0, proposal)
    proposals

  constructor: (@id, @di_callbacks, @sort_order) ->
    @throttlers = []
    @params = {}
    @filters_state = {}
    @yasen_client = null
    @tickets_by_sign = {}
    @tickets_list = []
    @tickets = []
    @gates = {}
    @search_id = {}
    @airlines = {}
    @airports = {}
    @average_prices = {}
    @currencies = {}
    @boundaries = {}
    @banner_info = {}
    @segments = []
    @products = []
    @gates_meta = []
    @smart_throttler = null
    @best_tickets_params = null
  start: (@params) ->
    @smart_throttler = throttler(@first_tickets_arrived_timeout)
    @yasen_client = yasen_client(@params, {
      on_update: @on_update
      on_finish: @on_finish
      on_error: @on_error
    })

  stop: ->
    return if !@yasen_client
    @yasen_client.stop()

  find_ticket: (tickets_list, compare_func) ->
    cheap_pair = tickets_list[0]
    for ticket_pair in tickets_list
      if compare_func(ticket_pair, cheap_pair) < 0
        cheap_pair = ticket_pair
    cheap_pair

  set_ticket_rating: (filtered_tickets) ->
    cheapest_ticket = @find_ticket(filtered_tickets, sorter.sort_funcs.price)
    fastest_ticket = @find_ticket(filtered_tickets, sorter.sort_funcs.duration)

    fastest_ticket_duration = fastest_ticket[0].total_duration
    cheapest_ticket_price = cheapest_ticket[1][0].unified_price

    best_ticket_rating = 1000  # less is better

    for ticket in filtered_tickets
      duration = ticket[0].total_duration
      price = ticket[1][0].unified_price
      ticket[0].ticket_rating = (duration / fastest_ticket_duration) * (price / cheapest_ticket_price)
      if ticket[0].min_stop_duration and ticket[0].min_stop_duration < 60
        ticket[0].ticket_rating += 1
      if ticket[0].ticket_rating < best_ticket_rating
        best_ticket_rating = ticket[0].ticket_rating

    for ticket in filtered_tickets
      _ticket = ticket[0]
      _ticket.best = 'hidden'
      if _ticket.ticket_rating == best_ticket_rating
        _ticket.best = 'rating'
      if _ticket.total_duration == fastest_ticket_duration
        _ticket.best = 'fastest'
      if ticket[1][0].unified_price == cheapest_ticket_price
        _ticket.best = 'price'

  _compose_products: bench('compose_products', (with_filtering) ->
    @cheapest_ticket = @find_ticket(@tickets_list, sorter.sort_funcs.price)

    @set_ticket_rating(@tickets_list)  # TODO ask why in explision it was called after filtering?
    # and may be it is better to call it not here which will be done every time filters is changed
    # but insted only once in place where this attribute is being set

    filtered_tickets = if with_filtering
      filter.filter(@tickets_list, @filters_state)
    else
      @tickets_list

    @tickets = sorter.sort(filtered_tickets, @sort_order)
    @products = seller.compose(@params, @tickets, @banner_info)
  )

  set_filters: (filters_state, with_composing) ->
    _.extend(@filters_state, filters_state)
    if with_composing
      @_compose_products(true)
      @di_callbacks.tickets_updated(@id, @tickets, 'filters_updated')
      @di_callbacks.products_updated(@id, @products, 'filters_updated')

  reset_filters: ->
    if  @filters_state.tour_ticket
      @filters_state = _.extend({}, tour_ticket: @filters_state.tour_ticket)
      @_compose_products(true)
    else
      @filters_state = {}
      @_compose_products(false)

    @di_callbacks.tickets_updated(@id, @tickets, 'filters_updated')
    @di_callbacks.products_updated(@id, @products, 'filters_updated')

  set_sort_order: (@sort_order) ->
    @_compose_products(true)
    @di_callbacks.tickets_updated(@id, @tickets, 'sort_order')
    @di_callbacks.products_updated(@id, @products, 'sort_order')

  highlight_best_tickets: (@best_tickets_params) ->
    @_compose_products(true)
    @di_callbacks.tickets_updated(@id, @tickets, 'best_tickets')
    @di_callbacks.products_updated(@id, @products, 'best_tickets')

  is_innovata: (ticket) ->
    _.include(_.keys(ticket.terms), '666')

  on_update: (yasen_response) =>
    callback_accum = [ [], [], [] ]
    # NOTE: you should use data name as part of event_name
    # for example: event - currencies_updated, data name - currencies
    # FOO_updated
    # city_distance_updated
    key_processes =
      segments: (@segments) =>
        Rollbar?.configure({payload: {yasen_response:{segments: @segments}}})
        callback_accum[1].push('segments_updated')
      city_distance: (@city_distance) =>
        callback_accum[0].push('city_distance_updated')
      search_id: (search_id) =>
        @search_id = {search_id: search_id}
        Rollbar?.configure({payload: {yasen_response: {search_id: search_id}}})
        callback_accum[0].push('search_id_updated')
      average_price: (@average_prices) =>
        callback_accum[0].push('average_prices_updated')
      currency_rates: (currency_rates) =>
        @currencies = currency_rates
        callback_accum[0].push('currencies_updated')
      banner_info: (@banner_info) =>
      gates_info: (gates_info) =>
        @gates = _.extend(@gates, gates_info)
        callback_accum[0].push('gates_updated')
      airports: (airports) =>
        @airports = _.extend(@airports, airports)
        callback_accum[0].push('airports_updated')
      airlines: (airlines) =>
        @airlines = _.extend(@airlines, airlines)
        callback_accum[0].push('airlines_updated')
      meta: (meta) =>
        return if !meta.gates
        @gates_meta = @gates_meta.concat(meta.gates)
        callback_accum[1].push('gates_meta_updated')

      filters_boundary: (filters_boundaries) =>
        for name in ['departure', 'arrival']
          @boundaries["#{name}_airports"] = if @boundaries["#{name}_airports"]
            _.union(@boundaries["#{name}_airports"], filters_boundaries['airports'][name])
          else
            filters_boundaries['airports']?[name]

        for bound_name in ['stops_duration', 'departure_time_0', 'departure_time_1', 'arrival_datetime_0', 'arrival_datetime_1', 'stops_count']
          bound_values = filters_boundaries[bound_name]
          continue if !bound_values
          @boundaries[bound_name] or= {}
          for key, value of bound_values
            if key of @boundaries[bound_name]
              compare_func = if key == 'min' or /^\d+$/.test(key) then utils.min else utils.max
              @boundaries[bound_name][key] = compare_func([@boundaries[bound_name][key], value])
            else
              @boundaries[bound_name][key] = value
        callback_accum[1].push('boundaries_updated')

      proposals: bench('merge', ((tickets) =>
        tickets_were_empty = _.isEmpty(@tickets_list)

        for ticket in tickets
          continue if @is_innovata(ticket)
          if ticket.sign of @tickets_by_sign
            @tickets_by_sign[ticket.sign][1] =
              @_merge_proposals(@tickets_by_sign[ticket.sign][1], ticket.terms)
          else
            proposals = @_merge_proposals([], ticket.terms)
            ticket_item = [ticket, proposals]
            @tickets_by_sign[ticket.sign] = ticket_item
            @tickets_list.push(ticket_item)

        @fuzzy_tickets = @tickets_list

        if tickets.length && @tickets_list.length
          callback_accum[2].push('fuzzy_tickets_updated')
          callback_accum[2].push('tickets_updated')

      ), {tickets: (args) -> args[0].length})

    for data in yasen_response
      for [key, value] in _(data).pairs().sortBy((el) -> el[0]).value()
        if key of key_processes and (!!value || _.isEmpty(value))
          key_processes[key](value)

    callback_accum = _.map(callback_accum, (callback_chunk) -> _.uniq(callback_chunk))
    callbacks = _.flatten(callback_accum)

    should_fire_products = 'tickets_updated' in callbacks

    if should_fire_products
      @_compose_products(true)
      callbacks.push('cheapest_ticket_updated')

    for callback_name in callbacks
      [data_name..., _verb] = callback_name.split('_')
      data_name = data_name.join('_')
      @di_callbacks[callback_name](@id, @[data_name])

    if should_fire_products and @products.length > 0
      @smart_throttler.update_th(@di_callbacks.products_updated)(@id, @products)

  on_finish: => @smart_throttler.finish_th(@di_callbacks.finish)(@id)

  on_error: (data) => @di_callbacks.error(@id, data)

  clear_throttlers: ->
    for throttler, idx in @throttlers
      throttler.cancel()

module.exports = RequestProcessor
