require 'delegate'

module Rubyxls
  class Workbook < SimpleDelegator

    attr_reader :sheets

    def initialize(**opts)
      @package = Axlsx::Package.new
      @package.use_shared_strings = true
      @name = opts.fetch(:name, "default_workbook")
      @style_codes = {}
      @sheets = []

      super(@package.workbook)
      build_sheets!
      add_sheets!
    end

    def to_stream
      @package.to_stream
    end

    def filename
      "#{@name}.xlsx"
    end

    private

    def build_sheets!
      @sheets << Rubyxls::Sheet.new
    end

    def add_sheets!
      @sheets.each do |sheet|
        @data_validations = {}
        add_sheet(sheet.build_options(*workbook_sheet_names))
        add_rows(sheet.build_rows)
        add_data_validations
        add_charts(sheet.build_charts)
        add_width_normalization(sheet.build_width_normalization)
      end
    end

    def add_sheet(options)
      add_worksheet(options) do |sheet|
        sheet.sheet_view.show_grid_lines = false
        sheet.page_setup.fit_to(width: 1, height: 999)
        @sheet = sheet
      end
    end

    def add_rows(rows)
      rows.each do |row|
        values = []
        widths = []
        types = []
        styles = []
        height = nil
        row.each do |cell|
          values << cell[:value]
          widths << cell[:width]
          types << cell[:type]
          styles << retrieve_style_code(cell[:style])
          height = cell[:height] if cell[:height]
          merge_cell(cell) if cell[:merge]
          @data_validations["#{cell[:column]}#{cell[:row]}"] = cell[:data_validation] if cell[:data_validation]
        end
        @sheet.add_row(values, { types: types, style: styles, widths: widths, height: height })
      end
    end

    def retrieve_style_code(style_attributes)
      hash_attributes = style_attributes.clone
      @style_codes[hash_attributes] ||= styles.add_style(style_attributes)
    end

    def merge_cell(cell)
      case cell[:merge]
      when :right
        @sheet.merge_cells("#{cell[:column]}#{cell[:row]}:#{cell[:column].next}#{cell[:row]}")
      when :down
        @sheet.merge_cells("#{cell[:column]}#{cell[:row]}:#{cell[:column]}#{cell[:row] + 1}")
      end
    end

    def add_data_validations
      @data_validations.each do |data_validation_cell, data_validation_values|
        start_column = "A"
        end_column = start_column.clone
        (data_validation_values.size - 1).times { end_column.next! }
        @sheet.add_row(data_validation_values, { style: Array.new(data_validation_values.size, retrieve_style_code({ fg_color: "FFFFFF", bg_color: "FFFFFF" })) })
        @sheet.add_data_validation(data_validation_cell, { type: :list, formula1: "$#{start_column}$#{@sheet.rows.count}:$#{end_column}$#{@sheet.rows.count}", showDropdown: false })
      end
    end

    def add_charts(charts)
      charts.each do |chart|
        @sheet.add_chart(chart.type, start_at: chart.start_at, end_at: chart.end_at, rotX: chart.rot_x, rotY: chart.rot_y) do |added_chart|
          added_chart.title = chart.title
          added_chart.bar_dir = chart.bar_dir if added_chart.respond_to?(:bar_dir)
          added_chart.show_legend = chart.show_legend
          added_chart.catAxis.title = chart.cat_axis_title if added_chart.respond_to?(:catAxis)
          added_chart.valAxis.title = chart.val_axis_title if added_chart.respond_to?(:valAxis)
          added_chart.catAxis.gridlines = chart.cat_axis_gridlines if added_chart.respond_to?(:catAxis)
          added_chart.valAxis.gridlines = chart.val_axis_gridlines if added_chart.respond_to?(:valAxis)
          chart.series.each do |series|
            added_chart.add_series(data: @sheet[series[:data]], labels: @sheet[series[:labels]], colors: series[:colors])
          end
        end
      end
    end

    def add_width_normalization(custom_widths: [], default_width: nil)
      if @sheet.rows.size > 0
        row_values = Array.new(@sheet.cols.count, nil)
        row_widths = Array.new(@sheet.cols.count, default_width)
        custom_widths.reverse.each do |custom_width|
          row_widths.unshift(custom_width)
          row_widths.pop
        end
        @sheet.add_row(row_values, { widths: row_widths })
      end
    end

    def workbook_sheet_names
      @package.workbook.worksheets.map(&:name)
    end

  end
end