# HADES-SA: 40-byte HEX TXT -> readable report + 37-byte decoder candidates
# Usage: ruby hades_hex_to_candidates_v2.rb CHECK26_D_11frames_40bytes_hex.txt
# Whitespace (spaces, tabs, CR, LF) is ignored. Each frame must be 40 bytes.
# The input file is never modified.

FRAME_SIZE = 40
EXPECTED_LENGTH = 0x25
TYPE_11 = 0xB3
SYNC = [0xBF, 0x35]

def spaced_hex(bytes)
  bytes.map { |byte| format("%02X", byte) }.join(" ")
end

def classify_frame(inverted)
  sync = inverted[0, 2]
  length = inverted[2]
  packet_type = inverted[3]

  if sync != SYNC
    ["Header differs", nil]
  elsif packet_type == TYPE_11 && length == EXPECTED_LENGTH
    ["Type 11 header OK", inverted[4]]
  elsif packet_type == TYPE_11
    ["Type 11 / size mismatch", inverted[4]]
  elsif length == EXPECTED_LENGTH
    ["Other packet type", inverted[4]]
  else
    ["Header differs (length/type differ)", inverted[4]]
  end
end

def main
  input_path = ARGV[0]
  unless input_path
    warn "Usage: ruby hades_hex_to_candidates_v2.rb input_40bytes_hex.txt"
    exit 1
  end
  unless File.file?(input_path)
    warn "ERROR: Input file not found: #{input_path}"
    exit 1
  end

  begin
    text = File.read(input_path, encoding: "UTF-8")
  rescue => e
    warn "ERROR: Cannot read input file: #{e.message}"
    exit 1
  end

  hex_text = text.gsub(/\s+/, "")
  unless hex_text.match?(/\A[0-9A-Fa-f]*\z/)
    warn "ERROR: Input contains characters other than HEX digits and whitespace."
    exit 1
  end
  if hex_text.empty?
    warn "ERROR: Input file contains no HEX data."
    exit 1
  end
  if hex_text.length.odd?
    warn "ERROR: Number of HEX characters is odd (#{hex_text.length})."
    exit 1
  end

  raw_data = [hex_text].pack("H*")
  unless (raw_data.bytesize % FRAME_SIZE).zero?
    warn "ERROR: Converted data is #{raw_data.bytesize} bytes, not a multiple of #{FRAME_SIZE}."
    exit 1
  end

  frame_count = raw_data.bytesize / FRAME_SIZE
  input_basename = File.basename(input_path, File.extname(input_path))
  output_dir = "#{input_basename}_candidates"

  begin
    Dir.mkdir(output_dir) unless Dir.exist?(output_dir)
  rescue => e
    warn "ERROR: Cannot create output folder: #{e.message}"
    exit 1
  end

  report = []
  report << "HADES-SA 40-byte HEX TXT conversion report"
  report << "Input file: #{input_path}"
  report << "HEX characters (whitespace excluded): #{hex_text.length}"
  report << "Converted data: #{raw_data.bytesize} bytes"
  report << "Frame size: #{FRAME_SIZE} bytes"
  report << "Number of frames: #{frame_count}"
  report << ""
  report << "Processing: raw 40 bytes -> XOR 0xFF -> remove first 3 bytes -> 37-byte candidate"
  report << "Both binary (.bin) and readable HEX (.txt) candidates are saved."
  report << ""
  report << "Frame details"
  report << "============="

  frame_count.times do |index|
    raw_frame = raw_data.byteslice(index * FRAME_SIZE, FRAME_SIZE).bytes
    inverted = raw_frame.map { |byte| byte ^ 0xFF }
    candidate = inverted.drop(3)

    assessment, frame_number = classify_frame(inverted)
    frame_label = frame_number ? format("F%03d", frame_number) : "F_unknown"
    sequence_label = format("Frame%02d", index + 1)
    base_name = "#{sequence_label}_#{frame_label}_candidate_37bytes"
    bin_name = "#{base_name}.bin"
    hex_name = "#{base_name}_hex.txt"

    begin
      File.binwrite(File.join(output_dir, bin_name), candidate.pack("C*"))
      File.write(File.join(output_dir, hex_name), spaced_hex(candidate) + "\n",
                 mode: "w", encoding: "ASCII")
    rescue => e
      warn "ERROR: Cannot write output for #{sequence_label}: #{e.message}"
      exit 1
    end

    frame_number_text = frame_number ?
      format("%03d (0x%02X)", frame_number, frame_number) : "unknown"

    report << ""
    report << "------------------------------------------------------------"
    report << "#{sequence_label} / Original frame number: #{frame_number_text}"
    report << "Assessment: #{assessment}"
    report << "Input raw 40 bytes:"
    report << spaced_hex(raw_frame)
    report << "Inverted 40 bytes (XOR 0xFF):"
    report << spaced_hex(inverted)
    report << "Candidate 37 bytes (first 3 bytes removed):"
    report << spaced_hex(candidate)
    report << "Binary candidate: #{bin_name}"
    report << "Readable HEX candidate: #{hex_name}"
  end

  summary_path = File.join(output_dir, "conversion_summary.txt")
  begin
    File.write(summary_path, report.join("\n") + "\n",
               mode: "w", encoding: "UTF-8")
  rescue => e
    warn "ERROR: Cannot write summary file: #{e.message}"
    exit 1
  end

  puts "Completed: #{frame_count} frame(s) processed."
  puts "Output folder: #{output_dir}"
  puts "Summary file: #{File.join(output_dir, 'conversion_summary.txt')}"
  puts "Created #{frame_count} binary 37-byte candidate file(s)."
  puts "Created #{frame_count} readable HEX 37-byte candidate file(s)."
  puts "The input TXT file was not changed."
end

main
