#!/usr/bin/env ruby

RESULT_ROOT = ENV['RESULT_ROOT']
LKP_SRC = ENV['LKP_SRC'] || File.dirname(File.dirname(File.realpath($PROGRAM_NAME)))

require "#{LKP_SRC}/lib/statistics"
require "#{LKP_SRC}/lib/bounds"
require "#{LKP_SRC}/lib/yaml"
require "#{LKP_SRC}/lib/job"
require "#{LKP_SRC}/lib/string_ext"
require "#{LKP_SRC}/lib/log"
require 'set'

monitor = ARGV[0]

def warn_stat(msg, monitor, k, v)
  log_warn event: msg, detail: "#{RESULT_ROOT}/#{monitor}: #{k}:#{v}"
end

result = {}
invalid_records = []
record_index = 0
while (line = STDIN.gets)
  line = line.resolve_invalid_bytes
  next if line[0] == '#'

  k, _, v = line.partition(': ')
  next if v.empty?

  # Line terminator is expected. If not, throw out an error warning.
  warn_stat 'no line terminator in stats value', monitor, k, v if v.chomp!.nil?

  v.strip!
  if v.empty?
    warn_stat 'empty stats value', monitor, k, v
    next
  end

  next if monitor =~ /^(dmesg|kmsg)$/ && k =~ /^(message|pattern):/

  k = monitor + '.' + k
  result[k] ||= []

  # assemble value which key end with .element
  if k.end_with?('.element')
    result[k] << v
    next
  end

  size = result[k].size
  if record_index < size
    record_index = size
  elsif record_index - size > 0
    # fill 0 for missing values
    result[k].concat([0] * (record_index - size))
  end

  if k =~ /[ \t]/
    invalid_records.push record_index
    warn_stat 'whitespace in stats name', monitor, k, v

    exit 1
  end

  # keep message | log line which key end with .message|.log
  if k.end_with?('.message', '.log')
    result[k] = String.class_eval(%Q("#{v}"))
    next
  end

  # only number is valid
  unless v.numeric?
    invalid_records.push record_index
    warn_stat 'invalid stats value', monitor, k, v

    exit 1
  end

  v = v.index('.') ? v.to_f : v.to_i
  unless valid_stats_range? k, v
    invalid_records.push record_index
    warn_stat 'outside valid range', monitor, k, v
  end

  result[k].push v
end

exit if result.empty?

max_cols = 0
min_cols = Float::INFINITY
min_cols_stat = ''
max_cols_stat = ''
zero_stats = []
result.each do |key, val|
  next if val.is_a?(String)
  next if val[0].is_a?(String)

  if max_cols < val.size
    max_cols = val.size
    max_cols_stat = key
  end
  if min_cols > val.size
    min_cols = val.size
    min_cols_stat = key
  end
  next if val[0] != 0
  next if val[-1] != 0
  next if val.sum != 0

  zero_stats << key
end
zero_stats.each { |x| result.delete x }

if monitor != 'ftrace'
  # delete invalid number in reverse order
  invalid_records.reverse_each do |index|
    result.each do |_k, value|
      value.delete_at index
    end
  end
end

UNSTRUCTURED_MONITORS = %w(ftrace).to_set

if min_cols < max_cols && !UNSTRUCTURED_MONITORS.include?(monitor)
  if min_cols == max_cols - 1
    result.each { |_k, y| y.pop if y.size == max_cols }
    puts "Last record seems incomplete. Truncated #{RESULT_ROOT}/#{monitor}.json"
  else
    warn_stat 'not a matrix with different value size', monitor, "#{min_cols_stat} [#{min_cols}]", "#{max_cols_stat} [#{max_cols}]"
  end
end

exit if result.empty?
exit if result.values[0].size.zero?
exit if result.values[-1].size.zero?
save_json(result, "#{RESULT_ROOT}/#{monitor}.json", result.size * min_cols > 1000)
