(*
 * Copyright (c) 2016 - present Facebook, Inc.
 * All rights reserved.
 *
 * This source code is licensed under the BSD style license found in the
 * LICENSE file in the root directory of this source tree. An additional grant
 * of patent rights can be found in the PATENTS file in the same directory.
 *)

open! Utils

open CFrontend_utils


let get_ivar_attributes ivar_decl =
  let open Clang_ast_t in
  match ivar_decl with
  | ObjCIvarDecl (ivar_decl_info, _, _, _, _) ->
      (match Ast_utils.get_property_of_ivar ivar_decl_info.Clang_ast_t.di_pointer with
       | Some ObjCPropertyDecl (_, _, obj_c_property_decl_info) ->
           obj_c_property_decl_info.Clang_ast_t.opdi_property_attributes
       | _ -> [])
  | _ -> []

(* list of cxx references captured by stmt *)
let captured_variables_cxx_ref stmt =
  let capture_var_is_cxx_ref reference_captured_vars captured_var =
    let decl_ref_opt = captured_var.Clang_ast_t.bcv_variable in
    match Ast_utils.get_decl_opt_with_decl_ref decl_ref_opt with
    | Some VarDecl (_, named_decl_info, qual_type, _)
    | Some ParmVarDecl (_, named_decl_info, qual_type, _)
    | Some ImplicitParamDecl (_, named_decl_info, qual_type, _) ->
        (match Ast_utils.get_desugared_type qual_type.Clang_ast_t.qt_type_ptr with
         | Some RValueReferenceType _ | Some LValueReferenceType _ ->
             named_decl_info::reference_captured_vars
         | _ -> reference_captured_vars)
    | _ -> reference_captured_vars in
  let captured_vars = match stmt with
    | Clang_ast_t.BlockExpr (_, _ , _, Clang_ast_t.BlockDecl (_, bdi)) ->
        bdi.Clang_ast_t.bdi_captured_variables
    | _ -> [] in
  IList.fold_left capture_var_is_cxx_ref [] captured_vars

let var_descs_name stmt =
  let capt_refs = captured_variables_cxx_ref stmt in
  let var_desc vars var_named_decl_info =
    vars ^ "'" ^ var_named_decl_info.Clang_ast_t.ni_name ^ "'" in
  IList.fold_left var_desc "" capt_refs


type t = string * string list (* (name, [param1,...,paramK]) *)

let is_declaration_kind decl s =
  Clang_ast_proj.get_decl_kind_string decl = s

let is_statement_kind stmt s =
  Clang_ast_proj.get_stmt_kind_string stmt = s

(* st |= call_method(m) *)
let call_method m st =
  match st with
  | Clang_ast_t.ObjCMessageExpr (_, _, _, omei) -> omei.omei_selector = m
  | _ -> false

let property_name_contains_word decl word =
  match Clang_ast_proj.get_named_decl_tuple decl with
  | Some (_, n) -> let pname = n.Clang_ast_t.ni_name in
      let rexp = Str.regexp_string_case_fold word in
      (try
         Str.search_forward rexp pname 0 >= 0
       with Not_found -> false)
  | _ -> false

let is_objc_extension = General_utils.is_objc_extension

let is_syntactically_global_var decl =
  Ast_utils.is_syntactically_global_var decl

let is_const_expr_var decl =
  Ast_utils.is_const_expr_var decl

let decl_ref_is_in names st =
  match st with
  | Clang_ast_t.DeclRefExpr (_, _, _, drti) ->
      (match drti.drti_decl_ref with
       | Some dr -> let ndi, _, _ = CFrontend_utils.Ast_utils.get_info_from_decl_ref dr in
           IList.exists (fun n -> n = ndi.ni_name) names
       | _ -> false)
  | _ -> false

let call_function_named st names =
  Ast_utils.exists_eventually_st decl_ref_is_in names st

let is_strong_property decl =
  match decl with
  | Clang_ast_t.ObjCPropertyDecl (_, _, pdi) ->
      ObjcProperty_decl.is_strong_property pdi
  | _ -> false

let is_assign_property decl =
  match decl with
  | Clang_ast_t.ObjCPropertyDecl (_, _, pdi) ->
      ObjcProperty_decl.is_assign_property pdi
  | _ -> false

let is_property_pointer_type decl =
  match decl with
  | Clang_ast_t.ObjCPropertyDecl (_, _, pdi) ->
      let type_ptr = pdi.opdi_type_ptr in
      let raw_ptr = Clang_ast_types.type_ptr_to_clang_pointer type_ptr in
      (match Clang_ast_main.PointerMap.find raw_ptr !CFrontend_config.pointer_type_index with
       | MemberPointerType _ | ObjCObjectPointerType _ | BlockPointerType _ -> true
       | TypedefType (_, tti) ->
           (Ast_utils.name_of_typedef_type_info tti) = CFrontend_config.id_cl
       | exception Not_found -> false
       | _ -> false)
  | _ -> false

let context_in_synchronized_block context =
  context.CLintersContext.in_synchronized_block

(* checks if ivar is defined among a set of fields and if it is atomic *)
let is_ivar_atomic stmt =
  match stmt with
  | Clang_ast_t.ObjCIvarRefExpr (_, _, _, irei) ->
      let dr_ref = irei.Clang_ast_t.ovrei_decl_ref in
      let ivar_pointer = dr_ref.Clang_ast_t.dr_decl_pointer in
      (match Ast_utils.get_decl ivar_pointer with
       | Some d ->
           let attributes = get_ivar_attributes d in
           IList.exists (Ast_utils.property_attribute_eq `Atomic) attributes
       | _ -> false)
  | _ -> false

let is_method_property_accessor_of_ivar stmt context =
  let open Clang_ast_t in
  match stmt with
  | ObjCIvarRefExpr (_, _, _, irei) ->
      let dr_ref = irei.Clang_ast_t.ovrei_decl_ref in
      let ivar_pointer = dr_ref.Clang_ast_t.dr_decl_pointer in
      (match context.CLintersContext.current_method with
       | Some ObjCMethodDecl (_, _, mdi) ->
           if mdi.omdi_is_property_accessor then
             let property_opt = mdi.omdi_property_decl in
             match Ast_utils.get_decl_opt_with_decl_ref property_opt with
             | Some ObjCPropertyDecl (_, _, pdi) ->
                 (match pdi.opdi_ivar_decl with
                  | Some decl_ref -> decl_ref.dr_decl_pointer = ivar_pointer
                  | None -> false)
             | _ -> false
           else false
       | _ -> false)
  | _ -> false

let is_objc_constructor context =
  match context.CLintersContext.current_method with
  | Some method_decl ->
      let method_name = (match Clang_ast_proj.get_named_decl_tuple method_decl with
          | Some (_, mnd) -> mnd.Clang_ast_t.ni_name
          | _ -> "") in
      Procname.is_objc_constructor method_name
  | _ -> false


let is_objc_dealloc context =
  match context.CLintersContext.current_method with
  | Some method_decl ->
      let method_name = (match Clang_ast_proj.get_named_decl_tuple method_decl with
          | Some (_, mnd) -> mnd.Clang_ast_t.ni_name
          | _ -> "") in
      Procname.is_objc_dealloc method_name
  | _ -> false

let captures_cxx_references stmt =
  IList.length (captured_variables_cxx_ref stmt) > 0

let is_binop_with_kind stmt str_kind =
  let kind = match str_kind with
    | "EQ" -> `EQ
    | "NE" -> `NE
    | _ -> failwith ("Kind " ^ str_kind ^ " is invalid or not yet supported") in
  match stmt with
  | Clang_ast_t.BinaryOperator (_, _, _, boi) when boi.boi_kind = kind -> true
  | _ -> false

let is_unop_with_kind stmt str_kind =
  let kind = match str_kind with
    | "LNot" -> `LNot
    | _ -> failwith ("Kind " ^ str_kind ^ " is invalid or not yet supported") in
  match stmt with
  | Clang_ast_t.UnaryOperator (_, _, _, uoi) when uoi.uoi_kind = kind -> true
  | _ -> false

let is_stmt stmt stmt_name =
  stmt_name = Clang_ast_proj.get_stmt_kind_string stmt

let isa stmt classname =
  match Clang_ast_proj.get_expr_tuple stmt with
  | Some (_, _, expr_info) ->
      let typ = CFrontend_utils.Ast_utils.get_desugared_type expr_info.ei_type_ptr in
      CFrontend_utils.Ast_utils.is_ptr_to_objc_class typ classname
  | _ -> false
