import { Subprocess, SpawnInfo, PipeID, ReadPipeRequest, WritePipeRequest } from "./lib/subprocess/index.js"

let M = new Subprocess()
let D = new TextDecoder();
let E = new TextEncoder();

class Pipe {
  constructor(pid, num) {
    let p = new PipeID();
    p.setPid(pid)
    p.setFd(num)
    this.pipe = p;
  }

  async close() {
    return M.ClosePipe(this.pipe)
  }

  async read() {
    const req = new ReadPipeRequest();
    req.setPipe(this.pipe)
    //req.setSize(4096)
    let result;
    try {
      result = await M.ReadPipe(req);
      let x = result.getData()
      return D.decode(x)
    } catch (e) {
      throw e
    }
  }

  async readAll() {
    return new Promise( (resolve, reject) => {
      let pending = "";

      let f = async (bs) => {
        pending = pending + bs

        try {
          while (bs.length > 0) {
            bs = await this.read();
            pending = pending + bs
          }
        } catch(e) {
          resolve(pending)
        }
      }
      // the process may not output any data
      this.read().then(f, ()=>{resolve(pending)})
    })
  }

  async write(chunk) {
    const req = new WritePipeRequest();
    req.setPipe(this.pipe);
    req.setData(E.encode(chunk));
    return M.WritePipe(req)
  }
}


/**
 * The Process class
 * @param {} pid
 */
class Process {
  constructor(pid) {
    this.pid = pid;
    this.stdin = new Pipe(pid, 0)
    this.stdout = new Pipe(pid, 1)
    this.stderr = new Pipe(pid, 2)

    this.end = false
  }

  /**
   * start the process
   * @returns {}
   */
  start() {
    return M.Start(this.pid)
  }


  /**
   * kill the running process
   * @returns {}
   */
  kill() {
    return M.Kill(this.pid)
  }


  /**
   * wait the process terminated.
   *  you must call this method otherwise the process will become zombie.
   * @returns {int} 1 if process failed otherwise 0
   */
  async wait() {
    try {
      this.exit_err = await M.Wait(this.pid)
      this.end = true
      return 0;
    } catch (e) {
      this.exit_err = e
      this.end = true
      return 1;
    }
  }
}

/**
 * System execute a shell command like libc's system(3)
 *
 * @param {} command
 * @param {} args
 * @returns string
 */
export async function System(command, ...args) {
  let p = await ExecFull({
    command: "sh",
    args: ["-c", [command].concat(args).join(" ")],
  })

  await p.start()

  let r = await p.stdout.readAll()

  await p.wait()

  return r
}

/**
 * Run execute the $prog and waiting process terminated
 * this will ignore all stdout/stderr/stdin
 * @param {} prog
 * @param {} args
 * @returns {}
 */
export async function Run(prog, ...args) {
  let p = await ExecFull({
    command: prog,
    args,
  })
  await p.start()
  return p.wait()
}

/**
 * Run execute the $prog and waiting process terminated
 * return all stdout by a promise.
 * You must call p.wait() to release Process resource.
 * @param {} prog
 * @param {} args
 * @returns {Process}
 */
export async function Start(prog, ...args) {
  let p = await ExecFull({
    command: prog,
    args,
  })
  await p.start()
  return p
}


/**
 * ExecFull execute the $prog and waiting process terminated
 * return the Process object by promise.
 * @param {} command
 * @param {} args
 * @param {} env
 * @param {} workdir
 * @param {} tty
 * @returns {Process}
 */
export async function ExecFull({command, args=[], env=[], workdir, tty=false}) {
  let sinfo = new SpawnInfo()
  sinfo.setCommand(command)
  sinfo.setWorkdir(workdir)
  if (tty) {
    sinfo.setTty(true)
  }

  for (let i=0; i<env.length; i++) {
    sinfo.addEnvironment(env[i])
  }
  for (let i=0; i<args.length; i++) {
    sinfo.addArguments(args[i])
  }

  let pid = await M.Spawn(sinfo)
  return new Process(pid)
}
