(*
 * Copyright (C) 2010, University of Copenhagen DIKU and INRIA.
 * Copyright (C) 2006, 2007 Julia Lawall
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License (GPL)
 * version 2 as published by the Free Software Foundation.
 *
 * This program 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
 * file license.txt for more details.
 *
 * This file was part of Coccinelle.
 *)

module Ast = Ast_cocci
module V = Visitor_ast

let error x s =
  failwith
    (Printf.sprintf "unparse_hrule: line: %d, %s" (Ast.get_line x) s)

let names = ref ([] : (string * int ref) list)

let started_files = ref ([] : (string * bool) list)
let typedefs = ref ([] : (string * string list ref) list)
let current_outfile = ref ""

let prefix = "_cocci_"

(* ----------------------------------------------------------------------- *)
(* Create rule to check for header include *)

let print_header_rule pr srcfile =
  match Str.split (Str.regexp "/") srcfile with
    [x] ->
      pr "@header@\n@@\n\n#include \"";
      pr x; pr "\"\n\n"; true
  | l ->
      let rec loop = function
	  [] -> false
	| [x] ->
	    pr "@header@\n@@\n\n#include \"";
	    pr x; pr "\"\n\n"; true
	| "include"::(x::xs) ->
	    pr "@header@\n@@\n\n#include <";
	    let x =
	      if Str.string_match (Str.regexp "asm-") x 0 then "asm" else x in
	    pr (String.concat "/" (x::xs));
	    pr ">\n\n"; true
	| x::xs -> loop xs in
      loop l

(* ----------------------------------------------------------------------- *)
(* Print check that we are not in the defining function *)

let print_check_rule pr function_name function_name_count header_req =
  (if header_req
  then pr (Printf.sprintf "@same_%s depends on header@\n" function_name_count)
  else pr (Printf.sprintf "@same_%s@\n" function_name_count));
  pr "position p;\n";
  pr "@@\n\n";
  pr function_name; pr "@p(...) { ... }\n\n"

(* ----------------------------------------------------------------------- *)
(* get parameters of the matched function *)

let rec env_lookup fn = function
    [] -> failwith "no binding"
  | (nm,vl)::rest when fn nm -> vl
  | _::rest -> env_lookup fn rest

let get_paramst env =
  let argname = ref ("","") in
  let fn ((_,nm) as name) =
    if nm = "ARGS"
    then (argname := name; true)
    else false in
  match env_lookup fn env with
    Ast_c.MetaParamListVal(paramst) -> (paramst,!argname)
  | _ -> failwith "not possible"

let get_function_name rule env =
  let donothing r k e = k e in
  let option_default = [] in
  let bind = Common.union_set in
  let do_any_list_list r any_list_list =
    List.fold_left
      (List.fold_left
	 (function prev -> function cur ->
	   bind (r.V.combiner_anything cur) prev))
      [] any_list_list in
  let mcode r mc =
    match Ast.get_mcodekind mc with
      Ast.MINUS(_,_,_,any_list_list) ->
	(match any_list_list with
	  Ast.NOREPLACEMENT -> []
	| Ast.REPLACEMENT(any_list_list,_) ->
	    do_any_list_list r any_list_list)
    | Ast.CONTEXT(_,any_befaft) ->
	(match any_befaft with
	  Ast.BEFORE(any_list_list,_) | Ast.AFTER(any_list_list,_) ->
	    do_any_list_list r any_list_list
	| Ast.BEFOREAFTER(ba,aa,_) ->
	    bind (do_any_list_list r ba) (do_any_list_list r aa)
	| Ast.NOTHING -> [])
    | Ast.PLUS _ -> [] in
  let expression r k e =
    bind (k e)
    (match Ast.unwrap e with
      Ast.FunCall(fn,lp,args,rp) ->
	(match Ast.undots args with
	  [e] ->
	    (match Ast.unwrap e with
	      Ast.MetaExprList(nm,_,_,_) ->
		(match (Ast.unwrap_mcode nm,Ast.get_mcodekind nm) with
		  ((_,"ARGS"), Ast.PLUS _) ->
		    (match Ast.unwrap fn with
		      Ast.Ident(id) ->
			(match Ast.unwrap id with
			  Ast.MetaId(nm,_,_,_)
			| Ast.MetaFunc(nm,_,_,_)
			| Ast.MetaLocalFunc(nm,_,_,_) ->
			    [Ast.unwrap_mcode nm]
			| _ -> [])
		    | _ -> [])
		| _ -> [])
	    | _ -> [])
	| _ -> [])
    | _ -> []) in
  let names =
    (V.combiner bind option_default
      mcode mcode mcode mcode mcode mcode mcode mcode mcode mcode mcode mcode
      donothing donothing donothing donothing donothing
      donothing expression donothing donothing donothing donothing donothing
      donothing donothing donothing donothing donothing).V.combiner_top_level
      rule in
  match names with
    [name] ->
      (match env_lookup (function nm -> nm = name) env with
	Ast_c.MetaIdVal(s,_) | Ast_c.MetaFuncVal(s)
      | Ast_c.MetaLocalFuncVal(s) -> s
      |	_ -> error rule "not possible")
  | _ -> error rule "inconsistent rule generation"

(* ----------------------------------------------------------------------- *)
(* Print metavariable declarations *)

let rec print_typedef pr = function
    (Ast_c.TypeName(name,_),_) ->
      let s = Ast_c.str_of_name name in
      let typedefs =
	try List.assoc !current_outfile !typedefs
	with Not_found ->
	  let td = ref [] in
	  typedefs := (!current_outfile,td)::!typedefs;
	  td in
      if not (List.mem s !typedefs)
      then (typedefs := s::!typedefs; pr "typedef "; pr s; pr ";\n")
  | (Ast_c.Pointer(_,ty),_) -> print_typedef pr ty
  | _ -> ()

let rewrap_str s ii =
  {ii with Ast_c.pinfo =
    (match ii.Ast_c.pinfo with
      Ast_c.OriginTok pi ->
	Ast_c.OriginTok { pi with Common.str = s;}
    | Ast_c.ExpandedTok (pi,vpi) ->
	Ast_c.ExpandedTok ({ pi with Common.str = s;},vpi)
    | Ast_c.FakeTok (_,vpi) -> Ast_c.FakeTok (s,vpi)
    | Ast_c.AbstractLineTok pi ->
	Ast_c.AbstractLineTok { pi with Common.str = s;})}

let rewrap_prefix_name prefix name =
  match name with
  | Ast_c.RegularName (s, iiname) ->
      let iis = Common.tuple_of_list1 iiname in
      let iis' = rewrap_str (prefix^s) iis in
      Ast_c.RegularName (prefix ^ s, [iis'])
  | Ast_c.CppConcatenatedName _ | Ast_c.CppVariadicName _
  | Ast_c.CppIdentBuilder _
      -> raise Common.Todo


let print_metavar pr = function
  | {Ast_c.p_namei = Some name;
     p_type = (_,(Ast_c.Pointer(_,(Ast_c.BaseType(Ast_c.Void),_)),_));
    }
    ->
      let param = Ast_c.str_of_name name in
      pr ("expression "^prefix); pr param
  | ({Ast_c.p_namei = Some name; p_type = (_,ty)} : Ast_c.parameterType) ->

      let name' = rewrap_prefix_name prefix name in

      print_typedef pr ty;

      Pretty_print_c.pp_param_gen
	(function x ->
	  let str = Ast_c.str_of_info x in
	  if not (List.mem str ["const";"volatile"])
	  then pr str)
	(function _ -> pr " ")
        {Ast_c.p_register = (false,[]);
         p_namei = Some name';
         p_type = (({Ast_c.const = false; Ast_c.volatile = false},[]),ty)
        }
  | _ -> failwith "function must have named parameters"

let make_exp = function
    ({Ast_c.p_namei = Some name; p_type = ty}, comma_ii) ->
      let no_info = (None,Ast_c.NotTest) in

      let name' = rewrap_prefix_name prefix name in

      let exp =
	((Ast_c.Ident (name'),ref no_info),Ast_c.noii) in
      (name,(Common.Left exp,comma_ii))
  | _ -> failwith "bad parameter"

let print_extra_typedefs pr env =
  let bigf =
    { Visitor_c.default_visitor_c with
      Visitor_c.ktype = (fun (k, bigf) ty ->
	match ty with
	  (_,((Ast_c.TypeName(_,_),_) as ty)) -> print_typedef pr ty
	| _ -> k ty) } in
  List.iter
    (function (_,vl) ->
      match vl with
	Ast_c.MetaIdVal(_) | Ast_c.MetaFuncVal(_)
      | Ast_c.MetaLocalFuncVal(_) -> ()
      | Ast_c.MetaExprVal(exp,_) -> Visitor_c.vk_expr bigf exp
      | Ast_c.MetaExprListVal(args) -> Visitor_c.vk_argument_list bigf args
      | Ast_c.MetaParamVal(param) -> Visitor_c.vk_param bigf param
      | Ast_c.MetaParamListVal(params) -> Visitor_c.vk_param_list bigf params

      | Ast_c.MetaTypeVal(ty) -> Visitor_c.vk_type bigf ty
      | Ast_c.MetaInitVal(ty) -> Visitor_c.vk_ini bigf ty
      | Ast_c.MetaInitListVal(ty) -> Visitor_c.vk_ini_list bigf ty
      | Ast_c.MetaDeclVal(decl) -> Visitor_c.vk_decl bigf decl
      | Ast_c.MetaFieldVal(field) -> Visitor_c.vk_struct_field bigf field
      | Ast_c.MetaFieldListVal(fields) -> Visitor_c.vk_struct_fields bigf fields
      | Ast_c.MetaStmtVal(stm) -> Visitor_c.vk_statement bigf stm
      | Ast_c.MetaPosVal _ | Ast_c.MetaPosValList _
      | Ast_c.MetaListlenVal _ -> ())
    env

let rename argids env =
  let argenv = List.map (function name ->
    let arg = Ast_c.str_of_name name in
    (arg,prefix^arg)
  ) argids in
  let lookup x = try List.assoc x argenv with Not_found -> x in
  let bigf =
    { Visitor_c.default_visitor_c_s with
    Visitor_c.kexpr_s = (fun (k,bigf) e ->
      match e with
	((Ast_c.Ident (name), info), []) ->

          (* pad: assert is_regular_ident ? *)
          let s = Ast_c.str_of_name name in
          let ii = Ast_c.info_of_name name in
	  let new_name = lookup s in
          let new_id = Ast_c.RegularName (new_name, [rewrap_str new_name ii]) in
	  ((Ast_c.Ident (new_id), info), Ast_c.noii)
      | _ -> k e) } in
  List.map
    (function (x,vl) ->
      (x,
       match vl with
	 Ast_c.MetaIdVal(_) | Ast_c.MetaFuncVal(_)
       | Ast_c.MetaLocalFuncVal(_) -> vl
       | Ast_c.MetaExprVal(exp,c) ->
	   Ast_c.MetaExprVal(Visitor_c.vk_expr_s bigf exp,c)
       | Ast_c.MetaExprListVal(args) ->
	   Ast_c.MetaExprListVal(Visitor_c.vk_arguments_s bigf args)
       | Ast_c.MetaParamVal(param) ->
	   Ast_c.MetaParamVal(Visitor_c.vk_param_s bigf param)
       | Ast_c.MetaParamListVal(params) ->
	   Ast_c.MetaParamListVal(Visitor_c.vk_params_s bigf params)

       | Ast_c.MetaTypeVal(ty) ->
	   Ast_c.MetaTypeVal(Visitor_c.vk_type_s bigf ty)
       | Ast_c.MetaInitVal(ini) ->
	   Ast_c.MetaInitVal(Visitor_c.vk_ini_s bigf ini)
       | Ast_c.MetaInitListVal(ini) ->
	   Ast_c.MetaInitListVal(Visitor_c.vk_inis_s bigf ini)
       | Ast_c.MetaDeclVal(stm) ->
	   Ast_c.MetaDeclVal(Visitor_c.vk_decl_s bigf stm)
       | Ast_c.MetaFieldVal(stm) ->
	   Ast_c.MetaFieldVal(Visitor_c.vk_struct_field_s bigf stm)
       | Ast_c.MetaFieldListVal(stm) ->
	   Ast_c.MetaFieldListVal(Visitor_c.vk_struct_fields_s bigf stm)
       | Ast_c.MetaStmtVal(stm) ->
	   Ast_c.MetaStmtVal(Visitor_c.vk_statement_s bigf stm)
       | Ast_c.MetaPosVal _ | Ast_c.MetaPosValList _
       | Ast_c.MetaListlenVal _ -> vl))
    env

let print_one_type pr env = function
    (Type_cocci.MetaType(name,keep,inherited)) as ty ->
      (try
	match List.assoc name env with
	  Ast_c.MetaTypeVal ty ->
	    Pretty_print_c.pp_type_gen
	      (function x -> pr (Ast_c.str_of_info x))
	      (function _ -> pr " ")
	      ty
        | _ -> failwith "impossible"
      with Not_found -> pr (Type_cocci.type2c ty))
  | ty -> pr (Type_cocci.type2c ty)

let print_types pr env = function
    None -> ()
  | Some [ty] -> print_one_type pr env ty
  | Some types ->
      pr "{";
      Common.print_between (function _ -> pr ", ") (print_one_type pr env)
	types;
      pr "}"

let pp_len pr len =
  let pp_name (_,n) = pr n in
  match len with
    Ast.AnyLen -> ()
  | Ast.MetaLen len -> pr "["; pp_name len; pr "]"
  | Ast.CstLen len -> pr "["; pr (string_of_int len); pr "]"

let pp_meta_decl pr env decl =
  let no_arity = function Ast.NONE -> () | _ -> failwith "no arity allowed" in
  let pp_name (_,n) = pr n in
  match decl with
    Ast.MetaMetaDecl(ar, name) ->
      (* ignore virtual *)
      no_arity ar; pr "metavariable "; pp_name name; pr ";\n"
  | Ast.MetaIdDecl(ar, name) ->
      (* ignore virtual *)
      no_arity ar; pr "identifier "; pp_name name; pr ";\n"
  | Ast.MetaFreshIdDecl(name, Ast.NoVal) ->
      pr "fresh identifier "; pp_name name; pr ";\n"
  | Ast.MetaFreshIdDecl(name, Ast.StringSeed x) ->
      pr "fresh identifier "; pp_name name; pr " = \""; pr x; pr "\";\n"
  | Ast.MetaFreshIdDecl(name, Ast.ListSeed x) ->
      failwith "unparse_hrule: not supported"
  | Ast.MetaTypeDecl(ar, name) ->
      no_arity ar; pr "type "; pp_name name; pr ";\n"
  | Ast.MetaInitDecl(ar, name) ->
      no_arity ar; pr "initialiser "; pp_name name; pr ";\n"
  | Ast.MetaInitListDecl(ar, name, len) ->
      no_arity ar; pr "initialiser list "; pp_name name; pp_len pr len; pr ";\n"
  | Ast.MetaListlenDecl(name) -> ()
  | Ast.MetaParamDecl(ar, name) ->
      no_arity ar; pr "parameter "; pp_name name; pr ";\n"
  | Ast.MetaParamListDecl(ar, name, len) ->
      no_arity ar; pr "parameter list "; pp_name name; pp_len pr len; pr ";\n"
  | Ast.MetaConstDecl(ar, name, types) ->
      no_arity ar; pr "constant "; print_types pr env types;
      pp_name name; pr ";\n"
  | Ast.MetaErrDecl(ar, name) ->
      no_arity ar; pr "error "; pp_name name; pr ";\n"
  | Ast.MetaExpDecl(ar, name, None) ->
      no_arity ar; pr "expression "; pp_name name; pr ";\n"
  | Ast.MetaExpDecl(ar, name, types) ->
      no_arity ar; print_types pr env types; pp_name name; pr ";\n"
  | Ast.MetaIdExpDecl(ar, name, types) ->
      no_arity ar; pr "idexpression ";
      print_types pr env types; pp_name name; pr ";\n"
  | Ast.MetaLocalIdExpDecl(ar, name, types) ->
      no_arity ar; pr "local idexpression ";
      print_types pr env types; pp_name name; pr ";\n"
  | Ast.MetaExpListDecl(ar, name, len) ->
      no_arity ar; pr "parameter list "; pp_name name; pp_len pr len; pr ";\n"
  | Ast.MetaDeclDecl(ar, name) ->
      no_arity ar; pr "declaration "; pp_name name; pr ";\n"
  | Ast.MetaFieldDecl(ar, name) ->
      no_arity ar; pr "field "; pp_name name; pr ";\n"
  | Ast.MetaFieldListDecl(ar, name, len) ->
      no_arity ar; pr "field list "; pp_name name; pp_len pr len; pr ";\n"
  | Ast.MetaStmDecl(ar, name) ->
      no_arity ar; pr "statement "; pp_name name; pr ";\n"
  | Ast.MetaStmListDecl(ar, name) ->
      no_arity ar; pr "statement list "; pp_name name; pr ";\n"
  | Ast.MetaFuncDecl(ar, name) ->
      no_arity ar; pr "function "; pp_name name; pr ";\n"
  | Ast.MetaLocalFuncDecl(ar, name) ->
      no_arity ar; pr "local function "; pp_name name; pr ";\n"
  | Ast.MetaPosDecl(ar, name) ->
      no_arity ar; pr "position "; pp_name name; pr ";\n"
  | Ast.MetaAnalysisDecl(code, name) ->
      pr "analysis"; pr code; pr " "; pp_name name; pr ";\n"
  | Ast.MetaDeclarerDecl(ar, name) ->
      no_arity ar; pr "declarer "; pp_name name; pr ";\n"
  | Ast.MetaIteratorDecl(ar, name) ->
      no_arity ar; pr "iterator "; pp_name name; pr ";\n"

let print_metavariables pr local_metas paramst env header_req function_name =
  (if header_req
  then pr "@depends on header@\n"
  else pr "@@\n");
  pr (Printf.sprintf "position _p!=same_%s.p;\n" function_name);
  pr "identifier _f;\n";
  let rec loop = function
      [] | [{Ast_c.p_type =(_,(Ast_c.BaseType(Ast_c.Void),_))},_] -> []
    | ((first,_) as f)::rest ->
	print_metavar pr first; pr ";\n";
	(make_exp f) :: loop rest in
  let args = loop paramst in
  print_extra_typedefs pr env;
  List.iter (pp_meta_decl pr env) local_metas;
  pr "@@\n\n";
  args

(* ----------------------------------------------------------------------- *)
(* print_start/end *)

let print_start pr =
  pr "_f@_p(...) { <+...\n"

let print_end pr =
  pr "\n...+> }\n"

(* ----------------------------------------------------------------------- *)
(* Print call to the defined function *)

let print_param_name pr = function
    {Ast_c.p_namei = Some name} -> pr (Ast_c.str_of_name name)
  | _ -> failwith "function must have named parameters"

let pp_def_gen pr defn isexp =
  let {Ast_c.f_name = name; f_type = (_, (paramst, (b, iib))); } = defn in
  pr (Ast_c.str_of_name name); pr "(";
  (if b then failwith "not handling variable argument functions");
  (match paramst with
    [] | [{Ast_c.p_type = (_,(Ast_c.BaseType(Ast_c.Void),_))},_] -> ()
  | (first,_)::rest ->
      print_param_name pr first;
      List.iter (function (x,_) -> pr ", "; print_param_name pr x) rest);
  pr ")"; if not isexp then pr ";"

(* ----------------------------------------------------------------------- *)
(* Entry point *)

let pp_rule local_metas ast env srcfile =
  let (paramst,args_name) = get_paramst env in
  (* get rule information *)
  let (rule,printable) =
    match ast with
      Ast.CocciRule(_,_,[body],_,_) -> (* could extend to use attributes *)
	(body,
	 match Ast.unwrap body with
	   Ast.NONDECL(s) -> [[Ast.StatementTag s]]
	 | Ast.CODE(ss) -> [[Ast.StmtDotsTag ss]]
	 | _ -> error body "bad rule body")
    | _ -> failwith "bad rule" in
  (* create the output file *)
  let outdir =
    match !Flag.make_hrule with
      Some outdir -> outdir
    | None -> error rule "not possible" in
  let function_name = get_function_name rule env in
  let function_name_count =
    try
      let cell = List.assoc function_name !names in
      let ct = !cell in
      cell := ct + 1;
      function_name ^ (string_of_int ct)
    with Not_found ->
      let cell = ref 1 in
      names := (function_name,cell) :: !names;
      function_name in
  let outfile = outdir ^ "/" ^
    (if !Flag.hrule_per_file
    then Filename.chop_extension (Filename.basename srcfile)
    else function_name_count) in
  let escape_re = Str.regexp_string "/" in
  let dir = if !Flag.dir = "" then Filename.dirname srcfile else !Flag.dir in
  let outdirfile = Str.global_replace escape_re "_"dir in
  let outfile = outfile ^ outdirfile ^ ".cocci" in
  let saved_header_req =
    try let res = List.assoc outfile !started_files in Some res
    with Not_found -> None in
  current_outfile := outfile;
  Common.with_open_outfile_append outfile (fun (pr,chan) ->
    let header_req =
      match saved_header_req with
	Some x -> x
      |	None ->
	  let res = print_header_rule pr srcfile in
	  started_files := (outfile,res)::!started_files;
	  res in
    print_check_rule pr function_name function_name_count header_req;
    let args =
      print_metavariables pr local_metas paramst env header_req
	function_name_count in
    let (argids,args) = List.split args in
    let env = rename argids env in
    let env = (args_name,Ast_c.MetaExprListVal args)::env in
    print_start pr;
    (* for printing C tokens *)
    let pr_c info =
      match Ast_c.pinfo_of_info info with
	Ast_c.AbstractLineTok _ -> pr (Ast_c.str_of_info info)
      | Ast_c.FakeTok (s,_) -> pr s
      |	_ ->
	  Printf.printf "line: %s\n" (Dumper.dump info);
	  error rule "not an abstract line" in
    let pr_space _ = pr " " in
    Unparse_cocci.pp_list_list_any
      ([env], (fun s _ _ _ _ -> pr s), pr_c, pr_space, pr_space, pr,
       (fun _ _ -> ()), (function _ -> ()), (function _ -> ()),
       (function _ -> ()))
      true printable Unparse_cocci.InPlace;
    print_end pr;
    pr "\n")
