require 'terminal-table'
require 'collins_shell/util/printer_util'

module CollinsShell

  class AssetPrinter

    include CollinsShell::PrinterUtil

    attr_accessor :asset, :detailed, :table, :col_size, :thor, :separator, :logs, :color

    def initialize asset, thor, options = {}
      @asset = asset
      @table = Terminal::Table.new
      @thor = thor
      @separator = options[:separator]
      @detailed = options.fetch(:detailed, true)
      @col_size = 6
      @color = options.fetch(:color, true)
      @logs = options[:logs]
    end

    def render
      table.title = header_text("Asset #{asset.tag}")
      collect_asset_basics
      collect_disk_data if detailed
      collect_ip_data if detailed
      collect_ipmi_data if detailed
      collect_nic_data if detailed
      collect_mem_data if detailed
      collect_power_data if detailed
      collect_xtra_data
      collect_log_data
      table_s = table.render
      if separator then
        width = (0...@col_size).inject(0) do |result, idx|
          result + table.column_width(idx)
        end
        sep_s = separator * (1.13 * width.to_f).to_i
        table_s + "\n#{sep_s}\n"
      else
        table_s
      end
    end
    alias :to_s :render

    def collect_asset_basics
      header = [:tag,:status,:type,:created,:updated,:location]
      table << get_row(header, true, header.length)
      table << :separator
      table << get_row(header.map{|s| asset.send(s)}, false, header.length)
      if asset.state && !asset.state.empty? then
        state = asset.state
        state_headers = [
          title_text("State Name"),
          title_text("State Label"),
          {:value => title_text("State Description"), :colspan => 4}
        ]
        table << :separator
        table << state_headers
        table << :separator
        table << [state.name, state.label, {:value => state.description, :colspan => 4}]
      end
      @col_size = table.number_of_columns
    end

    def collect_disk_data
      disk_header = ["SIZE_HUMAN", "SIZE", "TYPE", "DESCRIPTION"]
      row_title "Disks"
      table << get_row(disk_header, true)
      table << :separator
      asset.disks.each do |disk|
        table << get_row(disk_header.map{|s| disk[s]})
      end
    end

    def collect_log_data
      return if (not logs or logs.empty?)
      row_title "Log Entries"
      headers = [
        title_text("Created"),
        title_text("Severity"),
        title_text("Source"),
        title_text("Format"),
        {:value => title_text("Message"), :colspan => 2}
      ]
      table << headers
      table << :separator
      have_multi_row = false
      logs_formatted = logs.map do |log|
        rewritten_msg = log.MESSAGE.strip
        message = wrap_text(rewritten_msg).strip
        if message != rewritten_msg then
          have_multi_row = true
        end
        [
          format_datetime(log.CREATED), log.TYPE, log.SOURCE, log.FORMAT,
          {:value => message, :colspan => 2}
        ]
      end
      last_log_idx = logs.length - 1
      logs_formatted.each_with_index do |row, index|
        table << row
        # Don't print a separator if we don't have a multi-row message
        if index != last_log_idx && have_multi_row then
          table << :separator
        end
      end
    end

    def collect_ip_data
      address_header = [:address,:gateway,:netmask,:pool,:is_private?,:is_public?]
      row_title "IP Addresses"
      table << get_row(address_header, true)
      table << :separator
      asset.addresses.each{|a| table << get_row(address_header.map{|s| a.send(s)})}
    end

    def collect_ipmi_data
      ipmi_header = [:address,:gateway,:netmask,:username,:password]
      row_title "IPMI Information"
      table << get_row(ipmi_header, true)
      table << :separator
      table << get_row(ipmi_header.map {|i| asset.ipmi.send(i)})
    end

    def collect_nic_data
      nic_header = ["SPEED_HUMAN", "MAC_ADDRESS", "DESCRIPTION"]
      row_title "Network Interfaces"
      table << get_row(nic_header, true)
      table << :separator
      asset.nics.each {|nic| table << get_row(nic_header.map{|s| nic[s]})}
    end

    def collect_mem_data
      memory_header = ["BANK","SIZE","SIZE_HUMAN","DESCRIPTION"]
      row_title "Physical Memory"
      table << get_row(memory_header, true)
      table << :separator
      memory_total = 0
      asset.memory.each do |mem|
        memory_total += mem["SIZE"].to_i
        if mem["SIZE"].to_i != 0 then
          table << get_row(memory_header.map{|s| mem[s]})
        end
      end
      table << :separator
      table << [title_text("TOTAL SIZE (Bytes)"), {:value => memory_total.to_s, :colspan => 5}]
      table << [title_text("TOTAL SIZE (Human)"), {:value => memory_total.to_human_size, :colspan => 5}]
    end

    def collect_power_data
      power_header = [:type,:key,:value,:label]
      row_title "Power Information"
      table << get_row(power_header, true)
      table << :separator
      asset.power.each do |power|
        power.units.each do |unit|
          table << get_row(power_header.map {|key| unit.send(key)})
        end
      end
    end

    def format_text key, value
      key_s = key.to_s.downcase
      if key_s.start_with?("json_") or key_s.end_with?("_json") then
        begin
          JSON.parse(value, :symbolize_names => true).to_s
        rescue Exception => e
          value
        end
      else
        value
      end
    end

    def collect_xtra_data
      return unless (asset.extras and asset.extras["ATTRIBS"] && asset.extras["ATTRIBS"]["0"])
      row_title "Extra Attributes"
      attribs = []
      attribs_unsorted = asset.extras["ATTRIBS"]["0"]
      attribs_sorted = attribs_unsorted.keys.sort.inject({}) do |result,key|
        result.update(key => attribs_unsorted[key])
      end
      attribs_sorted.each do |key,value|
        attribs << title_text(key)
        attribs << format_text(key, value)
        if attribs.length == 6 then
          table << attribs
          attribs = []
        end
        if CollinsShell::Util::SIZABLE_ATTRIBUTES.include?(key.downcase.to_sym) then
          attribs << title_text("#{key} (Human)")
          attribs << value.to_f.to_human_size
        end
        if attribs.length == 6 then
          table << attribs
          attribs = []
        end
      end
      if attribs.length != 0 then
        until attribs.length == 6
          attribs << " "
        end
        table << attribs
      end
    end

    def header_text txt
      if @color then
        thor.set_color(txt, :bold, :magenta)
      else
        txt
      end
    end
    def title_text txt
      if @color then
        thor.set_color(txt, :bold, :white)
      else
        txt
      end
    end
    def regular_text txt
      txt
    end

    def get_row row_data, header = false, cols = 0
      num_cols = @col_size
      row_length = row_data.length
      last_index = row_length - 1
      average_size = (num_cols.to_f / row_length.to_f)
      est_size = (num_cols / row_length)
      est_is_actual = (average_size == est_size)
      used_cols = 0
      row = []
      row_data.each_with_index do |col,index|
        if est_is_actual then
          size_per_col = est_size
        elsif index == 0 then
          size_per_col = 1
        elsif average_size > (used_cols.to_f/index.to_f) then
          size_per_col = 2
        else
          size_per_col = 1
        end
        if col.is_a?(DateTime) then
          col = format_datetime(col)
        end
        used_cols += size_per_col
        alignment = if size_per_col > 1 then :center else :left end
        value = if header then title_text(col) else regular_text(col) end
        row << {:value => value, :alignment => alignment, :colspan => size_per_col}
      end
      row
    end

    def row_title txt
      table << :separator
      table << [{:value => header_text(txt), :alignment => :center, :colspan => @col_size}]
      table << :separator
    end

  end

end