let main () =
  let timer1 = Util.Timer.create "parsing" in
  let timer2 = Util.Timer.create "conversion" in
  let timer3 = Util.Timer.create "cudfio" in
  let timer4 = Util.Timer.create "solver" in
  let timer5 = Util.Timer.create "solution" in
  let args = OptParse.OptParser.parse_argv Options.options in
  Boilerplate.enable_debug (OptParse.Opt.get Options.verbose);
  Boilerplate.enable_bars (OptParse.Opt.get Options.progress) [] ;
  Boilerplate.enable_timers (OptParse.Opt.get Options.timers)
  ["parsing";"cudfio";"conversion";"solver";"solution"];

  (* Solver "exec:" line. Contains three named wildcards to be interpolated:
     "$in", "$out", and "$pref"; corresponding to, respectively, input CUDF
     document, output CUDF universe, user preferences. *)

  let exec_pat =
    if OptParse.Opt.is_set Options.solver then
      let f = OptParse.Opt.get Options.solver in
      fst (parse_solver_spec (Filename.concat solver_dir f))
    else
      let f = Filename.basename(Sys.argv.(0)) in
      fst (parse_solver_spec (Filename.concat solver_dir f))
  in
  let interpolate_solver_pat exec cudf_in cudf_out pref =
    let _, exec = String.replace ~str:exec ~sub:"$in"   ~by:cudf_in  in
    let _, exec = String.replace ~str:exec ~sub:"$out"  ~by:cudf_out in
    let _, exec = String.replace ~str:exec ~sub:"$pref" ~by:pref     in
    exec
  in

  let ch = 
    match args with 
    |[] -> (IO.input_channel stdin)
    |file::_ -> Input.open_file file 
  in
  
  Util.Timer.start timer1;
  let (request,pkglist) = Edsp.input_raw_ch ch in
  Util.Timer.stop timer1 ();
  
  if args <> [] then Input.close_ch ch;

  Util.Timer.start timer2;
  let tables = Debcudf.init_tables pkglist in
  let default_preamble =
    let l = List.map snd Edsp.extras_tocudf in
    CudfAdd.add_properties Debcudf.preamble l
  in
  
  let univ = Hashtbl.create (2*(List.length pkglist)-1) in
  let cudfpkglist = 
    List.map (fun pkg ->
      let p = Edsp.tocudf tables pkg in
      Hashtbl.add univ (p.Cudf.package,p.Cudf.version) pkg;
      p
    ) pkglist 
  in

  if OptParse.Opt.get Options.dump then begin
    info "dump universe in  /tmp/cudf-solver.universe.dump";
    let oc = open_out "/tmp/cudf-solver.universe.dump" in
    Cudf_printer.pp_preamble oc default_preamble;
    Printf.fprintf oc "\n";
    Cudf_printer.pp_packages oc cudfpkglist;
    close_out oc
  end;

  let universe = 
    try Cudf.load_universe cudfpkglist
    with Cudf.Constraint_violation s ->
      print_error "(CUDF) Malformed universe %s" s;
  in
  let cudf_request = make_request tables universe request in
  let cudf = (default_preamble,universe,cudf_request) in
  Util.Timer.stop timer2 ();

  let tmpdir = mktmpdir "tmp.apt-cudf.XXXXXXXXXX" in
  at_exit (fun () -> rmtmpdir tmpdir);
  let solver_in = Filename.concat tmpdir "in-cudf" in
  Unix.mkfifo solver_in 0o600;
  let solver_out = Filename.concat tmpdir "out-cudf" in

  let cmdline_criteria = OptParse.Opt.opt (Options.criteria) in
  let criteria = choose_criteria ~criteria:cmdline_criteria request in
  let cmd = interpolate_solver_pat exec_pat solver_in solver_out criteria in
  Printf.eprintf "CMD %s\n%!" cmd;

  let env = Unix.environment () in
  let (cin,cout,cerr) = Unix.open_process_full cmd env in

  Util.Timer.start timer3;
  let solver_in_fd = Unix.openfile solver_in [Unix.O_WRONLY ; Unix.O_SYNC] 0 in
  let oc = Unix.out_channel_of_descr solver_in_fd in
  Cudf_printer.pp_cudf oc cudf;
  close_out oc ;
  Util.Timer.stop timer3 ();

  Util.Timer.start timer4;
  let lines_cin = input_all_lines [] cin in
  let lines = input_all_lines lines_cin cerr in
  let stat = Unix.close_process_full (cin,cout,cerr) in
  begin match stat with
    |Unix.WEXITED 0 -> ()
    |Unix.WEXITED i ->
        print_error "command '%s' failed with code %d" cmd i
    |Unix.WSIGNALED i ->
        print_error "command '%s' killed by signal %d" cmd i
    |Unix.WSTOPPED i ->
        print_error "command '%s' stopped by signal %d" cmd i
  end;
  let debug = String.concat "\n" lines in
  info "%s\n%s\n%!" cmd debug; 
  Util.Timer.stop timer4 ();

  Util.Timer.start timer5;
  if not(Sys.file_exists solver_out) then 
    print_error "(CRASH) Solution file not found"
  else if check_fail solver_out then
    print_error "(UNSAT) No Solutions according to the give preferences"
  else begin
    try begin
      let sol = 
        if (Unix.stat solver_out).Unix.st_size <> 0 then
          let cudf_parser = Cudf_parser.from_file solver_out in
          let (_,sol,_) = Cudf_parser.parse cudf_parser in
          sol
        else []
      in
      let soluniv = Cudf.load_universe sol in
      let diff = CudfDiff.diff universe soluniv in
      let empty = ref true in
      Hashtbl.iter (fun pkgname s ->
        let inst = s.CudfDiff.installed in
        let rem = s.CudfDiff.removed in
        match CudfAdd.Cudf_set.is_empty inst, CudfAdd.Cudf_set.is_empty rem with
        |false,true -> begin
            empty := false;
            Format.printf "Install: %a@." pp_pkg (inst,univ)
        end
        |true,false -> begin
            empty := false;
            Format.printf "Remove: %a@." pp_pkg (rem,univ)
        end
        |false,false -> begin
            empty := false;
(* Do not remove a package that just needs upgrading! *)
(*            Format.printf "Remove: %a@." pp_pkg (rem,univ); *)
            Format.printf "Install: %a@." pp_pkg (inst,univ)
        end
        |true,true -> ()
      ) diff;

      if OptParse.Opt.get Options.explain then begin
        let (i,u,d,r) = CudfDiff.summary universe diff in
        Format.printf "Summary: " ;
        if i <> [] then
          Format.printf "%d to install " (List.length i);
        if r <> [] then
          Format.printf "%d to remove " (List.length r);
        if u <> [] then
          Format.printf "%d to upgrade " (List.length u);
        if d <> [] then
          Format.printf "%d to downgrade " (List.length d);
        Format.printf " @.";

        if i <> [] then
          Format.printf "Installed: %a@." pp_pkg_list (i,univ);
        if r <> [] then 
          Format.printf "Removed: %a@." pp_pkg_list (r,univ);
        if u <> [] then 
          Format.printf "Upgraded: %a@." pp_pkg_list_tran (u,univ);
        if d <> [] then 
          Format.printf "Downgraded: %a@." pp_pkg_list_tran (d,univ);
      end;
      
      if !empty then 
        print_progress ~i:100 "No packages removed or installed"
    end with Cudf.Constraint_violation s ->
      print_error "(CUDF) Malformed solution: %s" s
  end;
  Util.Timer.stop timer5 ();
  Sys.remove solver_in;
  Sys.remove solver_out;