import Ember from 'ember'
import EmberCPM from 'ember-cpm'
{ Macros: { sum, difference, product } } = EmberCPM
# SOURCES
# http://bl.ocks.org/kiranml1/6872226
# https://bost.ocks.org/mike/bar/2/
# https://bl.ocks.org/hrecht/f84012ee860cb4da66331f18d588eee3

GraphWrapper = Ember.Object.extend
  record: null
  start: null
  finish: null
  init: ->
    @_super arguments...
    @set 'start', value: 0, date: null, has_variable_value_part: false
    @set 'finish', value: 0, date: null, has_variable_value_part: false
  duration: Ember.computed 'start.value', 'finish.value', ->
    @get('finish.value') - @get('start.value')
export default Ember.Component.extend
  classNames: ['workflow-group-offset-diagram-component']
  bar_height: 20
  x_axis_space: 60
  left_space: 250
  svg_width: 1200
  number_of_groups: Ember.computed.alias 'workflow_groups.length'
  graph_height: product 'number_of_groups', 'bar_height'
  all_workflow_groups: Ember.computed.alias 'active_workflow_groups'
  workflow_groups: Ember.computed 'all_workflow_groups.@each.offset_type', 'all_workflow_groups.@each.duration_value', ->
    @get('all_workflow_groups').filter((r) -> r.get('offset_type') isnt 'none').sortBy('position')
  range_start_default: null
  range_start: null
  range_end: null
  range_bounds: {min: -7, max: 7}
  range_formatter: (-> [{to: @days_offset_format.bind(@)}, {to: @days_offset_format.bind(@)}]).property()
  #range_formatter: [{to: ((v)->v)}, {to: ((v)->v)}]
  reference_date_day_of_week: Ember.computed.oneWay 'project.reference_date_day_of_week'
  choose_reference_date: true # choose a specific date to display as planning. Use X with migration date at specific day otherwise
  chosen_absolute_date: null
  batch_size_simulation_range: Ember.computed 'project.{batch_size_lower_value,batch_size_upper_value}',->
    {min: @get('project.batch_size_lower_value'), max: @get('project.batch_size_upper_value')}
  batch_size_simulation_value: 0
  batch_size_formatter: (-> [to: (v) -> Math.round(v)]).property()
  #all_offset_values: Ember.computed.mapBy 'workflow_groups', 'offset_values'
  #uniq_offset_values: Ember.computed.uniq 'all_offset_values'
  #offset_values: Ember.computed 'uniq_offset_values', -> @get('uniq_offset_values').sort()
  #min_offset_value: Ember.computed.alias 'offset_values.firstObject'
  #max_offset_value: Ember.computed.alias 'offset_values.lastObject'
  #max_end_time: Ember.computed 'workflow_groups.@each.offset_value', 'workflow_groups.@each.duration_value', ->
  #  max_end_time = -1e-10
  #  @get('workflow_groups').forEach (workflow_group) ->
  #    end_time = workflow_group.get('offset_value') + workflow_group.get('duration_value')
  #    max_end_time = end_time if end_time > max_end_time
  #  max_end_time
  days_offset_format: (v) ->
    v = Math.round(v) # could be float from slider
    if @get('chosen_absolute_date')
      date = moment(@get('chosen_absolute_date'))
      date.add(v, 'day') if v
      date.format("D-M-YYYY")
    else
      return '' unless v
      return '' unless v and Number.isFinite(v)
      if v % 7 is 0
        v = v / 7
        if v > 0 then "X+#{v}w" else "X#{v}w"
      else
        if v > 0 then "X+#{v}" else "X#{v}"

  actions:
    range_changed: ([range_start, range_end]) ->
      [range_start, range_end] = [Math.round(range_start), Math.round(range_end)]
      @setProperties
        range_start_default: [range_start, range_end]
        range_start: range_start
        range_end: range_end

    batch_size_simulation_change: (value) ->
      @setProperties
        range_start: null
        range_end: null
        batch_size_simulation_value: Math.round(value)

    hide_absolute_date_choice: ->
      @set 'chosen_absolute_date', null # important! determinse wether the labels use X or dates
      @set 'choose_reference_date', false

  #didReceiveAttrs: ->
  init: ->
    @_super arguments...
    @set 'batch_size_simulation_value', @get('project.batch_size_lower_value') if not @get('batch_size_simulation_value') and @get('project.batch_size_lower_value')

  didReceiveAttrs: ->
    console.log 'Reload workflow-group-offset-diagram call from after render'
    Ember.run.scheduleOnce 'afterRender', @, @_setup_graph

  #_graph_observer: Ember.observer 'workflow_groups.@each.{offset_value,name,duration_value}', 'range_start', 'range_end', 'batch_size_simulation_value', 'batch_size_simulation_range', 'reference_date_day_of_week', 'chosen_absolute_date', ->
  _graph_observer: Ember.observer 'project.update_revision', '{range_start,range_end}', ->
    console.log 'Reload workflow-group-offset-diagram call from observer'
    Ember.run.debounce  @, @_setup_graph, 150

  _setup_graph: ->
    console.log 'Reload workflow-group-offset-diagram'
    container_selector = '.workflow-group-offset-diagram-container'
    component = @
    @$(container_selector).html('').append '<svg></svg>'
    workflow_groups = @get('workflow_groups').toArray()
    return unless workflow_groups.length
    active_workflow_group_ids = workflow_groups.mapBy('id').toArray()

    # create conditioned workflow_groups
    reference_date_day_of_week = @get('reference_date_day_of_week')
    if @get('chosen_absolute_date')
      reference_date = moment(@get('chosen_absolute_date'))
    else
      reference_date = moment()
      reference_date.day reference_date_day_of_week if reference_date_day_of_week # updates reference_date instance

    wrappers = []
    x_min = -1 # at least include zero
    x_max = 0
    batch_size_simulation_value = @get('batch_size_simulation_value')
    # recursive adder
    add_dependent_wrapper_to_wrappers = (base_wrapper, record) ->
      wrapper = GraphWrapper.create
        record: record
      if record.get('offset_type') is 'connected_finish'
        wrapper.set 'finish.value', base_wrapper.get('start.value') - (record.get('offset_value') || 0)
        wrapper.set 'start.constant_value', wrapper.get('finish.value') - (record.get('duration_value') || 0)
        if batch_size_simulation_value and threshold = wrapper.get('record.batch_element_threshold_value')
          # see how many times the threshold is exceeded. Within the threshold means no extra time
          threshold_exceedance = Math.floor(batch_size_simulation_value / (0.1 + parseInt(threshold))) # + 0.1 (delta) to exclude exact match of batch_size and threshold
          if threshold_exceedance
            wrapper.set 'has_variable_value_part', true
            period_unit = wrapper.get('record.batch_element_threshold_unit')
            base_date = reference_date.clone().add(wrapper.get('start.constant_value'), 'day')
            if period_unit is 'businessday'
              wrapper.set 'start.date', base_date.clone().businessSubtract(threshold_exceedance)
            else
              wrapper.set 'start.date', base_date.clone().subtract(threshold_exceedance, period_unit)
            wrapper.set 'start.value', wrapper.get('start.constant_value') - base_date.diff(wrapper.get('start.date'), 'day')
          else
            wrapper.set 'start.value', wrapper.get('start.constant_value')
        else
          wrapper.set 'start.value', wrapper.get('start.constant_value')
      else #if record.get('offset_type') is 'connected_start'
        wrapper.set 'start.value', base_wrapper.get('finish.value') + (record.get('offset_value') || 0)
        wrapper.set 'finish.constant_value', wrapper.get('start.value') + (record.get('duration_value') || 0)
        if batch_size_simulation_value and threshold = wrapper.get('record.batch_element_threshold_value')
          if threshold_exceedance = Math.floor(batch_size_simulation_value / (0.1 + parseInt(threshold))) # + 0.1 (delta) to exclude exact match of batch_size and threshold
            wrapper.set 'has_variable_value_part', true
            period_unit = wrapper.get('record.batch_element_threshold_unit')
            base_date = reference_date.clone().add(wrapper.get('finish.constant_value'))
            if period_unit is 'businessday'
              wrapper.set 'finish.date', base_date.clone().businessAdd(threshold_exceedance)
            else
              wrapper.set 'finish.date', base_date.clone().add(threshold_exceedance, period_unit)
            wrapper.set 'finish.value', wrapper.get('finish.constant_value') + wrapper.get('finish.date').diff(base_date, 'day')
          else
            wrapper.set 'finish.value', wrapper.get('finish.constant_value')
        else
          wrapper.set 'finish.value', wrapper.get('finish.constant_value')
      wrapper.set 'start.date',  reference_date.clone().add(wrapper.get('start.value'),  'day') unless wrapper.get('start.date')
      wrapper.set 'finish.date', reference_date.clone().add(wrapper.get('finish.value'), 'day') unless wrapper.get('finish.date')
      x_min = Math.min(x_min, wrapper.get('start.value'))
      x_max = Math.max(x_max, wrapper.get('finish.value'))
      wrappers.push wrapper
      record.get('offset_dependents').forEach (offset_dependent_record) ->
        add_dependent_wrapper_to_wrappers wrapper, offset_dependent_record if offset_dependent_record.get('id') in active_workflow_group_ids

    workflow_groups.rejectBy('offset_base.content').forEach (record) ->
      wrapper = GraphWrapper.create
        record: record
      add_dependent_wrapper_to_wrappers wrapper, record
    wrappers = wrappers.sortBy('record.position')
    x_max += 1 # design space at the right

    if @get('range_bounds.min') isnt x_min or @get('range_bounds.max') isnt x_max
      @set 'range_bounds', min: x_min, max: x_max
      @set 'range_start_default', [x_min, x_max] # bounds have changed, reset starts
    else if not @get('range_start_default') # uninitialized
      @set 'range_start_default', [x_min, x_max]

    graph_min = @get('range_start') || x_min
    graph_max = @get('range_end')   || x_max

    graph_range_width = @get('svg_width') - @get('left_space') - 22
    x = d3.scale.linear().domain([graph_min, graph_max]).range([0, graph_range_width])
    #yscale = d3.scale.linear().domain([0, @get('number_of_groups')]).range([0, @get('graph_height')])
    y = d3.scale.ordinal().rangeRoundBands([@get('graph_height'), 0], 0.1).domain(wrappers.mapBy('record.name').reverse())

    x_ticks = []
    wrappers.forEach (wrapper) ->
      [start, finish] = wrapper.getPropertyValues('start.value', 'finish.value')
      x_ticks.push start if x_ticks.indexOf(start) < 0
      x_ticks.push finish if x_ticks.indexOf(finish) < 0
    #workflow_groups.forEach (workflow_action_definition) ->
    #  offset_value = workflow_action_definition.get('offset_value')
    #  x_ticks.push offset_value unless offset_value is 0 or x_ticks.indexOf(offset_value) > -1
    #  if duration_value = workflow_action_definition.get('duration_value')
    #    end_time = offset_value + duration_value
    #    x_ticks.push end_time unless end_time is 0 or x_ticks.indexOf(end_time) > -1
    x_ticks = x_ticks.filter( (v) -> v >= graph_min and v <= graph_max).sort(d3.ascending)
    # now remove the ticks not having enough space
    displayed_x_ticks = []
    if x_ticks.length
      displayed_x_ticks.push x_ticks[0]
      latest_pixel_x_for_space = x(x_ticks[0])
      x_ticks.forEach (v) ->
        current_pixel_x = x(v)
        if current_pixel_x - latest_pixel_x_for_space > 14
          latest_pixel_x_for_space = current_pixel_x
          displayed_x_ticks.push v

    svg_height = @get('x_axis_space') + @get('graph_height')
    canvas = d3.select("#{container_selector} svg")
      .attr 'width', '100%'
      .attr 'height', svg_height
      .attr 'viewBox', "0 0 #{@get('svg_width')} #{svg_height}"

    lines_data = x_ticks
    lines_data = lines_data.slice(1) if x(lines_data[0]) is graph_min # first element is y-axis, so no tick
    lines = canvas.append('g')
      .attr('class', 'offset-lines')
      .attr('transform', "translate(#{@get('left_space')},0)")
      .selectAll('line')
      .data(lines_data)
      .enter()
      .append('line')
      .attr(
        x1: (d) -> x(d)
        y1: 0
        x2: (d) -> x(d)
        y2: @get('graph_height') + 5
        class: 'offset-line'
      )

    if graph_max >= 0 and graph_min <= 0 # base line is zero
      base_line = canvas.append('line').attr
        x1: x(0)
        y1: 0
        x2: x(0)
        y2: 15 + @get('graph_height')
        class: 'base-line'
        transform: "translate(#{@get('left_space')},0)"

    bars_grid = canvas.append('g')
      .attr('transform', "translate(#{@get('left_space')},0)")
      .attr('class', 'bars')

    bars = bars_grid.selectAll('.bar')
      .data(wrappers.filter((d) -> (d.get('finish.value') > graph_min) and (d.get('start.value') < graph_max)))
      .enter()
    bars.append('rect')
      .attr('class', (d) -> "bar lead-time-#{d.get('duration') || 'none'}")
      .attr('x', (d) -> x(Math.max(d.get('start.value'), graph_min)))
      .attr('y', (d) -> y(d.get('record.name')))
      .attr('height', y.rangeBand())
      .attr 'width', (d) ->
        if duration = d.get('duration')
          display_range = duration
          if d.get('start.value') < graph_min # remove difference from width
            display_range = display_range  + d.get('start.value') - graph_min
          if d.get('finish.value') > graph_max  # remove difference from width right threshold
            display_range = display_range + graph_max - d.get('finish.value')
          graph_range_width * display_range / (graph_max - graph_min)
        else
          # no duration to display, clickable width, gets different styling
          5
      .on 'click', (wrapper) =>
        return unless wrapper?.get('record')
        @modal 'project/edit-workflow-group',
          model: wrapper.get('record')

    wrappers_having_extra_duration_due_to_batch_simulation = wrappers.filter (d) ->
      return false unless d.get('has_variable_value_part')
      if d.get('record.offset_type') is 'connected_finish'
        d.get('start.constant_value') > graph_min and d.get('start.value') < graph_max
      else
        d.get('finish.value') > graph_min and d.get('finish.constant_value') < graph_max
    if wrappers_having_extra_duration_due_to_batch_simulation.length
      canvas.append('g')
      .attr('transform', "translate(#{@get('left_space')},0)")
      .attr('class', 'bars-parts-indicating-extra-duration-due-to-batch-size')
      .selectAll('.bar')
      .data(wrappers_having_extra_duration_due_to_batch_simulation)
      .enter()
      .append('rect')
      .attr('class', "bar extra-duration")
      .attr('x', (d) ->
        if d.get('record.offset_type') is 'connected_finish'
          x(Math.max(d.get('start.value'), graph_min))
        else
          x(Math.max(d.get('finish.constant_value'), graph_min))
      )
      .attr('y', (d) -> y(d.get('record.name')))
      .attr('height', y.rangeBand())
      .attr 'width', (d) ->
        if d.get('record.offset_type') is 'connected_finish'
          display_range = Math.abs(d.get('start.constant_value') - d.get('start.value'))
          if d.get('start.value') < graph_min # remove difference from width
            display_range = display_range  + d.get('start.value') - graph_min
          if d.get('start.constant_value') > graph_max  # remove difference from width right threshold
            display_range = display_range + graph_max - d.get('start.constant_value')
          graph_range_width * display_range / (graph_max - graph_min)
        else
          display_range = Math.abs(d.get('finish.value') - d.get('finish.constant_value'))
          if d.get('finish.constant_value') < graph_min # remove difference from width
            display_range = display_range  + d.get('finish.constant_value') - graph_min
          if d.get('finish.value') > graph_max  # remove difference from width right threshold
            display_range = display_range + graph_max - d.get('finish.value')
          graph_range_width * display_range / (graph_max - graph_min)


    # bars crossing the left y-axis due to slider restrictions. Show text
    groups_crossing_left_axis = wrappers.filter (d) ->
      d.get('start.value') < graph_min and (d.get('finish.value') >= graph_min) # allow connecting edge, therefore >= in stead of >
    if groups_crossing_left_axis.length
      canvas.append('g')
        .attr('transform', "translate(#{@get('left_space')},0)")
        .attr('class', 'groups-crossing-left-axis')
        .selectAll('text')
        .data(groups_crossing_left_axis)
        .enter()
        .append('text')
        .attr('y', (d) -> 14 + y(d.get('record.name'))) # 14 is visually chosen, a bit less than bar_height
        .html((d) -> "&larr; #{component.days_offset_format(d.get('start.value'))}")

    # bars crossing the right y-axis due to slider restrictions. Show text
    groups_crossing_right_axis = wrappers.filter (d) ->
      d.get('start.value') < graph_max and (d.get('finish.value') > graph_max)
    if groups_crossing_right_axis.length
      canvas.append('g')
        .attr('transform', "translate(#{@get('left_space')},0)")
        .attr('class', 'groups-crossing-right-axis')
        .selectAll('text')
        .data(groups_crossing_right_axis)
        .enter()
        .append('text')
        .attr('y', (d) -> 14 + y(d.get('record.name'))) # 14 is visually chosen, a bit less than bar_height
        .attr('x', graph_range_width)
        .html((d) -> "#{component.days_offset_format(d.get('finish.value'))} &rarr;")

    yAxis = d3.svg.axis()
    yAxis
      .orient('left')
      .scale(y)
      .tickSize(1)
    y_xis = canvas.append('g')
      .attr('transform', "translate(#{@get('left_space')},0)")
      .attr('class','y-axis')
      .call(yAxis)
    xAxis = d3.svg.axis()
      .orient('bottom')
      .scale(x)
      .tickSize(1)
      .tickValues(displayed_x_ticks)
      .tickFormat component.days_offset_format.bind(component)
    if displayed_x_ticks.length
      x_xis = canvas.append('g')
        .attr('transform', "translate(#{@get('left_space')},#{@get('graph_height')})")
        .attr('class','x-axis')
        .call(xAxis)
        .selectAll('text')
          .style('text-anchor', 'end')
          .attr('dx', '-.8em')
          .attr('dy', '.15em')
          .attr('transform', "rotate(-40)")
    canvas.selectAll('.y-axis .tick').on 'click', (d) =>
      return unless workflow_group = workflow_groups.findBy 'name', d
      @modal 'project/edit-workflow-group',
        model: workflow_group

    @set 'd3graph', canvas
