///|
fn print_help() -> Unit {
  let help =
    #|MiniVCD-MBT - lightweight VCD waveform viewer
    #|
    #|Usage:
    #|  minivcd info 
    #|  minivcd signals 
    #|  minivcd changes  
    #|  minivcd stats  
    #|  minivcd value   
    #|  minivcd export  
  println(help)
}

///|
fn read_vcd(path : String) -> @vcdlib.VcdFile raise @vcdlib.VcdError {
  let text = @fs.read_file_to_string(path) catch {
    @fs.IOError(message) =>
      raise @vcdlib.VcdError("cannot read '\{path}': \{message}")
  }
  @vcdlib.parse_vcd(text)
}

///|
fn show_time(value : Int64?) -> String {
  match value {
    Some(time) => time.to_string()
    None => "n/a"
  }
}

///|
fn run(args : Array[String]) -> Unit raise @vcdlib.VcdError {
  if args.length() < 2 {
    print_help()
    return
  }
  let command = args[1]
  match command {
    "info" => {
      if args.length() != 3 {
        raise @vcdlib.VcdError("usage: minivcd info ")
      }
      let vcd = read_vcd(args[2])
      let summary = @vcdlib.summary(vcd)
      println("VCD File")
      println("Timescale: \{vcd.timescale}")
      println("Signals: \{summary.signal_count}")
      println("Changes: \{summary.change_count}")
      println("Start time: \{show_time(summary.start_time)}")
      println("End time: \{show_time(summary.end_time)}")
    }
    "signals" => {
      if args.length() != 3 {
        raise @vcdlib.VcdError("usage: minivcd signals ")
      }
      let vcd = read_vcd(args[2])
      for signal in @vcdlib.list_signals(vcd) {
        println("\{signal.full_name}\t\{signal.signal_type}\t\{signal.width}")
      }
    }
    "changes" => {
      if args.length() != 4 {
        raise @vcdlib.VcdError(
          "usage: minivcd changes  ",
        )
      }
      let vcd = read_vcd(args[2])
      for change in @vcdlib.signal_changes(vcd, args[3]) {
        println("\{change.timestamp}\t\{change.value}")
      }
    }
    "stats" => {
      if args.length() != 4 {
        raise @vcdlib.VcdError(
          "usage: minivcd stats  ",
        )
      }
      let vcd = read_vcd(args[2])
      let signal = match @vcdlib.find_signal(vcd, args[3]) {
        Some(signal) => signal
        None => raise @vcdlib.VcdError("signal not found: \{args[3]}")
      }
      let stats = @vcdlib.signal_stats(vcd, args[3])
      println("Signal: \{signal.full_name}")
      println("Type: \{signal.signal_type}")
      println("Width: \{signal.width}")
      println("Changes: \{stats.change_count}")
      println("First change: \{show_time(stats.first_change_time)}")
      println("Last change: \{show_time(stats.last_change_time)}")
      if signal.width == 1 {
        println("Rising edges: \{stats.rising_edges}")
        println("Falling edges: \{stats.falling_edges}")
      }
    }
    "value" => {
      if args.length() != 5 {
        raise @vcdlib.VcdError(
          "usage: minivcd value   ",
        )
      }
      let timestamp = @string.parse_int64(args[4]) catch {
        _ => raise @vcdlib.VcdError("invalid timestamp '\{args[4]}'")
      }
      let vcd = read_vcd(args[2])
      match @vcdlib.value_at(vcd, args[3], timestamp) {
        Some(value) => println("\{args[3]} @ \{timestamp} = \{value}")
        None =>
          raise @vcdlib.VcdError(
            "no value for \{args[3]} at or before \{timestamp}",
          )
      }
    }
    "export" => {
      if args.length() != 4 {
        raise @vcdlib.VcdError("usage: minivcd export  ")
      }
      let vcd = read_vcd(args[2])
      let csv = @vcdlib.export_csv(vcd)
      @fs.write_string_to_file(args[3], csv) catch {
        @fs.IOError(message) =>
          raise @vcdlib.VcdError("cannot write '\{args[3]}': \{message}")
      }
      println("Exported \{vcd.changes.length()} changes to \{args[3]}")
    }
    "help" | "--help" | "-h" => print_help()
    _ => raise @vcdlib.VcdError("unknown command '\{command}'")
  }
}

///|
fn main {
  try run(@env.args()) catch {
    @vcdlib.VcdError(message) => println("Error: \{message}")
  } noraise {
    _ => ()
  }
}