(*
 * Copyright (c) 2017-present, Facebook, Inc.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 *)

open! IStd
open AbsLoc
module F = Format

module BoTrace = struct
  type elem =
    | ArrAccess of Location.t
    | ArrDecl of Location.t
    | Assign of Location.t
    | Call of Location.t
    | Return of Location.t
    | SymAssign of Loc.t * Location.t
    | UnknownFrom of Typ.Procname.t * Location.t
  [@@deriving compare]

  type t = {length: int; trace: elem list} [@@deriving compare]

  let singleton elem = {length= 1; trace= [elem]}

  let add_elem elem t = {length= t.length + 1; trace= elem :: t.trace}

  let add_elem_last elem t = {length= t.length + 1; trace= t.trace @ [elem]}

  let append x y = {length= x.length + y.length; trace= x.trace @ y.trace}

  let pp_elem : F.formatter -> elem -> unit =
   fun fmt elem ->
    match elem with
    | ArrAccess location ->
        F.fprintf fmt "ArrAccess (%a)" Location.pp_file_pos location
    | ArrDecl location ->
        F.fprintf fmt "ArrDecl (%a)" Location.pp_file_pos location
    | Assign location ->
        F.fprintf fmt "Assign (%a)" Location.pp_file_pos location
    | Call location ->
        F.fprintf fmt "Call (%a)" Location.pp_file_pos location
    | Return location ->
        F.fprintf fmt "Return (%a)" Location.pp_file_pos location
    | SymAssign (loc, location) ->
        F.fprintf fmt "SymAssign (%a, %a)" Loc.pp loc Location.pp_file_pos location
    | UnknownFrom (pname, location) ->
        F.fprintf fmt "UnknownFrom (%a, %a)" Typ.Procname.pp pname Location.pp_file_pos location


  let pp : F.formatter -> t -> unit =
   fun fmt t ->
    let pp_sep fmt () = F.pp_print_string fmt " :: " in
    F.pp_print_list ~pp_sep pp_elem fmt t.trace


  let is_unknown_elem = function UnknownFrom _ -> true | _ -> false

  let has_unknown x = List.exists x.trace ~f:is_unknown_elem
end

module Set = struct
  include AbstractDomain.FiniteSet (BoTrace)

  (* currently, we keep only one trace for efficiency *)
  let join x y =
    if is_empty x then y
    else if is_empty y then x
    else
      let tx, ty = (min_elt x, min_elt y) in
      if Int.( <= ) tx.length ty.length then x else y


  let choose_shortest set = min_elt set

  let singleton elem = singleton (BoTrace.singleton elem)

  let add_elem elem t = if is_empty t then singleton elem else map (BoTrace.add_elem elem) t

  let instantiate ~traces_caller ~traces_callee location =
    if is_empty traces_caller then
      map
        (fun trace_callee -> BoTrace.add_elem_last (BoTrace.Call location) trace_callee)
        traces_callee
    else
      fold
        (fun trace_callee traces ->
          fold
            (fun trace_caller traces ->
              let new_trace_caller = BoTrace.add_elem (BoTrace.Call location) trace_caller in
              let new_trace = BoTrace.append trace_callee new_trace_caller in
              add new_trace traces )
            traces_caller traces )
        traces_callee empty


  let merge ~arr_traces ~idx_traces location =
    if is_empty idx_traces then
      map (fun arr_traces -> BoTrace.add_elem (BoTrace.ArrAccess location) arr_traces) arr_traces
    else
      fold
        (fun idx_traces traces ->
          fold
            (fun arr_traces traces ->
              let new_trace_idx = BoTrace.add_elem (BoTrace.ArrAccess location) idx_traces in
              let new_trace = BoTrace.append new_trace_idx arr_traces in
              add new_trace traces )
            arr_traces traces )
        idx_traces empty


  let has_unknown t = exists BoTrace.has_unknown t
end

include BoTrace
