#
# Author:: Kurt Yoder (ktyopscode@yoderhome.com)
# Copyright:: Copyright (c) 2010 Kurt Yoder
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
# 
#     http://www.apache.org/licenses/LICENSE-2.0
# 
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#

Ohai.plugin(:DMI) do
  provides "dmi"

  # dmidecode does not return data without access to /dev/mem (or its equivalent)

  collect_data do
    require 'ohai/common/dmi'
    dmi Mash.new

    # all output lines should fall within one of these patterns
    handle_line = /^Handle (0x[0-9A-F]{4}), DMI type (\d+), (\d+) bytes/
    type_line = /^([A-Z][a-zA-Z ]+)( Information)?/
    blank_line = /^\s*$/
    data_line = /^\t([^:]+):(?: (.*))?/
    extended_data_line = /^\t\t(.+)/
    # first lines may contain some interesting information:
    # # dmidecode 2.10
    # SMBIOS 2.5 present.
    # 5 structures occupying 352 bytes.
    # Table at 0x000E1000.
    dmidecode_version_line = /^# dmidecode (\d+\.\d+)/
    smbios_version_line = /^SMBIOS (\d+\.\d+) present\./
    structures_line = /^(\d+) structures occupying (\d+) bytes\./
    table_location_line = /^Table at (0x[0-9A-E]+)\./

    dmi_record = nil
    field = nil

    so = shell_out("dmidecode")
    # ==== EXAMPLE RECORD: ====
    #Handle 0x0000, DMI type 0, 24 bytes
    #BIOS Information
    #        Vendor: American Megatrends Inc.
    #        Version: 080012 
    # ... similar lines trimmed
    #        Characteristics:
    #                ISA is supported
    #                PCI is supported
    # ... similar lines trimmed
    so.stdout.lines do |line|
      next if blank_line.match(line)
      line = line.encode(line.encoding, :universal_newline => true)

      if dmidecode_version = dmidecode_version_line.match(line)
        dmi[:dmidecode_version] = dmidecode_version[1]

      elsif smbios_version = smbios_version_line.match(line)
        dmi[:smbios_version] = smbios_version[1]

      elsif structures = structures_line.match(line)
        dmi[:structures] = Mash.new
        dmi[:structures][:count] = structures[1]
        dmi[:structures][:size] = structures[2]

      elsif table_location = table_location_line.match(line)
        dmi[:table_location] = table_location[1]

      elsif handle = handle_line.match(line)
        # Don't overcapture for now (OHAI-260)
        unless Ohai::Common::DMI::IdToCapture.include?(handle[2].to_i)
          dmi_record = nil
          next
        end

        dmi_record = {:type => Ohai::Common::DMI.id_lookup(handle[2])}

        dmi[dmi_record[:type]] = Mash.new unless dmi.has_key?(dmi_record[:type])
        dmi[dmi_record[:type]][:all_records] = [] unless dmi[dmi_record[:type]].has_key?(:all_records)
        dmi_record[:position] = dmi[dmi_record[:type]][:all_records].length
        dmi[dmi_record[:type]][:all_records].push(Mash.new)
        dmi[dmi_record[:type]][:all_records][dmi_record[:position]][:record_id] = handle[1]
        dmi[dmi_record[:type]][:all_records][dmi_record[:position]][:size] = handle[2]
        field = nil

      elsif type = type_line.match(line)
        if dmi_record == nil
          Ohai::Log.debug("unexpected data line found before header; discarding:\n#{line}")
          next
        end
        dmi[dmi_record[:type]][:all_records][dmi_record[:position]][:application_identifier] = type[1]

      elsif data = data_line.match(line)
        if dmi_record == nil
          Ohai::Log.debug("unexpected data line found before header; discarding:\n#{line}")
          next
        end
        dmi[dmi_record[:type]][:all_records][dmi_record[:position]][data[1]] = data[2]
        field = data[1]

      elsif extended_data = extended_data_line.match(line)
        if dmi_record == nil
          Ohai::Log.debug("unexpected extended data line found before header; discarding:\n#{line}")
          next
        end
        if field == nil
          Ohai::Log.debug("unexpected extended data line found outside data section; discarding:\n#{line}")
          next
        end
        # overwrite "raw" value with a new Mash
        dmi[dmi_record[:type]][:all_records][dmi_record[:position]][field] = Mash.new unless dmi[dmi_record[:type]][:all_records][dmi_record[:position]][field].class.to_s == 'Mash'
        dmi[dmi_record[:type]][:all_records][dmi_record[:position]][field][extended_data[1]] = nil

      else
        Ohai::Log.debug("unrecognized output line; discarding:\n#{line}")

      end
    end

    Ohai::Common::DMI.convenience_keys(dmi)
  end
end
