(*
 * generic tools for modules
 *
 * Copyright (C) 2010 Gautier Hattenberger
 *
 * This file is part of paparazzi.
 *
 * paparazzi 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, or (at your option)
 * any later version.
 *
 * paparazzi 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 paparazzi; see the file COPYING.  If not, write to
 * the Free Software Foundation, 59 Temple Place - Suite 330,
 * Boston, MA 02111-1307, USA.
 *
 *)

open Printf

type module_conf = { xml : Xml.xml; file : string; param : Xml.xml list; extra_targets : string list; }

let (//) = Filename.concat

let paparazzi_conf = Env.paparazzi_home // "conf"
let modules_dir = paparazzi_conf // "modules"
let autopilot_dir = paparazzi_conf // "autopilot"

let default_module_targets = "ap|sim|nps"
let default_freq = 60

(** remove all duplicated elements of a list *)
let singletonize = fun l ->
  let rec loop = fun l ->
    match l with
      [] | [_] -> l
    | x::((x'::_) as xs) ->
    if x = x' then loop xs else x::loop xs in
  loop (List.sort compare l)

(** union of two lists *)
let union = fun l1 l2 ->
  let l = l1 @ l2 in
  let sl = List.sort compare l in
  singletonize sl

(** union of a list of list *)
let union_of_lists = fun l ->
  let sl = List.sort compare (List.flatten l) in
  singletonize sl

(** [targets_of_field]
 * Returns the targets of a makefile node in modules
 * Default "ap|sim" *)
let pipe_regexp = Str.regexp "|"
let targets_of_field = fun field default ->
  try
    Str.split pipe_regexp (ExtXml.attrib_or_default field "target" default)
  with
    _ -> []

(** [get_autopilot_of_airframe xml]
 * Returns (autopilot xml, main freq) from airframe xml file *)
let get_autopilot_of_airframe = fun xml ->
  (* extract all "modules" sections *)
  let section = List.filter (fun s -> compare (Xml.tag s) "autopilot" = 0) (Xml.children xml) in
  (* Raise error if more than one modules section *)
  match section with
    [autopilot] ->
      let freq = try int_of_string (Xml.attrib autopilot "freq") with _ -> default_freq in
      let ap = try Xml.attrib autopilot "name" with _ -> raise Not_found in
      (autopilot_dir // ap, freq)
  | [] -> raise Not_found
  | _ -> failwith "Error: you have more than one 'autopilot' section in your airframe file"

(** [get_modules_of_airframe xml]
 * Returns a list of module configuration from airframe file *)
let rec get_modules_of_airframe = fun xml ->
  (* extract all "modules" sections *)
  let section = List.filter (fun s -> compare (Xml.tag s) "modules" = 0) (Xml.children xml) in
  (* get autopilot file if any *)
  let ap_file = try
    let (ap, _) = get_autopilot_of_airframe xml in
    ap
  with _ -> "" in
  (* Raise error if more than one modules section *)
  match section with
    [modules] ->
      (* if only one section, returns a list of configuration *)
      let t_global = targets_of_field modules "" in
      let get_module = fun m t ->
        let file = modules_dir // ExtXml.attrib m "name" in
        let targets = singletonize (t @ targets_of_field m "") in
        { xml = ExtXml.parse_file file; file = file; param = Xml.children m; extra_targets = targets }
      in
      let modules_list = List.map (fun m ->
        if compare (Xml.tag m) "load" <> 0 then Xml2h.xml_error "load";
        get_module m t_global
      ) (Xml.children modules) in
      let ap_modules = try
        get_modules_of_airframe (ExtXml.parse_file ap_file)
      with _ -> [] in
      modules_list @ ap_modules
  | [] -> []
  | _ -> failwith "Error: you have more than one 'modules' section in your airframe file"

(** [get_targets_of_module xml]
 * Returns the list of targets of a module *)
let get_targets_of_module = fun conf ->
  let targets = List.map (fun x ->
    match String.lowercase (Xml.tag x) with
      "makefile" -> targets_of_field x default_module_targets
    | _ -> []
  ) (Xml.children conf.xml) in
  let targets = (List.flatten targets) @ conf.extra_targets in
  (* return a singletonized list *)
  singletonize (List.sort compare targets)

(** [unload_unused_modules modules ?print_error]
 * Returns a list of [modules] where unused modules are removed
 * If [print_error] is true, a warning is printed *)
let unload_unused_modules = fun modules print_error ->
  let target = try Sys.getenv "TARGET" with _ -> "" in
  let is_target_in_module = fun m ->
    let target_is_in_module = List.exists (fun x -> String.compare target x = 0) (get_targets_of_module m) in
    if print_error && not target_is_in_module then
      Printf.fprintf stderr "Module %s unloaded, target %s not supported\n" (Xml.attrib m.xml "name") target;
    target_is_in_module
  in
  if String.length target = 0 then
    modules
  else
    List.find_all is_target_in_module modules

(** [get_modules_name xml]
 * Returns a list of loaded modules' name *)
let get_modules_name = fun xml ->
  (* extract all "modules" sections *)
  let modules = get_modules_of_airframe xml in
  (* filter the list if target is not supported *)
  let modules = unload_unused_modules modules false in
  (* return a list of modules name *)
  List.map (fun m -> ExtXml.attrib m.xml "name") modules

(** [get_modules_dir xml]
 * Returns the list of modules directories *)
let get_modules_dir = fun modules ->
  let dir = List.map (fun m -> try Xml.attrib m.xml "dir" with _ -> ExtXml.attrib m.xml "name") modules in
  singletonize (List.sort compare dir)

