module EasyExtensions
  module EasyQueryOutputs
    class ChartOutput < EasyExtensions::EasyQueryHelpers::EasyQueryOutput

      attr_reader :data_names

      def self.available_for?(query)
        query.groupable_columns.any? && query.chart_support?
      end

      CHART_TYPES = %w(pie bar line)

      CHART_TYPES.each do |type|
        define_method("#{type}?") do
          chart_settings['primary_renderer'] == type
        end
      end

      def order
        1
      end

      def configured?
        query.grouped?
      end

      def apply_settings
        @params_was = {}
        @params_was[:group_by] = query.group_by
        @params_was[:show_sum_row] = query.show_sum_row
        @params_was[:load_groups_opened] = query.load_groups_opened
        @params_was[:column_names] = query.column_names

        query.group_by = Array.wrap(chart_settings['axis_x_column']).first
        query.show_sum_row = '1'
        query.load_groups_opened = '0'
        query.column_names = ['name', 'subject', 'number'].concat(Array.wrap(chart_settings['axis_y_column']) + Array.wrap(chart_settings['secondary_axis_y_column'].presence).compact)
        chart_settings['period_column'] = Array.wrap(chart_settings['axis_x_column']).first if query.chart_grouped_by_date_column?
      end

      def restore_settings
        @params_was.each do |k, val|
          query.send("#{k}=", val)
        end
      end

      def configure_from_defaults
        return false unless configured?
        chart_settings['axis_x_column'] = query.group_by.first
        if (ycol = query.columns.detect{|col| col.sumable? })
          chart_settings['axis_y_column'] = ycol.name
          chart_settings['axis_y_type'] = 'sum'
        else
          chart_settings['axis_y_type'] = 'count'
        end
        true
      end

      def chart_settings
        query.chart_settings
      end

      def before_render
        super
        unless chart_settings['period_column'].blank?
          @period_filter_was = query.filters.delete( chart_settings['period_column'] )
          query.apply_period_filter( chart_settings['period_column'] )
        end
      end

      def after_render
        super
        if @period_filter_was
          query.filters[chart_settings['period_column']] = @period_filter_was
        else
          query.filters.delete(chart_settings['period_column'])
        end
      end

      def sum_col
        return nil if chart_settings['axis_y_type'] == 'count'
        @sum_col ||= query.sumable_columns.detect{|c| c.name.to_s == chart_settings['axis_y_column'].to_s}
      end

      def secondary_sum_col
        return nil if chart_settings['primary_renderer'] == 'pie' || chart_settings['secondary_axis_y_column'].blank?
        @secondary_sum_col ||= query.sumable_columns.detect{|c| c.name.to_s == chart_settings['secondary_axis_y_column'].to_s}
      end


      def chart_data
        return @chart_data if @chart_data
        query.group_by = Array.wrap(chart_settings['axis_x_column']).first
        query.column_names = [chart_settings['axis_x_column'], chart_settings['axis_y_column'], chart_settings['secondary_axis_y_column'].presence].flatten.compact
        @chart_data = {}
        @data_names = {}
        groups = query.groups
        grouped_by_date_column = query.chart_grouped_by_date_column?

        attr_count = chart_settings['axis_y_type'] == 'count'
        groups.each do |group, attrs|
          group_name = grouped_by_date_column ? attrs[:name] : group
          values = attr_count ? attrs[:count] : attrs[:sums][:bottom][sum_col]
          @chart_data[group_name] = { name: attrs[:name], values: values.to_f.round(2), entity: attrs[:entity] }
          @chart_data[group_name][:values2] = attrs[:sums][:bottom][secondary_sum_col].to_f.round(2) if secondary_sum_col
        end if !groups.blank?

        @yaxis_label = chart_settings['y_label'].presence
        if @yaxis_label.nil?
          if attr_count
            @yaxis_label = h.l(:label_page_module_chart_axis_y_count)
          else
            @yaxis_label = h.l(:label_page_module_chart_axis_y_sum)
            @yaxis_label << ' ' <<  sum_col.caption if sum_col && sum_col.caption
          end
        end
        @yaxis2_label = h.l(:label_page_module_chart_axis_y_sum) + ' ' + secondary_sum_col.caption if secondary_sum_col && secondary_sum_col.caption
        @data_names = {values: @yaxis_label}
        @data_names[:values2] = @yaxis2_label if @yaxis2_label

        # fill blank spaces in date groups
        if grouped_by_date_column
          backup = @chart_data
          @chart_data = {}
          current = query.beginning_of_period_zoom( query.period_start_date ).to_time
          backup.keys.compact.sort.each do |key|
            bound = query.beginning_of_period_zoom( key.to_time ).to_time
            while current.to_date < bound.to_date
              @chart_data[current] = { name: current, values: 0 }
              @chart_data[current][:values2] = 0 if secondary_sum_col
              current = query.beginning_of_period_zoom( next_period( query, current ) ).to_time
            end
            @chart_data[key] = backup[key]
            current = query.beginning_of_period_zoom( next_period( query, key ) ).to_time
          end
          beginning_of_period_zoom = query.beginning_of_period_zoom( query.period_end_date ).to_date
          while current.to_date <= beginning_of_period_zoom
            @chart_data[current] = { name: current, values: 0 }
            @chart_data[current][:values2] = 0 if secondary_sum_col
            current = query.beginning_of_period_zoom( next_period(query, current) ).to_time
          end
        end

        if chart_settings['primary_renderer'] == 'bar' && chart_settings['bar_direction'] == 'horizontal'
          @xaxis_label = @yaxis_label
          @yaxis_label = nil
        end

        if chart_settings['additional_queries'].is_a?(Hash)
          chart_settings['additional_queries'].each do |key, query_settings|
            next unless query_settings['easy_query_type'].present?
            q = query_settings['easy_query_type'].constantize.new rescue nil
            next unless q
            q.from_params(query_settings)
            q.project = query.project
            q.output = 'chart'
            q.period_settings = query.period_settings
            other_chart_output = RedmineExtensions::BasePresenter.present(q, h, query.options).outputs.first
            if other_chart_output
              other_chart_output.before_render
              add_data = other_chart_output.chart_data
              q_key = 'additional_'+key.to_s
              add_data.each do |key, val|
                @chart_data[key] ||= { name: val[:name] }
                @chart_data[key][q_key] = val[:values]
                @chart_data[key][q_key+'2'] = val[:values2] if val.key?(:values2)
              end
              @data_names[q_key] = other_chart_output.data_names[:values]
              @data_names[q_key+'2'] = other_chart_output.data_names[:values2] if other_chart_output.data_names.key?(:values2)
              other_chart_output.after_render
            end
          end
        end

        @chart_data
      end

      def should_sort_bars_by_value?(query)
        chart_settings['primary_renderer'] == 'bar' && !query.chart_grouped_by_date_column? && !query.sort_criteria_order_for(chart_settings['axis_x_column'])
      end

      def render_json_data(api)
        if chart_data
          sorted_data = chart_data.values
          sorted_data = sorted_data.sort_by{|d| -(d[:values].to_f) } if should_sort_bars_by_value?(query)
          unless bar? && chart_settings['bar_limit'].blank? || chart_settings['bar_limit'].to_i == 0
            sorted_data = sorted_data.first(chart_settings['bar_limit'].to_i)
          end
          global_sum = Hash.new(0.0)
          sorted_data.each do |datum|
            data_names.keys.each{|k| global_sum[k] += datum[k].to_f }
            datum[:name] = h.format_groupby_entity_attribute(query.entity, Array(query.group_by_column), datum[:name], entity: datum.delete(:entity), period: query.group_by_period, no_html: true).to_s
          end

          ticks = sorted_data.map{|d| d[:name]}
          api.ticks ticks
          api.data do
            if pie?
              unique_names!(ticks)

              api.array :columns do
                sorted_data.each_with_index do |d, i|
                  api.value [ticks[i], d[:values].to_f]
                end
              end
            else
              api.json sorted_data
              # api.array(:groups) do
              #   api.value ['values']
              # end
              api.keys do
                #api.x 'name' unless pie?
                api.value data_names.keys
              end
            end
            api.names data_names
            api.type chart_settings['primary_renderer']
          end
          api.total global_sum.tap{|gs| gs.keys.each{|k| gs[k] = gs[k].to_i } } if query.show_sum_row?
        end

        api.chart_options do

          if chart_settings['primary_renderer'] == 'pie'
            insets = {'nw' => 'top-left', 'ne' => 'top-right'}
            locations = {'s' => 'bottom', 'e' => 'right'}
            location = chart_settings['legend']['location']
            api.legend do
              if chart_settings['legend_enabled'] == '1'
                api.show true
                api.position insets.keys.include?( location ) ? 'inset' :  locations[location]
                api.inset do
                  api.anchor insets[ location ]
                end
              else
                api.hide true
              end
            end
          else
            api.axis do
              api.rotated chart_settings['primary_renderer'] == 'bar' && chart_settings['bar_direction'] == 'horizontal'
              api.x do
                api.label @xaxis_label
              end
              api.y do
                api.label @yaxis_label

                api.padding do
                  api.bottom 0
                end
              end
            end
          end

          # api.highlighter({'show' => true, 'tooltipAxes' => 'both', 'useAxesFormatters' => false, 'formatString' => '%s: %s', 'tooltipFormatString' => '%s: %s'})
        end
        if sum_col && sum_col.is_a?(EasyQueryCurrencyColumn) && query.easy_currency_code
          api.formats do
            api.delimiter h.t('number.currency.format.delimiter').presence || ' '
            api.separator h.t('number.currency.format.separator')
            api.y_axis 'currency'
            api.labels 'currency'
          end
          api.array :currency do
            api.prefix ''
            api.suffix EasyCurrency.get_symbol(query.easy_currency_code)
          end
        else
          api.formats do
            api.delimiter h.t('number.format.delimiter').presence || ' '
            api.separator h.t('number.format.separator')
          end
        end
      end


      # render helpers
      def has_period_filter?
        !period_column.nil?
      end

      def period_column
        return if query.chart_settings['period_column'].blank?
        @period_column ||= query.available_columns.select{|col| col.name == query.chart_settings['period_column'] }
      end

      def next_period(query, previous)
        previous = previous.to_date
        case query.period_zoom.to_s
          when 'day'
            previous.next_day
          when 'week'
            previous.next_week
          when 'month'
            previous.next_month.beginning_of_month
          when 'quarter'
            previous.next_quarter.beginning_of_month
          when 'year'
            previous.next_year.beginning_of_year
        end
      end

      def unique_names!(ticks)
        ticks.find_all {|e| ticks.rindex(e) != ticks.index(e) }.uniq.each do |duplicity|
          i = 0
          ticks.map! do |tick|
            if duplicity == tick
              s = "#{tick}#{' ' * i}"
              i += 1
              s
            else
              tick
            end
          end
        end
      end

    end
  end
end
