(* Copyright (c) 1998-2007 INRIA *)

open Geneweb
open Gwcomp
open Def

(* From OCaml manual, integer in binary format is 4 bytes long. *)
let sizeof_long = 4

(** Default source field for persons and families without source data *)
let default_source = ref ""

(** Base consistency check *)
let do_check = ref true

(** Compute consanguinity *)
let do_consang = ref false

(** Print base's statistics *)
let pr_stats = ref false

type person = (int, int, int) Def.gen_person
(** Extended person's entry in the base *)

type ascend = int Def.gen_ascend
(** Person's ascendants entry in the base *)

type union = int Def.gen_union
(** Person's union entry in the base *)

type family = (int, int, int) Def.gen_family
(** Family's entry in the base *)

type couple = int Def.gen_couple
(** Family's couple entry in the base *)

type descend = int Def.gen_descend
(** Family's descendants entry in the base *)

type ('person, 'string) gen_min_person = {
  mutable m_first_name : 'string;
  mutable m_surname : 'string;
  mutable m_occ : int;
  mutable m_rparents : ('person, 'string) gen_relation list;
  mutable m_related : int list;
  mutable m_pevents : ('person, 'string) gen_pers_event list;
  mutable m_sex : sex;
  mutable m_notes : 'string;
}
(** Restricted to the minimum [Def.gen_person] data type. *)

type min_person = (int, int) gen_min_person
(** Person's entry in the base *)

type cbase = {
  (* Array of persons. Person at position [i] has corresponding to him [ascend]
     and [union] at position [i] in [c_ascends] and [c_unions] respectively. *)
  mutable c_persons : min_person array; (* Array of ascendants of persons *)
  mutable c_ascends : ascend array; (* Array of unions of persons *)
  mutable c_unions : union array;
      (* Array of families. Family at position [i] has corresponding to it [couple]
         and [descend] at position [i] in [c_couples] and [c_descends] respectively. *)
  mutable c_families : family array;  (** Array of couples of families *)
  mutable c_couples : couple array; (* Array of descendants of families *)
  mutable c_descends : descend array;
      (* Array of unique strings. Stores every string encoded information
         (like person's name, bithplace, etc.) for other entries in the base. *)
  mutable c_strings : string array;
      (* Data base notes and extended page structure *)
  mutable c_bnotes : Def.base_notes;
}
(** State of the base collecting all information at link time used to
    create further Geneweb database *)

type file_info = {
  (* current .gw filename *)
  mutable f_curr_src_file : string; (* current .gwo filename *)
  mutable f_curr_gwo_file : string;
      (* all persons from current file should be separated  *)
  mutable f_separate : bool; (* behavior for base notes from current file *)
  mutable f_bnotes : [ `merge | `erase | `first | `drop ];
      (* shift all persons from the current file with the given number *)
  mutable f_shift : int;
      (* Table that associates person's names hash and its occurence number
         with the index of person's entry inside the [base]. Contains only
         persons from the local file. *)
  mutable f_local_names : (int * int, int) Hashtbl.t;
}
(** Information about current .gwo file. *)

type gen = {
  (* Table that associates unique string to its position inside
     [g_base]'s unique string array *)
  mutable g_strings : (string, int) Hashtbl.t;
  (* Table that associates person's names hash with the index of
     person's entry inside the [g_base].*)
  mutable g_names : (int, int) Hashtbl.t;
  (* Counter of persons inside [g_base] *)
  mutable g_pcnt : int;
  (* Counter of families inside [g_base] *)
  mutable g_fcnt : int;
  (* Counter of unique strings inside [g_base] *)
  mutable g_scnt : int;
  (* Current file info *)
  g_file_info : file_info;
  (* Base of collected information *)
  g_base : cbase;
  (* Wizard notes (wizard id and note's content) *)
  mutable g_wiznotes : (string * string) list;
  g_patch_p : (int, person) Hashtbl.t;
      (** Array that for every person from [g_base] says if he
        was defined before *)
  mutable g_def : bool array;
  (* Table that associates person's first and last name with
     the next availiable occurence number for the person with
     the same names.*)
  g_first_av_occ : (string * string, int) Hashtbl.t;
  (* Indicates if an error was occured  *)
  mutable g_errored : bool;
  (* Temprary output chanel containing [g_pcnt] integers where [i]nth integer
     corresponds to the position in [g_per] where [i]nth person is defined. *)
  g_per_index : out_channel;
  (* Temprary output chanel containing person's definition (or non-definition
     marker) *)
  g_per : out_channel;
  (* Temprary output chanel containing [g_fcnt] integers where [i]nth integer
     corresponds to the position in [g_fam] where [i]nth family is defined. *)
  g_fam_index : out_channel;
  (* Temprary output chanel containing family's definition *)
  g_fam : out_channel;
}
(** Global linker state *)

(** Set [gen.g_errored] telling that an error was occured *)
let check_error gen = gen.g_errored <- true

(** Function that will be called if base's checker will find an error *)
let set_error base gen x =
  Printf.printf "\nError: ";
  Check.print_base_error stdout base x;
  check_error gen

(** Function that will be called if base's checker will find a warning *)
let set_warning base x =
  Printf.printf "Warning: ";
  Check.print_base_warning stdout base x

(** Returns person's entry from [base] at position [i] *)
let poi base i = base.c_persons.(i)

(** Returns ascendant's entry from [base] at position [i] *)
let aoi base i = base.c_ascends.(i)

(** Returns union's entry from [base] at position [i] *)
let uoi base i = base.c_unions.(i)

(** Returns couple's entry from [base] at position [i] *)
let coi base i = base.c_couples.(i)

(** Returns string in [base]'s unque string array at position [i] *)
let sou base i = base.c_strings.(i)

(** Returns first name of a [base]'s person entry [p]. [p.m_first_name] contains
    index where first name string representation is stored. *)
let p_first_name base p = Mutil.nominative (sou base p.m_first_name)

(** Returns surname of a [base]'s person entry [p]. [p.m_first_name] contains
    index where surname string representation is stored. *)
let p_surname base p = Mutil.nominative (sou base p.m_surname)

(** Returns string designation of person {i firstname.occ surname}. *)
let designation base p =
  let prenom = p_first_name base p in
  let nom = p_surname base p in
  prenom ^ "." ^ string_of_int p.m_occ ^ " " ^ nom

(** Same as [Marshal.to_channel oc v [Marshal.No_sharing]] *)
let output_item_value oc v = Marshal.to_channel oc v [ Marshal.No_sharing ]

(** Same as [input_value] *)
let input_item_value ic = input_value ic

(** Empty string *)
let no_string = ""

(** Stores unique string (if not already present) inside the base's string array and
    associate this string to its index in mentioned array. Extens array if needed.
    Returns associated index. *)
let unique_string gen x =
  try Hashtbl.find gen.g_strings x
  with Not_found ->
    (* string not found *)
    if gen.g_scnt = Array.length gen.g_base.c_strings then (
      (* extend arrray of strings and copy previus elements *)
      let arr = gen.g_base.c_strings in
      let new_size = (2 * Array.length arr) + 1 in
      let new_arr = Array.make new_size no_string in
      Array.blit arr 0 new_arr 0 (Array.length arr);
      gen.g_base.c_strings <- new_arr);
    let u = gen.g_scnt in
    gen.g_base.c_strings.(gen.g_scnt) <- x;
    gen.g_scnt <- gen.g_scnt + 1;
    Hashtbl.add gen.g_strings x u;
    u

(** Dummy [family] with its empty [couple] and [descendants]. *)
let no_family gen =
  let empty_string = unique_string gen "" in
  let fam =
    {
      marriage = Date.cdate_None;
      marriage_place = empty_string;
      marriage_note = empty_string;
      marriage_src = empty_string;
      witnesses = [||];
      relation = NoMention;
      divorce = NotDivorced;
      fevents = [];
      comment = empty_string;
      origin_file = empty_string;
      fsources = empty_string;
      fam_index = -1;
    }
  in
  let cpl = Adef.couple 0 0 in
  let des = { children = [||] } in
  (fam, cpl, des)

(** Initialises [min_person] with occurence number and index of [p] for first name and
    index of [n] for surname in [base]. Other fields are initialised with default value.
    Returns also empty [ascend] and [union] attached to the considered person. *)
let make_person gen p n occ : min_person * ascend * union =
  let empty_string = unique_string gen "" in
  let p =
    {
      m_first_name = unique_string gen p;
      m_surname = unique_string gen n;
      m_occ = occ;
      m_rparents = [];
      m_related = [];
      m_pevents = [];
      m_sex = Neuter;
      m_notes = empty_string;
    }
  and a = { parents = None; consang = Adef.fix (-1) }
  and u = { family = [||] } in
  (p, a, u)

(** Dummy [min_person] with its empty [ascend] and [union]. *)
let no_person gen = make_person gen "" "" 0

(** Extends person's acendant's and union's arrays inside [gen.g_base]
    if needed. *)
let new_iper gen =
  if gen.g_pcnt = Array.length gen.g_base.c_persons then (
    let per_arr = gen.g_base.c_persons in
    let asc_arr = gen.g_base.c_ascends in
    let uni_arr = gen.g_base.c_unions in
    let new_size = (2 * Array.length per_arr) + 1 in
    let phony_per, phony_asc, phony_uni = no_person gen in
    let new_per_arr = Array.make new_size phony_per in
    let new_asc_arr = Array.make new_size phony_asc in
    let new_uni_arr = Array.make new_size phony_uni in
    let new_def = Array.make new_size false in
    Array.blit per_arr 0 new_per_arr 0 (Array.length per_arr);
    gen.g_base.c_persons <- new_per_arr;
    Array.blit asc_arr 0 new_asc_arr 0 (Array.length asc_arr);
    gen.g_base.c_ascends <- new_asc_arr;
    Array.blit uni_arr 0 new_uni_arr 0 (Array.length uni_arr);
    gen.g_base.c_unions <- new_uni_arr;
    Array.blit gen.g_def 0 new_def 0 (Array.length gen.g_def);
    gen.g_def <- new_def)

(** Extends family's couple's and decendant's arrays inside [gen.g_base]
    if needed. *)
let new_ifam gen =
  if gen.g_fcnt = Array.length gen.g_base.c_families then (
    let fam_arr = gen.g_base.c_families in
    let cpl_arr = gen.g_base.c_couples in
    let des_arr = gen.g_base.c_descends in
    let new_size = (2 * Array.length cpl_arr) + 1 in
    let phony_fam, phony_cpl, phony_des = no_family gen in
    let new_fam_arr = Array.make new_size phony_fam in
    let new_cpl_arr = Array.make new_size phony_cpl in
    let new_des_arr = Array.make new_size phony_des in
    Array.blit fam_arr 0 new_fam_arr 0 (Array.length fam_arr);
    gen.g_base.c_families <- new_fam_arr;
    Array.blit cpl_arr 0 new_cpl_arr 0 (Array.length cpl_arr);
    gen.g_base.c_couples <- new_cpl_arr;
    Array.blit des_arr 0 new_des_arr 0 (Array.length des_arr);
    gen.g_base.c_descends <- new_des_arr)

(** Convert [string Def.gen_title_name] to [int Def.gen_title_name].
    If title is [Tname] stores title name as a string in the base. *)
let title_name_unique_string gen = function
  | Tmain -> Tmain
  | Tname n -> Tname (unique_string gen n)
  | Tnone -> Tnone

(** Convert [(string Def.gen_title] to [int Def.gen_title] and insert
    all related to title information in the base. *)
let title_unique_string gen t =
  {
    t_name = title_name_unique_string gen t.t_name;
    t_ident = unique_string gen t.t_ident;
    t_place = unique_string gen t.t_place;
    t_date_start = t.t_date_start;
    t_date_end = t.t_date_end;
    t_nth = t.t_nth;
  }

(** Hash of person's first and last names. *)
let person_hash first_name surname =
  let first_name = Mutil.nominative first_name in
  let surname = Mutil.nominative surname in
  let s = Name.crush_lower (first_name ^ " " ^ surname) in
  Hashtbl.hash s

(** Returns index of a person's entry inside the [gen.base] that has the same
    first name, surname and occurence number. Raises [Not_found] if person
    is not found. *)
let find_person_by_global_name gen first_name surname occ =
  let first_name = Mutil.nominative first_name in
  let surname = Mutil.nominative surname in
  let s = Name.crush_lower (first_name ^ " " ^ surname) in
  let key = Hashtbl.hash s in
  let ipl = Hashtbl.find_all gen.g_names key in
  let first_name = Name.lower first_name in
  let surname = Name.lower surname in
  let rec loop = function
    | [] -> raise Not_found
    | ip :: ipl ->
        let p = poi gen.g_base ip in
        (* refine search by fullnames comparison (without crushlower) and with
           occurence comparison *)
        if
          p.m_occ = occ
          && Name.lower (p_first_name gen.g_base p) = first_name
          && Name.lower (p_surname gen.g_base p) = surname
        then ip
        else loop ipl
  in
  loop ipl

(** Returns index of a person's entry inside the [gen.base] that has the same
    first name, surname and occurence number. Searches only persons defined
    in the current file. Raises [Not_found] if person is not found. *)
let find_person_by_local_name gen first_name surname occ =
  let first_name = Mutil.nominative first_name in
  let surname = Mutil.nominative surname in
  let s = Name.crush_lower (first_name ^ " " ^ surname) in
  let key = Hashtbl.hash s in
  let ipl = Hashtbl.find_all gen.g_file_info.f_local_names (key, occ) in
  let first_name = Name.lower first_name in
  let surname = Name.lower surname in
  let rec loop = function
    | [] -> raise Not_found
    | ip :: ipl ->
        let p = poi gen.g_base ip in
        (* refine search by fullnames comparison (without crushlower) *)
        if
          Name.lower (p_first_name gen.g_base p) = first_name
          && Name.lower (p_surname gen.g_base p) = surname
        then ip
        else loop ipl
  in
  loop ipl

(** Returns index of a person's entry inside the [gen.base] that has the same
    first name, surname and occurence number. Calls [find_person_by_local_name]
    if option [f_separate] is enabled for the current file, otherwise calls
    [find_person_by_global_name]. Raises [Not_found] if person is not found. *)
let find_person_by_name gen first_name surname occ =
  if gen.g_file_info.f_separate then
    find_person_by_local_name gen first_name surname occ
  else find_person_by_global_name gen first_name surname occ

(** Add entry in the global names table [gen.g_names] for the giving
    first and last names associated to the index of their person's entry
    in [base]. *)
let add_person_by_name gen first_name surname int =
  let s = Name.crush_lower (Mutil.nominative (first_name ^ " " ^ surname)) in
  let key = Hashtbl.hash s in
  Hashtbl.add gen.g_names key int

(** Returns first available occurence number that is >= [occ] for the person
    with the giving information. *)
let find_first_available_occ gen fn sn occ =
  let occ =
    try max occ (Hashtbl.find gen.g_first_av_occ (fn, sn))
    with Not_found -> occ
  in
  let rec loop occ =
    match
      try Some (find_person_by_global_name gen fn sn occ)
      with Not_found -> None
    with
    | Some _ -> loop (occ + 1)
    | None ->
        Hashtbl.add gen.g_first_av_occ (fn, sn) occ;
        occ
  in
  loop occ

(** Insert person's reference in the base and modifies all coresponding
    fields in [gen] and returns its entry and entry's index in the base.
    In details:

      - if considered person doesn't exists in the base (wasn't defined or
        referenced before) then function:

        - maps its key (names and occurence number) within the varius
          hash tables
        - creates entry (of type [min_gen]) for the giving person and his
          ascendants and union in the base.
        - initialises its entry (with key information)
        - stores marker in the [gen.g_per] channel telling that person
        wasn't defined.
        - stores in [gen.g_per_index] position where marker was stored in
          [gen.g_per].

      - if considered person was referenced or defined before then doesn't do
        anything (just returns its entry and entry's index in the base)

     *)
let insert_undefined gen key =
  (* shift person's occurence *)
  let occ = key.pk_occ + gen.g_file_info.f_shift in
  (* person with its position in the base *)
  let x, ip =
    try
      if key.pk_first_name = "?" || key.pk_surname = "?" then raise Not_found
      else
        let ip = find_person_by_name gen key.pk_first_name key.pk_surname occ in
        (poi gen.g_base ip, ip) (* if person not found *)
    with Not_found ->
      (* available occurence number *)
      let new_occ =
        if
          gen.g_file_info.f_separate && key.pk_first_name <> "?"
          && key.pk_surname <> "?"
        then find_first_available_occ gen key.pk_first_name key.pk_surname occ
        else occ
      in
      (* person's entry index *)
      let i = gen.g_pcnt in
      let x, a, u = make_person gen key.pk_first_name key.pk_surname new_occ in
      (* strore names globally *)
      if key.pk_first_name <> "?" && key.pk_surname <> "?" then
        add_person_by_name gen key.pk_first_name key.pk_surname i
      else if !Gwcomp.create_all_keys then
        add_person_by_name gen key.pk_first_name key.pk_surname i;
      (* extend arrays if needed *)
      new_iper gen;
      (* add person to array *)
      gen.g_base.c_persons.(i) <- x;
      (* add associated to person ascendants to array *)
      gen.g_base.c_ascends.(i) <- a;
      (* add associated to person union to array *)
      gen.g_base.c_unions.(i) <- u;
      gen.g_pcnt <- gen.g_pcnt + 1;
      (* strore names locally *)
      (if key.pk_first_name <> "?" && key.pk_surname <> "?" then
       let h = person_hash key.pk_first_name key.pk_surname in
       Hashtbl.add gen.g_file_info.f_local_names (h, occ) i);
      (* write start position of person in [g_per] *)
      seek_out gen.g_per_index (sizeof_long * i);
      output_binary_int gen.g_per_index (pos_out gen.g_per);
      (* write marker *)
      output_char gen.g_per 'U';
      (x, i)
  in
  if not gen.g_errored then
    if
      sou gen.g_base x.m_first_name <> key.pk_first_name
      || sou gen.g_base x.m_surname <> key.pk_surname
    then (
      Printf.printf "\nPerson defined with two spellings:\n";
      Printf.printf "  \"%s%s %s\"\n" key.pk_first_name
        (match x.m_occ with 0 -> "" | n -> "." ^ string_of_int n)
        key.pk_surname;
      Printf.printf "  \"%s%s %s\"\n"
        (p_first_name gen.g_base x)
        (match occ with 0 -> "" | n -> "." ^ string_of_int n)
        (p_surname gen.g_base x);
      gen.g_def.(ip) <- true;
      check_error gen);
  (x, ip)

(** Insert person's definition in the base and modifies all coresponding
    fields in [gen] and returns its entry and entry's index in the base.
    In details:

    - if considered person doesn't exists in the base (wasn't defined or
      referenced before) then function:

        - maps its key (names and occurence number) within the varius
          hash tables
        - creates entry (of type [min_gen]) for the giving person and his
          ascendants and union in the base.
        - initialises its entry (with key information)
        - marks it as defined
        - convert [(_,_,string) gen_person] to [person] (sex, events, titles
          and related persons stays uninitialised) and stores it in the
          [gen.g_per] channel.
        - stores in [gen.g_per_index] position where person was stored in
          [gen.g_per].

    - if considered person was referenced before (but not defined) then
      function:

        - get person's entry and its index from the base
        - marks it as defined
        - convert [(_,_,string) gen_person] to [person] (sex, events and
          related persons stays uninitialised) and stores it in the
          [gen.g_per] channel.
        - updates previus index in [gen.g_per_index] in order to point to
          the definition instead of pointing to the reference.
*)
let insert_person gen so =
  (* shift person's occurence *)
  let occ = so.occ + gen.g_file_info.f_shift in
  (* person with its position in the base *)
  let x, ip =
    try
      if so.first_name = "?" || so.surname = "?" then raise Not_found
      else
        let ip = find_person_by_name gen so.first_name so.surname occ in
        (poi gen.g_base ip, ip) (* if person not found *)
    with Not_found ->
      (* available occurence number *)
      let new_occ =
        if
          gen.g_file_info.f_separate && so.first_name <> "?"
          && so.surname <> "?"
        then find_first_available_occ gen so.first_name so.surname occ
        else occ
      in
      (* person's entry index *)
      let i = gen.g_pcnt in
      let x, a, u = make_person gen so.first_name so.surname new_occ in
      (* strore names globally *)
      if so.first_name <> "?" && so.surname <> "?" then
        add_person_by_name gen so.first_name so.surname i
      else if !Gwcomp.create_all_keys then
        add_person_by_name gen so.first_name so.surname i;
      (* extend arrays if needed *)
      new_iper gen;
      (* add person to array *)
      gen.g_base.c_persons.(i) <- x;
      (* add associated to person ascendants to array *)
      gen.g_base.c_ascends.(i) <- a;
      (* add associated to person union to array *)
      gen.g_base.c_unions.(i) <- u;
      gen.g_pcnt <- gen.g_pcnt + 1;
      (* strore names locally *)
      (if so.first_name <> "?" && so.surname <> "?" then
       let h = person_hash so.first_name so.surname in
       Hashtbl.add gen.g_file_info.f_local_names (h, occ) i);
      (x, i)
  in
  (* if person wad defined before (not just referenced) *)
  if gen.g_def.(ip) then (
    (* print error about person beeing already defined *)
    Printf.printf "\nPerson already defined: \"%s%s %s\"\n" so.first_name
      (match x.m_occ with 0 -> "" | n -> "." ^ string_of_int n)
      so.surname;
    if
      p_first_name gen.g_base x <> so.first_name
      || p_surname gen.g_base x <> so.surname
    then
      Printf.printf "as name: \"%s%s %s\"\n"
        (p_first_name gen.g_base x)
        (match occ with 0 -> "" | n -> "." ^ string_of_int n)
        (p_surname gen.g_base x);
    flush stdout;
    check_error gen)
  else (* else set it as defined *)
    gen.g_def.(ip) <- true;
  if not gen.g_errored then
    if
      sou gen.g_base x.m_first_name <> so.first_name
      || sou gen.g_base x.m_surname <> so.surname
    then (
      (* print error about person defined with two spellings *)
      Printf.printf "\nPerson defined with two spellings:\n";
      Printf.printf "  \"%s%s %s\"\n" so.first_name
        (match x.m_occ with 0 -> "" | n -> "." ^ string_of_int n)
        so.surname;
      Printf.printf "  \"%s%s %s\"\n"
        (p_first_name gen.g_base x)
        (match occ with 0 -> "" | n -> "." ^ string_of_int n)
        (p_surname gen.g_base x);
      gen.g_def.(ip) <- true;
      check_error gen);
  if not gen.g_errored then (
    let empty_string = unique_string gen "" in
    (* Convert [(_,_,string) gen_person] to [person]. Save all strings in base *)
    let x =
      {
        first_name = empty_string;
        surname = empty_string;
        occ = 0;
        image = unique_string gen so.image;
        first_names_aliases =
          List.map (unique_string gen) so.first_names_aliases;
        surnames_aliases = List.map (unique_string gen) so.surnames_aliases;
        public_name = unique_string gen so.public_name;
        qualifiers = List.map (unique_string gen) so.qualifiers;
        aliases = List.map (unique_string gen) so.aliases;
        titles = List.map (title_unique_string gen) so.titles;
        rparents = [];
        related = [];
        occupation = unique_string gen so.occupation;
        sex = Neuter;
        access = so.access;
        birth = so.birth;
        birth_place = unique_string gen so.birth_place;
        birth_note = unique_string gen so.birth_note;
        birth_src = unique_string gen so.birth_src;
        baptism = so.baptism;
        baptism_place = unique_string gen so.baptism_place;
        baptism_note = unique_string gen so.baptism_note;
        baptism_src = unique_string gen so.baptism_src;
        death = so.death;
        death_place = unique_string gen so.death_place;
        death_note = unique_string gen so.death_note;
        death_src = unique_string gen so.death_src;
        burial = so.burial;
        burial_place = unique_string gen so.burial_place;
        burial_note = unique_string gen so.burial_note;
        burial_src = unique_string gen so.burial_src;
        pevents = [];
        notes = empty_string;
        psources =
          unique_string gen
            (if so.psources = "" then !default_source else so.psources);
        key_index = ip;
      }
    in
    (* write/update start position of person in [g_per] *)
    seek_out gen.g_per_index (sizeof_long * ip);
    output_binary_int gen.g_per_index (pos_out gen.g_per);
    (* write person *)
    output_char gen.g_per 'D';
    output_item_value gen.g_per (x : person));
  (x, ip)

(** Insert definition or reference in [gen] and returns its entry and
    entry's index in the [gen.g_base]. Calls [insert_person] for definition
    and [insert_undefined] for reference. *)
let insert_somebody gen = function
  | Undefined key -> insert_undefined gen key
  | Defined so -> insert_person gen so

(** Checks if children [ix] doesn't have another parents *)
let check_parents_not_already_defined gen ix fath moth =
  let x = poi gen.g_base ix in
  match (aoi gen.g_base ix).parents with
  | Some int ->
      let cpl = coi gen.g_base int in
      let p = Adef.father cpl in
      let m = Adef.mother cpl in
      Printf.printf
        "I cannot add \"%s\", child of\n\
        \    - \"%s\"\n\
        \    - \"%s\",\n\
         because this persons still exists as child of\n\
        \    - \"%s\"\n\
        \    - \"%s\"." (designation gen.g_base x)
        (designation gen.g_base fath)
        (designation gen.g_base moth)
        (designation gen.g_base (poi gen.g_base p))
        (designation gen.g_base (poi gen.g_base m));
      flush stdout;
      (*
              x.birth := Adef.cdate_None;
              x.death := DontKnowIfDead;
      *)
      check_error gen
  | _ -> ()

(** Assign sex to the person's entry if it's unitialised.
    Print message if sexes are different. *)
let notice_sex gen p s =
  if p.m_sex = Neuter then p.m_sex <- s
  else if p.m_sex <> s && s <> Neuter then
    Printf.printf "\nInconsistency about the sex of\n  %s %s\n"
      (p_first_name gen.g_base p)
      (p_surname gen.g_base p)

(** Convert [string Def.gen_fam_event_name] to [int Def.gen_fam_event_name].
    If event is [Efam_Name] stores event name as a string in the base. *)
let fevent_name_unique_string gen = function
  | ( Efam_Marriage | Efam_NoMarriage | Efam_NoMention | Efam_Engage
    | Efam_Divorce | Efam_Separated | Efam_Annulation | Efam_MarriageBann
    | Efam_MarriageContract | Efam_MarriageLicense | Efam_PACS | Efam_Residence
      ) as evt ->
      evt
  | Efam_Name n -> Efam_Name (unique_string gen n)

(** Update family by looking up information inferred from family events *)
let update_family_with_fevents _gen fam =
  let found_marriage = ref false in
  let found_divorce = ref false in
  let nsck_std_fields =
    match fam.relation with
    | NoSexesCheckNotMarried | NoSexesCheckMarried -> true
    | _ -> false
  in
  (* On veut cette fois ci que ce soit le dernier évènement *)
  (* qui soit mis dans les évènements principaux.           *)
  let convert relation =
    match relation with
    | Efam_Marriage ->
        if nsck_std_fields then Some NoSexesCheckMarried else Some Married
    | Efam_NoMarriage ->
        if nsck_std_fields then Some NoSexesCheckNotMarried else Some NotMarried
    | Efam_Engage -> Some Engaged
    | Efam_NoMention -> Some NoMention
    | Efam_MarriageBann -> Some MarriageBann
    | Efam_MarriageContract -> Some MarriageContract
    | Efam_MarriageLicense -> Some MarriageLicense
    | Efam_PACS -> Some Pacs
    | Efam_Residence -> Some Residence
    | _ -> None
  in
  let rec loop fevents fam =
    match fevents with
    | [] -> fam
    | evt :: l -> (
        match convert evt.efam_name with
        | Some relation' ->
            if !found_marriage then loop l fam
            else
              let witnesses = Array.map fst evt.efam_witnesses in
              let fam =
                {
                  fam with
                  relation = relation';
                  marriage = evt.efam_date;
                  marriage_place = evt.efam_place;
                  marriage_note = evt.efam_note;
                  marriage_src = evt.efam_src;
                  witnesses;
                }
              in
              let () = found_marriage := true in
              loop l fam
        | None -> (
            match evt.efam_name with
            | Efam_Divorce ->
                if !found_divorce then loop l fam
                else
                  let fam = { fam with divorce = Divorced evt.efam_date } in
                  let () = found_divorce := true in
                  loop l fam
            | Efam_Separated ->
                if !found_divorce then loop l fam
                else
                  let fam = { fam with divorce = Separated } in
                  let () = found_divorce := true in
                  loop l fam
            | _ -> loop l fam))
  in
  loop (List.rev fam.fevents) fam

(** Update family event list by looking up inferred family information. *)
let update_fevents_with_family gen fam =
  let empty_string = 0 in
  let evt_marr =
    let name =
      match fam.relation with
      | Married -> Efam_Marriage
      | NotMarried -> Efam_NoMarriage
      | Engaged -> Efam_Engage
      | NoSexesCheckNotMarried -> Efam_NoMarriage
      | NoMention -> Efam_NoMention
      | NoSexesCheckMarried -> Efam_Marriage
      | MarriageBann -> Efam_MarriageBann
      | MarriageContract -> Efam_MarriageContract
      | MarriageLicense -> Efam_MarriageLicense
      | Pacs -> Efam_PACS
      | Residence -> Efam_Residence
    in
    let witnesses = Array.map (fun ip -> (ip, Witness)) fam.witnesses in
    let evt =
      {
        efam_name = name;
        efam_date = fam.marriage;
        efam_place = fam.marriage_place;
        efam_reason = empty_string;
        efam_note = fam.marriage_note;
        efam_src = fam.marriage_src;
        efam_witnesses = witnesses;
      }
    in
    Some evt
  in
  let evt_div =
    match fam.divorce with
    | NotDivorced -> None
    | Divorced cd ->
        let evt =
          {
            efam_name = Efam_Divorce;
            efam_date = cd;
            efam_place = unique_string gen "";
            efam_reason = unique_string gen "";
            efam_note = unique_string gen "";
            efam_src = unique_string gen "";
            efam_witnesses = [||];
          }
        in
        Some evt
    | Separated ->
        let evt =
          {
            efam_name = Efam_Separated;
            efam_date = Date.cdate_None;
            efam_place = unique_string gen "";
            efam_reason = unique_string gen "";
            efam_note = unique_string gen "";
            efam_src = unique_string gen "";
            efam_witnesses = [||];
          }
        in
        Some evt
  in
  let fevents = [ evt_marr; evt_div ] in
  let fevents =
    List.fold_right
      (fun evt fevents ->
        match evt with Some evt -> evt :: fevents | None -> fevents)
      fevents []
  in
  { fam with fevents }

(** Insert family in the base and modifies all coresponding
    fields in [gen] and returns its entry and entry's index in the base.
    In details function does:

    - inserts father and mother in the person's base
    - insert every witness in the person's base and associate father
      as a related person.
    - order, convert, adjust and insert events
    - insert every children in the person's base
    - creates entry (of type [family]) for the giving family and its
      couple and descendants in the base.
    - associate father's and mother's union to the current family
    - associate every children's ascendants to the current family
      (current couple, since it has the same index)
    - stores family in the [gen.g_fam] channel.
    - stores in [gen.g_fam_index] position where person was stored in
      [gen.g_index].
*)
let insert_family gen co fath_sex moth_sex witl fevtl fo deo =
  let fath, ifath, moth, imoth =
    match
      ( insert_somebody gen (Adef.father co),
        insert_somebody gen (Adef.mother co) )
    with
    (* Look for inverted WIFE/HUSB *)
    | (fath, ifath), (moth, imoth) when fath.m_sex = Female && moth.m_sex = Male
      ->
        (moth, imoth, fath, ifath)
    | (fath, ifath), (moth, imoth) -> (fath, ifath, moth, imoth)
  in
  (* insert all family witnesses *)
  let witl =
    List.map
      (fun (wit, sex) ->
        let p, ip = insert_somebody gen wit in
        notice_sex gen p sex;
        (* add father to witness' related persons *)
        p.m_related <- ifath :: p.m_related;
        ip)
      witl
  in
  (* Events are sorted by chronological order (if equal then by alphabetical order) *)
  let fevents =
    Event.sort_events
      (fun (name, _, _, _, _, _, _) -> Event.Fevent name)
      (fun (_, date, _, _, _, _, _) -> date)
      fevtl
  in
  (* Create [int Def.gen_fam_event_name] list from [fevents]*)
  let fevents =
    List.map
      (fun (name, date, place, reason, src, notes, witl) ->
        (* insert all event witnesses *)
        let witnesses =
          List.map
            (fun (wit, sex, wk) ->
              let p, ip = insert_somebody gen wit in
              notice_sex gen p sex;
              p.m_related <- ifath :: p.m_related;
              (ip, wk))
            witl
        in
        {
          efam_name = fevent_name_unique_string gen name;
          efam_date = date;
          efam_place = unique_string gen place;
          efam_reason = unique_string gen reason;
          efam_note = unique_string gen notes;
          efam_src = unique_string gen src;
          efam_witnesses = Array.of_list witnesses;
        })
      fevents
  in
  (* insert all children *)
  let children =
    Array.map
      (fun key ->
        let e, ie = insert_person gen key in
        notice_sex gen e key.sex;
        ie)
      deo.children
  in
  (* insert family comment *)
  let comment = unique_string gen fo.comment in
  (* insert sources comment *)
  let fsources =
    unique_string gen
      (if fo.fsources = "" then !default_source else fo.fsources)
  in
  (* extend arrays if needed *)
  new_ifam gen;
  (* family's entry index *)
  let i = gen.g_fcnt in
  (* Convert [(_,_,string) gen_family] to [family]. Save all strings in base *)
  let fam =
    {
      marriage = fo.marriage;
      marriage_place = unique_string gen fo.marriage_place;
      marriage_note = unique_string gen fo.marriage_note;
      marriage_src = unique_string gen fo.marriage_src;
      witnesses = Array.of_list witl;
      relation = fo.relation;
      divorce = fo.divorce;
      fevents;
      comment;
      origin_file = unique_string gen fo.origin_file;
      fsources;
      fam_index = i;
    }
  (* create couple *)
  and cpl = Adef.couple ifath imoth
  (* created descandants *)
  and des = { children } in
  (* On mets à jour les fevents et events normaux *)
  let fam =
    if fevents <> [] then update_family_with_fevents gen fam
    else update_fevents_with_family gen fam
  in
  (* father's union *)
  let fath_uni = uoi gen.g_base ifath in
  (* mother's union *)
  let moth_uni = uoi gen.g_base imoth in
  (* write start position of family in [g_fam] *)
  seek_out gen.g_fam_index (sizeof_long * i);
  output_binary_int gen.g_fam_index (pos_out gen.g_fam);
  (* write family *)
  output_item_value gen.g_fam (fam : family);
  (* add family to array *)
  gen.g_base.c_families.(gen.g_fcnt) <- fam;
  (* add couple to array *)
  gen.g_base.c_couples.(gen.g_fcnt) <- cpl;
  (* add descendants to array *)
  gen.g_base.c_descends.(gen.g_fcnt) <- des;
  gen.g_fcnt <- gen.g_fcnt + 1;
  (* append this family to father's and mother's union  *)
  let fath_uni = { family = Array.append fath_uni.family [| i |] } in
  gen.g_base.c_unions.(ifath) <- fath_uni;
  let moth_uni = { family = Array.append moth_uni.family [| i |] } in
  gen.g_base.c_unions.(imoth) <- moth_uni;
  notice_sex gen fath fath_sex;
  notice_sex gen moth moth_sex;
  (* Append familly to the children's ascendant *)
  Array.iter
    (fun ix ->
      let a = gen.g_base.c_ascends.(ix) in
      (* check if children has no another parents *)
      check_parents_not_already_defined gen ix fath moth;
      let a = { a with parents = Some i } in
      gen.g_base.c_ascends.(ix) <- a)
    children

(** Convert [string Def.gen_pers_event_name] to [int Def.gen_pers_event_name].
    If event is [Epers_Name] stores event name as a string in the base. *)
let pevent_name_unique_string gen = function
  | ( Epers_Birth | Epers_Baptism | Epers_Death | Epers_Burial | Epers_Cremation
    | Epers_Accomplishment | Epers_Acquisition | Epers_Adhesion
    | Epers_BaptismLDS | Epers_BarMitzvah | Epers_BatMitzvah | Epers_Benediction
    | Epers_ChangeName | Epers_Circumcision | Epers_Confirmation
    | Epers_ConfirmationLDS | Epers_Decoration | Epers_DemobilisationMilitaire
    | Epers_Diploma | Epers_Distinction | Epers_Dotation | Epers_DotationLDS
    | Epers_Education | Epers_Election | Epers_Emigration
    | Epers_Excommunication | Epers_FamilyLinkLDS | Epers_FirstCommunion
    | Epers_Funeral | Epers_Graduate | Epers_Hospitalisation | Epers_Illness
    | Epers_Immigration | Epers_ListePassenger | Epers_MilitaryDistinction
    | Epers_MilitaryPromotion | Epers_MilitaryService
    | Epers_MobilisationMilitaire | Epers_Naturalisation | Epers_Occupation
    | Epers_Ordination | Epers_Property | Epers_Recensement | Epers_Residence
    | Epers_Retired | Epers_ScellentChildLDS | Epers_ScellentParentLDS
    | Epers_ScellentSpouseLDS | Epers_VenteBien | Epers_Will ) as evt ->
      evt
  | Epers_Name n -> Epers_Name (unique_string gen n)

(** Insert all related to the event information and add it to the person's entry
    in the [gen.g_base] *)
let insert_pevents fname gen sb pevtl =
  (* insert concered person *)
  let p, ip = insert_somebody gen sb in
  if p.m_pevents <> [] then (
    Printf.printf "\nFile \"%s\"\n" fname;
    Printf.printf "Individual events already defined for \"%s%s %s\"\n"
      (sou gen.g_base p.m_first_name)
      (if p.m_occ = 0 then "" else "." ^ string_of_int p.m_occ)
      (sou gen.g_base p.m_surname);
    check_error gen)
  else
    (* sort evenets *)
    let pevents =
      Event.sort_events
        (fun (name, _, _, _, _, _, _) -> Event.Pevent name)
        (fun (_, date, _, _, _, _, _) -> date)
        pevtl
    in
    (* convert evenets. Insert all related to evenet information in the base *)
    let pevents =
      List.map
        (fun (name, date, place, reason, src, notes, witl) ->
          let witnesses =
            List.map
              (fun (wit, sex, wk) ->
                (* insert witnesses *)
                let wp, wip = insert_somebody gen wit in
                notice_sex gen wp sex;
                (* add concerned person as witness' relation *)
                wp.m_related <- ip :: wp.m_related;
                (wip, wk))
              witl
          in
          {
            epers_name = pevent_name_unique_string gen name;
            epers_date = date;
            epers_place = unique_string gen place;
            epers_reason = unique_string gen reason;
            epers_note = unique_string gen notes;
            epers_src = unique_string gen src;
            epers_witnesses = Array.of_list witnesses;
          })
        pevents
    in
    (* add events to the person's entry in the base *)
    p.m_pevents <- pevents

(** Insert person's notes in the base and associate it to the referenced
    with [key] person *)
let insert_notes fname gen key str =
  let occ = key.pk_occ + gen.g_file_info.f_shift in
  match
    try Some (find_person_by_name gen key.pk_first_name key.pk_surname occ)
    with Not_found -> None
  with
  | Some ip ->
      let p = poi gen.g_base ip in
      if sou gen.g_base p.m_notes <> "" then (
        Printf.printf "\nFile \"%s\"\n" fname;
        Printf.printf "Notes already defined for \"%s%s %s\"\n"
          key.pk_first_name
          (if occ = 0 then "" else "." ^ string_of_int occ)
          key.pk_surname;
        check_error gen)
      else p.m_notes <- unique_string gen str
  | None ->
      Printf.printf "File \"%s\"\n" fname;
      Printf.printf "*** warning: undefined person: \"%s%s %s\"\n"
        key.pk_first_name
        (if occ = 0 then "" else "." ^ string_of_int occ)
        key.pk_surname;
      flush stdout

(** Changes [gen.g_base.c_bnotes] to take into account [nfname] page
    and its content [str] that is treated by the way mentioned in
    [gen.g_file_info.f_bnotes]. *)
let insert_bnotes fname gen nfname str =
  if gen.g_file_info.f_bnotes <> `drop then
    let old_nread = gen.g_base.c_bnotes.nread in
    (* Convert path notation from 'dir1:dir2:file' to 'dir1/dir2/file'
       (if a valid path) *)
    let nfname =
      if nfname = "" then ""
      else
        match NotesLinks.check_file_name nfname with
        | Some (dl, f) -> List.fold_right Filename.concat dl f
        | None -> "bad"
    in
    let bnotes =
      let str =
        match gen.g_file_info.f_bnotes with
        | `drop -> assert false
        | `erase -> str
        | `merge -> old_nread nfname RnAll ^ str
        | `first -> (
            match old_nread nfname RnAll with "" -> str | str -> str)
      in
      {
        nread = (fun f n -> if f = nfname then str else old_nread f n);
        norigin_file = fname;
        efiles =
          (if nfname <> "" then
           let efiles = gen.g_base.c_bnotes.efiles () in
           fun () -> nfname :: efiles
          else gen.g_base.c_bnotes.efiles);
      }
    in
    gen.g_base.c_bnotes <- bnotes

(** Add wizard and his note to the [gen] *)
let insert_wiznote gen wizid str =
  gen.g_wiznotes <- (wizid, str) :: gen.g_wiznotes

let map_option f = function Some x -> Some (f x) | None -> None

(** Insert parent in the base and adjust his sex if needed. Concerned
    person is added in the list of parent's related persons. *)
let insert_relation_parent gen ip s k =
  let par, ipar = insert_somebody gen k in
  par.m_related <- ip :: par.m_related;
  if par.m_sex = Neuter then par.m_sex <- s;
  ipar

(** Convert [(Dune__exe.Gwcomp.somebody, string) Def.gen_relation] to
    [(int, int) Def.gen_relation] and insert all related to relation
    information in the base. *)
let insert_relation gen ip r =
  {
    r_type = r.r_type;
    r_fath = map_option (insert_relation_parent gen ip Male) r.r_fath;
    r_moth = map_option (insert_relation_parent gen ip Female) r.r_moth;
    r_sources = unique_string gen r.r_sources;
  }

(** Insert all information related to the person's relations and add those
    relations to the person's list of related parents. *)
let insert_relations fname gen sb sex rl =
  (* insert concerned person *)
  let p, ip = insert_somebody gen sb in
  if p.m_rparents <> [] then (
    Printf.printf "\nFile \"%s\"\n" fname;
    Printf.printf "Relations already defined for \"%s%s %s\"\n"
      (sou gen.g_base p.m_first_name)
      (if p.m_occ = 0 then "" else "." ^ string_of_int p.m_occ)
      (sou gen.g_base p.m_surname);
    check_error gen)
  else (
    notice_sex gen p sex;
    let rl = List.map (insert_relation gen ip) rl in
    p.m_rparents <- rl)

(** Insert syntax element read from .gwo file. *)
let insert_syntax fname gen = function
  | Family (cpl, fs, ms, witl, fevents, fam, des) ->
      insert_family gen cpl fs ms witl fevents fam des
  | Notes (key, str) -> insert_notes fname gen key str
  | Relations (sb, sex, rl) -> insert_relations fname gen sb sex rl
  | Pevent (sb, _, pevents) -> insert_pevents fname gen sb pevents
  | Bnotes (nfname, str) -> insert_bnotes fname gen nfname str
  | Wnotes (wizid, str) -> insert_wiznote gen wizid str

(** Update person by looking up information inferred from person events *)
let update_person_with_pevents p =
  let found_birth = ref false in
  let found_baptism = ref false in
  let found_death = ref false in
  let found_burial = ref false in
  let death_std_fields = p.death in
  let death_reason_std_fields =
    match death_std_fields with Death (dr, _) -> dr | _ -> Unspecified
  in
  let rec loop pevents p =
    match pevents with
    | [] -> p
    | evt :: l -> (
        match evt.epers_name with
        | Epers_Birth ->
            if !found_birth then loop l p
            else
              let p =
                {
                  p with
                  birth = evt.epers_date;
                  birth_place = evt.epers_place;
                  birth_note = evt.epers_note;
                  birth_src = evt.epers_src;
                }
              in
              let () = found_birth := true in
              loop l p
        | Epers_Baptism ->
            if !found_baptism then loop l p
            else
              let p =
                {
                  p with
                  baptism = evt.epers_date;
                  baptism_place = evt.epers_place;
                  baptism_note = evt.epers_note;
                  baptism_src = evt.epers_src;
                }
              in
              let () = found_baptism := true in
              loop l p
        | Epers_Death ->
            if !found_death then loop l p
            else
              let death =
                match Date.od_of_cdate evt.epers_date with
                | Some d -> Death (death_reason_std_fields, Date.cdate_of_date d)
                | None -> (
                    match death_std_fields with
                    | OfCourseDead -> OfCourseDead
                    | DeadYoung -> DeadYoung
                    | _ -> DeadDontKnowWhen)
              in
              let p =
                {
                  p with
                  death;
                  death_place = evt.epers_place;
                  death_note = evt.epers_note;
                  death_src = evt.epers_src;
                }
              in
              let () = found_death := true in
              loop l p
        | Epers_Burial ->
            if !found_burial then loop l p
            else
              let p =
                {
                  p with
                  burial = Buried evt.epers_date;
                  burial_place = evt.epers_place;
                  burial_note = evt.epers_note;
                  burial_src = evt.epers_src;
                }
              in
              let () = found_burial := true in
              loop l p
        | Epers_Cremation ->
            if !found_burial then loop l p
            else
              let p =
                {
                  p with
                  burial = Cremated evt.epers_date;
                  burial_place = evt.epers_place;
                  burial_note = evt.epers_note;
                  burial_src = evt.epers_src;
                }
              in
              let () = found_burial := true in
              loop l p
        | _ -> loop l p)
  in
  loop p.pevents p

(** Update person's event list by looking up inferred personal information. *)
let update_pevents_with_person p =
  let empty_string = 0 in
  let evt_birth =
    match Date.od_of_cdate p.birth with
    | Some _ ->
        let evt =
          {
            epers_name = Epers_Birth;
            epers_date = p.birth;
            epers_place = p.birth_place;
            epers_reason = empty_string;
            epers_note = p.birth_note;
            epers_src = p.birth_src;
            epers_witnesses = [||];
          }
        in
        Some evt
    | None ->
        if p.birth_place = 0 && p.birth_src = 0 then None
        else
          let evt =
            {
              epers_name = Epers_Birth;
              epers_date = p.birth;
              epers_place = p.birth_place;
              epers_reason = empty_string;
              epers_note = p.birth_note;
              epers_src = p.birth_src;
              epers_witnesses = [||];
            }
          in
          Some evt
  in
  let evt_bapt =
    match Date.od_of_cdate p.baptism with
    | Some _ ->
        let evt =
          {
            epers_name = Epers_Baptism;
            epers_date = p.baptism;
            epers_place = p.baptism_place;
            epers_reason = empty_string;
            epers_note = p.baptism_note;
            epers_src = p.baptism_src;
            epers_witnesses = [||];
          }
        in
        Some evt
    | None ->
        if p.baptism_place = 0 && p.baptism_src = 0 then None
        else
          let evt =
            {
              epers_name = Epers_Baptism;
              epers_date = p.baptism;
              epers_place = p.baptism_place;
              epers_reason = empty_string;
              epers_note = p.baptism_note;
              epers_src = p.baptism_src;
              epers_witnesses = [||];
            }
          in
          Some evt
  in
  let evt_death =
    match p.death with
    | NotDead | DontKnowIfDead ->
        if p.death_place = 0 && p.death_src = 0 then None
        else
          let evt =
            {
              epers_name = Epers_Death;
              epers_date = Date.cdate_None;
              epers_place = p.death_place;
              epers_reason = empty_string;
              epers_note = p.death_note;
              epers_src = p.death_src;
              epers_witnesses = [||];
            }
          in
          Some evt
    | Death (_, cd) ->
        let date = Date.cdate_of_od (Some (Date.date_of_cdate cd)) in
        let evt =
          {
            epers_name = Epers_Death;
            epers_date = date;
            epers_place = p.death_place;
            epers_reason = empty_string;
            epers_note = p.death_note;
            epers_src = p.death_src;
            epers_witnesses = [||];
          }
        in
        Some evt
    | DeadYoung | DeadDontKnowWhen | OfCourseDead ->
        let evt =
          {
            epers_name = Epers_Death;
            epers_date = Date.cdate_None;
            epers_place = p.death_place;
            epers_reason = empty_string;
            epers_note = p.death_note;
            epers_src = p.death_src;
            epers_witnesses = [||];
          }
        in
        Some evt
  in
  let evt_burial =
    match p.burial with
    | UnknownBurial ->
        if p.burial_place = 0 && p.burial_src = 0 then None
        else
          let evt =
            {
              epers_name = Epers_Burial;
              epers_date = Date.cdate_None;
              epers_place = p.burial_place;
              epers_reason = empty_string;
              epers_note = p.burial_note;
              epers_src = p.burial_src;
              epers_witnesses = [||];
            }
          in
          Some evt
    | Buried cd ->
        let evt =
          {
            epers_name = Epers_Burial;
            epers_date = cd;
            epers_place = p.burial_place;
            epers_reason = empty_string;
            epers_note = p.burial_note;
            epers_src = p.burial_src;
            epers_witnesses = [||];
          }
        in
        Some evt
    | Cremated cd ->
        let evt =
          {
            epers_name = Epers_Cremation;
            epers_date = cd;
            epers_place = p.burial_place;
            epers_reason = empty_string;
            epers_note = p.burial_note;
            epers_src = p.burial_src;
            epers_witnesses = [||];
          }
        in
        Some evt
  in
  let pevents = [ evt_birth; evt_bapt; evt_death; evt_burial ] in
  let pevents =
    List.fold_right
      (fun evt pevents ->
        match evt with Some evt -> evt :: pevents | None -> pevents)
      pevents []
  in
  { p with pevents }

(** Returns list of persons from [min_person list] where some absent information
    was extracted from file [per_ic]. Adjusts person with inffered information
    from events or inversely. *)
let convert_persons per_index_ic per_ic persons =
  Array.mapi
    (fun i mp ->
      let p =
        let c =
          try
            seek_in per_index_ic (sizeof_long * i);
            let pos = input_binary_int per_index_ic in
            seek_in per_ic pos;
            input_char per_ic
          with End_of_file -> 'U'
        in
        match c with
        (* if person is defined read person *)
        | 'D' -> (input_item_value per_ic : person)
        (* if read person is undefined then create dummy person *)
        | 'U' ->
            let empty_string = 0 in
            {
              first_name = empty_string;
              surname = empty_string;
              occ = 0;
              image = empty_string;
              first_names_aliases = [];
              surnames_aliases = [];
              public_name = empty_string;
              qualifiers = [];
              aliases = [];
              titles = [];
              rparents = [];
              related = [];
              occupation = empty_string;
              sex = Neuter;
              access = IfTitles;
              birth = Date.cdate_None;
              birth_place = empty_string;
              birth_note = empty_string;
              birth_src = empty_string;
              baptism = Date.cdate_None;
              baptism_place = empty_string;
              baptism_note = empty_string;
              baptism_src = empty_string;
              death = DontKnowIfDead;
              death_place = empty_string;
              death_note = empty_string;
              death_src = empty_string;
              burial = UnknownBurial;
              burial_place = empty_string;
              burial_note = empty_string;
              burial_src = empty_string;
              pevents = [];
              notes = empty_string;
              psources = empty_string;
              key_index = 0;
            }
        | _ -> assert false
      in
      let p =
        {
          p with
          first_name = mp.m_first_name;
          surname = mp.m_surname;
          occ = mp.m_occ;
          rparents = mp.m_rparents;
          related = mp.m_related;
          pevents = mp.m_pevents;
          sex = mp.m_sex;
          notes = mp.m_notes;
          key_index = i;
        }
      in
      (* Si on a trouvé des évènements, on mets à jour *)
      if p.pevents <> [] then update_person_with_pevents p
      else update_pevents_with_person p)
    persons

(** File containing the particles to use *)
let particules_file = ref ""

(** Returns list of particles from the file. If filename is empty string
    then returns default particles list *)
let input_particles = function
  | "" -> Mutil.default_particles
  | file -> Mutil.input_particles file

(** Empty base *)
let empty_base : cbase =
  {
    c_persons = [||];
    c_ascends = [||];
    c_unions = [||];
    c_families = [||];
    c_couples = [||];
    c_descends = [||];
    c_strings = [||];
    c_bnotes =
      { nread = (fun _ _ -> ""); norigin_file = ""; efiles = (fun _ -> []) };
  }

(** Extract information from the [gen.g_base] and create database *)
let make_base bname gen per_index_ic per_ic =
  let _ =
    Printf.eprintf "pcnt %d persons %d\n" gen.g_pcnt
      (Array.length gen.g_base.c_persons);
    flush stderr
  in
  (* get full persons information *)
  let persons =
    let a = Array.sub gen.g_base.c_persons 0 gen.g_pcnt in
    gen.g_base.c_persons <- [||];
    convert_persons per_index_ic per_ic a
  in
  let ascends =
    let a = Array.sub gen.g_base.c_ascends 0 gen.g_pcnt in
    gen.g_base.c_ascends <- [||];
    a
  in
  let unions =
    let a = Array.sub gen.g_base.c_unions 0 gen.g_pcnt in
    gen.g_base.c_unions <- [||];
    a
  in
  let _ =
    Printf.eprintf "fcnt %d families %d\n" gen.g_fcnt
      (Array.length gen.g_base.c_couples);
    flush stderr
  in
  let families =
    let a = Array.sub gen.g_base.c_families 0 gen.g_fcnt in
    gen.g_base.c_families <- [||];
    a
  in
  let couples =
    let a = Array.sub gen.g_base.c_couples 0 gen.g_fcnt in
    gen.g_base.c_couples <- [||];
    a
  in
  let descends =
    let a = Array.sub gen.g_base.c_descends 0 gen.g_fcnt in
    gen.g_base.c_descends <- [||];
    a
  in
  let _ =
    Printf.eprintf "scnt %d strings %d\n" gen.g_scnt
      (Array.length gen.g_base.c_strings);
    flush stderr
  in
  let strings =
    let a = Array.sub gen.g_base.c_strings 0 gen.g_scnt in
    gen.g_base.c_strings <- [||];
    a
  in
  Gwdb.make bname
    (input_particles !particules_file)
    ( (persons, ascends, unions),
      (families, couples, descends),
      strings,
      gen.g_base.c_bnotes )

(** Write content in the file *)
let write_file_contents fname text =
  let oc = open_out fname in
  output_string oc text;
  close_out oc

(** Create and fill a file for every wizard note *)
let output_wizard_notes bdir wiznotes =
  let wizdir = Filename.concat bdir "wiznotes" in
  if wiznotes <> [] then (
    (* FIXME ad hoc solution. wiznotes should be handled same as notes_d *)
    if not @@ Sys.file_exists wizdir then Unix.mkdir wizdir 0o755;
    List.iter
      (fun (wizid, text) ->
        let fname = Filename.concat wizdir wizid ^ ".txt" in
        write_file_contents fname text)
      wiznotes)

(** Create file that contains command used to call this program *)
let output_command_line bdir =
  let oc = open_out (Filename.concat bdir "command.txt") in
  Printf.fprintf oc "%s" Sys.argv.(0);
  for i = 1 to Array.length Sys.argv - 1 do
    Printf.fprintf oc " %s" Sys.argv.(i)
  done;
  Printf.fprintf oc "\n";
  close_out oc

(** Link .gwo files and create a database. *)
let link next_family_fun bdir =
  let tmp_dir = Filename.concat "gw_tmp" bdir in
  Mutil.mkdir_p tmp_dir;
  let tmp_per_index = Filename.concat tmp_dir "gwc_per_index" in
  let tmp_per = Filename.concat tmp_dir "gwc_per" in
  let tmp_fam_index = Filename.concat tmp_dir "gwc_fam_index" in
  let tmp_fam = Filename.concat tmp_dir "gwc_fam" in
  let fi =
    {
      f_local_names = Hashtbl.create 20011;
      f_curr_src_file = "";
      f_curr_gwo_file = "";
      f_separate = false;
      f_shift = 0;
      f_bnotes = `merge;
    }
  in
  let gen =
    {
      g_strings = Hashtbl.create 20011;
      g_names = Hashtbl.create 20011;
      g_pcnt = 0;
      g_fcnt = 0;
      g_scnt = 0;
      g_file_info = fi;
      g_base = empty_base;
      g_patch_p = Hashtbl.create 20011;
      g_wiznotes = [];
      g_def = [||];
      g_first_av_occ = Hashtbl.create 1;
      g_errored = false;
      g_per_index = open_out_bin tmp_per_index;
      g_per = open_out_bin tmp_per;
      g_fam_index = open_out_bin tmp_fam_index;
      g_fam = open_out_bin tmp_fam;
    }
  in
  let per_index_ic = open_in_bin tmp_per_index in
  let per_ic = open_in_bin tmp_per in
  let istr_empty = unique_string gen "" in
  let istr_quest = unique_string gen "?" in
  assert (istr_empty = 0);
  assert (istr_quest = 1);
  if Sys.unix then Sys.remove tmp_per_index;
  if Sys.unix then Sys.remove tmp_per;
  if Sys.unix then Sys.remove tmp_fam_index;
  if Sys.unix then Sys.remove tmp_fam;
  let next_family = next_family_fun fi in
  (let rec loop () =
     match next_family () with
     | Some fam ->
         insert_syntax fi.f_curr_src_file gen fam;
         loop ()
     | None -> ()
   in
   loop ());
  close_out gen.g_per_index;
  close_out gen.g_per;
  close_out gen.g_fam_index;
  close_out gen.g_fam;
  Hashtbl.clear gen.g_strings;
  Hashtbl.clear gen.g_names;
  Hashtbl.clear fi.f_local_names;
  Gc.compact ();
  let base = make_base bdir gen per_index_ic per_ic in
  Hashtbl.clear gen.g_patch_p;
  if !do_check && gen.g_pcnt > 0 then (
    Check.check_base base (set_error base gen) (set_warning base) ignore;
    if !pr_stats then Stats.(print_stats base @@ stat_base base));
  if not gen.g_errored then (
    if !do_consang then ignore @@ ConsangAll.compute base true;
    Gwdb.sync base;
    output_wizard_notes bdir gen.g_wiznotes;
    Mutil.remove_dir tmp_dir;
    output_command_line bdir;
    true)
  else false
