///|
fn read_document(path : String) -> String raise {
  let text = @fs.read_file_to_string(path)
  if text.length() > 16777216 {
    raise @lens.Capacity(resource="CLI text", limit=16777216)
  }
  text
}

///|
fn read_log(path : String) -> @lens.EventLog raise {
  let text = read_document(path)
  if path.to_lower().has_suffix(".xes") {
    @lens.parse_xes(text)
  } else {
    @lens.parse_log(text)
  }
}

///|
fn read_model(path : String) -> @lens.PetriNet raise {
  let text = read_document(path)
  if path.to_lower().has_suffix(".bpmn") {
    @lens.parse_bpmn(text).to_petri_net()
  } else if path.to_lower().has_suffix(".pnml") {
    @lens.parse_pnml(text)
  } else {
    @lens.parse_net(text)
  }
}

///|
fn run(args : Array[String]) -> Unit raise {
  if args.length() < 3 {
    println(
      "usage: processlens analyze LOG | align LOG REFERENCES.json | discover LOG alpha|prefix | discover-pnml LOG alpha|prefix | check LOG MODEL | model MODEL | to-xes LOG | from-xes LOG.xes completed|pairs | audit LOG POLICIES.json | compare BEFORE AFTER | resources LOG",
    )
    @sys.exit(2)
    return
  }
  match args[1] {
    "analyze" if args.length() == 3 => {
      let log = read_log(args[2])
      println(@lens.process_map_to_json(log.discover()).stringify())
      println(@lens.performance_to_json(log.performance()).stringify())
    }
    "align" if args.length() == 4 => {
      let log = read_log(args[2])
      let spec = @lens.parse_alignment_spec(read_document(args[3]))
      // Validate all case results before emitting any document.
      let results = log.align(spec.paths(), costs=spec.costs())
      for result in results {
        println(@lens.alignment_to_json(result).stringify())
      }
    }
    "discover" | "discover-pnml" if args.length() == 4 => {
      let log = read_log(args[2])
      let net = match args[3] {
        "alpha" => log.discover_alpha()
        "prefix" => log.discover_prefix_net()
        _ =>
          raise @lens.InvalidLog(
            path="discovery.algorithm",
            reason="expected alpha or prefix",
          )
      }
      println(
        if args[1] == "discover" {
          net.to_json().stringify()
        } else {
          net.to_pnml()
        },
      )
    }
    "check" if args.length() == 4 => {
      let log = read_log(args[2])
      if log.case_count() == 0 {
        raise @lens.InvalidLog(
          path="check.log",
          reason="cannot pass a check without cases",
        )
      }
      let net = read_model(args[3])
      let results = log.align_net(net)
      let output : Json = {
        "model": @lens.model_analysis_to_json(net.analyze()),
        "alignments": Json::array(results.map(@lens.model_alignment_to_json)),
      }
      println(output.stringify())
      if !results.all(result => result.conforming) {
        @sys.exit(3)
      }
    }
    "model" if args.length() == 3 =>
      println(
        @lens.model_analysis_to_json(read_model(args[2]).analyze()).stringify(),
      )
    "to-xes" if args.length() == 3 => println(read_log(args[2]).to_xes())
    "from-xes" if args.length() == 4 => {
      let mode = match args[3] {
        "completed" => @lens.Completed
        "pairs" => @lens.LifecyclePairs
        _ =>
          raise @lens.InvalidLog(
            path="xes.mode",
            reason="expected completed or pairs",
          )
      }
      println(
        @lens.log_to_json(@lens.parse_xes(read_document(args[2]), mode~)).stringify(),
      )
    }
    "audit" if args.length() == 4 => {
      let log = read_log(args[2])
      let results = log.check_policies(
        @lens.parse_policies(read_document(args[3])),
      )
      println(@lens.policy_results_to_json(results).stringify())
      if results.any(result => {
          result.status == Violated || result.status == Inconclusive
        }) {
        @sys.exit(3)
      }
    }
    "compare" if args.length() == 4 =>
      println(
        @lens.cohort_comparison_to_json(
          read_log(args[2]).compare_cohort(read_log(args[3])),
        ).stringify(),
      )
    "resources" if args.length() == 3 =>
      println(
        @lens.resource_handoffs_to_json(read_log(args[2]).resource_handoffs()).stringify(),
      )
    _ => {
      println("unknown command or wrong argument count")
      @sys.exit(2)
    }
  }
}

///|
fn main {
  run(@env.args()) catch {
    error => {
      match error {
        @lens.InvalidLog(path~, reason~) =>
          println("ERROR at \{path}: \{reason}")
        @lens.Capacity(resource~, limit~) =>
          println("ERROR: capacity \{resource} exceeded (\{limit})")
        _ => println("ERROR: \{error.to_string()}")
      }
      @sys.exit(1)
    }
  }
}