// A portable stand-in for the shell tools tests use, so the same test
// documents run on every platform. Commands can be chained with `+`:
//
//   scrut-testbin print 'a\n' + stderr 'b\n' + exit 3
//
// - `echo WORD...`: the words and a line feed
// - `print TEXT`: TEXT with `\n`, `\t`, `\r`, `\\` and `\xNN` escapes
// - `stderr TEXT`: the same on standard error
// - `cat`: standard input to standard output
// - `env NAME...`: the variables' values, one per line (empty if unset)
// - `pwd`: the current directory
// - `ls [DIR]`: directory entries, sorted, one per line
// - `sleep MS`: wait
// - `exit CODE`: stop with the exit code

///|
fn unescape(text : String) -> Bytes {
  let out = Buffer()
  let chars = text.to_array()
  let mut i = 0
  let hex = (c : Char) => {
    match c {
      '0'..<='9' => c.to_int() - '0'.to_int()
      'a'..<='f' => c.to_int() - 'a'.to_int() + 10
      'A'..<='F' => c.to_int() - 'A'.to_int() + 10
      _ => -1
    }
  }
  while i < chars.length() {
    let c = chars[i]
    if c == '\\' && i + 1 < chars.length() {
      match chars[i + 1] {
        'n' => {
          out.write_byte(b'\n')
          i += 2
          continue
        }
        't' => {
          out.write_byte(b'\t')
          i += 2
          continue
        }
        'r' => {
          out.write_byte(b'\r')
          i += 2
          continue
        }
        '\\' => {
          out.write_byte(b'\\')
          i += 2
          continue
        }
        'x' if i + 3 < chars.length() &&
          hex(chars[i + 2]) >= 0 &&
          hex(chars[i + 3]) >= 0 => {
          out.write_byte((hex(chars[i + 2]) * 16 + hex(chars[i + 3])).to_byte())
          i += 4
          continue
        }
        _ => ()
      }
    }
    out.write_string_utf8(c.to_string())
    i += 1
  }
  out.to_bytes()
}

///|
async fn run(command : Array[String]) -> Int32? {
  match command {
    [] => None
    ["echo", .. words] => {
      @stdio.stdout.write(words.join(" ") + "\n")
      None
    }
    ["print", text] => {
      @stdio.stdout.write(unescape(text))
      None
    }
    ["stderr", text] => {
      @stdio.stderr.write(unescape(text))
      None
    }
    ["cat"] => {
      @stdio.stdout.write(@stdio.stdin.read_all())
      None
    }
    ["env", .. names] => {
      let env = @env.get_env_vars()
      for name in names {
        @stdio.stdout.write(env.get(name).unwrap_or("") + "\n")
      }
      None
    }
    ["pwd"] => {
      @stdio.stdout.write(@fs.realpath(".") + "\n")
      None
    }
    ["ls", .. dir] => {
      let dir = dir.get(0).unwrap_or(".")
      for entry in @fs.readdir(dir, include_hidden=true, sort=true) {
        @stdio.stdout.write(entry + "\n")
      }
      None
    }
    ["sleep", ms] => {
      @async.sleep(@string.parse_int(ms))
      None
    }
    ["exit", code] => Some(@string.parse_int(code))
    _ => {
      @stdio.stderr.write(
        "scrut-testbin: unknown command: \{command.join(" ")}\n",
      )
      Some(2)
    }
  }
}

///|
async fn main {
  let args = @env.args()[1:].to_owned()
  let mut start = 0
  for i in 0..<=args.length() {
    if i == args.length() || args[i] == "+" {
      if run(args[start:i].to_owned()) is Some(code) {
        @sys.exit(code)
      }
      start = i + 1
    }
  }
}