(*
This file is part of Ceve.

Ceve is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.

Ceve is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with Ceve; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
*)

open Napkin

type pkg_dep_list = (string, string, string) versioned list list * (string, string, string) versioned list * (string, string, string) versioned list;;
type pkg_hashtable = (string, (string * pkg_dep_list option) list) Hashtbl.t;;

let create_pkg_hashtable (pkgs: default_package list): pkg_hashtable =
let result = Hashtbl.create (List.length pkgs) in
begin
	List.iter (fun pkg ->
	let pkg_versions = try Hashtbl.find result pkg.pk_unit with Not_found -> [] in
		Hashtbl.replace result pkg.pk_unit ((pkg.pk_version, Some (pkg.pk_depends, pkg.pk_conflicts, pkg.pk_provides))::pkg_versions);
		List.iter (fun disj ->
			List.iter (fun d ->
			match d with
			| Unit_version (target, spec) ->
			let vl = try Hashtbl.find result target with Not_found -> [] in
			let v = Dependencies.version_of spec in
				if v <> "" then Hashtbl.replace result target ((v, None)::vl)
			| Glob_pattern _ -> raise (Failure "ARGH VII")
			) disj
		) pkg.pk_depends;
    List.iter (fun d ->
		match d with
		| Unit_version (target, spec) ->
			let vl = try Hashtbl.find result target with Not_found -> [] in
			let v = Dependencies.version_of spec in
				if v <> "" then Hashtbl.replace result target ((v, None)::vl)
		| Glob_pattern _ -> raise (Failure "ARGH VIII")
		) pkg.pk_conflicts;
		List.iter (fun d ->
		match d with
		| Unit_version (target, spec) ->
			let vl = try Hashtbl.find result target with Not_found -> [] in
			let v = Dependencies.version_of spec in
				if v <> "" then Hashtbl.replace result target ((v, None)::vl)
		| Glob_pattern _ -> raise (Failure "ARGH IX")
		) pkg.pk_provides;
	) pkgs;
	Hashtbl.iter (fun name vs ->
		Hashtbl.replace result name (Tools.uniq (List.sort
		(fun (x1, y1) (x2, y2) -> Dependencies.compare_versions x1 x2) vs))
	) result;
  result
end;;

let index_of (v: 'a) (vl: 'a list): int =
let count = ref 0
and result = ref (-1) in
begin
	List.iter (fun x -> if x = v then result := !count else incr count) vl;
	if !result = (-1) then raise Not_found
	else !result
end;;

let oz_identifier_of (s: string): string =
begin
	"'" ^ s ^ "'"
end;;

let output_package (oc: out_channel) (ht: pkg_hashtable) (pkg_name: string) (pkg_versions: (string * pkg_dep_list option) list): unit =
let oz_of_dependency (dep: (string, string, string) versioned): string =
match dep with
| Glob_pattern _ -> raise (Failure "ARGH X")
| Unit_version (target, spec) -> 
let versions = List.map fst (try Hashtbl.find ht target with Not_found -> []) in
begin
	"'" ^ target ^ "'#" ^ 
	(match spec with
		Sel_ANY -> "gt(0)"
	| Sel_LT x -> ("lt(" ^ (string_of_int ((index_of x versions) + 1)) ^ ")")
	| Sel_LEQ x -> ("le(" ^ (string_of_int ((index_of x versions) + 1)) ^ ")")
	| Sel_EQ x -> ("eq(" ^ (string_of_int ((index_of x versions) + 1)) ^ ")")
	| Sel_GEQ x -> ("ge(" ^ (string_of_int ((index_of x versions) + 1)) ^ ")")
	| Sel_GT x -> ("gt(" ^ (string_of_int ((index_of x versions) + 1)) ^ ")"))
end
and count = ref 1 in
begin
	if pkg_versions <> [] then
	begin
		output_string oc ("  " ^ (oz_identifier_of pkg_name) ^ ": versions(\n");
		List.iter (fun (pkg_version, deps) ->
			match deps with
		  	None ->
				(* begin
					output_string oc ("    " ^ (string_of_int !count) ^ ": 'dep-only'\n"); *)
					incr count
				(* end *)
			| Some (d, c, p) ->
				begin
					output_string oc ("    " ^ (string_of_int !count) ^ ": relations(\n");
					output_string oc "      depends: ";
					output_string oc (if d = [] then "nil\n" else ("[ " ^ (String.concat " " (List.map (fun x -> if List.length x = 1 then oz_of_dependency (List.hd x) else "[ " ^ (String.concat " " (List.map oz_of_dependency x)) ^ " ]") (List.sort (fun x y -> compare (List.length x) (List.length y)) d))) ^ " ]\n"));
					output_string oc "      conflicts: ";
					output_string oc (if c = [] then "nil\n" else ("[ " ^ (String.concat " " (List.map oz_of_dependency c)) ^ " ]\n")); 
					output_string oc "      provides: ";
					output_string oc (if p = [] then "nil\n" else ("[ " ^ (String.concat " " (List.map oz_of_dependency p)) ^ " ]\n")); 
					output_string oc "    )\n";
				end;
				incr count
		) pkg_versions;
		output_string oc "  )\n"
	end
end;;

let output_version_mappings (oc: out_channel) (ht: pkg_hashtable): unit =
begin
	output_string oc "% Version mappings\n";
	output_string oc "% ----------------\n";
	Hashtbl.iter (fun name versions ->
	let count = ref 0 in
		output_string oc ("% " ^ name ^ ": {" ^ (String.concat ", " (List.map (fun x -> incr count; (fst x) ^ "=" ^ (string_of_int !count)) versions)) ^ "}\n");
	) ht;
	output_string oc "\n"
end;;

let output_oz (pkgs: default_package list): unit =
let ht = create_pkg_hashtable pkgs in
begin
	prerr_endline "[Oz] Outputting Oz file...";
	output_version_mappings !Options.output_channel ht;
	output_string !Options.output_channel "Packages = packages(\n";
	Hashtbl.iter (fun name versions ->
		output_package !Options.output_channel ht name versions
	) ht;
	output_string !Options.output_channel ")\n"
end;;
