require 'csv'

module DclgStatsSelector
  class CSVBuilder
    DEFAULT_HEADER_ROW_STRATEGIES = {
      dataset:          { display: :uri,   padding: :blank },
      measure_property: { display: :label, padding: :blank },
      dimension_value:  { display: :label, padding: :duplicate },
      blank:            { display: :none,  padding: :blank }
    }.freeze

    class << self
      # Helper method to make a new stats selector. Note the overloading
      # of the word "build" here - this method doesn't return a CSV!
      def build(metadata)
        new(
          metadata.merge(header_row_strategies: DEFAULT_HEADER_ROW_STRATEGIES)
        )
      end
    end

    def initialize(metadata_and_opts)
      @source_url = metadata_and_opts.fetch(:source_url)
      @timestamp  = metadata_and_opts.fetch(:timestamp)
      @site_name  = metadata_and_opts.fetch(:site_name)

      @header_row_strategies = metadata_and_opts.fetch(:header_row_strategies)

      @csv = CSV.new(String.new)
    end

    def document_header_started
      @csv << ["Generated by #{@site_name}", @timestamp]
      @csv << ["Source:", @source_url]
    end

    def document_header_finished
      @csv << []
    end

    def header_row(row, has_secondary_row_headings=false)
      padding = has_secondary_row_headings ? ['','','',''] : ['','']
      @csv << padding + row.inject([]) { |csv_row, cell|
        csv_row.concat(text_cells(cell))
      }
    end

    def table_row(row_description)
      row_values = [
        row_description.fetch(:row_uri),
        row_description.fetch(:row_label),
        *row_description.fetch(:values)
      ]
      if row_description[:secondary_row_uri]
        row_values = [row_description.fetch(:secondary_row_uri), row_description.fetch(:secondary_row_label)] + row_values
      end
      @csv << row_values
    end

    def to_csv
      @csv.string
    end

    private

    def text_cells(cell)
      cell_type_options = @header_row_strategies[cell.type] or
        raise ArgumentError.new("No header row strategy for #{cell.type.inspect}")
      cell_display_type = cell_type_options[:display] or
        raise ArgumentError.new("No :display option given for #{cell.type.inspect}")
      cell_padding_strategy = cell_type_options[:padding] or
        raise ArgumentError.new("No :padding option given for #{cell.type.inspect}")

      cell_text =
        case cell_display_type
        when :uri
          cell.uri
        when :label
          cell.label
        when :none
          ""
        else
          raise ArgumentError.new("Unknown cell display type #{cell_display_type.inspect}")
        end

      cells = []
      cells << cell_text

      padding_cells =
        case cell_padding_strategy
        when :blank
          [""] * (cell.width - 1)
        when :duplicate
          [cell_text] * (cell.width - 1)
        else
          raise ArgumentError.new("Unknown cell padding strategy #{cell_padding_strategy.inspect}")
        end

      cells.concat(padding_cells)
    end
  end
end