(*
 * 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! IStd
module L = Logging
module F = Format

module MockTraceElem = struct
  type t = Kind1 | Kind2 | Footprint | Unknown [@@deriving compare]

  let unknown = Unknown

  let call_site _ = CallSite.dummy

  let kind t = t

  let make ?indexes:_ kind _ = kind

  let pp fmt = function
    | Kind1
     -> F.fprintf fmt "Kind1"
    | Kind2
     -> F.fprintf fmt "Kind2"
    | Footprint
     -> F.fprintf fmt "Footprint"
    | Unknown
     -> F.fprintf fmt "Unknown"

  module Kind = struct
    type nonrec t = t

    let compare = compare

    let pp = pp
  end

  module Set = PrettyPrintable.MakePPSet (struct
    type nonrec t = t

    let compare = compare

    let pp = pp
  end)

  let with_callsite t _ = t
end

module MockSource = struct
  include Source.Make (struct
    include MockTraceElem

    let get _ _ = assert false

    let get_tainted_formals _ = assert false
  end)

  let equal = [%compare.equal : t]
end

module MockSink = struct
  include MockTraceElem

  type parameter = {sink: t; index: int}

  let get _ = assert false

  let indexes _ = IntSet.empty

  let equal = [%compare.equal : t]
end

module MockTrace = Trace.Make (struct
  module Source = MockSource
  module Sink = MockSink

  let should_report source sink =
    [%compare.equal : MockTraceElem.t] (Source.kind source) (Sink.kind sink)
end)

let tests =
  let source1 = MockSource.make MockTraceElem.Kind1 CallSite.dummy in
  let source2 = MockSource.make MockTraceElem.Kind2 CallSite.dummy in
  let sink1 = MockSink.make MockTraceElem.Kind1 CallSite.dummy in
  let sink2 = MockSink.make MockTraceElem.Kind2 CallSite.dummy in
  let open OUnit2 in
  let get_reports =
    let get_reports_ _ =
      let trace =
        MockTrace.of_source source1 |> MockTrace.add_source source2 |> MockTrace.add_sink sink1
        |> MockTrace.add_sink sink2
      in
      let reports = MockTrace.get_reports trace in
      assert_equal (List.length reports) 2 ;
      assert_bool "Reports should contain source1 -> sink1"
        (List.exists
           ~f:(fun (source, sink, _) ->
             MockSource.equal source source1 && MockSink.equal sink sink1)
           reports) ;
      assert_bool "Reports should contain source2 -> sink2"
        (List.exists
           ~f:(fun (source, sink, _) ->
             MockSource.equal source source2 && MockSink.equal sink sink2)
           reports)
    in
    "get_reports" >:: get_reports_
  in
  let append =
    let append_ _ =
      let call_site = CallSite.dummy in
      let footprint_ap =
        AccessPath.Exact (AccessPath.of_id (Ident.create_none ()) (Typ.mk Tvoid))
      in
      let dummy_pdesc =
        Cfg.create_proc_desc (Cfg.create_cfg ())
          (ProcAttributes.default Typ.Procname.empty_block !Config.curr_language)
      in
      let footprint_source = MockSource.make_footprint footprint_ap dummy_pdesc in
      let source_trace = MockTrace.of_source source1 in
      let footprint_trace = MockTrace.of_source footprint_source |> MockTrace.add_sink sink1 in
      let expected_trace = MockTrace.of_source source1 |> MockTrace.add_sink sink1 in
      assert_bool "Appended trace should contain source and sink"
        (MockTrace.equal (MockTrace.append source_trace footprint_trace call_site) expected_trace)
    in
    "append" >:: append_
  in
  "trace_domain_suite" >::: [get_reports; append]
