module ManageIQ
  module Reporting
    module Formatter
      class Text < Ruport::Formatter::Text
        renders :text, :for => ReportRenderer

        # determines the text widths for each column.
        def calculate_max_col_widths
          mri = options.mri
          tz = mri.get_time_zone(Time.zone.name)

          @max_col_width = []
          unless mri.headers.empty?
            mri.headers.each_index do |i|
              @max_col_width[i] = mri.headers[i].to_s.length
            end
          end
          mri.table.data.each do |r|
            mri.col_formats ||= []                 # Backward compat - create empty array for formats
            mri.col_order.each_with_index do |f, i|
              unless ["<compare>", "<drift>"].include?(mri.db)
                data = mri.format(f,
                                  r[f],
                                  :format => mri.col_formats[i] || :_default_,
                                  :tz     => tz)
              else
                data = r[f].to_s
              end
              if !@max_col_width[i] || data.length > @max_col_width[i]
                @max_col_width[i] = data.length
              end
            end
          end
        end

        # method to get friendly values for company tag and user filters
        def calculate_filter_names(tag)
          categories = Classification.categories.collect { |c| c if c.show }.compact
          tag_val = ""
          categories.each do |category|
            entries = {}
            category.entries.each do |entry|
              entries[entry.description] = entry.tag.name # Get the fully qual tag name
              if tag == entry.tag.name
                tag_val = "#{category.description}: #{entry.description}"
              end
            end
          end
          tag_val
        end

        # Uses the column names from the given Data::Table to generate a table
        # header.
        #
        # calls fit_to_width to truncate table heading if necessary.
        def build_document_header
          mri = options.mri
          raise "No settings configured for Table" if mri.table.nil?

          calculate_max_col_widths
          @hr = hr

          unless mri.title.nil? # generate title line, if present
            output << fit_to_width(@hr)

            temp_title = mri.title
            temp_title << " (" << mri.report_run_time.to_s << ")" unless mri.report_run_time.nil?
            t = temp_title.center(@line_len - 2)
            output << fit_to_width("|#{t}|" + CRLF)
            if !mri.db.nil? && mri.db == "<drift>"
              t2 = "(* = Value changed from previous column)"
              t2 = t2.center(@line_len - 2)
              output << fit_to_width("|#{t2}|" + CRLF)
            end
          end

          return if mri.headers.empty?

          c = mri.headers.dup
          # Remove headers of hidden columns
          mri.col_order.each_with_index do |f, i|
            c.delete_at(i) if mri.column_is_hidden?(f)
          end
          c.each_with_index do |f, i|
            c[i] = f.to_s.center(@max_col_width[i])
          end
          output << fit_to_width("#{@hr}| #{Array(c).join(' | ')} |" + CRLF)
        end

        # Generates the body of the text table.
        #
        # Defaults to numeric values being right justified, and other values being
        # left justified.  Can be changed to support centering of output by
        # setting alignment to :center
        #
        # Uses fit_to_width to truncate table if necessary
        def build_document_body
          mri = options.mri
          tz = mri.get_time_zone(Time.zone.name)
          s = @hr

          save_val = nil
          counter = 0

          row_limit = mri.rpt_options && mri.rpt_options[:row_limit] ? mri.rpt_options[:row_limit] : 0
          use_table = mri.sub_table || mri.table
          use_table.data.each_with_index do |r, d_idx|
            break if row_limit != 0 && d_idx > row_limit - 1

            line = []
            line_wrapper = false        # Clear line wrapper flag
            if ["<compare>"].include?(mri.db) && r[0] == "% Match:"
              line_wrapper = true       # Wrap compare % lines with header rows
            elsif ["<drift>"].include?(mri.db) && r[0] == "Changed:"
              line_wrapper = true       # Wrap drift changed lines with header rows
            end
            mri.col_formats ||= []                 # Backward compat - create empty array for formats
            mri.col_order.each_with_index do |f, i|
              next if mri.column_is_hidden?(f)

              unless ["<compare>", "<drift>"].include?(mri.db)
                data = mri.format(f,
                                  r[f],
                                  :format => mri.col_formats[i] || :_default_,
                                  :tz     => tz)
              else
                data = r[f].to_s
              end
              if options.alignment.eql? :center
                line << data.center(@max_col_width[i])
              else
                align = data.kind_of?(Numeric) ? :rjust : :ljust
                line << data.send(align, @max_col_width[i])
              end
            end

            # generate a break line if grouping is turned on
            if ["y", "c"].include?(mri.group) && !mri.sortby.nil?
              if d_idx > 0 && save_val != r[mri.sortby[0]]
                if mri.group == "c"
                  s += @hr
                  t = " Total for #{save_val}: #{counter} ".center(@line_len - 2)
                  s += fit_to_width("|#{t}|" + CRLF)
                  s += @hr
                  counter = 0
                else
                  s += @hr
                end
              end
              save_val = r[mri.sortby[0]]
              counter += 1
            end
            s += @hr if line_wrapper
            s += "| #{line.join(' | ')} |" + CRLF
            s += @hr if line_wrapper
          end

          # see if a final group line needs to be written
          if ["y", "c"].include?(mri.group) && !mri.sortby.nil?
            if mri.group == "c"
              s += @hr
              t = " Total for #{save_val}: #{counter} ".center(@line_len - 2)
              s += fit_to_width("|#{t}|" + CRLF)
            end
          end

          s += @hr
          output << fit_to_width(s)
        end

        def build_document_footer
          mri = options.mri
          tz = mri.get_time_zone(Time.zone.name)
          if mri.user_categories.present? || mri.categories.present? || !mri.conditions.nil? || !mri.display_filter.nil?
            output << fit_to_width(@hr)
            if mri.user_categories.present?
              user_filters = mri.user_categories.flatten
              if user_filters.present?
                customer_name = Tenant.root_tenant.name
                user_filter = "User assigned " + customer_name + " Tag filters:"
                t = user_filter.ljust(@line_len - 2)
                output << fit_to_width("|#{t}|" + CRLF)
                user_filters.each do |filters|
                  tag_val = "  " + calculate_filter_names(filters)
                  tag_val1 = tag_val + " " * (@line_len - tag_val.length - 2)
                  output << fit_to_width("|#{tag_val1}|" + CRLF)
                end
              end
            end

            if mri.categories.present?
              categories = mri.categories.flatten
              if categories.present?
                customer_name = Tenant.root_tenant.name
                customer_name_title = "Report based " + customer_name + " Tag filters:"
                t = customer_name_title + " " * (@line_len - customer_name_title.length - 2)
                output << fit_to_width("|#{t}|" + CRLF)
                categories.each do |filters|
                  tag_val = "  " + calculate_filter_names(filters)
                  tag_val1 = tag_val + " " * (@line_len - tag_val.length - 2)
                  output << fit_to_width("|#{tag_val1}|" + CRLF)
                end
              end
            end

            unless mri.conditions.nil?
              if mri.conditions.kind_of?(Hash)
                filter_fields = "Report based filter fields:"
                t = filter_fields.ljust(@line_len - 2)
                output << fit_to_width("|#{t}|" + CRLF)

                # Clean up the conditions for display
                tables = mri.conditions[:field].split("-")[0].split(".")  # Get the model and tables
                field = Dictionary.gettext(tables[0], :type => :model, :notfound => :titleize) # Start with the model
                tables[1..-1].each do |t| # Add on any tables
                  field += "." + Dictionary.gettext(t, :type => :table, :notfound => :titleize)
                end
                # Add on the column name
                field += " : " + Dictionary.gettext(mri.conditions[:field].split("-")[1], :type => :column, :notfound => :titleize)

                filter_val = "  " + field + " " + mri.conditions[:operator] + " " + mri.conditions[:string].to_s
                t = filter_val + " " * (@line_len - filter_val.length - 2)
                output << fit_to_width("|#{t}|" + CRLF)
              else
                filter_fields = "Report based filter fields:"
                t = filter_fields.ljust(@line_len - 2)
                output << fit_to_width("|#{t}|" + CRLF)
                filter_val = mri.conditions.to_human
                t = filter_val.ljust(@line_len - 2)
                output << fit_to_width("|#{t}|" + CRLF)
              end
            end

            unless mri.display_filter.nil?
              filter_fields = "Display Filter:"
              t = filter_fields.ljust(@line_len - 2)
              output << fit_to_width("|#{t}|" + CRLF)
              filter_val = mri.display_filter.to_human
              t = filter_val + " " * (@line_len - filter_val.length - 2)
              output << fit_to_width("|#{t}|" + CRLF)
            end
          end

          output << fit_to_width(@hr)
          # Label footer with last run on time of selected report or current time for other downloads
          last_run_on = mri.rpt_options && mri.rpt_options[:last_run_on] || Time.zone.now
          cr = format_timezone(last_run_on, tz).to_s
          f = cr.center(@line_len - 2)
          output << fit_to_width("|#{f}|" + CRLF)
          output << fit_to_width(@hr)
        end

        # Generates the horizontal rule by calculating the total table width and
        # then generating a bar that looks like this:
        #
        #   "+------------------+"
        def hr
          columns = options.mri.table.column_names
          if columns.include?("id") # Use 1 less column if "id" is present
            @line_len = @max_col_width.inject((columns.length - 1) * 3) { |s, e| s + e } + 1
          else
            @line_len = @max_col_width.inject(columns.length * 3) { |s, e| s + e }
          end
          "+" + "-" * (@line_len - 2) + "+" + CRLF
        end
      end
    end
  end
end
