/*
 * vim: set ft=rust:
 * vim: set ft=reason:
 *
 * Copyright (c) 2009 - 2013 Monoidics ltd.
 * Copyright (c) 2013 - 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;


/** The Smallfoot Intermediate Language: Struct Types */
let module L = Logging;

let module F = Format;

type fields = list (Ident.fieldname, Typ.t, Annot.Item.t);


/** Type for a structured value. */
type t = {
  fields: fields, /** non-static fields */
  statics: fields, /** static fields */
  supers: list Typename.t, /** superclasses */
  methods: list Procname.t, /** methods defined */
  annots: Annot.Item.t /** annotations */
};

type lookup = Typename.t => option t;

let fld_typ_ann_compare fta1 fta2 =>
  triple_compare Ident.fieldname_compare Typ.compare Annot.Item.compare fta1 fta2;

let pp pe pp_base name f {fields} =>
  if false {
    /* change false to true to print the details of struct */
    F.fprintf
      f
      "%a {%a} %a"
      Typename.pp
      name
      (pp_seq (fun f (fld, t, _) => F.fprintf f "%a %a" (Typ.pp_full pe) t Ident.pp_fieldname fld))
      fields
      pp_base
      ()
  } else {
    F.fprintf f "%a %a" Typename.pp name pp_base ()
  };

let internal_mk_struct
    default::default=?
    fields::fields=?
    statics::statics=?
    methods::methods=?
    supers::supers=?
    annots::annots=?
    () => {
  let mk_struct_
      default::default={fields: [], statics: [], methods: [], supers: [], annots: Annot.Item.empty}
      fields::fields=default.fields
      statics::statics=default.statics
      methods::methods=default.methods
      supers::supers=default.supers
      annots::annots=default.annots
      () => {
    fields,
    statics,
    methods,
    supers,
    annots
  };
  mk_struct_
    default::?default
    fields::?fields
    statics::?statics
    methods::?methods
    supers::?supers
    annots::?annots
    ()
};


/** the element typ of the final extensible array in the given typ, if any */
let rec get_extensible_array_element_typ lookup::lookup (typ: Typ.t) =>
  switch typ {
  | Tarray typ _ => Some typ
  | Tstruct name =>
    switch (lookup name) {
    | Some {fields} =>
      switch (IList.last fields) {
      | Some (_, fld_typ, _) => get_extensible_array_element_typ lookup::lookup fld_typ
      | None => None
      }
    | None => None
    }
  | _ => None
  };


/** If a struct type with field f, return the type of f. If not, return the default */
let fld_typ lookup::lookup default::default fn (typ: Typ.t) =>
  switch typ {
  | Tstruct name =>
    switch (lookup name) {
    | Some {fields} =>
      try (snd3 (IList.find (fun (f, _, _) => Ident.fieldname_equal f fn) fields)) {
      | Not_found => default
      }
    | None => default
    }
  | _ => default
  };

let get_field_type_and_annotation lookup::lookup fn (typ: Typ.t) =>
  switch typ {
  | Tstruct name
  | Tptr (Tstruct name) _ =>
    switch (lookup name) {
    | Some {fields, statics} =>
      try {
        let (_, t, a) = IList.find (fun (f, _, _) => Ident.fieldname_equal f fn) (fields @ statics);
        Some (t, a)
      } {
      | Not_found => None
      }
    | None => None
    }
  | _ => None
  };

let objc_ref_counter_annot = [({Annot.class_name: "ref_counter", parameters: []}, false)];


/** Field used for objective-c reference counting */
let objc_ref_counter_field = (Ident.fieldname_hidden, Typ.Tint IInt, objc_ref_counter_annot);

let is_objc_ref_counter_field (fld, _, a) =>
  Ident.fieldname_is_hidden fld && Annot.Item.compare a objc_ref_counter_annot == 0;
