require_relative './report/cell'
require_relative './report/row'

module XeroGateway
  class Report

    include Money
    include Dates

    attr_reader   :errors
    attr_accessor :report_id, :report_name, :report_type, :report_titles, :report_date, :updated_at,
                  :body, :column_names

    alias :rows :body

    def initialize(params={})
      @errors         ||= []
      @report_titles  ||= []
      @body           ||= []

      params.each do |k,v|
        self.send("#{k}=", v)
      end
    end

    class << self

      def from_xml(report_element)
        report = Report.new
        report_element.children.each do | element |
          case element.name
            when 'ReportID'         then report.report_id = element.text
            when 'ReportName'       then report.report_name = element.text
            when 'ReportType'       then report.report_type = element.text
            when 'ReportTitles'
              each_title(element) do |title|
                report.report_titles << title
              end
            when 'ReportDate'       then report.report_date = Date.parse(element.text)
            when 'UpdatedDateUTC'   then report.updated_at = parse_date_time_utc(element.text)
            when 'Rows'
              report.column_names ||= find_body_column_names(element)
              each_row_content(element) do |row|
                report.body << row
              end
          end
        end
        report
      end

      private

        def each_row_content(xml_element, &block)
          column_names    = find_body_column_names(xml_element).values
          report_sections = REXML::XPath.each(xml_element, "//RowType[text()='Section']/parent::Row")

          report_sections.each do |section_row|
            section_name = section_row.get_elements("Title").first.try(:text)
            section_row.elements.each("Rows/Row") do |xpath_cells|
              values = find_body_cell_values(xpath_cells)
              yield Row.new(column_names, values, section_name)
            end
          end
        end

        def each_title(xml_element, &block)
          xpath_titles = REXML::XPath.first(xml_element, "//ReportTitles")
          xpath_titles.elements.each("//ReportTitle") do |xpath_title|
            title = xpath_title.text.strip
            yield title if block_given?
          end
        end

        def find_body_cell_values(xml_cells)
          values = []
          xml_cells.elements.each("Cells/Cell") do |xml_cell|
            if value = xml_cell.children.first # finds <Value>...</Value>
              values << Cell.new(value.text.try(:strip), collect_attributes(xml_cell))
              next
            end
            values << nil
          end
          values
        end

        # Collects "<Attribute>" elements into a hash
        def collect_attributes(xml_cell)
          Array.wrap(xml_cell.elements["Attributes/Attribute"]).inject({}) do |hash, xml_attribute|
            if (key   = xml_attribute.elements["Id"].try(:text)) &&
              (value = xml_attribute.elements["Value"].try(:text))

              hash[key] = value
            end
            hash
          end.symbolize_keys
        end

        # returns something like { column_1: "Amount", column_2: "Description", ... }
        def find_body_column_names(body)
          header       = REXML::XPath.first(body, "//RowType[text()='Header']")
          names_map    = {}
          column_count = 0
          header.parent.elements.each("Cells/Cell") do |header_cell|
            column_count += 1
            column_key    = "column_#{column_count}".to_sym
            column_name   = nil
            name_value    = header_cell.children.first
            column_name   = name_value.text.strip unless name_value.blank? # finds <Value>...</Value>
            names_map[column_key] = column_name
          end
          names_map
        end
    end

  end
end