#
# Gramps - a GTK+/GNOME based genealogy program
#
# Copyright (C) 2000-2002 Bruce J. DeGrasse
# Copyright (C) 2000-2007 Donald N. Allingham
# Copyright (C) 2007-2009 Brian G. Matherly
# Copyright (C) 2007      Robert Cawley  <rjc@cawley.id.au>
# Copyright (C) 2008-2009 James Friedmann <jfriedmannj@gmail.com>
# Copyright (C) 2009      Benny Malengier <benny.malengier@gramps-project.org>
# Copyright (C) 2010      Jakim Friant
# Copyright (C) 2010      Vlada Peri\u0107
# Copyright (C) 2011      Matt Keenan <matt.keenan@gmail.com>
# Copyright (C) 2011      Tim G L Lyons
# Copyright (C) 2013      Paul Franklin
#
# 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.
#

"""Reports/Text Reports/Detailed Descendant Report"""

#------------------------------------------------------------------------
#
# standard python modules
#
#------------------------------------------------------------------------
from functools import partial

#------------------------------------------------------------------------
#
# GRAMPS modules
#
#------------------------------------------------------------------------
from gramps.gen.const import GRAMPS_LOCALE as glocale
_ = glocale.translation.gettext
from gramps.gen.errors import ReportError
from gramps.gen.lib import FamilyRelType, Person, NoteType
from gramps.gen.plug.menu import (BooleanOption, NumberOption, PersonOption,
                           EnumeratedListOption)
from gramps.gen.plug.docgen import (IndexMark, FontStyle, ParagraphStyle,
                             FONT_SANS_SERIF, FONT_SERIF,
                             INDEX_TYPE_TOC, PARA_ALIGN_CENTER)
from gramps.gen.plug.report import (Report, Bibliography)
from gramps.gen.plug.report import endnotes
from gramps.gen.plug.report import utils as ReportUtils
from gramps.gen.plug.report import MenuReportOptions
from gramps.gen.plug.report import stdoptions
from gramps.plugins.lib.libnarrate import Narrator

#------------------------------------------------------------------------
#
# Constants
#
#------------------------------------------------------------------------
EMPTY_ENTRY = "_____________"
HENRY = "123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"

#------------------------------------------------------------------------
#
#
#
#------------------------------------------------------------------------
class DetDescendantReport(Report):

    def __init__(self, database, options, user):
        """
        Create the DetDescendantReport object that produces the report.

        The arguments are:

        database        - the GRAMPS database instance
        options         - instance of the Options class for this report
        user            - a gen.user.User() instance

        This report needs the following parameters (class variables)
        that come in the options class.

        gen           - Maximum number of generations to include.
        pagebgg       - Whether to include page breaks between generations.
        pageben       - Whether to include page break before End Notes.
        fulldates     - Whether to use full dates instead of just year.
        listc         - Whether to list children.
        incnotes      - Whether to include notes.
        usecall       - Whether to use the call name as the first name.
        repplace      - Whether to replace missing Places with ___________.
        repdate       - Whether to replace missing Dates with ___________.
        computeage    - Whether to compute age.
        omitda        - Whether to omit duplicate ancestors (e.g. when distant cousins marry).
        verbose       - Whether to use complete sentences.
        numbering     - The descendancy numbering system to be utilized.
        desref        - Whether to add descendant references in child list.
        incphotos     - Whether to include images.
        incnames      - Whether to include other names.
        incevents     - Whether to include events.
        incaddresses  - Whether to include addresses.
        incsrcnotes   - Whether to include source notes in the Endnotes section. Only works if Include sources is selected.
        incmates      - Whether to include information about spouses
        incattrs      - Whether to include attributes
        incpaths      - Whether to include the path of descendancy from the start-person to each descendant.
        incssign      - Whether to include a sign ('+') before the descendant number in the child-list to indicate a child has succession.
        pid           - The Gramps ID of the center person for the report.
        name_format   - Preferred format to display names
        incmateref    - Whether to print mate information or reference
        """
        Report.__init__(self, database, options, user)

        self.map = {}
        self._user = user

        menu = options.menu
        get_option_by_name = menu.get_option_by_name
        get_value = lambda name: get_option_by_name(name).get_value()
        self.max_generations = get_value('gen')
        self.pgbrk         = get_value('pagebbg')
        self.pgbrkenotes   = get_value('pageben')
        self.fulldate      = get_value('fulldates')
        use_fulldate     = self.fulldate
        self.listchildren  = get_value('listc')
        self.inc_notes     = get_value('incnotes')
        use_call           = get_value('usecall')
        blankplace         = get_value('repplace')
        blankdate          = get_value('repdate')
        self.calcageflag   = get_value('computeage')
        self.dubperson     = get_value('omitda')
        self.verbose       = get_value('verbose')
        self.numbering     = get_value('numbering')
        self.childref      = get_value('desref')
        self.addimages     = get_value('incphotos')
        self.inc_names     = get_value('incnames')
        self.inc_events    = get_value('incevents')
        self.inc_addr      = get_value('incaddresses')
        self.inc_sources   = get_value('incsources')
        self.inc_srcnotes  = get_value('incsrcnotes')
        self.inc_mates     = get_value('incmates')
        self.inc_attrs     = get_value('incattrs')
        self.inc_paths     = get_value('incpaths')
        self.inc_ssign     = get_value('incssign')
        self.inc_materef   = get_value('incmateref')
        pid                = get_value('pid')
        self.center_person = database.get_person_from_gramps_id(pid)
        if (self.center_person == None) :
            raise ReportError(_("Person %s is not in the Database") % pid )

        self.gen_handles = {}
        self.prev_gen_handles = {}
        self.gen_keys = []
        self.dnumber = {}
        self.dmates = {}

        if blankdate:
            empty_date = EMPTY_ENTRY
        else:
            empty_date = ""

        if blankplace:
            empty_place = EMPTY_ENTRY
        else:
            empty_place = ""

        self._locale = self.set_locale(get_value('trans'))

        name_format = menu.get_option_by_name("name_format").get_value()
        if name_format != 0:
            self._name_display.set_default_format(name_format)

        self.__narrator = Narrator(self.database, self.verbose,
                                   use_call, use_fulldate,
                                   empty_date, empty_place,
                                   nlocale=self._locale,
                                   get_endnote_numbers=self.endnotes)

        self.bibli = Bibliography(Bibliography.MODE_DATE|Bibliography.MODE_PAGE)

    def apply_henry_filter(self,person_handle, index, pid, cur_gen=1):
        if (not person_handle) or (cur_gen > self.max_generations):
            return
        self.dnumber[person_handle] = pid
        self.map[index] = person_handle

        if len(self.gen_keys) < cur_gen:
            self.gen_keys.append([index])
        else:
            self.gen_keys[cur_gen-1].append(index)

        person = self.database.get_person_from_handle(person_handle)
        index = 0
        for family_handle in person.get_family_handle_list():
            family = self.database.get_family_from_handle(family_handle)
            for child_ref in family.get_child_ref_list():
                ix = max(self.map)
                self.apply_henry_filter(child_ref.ref, ix+1,
                                  pid+HENRY[index], cur_gen+1)
                index += 1

    # Filter for d'Aboville numbering
    def apply_daboville_filter(self,person_handle, index, pid, cur_gen=1):
        if (not person_handle) or (cur_gen > self.max_generations):
            return
        self.dnumber[person_handle] = pid
        self.map[index] = person_handle

        if len(self.gen_keys) < cur_gen:
            self.gen_keys.append([index])
        else:
            self.gen_keys[cur_gen-1].append(index)

        person = self.database.get_person_from_handle(person_handle)
        index = 1
        for family_handle in person.get_family_handle_list():
            family = self.database.get_family_from_handle(family_handle)
            for child_ref in family.get_child_ref_list():
                ix = max(self.map)
                self.apply_daboville_filter(child_ref.ref, ix+1,
                                  pid+"."+str(index), cur_gen+1)
                index += 1

    # Filter for Record-style (Modified Register) numbering
    def apply_mod_reg_filter_aux(self, person_handle, index, cur_gen=1):
        if (not person_handle) or (cur_gen > self.max_generations):
            return
        self.map[index] = person_handle

        if len(self.gen_keys) < cur_gen:
            self.gen_keys.append([index])
        else:
            self.gen_keys[cur_gen-1].append(index)

        person = self.database.get_person_from_handle(person_handle)

        for family_handle in person.get_family_handle_list():
            family = self.database.get_family_from_handle(family_handle)
            for child_ref in family.get_child_ref_list():
                ix = max(self.map)
                self.apply_mod_reg_filter_aux(child_ref.ref, ix+1, cur_gen+1)

    def apply_mod_reg_filter(self, person_handle):
        self.apply_mod_reg_filter_aux(person_handle, 1, 1)
        mod_reg_number = 1
        for generation in range(len(self.gen_keys)):
            for key in self.gen_keys[generation]:
                person_handle = self.map[key]
                if person_handle not in self.dnumber:
                    self.dnumber[person_handle] = mod_reg_number
                    mod_reg_number += 1

    def write_report(self):
        """
        This function is called by the report system and writes the report.
        """
        if self.numbering == "Henry":
            self.apply_henry_filter(self.center_person.get_handle(), 1, "1")
        elif self.numbering == "d'Aboville":
            self.apply_daboville_filter(self.center_person.get_handle(), 1, "1")
        elif self.numbering == "Record (Modified Register)":
            self.apply_mod_reg_filter(self.center_person.get_handle())
        else:
            raise AttributeError("no such numbering: '%s'" % self.numbering)

        name = self._name_display.display_name(self.center_person.get_primary_name())

        self.doc.start_paragraph("DDR-Title")

        # feature request 2356: avoid genitive form
        title = self._("Descendant Report for %(person_name)s") % {
                    'person_name' : name }
        mark = IndexMark(title, INDEX_TYPE_TOC, 1)
        self.doc.write_text(title, mark)
        self.doc.end_paragraph()

        generation = 0

        self.numbers_printed = list()
        for generation in range(len(self.gen_keys)):
            if self.pgbrk and generation > 0:
                self.doc.page_break()
            self.doc.start_paragraph("DDR-Generation")
            text = self._("Generation %d") % (generation+1)
            mark = IndexMark(text, INDEX_TYPE_TOC, 2)
            self.doc.write_text(text, mark)
            self.doc.end_paragraph()
            if self.childref:
                self.prev_gen_handles = self.gen_handles.copy()
                self.gen_handles.clear()

            for key in self.gen_keys[generation]:
                person_handle = self.map[key]
                self.gen_handles[person_handle] = key
                self.write_person(key)

        if self.inc_sources:
            if self.pgbrkenotes:
                self.doc.page_break()
            # it ignores language set for Note type (use locale)
            endnotes.write_endnotes(self.bibli, self.database, self.doc,
                                    printnotes=self.inc_srcnotes,
                                    elocale=self._locale)

    def write_path(self, person):
        path = []
        while True:
            #person changes in the loop
            family_handle = person.get_main_parents_family_handle()
            if family_handle:
                family = self.database.get_family_from_handle(family_handle)
                mother_handle = family.get_mother_handle()
                father_handle = family.get_father_handle()
                if mother_handle and mother_handle in self.dnumber:
                    person = self.database.get_person_from_handle(mother_handle)
                    person_name = \
                        self._name_display.display_name(person.get_primary_name())
                    path.append(person_name)
                elif father_handle and father_handle in self.dnumber:
                    person = self.database.get_person_from_handle(father_handle)
                    person_name = \
                        self._name_display.display_name(person.get_primary_name())
                    path.append(person_name)
                else:
                    break
            else:
                break

        index = len(path)

        if index:
            self.doc.write_text("(")

        for name in path:
            if index == 1:
                self.doc.write_text(name + "-" + str(index) + ") ")
            else:
                # translators: needed for Arabic, ignore otherwise
                self.doc.write_text(name + "-" + str(index) + self._("; "))
            index -= 1

    def write_person(self, key):
        """Output birth, death, parentage, marriage and notes information """

        person_handle = self.map[key]
        person = self.database.get_person_from_handle(person_handle)

        val = self.dnumber[person_handle]

        if val in self.numbers_printed:
            return
        else:
            self.numbers_printed.append(val)

        self.doc.start_paragraph("DDR-First-Entry","%s." % val)

        name = self._name_display.display_formal(person)
        mark = ReportUtils.get_person_mark(self.database, person)

        self.doc.start_bold()
        self.doc.write_text(name, mark)
        if name[-1:] == '.':
            self.doc.write_text_citation("%s " % self.endnotes(person))
        else:
            self.doc.write_text_citation("%s. " % self.endnotes(person))
        self.doc.end_bold()

        if self.inc_paths:
            self.write_path(person)

        if self.dubperson:
            # Check for duplicate record (result of distant cousins marrying)
            for dkey in sorted(self.map):
                if dkey >= key:
                    break
                if self.map[key] == self.map[dkey]:
                    self.doc.write_text(self._(
                        "%(name)s is the same person as [%(id_str)s].") % {
                            'name' :'',
                            'id_str': self.dnumber[self.map[dkey]],
                            }
                        )
                    self.doc.end_paragraph()
                    return

        self.doc.end_paragraph()

        self.write_person_info(person)

        if (self.inc_mates or self.listchildren or self.inc_notes or
            self.inc_events or self.inc_attrs):
            for family_handle in person.get_family_handle_list():
                family = self.database.get_family_from_handle(family_handle)
                if self.inc_mates:
                    self.__write_mate(person, family)
                if self.listchildren:
                    self.__write_children(family)
                if self.inc_notes:
                    self.__write_family_notes(family)
                first = True
                if self.inc_events:
                    first = self.__write_family_events(family)
                if self.inc_attrs:
                    self.__write_family_attrs(family, first)

    def write_event(self, event_ref):
        text = ""
        event = self.database.get_event_from_handle(event_ref.ref)

        if self.fulldate:
            date = self._get_date(event.get_date_object())
        else:
            date = event.get_date_object().get_year()

        ph = event.get_place_handle()
        if ph:
            place = self.database.get_place_from_handle(ph).get_title()
        else:
            place = ''

        self.doc.start_paragraph('DDR-MoreDetails')
        event_name = self._get_type(event.get_type())
        if date and place:
            text +=  self._('%(date)s, %(place)s') % {
                       'date' : date, 'place' : place }
        elif date:
            text += self._('%(date)s') % {'date' : date}
        elif place:
            text += self._('%(place)s') % { 'place' : place }

        if event.get_description():
            if text:
                text += ". "
            text += event.get_description()

        text += self.endnotes(event)

        if text:
            text += ". "

        text = self._('%(event_name)s: %(event_text)s') % {
                 'event_name' : self._(event_name),
                 'event_text' : text }

        self.doc.write_text_citation(text)

        if self.inc_attrs:
            text = ""
            attr_list = event.get_attribute_list()
            attr_list.extend(event_ref.get_attribute_list())
            for attr in attr_list:
                if text:
                    # translators: needed for Arabic, ignore otherwise
                    text += self._("; ")
                attrName = self._get_type(attr.get_type())
                text += self._("%(type)s: %(value)s%(endnotes)s") % {
                    'type'     : self._(attrName),
                    'value'    : attr.get_value(),
                    'endnotes' : self.endnotes(attr) }
            text = " " + text
            self.doc.write_text_citation(text)

        self.doc.end_paragraph()

        if self.inc_notes:
            # if the event or event reference has a note attached to it,
            # get the text and format it correctly
            notelist = event.get_note_list()
            notelist.extend(event_ref.get_note_list())
            for notehandle in notelist:
                note = self.database.get_note_from_handle(notehandle)
                self.doc.write_styled_note(note.get_styledtext(),
                        note.get_format(),"DDR-MoreDetails",
                        contains_html= note.get_type() == NoteType.HTML_CODE)

    def __write_parents(self, person):
        family_handle = person.get_main_parents_family_handle()
        if family_handle:
            family = self.database.get_family_from_handle(family_handle)
            mother_handle = family.get_mother_handle()
            father_handle = family.get_father_handle()
            if mother_handle:
                mother = self.database.get_person_from_handle(mother_handle)
                mother_name = \
                    self._name_display.display_name(mother.get_primary_name())
                mother_mark = ReportUtils.get_person_mark(self.database, mother)
            else:
                mother_name = ""
                mother_mark = ""
            if father_handle:
                father = self.database.get_person_from_handle(father_handle)
                father_name = \
                    self._name_display.display_name(father.get_primary_name())
                father_mark = ReportUtils.get_person_mark(self.database, father)
            else:
                father_name = ""
                father_mark = ""
            text = self.__narrator.get_child_string(father_name, mother_name)
            if text:
                self.doc.write_text(text)
                if father_mark:
                    self.doc.write_text("", father_mark)
                if mother_mark:
                    self.doc.write_text("", mother_mark)

    def write_marriage(self, person):
        """
        Output marriage sentence.
        """
        is_first = True
        for family_handle in person.get_family_handle_list():
            family = self.database.get_family_from_handle(family_handle)
            spouse_handle = ReportUtils.find_spouse(person, family)
            spouse = self.database.get_person_from_handle(spouse_handle)
            if spouse:
                name = self._name_display.display_formal(spouse)
            else:
                name = ""
            text = ""
            spouse_mark = ReportUtils.get_person_mark(self.database, spouse)

            text = self.__narrator.get_married_string(family, is_first, self._name_display)

            if text:
                self.doc.write_text_citation(text, spouse_mark)
                is_first = False

    def __write_mate(self, person, family):
        """
        Write information about the person's spouse/mate.
        """
        if person.get_gender() == Person.MALE:
            mate_handle = family.get_mother_handle()
        else:
            mate_handle = family.get_father_handle()

        if mate_handle:
            mate = self.database.get_person_from_handle(mate_handle)

            self.doc.start_paragraph("DDR-MoreHeader")
            name = self._name_display.display_formal(mate)
            mark = ReportUtils.get_person_mark(self.database, mate)
            if family.get_relationship() == FamilyRelType.MARRIED:
                self.doc.write_text(self._("Spouse: %s") % name, mark)
            else:
                self.doc.write_text(self._("Relationship with: %s") % name, mark)
            if name[-1:] != '.':
                self.doc.write_text(".")
            self.doc.write_text_citation(self.endnotes(mate))
            self.doc.end_paragraph()

            if not self.inc_materef:
                # Don't want to just print reference
                self.write_person_info(mate)
            else:
                # Check to see if we've married a cousin
                if mate_handle in self.dnumber:
                    self.doc.start_paragraph('DDR-MoreDetails')
                    self.doc.write_text_citation(
                        self._("Ref: %(number)s. %(name)s") %
                        {'number': self.dnumber[mate_handle], 'name': name})
                    self.doc.end_paragraph()
                else:
                    self.dmates[mate_handle] = person.get_handle()
                    self.write_person_info(mate)

    def __get_mate_names(self, family):
        mother_handle = family.get_mother_handle()
        if mother_handle:
            mother = self.database.get_person_from_handle(mother_handle)
            mother_name = self._name_display.display(mother)
        else:
            mother_name = self._("unknown")

        father_handle = family.get_father_handle()
        if father_handle:
            father = self.database.get_person_from_handle(father_handle)
            father_name = self._name_display.display(father)
        else:
            father_name = self._("unknown")

        return mother_name, father_name

    def __write_children(self, family):
        """
        List the children for the given family.
        """
        if not family.get_child_ref_list():
            return

        mother_name, father_name = self.__get_mate_names(family)

        self.doc.start_paragraph("DDR-ChildTitle")
        self.doc.write_text(
                        self._("Children of %(mother_name)s and %(father_name)s") %
                            {'father_name': father_name,
                             'mother_name': mother_name
                             } )
        self.doc.end_paragraph()

        cnt = 1
        for child_ref in family.get_child_ref_list():
            child_handle = child_ref.ref
            child = self.database.get_person_from_handle(child_handle)
            child_name = self._name_display.display(child)
            child_mark = ReportUtils.get_person_mark(self.database, child)

            if self.childref and self.prev_gen_handles.get(child_handle):
                value = str(self.prev_gen_handles.get(child_handle))
                child_name += " [%s]" % value

            if self.inc_ssign:
                prefix = " "
                for family_handle in child.get_family_handle_list():
                    family = self.database.get_family_from_handle(family_handle)
                    if family.get_child_ref_list():
                        prefix = "+ "
                        break
            else:
                prefix = ""

            if child_handle in self.dnumber:
                self.doc.start_paragraph("DDR-ChildList",
                        prefix
                        + str(self.dnumber[child_handle])
                        + " "
                        + ReportUtils.roman(cnt).lower()
                        + ".")
            else:
                self.doc.start_paragraph("DDR-ChildList",
                              prefix + ReportUtils.roman(cnt).lower() + ".")
            cnt += 1

            self.doc.write_text("%s. " % child_name, child_mark)
            self.__narrator.set_subject(child)
            self.doc.write_text_citation(self.__narrator.get_born_string() or
                                         self.__narrator.get_christened_string() or
                                         self.__narrator.get_baptised_string())
            self.doc.write_text_citation(self.__narrator.get_died_string() or
                                         self.__narrator.get_buried_string())
            self.doc.end_paragraph()

    def __write_family_notes(self, family):
        """
        Write the notes for the given family.
        """
        notelist = family.get_note_list()
        if len(notelist) > 0:
            mother_name, father_name = self.__get_mate_names(family)

            self.doc.start_paragraph("DDR-NoteHeader")
            self.doc.write_text(
                self._('Notes for %(mother_name)s and %(father_name)s:') % {
                'mother_name' : mother_name,
                'father_name' : father_name })
            self.doc.end_paragraph()
            for notehandle in notelist:
                note = self.database.get_note_from_handle(notehandle)
                self.doc.write_styled_note(note.get_styledtext(),
                                           note.get_format(),"DDR-Entry")

    def __write_family_events(self, family):
        """
        List the events for the given family.
        """
        if not family.get_event_ref_list():
            return

        mother_name, father_name = self.__get_mate_names(family)

        first = 1
        for event_ref in family.get_event_ref_list():
            if first:
                self.doc.start_paragraph('DDR-MoreHeader')
                self.doc.write_text(
                    self._('More about %(mother_name)s and %(father_name)s:') % {
                    'mother_name' : mother_name,
                    'father_name' : father_name })
                self.doc.end_paragraph()
                first = 0
            self.write_event(event_ref)
            return first

    def __write_family_attrs(self, family, first):
        """
        List the attributes for the given family.
        """
        attrs = family.get_attribute_list()

        if first and attrs:
            mother_name, father_name = self.__get_mate_names(family)

            self.doc.start_paragraph('DDR-MoreHeader')
            self.doc.write_text(
                self._('More about %(mother_name)s and %(father_name)s:') % {
                'mother_name' : mother_name,
                'father_name' : father_name })
            self.doc.end_paragraph()

        for attr in attrs:
            self.doc.start_paragraph('DDR-MoreDetails')
            attrName = self._get_type(attr.get_type())
            text = self._("%(type)s: %(value)s%(endnotes)s") % {
                'type'     : self._(attrName),
                'value'    : attr.get_value(),
                'endnotes' : self.endnotes(attr) }
            self.doc.write_text_citation( text )
            self.doc.end_paragraph()

            if self.inc_notes:
                # if the attr or attr reference has a note attached to it,
                # get the text and format it correctly
                notelist = attr.get_note_list()
                for notehandle in notelist:
                    note = self.database.get_note_from_handle(notehandle)
                    self.doc.write_styled_note(note.get_styledtext(),
                                               note.get_format(),"DDR-MoreDetails")

    def write_person_info(self, person):
        name = self._name_display.display_formal(person)
        self.__narrator.set_subject(person)

        plist = person.get_media_list()
        if self.addimages and len(plist) > 0:
            photo = plist[0]
            ReportUtils.insert_image(self.database, self.doc, photo, self._user)

        self.doc.start_paragraph("DDR-Entry")

        if not self.verbose:
            self.__write_parents(person)

        text = self.__narrator.get_born_string()
        if text:
            self.doc.write_text_citation(text)

        text = self.__narrator.get_baptised_string()
        if text:
            self.doc.write_text_citation(text)

        text = self.__narrator.get_christened_string()
        if text:
            self.doc.write_text_citation(text)

        text = self.__narrator.get_died_string(self.calcageflag)
        if text:
            self.doc.write_text_citation(text)

        text = self.__narrator.get_buried_string()
        if text:
            self.doc.write_text_citation(text)

        if self.verbose:
            self.__write_parents(person)
        self.write_marriage(person)
        self.doc.end_paragraph()

        notelist = person.get_note_list()
        if len(notelist) > 0 and self.inc_notes:
            self.doc.start_paragraph("DDR-NoteHeader")
            # feature request 2356: avoid genitive form
            self.doc.write_text(self._("Notes for %s") % name)
            self.doc.end_paragraph()
            for notehandle in notelist:
                note = self.database.get_note_from_handle(notehandle)
                self.doc.write_styled_note(note.get_styledtext(),
                        note.get_format(),"DDR-Entry",
                        contains_html= note.get_type() == NoteType.HTML_CODE)

        first = True
        if self.inc_names:
            for alt_name in person.get_alternate_names():
                if first:
                    self.doc.start_paragraph('DDR-MoreHeader')
                    self.doc.write_text(self._('More about %(person_name)s:') % {
                        'person_name' : name })
                    self.doc.end_paragraph()
                    first = False
                self.doc.start_paragraph('DDR-MoreDetails')
                atype = self._get_type(alt_name.get_type())
                aname = alt_name.get_regular_name()
                self.doc.write_text_citation(self._('%(name_kind)s: %(name)s%(endnotes)s') % {
                    'name_kind' : self._(atype),
                    'name' : aname,
                    'endnotes' : self.endnotes(alt_name),
                    })
                self.doc.end_paragraph()

        if self.inc_events:
            for event_ref in person.get_primary_event_ref_list():
                if first:
                    self.doc.start_paragraph('DDR-MoreHeader')
                    self.doc.write_text(self._('More about %(person_name)s:') % {
                        'person_name' : self._name_display.display(person) })
                    self.doc.end_paragraph()
                    first = 0

                self.write_event(event_ref)

        if self.inc_addr:
            for addr in person.get_address_list():
                if first:
                    self.doc.start_paragraph('DDR-MoreHeader')
                    self.doc.write_text(self._('More about %(person_name)s:') % {
                        'person_name' : name })
                    self.doc.end_paragraph()
                    first = False
                self.doc.start_paragraph('DDR-MoreDetails')

                text = ReportUtils.get_address_str(addr)

                if self.fulldate:
                    date = self._get_date(addr.get_date_object())
                else:
                    date = addr.get_date_object().get_year()

                self.doc.write_text(self._('Address: '))
                if date:
                    # translators: needed for Arabic, ignore otherwise
                    self.doc.write_text(self._('%s, ') % date )
                self.doc.write_text( text )
                self.doc.write_text_citation( self.endnotes(addr) )
                self.doc.end_paragraph()

        if self.inc_attrs:
            attrs = person.get_attribute_list()
            if first and attrs:
                self.doc.start_paragraph('DDR-MoreHeader')
                self.doc.write_text(self._('More about %(person_name)s:') % {
                    'person_name' : name })
                self.doc.end_paragraph()
                first = False

            for attr in attrs:
                self.doc.start_paragraph('DDR-MoreDetails')
                attrName = self._get_type(attr.get_type())
                text = self._("%(type)s: %(value)s%(endnotes)s") % {
                    'type'     : self._(attrName),
                    'value'    : attr.get_value(),
                    'endnotes' : self.endnotes(attr) }
                self.doc.write_text_citation( text )
                self.doc.end_paragraph()

    def endnotes(self, obj):
        if not obj or not self.inc_sources:
            return ""

        txt = endnotes.cite_source(self.bibli, self.database, obj)
        if txt:
            txt = '<super>' + txt + '</super>'
        return txt

#------------------------------------------------------------------------
#
# DetDescendantOptions
#
#------------------------------------------------------------------------
class DetDescendantOptions(MenuReportOptions):

    """
    Defines options and provides handling interface.
    """

    def __init__(self, name, dbase):
        MenuReportOptions.__init__(self, name, dbase)

    def add_menu_options(self, menu):
        """
        Add options to the menu for the detailed descendant report.
        """

        # Report Options
        category = _("Report Options")
        add_option = partial(menu.add_option, category)

        pid = PersonOption(_("Center Person"))
        pid.set_help(_("The center person for the report"))
        add_option("pid", pid)

        stdoptions.add_name_format_option(menu, category)

        numbering = EnumeratedListOption(_("Numbering system"), "Henry")
        numbering.set_items([
                ("Henry",      _("Henry numbering")),
                ("d'Aboville", _("d'Aboville numbering")),
                ("Record (Modified Register)",
                               _("Record (Modified Register) numbering"))])
        numbering.set_help(_("The numbering system to be used"))
        add_option("numbering", numbering)

        generations = NumberOption(_("Generations"), 10, 1, 100)
        generations.set_help(
            _("The number of generations to include in the report")
            )
        add_option("gen", generations)

        pagebbg = BooleanOption(_("Page break between generations"), False)
        pagebbg.set_help(
                     _("Whether to start a new page after each generation."))
        add_option("pagebbg", pagebbg)

        pageben = BooleanOption(_("Page break before end notes"),False)
        pageben.set_help(
                     _("Whether to start a new page before the end notes."))
        add_option("pageben", pageben)

        stdoptions.add_localization_option(menu, category)

        # Content

        add_option = partial(menu.add_option, _("Content"))

        usecall = BooleanOption(_("Use callname for common name"), False)
        usecall.set_help(_("Whether to use the call name as the first name."))
        add_option("usecall", usecall)

        fulldates = BooleanOption(_("Use full dates instead of only the year"),
                                  True)
        fulldates.set_help(_("Whether to use full dates instead of just year."))
        add_option("fulldates", fulldates)

        listc = BooleanOption(_("List children"), True)
        listc.set_help(_("Whether to list children."))
        add_option("listc", listc)

        computeage = BooleanOption(_("Compute death age"),True)
        computeage.set_help(_("Whether to compute a person's age at death."))
        add_option("computeage", computeage)

        omitda = BooleanOption(_("Omit duplicate ancestors"), True)
        omitda.set_help(_("Whether to omit duplicate ancestors."))
        add_option("omitda", omitda)

        verbose = BooleanOption(_("Use complete sentences"), True)
        verbose.set_help(
                 _("Whether to use complete sentences or succinct language."))
        add_option("verbose", verbose)

        desref = BooleanOption(_("Add descendant reference in child list"),
                               True)
        desref.set_help(
                    _("Whether to add descendant references in child list."))
        add_option("desref", desref)

        category_name = _("Include")
        add_option = partial(menu.add_option, _("Include"))

        incnotes = BooleanOption(_("Include notes"), True)
        incnotes.set_help(_("Whether to include notes."))
        add_option("incnotes", incnotes)

        incattrs = BooleanOption(_("Include attributes"), False)
        incattrs.set_help(_("Whether to include attributes."))
        add_option("incattrs", incattrs)

        incphotos = BooleanOption(_("Include Photo/Images from Gallery"), False)
        incphotos.set_help(_("Whether to include images."))
        add_option("incphotos", incphotos)

        incnames = BooleanOption(_("Include alternative names"), False)
        incnames.set_help(_("Whether to include other names."))
        add_option("incnames", incnames)

        incevents = BooleanOption(_("Include events"), False)
        incevents.set_help(_("Whether to include events."))
        add_option("incevents", incevents)

        incaddresses = BooleanOption(_("Include addresses"), False)
        incaddresses.set_help(_("Whether to include addresses."))
        add_option("incaddresses", incaddresses)

        incsources = BooleanOption(_("Include sources"), False)
        incsources.set_help(_("Whether to include source references."))
        add_option("incsources", incsources)

        incsrcnotes = BooleanOption(_("Include sources notes"), False)
        incsrcnotes.set_help(_("Whether to include source notes in the "
            "Endnotes section. Only works if Include sources is selected."))
        add_option("incsrcnotes", incsrcnotes)

        incmates = BooleanOption(_("Include spouses"), False)
        incmates.set_help(_("Whether to include detailed spouse information."))
        add_option("incmates", incmates)

        incmateref = BooleanOption(_("Include spouse reference"), False)
        incmateref.set_help(_("Whether to include reference to spouse."))
        add_option("incmateref", incmateref)

        incssign = BooleanOption(_("Include sign of succession ('+')"
                                   " in child-list"), True)
        incssign.set_help(_("Whether to include a sign ('+') before the"
                            " descendant number in the child-list to indicate"
                            " a child has succession."))
        add_option("incssign", incssign)

        incpaths = BooleanOption(_("Include path to start-person"), False)
        incpaths.set_help(_("Whether to include the path of descendancy "
                            "from the start-person to each descendant."))
        add_option("incpaths", incpaths)

        # Missing information

        add_option = partial(menu.add_option, _("Missing information"))

        repplace = BooleanOption(_("Replace missing places with ______"), False)
        repplace.set_help(_("Whether to replace missing Places with blanks."))
        add_option("repplace", repplace)

        repdate = BooleanOption(_("Replace missing dates with ______"), False)
        repdate.set_help(_("Whether to replace missing Dates with blanks."))
        add_option("repdate", repdate)

    def make_default_style(self, default_style):
        """Make the default output style for the Detailed Ancestral Report"""
        font = FontStyle()
        font.set(face=FONT_SANS_SERIF, size=16, bold=1)
        para = ParagraphStyle()
        para.set_font(font)
        para.set_header_level(1)
        para.set_top_margin(0.25)
        para.set_bottom_margin(0.25)
        para.set_alignment(PARA_ALIGN_CENTER)
        para.set_description(_('The style used for the title of the page.'))
        default_style.add_paragraph_style("DDR-Title", para)

        font = FontStyle()
        font.set(face=FONT_SANS_SERIF, size=14, italic=1)
        para = ParagraphStyle()
        para.set_font(font)
        para.set_header_level(2)
        para.set_top_margin(0.25)
        para.set_bottom_margin(0.25)
        para.set_description(_('The style used for the generation header.'))
        default_style.add_paragraph_style("DDR-Generation", para)

        font = FontStyle()
        font.set(face=FONT_SANS_SERIF, size=10, italic=0, bold=1)
        para = ParagraphStyle()
        para.set_font(font)
        para.set_left_margin(1.5)   # in centimeters
        para.set_top_margin(0.25)
        para.set_bottom_margin(0.25)
        para.set_description(_('The style used for the children list title.'))
        default_style.add_paragraph_style("DDR-ChildTitle", para)

        font = FontStyle()
        font.set(size=10)
        para = ParagraphStyle()
        para.set_font(font)
        para.set(first_indent=-0.75, lmargin=2.25)
        para.set_top_margin(0.125)
        para.set_bottom_margin(0.125)
        para.set_description(_('The style used for the children list.'))
        default_style.add_paragraph_style("DDR-ChildList", para)

        font = FontStyle()
        font.set(face=FONT_SANS_SERIF, size=10, italic=0, bold=1)
        para = ParagraphStyle()
        para.set_font(font)
        para.set(first_indent=0.0, lmargin=1.5)
        para.set_top_margin(0.25)
        para.set_bottom_margin(0.25)
        default_style.add_paragraph_style("DDR-NoteHeader", para)

        para = ParagraphStyle()
        para.set(lmargin=1.5)
        para.set_top_margin(0.25)
        para.set_bottom_margin(0.25)
        para.set_description(_('The basic style used for the text display.'))
        default_style.add_paragraph_style("DDR-Entry", para)

        para = ParagraphStyle()
        para.set(first_indent=-1.5, lmargin=1.5)
        para.set_top_margin(0.25)
        para.set_bottom_margin(0.25)
        para.set_description(_('The style used for the first personal entry.'))
        default_style.add_paragraph_style("DDR-First-Entry", para)

        font = FontStyle()
        font.set(size=10, face=FONT_SANS_SERIF, bold=1)
        para = ParagraphStyle()
        para.set_font(font)
        para.set(first_indent=0.0, lmargin=1.5)
        para.set_top_margin(0.25)
        para.set_bottom_margin(0.25)
        para.set_description(_('The style used for the More About header and '
            'for headers of mates.'))
        default_style.add_paragraph_style("DDR-MoreHeader", para)

        font = FontStyle()
        font.set(face=FONT_SERIF, size=10)
        para = ParagraphStyle()
        para.set_font(font)
        para.set(first_indent=0.0, lmargin=1.5)
        para.set_top_margin(0.25)
        para.set_bottom_margin(0.25)
        para.set_description(_('The style used for additional detail data.'))
        default_style.add_paragraph_style("DDR-MoreDetails", para)

        endnotes.add_endnote_styles(default_style)


import os
import csv
from collections import Counter

import tensorflow as tf
import numpy as np
import cv2
from tqdm import tqdm
import matplotlib

from dataset import Dataset
from utils import set_chars_type

FLAGS = tf.app.flags.FLAGS
matplotlib.use('Agg')

class Evaluating():
    """Evaluating generated fonts

    This class is for evaluating generated fonts.
    Measure between generated fonts and real fonts by using pseudo-Hamming distance.
    """

    def __init__(self):
        global FLAGS
        self._setup_dirs()
        self._load_dataset()
        self._setup_chars()

    def _setup_dirs(self):
        """Setup output directories

        If destinations are not existed, make directories like this:
            FLAGS.gan_dir
            └ evaluated
        """
        self.dst_evaluated = os.path.join(FLAGS.gan_dir, 'evaluated')
        if not os.path.exists(self.dst_evaluated):
            os.mkdir(self.dst_evaluated)

    def _load_dataset(self):
        """Load dataset

        Setup dataset, generated fonts and real fonts.
        """
        self.generated_dataset = Dataset(FLAGS.generated_h5, 'r', FLAGS.img_width, FLAGS.img_height, FLAGS.img_dim)
        self.generated_dataset.set_load_data()
        self.real_dataset = Dataset(FLAGS.font_h5, 'r', FLAGS.img_width, FLAGS.img_height, FLAGS.img_dim)
        self.real_dataset.set_load_data()

    def _setup_chars(self):
        """Setup characters' type

        Setup characters\' type, caps or hiragana or both.
        """
        self.embedding_chars = set_chars_type(FLAGS.chars_type)
        assert self.embedding_chars != [], 'embedding_chars is empty'
        self.char_embedding_n = len(self.embedding_chars)

    def calc_hamming_distance(self):
        """Calculate pseudo-Hamming distance

        Measure between generated fonts and real fonts by using pseudo-Hamming distance.
        If you want to know pseudo-Hamming distance, check this:
        Uchida, et al. "Exploring the World of Fonts for Discovering the Most Standard Fonts and the Missing Fonts", ICDAR, 2015.
        """
        from matplotlib import pyplot as plt

        def transform_backgorund_distance(src_imgs):
            bin_imgs = np.where(src_imgs > 0, 255, 0).astype(np.uint8)
            mask_imgs = np.where(src_imgs > 0, 0, 1).astype(np.uint8)
            dist_imgs = np.empty(src_imgs.shape)
            img_n = src_imgs.shape[0]
            for i in range(img_n):
                dist_imgs[i] = cv2.distanceTransform(bin_imgs[i], cv2.DIST_L2, 3)
            return mask_imgs, dist_imgs

        def plot(distances, filename):
            fig = plt.figure(figsize=(16, 9))
            ax = fig.add_subplot(1, 1, 1)
            ax.hist(distances, bins=50)
            plt.savefig(os.path.join(self.dst_evaluated, '{}.png'.format(filename)))
            plt.close()

        min_distances_list = list()
        min_real_indices_list = list()
        try:
            for c in tqdm(self.embedding_chars):
                generated_n = self.generated_dataset.get_data_n_by_labels([c])
                generated_imgs = np.mean(self.generated_dataset.get_batch_by_labels(0, generated_n, [c]), axis=3)
                mask_generated_imgs, dist_generated_imgs = transform_backgorund_distance(generated_imgs)
                real_n = self.real_dataset.get_data_n_by_labels([c])
                real_imgs = np.mean(self.real_dataset.get_batch_by_labels(0, real_n, [c]), axis=3)
                mask_real_imgs, dist_real_imgs = transform_backgorund_distance(real_imgs)
                min_distances = list()
                min_real_indices = list()
                for generated_i in tqdm(range(generated_n)):
                    min_distance = float('inf')
                    for real_i in range(real_n):
                        distance = np.sum(np.multiply(dist_generated_imgs[generated_i], mask_real_imgs[real_i]) +
                                          np.multiply(dist_real_imgs[real_i], mask_generated_imgs[generated_i]))
                        if distance < min_distance:
                            min_distance = distance
                            min_real_index = real_i
                    min_distances.append(min_distance)
                    min_real_indices.append(min_real_index)
                plot(min_distances, c)
                min_distances_list.append(min_distances)
                min_real_indices_list.append(min_real_indices)
        except KeyboardInterrupt:
            print('cancelled. but write csv...')
        finally:
            mean_all_min_dinstances = np.mean(np.array(min_distances_list), axis=0).tolist()
            plot(mean_all_min_dinstances, 'all')
            self._write_csv(min_distances_list, mean_all_min_dinstances, min_real_indices_list)

    def _write_csv(self, distances_list, all_distances, real_indices_list):
        """Write CSV

        Write distances in CSV file.

        Args:
            distance_list: A distance, which is only 1 character's.
            all_distances: All character's average.
            real_indices_list: Index list of real fonts, that have minimum distance from a generated font.
        """
        with open(os.path.join(self.dst_evaluated, 'evaluate.csv'), 'w') as csv_file:
            csv_writer = csv.writer(csv_file)
            font_n = len(all_distances)
            char_n = len(real_indices_list)
            header = ['', 'all_dist', 'fontname', 'most_n'] + self.embedding_chars[:char_n] + ['index_' + char for char in self.embedding_chars[:char_n]]
            csv_writer.writerow(header)
            for i in range(font_n):
                line = list()
                line.append(i)
                line.append(all_distances[i])
                real_indices = list()
                for j in range(char_n):
                    real_indices.append(real_indices_list[j][i])
                count = Counter(real_indices)
                line.append(self.real_dataset.get_fontname_by_label_id('A', count.most_common()[0][0]))
                line.append(count.most_common()[0][1])
                for j in range(char_n):
                    line.append(distances_list[j][i])
                line += [self.real_dataset.get_fontname_by_label_id('A', real_index) for real_index in real_indices]
                csv_writer.writerow(line)


import turtle

#Functions that would be used in the below functions:

#defining a function to create a rectangle.
def drawRectangle(rec_length,rec_width,rec_color,pen_color):
    turtle.fillcolor(rec_color)
    turtle.begin_fill()
    turtle.pencolor(pen_color)
    drawLine(0,rec_length)
    drawLine(90,rec_width)
    drawLine(90,rec_length)
    drawLine(90,rec_width)
    drawLine(90,0)
    turtle.end_fill()

#Defining a function to reposition the turtle to a provided coordinate.
def turtleReposition(x_value,y_value):
    turtle.penup()
    turtle.goto(x_value,y_value)
    turtle.pendown()

# Question 1: Drawing a Line.

#Define a function that accepts two parameters; 'angle' and 'length'.
def drawLine(angle, length):

    #1. Turn left by some degrees.
    turtle.left(angle)

    #2. Move forward by the distance provided.
    turtle.forward(length)

# Question 2: Star Gazing!

#Define a function that draws a star and which accepts one parameter; 'starside' as the side of the star.
def drawStar(starside):

    #1. Call drawLine function with angle=36 degrees and lenght=starside.
    #   Makes one side of the Star.
    drawLine(36,starside)

    #2. Call drawLine function with angle=144 degrees and lenght=starside.
    #3. Repeat this step 4 times in total.
    #  Makes the other 4 sides of the Star.
    for i in range(4):
        drawLine(144,starside)
    turtle.left(108)

# Question 3: The Crescent!

#Define a function that draws and fills a crescent which accepts two parameters; 'moonradius' and 'mooncolor'.
def drawCrescent(moonradius,mooncolor):

    turtle.fillcolor(mooncolor)
    turtle.begin_fill()

    #Draw the outer arc of the crescent.
    initialdeg=turtle.heading()
    turtle.circle(moonradius,-200)
    turtle.circle(moonradius,220)

    #Repositioning the turtle for creating the inner arc of the moon
    deg=turtle.heading()
    print(deg)
    drawLine(-(deg-initialdeg),0)

    #Draw the inner arc of the crescent.
    turtle.circle(moonradius*0.93969262078,-180)

    #Complete the crescent to fill the color.
    drawLine(-(deg-initialdeg),0)
    turtle.circle(moonradius,220)

    turtle.end_fill()

drawCrescent(100,'blue')

#Question 4: Qatar Flag

#Define a function that draws the flag of Qatar.

#Defining a fuction to make the Spikes.
def drawSpikes(length):
    drawLine(21.16844816,length)
    drawLine(137.66310368,length)
    drawLine(-158.83155184,0)

def drawQatarFlag(x,y):

    #0. Repositioning the flag to a provided coordinate
    turtleReposition(x,y)

    #1. Create the maroon part of the flag with the color.
    drawRectangle(250,130,'maroon','black')

    #2. Reposition for creating the white part of the flag.
    turtleReposition((x+50),y)

    #3. Create the white part of the flag and the zig-zags.
    turtle.fillcolor('white')
    turtle.begin_fill()

    # Create the zig-zags.
    for j in range(9):
        turtle.pencolor('white')
        drawSpikes(20)
    turtle.pencolor('black')

    # Create the rectangluar reigon of the white part.
    drawLine(0,2)
    drawLine(180,52)
    drawLine(90,130)
    drawLine(90,52)
    turtle.end_fill()

#Question 4: The US Flag

#Define a function that draws the flag of United States.
def drawUSFlag(x,y):
    turtleReposition(x,y)
    loopcount=1
    coordinate_count=0
    drawLine(0,250)
    #1. Makes the stripes of alternate colors; red and white.

    for i in range(13):
        #Condition that creates the longer 6 stripes of the flag.

        if(loopcount<7):
            #Condition that checks for alternating colors of the stripes of the flag.
            #If the loopcount is odd then the 'if' part of the code is excecuted to fill in a red stripe.
            if(loopcount%2!=0):

                #Creates the red colored stripes.
                turtle.fillcolor('dark red')
                turtle.begin_fill()
                drawLine(90,10)
                drawLine(90,250)
                drawLine(90,10)
                drawLine(180,0)
                turtle.end_fill()

                coordinate_count=coordinate_count+1

                turtleReposition((x+0),(y+(10*coordinate_count)))

            else:

                #Creates the white stripes.
                #If the loopcount is even then the 'else' part of the code is excecuted to fill in a white stripe.
                drawLine(0,10)
                drawLine(270,250)
                drawLine(270,10)
                drawLine(90,0)

                coordinate_count=coordinate_count+1

                turtleReposition((x+250),(y+(10*coordinate_count)))

            loopcount=loopcount+1
        else:

            #Condition to create the smaller stripes of the flag
            if(loopcount%2!=0):

                #Creates the red colored stripes.
                #If the loopcount is odd then the 'if' part of the code is excecuted to fill in a red stripe.
                turtle.fillcolor('dark red')
                turtle.begin_fill()
                drawLine(90,10)
                drawLine(90,140)
                drawLine(90,10)
                drawLine(180,0)
                turtle.end_fill()

                coordinate_count=coordinate_count+1

                turtleReposition((x+110),(y+(10*coordinate_count)))

            else:

                #Creates the white stripes.
                #If the loopcount is even then the 'else' part of the code is excecuted to fill in a white stripe.
                drawLine(0,10)
                drawLine(270,140)
                drawLine(270,10)
                drawLine(90,0)

                coordinate_count=coordinate_count+1

                turtleReposition((x+250),(y+(10*coordinate_count)))

            loopcount=loopcount+1

    #Creating the Blue part.
    turtle.fillcolor('dark blue')
    turtle.begin_fill()
    drawLine(90,110)
    drawLine(90,70)
    drawLine(90,110)
    turtle.end_fill()

    #Repositioning turtle to make the Stars.
    column=0
    star_count=0

    #Creates a loop to column-wise fill the 6 Stars; starting from bottom to top .
    for z in range(6):
        for p in range(5):

            turtle.pencolor('white')

            #Creates the Star at a point and then increments its y-coordinate by a value.
            #Repositions for creating the next star in the column.
            turtleReposition((x+(3+(20*column))),(y+(63+(14*star_count))))

            #Counting the number of Stars created.
            star_count=star_count+1

            turtle.fillcolor('white')
            turtle.begin_fill()
            drawStar(6)
            turtle.end_fill()
        #Counting the columns and shifting turtle right by providing a value to its x-coordinate in the loop.
        column=column+1
        star_count=0

    column1=0
    star_count1=0
    #Creates a loop to column-wise fill the 4 Stars; starting from bottom to top .
    for h in range(5):
        for q in range(4):

            turtle.pencolor('white')

            #Creates the Star at a point and then increments its y-coordinate by a value.
            #Repositions for creating the next star in the column.
            turtleReposition((x+(13+(20*column1))),(y+(71+(14*star_count1))))

            #Counting the number of Stars created.
            star_count1=star_count1+1

            turtle.fillcolor('white')
            turtle.begin_fill()
            drawStar(6)
            turtle.end_fill()
        #Counting the columns and shifting turtle right by providing a value to its x-coordinate in the loop.
        column1=column1+1
        star_count1=0

#Question 6: The Pakistan Flag.

#Define a function that draws the flag of Pakistan.

def drawPAKFlag(x,y):
    turtleReposition(x,y)
    #Creating a rectangle to create the white part of the flag.
    drawRectangle(62,130,'white','black')

    #Repositioning turtle to draw the green part of the flag.
    turtleReposition((x+62),y)

    #Filling the green part of the flag.
    turtle.fillcolor('dark green')
    turtle.begin_fill()

    drawLine(0,188)
    drawLine(90,130)
    drawLine(90,188)
    drawLine(90,130)
    turtle.end_fill()

    #Repositioning turtle to draw the crescent of the flag
    turtleReposition((x+195),(y+40))
    drawLine(140,0)
    turtle.pencolor('white')

    #Draws the crescent of the flag.
    drawCrescent(45,'white')

    #Realigning the turtle to draw the Star of the flag.
    turtleReposition((x+180),(y+80))
    deg=turtle.heading()
    drawLine(((-deg)+30),0)

    #Fills white color for the Star.
    turtle.fillcolor('white')
    turtle.begin_fill()

    #Draws the Star of the flag.
    drawStar(25)

    turtle.end_fill()

    #Resetting the turtle to intitial degrees.
    drawLine(-30,0)

#Question 7: The South Korea Flag

#Defining a function to make the rectangles of any pattern.
def drawTripleRectangles(rec_length1,rec_length2,rec_length3,common_width):
    drawRectangle(rec_length1,common_width,'black','black')
    drawRectangle(rec_length2,common_width,'white','white')
    drawRectangle(rec_length3,common_width,'black','black')

#Define a function that draws the flag of South Korea.
def drawSKFlag(x,y):

    turtleReposition(x,y)
    #Draws the box outline of the flag.
    drawRectangle(250,130,'white','black')
    drawLine(180,0)

    #Repositioning the turtle to draw the red circle.
    turtleReposition((x+125),(y+95))

    #Draws the red circle.
    turtle.fillcolor('red')
    turtle.begin_fill()
    turtle.pencolor('red')
    turtle.circle(30)
    turtle.end_fill()

    #Draws the semi-circles for dividing the circle to be filled with 'blue' and 'red'.
    turtle.circle(30,60)
    turtle.circle(15,180)
    turtle.left(180)

    #Filling the semi-cirlces 'blue'.
    turtle.fillcolor('blue')
    turtle.begin_fill()
    turtle.pencolor('blue')
    turtle.circle(15,-180)
    turtle.circle(30,-180)
    turtle.circle(15,180)
    turtle.end_fill()

    #Repositioning the turtle to create the rectangles.
    turtle.pencolor('black')
    turdeg=turtle.heading()
    drawLine(-turdeg,0)

    #Creates the top left part of the rectangles.
    #Reposition the turtle to draw the set of rectangle
    turtleReposition((x+70),(y+79))
    drawLine(55,0)
    #Draws rectangle
    drawTripleRectangles(30,0,0,5)
    #Reposition the turtle to draw the next rectangle
    turtleReposition((x+62.6276316014),(y+84.1621879272))
    drawTripleRectangles(30,0,0,5)
    #Reposition the turtle to draw the next rectangle
    turtleReposition((x+55.2552632028),(y+89.3243758543))
    drawTripleRectangles(30,0,0,5)

    #Creates the top right part of the rectangles.
    #Reposition the turtle to draw the next set of rectangle
    turtleReposition((x+180),(y+79))
    drawLine(70,0)
    drawTripleRectangles(30,17,13,5)
    #Reposition the turtle to draw the next rectangle
    turtleReposition((x+187.372368399),(y+84.1621879272))
    drawTripleRectangles(30,0,0,5)
    #Reposition the turtle to draw the next rectangle
    turtleReposition((x+194.744736797),(y+89.3243758543))
    drawTripleRectangles(30,17,13,5)

    #Creates the bottow right part of the rectangles.
    #Reposition the turtle to draw the next set of rectangle
    turtleReposition((x+180),(y+49))
    drawLine(110,0)
    drawTripleRectangles(30,17,13,5)
    #Reposition the turtle to draw the next rectangle
    turtleReposition((x+187.372368399),(y+43.8378120728))
    drawTripleRectangles(30,17,13,5)
    #Reposition the turtle to draw the next rectangle
    turtleReposition((x+194.744736797),(y+38.6756241457))
    drawTripleRectangles(30,17,13,5)

    #Creates the bottom left part of the rectangles.
    #Reposition the turtle to draw the next set of rectangle
    turtleReposition((x+70),(y+49))
    drawLine(70,0)
    drawTripleRectangles(30,0,0,5)
    #Reposition the turtle to draw the next rectangle
    turtleReposition((x+62.6276316014),(y+43.8378120728))
    drawTripleRectangles(30,17,13,5)
    #Reposition the turtle to draw the next rectangle
    turtleReposition((x+55.2552632028),(y+38.6756241457))
    drawTripleRectangles(30,0,0,5)

#Question 8: Put it all together

def drawAllFlags():
    #Increases the turtle's speed.
    turtle.speed(15)

    #Calls all the functions that draw flags at respective points
    drawQatarFlag(-150,0)
    drawUSFlag(120,0)
    drawPAKFlag(-150,-150)
    drawSKFlag(120,-150)

#drawAllFlags()


# coding: utf-8

# In[1]:

import math
import numpy as np
import time
import pandas as pd
import os
import configparser   #來存取ini文檔
import numpy as np
import matplotlib.pyplot as plt
import xlsxwriter

def matmult(a,b): #input兩個矩陣返回乘法結果
    zip_b = zip(*b)
    # uncomment next line if python 3 :
    # zip_b = list(zip_b)
    return [[sum(ele_a*ele_b for ele_a, ele_b in zip(row_a, col_b))
             for col_b in zip_b] for row_a in a]

def hh(z):
    return (z + math.exp(z*-1))/2

def Hmat(u, v):
    return hh(u+v) - hh(abs(u-v))

def Galfa(alfa, Q, mm, umax, nrofcoup, T2, Tau):
    h = []
    temp1 = []
    # for i in range(umax*nrofcoup):
    #     h.append([])
    #     for j in range(umax*nrofcoup):
    #         h[i].append([])
    # for i in range(mm):
    #     h.append([])
    #     for j in range(1):
    #         h[i].append([])

    for i in range(1, umax*nrofcoup+1):
        h.append([])
        for j in range(1, umax*nrofcoup+1):
            h[i-1].append(Hmat(alfa * i / nrofcoup, alfa * j / nrofcoup))

    for i in range(mm):
        temp1.append([])
        temp1[i].append(1 - sum(Q[i]))

    Q = np.matrix(Q)
    h = np.matrix(h)
    b = np.linalg.inv((Q*h)*Q.transpose())*temp1

    gamma = Q.transpose() * b
    temp2 = 0
    temp3 = 0
    for i in range(1, umax*nrofcoup+1):
        # print('temp2=='+str(temp2)+' gamma=='+str(gamma[i-1, 0]))
        temp2 = temp2 + gamma[i-1, 0] * i / nrofcoup
        temp3 = temp3 + gamma[i-1, 0] * np.sinh(alfa * i / nrofcoup)
    kappa = (1 + alfa * temp2) / temp3
    output = []
    output.append(alfa / abs(1 - kappa * math.exp(T2 * alfa)) - Tau)
    output.append(gamma)
    return output

def AlfaScan(lastalfa, stepsize, Q, mm, umax, nrofcoup, T2, Tau):
    '''
    這邊可能有錯 要檢查迴圈
    '''
    alfaReturn = 0
    for alfa in np.arange(lastalfa + stepsize / 10 - stepsize, lastalfa, stepsize / 10):
        alfa = float(round(alfa, 7))
        galfa_output = Galfa(alfa, Q, mm, umax, nrofcoup, T2, Tau)
        alfaReturn = alfa
        if galfa_output[0] <= 0:
            break
    return [alfaReturn, galfa_output[1]]

def SmithWilsonBruteForce(Instrument, DataIn, nrofcoup, CRA, UFRac, alfamin, Tau, T2):
    precision = 6
    Tau = Tau / 10000
    nrofrates = 0
    for i in range(len(DataIn)):
        nrofrates += DataIn[i][0]
    # print('nrofrates=='+str(nrofrates))

    u = []  #len(nrofrates)
    r = []  #len(nrofrates)
    j = 0
    for i in range(len(DataIn)):
        if DataIn[i][0] == 1:
            j = j + 1
            u.append(DataIn[i][1])
            r.append(DataIn[i][2] - CRA / 10000)
    umax = max(u)
    # print('umax==='+str(umax))
    lnUFR = math.log(1+UFRac)
    # print('lnUFR==='+str(lnUFR))

    if Instrument == 'Zero':
        nrofcoup = 1

    q = []
    for i in range(nrofrates):
        q.append([])
        for j in range(umax*nrofcoup):
            q[i].append(0)
    # print('q是'+str(nrofrates)+'X'+str(umax*nrofcoup)+'陣列')
    if Instrument == 'Zero':
        for i in range(nrofrates):
            for j in range(nrofrates):
                if i != j:
                    q[i][j] = 0
                else:
                    q[i][u[i]-1] = math.exp(lnUFR*-1 * u[i]) * (math.pow((1 + r[i]),u[i]))
    elif Instrument == 'Swap' or Instrument == 'Bond':
        for i in range(nrofrates):
            for j in range(u[i]*nrofcoup-1):
                q[i][j] = math.exp(lnUFR*-1 * j / nrofcoup) * r[i] / nrofcoup
            q[i][j] = math.exp(lnUFR*-1 * j / nrofcoup) * (1 + r[i] / nrofcoup)
    galfa_output = Galfa(alfamin, q, nrofrates, umax, nrofcoup, T2, Tau)
    # print(galfa_output[0])
    # time.sleep(50)
    if galfa_output[0] <= 0:
        alfaReturn = alfamin
        gamma = galfa_output[1]
    else:
        stepsize = 0.1
        alfaReturn = 0
        for alfa in np.arange(alfamin+stepsize, 20, stepsize):
            alfa = float(round(alfa, 4))
            alfaReturn = alfa
            if Galfa(alfa, q, nrofrates, umax, nrofcoup, T2, Tau)[0] <= 0:
                break
        for i in range(1, precision):
            alfascanoutput = AlfaScan(alfaReturn, stepsize, q, nrofrates, umax, nrofcoup, T2, Tau)
            alfaReturn = alfascanoutput[0]
            stepsize = stepsize / 10
        gamma = alfascanoutput[1]

    h = []
    for i in range(121+1):
        h.append([])
        for j in range(umax*nrofcoup):
            h[i].append([])

    g = []
    for i in range(121+1):
        g.append([])
        for j in range(umax*nrofcoup):
            g[i].append([])

    for i in range(121+1):
        for j in range(1, umax*nrofcoup+1):
            h[i][j-1] = Hmat(alfaReturn * i, alfaReturn * j / nrofcoup)
            if j / nrofcoup > i:
                g[i][j-1] = (alfaReturn * (1 - math.exp(alfaReturn*-1 * j / nrofcoup) * np.cosh(alfaReturn * i)))
            else:
                g[i][j-1] = (alfaReturn * math.exp(alfaReturn*-1 * i) * np.sinh(alfaReturn * j / nrofcoup))

    tempDiscount = []
    tempintensity = []
    discount = []
    fwintensity = []
    yldintensity = []
    forwardac = []
    zeroac = []
    forwardcc = []
    zerocc = []

    temptempdiscount = (np.matrix(h)*gamma).transpose()
    temptempintensity = (np.matrix(g)*gamma).transpose()
    for i in range(121+1):
        tempDiscount.append(temptempdiscount[0,i])
        tempintensity.append(temptempintensity[0,i])
    temp = 0
    for i in range(1, umax*nrofcoup+1):
        temp = temp + (1 - math.exp(alfaReturn * -1 * i / nrofcoup)) * gamma[i-1, 0]

    yldintensity.append(lnUFR - alfaReturn * temp)
    fwintensity.append(yldintensity[0])
    discount.append(1)
    yldintensity.append(lnUFR - math.log(1 + tempDiscount[1]))
    fwintensity.append(lnUFR - tempintensity[1] / (1 + tempDiscount[1]))
    discount.append(math.exp(lnUFR*-1) * (1 + tempDiscount[1]))
    zeroac.append(0)
    zeroac.append(1 / discount[1] - 1)
    forwardac.append(0)
    forwardac.append(zeroac[1])
    for i in range(2, 121):
        yldintensity.append(lnUFR - math.log(1 + tempDiscount[i]) / i)
        fwintensity.append(lnUFR - tempintensity[i] / (1 + tempDiscount[i]))
        discount.append(math.exp(lnUFR * -1 * i) * (1 + tempDiscount[i]))
        zeroac.append(math.pow(1 / discount[i], (1 / i)) - 1)
        forwardac.append(discount[i - 1] / discount[i] - 1)

    yldintensity.append(0)
    fwintensity.append(0)
    zeroac.append(0)
    forwardac.append(0)
    discount.append(alfaReturn)

    forwardcc.append(0)
    zerocc.append(0)
    for i in range(1, 121):
        forwardcc.append(math.log(1 + forwardac[i]))
        zerocc.append(math.log(1 + zeroac[i]))
    forwardcc.append(0)
    zerocc.append(0)
    # print(len(discount))
    # print(len(yldintensity))
    # print(len(zeroac))
    # print(len(fwintensity))
    # print(len(forwardcc))
    # print(len(forwardac))
    # print(forwardcc)
    #zeroac forwardcc forwardac(少了第一項 index=1)有誤
    # print(yldintensity)
    return [discount, yldintensity, zeroac, fwintensity, forwardcc, forwardac]

def genDataIn(dataFileName, dataDate, needOneList):        #needOneList會是1-10或1 5 10 其中一種
    dataIn = []
    dataFile = open(dataFileName, 'r')
    dataLines = dataFile.readlines()
    for data in dataLines:
        if dataDate == data.split(',')[0]:          #???為啥需要這行
            for i in range(10):
                if i+1 in needOneList:
                    dataIn.append([1, i+1, float(data.split(',')[i+1].replace('\n', ''))])
                else:
                    dataIn.append([0, i+1, float(data.split(',')[i+1].replace('\n', ''))])
    return dataIn

def getDateList(dataFileName):
    dateList = []
    dataFile = open(dataFileName, 'r')    #'r':讀取   'w': 新建檔案寫入(檔案可不存在，若存在則清空)
    dataLines = dataFile.readlines()
    for data in dataLines:
        dateList.append(data.split(',')[0])     # [0]: 使得datelist只有時間
    return dateList

#main

# 從config.ini讀取各參數
config = configparser.ConfigParser()     # 創建對象
config.read('config.ini', encoding='utf-8')
dataFileName = config.get('Parameter', 'DataFileName')
Instrument = config.get('Parameter', 'Instrument')
nrofcoup = float(config.get('Parameter', 'Nrofcoup'))
CRA = float(config.get('Parameter', 'CRA'))
UFRac = float(config.get('Parameter', 'UFRac'))
alfamin = float(config.get('Parameter', 'Alfamin'))
Tau = float(config.get('Parameter', 'Tau'))
T2 = float(config.get('Parameter', 'T2'))
rateType = config.get('Parameter', 'RateType')

needOneBigList = []
for needOne in config.get('Parameter', 'LiquidMaturity').split('|'):
    needOneBigList.append([float(i) for i in needOne.split(',')])
    # needOneBigList:[[1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0], [1.0, 5.0, 10.0]]

dateList = getDateList(dataFileName)

dirName = 'output'
if os.path.isdir(dirName) == False:
    os.mkdir(dirName)                 #創建目錄

for k in range(len(needOneBigList)):      # k代表用1-10資料 還是1 5 10資料
    row = 0
    col = 0
    workbook = xlsxwriter.Workbook(dirName+'/output'+str(k)+'_'+rateType+'.xlsx') #k=0代表用1-10, k=1代表用1 5 10
    worksheet = workbook.add_worksheet()
    # for i in range(len(dateList)):
    #     if i == 0:
    #         worksheet.write(row, col, 'Year')
    #     else:
    #         worksheet.write(row, col, str(i))
    #     row = row + 1

    worksheet.write(0, col, 'Year')
    for date in dateList:
        row = 0
        col = col + 1
        print('正在運行 日期=='+date)
        worksheet.write_string(row, col, str(date))     #???  這個感覺跟下面重複
        DataIn = genDataIn(dataFileName, date, needOneBigList[k])
        if rateType == 'forward':
            rateList = SmithWilsonBruteForce(Instrument, DataIn, nrofcoup, CRA, UFRac, alfamin, Tau, T2)[5]
        else:
            rateList = SmithWilsonBruteForce(Instrument, DataIn, nrofcoup, CRA, UFRac, alfamin, Tau, T2)[2]
        row = row + 1
        worksheet.write_string(row, col, str(date))     #???重複了ㄅ
        for i in range(len(rateList)-1):
            # print(rateList[i])
            worksheet.write_string(row, 0, str(i))
            if i == 0:
                continue
            else:
                worksheet.write_string(row, col, str(rateList[i]))
            row = row + 1
    workbook.close()

    # df = pd.read_csv(dirName+'/output'+str(k)+'_'+rateType+'.csv', encoding='utf-8')
    # df2 = df.T
    # df2.to_csv(dirName+'/output'+str(k)+'_'+rateType+'.csv', header=False)

    df = pd.read_excel(dirName+'/output'+str(k)+'_'+rateType+'.xlsx')
    df.set_index('Year',inplace=True)

    name = df.columns

    x = rateType+'_'+str(UFRac)+'_Interpolation_'+str(T2) #儲存圖片的檔名＆圖片的大標名稱
    for i in range(0,len(name),6):  #間隔6個月畫一條線
        plt.style.use('ggplot')
        plt.figure(num = 3,figsize= (20,13))
        plt.title(x)

        plt.xlabel('year',fontsize=30)
        plt.ylabel('forward rate', fontsize=30)
        plt.plot(df[name[i]][0:60].values,label = name[i]) #60年代表畫圖畫到60, 如果今天是收斂到40年可以改成40~
 #       plt.legend(bbox_to_anchor=(1, 0),loc = 3,borderaxespad=0) #看要不要加圖標

    plt.savefig(dirName+'/output'+str(k)+'_'+x+'.png')

# In[13]:

def genDataIn(dataFileName, dataDate, needOneList):        #needOneList會是1-10或1 5 10 其中一種
    dataIn = []
    dataFile = open(dataFileName, 'r')
    dataLines = dataFile.readlines()
    for data in dataLines:
        if dataDate == data.split(',')[0]:          #???為啥需要這行
            for i in range(10):
                if i+1 in needOneList:
                    dataIn.append([1, i+1, float(data.split(',')[i+1].replace('\n', ''))])
                else:
                    dataIn.append([0, i+1, float(data.split(',')[i+1].replace('\n', ''))])
    return dataIn

# In[20]:

DataIn = genDataIn(dataFileName, date, needOneBigList[1])

# In[21]:

DataIn

# In[22]:

DataIn = genDataIn(dataFileName, date, needOneBigList[0])
DataIn


#
# Copyright (c) 2012-2023 Snowflake Computing Inc. All rights reserved.
#

from __future__ import annotations

import os
from typing import Literal

from platformdirs import PlatformDirs, PlatformDirsABC

def _resolve_platform_dirs() -> PlatformDirsABC:
    """Decide on what PlatformDirs class to use.

    In case a folder exists (which can be customized with the environmental
    variable `SNOWFLAKE_HOME`) we use that directory as all platform
    directories. If this folder does not exist we'll fall back to platformdirs
    defaults.

    This helper function was introduced to make this code testable.
    """
    platformdir_kwargs = {
        "appname": "snowflake",
        "appauthor": False,
    }
    snowflake_home = os.path.expanduser(
        os.environ.get("SNOWFLAKE_HOME", "~/.snowflake/"),
    )
    if os.path.exists(snowflake_home):
        return SFPlatformDirs(
            snowflake_home,
            **platformdir_kwargs,
        )
    else:
        # In case SNOWFLAKE_HOME does not exist we fall back to using
        # platformdirs to determine where system files should be placed. Please
        # see docs for all the directories defined in the module at
        # https://platformdirs.readthedocs.io/
        return PlatformDirs(**platformdir_kwargs)

class SFPlatformDirs(PlatformDirsABC):
    """Single folder platformdirs.

    This class introduces a PlatformDir class where everything is placed into a
    single folder. This is intended for users who prefer portability over all
    else.
    """

    def __init__(
        self,
        single_dir: str,
        appname: str | None = None,
        appauthor: str | None | Literal[False] = None,
        version: str | None = None,
        roaming: bool = False,
        multipath: bool = False,
        opinion: bool = True,
        ensure_exists: bool = False,
    ) -> None:
        super().__init__(
            appname=appname,
            appauthor=appauthor,
            version=version,
            roaming=roaming,
            multipath=multipath,
            opinion=opinion,
            ensure_exists=ensure_exists,
        )
        self.single_dir = single_dir

    @property
    def user_data_dir(self) -> str:
        """data directory tied to to the user"""
        return self.single_dir

    @property
    def site_data_dir(self) -> str:
        """data directory shared by users"""
        return self.user_data_dir

    @property
    def user_config_dir(self) -> str:
        """config directory tied to the user"""
        return self.user_data_dir

    @property
    def site_config_dir(self) -> str:
        """config directory shared by the users"""
        return self.user_data_dir

    @property
    def user_cache_dir(self) -> str:
        """cache directory tied to the user"""
        return self.user_data_dir

    @property
    def site_cache_dir(self) -> str:
        """cache directory shared by users"""
        return self.user_data_dir

    @property
    def user_state_dir(self) -> str:
        """state directory tied to the user"""
        return self.user_data_dir

    @property
    def user_log_dir(self) -> str:
        """log directory tied to the user"""
        return self.user_data_dir

    @property
    def user_documents_dir(self) -> str:
        """documents directory tied to the user"""
        return self.user_data_dir

    @property
    def user_runtime_dir(self) -> str:
        """runtime directory tied to the user"""
        return self.user_data_dir

    @property
    def user_music_dir(self) -> str:
        """music directory tied to the user"""
        return self.user_data_dir

    @property
    def user_pictures_dir(self) -> str:
        """pictures directory tied to the user"""
        return self.user_data_dir

    @property
    def user_videos_dir(self) -> str:
        """videos directory tied to the user"""
        return self.user_data_dir

    @property
    def user_downloads_dir(self) -> str:
        """downloads directory tied to the user"""
        return self.user_data_dir


from django.conf.urls import include, url
from django.contrib import admin

admin.autodiscover()

urlpatterns = [
    url(r'^admin/', include(admin.site.urls)),

    url(r'^api-auth/', include('rest_framework.urls',
                               namespace='rest_framework')),

    url(r'^account/', include('account.urls')),

    url(r'^api/', include('orienteer.api.urls')),

    url(r'^', include('orienteer.urls')),
]


from django.contrib import admin
from django.urls import path, include

urlpatterns = [
    path('admin/', admin.site.urls),
    path('maps/', include('maps.urls')),
    path('times/', include('times.urls')),
    path('players/', include('players.urls')),
    path('servers/', include('servers.urls')),
    path('keys/', include('keys.urls'))
]


import pandas as pd
import numpy as np
import sys
import os
import random

from sklearn.metrics import mean_squared_error
from sklearn.metrics import cohen_kappa_score
from sklearn.metrics import mean_absolute_error
from sklearn.metrics import r2_score

from statsmodels import robust
from scipy import stats

from sklearn.model_selection import KFold
from sklearn import ensemble
from sklearn.linear_model import LinearRegression
from sklearn.neural_network import MLPRegressor
from sklearn.svm import SVR
from sklearn.linear_model import Lasso
import lightgbm as lgb

# Parameters
LABEL_COLUMN_NAME = '7444'
UNWANTED_COLUMNS = ['empresa']

N_FOLDS = 5
RANDOM_STATE = 1

def eval_bootstrap(df, features, md):
    X = df[features].values
    y = df[LABEL_COLUMN_NAME].values

    aa = []
    bb = []
    cc = []
    dd = []
    for i in range(1,5):
        a = []
        b = []
        c = []
        d = []
        cv = KFold(n_splits=N_FOLDS, shuffle=True, random_state=i)
        for (train, val) in cv.split(X, y):
            if md == 1: regressor = ensemble.GradientBoostingRegressor(n_estimators = 30, max_depth = 4, min_samples_split = 2, learning_rate = 0.1, loss = 'ls', random_state = RANDOM_STATE)
            elif md == 2: regressor = ensemble.RandomForestRegressor(n_estimators = 30, max_depth = 10, min_samples_split = 4, random_state = RANDOM_STATE)
            elif md == 3: regressor = SVR(kernel='rbf', C=100, gamma=0.1, epsilon=.1)
            elif md == 4: regressor = MLPRegressor(hidden_layer_sizes=(20,30,30,5,), batch_size = 10, activation='relu', random_state = RANDOM_STATE)
            elif md == 5: regressor = LinearRegression()
            elif md == 6: regressor = Lasso(alpha=0.1, random_state = RANDOM_STATE)
            elif md == 7: regressor = lgb.LGBMRegressor()

            regressor = regressor.fit(X[train], y[train])
            pred = regressor.predict(X[val])

            rmse = np.sqrt(np.mean((pred - y[val])**2))
            mae = mean_absolute_error(pred, y[val])
            r2 = r2_score(pred, y[val])
            kappa = cohen_kappa_score(np.round(pred), np.round(y[val]), weights='quadratic')

            a.insert(len(a), rmse)
            b.insert(len(b), mae)
            c.insert(len(c), r2)
            d.insert(len(d), kappa)

        aa.append(np.mean(a))
        bb.append(np.mean(b))
        cc.append(np.mean(c))
        dd.append(np.mean(d))
    return np.mean(aa),np.mean(bb),np.mean(dd)

def back_one(df, f, md):
    v = 0
    f1 = []
    f2 = []
    for i in f:
        f1.insert(len(f1), i)
        f2.insert(len(f2), i)
    A,B,C = eval_bootstrap(df, f1, md)
    z = A
    for i in f:
        f1.remove(i)
        A,B,C = eval_bootstrap(df, f1, md)
        print("%s,%f,%f,%f" % (f1,A,B,C))
        if A < z:
            v = 1
            z = A
            f2 = []
            for j in f1:
                f2.insert(len(f2), j)
        f1.insert(len(f1), i)
    return v,f2

# Reads dataset
df = pd.read_csv(sys.argv[1])
df.dropna(axis=0, subset=[LABEL_COLUMN_NAME], inplace=True)

RANDOM_STATE = 1
all_features = list(df.columns)

f = []
for x in UNWANTED_COLUMNS:
    if x in all_features: f.insert(len(f), x)
for x in f + [LABEL_COLUMN_NAME]:
    all_features.remove(x)

md = int(sys.argv[2])
f = []
i = 0
for f1 in all_features:
    if i == 50: break
    if f1 in f: continue
    k = 1000
    x = f1
    i = i + 1
    j = 0
    for f2 in all_features:
        if f2 in f:
            continue
        j = j + 1
        f.insert(len(f), f2)
        A,B,C = eval_bootstrap(df, f, md)
        print("%s,%f,%f,%f" % (f,A,B,C))
        z = A
        f.remove(f2)
        sys.stdout.flush()
        if z < k:
            x = f2
            k = z
    f.insert(len(f), x)
    if i > 2:
        v,f = back_one(df, f, md)
        while v == 1:
            v,f = back_one(df, f, md)
        i = len(f)


"""Configure and control camera via onvif."""

import datetime
import logging
import site
from enum import Enum

import numpy
from onvif import ONVIFCamera, ONVIFError

from frigate.config import FrigateConfig
from frigate.types import PTZMetricsTypes

logger = logging.getLogger(__name__)

class OnvifCommandEnum(str, Enum):
    """Holds all possible move commands"""

    init = "init"
    move_down = "move_down"
    move_left = "move_left"
    move_right = "move_right"
    move_up = "move_up"
    preset = "preset"
    stop = "stop"
    zoom_in = "zoom_in"
    zoom_out = "zoom_out"

class OnvifController:
    def __init__(
        self, config: FrigateConfig, ptz_metrics: dict[str, PTZMetricsTypes]
    ) -> None:
        self.cams: dict[str, ONVIFCamera] = {}
        self.ptz_metrics = ptz_metrics

        for cam_name, cam in config.cameras.items():
            if not cam.enabled:
                continue

            if cam.onvif.host:
                try:
                    self.cams[cam_name] = {
                        "onvif": ONVIFCamera(
                            cam.onvif.host,
                            cam.onvif.port,
                            cam.onvif.user,
                            cam.onvif.password,
                            wsdl_dir=site.getsitepackages()[0].replace(
                                "dist-packages", "site-packages"
                            )
                            + "/wsdl",
                        ),
                        "init": False,
                        "active": False,
                        "features": [],
                        "presets": {},
                    }
                except ONVIFError as e:
                    logger.error(f"Onvif connection to {cam.name} failed: {e}")

    def _init_onvif(self, camera_name: str) -> bool:
        onvif: ONVIFCamera = self.cams[camera_name]["onvif"]

        # create init services
        media = onvif.create_media_service()

        try:
            profile = media.GetProfiles()[0]
        except ONVIFError as e:
            logger.error(f"Unable to connect to camera: {camera_name}: {e}")
            return False

        ptz = onvif.create_ptz_service()
        request = ptz.create_type("GetConfigurationOptions")
        request.ConfigurationToken = profile.PTZConfiguration.token
        ptz_config = ptz.GetConfigurationOptions(request)
        logger.debug(f"Onvif config for {camera_name}: {ptz_config}")

        fov_space_id = next(
            (
                i
                for i, space in enumerate(
                    ptz_config.Spaces.RelativePanTiltTranslationSpace
                )
                if "TranslationSpaceFov" in space["URI"]
            ),
            None,
        )

        # setup continuous moving request
        move_request = ptz.create_type("ContinuousMove")
        move_request.ProfileToken = profile.token
        self.cams[camera_name]["move_request"] = move_request

        # setup relative moving request for autotracking
        move_request = ptz.create_type("RelativeMove")
        move_request.ProfileToken = profile.token
        if move_request.Translation is None and fov_space_id is not None:
            move_request.Translation = ptz.GetStatus(
                {"ProfileToken": profile.token}
            ).Position
            move_request.Translation.PanTilt.space = ptz_config["Spaces"][
                "RelativePanTiltTranslationSpace"
            ][fov_space_id]["URI"]

        try:
            move_request.Translation.Zoom.space = ptz_config["Spaces"][
                "RelativeZoomTranslationSpace"
            ][0]["URI"]
        except Exception:
            # camera does not support relative zoom
            pass

        if move_request.Speed is None:
            move_request.Speed = ptz.GetStatus({"ProfileToken": profile.token}).Position
        self.cams[camera_name]["relative_move_request"] = move_request

        # setup relative moving request for autotracking
        move_request = ptz.create_type("AbsoluteMove")
        move_request.ProfileToken = profile.token
        self.cams[camera_name]["absolute_move_request"] = move_request

        # status request for autotracking
        status_request = ptz.create_type("GetStatus")
        status_request.ProfileToken = profile.token
        self.cams[camera_name]["status_request"] = status_request

        # setup existing presets
        try:
            presets: list[dict] = ptz.GetPresets({"ProfileToken": profile.token})
        except ONVIFError as e:
            logger.warning(f"Unable to get presets from camera: {camera_name}: {e}")
            presets = []

        for preset in presets:
            self.cams[camera_name]["presets"][preset["Name"].lower()] = preset["token"]

        # get list of supported features
        ptz_config = ptz.GetConfigurationOptions(request)
        supported_features = []

        if ptz_config.Spaces and ptz_config.Spaces.ContinuousPanTiltVelocitySpace:
            supported_features.append("pt")

        if ptz_config.Spaces and ptz_config.Spaces.ContinuousZoomVelocitySpace:
            supported_features.append("zoom")

        if ptz_config.Spaces and ptz_config.Spaces.RelativePanTiltTranslationSpace:
            supported_features.append("pt-r")

        if ptz_config.Spaces and ptz_config.Spaces.RelativeZoomTranslationSpace:
            supported_features.append("zoom-r")

        if fov_space_id is not None:
            supported_features.append("pt-r-fov")
            self.cams[camera_name][
                "relative_fov_range"
            ] = ptz_config.Spaces.RelativePanTiltTranslationSpace[fov_space_id]

        self.cams[camera_name]["relative_fov_supported"] = fov_space_id is not None

        self.cams[camera_name]["features"] = supported_features

        self.cams[camera_name]["init"] = True
        return True

    def _stop(self, camera_name: str) -> None:
        onvif: ONVIFCamera = self.cams[camera_name]["onvif"]
        move_request = self.cams[camera_name]["move_request"]
        onvif.get_service("ptz").Stop(
            {
                "ProfileToken": move_request.ProfileToken,
                "PanTilt": True,
                "Zoom": True,
            }
        )
        self.cams[camera_name]["active"] = False

    def _move(self, camera_name: str, command: OnvifCommandEnum) -> None:
        if self.cams[camera_name]["active"]:
            logger.warning(
                f"{camera_name} is already performing an action, stopping..."
            )
            self._stop(camera_name)

        self.cams[camera_name]["active"] = True
        onvif: ONVIFCamera = self.cams[camera_name]["onvif"]
        move_request = self.cams[camera_name]["move_request"]

        if command == OnvifCommandEnum.move_left:
            move_request.Velocity = {"PanTilt": {"x": -0.5, "y": 0}}
        elif command == OnvifCommandEnum.move_right:
            move_request.Velocity = {"PanTilt": {"x": 0.5, "y": 0}}
        elif command == OnvifCommandEnum.move_up:
            move_request.Velocity = {
                "PanTilt": {
                    "x": 0,
                    "y": 0.5,
                }
            }
        elif command == OnvifCommandEnum.move_down:
            move_request.Velocity = {
                "PanTilt": {
                    "x": 0,
                    "y": -0.5,
                }
            }

        onvif.get_service("ptz").ContinuousMove(move_request)

    def _move_relative(self, camera_name: str, pan, tilt, speed) -> None:
        if not self.cams[camera_name]["relative_fov_supported"]:
            logger.error(f"{camera_name} does not support ONVIF RelativeMove (FOV).")
            return

        logger.debug(f"{camera_name} called RelativeMove: pan: {pan} tilt: {tilt}")

        if self.cams[camera_name]["active"]:
            logger.warning(
                f"{camera_name} is already performing an action, not moving..."
            )
            return

        self.cams[camera_name]["active"] = True
        self.ptz_metrics[camera_name]["ptz_stopped"].clear()
        logger.debug(f"PTZ start time: {datetime.datetime.now().timestamp()}")
        self.ptz_metrics[camera_name][
            "ptz_start_time"
        ].value = datetime.datetime.now().timestamp()
        self.ptz_metrics[camera_name]["ptz_stop_time"].value = 0
        onvif: ONVIFCamera = self.cams[camera_name]["onvif"]
        move_request = self.cams[camera_name]["relative_move_request"]

        # function takes in -1 to 1 for pan and tilt, interpolate to the values of the camera.
        # The onvif spec says this can report as +INF and -INF, so this may need to be modified
        pan = numpy.interp(
            pan,
            [-1, 1],
            [
                self.cams[camera_name]["relative_fov_range"]["XRange"]["Min"],
                self.cams[camera_name]["relative_fov_range"]["XRange"]["Max"],
            ],
        )
        tilt = numpy.interp(
            tilt,
            [-1, 1],
            [
                self.cams[camera_name]["relative_fov_range"]["YRange"]["Min"],
                self.cams[camera_name]["relative_fov_range"]["YRange"]["Max"],
            ],
        )

        move_request.Speed = {
            "PanTilt": {
                "x": speed,
                "y": speed,
            },
            "Zoom": 0,
        }

        move_request.Translation.PanTilt.x = pan
        move_request.Translation.PanTilt.y = tilt
        move_request.Translation.Zoom.x = 0

        onvif.get_service("ptz").RelativeMove(move_request)

        self.cams[camera_name]["active"] = False

    def _move_to_preset(self, camera_name: str, preset: str) -> None:
        if preset not in self.cams[camera_name]["presets"]:
            logger.error(f"{preset} is not a valid preset for {camera_name}")
            return

        self.cams[camera_name]["active"] = True
        self.ptz_metrics[camera_name]["ptz_stopped"].clear()
        move_request = self.cams[camera_name]["move_request"]
        onvif: ONVIFCamera = self.cams[camera_name]["onvif"]
        preset_token = self.cams[camera_name]["presets"][preset]
        onvif.get_service("ptz").GotoPreset(
            {
                "ProfileToken": move_request.ProfileToken,
                "PresetToken": preset_token,
            }
        )
        self.ptz_metrics[camera_name]["ptz_stopped"].set()
        self.cams[camera_name]["active"] = False

    def _zoom(self, camera_name: str, command: OnvifCommandEnum) -> None:
        if self.cams[camera_name]["active"]:
            logger.warning(
                f"{camera_name} is already performing an action, stopping..."
            )
            self._stop(camera_name)

        self.cams[camera_name]["active"] = True
        onvif: ONVIFCamera = self.cams[camera_name]["onvif"]
        move_request = self.cams[camera_name]["move_request"]

        if command == OnvifCommandEnum.zoom_in:
            move_request.Velocity = {"Zoom": {"x": 0.5}}
        elif command == OnvifCommandEnum.zoom_out:
            move_request.Velocity = {"Zoom": {"x": -0.5}}

        onvif.get_service("ptz").ContinuousMove(move_request)

    def handle_command(
        self, camera_name: str, command: OnvifCommandEnum, param: str = ""
    ) -> None:
        if camera_name not in self.cams.keys():
            logger.error(f"Onvif is not setup for {camera_name}")
            return

        if not self.cams[camera_name]["init"]:
            if not self._init_onvif(camera_name):
                return

        if command == OnvifCommandEnum.init:
            # already init
            return
        elif command == OnvifCommandEnum.stop:
            self._stop(camera_name)
        elif command == OnvifCommandEnum.preset:
            self._move_to_preset(camera_name, param)
        elif (
            command == OnvifCommandEnum.zoom_in or command == OnvifCommandEnum.zoom_out
        ):
            self._zoom(camera_name, command)
        else:
            self._move(camera_name, command)

    def get_camera_info(self, camera_name: str) -> dict[str, any]:
        if camera_name not in self.cams.keys():
            logger.error(f"Onvif is not setup for {camera_name}")
            return {}

        if not self.cams[camera_name]["init"]:
            self._init_onvif(camera_name)

        return {
            "name": camera_name,
            "features": self.cams[camera_name]["features"],
            "presets": list(self.cams[camera_name]["presets"].keys()),
        }

    def get_camera_status(self, camera_name: str) -> dict[str, any]:
        if camera_name not in self.cams.keys():
            logger.error(f"Onvif is not setup for {camera_name}")
            return {}

        if not self.cams[camera_name]["init"]:
            self._init_onvif(camera_name)

        onvif: ONVIFCamera = self.cams[camera_name]["onvif"]
        status_request = self.cams[camera_name]["status_request"]
        status = onvif.get_service("ptz").GetStatus(status_request)

        if status.MoveStatus.PanTilt == "IDLE" and status.MoveStatus.Zoom == "IDLE":
            self.cams[camera_name]["active"] = False
            if not self.ptz_metrics[camera_name]["ptz_stopped"].is_set():
                self.ptz_metrics[camera_name]["ptz_stopped"].set()

                logger.debug(f"PTZ stop time: {datetime.datetime.now().timestamp()}")

                self.ptz_metrics[camera_name][
                    "ptz_stop_time"
                ].value = datetime.datetime.now().timestamp()
        else:
            self.cams[camera_name]["active"] = True
            if self.ptz_metrics[camera_name]["ptz_stopped"].is_set():
                self.ptz_metrics[camera_name]["ptz_stopped"].clear()

                logger.debug(f"PTZ start time: {datetime.datetime.now().timestamp()}")

                self.ptz_metrics[camera_name][
                    "ptz_start_time"
                ].value = datetime.datetime.now().timestamp()
                self.ptz_metrics[camera_name]["ptz_stop_time"].value = 0

        return {
            "pan": status.Position.PanTilt.x,
            "tilt": status.Position.PanTilt.y,
            "zoom": status.Position.Zoom.x,
            "pantilt_moving": status.MoveStatus.PanTilt,
            "zoom_moving": status.MoveStatus.Zoom,
        }