(*
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
*)

(**
This module deals with options. It contains references that are filled
according to the command options and can be used by the other modules
@author Jaap Boender
*)

type input_types = Debian | DebianCache | RPM | HDList | SynHDList | Pkgsrc | EGraphIn
type output_types = PrettyPrint | EGraphOut | (* RPMFind | *) Dose | Oz | Graphviz | TartCNF | TartSize | DimacsCNF
type version_types = Unspecified | DebianVersions | RPMVersions | DeweyVersions
type db_types = MySQL
type db_info_type = {
	db_type: db_types; 
	db_hostname: string;
	db_name: string;
	db_port: int;
	db_user: string;
	db_password: string
}
type dep_types = Conflicts | Depends | Enhances | PreDepends | Recommends | Replaces | Suggests
;;

let output_channel = ref stdout (** global reference to output channel *)
let output_dir = ref "" (** global reference to output directory *)
let input_type = ref Debian (** global reference to type of input *)
let input_files = ref [] (** global reference to input files *)
let output_type = ref PrettyPrint (** global reference to type of output *)
let version_type = ref Unspecified (** global reference to version comparison type *)
let resolve_dependencies = ref false (** resolve dependencies or not *)
let database_info = ref {db_type = MySQL; db_hostname = ""; db_name = ""; db_port = 5432; db_user = ""; db_password = ""} (** database information *)
let create_tables = ref true (** create non-present tables *)
let verbose = ref false (** verbosity *)
let cone_package = ref None (** extract cone for package *) 
let cone_types = ref [Conflicts; Depends; Enhances; PreDepends; Recommends; Replaces; Suggests] (** cone types to extract *)
let cone_stops = ref [] (** do not cone-extract these packages *)
let dose_date = ref (0, 0, 0) (** Interval for Dose *)
let dose_archive = ref "" (** Dose archive name *)
let dose_type = ref "" (** Dose type name *)
let ignore_unknown_fields = ref false (** Ignore unknown metadata fields *)
let explicit_conflicts = ref false (** Add explicit conflicts *)
let ignore_file_deps = ref ""
;;

(** Set the output file *)
let set_output_file (filename: string): unit =
begin
	output_channel := open_out filename
end;;

(** Set the output directory *)
let set_output_dir (dirname: string): unit =
begin
	output_dir := dirname
end;;

(** Set the input type *)
let set_input_type (type_name: string): unit =
let t = String.lowercase type_name in
begin
	if t = "debian" then
	begin
		input_type := Debian;
		if !version_type = Unspecified then version_type := DebianVersions;
		explicit_conflicts := true;
	end
	else if t = "debian-cache" || t = "debian-pool" then
	begin
		input_type := DebianCache;
		if !version_type = Unspecified then version_type := DebianVersions;
		explicit_conflicts := true;
	end
	else if t = "rpm" then
	begin
		input_type := RPM;
		if !version_type = Unspecified then version_type := RPMVersions
	end
	else if t = "hdlist" then
	begin
		input_type := HDList;
		if !version_type = Unspecified then version_type := RPMVersions
	end
	else if t = "synthesis-hdlist" then
	begin
		input_type := SynHDList;
		if !version_type = Unspecified then version_type := RPMVersions
	end
	else if t = "pkgsrc" then
	begin
		input_type := Pkgsrc;
		if !version_type = Unspecified then version_type := DeweyVersions
	end
	else if t = "egraph" then
		input_type := EGraphIn
	else
		raise (Failure ("Unknown input type " ^ type_name))
end;;

(** Add a file for input *)
let add_input_file (filename: string): unit =
begin
	input_files := (filename::!input_files)
end;;

(** Set the output type *)
let set_output_type (type_name: string): unit =
let t = String.lowercase type_name in
begin
	if t = "prettyprint" then
		output_type := PrettyPrint
	else if t = "dgraph" || t = "egraph" then	
		output_type := EGraphOut
	(* else if t = "rpmfind" || t = "sql-rpmfind" then
		output_type := RPMFind *)
	else if t = "dose" then
		output_type := Dose
	else if t = "oz" then
		output_type := Oz
	else if t = "graphviz" || t = "dot" then
		output_type := Graphviz
	else if t = "tart" || t = "tart-cnf" || t = "cnf" then
	begin
		output_type := TartCNF;
		resolve_dependencies := true (* necessary! *)
	end
	else if t = "tart-size" then
		output_type := TartSize
  else if t = "dimacs" || t = "dimacs-cnf" then
  begin
    output_type := DimacsCNF;
    resolve_dependencies := true (* necessary! *)
  end
	else
		raise (Failure ("Unknown output type " ^ type_name))
end;;

(** Set SQL database information *)
let set_database_info (info_string: string): unit =
let info_parts = Str.split_delim (Str.regexp_string ":") info_string in
begin
	if List.length info_parts = 5 then
		database_info := {
			db_type = MySQL;
			db_hostname = List.nth info_parts 0;
			db_name = List.nth info_parts 1;
			db_user = List.nth info_parts 3;
			db_password = List.nth info_parts 4;
			db_port = let port_string = List.nth info_parts 2 in
				if port_string = "" then 5432 else int_of_string port_string
		}
	else
		raise (Failure ("Invalid database string " ^ info_string));
end;;

(** Force usage of Debian versions *)
let set_debian_versions (_: unit): unit =
begin
	version_type := DebianVersions;
	explicit_conflicts := true;
end;;

(** Force usage of RPM versions *)
let set_rpm_versions (_: unit): unit =
begin
	version_type := RPMVersions
end;;

(** Extract cone *)
let set_cone_package (spec: string): unit =
begin
	try
		let eq_index = String.index spec '=' in
		let name = String.sub spec 0 eq_index in
		let version = String.sub spec (eq_index + 1) (String.length spec - eq_index - 1) in
			cone_package := Some (name, version)
	with Not_found -> cone_package := Some (spec, "")
end;;

(** set cone types *)
let set_cone_types (spec: string): unit =
let specs = List.map (fun x -> String.lowercase (Tools.truncate x)) (Str.split (Str.regexp_string ",") spec) in
begin
	cone_types := [];
	List.iter (fun s ->
		if s = "conflicts" then cone_types := Conflicts::!cone_types  
		else if s = "depends" then cone_types := Depends::!cone_types  
		else if s = "enhances" then cone_types := Enhances::!cone_types  
		else if s = "predepends" || s = "pre-depends" || s = "install" then
			cone_types := PreDepends::!cone_types  
		else if s = "recommends" then cone_types := Recommends::!cone_types  
		else if s = "replaces" then cone_types := Replaces::!cone_types  
		else if s = "suggests" then cone_types := Suggests::!cone_types  
		else raise (Failure ("Unknown dependency type: " ^ s))
	) specs;
	cone_types := Tools.uniq (List.sort compare !cone_types)
end;;

let set_cone_stops (spec: string): unit =
let specs = List.map Tools.truncate (Str.split (Str.regexp_string ",") spec) in
begin
	cone_stops := [];
	List.iter (fun s ->
		try
			let eq_index = String.index s '=' in
			let name = String.sub s 0 eq_index in
			let version = String.sub s (eq_index + 1) (String.length s - eq_index - 1) in
				cone_stops := (name, version)::!cone_stops	
		with
			Not_found -> cone_stops := (s, "")::!cone_stops
	) specs;
	cone_stops := Tools.uniq (List.sort compare !cone_stops)
end;;

(** Set DOSE date *)
let set_dose_date (opt_string: string): unit =
begin
	Scanf.sscanf opt_string "%[0-9]-%[0-9]-%[0-9]"
	(fun y m d ->
		prerr_endline ("Year " ^ y ^ ", month: " ^ m ^ ", date: " ^ d);
		dose_date := (int_of_string y, int_of_string m, int_of_string d))
end;;

(** command line options *)
let options_spec =
	[("-o", Arg.String set_output_file, "Set output file (default stdout)");
	 ("-output-file", Arg.String set_output_file, "Set output file (default stdout)");
	 ("-output-dir", Arg.String set_output_dir, "Set output directory (for dose)");
	 ("-p", Arg.String set_input_type, "Set type of input (default debian)");
	 ("-input-type", Arg.String set_input_type, "Set type of input (default debian)");
	 ("-t", Arg.String set_output_type, "Set type of output (default prettyprint)");
	 ("-output-type", Arg.String set_output_type, "Set type of output (default prettyprint)");
	 ("-x", Arg.String set_cone_package, "Extract dependency cone for a given package (and possibly version)");
	 ("-extract-cone", Arg.String set_cone_package, "Extract dependency cone for a given package (and possibly version)");
	 ("-cone-dep-types", Arg.String set_cone_types, "Dependency types to follow for cone extraction (default: all)");
	 ("-stop-extraction", Arg.String set_cone_stops, "Set packages to not extract for cone extraction");
	 ("-r", Arg.Set resolve_dependencies, "Resolve virtual dependencies");
	 ("-resolve-dependencies", Arg.Set resolve_dependencies, "Resolve virtual dependencies");
	 ("-vr", Arg.Unit set_rpm_versions, "Force usage of RPM version comparisons");
	 ("-rpm-versions", Arg.Unit set_rpm_versions, "Force usage of RPM version comparisons");
	 ("-vd", Arg.Unit set_debian_versions, "Force usage of Debian version comparisons");
	 ("-debian-versions", Arg.Unit set_debian_versions, "Force usage of Debian version comparisons");
	 ("-d", Arg.String set_database_info, "Set database info (SQL only; format: hostname:database:port:user:password)");
	 ("-database-info", Arg.String set_database_info, "Set database info (SQL only; format: hostname:database:port:user:password)");
	 ("-create-tables", Arg.Set create_tables, "Create SQL tables if not present already (default behaviour)");
	 ("-no-create-tables", Arg.Clear create_tables, "Do not create SQL tables");
	 ("-dose-date", Arg.String set_dose_date, "Set DOSE date (format: <yyyy-mm-dd>)");
	 ("-dose-archive", Arg.Set_string dose_archive, "Set DOSE archive name");
   ("-dose-type", Arg.Set_string dose_type, "Override DOSE archive type (determined automatically by default)");
	 ("-v", Arg.Set verbose, "Verbose output to stderr for debugging purposes");
	 ("-verbose", Arg.Set verbose, "Verbose output to stderr for debugging purposes");
	 ("-ignore-unknown-fields", Arg.Set ignore_unknown_fields, "Do not emit warnings on unknown fields in package metadata");
	 ("-generate-explicit-conflicts", Arg.Set explicit_conflicts, "Generate explicit conflicts between different versions of same package");
   ("-ignore-file-deps", Arg.Set_string ignore_file_deps, "Ignore file dependencies from this file");
	];;

(** Fill the global references with command line options *)
let parse_options: unit =
begin
	Arg.parse options_spec add_input_file "Ceve version 1.4"
end;;
