(* Yoann Padioleau
 *
 * Copyright (C) 2010, University of Copenhagen DIKU and INRIA.
 * Copyright (C) 2006, 2007, 2008 Ecole des Mines de Nantes
 *
 * 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.
 *)

open Common


(*****************************************************************************)
(* Wrappers *)
(*****************************************************************************)
let pr2_err, pr2_once = Common.mk_pr2_wrappers Flag_parsing_c.verbose_parsing

(*****************************************************************************)
(* Consistency checking *)
(*****************************************************************************)

(* todo:
 *  could check that an ident has always the same class, be it a typedef
 *  (but sometimes do 'acpi_val acpi_val;'), an ident, a TMacroStatement,
 *  etc.
 *)

type class_ident =
  | CIdent (* can be var, func, field, tag, enum constant *)
  | CTypedef

let str_of_class_ident = function
  | CIdent -> "Ident"
  | CTypedef -> "Typedef"

(*
  | CMacro
  | CMacroString
  | CMacroStmt
  | CMacroDecl
  | CMacroIterator
  | CAttr

(* but take care that must still be able to use '=' *)
type context = InFunction | InEnum | InStruct | InInitializer | InParams
type class_token =
  | CIdent of class_ident

  | CComment
  | CSpace
  | CCommentCpp of cppkind
  | CCommentMisc
  | CCppDirective

  | COPar
  | CCPar
  | COBrace
  | CCBrace

  | CSymbol
  | CReservedKwd (type | decl | qualif | flow | misc | attr)
*)

let ident_to_typename ident : Ast_c.fullType =
  Ast_c.mk_ty (Ast_c.NamedType  (ident, Ast_c.noTypedefDef())) Ast_c.noii


(* parse_typedef_fix4 *)
let consistency_checking2 xs =

  (* first phase, gather data *)
  let stat = Hashtbl.create 101 in

  (* default value for hash *)
  let v1 () = Hashtbl.create 101 in
  let v2 () = ref 0 in

  let bigf = { Visitor_c.default_visitor_c with

    Visitor_c.kexpr = (fun (k,bigf) x ->
      match Ast_c.unwrap_expr x with
      | Ast_c.Ident (id) ->
          let s = Ast_c.str_of_name id in
          stat +>
            Common.hfind_default s v1 +> Common.hfind_default CIdent v2 +>
            (fun aref -> incr aref)

      | _ -> k x
    );
    Visitor_c.ktype = (fun (k,bigf) t ->
      match Ast_c.unwrap_typeC t with
      | Ast_c.NamedType (name,_typ) ->
          let s = Ast_c.str_of_name name in
          stat +>
            Common.hfind_default s v1 +> Common.hfind_default CTypedef v2 +>
            (fun aref -> incr aref)

      | _ -> k t
    );
  }
  in
  xs +> List.iter (fun (p) -> Visitor_c.vk_toplevel bigf p);


  let ident_to_type = ref [] in


  (* second phase, analyze data *)
  stat +> Hashtbl.iter (fun k v ->
    let xs = Common.hash_to_list v in
    if List.length xs >= 2
    then begin
      pr2_err ("TYPEDEF CONFLICT:" ^ k);
      let sorted = xs +> List.sort (fun (ka,va) (kb,vb) ->
        if !va = !vb then
          (match ka, kb with
          | CTypedef, _ -> 1 (* first is smaller *)
          | _, CTypedef -> -1
          | _ -> 0
          )
        else compare !va !vb
      ) in
      let sorted = List.rev sorted in
      match sorted with
      | [CTypedef, i1;CIdent, i2] ->
          pr2_err ("transforming some ident into a typedef");
          push2 k ident_to_type;
      | [CIdent, i1;CTypedef, i2] ->
          pr2_err ("TODO:typedef now used as an identifier");
      | _ ->
          pr2_err ("TODO:other transforming?");

    end
  );

  (* third phase, update ast.
   * todo? but normally should try to handle correctly scope ? maybe sometime
   * sizeof(id) and even if id was for a long time an identifier, maybe
   * a few time, because of the scope it's actually really a type.
   *)
  if (!ident_to_type = [])
  then xs
  else
    let bigf = { Visitor_c.default_visitor_c_s with
      Visitor_c.kdefineval_s = (fun (k,bigf) x ->
        match x with
        | Ast_c.DefineExpr e ->
            (match Ast_c.unwrap_expr e with
            | Ast_c.Ident (ident)  ->
                let s = Ast_c.str_of_name ident in
                if List.mem s !ident_to_type
                then
                  let t = ident_to_typename ident in
                  Ast_c.DefineType t
                else k x
            | _ -> k x
            )
        | _ -> k x
      );
      Visitor_c.kexpr_s = (fun (k, bigf) x ->
        match Ast_c.get_e_and_ii x with
        | (Ast_c.SizeOfExpr e, tref), isizeof ->
            let i1 = tuple_of_list1 isizeof in
            (match Ast_c.get_e_and_ii e with
            | (Ast_c.ParenExpr e, _), iiparen ->
                let (i2, i3) = tuple_of_list2 iiparen in
                (match Ast_c.get_e_and_ii e with
                | (Ast_c.Ident (ident), _), _ii  ->

                    let s = Ast_c.str_of_name ident in
                    if List.mem s !ident_to_type
                    then
                      let t = ident_to_typename ident in
                      (Ast_c.SizeOfType t, tref),[i1;i2;i3]
                    else  k x
                | _ -> k x
                )
            | _ -> k x
            )
        | _ -> k x
      );
    } in
    xs +> List.map (fun (p) ->
      Visitor_c.vk_toplevel_s bigf p
    )


let consistency_checking a  =
  Common.profile_code "C consistencycheck" (fun () -> consistency_checking2 a)
