(*
 * Copyright (c) 2018-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
module L = Logging

module PerfProfilerDataMap = Caml.Map.Make (struct
  type t = Typ.Procname.t

  let compare = Typ.Procname.compare
end)

let global_perf_profiler_data : Perf_profiler_t.perf_profiler_item PerfProfilerDataMap.t ref =
  ref PerfProfilerDataMap.empty


let split_class_method_name =
  let class_sep = String.Search_pattern.create "::" in
  fun qualified_method_name ->
    match String.Search_pattern.index class_sep ~in_:qualified_method_name with
    | Some class_sep_pos ->
        let class_name =
          String.sub qualified_method_name ~pos:0 ~len:class_sep_pos
          |> String.tr ~target:'/' ~replacement:'.'
        in
        let method_name =
          let prefix_len = class_sep_pos + 2 in
          String.sub qualified_method_name ~pos:prefix_len
            ~len:(String.length qualified_method_name - prefix_len)
        in
        Some (class_name, method_name)
    | _ ->
        None


let pp_perf_profiler_item itm =
  let open Perf_profiler_t in
  L.(debug Analysis Medium)
    "@\n\n\
    \               [Perf Profiler Log] Function: '%s'  @\n\
    \               count trace id = %i @\n\
    \               sum inclusive cpu time = %f@\n\
    \               avg inclusive time = %f @\n\
    \               sum exclusive cpu time = %f @\n\
    \               avg exclusive_time = %f  @\n\
    \               inclusive p90 = %f  @\n\
    \               exclusive p90 = %f @\n\
    \               inclusive p50 = %f  @\n\
    \               exclusive p50 = %f @\n\
    \               inclusive p25 = %f  @\n\
    \               exclusive p25 = %f @\n"
    itm.function_name itm.count_trace_id itm.sum_inclusive_cpu_time itm.avg_inclusive_cpu_time_ms
    itm.sum_exclusive_cpu_time itm.avg_exclusive_cpu_time_ms itm.p90_inclusive_cpu_time_ms
    itm.p90_exclusive_cpu_time_ms itm.p50_inclusive_cpu_time_ms itm.p50_exclusive_cpu_time_ms
    itm.p25_inclusive_cpu_time_ms itm.p25_exclusive_cpu_time_ms


let make_void_signature_procname classname methodname =
  let signature = JavaProfilerSamples.JNI.void_method_with_no_arguments in
  L.(debug Analysis Medium)
    "@\n Making procname with void signature for classname = %s   methodname = %s @\n" classname
    methodname ;
  let procname = JavaProfilerSamples.create_procname ~classname ~methodname ~signature in
  L.(debug Analysis Medium) " Resulting procname= %a @\n" Typ.Procname.pp procname ;
  procname


let _read_file_perf_data fname =
  let perf_profiler_data_str =
    match Utils.read_file fname with
    | Ok l ->
        List.map ~f:Perf_profiler_j.perf_profiler_of_string l
    | Error error ->
        L.user_error "Failed to read file '%s': %s@." fname error ;
        []
  in
  let do_item itm =
    pp_perf_profiler_item itm ;
    match split_class_method_name itm.Perf_profiler_t.function_name with
    | Some (classname, methodname) ->
        let procname = make_void_signature_procname classname methodname in
        global_perf_profiler_data :=
          PerfProfilerDataMap.add procname itm !global_perf_profiler_data
    | _ ->
        ()
  in
  List.iter ~f:(fun items -> List.iter ~f:do_item items) perf_profiler_data_str


let read_file_flag = ref false

let in_profiler_data_map key =
  match Config.perf_profiler_data_file with
  | Some fname ->
      if not !read_file_flag then (
        _read_file_perf_data fname ;
        read_file_flag := true ) ;
      if PerfProfilerDataMap.is_empty !global_perf_profiler_data then
        L.(debug Analysis Medium) "@\n\n[Perf Profiler Log] WARNING: EMPTY PERF PROFILER DATA@\n" ;
      PerfProfilerDataMap.mem key !global_perf_profiler_data
  | _ ->
      false
