(* $Id$ -*- tuareg -*-
 * ----------------------------------------------------------------------
 *
 *)
open Fl_metatoken
  
open Printf
  
type formal_pred = [ | `Pred of string | `NegPred of string ]

type flavour = [ | `BaseDef | `Appendix ]

type pkg_definition =
  { def_var : string; def_flav : flavour; def_preds : formal_pred list;
    def_value : string
  }

type pkg_expr =
  { pkg_defs : pkg_definition list; pkg_children : (string * pkg_expr) list
  }

exception Error of string

let string_of_preds pl =
  let print = function | `Pred n -> n | `NegPred n -> "-" ^ n
  in
    if pl = []
    then ""
    else "(" ^ ((String.concat "," (List.map print pl)) ^ ")")
  

let scan_lexing buf =
  (* transform an in_channel to a token stream; 'Space' tokens are left
   * out.
   *)
  let (line_ref, pos0_ref, eof_found) = ((ref 1), (ref 0), (ref false))
  in
    fun () ->
      let rec next line pos0 =
        let t = Fl_meta.token buf
        in
          match t with
          | Space -> next line pos0
          | Newline -> next (line + 1) (Lexing.lexeme_end buf)
          | Eof -> (eof_found := true; produce line pos0 Eof)
          | _ -> produce line pos0 t
      and produce line pos0 t =
        (line_ref := line;
         pos0_ref := pos0;
         let pos = (Lexing.lexeme_start buf) - pos0 in (line, pos, t))
      in
        if !eof_found
        then produce !line_ref !pos0_ref Eof
        else next !line_ref !pos0_ref
  
let scan ch = scan_lexing (Lexing.from_channel ch)
  
let parse_lexing lexbuf =
  let rec mk_set l =
    match l with
    | x :: l' -> if List.mem x l' then mk_set l' else x :: (mk_set l')
    | [] -> [] in
  let error_msg msg line col =
    Printf.sprintf "%s at line %d position %d" msg line col in
  let next_token = scan_lexing lexbuf in
  let raise_err error_fun line col =
    raise (Error (error_fun line col)) in
  let get_tok test error_fun =
    let (line, col, tok) = next_token ()
    in
      match test tok with
      | None -> raise_err error_fun line col
      | Some result -> result in
  let get_rule rule arg error_fmt line col =
    try rule arg with | Error _ -> raise_err error_fmt line col in
  let rec parse_all need_rparen =
    match next_token () with
    | (line, col, Name "package") ->
        let n =
          get_tok string_tok
            (error_msg "String literal expected after 'package'") in
        let () =
          get_tok (const_tok LParen) (error_msg "'(' expected after string") in
        let subpkg =
          get_rule parse_all true
            (error_msg "Error in subpackage definition") line col in
        let rest = parse_all need_rparen
        in
          {
            pkg_defs = rest.pkg_defs;
            pkg_children = (n, subpkg) :: rest.pkg_children;
          }
    | (line, col, Name n) ->
        let (args, flav, value) =
          get_rule parse_properties ()
            (error_msg "Error in 'name = value' clause") line col in
        let rest = parse_all need_rparen in (* TODO: Check args *)
        let args' = List.sort compare (mk_set args) in
        let def =
          {
            def_var = n;
            def_flav = flav;
            def_preds = args';
            def_value = value;
          }
        in
          {
            pkg_defs = def :: rest.pkg_defs;
            pkg_children = rest.pkg_children;
          }
    | (line, col, Eof) ->
        (if need_rparen
         then
           raise_err
             (Printf.sprintf "Unexpected end of file in line %d position %d")
             line col
         else ();
         { pkg_defs = []; pkg_children = []; })
    | (line, col, RParen) ->
        (if not need_rparen
         then
           raise_err
             (Printf.sprintf "Unexpected end of file in line %d position %d")
             line col
         else ();
         { pkg_defs = []; pkg_children = []; })
    | (line, col, _) ->
        raise_err (error_msg "Expected 'name = value' clause") line col
  and parse_properties () =
    match next_token () with
    | (line, col, LParen) ->
        let arg1 = parse_argument () in
        let args = parse_arguments () in
        let flav = parse_flavour () in
        let s =
          get_tok string_tok (error_msg "Expected string constant after '='")
        in ((arg1 :: args), flav, s)
    | (line, col, Equal) ->
        let s =
          get_tok string_tok
            (error_msg "'=' must be followed by a string constant")
        in ([], `BaseDef, s)
    | (line, col, PlusEqual) ->
        let s =
          get_tok string_tok
            (error_msg "'+=' must be followed by a string constant")
        in ([], `Appendix, s)
    | (line, col, _) ->
        raise_err (error_msg "Expected a '=' or a '(arguments,...)=' clause")
          line col
  and parse_arguments () =
    match next_token () with
    | (line, col, Comma) ->
        let arg = parse_argument () in
        let args = parse_arguments () in arg :: args
    | (_, _, RParen) -> []
    | (line, col, _) ->
        raise_err (error_msg "Another predicate or a ')' expected") line col
  and parse_argument () =
    match next_token () with
    | (line, col, Name n) -> `Pred n
    | (line, col, Minus) ->
        let n = get_tok name_tok (error_msg "Name expected after '-'")
        in `NegPred n
    | (line, col, _) ->
        raise_err (error_msg "Name or -Name expected") line col
  and parse_flavour () =
    match next_token () with
    | (line, col, Equal) -> `BaseDef
    | (line, col, PlusEqual) -> `Appendix
    | (line, col, _) -> raise_err (error_msg "'+' or '+=' expected") line col in
  let rec check_defs p l =
    match l with
    | [] -> ()
    | def :: l' ->
        (List.iter
           (fun def' ->
              if
                (def.def_var = def'.def_var) &&
                  ((def.def_preds = def'.def_preds) &&
                     ((def.def_flav = `BaseDef) && (def'.def_flav = `BaseDef)))
              then
                (let prefix =
                   if p = "" then "" else "In subpackage " ^ (p ^ ": ") in
                 let args = string_of_preds def.def_preds
                 in
                   raise
                     (Error
                        (prefix ^
                           ("Double definition of '" ^
                              (def.def_var ^ (args ^ "'"))))))
              else ())
           l';
         check_defs p l') in
  let rec check_pkg p pkg =
    (check_defs p pkg.pkg_defs;
     let l = ref []
     in
       List.iter
         (fun (n, subpkg) ->
            let p' = if p = "" then n else p ^ ("." ^ n)
            in
              (if List.mem n !l
               then
                 raise
                   (Error ("Double definition for subpackage " ^ p'))
               else ();
               if String.contains n '.'
               then
                 raise
                   (Error
                      ("Subpackage name must not contain '.': \"" ^
                         (n ^ "\"")))
               else ();
               check_pkg p' subpkg;
               l := n :: !l))
         pkg.pkg_children)
  in
    try let pkg = parse_all false in (check_pkg "" pkg; pkg)
    with | Error "" -> raise (Error "Syntax Error")
  
let parse ch = parse_lexing (Lexing.from_channel ch)

let escape s = (* no Str available :-( *)
  let b = Buffer.create (String.length s)
  in
  (for k = 0 to (String.length s) - 1 do
     (match s.[k] with
      | '\\' -> Buffer.add_string b "\\\\"
      | '"' -> Buffer.add_string b "\\\""
      | c -> Buffer.add_char b c)
   done;
   Buffer.contents b)

let print_def f def =
  let format_pred = function | `Pred s -> s | `NegPred s -> "-" ^ s in
  fprintf f "%s%s %s \"%s\"\n" def.def_var
    (match def.def_preds with
     | [] -> ""
     | l -> "(" ^ ((String.concat "," (List.map format_pred l)) ^ ")"))
    (match def.def_flav with | `BaseDef -> "=" | `Appendix -> "+=")
    (escape def.def_value)


let rec print f pkg =
    (List.iter (print_def f) pkg.pkg_defs;
     List.iter
       (fun (name, child) ->
          (fprintf f "\npackage \"%s\" (\n" (escape name);
           print f child;
           fprintf f ")\n"))
       pkg.pkg_children)
  
let rec remove_dups l =
  (* FIXME: O(n^2) *)
  match l with
    x :: l' ->
      if List.mem x l' then remove_dups l' else x::remove_dups l'
  | [] -> []

let lookup_2 name predicate_list def =
  let fulfills actual_preds formal_preds =
    List.for_all
      (function
       | `Pred n -> List.mem n predicate_list
       | `NegPred n -> not (List.mem n predicate_list))
      formal_preds in
  let rec search_base best_n best_value l =
    match l with
    | [] -> if best_n >= 0 then best_value else raise Not_found
    | def :: l' ->
        if
          (name = def.def_var) &&
            ((def.def_flav = `BaseDef) &&
               ((fulfills predicate_list def.def_preds) &&
                  ((List.length def.def_preds) > best_n)))
        then search_base
               (List.length def.def_preds)
               (def.def_value, def.def_preds)
               l'
        else search_base best_n best_value l' in
  let rec search_appdx l =
    match l with
    | [] -> []
    | def :: l' ->
        if
          (name = def.def_var) &&
            ((def.def_flav = `Appendix) &&
               (fulfills predicate_list def.def_preds))
        then (def.def_value, def.def_preds) :: (search_appdx l')
        else search_appdx l' in
  let value_a, preds_a  = search_base (-1) ("",[]) def in
  let additions = search_appdx def in
  let values_b = List.map fst additions in
  let preds_b = List.flatten (List.map snd additions) in
  let value = String.concat " " (value_a :: values_b) in
  let preds = remove_dups (preds_a @ preds_b) in
  (value, preds)
  
let lookup name predicate_list def =
  fst(lookup_2 name predicate_list def)

let predicate_exists p defs =
  List.exists
    (fun def ->
       List.exists (function | `Pred n -> n = p | `NegPred n -> n = p)
         def.def_preds)
    defs
  

