# encoding: utf-8
#
# Gramps - a GTK+/GNOME based genealogy program
#
# Copyright (C) 2000-2006  Martin Hawlisch, Donald N. Allingham
# Copyright (C) 2008       Brian G. Matherly
# Copyright (C) 2010       Jakim Friant
# Copyright (C) 2011       Tim G L Lyons
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
#

"""Tools/Debug/Generate Testcases for Persons and Families"""

# pylint: disable=too-many-statements,too-many-locals,too-many-branches
# pylint: disable=wrong-import-position,too-many-public-methods,no-self-use
# pylint: disable=too-many-arguments
# ------------------------------------------------------------------------
#
# standard python modules
#
# ------------------------------------------------------------------------
import sys
import os
import random

from gramps.gen.const import GRAMPS_LOCALE as glocale

_ = glocale.translation.gettext

# ------------------------------------------------------------------------
#
# GNOME libraries
#
# ------------------------------------------------------------------------
from gi.repository import Gtk

# ------------------------------------------------------------------------
#
# Gramps modules
#
# ------------------------------------------------------------------------
from gramps.gen.lib import (
    Address,
    Attribute,
    AttributeType,
    ChildRef,
    ChildRefType,
    Citation,
    Date,
    Event,
    EventRef,
    EventRoleType,
    EventType,
    Family,
    FamilyRelType,
    GrampsType,
    LdsOrd,
    Location,
    Media,
    MediaRef,
    Name,
    NameOriginType,
    NameType,
    Note,
    NoteType,
    Person,
    PersonRef,
    Place,
    PlaceType,
    PlaceRef,
    PlaceName,
    RepoRef,
    Repository,
    RepositoryType,
    Source,
    SourceMediaType,
    SrcAttribute,
    SrcAttributeType,
    Surname,
    Tag,
    Url,
    UrlType,
)
from gramps.gen.lib.addressbase import AddressBase
from gramps.gen.lib.attrbase import AttributeBase
from gramps.gen.lib.primaryobj import BasicPrimaryObject
from gramps.gen.lib.citationbase import CitationBase
from gramps.gen.lib.date import Today
from gramps.gen.lib.datebase import DateBase
from gramps.gen.lib.ldsordbase import LdsOrdBase
from gramps.gen.lib.locationbase import LocationBase
from gramps.gen.lib.mediabase import MediaBase
from gramps.gen.lib.notebase import NoteBase
from gramps.gen.lib.placebase import PlaceBase
from gramps.gen.lib.privacybase import PrivacyBase
from gramps.gen.lib.tagbase import TagBase
from gramps.gen.lib.urlbase import UrlBase
from gramps.gen.lib import StyledText, StyledTextTag, StyledTextTagType
from gramps.gen.db import DbTxn
from gramps.gen.mime import get_type
from gramps.gui.plug import tool
from gramps.gen.utils.string import conf_strings
from gramps.gen.utils.lds import TEMPLES
from gramps.gen.db.dbconst import *
from gramps.gen.const import ICON, LOGO, SPLASH
from gramps.gui.display import display_help
from gramps.gen.const import URL_MANUAL_PAGE

# ------------------------------------------------------------------------
#
# Constants
#
# ------------------------------------------------------------------------
WIKI_HELP_PAGE = "%s_-_Tools" % URL_MANUAL_PAGE
WIKI_HELP_SEC = _("Generate_Testcases_for_Persons_and_Families")

# the following allows test code to access a private copy of the random
# number generator.  The access is typically used for seeding the generator
# to make it repeatable across runs.  The private copy is unaffected by other
# uses of the global random() functions.
from gramps.gen.const import TEST_RANDOM

_random = TEST_RANDOM.random
_choice = TEST_RANDOM.choice
_randint = TEST_RANDOM.randint


LDS_ORD_BAPT_STATUS = (
    LdsOrd.STATUS_NONE,
    LdsOrd.STATUS_CHILD,
    LdsOrd.STATUS_CLEARED,
    LdsOrd.STATUS_COMPLETED,
    LdsOrd.STATUS_INFANT,
    LdsOrd.STATUS_PRE_1970,
    LdsOrd.STATUS_QUALIFIED,
    LdsOrd.STATUS_STILLBORN,
    LdsOrd.STATUS_SUBMITTED,
    LdsOrd.STATUS_UNCLEARED,
)

LDS_ORD_CHILD_SEALING_STATUS = (
    LdsOrd.STATUS_NONE,
    LdsOrd.STATUS_BIC,
    LdsOrd.STATUS_CLEARED,
    LdsOrd.STATUS_COMPLETED,
    LdsOrd.STATUS_DNS,
    LdsOrd.STATUS_PRE_1970,
    LdsOrd.STATUS_QUALIFIED,
    LdsOrd.STATUS_STILLBORN,
    LdsOrd.STATUS_SUBMITTED,
    LdsOrd.STATUS_UNCLEARED,
)

LDS_ENDOWMENT_DATE_STATUS = (
    LdsOrd.STATUS_NONE,
    LdsOrd.STATUS_CHILD,
    LdsOrd.STATUS_CLEARED,
    LdsOrd.STATUS_COMPLETED,
    LdsOrd.STATUS_INFANT,
    LdsOrd.STATUS_PRE_1970,
    LdsOrd.STATUS_QUALIFIED,
    LdsOrd.STATUS_STILLBORN,
    LdsOrd.STATUS_SUBMITTED,
    LdsOrd.STATUS_UNCLEARED,
)

LDS_SPOUSE_SEALING_DATE_STATUS = (
    LdsOrd.STATUS_NONE,
    LdsOrd.STATUS_CANCELED,
    LdsOrd.STATUS_CLEARED,
    LdsOrd.STATUS_COMPLETED,
    LdsOrd.STATUS_DNS,
    LdsOrd.STATUS_DNS_CAN,
    LdsOrd.STATUS_PRE_1970,
    LdsOrd.STATUS_QUALIFIED,
    LdsOrd.STATUS_SUBMITTED,
    LdsOrd.STATUS_UNCLEARED,
)

LDS_INDIVIDUAL_ORD = [
    (LdsOrd.BAPTISM, LDS_ORD_BAPT_STATUS),
    (LdsOrd.CONFIRMATION, LDS_ORD_BAPT_STATUS),
    (LdsOrd.ENDOWMENT, LDS_ENDOWMENT_DATE_STATUS),
    (LdsOrd.SEAL_TO_PARENTS, LDS_ORD_CHILD_SEALING_STATUS),
]

LDS_SPOUSE_SEALING = [(LdsOrd.SEAL_TO_SPOUSE, LDS_SPOUSE_SEALING_DATE_STATUS)]


# ------------------------------------------------------------------------


class TestcaseGenerator(tool.BatchTool):
    """
    This tool generates various test cases for problems that have occured.
    The issues it generates can be corrected via the 'Check and Repair' tool.
    """

    NUMERIC = 0
    FIRSTNAME = 1
    FIRSTNAME_FEMALE = 2
    FIRSTNAME_MALE = 3
    LASTNAME = 4
    NOTE = 5
    SHORT = 6
    LONG = 7
    TAG = 8
    STYLED_TEXT = 9

    #    GEDCON definition:
    #
    #    FAMILY_EVENT_STRUCTURE:=
    #    [
    #    n [ ANUL | CENS | DIV | DIVF ] [Y|<NULL>] {1:1}
    #    +1 <<EVENT_DETAIL>> {0:1} p.29
    #    |
    #    n [ ENGA | MARR | MARB | MARC ] [Y|<NULL>] {1:1}
    #    +1 <<EVENT_DETAIL>> {0:1} p.29
    #    |
    #    n [ MARL | MARS ] [Y|<NULL>] {1:1}
    #    +1 <<EVENT_DETAIL>> {0:1} p.29
    #    |
    #    n EVEN {1:1}
    #    +1 <<EVENT_DETAIL>> {0:1} p.29
    #    ]

    FAMILY_EVENTS = set(
        [
            EventType.ANNULMENT,
            EventType.CENSUS,
            EventType.DIVORCE,
            EventType.DIV_FILING,
            EventType.ENGAGEMENT,
            EventType.MARRIAGE,
            EventType.MARR_BANNS,
            EventType.MARR_CONTR,
            EventType.MARR_LIC,
            EventType.MARR_SETTL,
            EventType.CUSTOM,
        ]
    )

    def __init__(self, dbstate, user, options_class, name, callback=None):
        uistate = user.uistate
        if uistate:
            parent_window = uistate.window
        else:
            parent_window = None
        self.progress = user.progress

        # ******** This ensures that a chunk of code below never executes!!!!
        self.person = None

        if dbstate.db.readonly:
            return

        tool.BatchTool.__init__(
            self, dbstate, user, options_class, name, parent=parent_window
        )

        if self.fail:
            return

        self.options_dict = self.options.handler.options_dict
        self.person_count = 0
        self.max_person_count = self.options_dict["person_count"]
        self.persons_todo = []
        self.parents_todo = []
        self.person_dates = {}
        self.generated_repos = []
        self.generated_sources = []
        self.generated_citations = []
        self.generated_media = []
        self.generated_places = []
        self.generated_events = []
        self.generated_families = []
        self.generated_notes = []
        self.generated_tags = []
        self.text_serial_number = 1
        self.trans = None

        self.parent_places = {}
        for type_num in range(1, 8):
            self.parent_places[type_num] = []

        # If an active persons exists the generated tree is connected to that
        # person
        if self.person:
            # try to get birth and death year
            try:
                birth_h = self.person.get_birth_handle()
                birth_e = self.db.get_event_from_handle(birth_h)
                dat_o = birth_e.get_date_object()
                birth = dat_o.get_year()
            except AttributeError:
                birth = None
            try:
                death_h = self.person.get_death_handle()
                death_e = self.db.get_event_from_handle(death_h)
                dat_o = death_e.get_date_object()
                death = dat_o.get_year()
            except AttributeError:
                death = None
            if not birth and not death:
                birth = _randint(1700, 1900)
            if birth and not death:
                death = birth + _randint(20, 90)
            if death and not birth:
                birth = death - _randint(20, 90)
            self.person_dates[self.person.get_handle()] = (birth, death)

            self.persons_todo.append(self.person.get_handle())
            self.parents_todo.append(self.person.get_handle())

        if uistate:
            self.init_gui(uistate)
        else:
            self.run_tool(cli=True)

    def init_gui(self, uistate):
        title = "%s - Gramps" % _("Generate testcases")
        self.top = Gtk.Dialog(title=title, transient_for=uistate.window)
        self.window = uistate.window
        self.top.set_default_size(400, 150)
        self.top.vbox.set_spacing(5)
        label = Gtk.Label(
            label='<span size="larger" weight="bold">%s</span>'
            % _("Generate testcases")
        )
        label.set_use_markup(True)
        self.top.vbox.pack_start(label, 0, 0, 5)

        self.check_lowlevel = Gtk.CheckButton(
            label=_(
                "Generate low level database "
                "errors\nCorrection needs database reload"
            )
        )
        self.check_lowlevel.set_active(self.options_dict["lowlevel"])
        self.top.vbox.pack_start(self.check_lowlevel, 0, 0, 5)

        self.check_bugs = Gtk.CheckButton(label=_("Generate database errors"))
        self.check_bugs.set_active(self.options_dict["bugs"])
        self.top.vbox.pack_start(self.check_bugs, 0, 0, 5)

        self.check_persons = Gtk.CheckButton(label=_("Generate dummy data"))
        self.check_persons.set_active(self.options_dict["persons"])
        self.check_persons.connect("clicked", self.on_dummy_data_clicked)
        self.top.vbox.pack_start(self.check_persons, 0, 0, 5)

        self.check_longnames = Gtk.CheckButton(label=_("Generate long names"))
        self.check_longnames.set_active(self.options_dict["long_names"])
        self.top.vbox.pack_start(self.check_longnames, 0, 0, 5)

        self.check_specialchars = Gtk.CheckButton(label=_("Add special characters"))
        self.check_specialchars.set_active(self.options_dict["specialchars"])
        self.top.vbox.pack_start(self.check_specialchars, 0, 0, 5)

        self.check_serial = Gtk.CheckButton(label=_("Add serial number"))
        self.check_serial.set_active(self.options_dict["add_serial"])
        self.top.vbox.pack_start(self.check_serial, 0, 0, 5)

        self.check_linebreak = Gtk.CheckButton(label=_("Add line break"))
        self.check_linebreak.set_active(self.options_dict["add_linebreak"])
        self.top.vbox.pack_start(self.check_linebreak, 0, 0, 5)

        self.label = Gtk.Label(
            label=_(
                "Number of people to generate\n"
                "(Number is approximate because families are generated)"
            )
        )
        self.label.set_halign(Gtk.Align.START)
        self.top.vbox.pack_start(self.label, 0, 0, 5)

        self.entry_count = Gtk.Entry()
        self.entry_count.set_text(str(self.max_person_count))
        self.on_dummy_data_clicked(self.check_persons)
        self.top.vbox.pack_start(self.entry_count, 0, 0, 5)

        self.top.add_button(_("_Cancel"), Gtk.ResponseType.CANCEL)
        self.top.add_button(_("_OK"), Gtk.ResponseType.OK)
        self.top.add_button(_("_Help"), Gtk.ResponseType.HELP)
        self.top.show_all()

        while True:
            response = self.top.run()
            if response == Gtk.ResponseType.HELP:
                display_help(webpage=WIKI_HELP_PAGE, section=WIKI_HELP_SEC)
            else:
                break
        self.options_dict["lowlevel"] = int(self.check_lowlevel.get_active())
        self.options_dict["bugs"] = int(self.check_bugs.get_active())
        self.options_dict["persons"] = int(self.check_persons.get_active())
        self.options_dict["long_names"] = int(self.check_longnames.get_active())
        self.options_dict["specialchars"] = int(self.check_specialchars.get_active())
        self.options_dict["add_serial"] = int(self.check_serial.get_active())
        self.options_dict["add_linebreak"] = int(self.check_linebreak.get_active())
        self.options_dict["person_count"] = int(self.entry_count.get_text())
        self.top.destroy()

        if response == Gtk.ResponseType.OK:
            self.run_tool(cli=False)
            # Save options
            self.options.handler.save_options()

    def on_dummy_data_clicked(self, obj):
        self.label.set_sensitive(obj.get_active())
        self.entry_count.set_sensitive(obj.get_active())

    def run_tool(self, cli=False):
        self.cli = cli
        if not cli:
            while Gtk.events_pending():
                Gtk.main_iteration()
        else:
            self.window = None

        self.transaction_count = 0

        if self.options_dict["lowlevel"]:
            with self.progress(
                _("Generating testcases"), _("Generating low level database errors"), 1
            ) as step:
                self.test_low_level()
                step()

        if self.options_dict["bugs"] or self.options_dict["persons"]:
            self.generate_tags()

        if self.options_dict["bugs"]:
            with self.progress(
                _("Generating testcases"), _("Generating database errors"), 20
            ) as step:
                self.generate_data_errors(step)

        if self.options_dict["persons"]:
            with self.progress(
                _("Generating testcases"),
                _("Generating families"),
                self.max_person_count,
            ) as self.progress_step:
                self.person_count = 0

                with DbTxn(
                    _("Testcase generator step %d") % self.transaction_count, self.db
                ) as self.trans:
                    self.transaction_count += 1
                    while True:
                        if not self.persons_todo:
                            pers_h = self.generate_person(0)
                            self.persons_todo.append(pers_h)
                            self.parents_todo.append(pers_h)
                        person_h = self.persons_todo.pop(0)
                        self.generate_family(person_h)
                        if _randint(0, 3) == 0:
                            self.generate_family(person_h)
                        if _randint(0, 7) == 0:
                            self.generate_family(person_h)
                        if self.person_count > self.max_person_count:
                            break
                        for child_h in self.parents_todo:
                            self.generate_parents(child_h)
                            if self.person_count > self.max_person_count:
                                break

        if not cli:
            self.top.destroy()

    def generate_data_errors(self, step):
        """This generates errors in the database to test src/plugins/tool/Check
        The module names correspond to the checking methods in
        src/plugins/tool/Check.CheckIntegrity"""
        # The progress meter is normally stepped every time a person is
        # generated by generate_person. However in this case, generate_person
        # is called by some of the constituent functions, but we only want the
        # meter to be stepped every time a test function has been completed.
        self.progress_step = lambda: None

        self.test_fix_encoding()
        step()
        self.test_fix_ctrlchars_in_notes()
        step()
        self.test_fix_alt_place_names()
        step()
        self.test_fix_duplicated_grampsid()
        step()
        self.test_clean_deleted_name_format()
        step()
        self.test_cleanup_empty_objects()
        step()
        self.test_chk_for_broke_family_link()
        step()
        self.test_check_parent_relationships()
        step()
        self.test_cleanup_empty_families()
        step()
        self.test_cleanup_duplicate_spouses()
        step()
        self.test_check_events()
        step()
        self.test_check_person_references()
        step()
        self.test_check_family_references()
        step()
        self.test_check_place_references()
        step()
        self.test_check_source_references()
        step()
        self.test_check_citation_references()
        step()
        self.test_check_media_references()
        step()
        self.test_check_repo_references()
        step()
        self.test_check_note_references()
        step()

    def test_low_level(self):
        with DbTxn(
            _("Testcase generator step %d") % self.transaction_count, self.db
        ) as self.trans:
            self.transaction_count += 1

            obj = Note()
            obj.set("dup 1" + self.rand_text(self.NOTE))
            obj.set_format(_choice((Note.FLOWED, Note.FORMATTED)))
            obj.set_type(self.rand_type(NoteType()))
            self.db.add_note(obj, self.trans)
            print(
                "object %s, handle %s, Gramps_Id %s" % (obj, obj.handle, obj.gramps_id)
            )

            handle = obj.get_handle()

            src = Source()
            src.set_title("dup 2" + self.rand_text(self.SHORT))
            if _randint(0, 1) == 1:
                src.set_author(self.rand_text(self.SHORT))
            if _randint(0, 1) == 1:
                src.set_publication_info(self.rand_text(self.LONG))
            if _randint(0, 1) == 1:
                src.set_abbreviation(self.rand_text(self.SHORT))
            while _randint(0, 1) == 1:
                sattr = SrcAttribute()
                sattr.set_type(self.rand_text(self.SHORT))
                sattr.set_value(self.rand_text(self.SHORT))
                src.add_attribute(sattr)
            src.set_handle(handle)
            self.db.add_source(src, self.trans)
            print(
                "object %s, handle %s, Gramps_Id %s" % (src, src.handle, src.gramps_id)
            )

    def test_fix_encoding(self):
        """Creates a media object with character encoding errors. This tests
        Check.fix_encoding() and also cleanup_missing_photos
        """
        with DbTxn(
            _("Testcase generator step %d") % self.transaction_count, self.db
        ) as self.trans:
            self.transaction_count += 1

            med = Media()
            self.fill_object(med)
            med.set_description("leave this media object invalid description" "\x9f")
            med.set_path("/tmp/click_on_keep_reference.png\x9f")
            med.set_mime_type("image/png\x9f")
            self.db.add_media(med, self.trans)

            med = Media()
            self.fill_object(med)
            med.set_description("reselect this media object invalid " "description\x9f")
            med.set_path("/tmp/click_on_select_file.png\x9f")
            med.set_mime_type("image/png\x9f")
            self.db.add_media(med, self.trans)

            # setup media attached to Source and Citation to be removed

            med = Media()
            self.fill_object(med)
            med.set_description("remove this media object")
            med.set_path("/tmp/click_on_remove_object.png")
            med.set_mime_type("image/png")
            self.db.add_media(med, self.trans)

            src = Source()
            src.set_title("media should be removed from this source")
            ref = MediaRef()
            ref.set_reference_handle(med.handle)
            src.add_media_reference(ref)
            self.db.add_source(src, self.trans)

            cit = Citation()
            self.fill_object(cit)
            cit.set_reference_handle(src.handle)
            cit.set_page("media should be removed from this citation")
            ref = MediaRef()
            ref.set_reference_handle(med.handle)
            cit.add_media_reference(ref)
            self.db.add_citation(cit, self.trans)

    def test_fix_ctrlchars_in_notes(self):
        """Creates a note with control characters. This tests
        Check.fix_ctrlchars_in_notes()
        """
        with DbTxn(
            _("Testcase generator step %d") % self.transaction_count, self.db
        ) as self.trans:
            self.transaction_count += 1

            obj = Note()
            obj.set("This is a text note with a \x03 control character")
            obj.set_format(_choice((Note.FLOWED, Note.FORMATTED)))
            obj.set_type(self.rand_type(NoteType()))
            self.db.add_note(obj, self.trans)

    def test_fix_alt_place_names(self):
        """
        Creates a place with a duplicate of primary in alt_names,
        a blank alt_name, and a duplicate of one of the alt_names. Also
        include two alt names that are almost duplicates, but not quite.
        This tests Check.fix_alt_place_names()
        """
        with DbTxn(
            _("Testcase generator step %d") % self.transaction_count, self.db
        ) as self.trans:
            self.transaction_count += 1

            plac = Place()
            pri_name = PlaceName()
            pri_name.set_value("Primary name")
            alt_name1 = PlaceName()
            alt_name1.set_value("Alt name 1")
            alt_name2 = PlaceName()
            alt_name2.set_value("Alt name 1")
            alt_name2.set_language("testish")
            alt_name3 = PlaceName()
            alt_name3.set_value("Alt name 1")
            alt_name3.set_date_object(Today())
            alt_names = [
                pri_name,
                alt_name1,
                alt_name1,
                PlaceName(),
                alt_name2,
                alt_name3,
            ]
            plac.set_name(pri_name)
            plac.set_alternative_names(alt_names)
            self.db.add_place(plac, self.trans)

    def test_fix_duplicated_grampsid(self):
        """
        Create some duplicate Gramps IDs in various object types
        This tests Check.fix_duplicated_grampsid()
        """
        with DbTxn(
            _("Testcase generator step %d") % self.transaction_count, self.db
        ) as self.trans:
            self.transaction_count += 1
            for dummy in range(0, 2):
                cit = Citation()
                self.fill_object(cit)
                cit.set_gramps_id("C1001")
                self.db.add_citation(cit, self.trans)

                evt = Event()
                self.fill_object(evt)
                evt.set_gramps_id("E1001")
                self.db.add_event(evt, self.trans)

                person1_h = self.generate_person(
                    Person.MALE, "Smith", "Dup Gramps ID test F1001"
                )
                person2_h = self.generate_person(Person.FEMALE, "Jones", None)
                fam = Family()
                fam.set_father_handle(person1_h)
                fam.set_mother_handle(person2_h)
                fam.set_relationship((FamilyRelType.MARRIED, ""))
                fam.set_gramps_id("F1001")
                fam_h = self.db.add_family(fam, self.trans)
                person1 = self.db.get_person_from_handle(person1_h)
                person1.add_family_handle(fam_h)
                self.db.commit_person(person1, self.trans)
                person2 = self.db.get_person_from_handle(person2_h)
                person2.add_family_handle(fam_h)
                self.db.commit_person(person2, self.trans)

                med = Media()
                self.fill_object(med)
                med.set_gramps_id("O1001")
                self.db.add_media(med, self.trans)

                note = Note()
                self.fill_object(note)
                note.set_gramps_id("N1001")
                self.db.add_note(note, self.trans)

                person1_h = self.generate_person(
                    Person.MALE, "Smith", "Dup GID test GID I1001"
                )
                person1 = self.db.get_person_from_handle(person1_h)
                person1.set_gramps_id("I1001")
                self.db.commit_person(person1, self.trans)

                place = Place()
                self.fill_object(place)
                place.set_gramps_id("P1001")
                self.db.add_place(place, self.trans)

                rep = Repository()
                self.fill_object(rep)
                rep.set_gramps_id("R1001")
                self.db.add_repository(rep, self.trans)

                src = Source()
                self.fill_object(src)
                src.set_gramps_id("S1001")
                self.db.add_source(src, self.trans)

    def test_cleanup_missing_photos(self):
        pass

    def test_clean_deleted_name_format(self):
        pass

    def test_cleanup_empty_objects(self):
        """Generate empty objects to test their deletion"""
        with DbTxn(
            _("Testcase generator step %d") % self.transaction_count, self.db
        ) as self.trans:
            self.transaction_count += 1

            pers = Person()
            self.db.add_person(pers, self.trans)

            fam = Family()
            self.db.add_family(fam, self.trans)

            evt = Event()
            self.db.add_event(evt, self.trans)

            place = Place()
            self.db.add_place(place, self.trans)

            src = Source()
            self.db.add_source(src, self.trans)

            cit = Citation()
            self.db.add_citation(cit, self.trans)

            med = Media()
            self.db.add_media(med, self.trans)

            ref = Repository()
            self.db.add_repository(ref, self.trans)

            note = Note()
            self.db.add_note(note, self.trans)

    def test_chk_for_broke_family_link(self):
        """Create various family related errors"""
        # Create a family, that links to father and mother, but father does not
        # link back
        with DbTxn(
            _("Testcase generator step %d") % self.transaction_count, self.db
        ) as self.trans:
            self.transaction_count += 1
            person1_h = self.generate_person(
                Person.MALE,
                "Broken1",
                "Family links to this person, but person does not link back",
            )
            person2_h = self.generate_person(Person.FEMALE, "Broken1", None)
            fam = Family()
            fam.set_father_handle(person1_h)
            fam.set_mother_handle(person2_h)
            fam.set_relationship((FamilyRelType.MARRIED, ""))
            fam_h = self.db.add_family(fam, self.trans)
            # person1 = self.db.get_person_from_handle(person1_h)
            # person1.add_family_handle(fam_h)
            # self.db.commit_person(person1, self.trans)
            person2 = self.db.get_person_from_handle(person2_h)
            person2.add_family_handle(fam_h)
            self.db.commit_person(person2, self.trans)

        # Create a family, that misses the link to the father
        with DbTxn(
            _("Testcase generator step %d") % self.transaction_count, self.db
        ) as self.trans:
            self.transaction_count += 1
            person1_h = self.generate_person(Person.MALE, "Broken2", None)
            person2_h = self.generate_person(Person.FEMALE, "Broken2", None)
            fam = Family()
            # fam.set_father_handle(person1_h)
            fam.set_mother_handle(person2_h)
            fam.set_relationship((FamilyRelType.MARRIED, ""))
            fam_h = self.db.add_family(fam, self.trans)
            person1 = self.db.get_person_from_handle(person1_h)
            person1.add_family_handle(fam_h)
            self.db.commit_person(person1, self.trans)
            person2 = self.db.get_person_from_handle(person2_h)
            person2.add_family_handle(fam_h)
            self.db.commit_person(person2, self.trans)

        # Create a family, that misses the link to the mother
        with DbTxn(
            _("Testcase generator step %d") % self.transaction_count, self.db
        ) as self.trans:
            self.transaction_count += 1
            person1_h = self.generate_person(Person.MALE, "Broken3", None)
            person2_h = self.generate_person(Person.FEMALE, "Broken3", None)
            fam = Family()
            fam.set_father_handle(person1_h)
            # fam.set_mother_handle(person2_h)
            fam.set_relationship((FamilyRelType.MARRIED, ""))
            fam_h = self.db.add_family(fam, self.trans)
            person1 = self.db.get_person_from_handle(person1_h)
            person1.add_family_handle(fam_h)
            self.db.commit_person(person1, self.trans)
            person2 = self.db.get_person_from_handle(person2_h)
            person2.add_family_handle(fam_h)
            self.db.commit_person(person2, self.trans)

        # Create a family, that links to father and mother, but mother does not
        # link back
        with DbTxn(
            _("Testcase generator step %d") % self.transaction_count, self.db
        ) as self.trans:
            self.transaction_count += 1
            person1_h = self.generate_person(Person.MALE, "Broken4", None)
            person2_h = self.generate_person(
                Person.FEMALE,
                "Broken4",
                "Family links to this person, but person does not link back",
            )
            fam = Family()
            fam.set_father_handle(person1_h)
            fam.set_mother_handle(person2_h)
            fam.set_relationship((FamilyRelType.MARRIED, ""))
            fam_h = self.db.add_family(fam, self.trans)
            person1 = self.db.get_person_from_handle(person1_h)
            person1.add_family_handle(fam_h)
            self.db.commit_person(person1, self.trans)
            # person2 = self.db.get_person_from_handle(person2_h)
            # person2.add_family_handle(fam_h)
            # self.db.commit_person(person2, self.trans)

        # Create two married people of same sex.
        # This is NOT detected as an error by plugins/tool/Check.py
        with DbTxn(
            _("Testcase generator step %d") % self.transaction_count, self.db
        ) as self.trans:
            self.transaction_count += 1
            person1_h = self.generate_person(Person.MALE, "Broken5", None)
            person2_h = self.generate_person(Person.MALE, "Broken5", None)
            fam = Family()
            fam.set_father_handle(person1_h)
            fam.set_mother_handle(person2_h)
            fam.set_relationship((FamilyRelType.MARRIED, ""))
            fam_h = self.db.add_family(fam, self.trans)
            person1 = self.db.get_person_from_handle(person1_h)
            person1.add_family_handle(fam_h)
            self.db.commit_person(person1, self.trans)
            person2 = self.db.get_person_from_handle(person2_h)
            person2.add_family_handle(fam_h)
            self.db.commit_person(person2, self.trans)

        # Create a family, that contains an invalid handle to for the father
        with DbTxn(
            _("Testcase generator step %d") % self.transaction_count, self.db
        ) as self.trans:
            self.transaction_count += 1
            # person1_h = self.generate_person(Person.MALE, "Broken6", None)
            person2_h = self.generate_person(Person.FEMALE, "Broken6", None)
            fam = Family()
            fam.set_father_handle("InvalidHandle1")
            fam.set_mother_handle(person2_h)
            fam.set_relationship((FamilyRelType.MARRIED, ""))
            fam_h = self.db.add_family(fam, self.trans)
            # person1 = self.db.get_person_from_handle(person1_h)
            # person1.add_family_handle(fam_h)
            # self.db.commit_person(person1, self.trans)
            person2 = self.db.get_person_from_handle(person2_h)
            person2.add_family_handle(fam_h)
            self.db.commit_person(person2, self.trans)

        # Create a family, that contains an invalid handle to for the mother
        with DbTxn(
            _("Testcase generator step %d") % self.transaction_count, self.db
        ) as self.trans:
            self.transaction_count += 1
            person1_h = self.generate_person(Person.MALE, "Broken7", None)
            # person2_h = self.generate_person(Person.FEMALE, "Broken7", None)
            fam = Family()
            fam.set_father_handle(person1_h)
            fam.set_mother_handle("InvalidHandle2")
            fam.set_relationship((FamilyRelType.MARRIED, ""))
            fam_h = self.db.add_family(fam, self.trans)
            person1 = self.db.get_person_from_handle(person1_h)
            person1.add_family_handle(fam_h)
            self.db.commit_person(person1, self.trans)
            # person2 = self.db.get_person_from_handle(person2_h)
            # person2.add_family_handle(fam_h)
            # self.db.commit_person(person2, self.trans)

        # Creates a family where the child does not link back to the family
        with DbTxn(
            _("Testcase generator step %d") % self.transaction_count, self.db
        ) as self.trans:
            self.transaction_count += 1
            person1_h = self.generate_person(Person.MALE, "Broken8", None)
            person2_h = self.generate_person(Person.FEMALE, "Broken8", None)
            child_h = self.generate_person(None, "Broken8", None)
            fam = Family()
            fam.set_father_handle(person1_h)
            fam.set_mother_handle(person2_h)
            fam.set_relationship((FamilyRelType.MARRIED, ""))
            child_ref = ChildRef()
            child_ref.set_reference_handle(child_h)
            self.fill_object(child_ref)
            fam.add_child_ref(child_ref)
            fam_h = self.db.add_family(fam, self.trans)
            person1 = self.db.get_person_from_handle(person1_h)
            person1.add_family_handle(fam_h)
            self.db.commit_person(person1, self.trans)
            person2 = self.db.get_person_from_handle(person2_h)
            person2.add_family_handle(fam_h)
            self.db.commit_person(person2, self.trans)
            # child = self.db.get_person_from_handle(child_h)
            # person2.add_parent_family_handle(fam_h)
            # self.db.commit_person(child, self.trans)

        # Creates a family where the child is not linked, but the child links
        # to the family
        with DbTxn(
            _("Testcase generator step %d") % self.transaction_count, self.db
        ) as self.trans:
            self.transaction_count += 1
            person1_h = self.generate_person(Person.MALE, "Broken9", None)
            person2_h = self.generate_person(Person.FEMALE, "Broken9", None)
            child_h = self.generate_person(None, "Broken9", None)
            fam = Family()
            fam.set_father_handle(person1_h)
            fam.set_mother_handle(person2_h)
            fam.set_relationship((FamilyRelType.MARRIED, ""))
            # child_ref = ChildRef()
            # child_ref.set_reference_handle(child_h)
            # self.fill_object(child_ref)
            # fam.add_child_ref(child_ref)
            fam_h = self.db.add_family(fam, self.trans)
            person1 = self.db.get_person_from_handle(person1_h)
            person1.add_family_handle(fam_h)
            self.db.commit_person(person1, self.trans)
            person2 = self.db.get_person_from_handle(person2_h)
            person2.add_family_handle(fam_h)
            self.db.commit_person(person2, self.trans)
            child = self.db.get_person_from_handle(child_h)
            child.add_parent_family_handle(fam_h)
            self.db.commit_person(child, self.trans)

        # Creates a family where the child is one of the parents
        with DbTxn(
            _("Testcase generator step %d") % self.transaction_count, self.db
        ) as self.trans:
            self.transaction_count += 1
            person1_h = self.generate_person(Person.MALE, "Broken19", None)
            person2_h = self.generate_person(Person.FEMALE, "Broken19", None)
            child_h = person2_h
            fam = Family()
            fam.set_father_handle(person1_h)
            fam.set_mother_handle(person2_h)
            fam.set_relationship((FamilyRelType.MARRIED, ""))
            child_ref = ChildRef()
            child_ref.set_reference_handle(child_h)
            self.fill_object(child_ref)
            fam.add_child_ref(child_ref)
            fam_h = self.db.add_family(fam, self.trans)
            person1 = self.db.get_person_from_handle(person1_h)
            person1.add_family_handle(fam_h)
            self.db.commit_person(person1, self.trans)
            person2 = self.db.get_person_from_handle(person2_h)
            person2.add_family_handle(fam_h)
            self.db.commit_person(person2, self.trans)
            child = self.db.get_person_from_handle(child_h)
            child.add_parent_family_handle(fam_h)
            self.db.commit_person(child, self.trans)

        # Creates a couple that refer to a family that does not exist in the
        # database.
        with DbTxn(
            _("Testcase generator step %d") % self.transaction_count, self.db
        ) as self.trans:
            self.transaction_count += 1
            person1_h = self.generate_person(Person.MALE, "Broken20", None)
            person2_h = self.generate_person(Person.FEMALE, "Broken20", None)
            #            fam = Family()
            #            fam.set_father_handle(person1_h)
            #            fam.set_mother_handle(person2_h)
            #            fam.set_relationship((FamilyRelType.MARRIED, ''))
            #            child_ref = ChildRef()
            #            # child_ref.set_reference_handle(child_h)
            #            # self.fill_object(child_ref)
            #            # fam.add_child_ref(child_ref)
            #            fam_h = self.db.add_family(fam, self.trans)
            person1 = self.db.get_person_from_handle(person1_h)
            person1.add_family_handle("InvalidHandle3")
            self.db.commit_person(person1, self.trans)
            person2 = self.db.get_person_from_handle(person2_h)
            person2.add_family_handle("InvalidHandle3")
            self.db.commit_person(person2, self.trans)

    #            child = self.db.get_person_from_handle(child_h)
    #            child.add_parent_family_handle(fam_h)
    #            self.db.commit_person(child, self.trans)

    def test_check_parent_relationships(self):
        pass

    def test_cleanup_empty_families(self):
        pass

    def test_cleanup_duplicate_spouses(self):
        pass

    def test_check_events(self):
        """Various event related tests"""
        # Creates a person having a non existing birth event handle set
        with DbTxn(
            _("Testcase generator step %d") % self.transaction_count, self.db
        ) as self.trans:
            self.transaction_count += 1
            person_h = self.generate_person(None, "Broken11", None)
            person = self.db.get_person_from_handle(person_h)
            event_ref = EventRef()
            event_ref.set_reference_handle("InvalidHandle4")
            person.set_birth_ref(event_ref)
            self.db.commit_person(person, self.trans)

        # Creates a person having a non existing death event handle set
        with DbTxn(
            _("Testcase generator step %d") % self.transaction_count, self.db
        ) as self.trans:
            self.transaction_count += 1
            person_h = self.generate_person(None, "Broken12", None)
            person = self.db.get_person_from_handle(person_h)
            event_ref = EventRef()
            event_ref.set_reference_handle("InvalidHandle5")
            person.set_death_ref(event_ref)
            self.db.commit_person(person, self.trans)

        # Creates a person having a non existing event handle set
        with DbTxn(
            _("Testcase generator step %d") % self.transaction_count, self.db
        ) as self.trans:
            self.transaction_count += 1
            person_h = self.generate_person(None, "Broken13", None)
            person = self.db.get_person_from_handle(person_h)
            event_ref = EventRef()
            event_ref.set_reference_handle("InvalidHandle6")
            person.add_event_ref(event_ref)
            self.db.commit_person(person, self.trans)

        # Creates a person with a birth event having an empty type
        with DbTxn(
            _("Testcase generator step %d") % self.transaction_count, self.db
        ) as self.trans:
            self.transaction_count += 1
            person_h = self.generate_person(None, "Broken14", None)
            event = Event()
            # The default type _DEFAULT = BIRTH is set in eventtype
            event.set_type("")
            event.set_description("Test for Broken14")
            event_h = self.db.add_event(event, self.trans)
            event_ref = EventRef()
            event_ref.set_reference_handle(event_h)
            person = self.db.get_person_from_handle(person_h)
            person.set_birth_ref(event_ref)
            self.db.commit_person(person, self.trans)

        # Creates a person with a death event having an empty type
        with DbTxn(
            _("Testcase generator step %d") % self.transaction_count, self.db
        ) as self.trans:
            self.transaction_count += 1
            person_h = self.generate_person(None, "Broken15", None)
            event = Event()
            # The default type _DEFAULT = BIRTH is set in eventtype
            event.set_type("")
            event.set_description("Test for Broken15")
            event_h = self.db.add_event(event, self.trans)
            event_ref = EventRef()
            event_ref.set_reference_handle(event_h)
            person = self.db.get_person_from_handle(person_h)
            person.set_death_ref(event_ref)
            self.db.commit_person(person, self.trans)

        # Creates a person with an event having an empty type
        # This is NOT detected as an error by plugins/tool/Check.py
        with DbTxn(
            _("Testcase generator step %d") % self.transaction_count, self.db
        ) as self.trans:
            self.transaction_count += 1
            person_h = self.generate_person(None, "Broken16", None)
            event = Event()
            # The default type _DEFAULT = BIRTH is set in eventtype
            event.set_type("")
            event.set_description("Test for Broken16")
            event_h = self.db.add_event(event, self.trans)
            event_ref = EventRef()
            event_ref.set_reference_handle(event_h)
            person = self.db.get_person_from_handle(person_h)
            person.add_event_ref(event_ref)
            self.db.commit_person(person, self.trans)

    def test_check_person_references(self):
        pass

    def test_check_family_references(self):
        pass

    def test_check_place_references(self):
        """Tests various place reference errors"""
        # Creates a person with a birth event pointing to nonexisting place
        with DbTxn(
            _("Testcase generator step %d") % self.transaction_count, self.db
        ) as self.trans:
            self.transaction_count += 1
            person_h = self.generate_person(None, "Broken17", None)
            event = Event()
            event.set_type(EventType.BIRTH)
            event.set_place_handle("InvalidHandle7")
            event.set_description("Test for Broken17")
            event_h = self.db.add_event(event, self.trans)
            event_ref = EventRef()
            event_ref.set_reference_handle(event_h)
            person = self.db.get_person_from_handle(person_h)
            person.set_birth_ref(event_ref)
            self.db.commit_person(person, self.trans)

        # Creates a person with an event pointing to nonexisting place
        with DbTxn(
            _("Testcase generator step %d") % self.transaction_count, self.db
        ) as self.trans:
            self.transaction_count += 1
            person_h = self.generate_person(None, "Broken18", None)
            event = Event()
            event.set_type(EventType.BIRTH)
            event.set_place_handle("InvalidHandle8")
            event.set_description("Test for Broken18")
            event_h = self.db.add_event(event, self.trans)
            event_ref = EventRef()
            event_ref.set_reference_handle(event_h)
            person = self.db.get_person_from_handle(person_h)
            person.add_event_ref(event_ref)
            self.db.commit_person(person, self.trans)

    def test_check_source_references(self):
        """Tests various source reference errors"""
        with DbTxn(
            _("Testcase generator step %d") % self.transaction_count, self.db
        ) as self.trans:
            self.transaction_count += 1

            cit = Citation()
            self.fill_object(cit)
            cit.set_reference_handle("unknownsourcehandle")
            cit.set_page("unreferenced citation with invalid source ref")
            self.db.add_citation(cit, self.trans)

            cit = Citation()
            self.fill_object(cit)
            cit.set_reference_handle(None)
            cit.set_page("unreferenced citation with invalid source ref")
            self.db.add_citation(cit, self.trans)

            cit = Citation()
            self.fill_object(cit)
            cit.set_reference_handle("unknownsourcehandle")
            cit.set_page("citation and references to it should be removed")
            c_h1 = self.db.add_citation(cit, self.trans)

            cit = Citation()
            self.fill_object(cit)
            cit.set_reference_handle(None)
            cit.set_page("citation and references to it should be removed")
            c_h2 = self.db.add_citation(cit, self.trans)

            self.create_all_possible_citations(
                [c_h1, c_h2], "Broken21", "non-existent source"
            )

    def test_check_citation_references(self):
        """Generate objects that refer to non-existant citations"""
        with DbTxn(
            _("Testcase generator step %d") % self.transaction_count, self.db
        ) as self.trans:
            self.transaction_count += 1

            c_h = "unknowncitationhandle"
            self.create_all_possible_citations(
                [c_h, ""], "Broken22", "non-existent citation"
            )

    def create_all_possible_citations(self, c_h_list, name, message):
        """Create citations attached to each of the following objects:
        Person
         Name
         Address
         Attribute
         PersonRef
         MediaRef
          Attribute
         LdsOrd

        Family
         Attribute
         ChildRef
         MediaRef
          Attribute
         LdsOrd

        Event
         Attribute
         MediaRef
          Attribute

        Media
         Attribute

        Place
         MediaRef
          Attribute

        Repository (Repositories themselves do not have SourceRefs)
         Address
        """
        med = Media()
        med.set_description(message)
        med.set_path(os.path.abspath(str(ICON)))
        med.set_mime_type(get_type(med.get_path()))
        med.add_citation(_choice(c_h_list))
        # Media : Attribute
        att = Attribute()
        att.set_type(self.rand_type(AttributeType()))
        att.set_value(message)
        att.add_citation(_choice(c_h_list))
        med.add_attribute(att)
        self.db.add_media(med, self.trans)

        person1_h = self.generate_person(Person.MALE, name, None)
        person2_h = self.generate_person(Person.FEMALE, name, None)
        child_h = self.generate_person(None, name, None)
        fam = Family()
        fam.set_father_handle(person1_h)
        fam.set_mother_handle(person2_h)
        fam.set_relationship((FamilyRelType.MARRIED, ""))
        # Family
        fam.add_citation(_choice(c_h_list))
        # Family : Attribute
        att = Attribute()
        att.set_type(self.rand_type(AttributeType()))
        att.set_value(message)
        att.add_citation(_choice(c_h_list))
        fam.add_attribute(att)
        # Family : ChildRef
        child_ref = ChildRef()
        child_ref.set_reference_handle(child_h)
        self.fill_object(child_ref)
        child_ref.add_citation(_choice(c_h_list))
        fam.add_child_ref(child_ref)
        # Family : MediaRef
        mref = MediaRef()
        mref.set_reference_handle(med.handle)
        mref.add_citation(_choice(c_h_list))
        # Family : MediaRef : Attribute
        att = Attribute()
        att.set_type(self.rand_type(AttributeType()))
        att.set_value(message)
        att.add_citation(_choice(c_h_list))
        mref.add_attribute(att)
        fam.add_media_reference(mref)
        # Family : LDSORD
        ldsord = LdsOrd()
        self.fill_object(ldsord)
        # TODO: adapt type and status to family/person
        # if isinstance(obj, Person):
        # if isinstance(obj, Family):
        # pylint: disable=protected-access
        ldsord.set_type(_choice([item[0] for item in LdsOrd._TYPE_MAP]))
        ldsord.set_status(_randint(0, len(LdsOrd._STATUS_MAP) - 1))
        ldsord.add_citation(_choice(c_h_list))
        fam.add_lds_ord(ldsord)
        # Family : EventRef
        evt = Event()
        evt.set_type(EventType.MARRIAGE)
        (dummy, date) = self.rand_date()
        evt.set_date_object(date)
        evt.set_description(message)
        event_h = self.db.add_event(evt, self.trans)
        eref = EventRef()
        eref.set_reference_handle(event_h)
        eref.set_role(self.rand_type(EventRoleType()))
        # Family : EventRef : Attribute
        att = Attribute()
        att.set_type(self.rand_type(AttributeType()))
        att.set_value(message)
        att.add_citation(_choice(c_h_list))
        eref.add_attribute(att)
        fam.add_event_ref(eref)
        fam_h = self.db.add_family(fam, self.trans)
        person1 = self.db.get_person_from_handle(person1_h)
        person1.add_family_handle(fam_h)
        # Person
        person1.add_citation(_choice(c_h_list))
        # Person : Name
        alt_name = Name(person1.get_primary_name())
        alt_name.set_first_name(message)
        alt_name.add_citation(_choice(c_h_list))
        person1.add_alternate_name(alt_name)
        # Person : Address
        add = Address()
        add.set_street(message)
        add.add_citation(_choice(c_h_list))
        person1.add_address(add)
        # Person : Attribute
        att = Attribute()
        att.set_type(self.rand_type(AttributeType()))
        att.set_value(message)
        att.add_citation(_choice(c_h_list))
        person1.add_attribute(att)
        # Person : PersonRef
        asso_h = self.generate_person()
        asso = PersonRef()
        asso.set_reference_handle(asso_h)
        asso.set_relation(self.rand_text(self.SHORT))
        self.fill_object(asso)
        asso.add_citation(_choice(c_h_list))
        person1.add_person_ref(asso)
        # Person : MediaRef
        mref = MediaRef()
        mref.set_reference_handle(med.handle)
        mref.add_citation(_choice(c_h_list))
        # Person : MediaRef : Attribute
        att = Attribute()
        att.set_type(self.rand_type(AttributeType()))
        att.set_value(self.rand_text(self.SHORT))
        att.add_citation(_choice(c_h_list))
        mref.add_attribute(att)
        person1.add_media_reference(mref)
        # Person : LDSORD
        ldsord = LdsOrd()
        self.fill_object(ldsord)
        # TODO: adapt type and status to family/person
        # if isinstance(obj, Person):
        # if isinstance(obj, Family):
        ldsord.set_type(_choice([item[0] for item in LdsOrd._TYPE_MAP]))
        ldsord.set_status(_randint(0, len(LdsOrd._STATUS_MAP) - 1))
        ldsord.add_citation(_choice(c_h_list))
        person1.add_lds_ord(ldsord)
        # Person : EventRef
        evt = Event()
        evt.set_type(EventType.ELECTED)
        (dummy, dat) = self.rand_date()
        evt.set_date_object(dat)
        evt.set_description(message)
        event_h = self.db.add_event(evt, self.trans)
        eref = EventRef()
        eref.set_reference_handle(event_h)
        eref.set_role(self.rand_type(EventRoleType()))
        # Person : EventRef : Attribute
        att = Attribute()
        att.set_type(self.rand_type(AttributeType()))
        att.set_value(message)
        att.add_citation(_choice(c_h_list))
        eref.add_attribute(att)
        person1.add_event_ref(eref)
        self.db.commit_person(person1, self.trans)
        person2 = self.db.get_person_from_handle(person2_h)
        person2.add_family_handle(fam_h)
        self.db.commit_person(person2, self.trans)

        evt = Event()
        evt.set_description(message)
        evt.set_type(EventType.MARRIAGE)
        # Event
        evt.add_citation(_choice(c_h_list))
        # Event : Attribute
        att = Attribute()
        att.set_type(self.rand_type(AttributeType()))
        att.set_value(message)
        att.add_citation(_choice(c_h_list))
        evt.add_attribute(att)
        # Event : MediaRef
        mref = MediaRef()
        mref.set_reference_handle(med.handle)
        mref.add_citation(_choice(c_h_list))
        # Event : MediaRef : Attribute
        att = Attribute()
        att.set_type(self.rand_type(AttributeType()))
        att.set_value(self.rand_text(self.SHORT))
        att.add_citation(_choice(c_h_list))
        mref.add_attribute(att)
        evt.add_media_reference(mref)
        self.db.add_event(evt, self.trans)

        place = Place()
        place.set_title(message)
        place.add_citation(_choice(c_h_list))
        # Place : MediaRef
        mref = MediaRef()
        mref.set_reference_handle(med.handle)
        mref.add_citation(_choice(c_h_list))
        # Place : MediaRef : Attribute
        att = Attribute()
        att.set_type(self.rand_type(AttributeType()))
        att.set_value(self.rand_text(self.SHORT))
        att.add_citation(_choice(c_h_list))
        mref.add_attribute(att)
        place.add_media_reference(mref)
        self.db.add_place(place, self.trans)

        ref = Repository()
        ref.set_name(message)
        ref.set_type(RepositoryType.LIBRARY)
        # Repository : Address
        add = Address()
        add.set_street(message)
        add.add_citation(_choice(c_h_list))
        ref.add_address(add)
        self.db.add_repository(ref, self.trans)

    def test_check_media_references(self):
        pass

    def test_check_repo_references(self):
        pass

    def test_check_note_references(self):
        pass

    def generate_person(
        self, gender=None, lastname=None, note=None, alive_in_year=None
    ):
        """This generates a person with lots of attachments"""
        if not self.cli:
            if self.person_count % 10 == 0:
                while Gtk.events_pending():
                    Gtk.main_iteration()

        pers = Person()
        self.fill_object(pers)

        # Gender
        if gender is None:
            gender = _randint(0, 1)
        if _randint(0, 10) == 1:  # Set some persons to unknown and other gender
            if _randint(0, 1) == 0:
                pers.set_gender(Person.OTHER)
            else:
                pers.set_gender(Person.UNKNOWN)
        else:
            pers.set_gender(gender)

        # Name
        name = Name()
        (firstname, lastname) = self.rand_name(lastname, gender)
        name.set_first_name(firstname)
        surname = Surname()
        surname.set_surname(lastname)
        name.add_surname(surname)
        self.fill_object(name)
        pers.set_primary_name(name)

        # generate some slightly different alternate name
        firstname2 = firstname.replace("m", "n").replace("l", "i").replace("b", "d")
        if firstname2 != firstname:
            alt_name = Name(name)
            self.fill_object(alt_name)
            if _randint(0, 2) == 1:
                surname = Surname()
                surname.set_surname(self.rand_text(self.LASTNAME))
                alt_name.add_surname(surname)
            elif _randint(0, 2) == 1:
                surname = Surname()
                surname.set_surname(lastname)
                alt_name.add_surname(surname)
            if _randint(0, 1) == 1:
                alt_name.set_first_name(firstname2)
            if _randint(0, 1) == 1:
                alt_name.set_title(self.rand_text(self.SHORT))
            if _randint(0, 1) == 1:
                patronymic = Surname()
                patronymic.set_surname(self.rand_text(self.FIRSTNAME_MALE))
                patronymic.set_origintype(NameOriginType.PATRONYMIC)
                alt_name.add_surname(patronymic)
            if _randint(0, 1) == 1:
                alt_name.get_primary_surname().set_prefix(self.rand_text(self.SHORT))
            if _randint(0, 1) == 1:
                alt_name.set_suffix(self.rand_text(self.SHORT))
            if _randint(0, 1) == 1:
                alt_name.set_call_name(self.rand_text(self.FIRSTNAME))
            pers.add_alternate_name(alt_name)
        firstname2 = firstname.replace("a", "e").replace("o", "u").replace("r", "p")
        if firstname2 != firstname:
            alt_name = Name(name)
            self.fill_object(alt_name)
            if _randint(0, 2) == 1:
                surname = Surname()
                surname.set_surname(self.rand_text(self.LASTNAME))
                alt_name.add_surname(surname)
            elif _randint(0, 2) == 1:
                surname = Surname()
                surname.set_surname(lastname)
                alt_name.add_surname(surname)
            if _randint(0, 1) == 1:
                alt_name.set_first_name(firstname2)
            if _randint(0, 1) == 1:
                alt_name.set_title(self.rand_text(self.SHORT))
            if _randint(0, 1) == 1:
                patronymic = Surname()
                patronymic.set_surname(self.rand_text(self.FIRSTNAME_MALE))
                patronymic.set_origintype(NameOriginType.PATRONYMIC)
                alt_name.add_surname(patronymic)
            if _randint(0, 1) == 1:
                alt_name.get_primary_surname().set_prefix(self.rand_text(self.SHORT))
            if _randint(0, 1) == 1:
                alt_name.set_suffix(self.rand_text(self.SHORT))
            if _randint(0, 1) == 1:
                alt_name.set_call_name(self.rand_text(self.FIRSTNAME))
            pers.add_alternate_name(alt_name)

        if not alive_in_year:
            alive_in_year = _randint(1700, 2000)

        b_y = alive_in_year - _randint(0, 60)
        d_y = alive_in_year + _randint(0, 60)

        # birth
        if _randint(0, 1) == 1:
            (dummy, eref) = self.rand_personal_event(EventType.BIRTH, b_y, b_y)
            pers.set_birth_ref(eref)

        # baptism
        if _randint(0, 1) == 1:
            (dummy, eref) = self.rand_personal_event(
                _choice((EventType.BAPTISM, EventType.CHRISTEN)), b_y, b_y + 2
            )
            pers.add_event_ref(eref)

        # death
        if _randint(0, 1) == 1:
            (dummy, eref) = self.rand_personal_event(EventType.DEATH, d_y, d_y)
            pers.set_death_ref(eref)

        # burial
        if _randint(0, 1) == 1:
            (dummy, eref) = self.rand_personal_event(
                _choice((EventType.BURIAL, EventType.CREMATION)), d_y, d_y + 2
            )
            pers.add_event_ref(eref)

        # some other events
        while _randint(0, 5) == 1:
            (dummy, eref) = self.rand_personal_event(None, b_y, d_y)
            pers.add_event_ref(eref)

        # some shared events
        if self.generated_events:
            while _randint(0, 5) == 1:
                e_h = _choice(self.generated_events)
                eref = EventRef()
                self.fill_object(eref)
                eref.set_reference_handle(e_h)
                pers.add_event_ref(eref)

        # PersonRef
        if _randint(0, 3) == 1:
            for dummy in range(0, _randint(1, 2)):
                if self.person_count > self.max_person_count:
                    break
                if alive_in_year:
                    asso_h = self.generate_person(
                        None, None, alive_in_year=alive_in_year
                    )
                else:
                    asso_h = self.generate_person()
                asso = PersonRef()
                asso.set_reference_handle(asso_h)
                asso.set_relation(self.rand_text(self.SHORT))
                self.fill_object(asso)
                pers.add_person_ref(asso)
                if _randint(0, 2) == 0:
                    self.persons_todo.append(asso_h)

        # Note
        if note:
            pass  # Add later?

        person_handle = self.db.add_person(pers, self.trans)

        self.person_count = self.person_count + 1
        self.progress_step()
        if self.person_count % 10 == 1:
            print("person count", self.person_count)
        self.person_dates[person_handle] = (b_y, d_y)

        return person_handle

    def generate_family(self, person1_h):
        """Make up a family"""
        person1 = self.db.get_person_from_handle(person1_h)
        if not person1:
            return
        alive_in_year = None
        if person1_h in self.person_dates:
            (born, died) = self.person_dates[person1_h]
            alive_in_year = min(born + _randint(10, 50), died + _randint(-10, 10))

        if person1.get_gender() == 1:
            if _randint(0, 7) == 1:
                person2_h = None
            else:
                if alive_in_year:
                    person2_h = self.generate_person(0, alive_in_year=alive_in_year)
                else:
                    person2_h = self.generate_person(0)
        else:
            person2_h = person1_h
            if _randint(0, 7) == 1:
                person1_h = None
            else:
                if alive_in_year:
                    person1_h = self.generate_person(1, alive_in_year=alive_in_year)
                else:
                    person1_h = self.generate_person(1)

        if person1_h and _randint(0, 2) > 0:
            self.parents_todo.append(person1_h)
        if person2_h and _randint(0, 2) > 0:
            self.parents_todo.append(person2_h)

        fam = Family()
        self.add_defaults(fam)
        if person1_h:
            fam.set_father_handle(person1_h)
        if person2_h:
            fam.set_mother_handle(person2_h)

        # Avoid adding the same event more than once to the same family
        event_set = set()

        # Generate at least one family event with a probability of 75%
        if _randint(0, 3) > 0:
            (dummy, eref) = self.rand_family_event(None)
            fam.add_event_ref(eref)
            event_set.add(eref.get_reference_handle())

        # generate some more events with a lower probability
        while _randint(0, 3) == 1:
            (dummy, eref) = self.rand_family_event(None)
            if eref.get_reference_handle() in event_set:
                continue
            fam.add_event_ref(eref)
            event_set.add(eref.get_reference_handle())

        # some shared events
        if self.generated_events:
            while _randint(0, 5) == 1:
                typeval = EventType.UNKNOWN
                while int(typeval) not in self.FAMILY_EVENTS:
                    e_h = _choice(self.generated_events)
                    typeval = self.db.get_event_from_handle(e_h).get_type()
                if e_h in event_set:
                    break
                eref = EventRef()
                self.fill_object(eref)
                eref.set_reference_handle(e_h)
                fam.add_event_ref(eref)
                event_set.add(e_h)

        fam_h = self.db.add_family(fam, self.trans)
        self.generated_families.append(fam_h)
        fam = self.db.commit_family(fam, self.trans)
        if person1_h:
            person1 = self.db.get_person_from_handle(person1_h)
            person1.add_family_handle(fam_h)
            self.db.commit_person(person1, self.trans)
        if person2_h:
            person2 = self.db.get_person_from_handle(person2_h)
            person2.add_family_handle(fam_h)
            self.db.commit_person(person2, self.trans)

        lastname = person1.get_primary_name().get_surname()

        for i in range(0, _randint(1, 10)):
            if self.person_count > self.max_person_count:
                break
            if alive_in_year:
                child_h = self.generate_person(
                    None,
                    lastname,
                    alive_in_year=alive_in_year + _randint(16 + 2 * i, 30 + 2 * i),
                )
            else:
                child_h = self.generate_person(None, lastname)
                (born, died) = self.person_dates[child_h]
                alive_in_year = born
            fam = self.db.get_family_from_handle(fam_h)
            child_ref = ChildRef()
            child_ref.set_reference_handle(child_h)
            self.fill_object(child_ref)
            fam.add_child_ref(child_ref)
            self.db.commit_family(fam, self.trans)
            child = self.db.get_person_from_handle(child_h)
            child.add_parent_family_handle(fam_h)
            self.db.commit_person(child, self.trans)
            if _randint(0, 3) > 0:
                self.persons_todo.append(child_h)

    def generate_parents(self, child_h):
        """Add parents to a person, if not present already"""
        if not child_h:
            return
        child = self.db.get_person_from_handle(child_h)
        if not child:
            print("ERROR: Person handle %s does not exist in database" % child_h)
            return
        if child.get_parent_family_handle_list():
            return

        lastname = child.get_primary_name().get_surname()
        if child_h in self.person_dates:
            (born, dummy) = self.person_dates[child_h]
            person1_h = self.generate_person(1, lastname, alive_in_year=born)
            person2_h = self.generate_person(0, alive_in_year=born)
        else:
            person1_h = self.generate_person(1, lastname)
            person2_h = self.generate_person(0)

        if _randint(0, 2) > 1:
            self.parents_todo.append(person1_h)
        if _randint(0, 2) > 1:
            self.parents_todo.append(person2_h)

        fam = Family()
        self.add_defaults(fam)
        fam.set_father_handle(person1_h)
        fam.set_mother_handle(person2_h)
        child_ref = ChildRef()
        child_ref.set_reference_handle(child_h)
        self.fill_object(child_ref)
        fam.add_child_ref(child_ref)
        fam_h = self.db.add_family(fam, self.trans)
        self.generated_families.append(fam_h)
        fam = self.db.commit_family(fam, self.trans)
        person1 = self.db.get_person_from_handle(person1_h)
        person1.add_family_handle(fam_h)
        self.db.commit_person(person1, self.trans)
        person2 = self.db.get_person_from_handle(person2_h)
        person2.add_family_handle(fam_h)
        self.db.commit_person(person2, self.trans)
        child.add_parent_family_handle(fam_h)
        self.db.commit_person(child, self.trans)

    def generate_tags(self):
        """Make up some odd tags"""
        with DbTxn(
            _("Testcase generator step %d") % self.transaction_count, self.db
        ) as self.trans:
            self.transaction_count += 1
            for dummy in range(10):
                tag = Tag()
                tag.set_name(self.rand_text(self.TAG))
                tag.set_color(self.rand_color())
                tag.set_priority(self.db.get_number_of_tags())
                tag_handle = self.db.add_tag(tag, self.trans)
                self.generated_tags.append(tag_handle)

    def add_defaults(self, obj):
        self.fill_object(obj)

    def rand_name(self, lastname=None, gender=None):
        """Create a name pair (first, last)"""
        if gender == Person.MALE:
            firstname = self.rand_text(self.FIRSTNAME_MALE)
        elif gender == Person.FEMALE:
            firstname = self.rand_text(self.FIRSTNAME_FEMALE)
        else:
            firstname = self.rand_text(self.FIRSTNAME)
        if not lastname:
            lastname = self.rand_text(self.LASTNAME)
        return (firstname, lastname)

    def rand_date(self, start=None, end=None):
        """
        Generates a random date object between the given years start and end
        """
        if not start and not end:
            start = _randint(1700, 2000)
        if start and not end:
            end = start + _randint(0, 100)
        if end and not start:
            start = end - _randint(0, 100)
        year = _randint(start, end)

        ndate = Date()
        if _randint(0, 10) == 1:
            # Some get a textual date
            ndate.set_as_text(
                _choice(
                    (self.rand_text(self.SHORT), "Unknown", "??", "Don't know", "TODO!")
                )
            )
        else:
            if _randint(0, 10) == 1:
                # some get an empty date
                pass
            else:
                # regular dates
                calendar = Date.CAL_GREGORIAN
                quality = _choice(
                    (Date.QUAL_NONE, Date.QUAL_ESTIMATED, Date.QUAL_CALCULATED)
                )
                modifier = _choice(
                    (
                        Date.MOD_NONE,
                        Date.MOD_BEFORE,
                        Date.MOD_AFTER,
                        Date.MOD_ABOUT,
                        Date.MOD_RANGE,
                        Date.MOD_SPAN,
                    )
                )
                day = _randint(0, 28)
                if day > 0:  # avoid days without month
                    month = _randint(1, 12)
                else:
                    month = _randint(0, 12)

                if modifier in (Date.MOD_RANGE, Date.MOD_SPAN):
                    day2 = _randint(0, 28)
                    if day2 > 0:
                        month2 = _randint(1, 12)
                    else:
                        month2 = _randint(0, 12)
                    year2 = year + _randint(1, 5)
                    ndate.set(
                        quality,
                        modifier,
                        calendar,
                        (day, month, year, False, day2, month2, year2, False),
                        "",
                    )
                else:
                    ndate.set(
                        quality, modifier, calendar, (day, month, year, False), ""
                    )

        return (year, ndate)

    def fill_object(self, obj):
        """Generic object fill routine"""

        if issubclass(obj.__class__, AddressBase):
            while _randint(0, 1) == 1:
                addr = Address()
                self.fill_object(addr)
                obj.add_address(addr)

        if isinstance(obj, Attribute):
            obj.set_type(self.rand_type(AttributeType()))
            obj.set_value(self.rand_text(self.SHORT))

        if issubclass(obj.__class__, AttributeBase):
            while _randint(0, 1) == 1:
                att = Attribute()
                self.fill_object(att)
                obj.add_attribute(att)

        if isinstance(obj, ChildRef):
            if _randint(0, 3) == 1:
                obj.set_mother_relation(self.rand_type(ChildRefType()))
            if _randint(0, 3) == 1:
                obj.set_father_relation(self.rand_type(ChildRefType()))

        if issubclass(obj.__class__, DateBase):
            if _randint(0, 1) == 1:
                (dummy, dat) = self.rand_date()
                obj.set_date_object(dat)

        if isinstance(obj, Event):
            if _randint(0, 1) == 1:
                obj.set_description(self.rand_text(self.LONG))

        if issubclass(obj.__class__, EventRef):
            obj.set_role(self.rand_type(EventRoleType()))

        if isinstance(obj, Family):
            if _randint(0, 2) == 1:
                obj.set_relationship(self.rand_type(FamilyRelType()))
            else:
                obj.set_relationship(FamilyRelType(FamilyRelType.MARRIED))

        if isinstance(obj, LdsOrd):
            if _randint(0, 1) == 1:
                obj.set_temple(_choice(TEMPLES.name_code_data())[1])

        if issubclass(obj.__class__, LdsOrdBase):
            while _randint(0, 1) == 1:
                ldsord = LdsOrd()
                self.fill_object(ldsord)
                if isinstance(obj, Person):
                    lds_type = _choice([item for item in LDS_INDIVIDUAL_ORD])
                if isinstance(obj, Family):
                    lds_type = LDS_SPOUSE_SEALING[0]
                    if self.generated_families:
                        ldsord.set_family_handle(_choice(self.generated_families))
                ldsord.set_type(lds_type[0])
                status = _choice(lds_type[1])
                if status != LdsOrd.STATUS_NONE:
                    ldsord.set_status(status)
                obj.add_lds_ord(ldsord)

        if isinstance(obj, Location):
            if _randint(0, 1) == 1:
                obj.set_parish(self.rand_text(self.SHORT))

        if issubclass(obj.__class__, LocationBase):
            if _randint(0, 1) == 1:
                obj.set_street(self.rand_text(self.SHORT))
            if _randint(0, 1) == 1:
                obj.set_city(self.rand_text(self.SHORT))
            if _randint(0, 1) == 1:
                obj.set_postal_code(self.rand_text(self.SHORT))
            if _randint(0, 1) == 1:
                obj.set_phone(self.rand_text(self.SHORT))
            if _randint(0, 1) == 1:
                obj.set_state(self.rand_text(self.SHORT))
            if _randint(0, 1) == 1:
                obj.set_country(self.rand_text(self.SHORT))
            if _randint(0, 1) == 1:
                obj.set_county(self.rand_text(self.SHORT))

        if issubclass(obj.__class__, MediaBase):
            # FIXME: frequency changed to prevent recursion
            while _randint(0, 10) == 1:
                obj.add_media_reference(self.fill_object(MediaRef()))

        if isinstance(obj, Media):
            if _randint(0, 3) == 1:
                obj.set_description(str(self.rand_text(self.LONG)))
                path = os.path.abspath(_choice((ICON, LOGO, SPLASH)))
                obj.set_path(str(path))
                mime = get_type(path)
                obj.set_mime_type(mime)
            else:
                obj.set_description(str(self.rand_text(self.SHORT)))
                obj.set_path(os.path.abspath(str(ICON)))
                obj.set_mime_type("image/png")

        if isinstance(obj, MediaRef):
            if not self.generated_media or _randint(0, 10) == 1:
                med = Media()
                self.fill_object(med)
                self.db.add_media(med, self.trans)
                self.generated_media.append(med.get_handle())
            obj.set_reference_handle(_choice(self.generated_media))
            if _randint(0, 1) == 1:
                obj.set_rectangle(
                    (
                        _randint(0, 200),
                        _randint(0, 200),
                        _randint(0, 200),
                        _randint(0, 200),
                    )
                )

        if isinstance(obj, Name):
            obj.set_type(self.rand_type(NameType()))
            if _randint(0, 1) == 1:
                obj.set_title(self.rand_text(self.SHORT))
            if _randint(0, 1) == 1:
                patronymic = Surname()
                patronymic.set_surname(self.rand_text(self.FIRSTNAME_MALE))
                patronymic.set_origintype(NameOriginType.PATRONYMIC)
                obj.add_surname(patronymic)
            if _randint(0, 1) == 1:
                obj.get_primary_surname().set_prefix(self.rand_text(self.SHORT))
            if _randint(0, 1) == 1:
                obj.set_suffix(self.rand_text(self.SHORT))
            if _randint(0, 1) == 1:
                obj.set_call_name(self.rand_text(self.FIRSTNAME))
            if _randint(0, 1) == 1:
                obj.set_group_as(obj.get_surname()[:1])
            # obj.set_display_as()
            # obj.set_sort_as()

        if isinstance(obj, Note):
            n_type = self.rand_type(NoteType())
            if n_type == NoteType.HTML_CODE:
                obj.set(self.rand_text(self.NOTE))
            else:
                obj.set_styledtext(self.rand_text(self.STYLED_TEXT))
            obj.set_format(_choice((Note.FLOWED, Note.FORMATTED)))
            obj.set_type(n_type)

        if issubclass(obj.__class__, NoteBase):
            while _randint(0, 1) == 1:
                if not self.generated_notes or _randint(0, 10) == 1:
                    note = Note()
                    self.fill_object(note)
                    self.db.add_note(note, self.trans)
                    self.generated_notes.append(note.get_handle())
                n_h = _choice(self.generated_notes)
                obj.add_note(n_h)

        if isinstance(obj, Place):
            obj.set_title(self.rand_text(self.LONG))
            obj.set_name(PlaceName(value=self.rand_text(self.SHORT)))
            obj.set_code(self.rand_text(self.SHORT))
            if _randint(0, 1) == 1:
                if _randint(0, 4) == 1:
                    obj.set_longitude(self.rand_text(self.SHORT))
                else:
                    obj.set_longitude(str(_random() * 360.0 - 180.0))
            if _randint(0, 1) == 1:
                if _randint(0, 4) == 1:
                    obj.set_latitude(self.rand_text(self.SHORT))
                else:
                    obj.set_latitude(str(_random() * 180.0 - 90.0))
            while _randint(0, 1) == 1:
                obj.add_alternate_locations(self.fill_object(Location()))

        if issubclass(obj.__class__, PlaceBase):
            if _randint(0, 1) == 1:
                obj.set_place_handle(self.rand_place())

        if issubclass(obj.__class__, BasicPrimaryObject):
            if _randint(0, 1) == 1:
                obj.set_gramps_id(self.rand_text(self.SHORT))

        if issubclass(obj.__class__, PrivacyBase):
            obj.set_privacy(_randint(0, 5) == 1)

        if isinstance(obj, RepoRef):
            if not self.generated_repos or _randint(0, 10) == 1:
                rep = Repository()
                self.fill_object(rep)
                self.db.add_repository(rep, self.trans)
                self.generated_repos.append(rep.get_handle())
            obj.set_reference_handle(_choice(self.generated_repos))
            if _randint(0, 1) == 1:
                obj.set_call_number(self.rand_text(self.SHORT))
            obj.set_media_type(self.rand_type(SourceMediaType()))

        if isinstance(obj, Repository):
            obj.set_type(self.rand_type(RepositoryType()))
            obj.set_name(self.rand_text(self.SHORT))

        if isinstance(obj, Source):
            obj.set_title(self.rand_text(self.SHORT))
            if _randint(0, 1) == 1:
                obj.set_author(self.rand_text(self.SHORT))
            if _randint(0, 1) == 1:
                obj.set_publication_info(self.rand_text(self.LONG))
            if _randint(0, 1) == 1:
                obj.set_abbreviation(self.rand_text(self.SHORT))
            while _randint(0, 1) == 1:
                sattr = SrcAttribute()
                sattr.set_type(self.rand_text(self.SHORT))
                sattr.set_value(self.rand_text(self.SHORT))
                obj.add_attribute(sattr)
            while _randint(0, 1) == 1:
                rep_ref = RepoRef()
                self.fill_object(rep_ref)
                obj.add_repo_reference(rep_ref)

        if issubclass(obj.__class__, CitationBase):
            while _randint(0, 1) == 1:
                if not self.generated_citations or _randint(1, 10) == 1:
                    cit = Citation()
                    self.fill_object(cit)
                    self.db.add_citation(cit, self.trans)
                    self.generated_citations.append(cit.get_handle())
                s_h = _choice(self.generated_citations)
                obj.add_citation(s_h)

        if isinstance(obj, Citation):
            if not self.generated_sources or _randint(0, 10) == 1:
                src = Source()
                self.fill_object(src)
                self.db.add_source(src, self.trans)
                self.generated_sources.append(src.get_handle())
            obj.set_reference_handle(_choice(self.generated_sources))
            if _randint(0, 1) == 1:
                obj.set_page(self.rand_text(self.NUMERIC))
            # if _randint(0, 1) == 1:
            #    obj.set_text( self.rand_text(self.SHORT))
            # if _randint(0, 1) == 1:
            #    (year, dat) = self.rand_date( )
            #    obj.set_date_object( dat)
            # sort to provide deterministic output in unit tests
            obj.set_confidence_level(_choice(sorted(conf_strings.keys())))

        if issubclass(obj.__class__, TagBase):
            if _randint(0, 1) == 1:
                obj.set_tag_list(self.rand_tags())

        if issubclass(obj.__class__, UrlBase):
            while _randint(0, 1) == 1:
                url = Url()
                self.fill_object(url)
                obj.add_url(url)

        if isinstance(obj, Url):
            obj.set_path(
                "http://www.gramps-project.org/?test=%s" % self.rand_text(self.SHORT)
            )
            obj.set_description(self.rand_text(self.SHORT))
            obj.set_type(self.rand_type(UrlType()))

        return obj

    def rand_personal_event(self, e_type=None, start=None, end=None):
        """Random personal event"""
        if e_type:
            typeval = EventType(e_type)
        else:
            typeval = self.rand_type(EventType())
        return self._rand_event(typeval, start, end)

    def rand_family_event(self, e_type=None, start=None, end=None):
        """Random family event"""
        if e_type:
            EventType(e_type)
        else:
            typeval = EventType.UNKNOWN
            while int(typeval) not in self.FAMILY_EVENTS:
                typeval = self.rand_type(EventType())
        return self._rand_event(typeval, start, end)

    def _rand_event(self, e_type, start, end):
        """Random general event"""
        evt = Event()
        self.fill_object(evt)
        evt.set_type(e_type)
        (year, dat) = self.rand_date(start, end)
        evt.set_date_object(dat)
        event_h = self.db.add_event(evt, self.trans)
        self.generated_events.append(event_h)
        event_ref = EventRef()
        self.fill_object(event_ref)
        event_ref.set_reference_handle(event_h)
        return (year, event_ref)

    def rand_type(self, gtype):
        if issubclass(gtype.__class__, GrampsType):
            gmap = gtype.get_map()
            # sort to provide deterministic output in unit tests
            key = _choice(sorted(gmap.keys()))
            if key == gtype.get_custom():
                value = self.rand_text(self.SHORT)
            else:
                value = ""
            gtype.set((key, value))
            return gtype

    def rand_place(self):
        if not self.generated_places or _randint(0, 10) == 1:
            self.generate_place()
        return _choice(self.generated_places)

    def generate_place(self):
        parent_handle = None
        for type_num in range(1, 8):
            if type_num > 1 and _randint(1, 3) == 1:
                # skip some levels in the place hierarchy
                continue
            place = Place()
            place.set_type(PlaceType(type_num))
            if parent_handle is not None:
                self.add_parent_place(place, parent_handle)
            if type_num > 1 and _randint(1, 3) == 1:
                # add additional parent place
                parent_handle = self.find_parent_place(type_num - 1)
                if parent_handle is not None:
                    self.add_parent_place(place, parent_handle)
            self.fill_object(place)
            self.db.add_place(place, self.trans)
            parent_handle = place.get_handle()
            self.generated_places.append(place.get_handle())
            self.parent_places[type_num].append(place.get_handle())

    def find_parent_place(self, type_num):
        if len(self.parent_places[type_num]) > 0:
            return _choice(self.parent_places[type_num])
        else:
            return None

    def add_parent_place(self, place, handle):
        place_ref = PlaceRef()
        place_ref.ref = handle
        dummy, random_date = self.rand_date()
        place_ref.set_date_object(random_date)
        place.add_placeref(place_ref)

    def rand_text(self, t_type=None):
        """make random text strings according to desired type"""
        # for lastnamesnames
        syllables1 = [
            "sa",
            "li",
            "na",
            "ma",
            "no",
            "re",
            "mi",
            "cha",
            "ki",
            "du",
            "ba",
            "ku",
            "el",
        ]
        # for firstnames
        syllables2 = [
            "as",
            "il",
            "an",
            "am",
            "on",
            "er",
            "im",
            "ach",
            "ik",
            "ud",
            "ab",
            "ul",
            "le",
        ]
        # others
        syllables3 = [
            "ka",
            "po",
            "lo",
            "chi",
            "she",
            "di",
            "fa",
            "go",
            "ja",
            "ne",
            "pe",
        ]

        syllables = syllables1 + syllables2 + syllables3
        minwords = 5
        maxwords = 8
        minsyllables = 2
        maxsyllables = 5

        # result = "" if t_type != self.STYLED_TEXT else StyledText("")
        if t_type == self.STYLED_TEXT:
            result = StyledText("")
        else:
            result = ""

        if self.options_dict["specialchars"]:
            result = result + "ä<ö&ü%ß'\""

        if self.options_dict["add_serial"] and t_type != self.TAG:
            result = result + "#+#%06d#-#" % self.text_serial_number
            self.text_serial_number = self.text_serial_number + 1

        if not t_type:
            t_type = self.SHORT

        if t_type == self.SHORT or t_type == self.TAG:
            minwords = 1
            maxwords = 3
            minsyllables = 2
            maxsyllables = 4

        if t_type == self.LONG:
            minwords = 5
            maxwords = 8
            minsyllables = 2
            maxsyllables = 5

        if t_type == self.FIRSTNAME:
            t_type = _choice((self.FIRSTNAME_MALE, self.FIRSTNAME_FEMALE))

        if t_type == self.FIRSTNAME_MALE or t_type == self.FIRSTNAME_FEMALE:
            syllables = syllables2
            minwords = 1
            maxwords = 5
            minsyllables = 2
            maxsyllables = 5
            if not self.options_dict["long_names"]:
                maxwords = 2
                maxsyllables = 3

        if t_type == self.LASTNAME:
            syllables = syllables1
            minwords = 1
            maxwords = 1
            minsyllables = 2
            maxsyllables = 5
            if not self.options_dict["long_names"]:
                maxsyllables = 3

        if t_type == self.NOTE or t_type == self.STYLED_TEXT:
            result = result + "Generated by TestcaseGenerator."
            minwords = 20
            maxwords = 100

        if t_type == self.NUMERIC:
            if _randint(0, 1) == 1:
                return "%d %s" % (_randint(1, 100), result)
            if _randint(0, 1) == 1:
                return "%d, %d %s" % (_randint(1, 100), _randint(100, 1000), result)
            med = _randint(100, 1000)
            return "%d - %d %s" % (med, med + _randint(1, 5), result)

        for dummy in range(0, _randint(minwords, maxwords)):
            if result:
                result = result + " "
            word = ""
            for j in range(0, _randint(minsyllables, maxsyllables)):
                word = word + _choice(syllables)
            if t_type == self.FIRSTNAME_MALE:
                word = word + _choice(("a", "e", "i", "o", "u"))
            if _randint(0, 3) == 1:
                word = word.title()
            if t_type == self.NOTE:
                if _randint(0, 10) == 1:
                    word = "<b>%s</b>" % word
                elif _randint(0, 10) == 1:
                    word = "<i>%s</i>" % word
                elif _randint(0, 10) == 1:
                    word = "<i>%s</i>" % word
                if _randint(0, 20) == 1:
                    word = word + "."
                elif _randint(0, 30) == 1:
                    word = word + ".\n"
            if t_type == self.STYLED_TEXT:
                tags = []
                if _randint(0, 10) == 1:
                    tags += [
                        StyledTextTag(StyledTextTagType.BOLD, True, [(0, len(word))])
                    ]
                elif _randint(0, 10) == 1:
                    tags += [
                        StyledTextTag(StyledTextTagType.ITALIC, True, [(0, len(word))])
                    ]
                elif _randint(0, 10) == 1:
                    tags += [
                        StyledTextTag(
                            StyledTextTagType.UNDERLINE, True, [(0, len(word))]
                        )
                    ]
                sword = StyledText(word, tags)
                if _randint(0, 20) == 1:
                    sword = sword + "."
                elif _randint(0, 30) == 1:
                    sword = sword + ".\n"
                result = StyledText("").join((result, sword))
            else:
                result += word

        if t_type == self.LASTNAME:
            case = _randint(0, 2)
            if case == 0:
                result = result.title()
            elif case == 1:
                result = result.upper()

        if self.options_dict["add_linebreak"] and t_type != self.TAG:
            result = result + "\nNEWLINE"

        return result

    def rand_color(self):
        return "#%012X" % _randint(0, 281474976710655)

    def rand_tags(self):
        maxtags = 5
        taglist = []
        for dummy in range(0, _randint(1, maxtags)):
            tag = _choice(self.generated_tags)
            if tag not in taglist:
                taglist.append(tag)
        return taglist


# -----------------------------------------------------------------------
#
# The options class for the tool
#
# -----------------------------------------------------------------------
class TestcaseGeneratorOptions(tool.ToolOptions):
    """
    Defines options and provides handling interface.
    """

    def __init__(self, name, person_id=None):
        tool.ToolOptions.__init__(self, name, person_id)

        # Options specific for this report
        self.options_dict = {
            "lowlevel": 0,
            "bugs": 0,
            "persons": 1,
            "person_count": 2000,
            "long_names": 0,
            "specialchars": 0,
            "add_serial": 0,
            "add_linebreak": 0,
        }
        self.options_help = {
            "lowlevel": (
                "=0/1",
                "Whether to create low level database errors.",
                ["Skip test", "Create low level database errors"],
                True,
            ),
            "bugs": (
                "=0/1",
                "Whether to create invalid database references.",
                ["Skip test", "Create invalid Database references"],
                True,
            ),
            "persons": (
                "=0/1",
                "Whether to create a bunch of dummy persons",
                ["Dont create persons", "Create dummy persons"],
                True,
            ),
            "person_count": (
                "=int",
                "Number of dummy persons to generate",
                "Number of persons",
            ),
            "long_names": (
                "=0/1",
                "Whether to create short or long names",
                ["Short names", "Long names"],
                True,
            ),
            "specialchars": (
                "=0/1",
                "Whether to ass some special characters to every" " text field",
                ["No special characters", "Add special characters"],
                True,
            ),
            "add_serial": (
                "=0/1",
                "Whether to add a serial number to every text " "field",
                ["No serial", "Add serial number"],
                True,
            ),
            "add_linebreak": (
                "=0/1",
                "Whether to add a line break to every text " "field",
                ["No linebreak", "Add line break"],
                True,
            ),
        }
