# -*- python -*-
# -*- coding: utf-8 -*-
#
# Gramps - a GTK+/GNOME based genealogy program
#
# Copyright (C) 2011  Serge Noiraud
#
# 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
#

# $Id$

"""
Geography for one person
"""
#-------------------------------------------------------------------------
#
# Python modules
#
#-------------------------------------------------------------------------
import os
import sys
import operator
from gi.repository import Gdk
KEY_TAB = Gdk.KEY_Tab
import socket
from gi.repository import Gtk
from gi.repository import GObject
from gi.repository import GLib

#-------------------------------------------------------------------------
#
# set up logging
#
#-------------------------------------------------------------------------
import logging
_LOG = logging.getLogger("GeoGraphy.geoperson")

#-------------------------------------------------------------------------
#
# Gramps Modules
#
#-------------------------------------------------------------------------
from gramps.gen.const import GRAMPS_LOCALE as glocale
_ = glocale.translation.gettext
from gramps.gen.lib import EventRoleType, EventType
from gramps.gen.config import config
from gramps.gen.datehandler import displayer
from gramps.gen.display.name import displayer as _nd
from gramps.gen.utils.place import conv_lat_lon
from gramps.gui.views.pageview import PageView
from gramps.gui.editors import EditPlace
from gramps.gui.selectors.selectplace import SelectPlace
from gramps.gui.filters.sidebar import PersonSidebarFilter
from gramps.gui.views.navigationview import NavigationView
from gramps.gui.views.bookmarks import PersonBookmarks
from gramps.plugins.lib.maps import constants
from gramps.plugins.lib.maps.geography import GeoGraphyView

#-------------------------------------------------------------------------
#
# Constants
#
#-------------------------------------------------------------------------

_UI_DEF = '''\
<ui>
<menubar name="MenuBar">
<menu action="GoMenu">
  <placeholder name="CommonGo">
    <menuitem action="Back"/>
    <menuitem action="Forward"/>
    <separator/>
    <menuitem action="HomePerson"/>
    <separator/>
  </placeholder>
</menu>
<menu action="EditMenu">
  <placeholder name="CommonEdit">
    <menuitem action="PrintView"/>
  </placeholder>
</menu>
<menu action="BookMenu">
  <placeholder name="AddEditBook">
    <menuitem action="AddBook"/>
    <menuitem action="EditBook"/>
  </placeholder>
</menu>
</menubar>
<toolbar name="ToolBar">
<placeholder name="CommonNavigation">
  <toolitem action="Back"/>  
  <toolitem action="Forward"/>  
  <toolitem action="HomePerson"/>
</placeholder>
<placeholder name="CommonEdit">
  <toolitem action="PrintView"/>
</placeholder>
</toolbar>
</ui>
'''

#-------------------------------------------------------------------------
#
# GeoView
#
#-------------------------------------------------------------------------
class GeoPerson(GeoGraphyView):
    """
    The view used to render person map.
    """
    CONFIGSETTINGS = (
        ('geography.path', constants.GEOGRAPHY_PATH),

        ('geography.zoom', 10),
        ('geography.zoom_when_center', 12),
        ('geography.show_cross', True),
        ('geography.lock', False),
        ('geography.center-lat', 0.0),
        ('geography.center-lon', 0.0),

        #('geography.gps_mode', GPS_DISABLED),
        #('geography.gps_update_rate', float(1.0)),
        #('geography.max_gps_zoom', 16),
        #('geography.gps_increment', GPS_INCREMENT),

        ('geography.map_service', constants.OPENSTREETMAP),
        ('geography.max_places', 5000),

        # specific to geoperson :

        ('geography.steps', 20),
        ('geography.maximum_lon_lat', 30),
        ('geography.speed', 100),
        )

    def __init__(self, pdata, dbstate, uistate, nav_group=0):
        GeoGraphyView.__init__(self, _("Person places map"),
                                      pdata, dbstate, uistate, 
                                      PersonBookmarks,
                                      nav_group)
        self.dbstate = dbstate
        self.uistate = uistate
        self.place_list = []
        self.place_without_coordinates = []
        self.minlat = self.maxlat = self.minlon = self.maxlon = 0.0
        self.minyear = 9999
        self.maxyear = 0
        self.nbplaces = 0
        self.nbmarkers = 0
        self.sort = []
        self.additional_uis.append(self.additional_ui())
        self.no_show_places_in_status_bar = False

    def get_title(self):
        """
        Used to set the titlebar in the configuration window.
        """
        return _('GeoPerson')

    def get_stock(self):
        """
        Returns the name of the stock icon to use for the display.
        This assumes that this icon has already been registered 
        as a stock icon.
        """
        return 'geo-show-person'
    
    def get_viewtype_stock(self):
        """Type of view in category
        """
        return 'geo-show-person'

    def additional_ui(self):
        """
        Specifies the UIManager XML code that defines the menus and buttons
        associated with the interface.
        """
        return _UI_DEF

    def navigation_type(self):
        """
        Indicates the navigation type. Navigation type can be the string
        name of any of the primary objects.
        """
        return 'Person'

    def add_bookmark(self, obj):
        mlist = self.selected_handles()
        if mlist:
            self.bookmarks.add(mlist[0])
        else:
            from gramps.gui.dialog import WarningDialog
            WarningDialog(
                _("Could Not Set a Bookmark"), 
                _("A bookmark could not be set because "
                  "no one was selected."))

    def goto_handle(self, handle=None):
        """
        Rebuild the tree with the given person handle as the root.
        """
        self.places_found = []
        active = self.get_active()
        if handle:
            self._createmap(handle)
        elif active:
            p1 = self.dbstate.db.get_person_from_handle(active)
            self._createmap(p1)
        self.uistate.modify_statusbar(self.dbstate)

    def build_tree(self):
        """
        This is called by the parent class when the view becomes visible. Since
        all handling of visibility is now in rebuild_trees, see that for more
        information.
        """
        active = self.get_active()
        self._createmap(active)

    def animate(self, menu, marks, index, stepyear):
        """
        Create all movements for the people's event.
        Yes, you can see the person moving.
        """
        if len(marks) == 0:
            self.already_started = False
            return False
        i = int(index)
        ni = i + 1
        if ni == len(marks) :
            self.already_started = False
            return False
        startlat = float(marks[i][3])
        startlon = float(marks[i][4])
        heading = 1
        if index == 0 and stepyear == 0:
            self.remove_all_gps()
            self.large_move = False
            self.osm.gps_add(startlat, startlon, heading)
        endlat = float(marks[ni][3])
        endlon = float(marks[ni][4])
        max_lon_lat = float(self._config.get("geography.maximum_lon_lat")) / 10
        if stepyear < 9000:
            if (( abs(float(endlat) - float(startlat)) > max_lon_lat ) or
                ( abs(float(endlon) - float(startlon)) > max_lon_lat )):
                self.large_move = True
                stepyear = 9000
            else:
                self.large_move = False
        # year format = YYYYMMDD ( for sort )
        startyear = str(marks[i][6])[0:4]
        endyear = str(marks[ni][6])[0:4]
        endmov = str(marks[len(marks)-1][6])[0:4]
        years = int(endyear) - int(startyear)
        if years < 1:
            years = 1
        if stepyear > 8999:
            latstep = ( endlat - startlat ) / self._config.get("geography.steps")
            lonstep = ( endlon - startlon ) / self._config.get("geography.steps")
            startlat += ( latstep * (stepyear - 8999) )
            startlon += ( lonstep * (stepyear - 8999) )
        else:
            latstep = ( endlat - startlat ) / years
            lonstep = ( endlon - startlon ) / years
            stepyear = 1 if stepyear < 1 else stepyear
            startlat += ( latstep * stepyear )
            startlon += ( lonstep * stepyear )
        self.osm.gps_add(startlat, startlon, heading)
        stepyear += 1
        difflat = round(( startlat - endlat ) if startlat > endlat else \
                                           ( endlat - startlat ), 8)
        difflon = round(( startlon - endlon ) if startlon > endlon else \
                                           ( endlon - startlon ), 8)
        if ( difflat == 0.0 and difflon == 0.0 ):
            i += 1
            self.large_move = False
            stepyear = 1
        # if geography.speed = 100 => 100ms => 1s per 10 years. 
        # For a 100 years person, it takes 10 secondes.
        # if large_move, one step is the difflat or difflon / geography.steps
        # in this case, stepyear is >= 9000
        # large move means longitude or latitude differences greater than geography.maximum_lon_lat
        # degrees.
        GLib.timeout_add(int(self._config.get("geography.speed")), self.animate,
                         menu, marks, i, stepyear)
        return False

    def _createmap(self,obj):
        """
        Create all markers for each people's event in the database which has 
        a lat/lon.
        """
        dbstate = self.dbstate
        self.cal = config.get('preferences.calendar-format-report')
        self.place_list = []
        self.place_without_coordinates = []
        self.minlat = self.maxlat = self.minlon = self.maxlon = 0.0
        self.minyear = 9999
        self.maxyear = 0
        latitude = ""
        longitude = ""
        self.message_layer.clear_messages()
        person_handle = self.uistate.get_active('Person')
        person = dbstate.db.get_person_from_handle(person_handle)
        if person is not None:
            # For each event, if we have a place, set a marker.
            self.message_layer.add_message(_("Person places for %s") % _nd.display(person))
            for event_ref in person.get_event_ref_list():
                if not event_ref:
                    continue
                event = dbstate.db.get_event_from_handle(event_ref.ref)
                role = event_ref.get_role()
                eyear = str("%04d" % event.get_date_object().to_calendar(self.cal).get_year()) + \
                          str("%02d" % event.get_date_object().to_calendar(self.cal).get_month()) + \
                          str("%02d" % event.get_date_object().to_calendar(self.cal).get_day())
                place_handle = event.get_place_handle()
                if place_handle:
                    place = dbstate.db.get_place_from_handle(place_handle)
                    if place:
                        longitude = place.get_longitude()
                        latitude = place.get_latitude()
                        latitude, longitude = conv_lat_lon(latitude,
                                                           longitude, "D.D8")
                        descr = place.get_title()
                        evt = EventType(event.get_type())
                        descr1 = _("%(eventtype)s : %(name)s") % {
                                        'eventtype': evt,
                                        'name': _nd.display(person)}
                        # place.get_longitude and place.get_latitude return
                        # one string. We have coordinates when the two values
                        # contains non null string.
                        if ( longitude and latitude ):
                            self._append_to_places_list(descr, evt,
                                                        _nd.display(person),
                                                        latitude, longitude,
                                                        descr1, eyear,
                                                        event.get_type(),
                                                        person.gramps_id,
                                                        place.gramps_id,
                                                        event.gramps_id,
                                                        role
                                                        )
                        else:
                            self._append_to_places_without_coord(
                                                        place.gramps_id, descr)
            family_list = person.get_family_handle_list()
            for family_hdl in family_list:
                family = self.dbstate.db.get_family_from_handle(family_hdl)
                if family is not None:
                    fhandle = family_list[0] # first is primary
                    fam = dbstate.db.get_family_from_handle(fhandle)
                    handle = fam.get_father_handle()
                    father = dbstate.db.get_person_from_handle(handle)
                    descr1 = " - "
                    if father:
                        descr1 = "%s - " % _nd.display(father)
                    handle = fam.get_mother_handle()
                    mother = dbstate.db.get_person_from_handle(handle)
                    if mother:
                        descr1 = "%s%s" % ( descr1, _nd.display(mother))
                    for event_ref in family.get_event_ref_list():
                        if event_ref:
                            event = dbstate.db.get_event_from_handle(event_ref.ref)
                            role = event_ref.get_role()
                            if event.get_place_handle():
                                place_handle = event.get_place_handle()
                                if place_handle:
                                    place = dbstate.db.get_place_from_handle(place_handle)
                                    if place:
                                        longitude = place.get_longitude()
                                        latitude = place.get_latitude()
                                        latitude, longitude = conv_lat_lon(latitude,
                                                                           longitude, "D.D8")
                                        descr = place.get_title()
                                        evt = EventType(event.get_type())
                                        eyear = str("%04d" % event.get_date_object().to_calendar(self.cal).get_year()) + \
                                                  str("%02d" % event.get_date_object().to_calendar(self.cal).get_month()) + \
                                                  str("%02d" % event.get_date_object().to_calendar(self.cal).get_day())
                                        if ( longitude and latitude ):
                                            self._append_to_places_list(descr,
                                                 evt, _nd.display(person),
                                                 latitude, longitude,
                                                 descr1, eyear,
                                                 event.get_type(),
                                                 person.gramps_id,
                                                 place.gramps_id,
                                                 event.gramps_id,
                                                 role
                                                 )
                                        else:
                                            self._append_to_places_without_coord( place.gramps_id, descr)

            self.sort = sorted(self.place_list,
                               key=operator.itemgetter(6)
                              )
            self._create_markers()

    def bubble_message(self, event, lat, lon, marks):
        self.menu = Gtk.Menu()
        menu = self.menu
        menu.set_title("person")
        message = ""
        oldplace = ""
        prevmark = None
        for mark in marks:
            if oldplace != "":
                add_item = Gtk.MenuItem(label=message)
                add_item.show()
                menu.append(add_item)
                self.itemoption = Gtk.Menu()
                itemoption = self.itemoption
                itemoption.set_title(message)
                itemoption.show()
                message = ""
                add_item.set_submenu(itemoption)
                modify = Gtk.MenuItem(label=_("Edit Event"))
                modify.show()
                modify.connect("activate", self.edit_event,
                               event, lat, lon, prevmark)
                itemoption.append(modify)
                center = Gtk.MenuItem(label=_("Center on this place"))
                center.show()
                center.connect("activate", self.center_here,
                               event, lat, lon, prevmark)
                itemoption.append(center)
            if mark[0] != oldplace:
                if message != "":
                    add_item = Gtk.MenuItem()
                    add_item.show()
                    menu.append(add_item)
                    self.itemoption = Gtk.Menu()
                    itemoption = self.itemoption
                    itemoption.set_title(message)
                    itemoption.show()
                    message = ""
                    add_item.set_submenu(itemoption)
                    modify = Gtk.MenuItem(label=_("Edit Event"))
                    modify.show()
                    modify.connect("activate", self.edit_event,
                                   event, lat, lon, mark)
                    itemoption.append(modify)
                    center = Gtk.MenuItem(label=_("Center on this place"))
                    center.show()
                    center.connect("activate", self.center_here,
                                   event, lat, lon, mark)
                    itemoption.append(center)
                message = "%s :" % mark[0]
                self.add_place_bubble_message(event, lat, lon,
                                              marks, menu, message, mark)
                oldplace = mark[0]
                message = ""
            evt = self.dbstate.db.get_event_from_gramps_id(mark[10])
            # format the date as described in preferences.
            date = displayer.display(evt.get_date_object())
            if date == "":
                date = _("Unknown")
            if ( mark[11] == EventRoleType.PRIMARY ):
                message = "(%s) %s : %s" % ( date, mark[2], mark[1] )
            elif ( mark[11] == EventRoleType.FAMILY ):
                (father_name, mother_name) = self._get_father_and_mother_name(evt)
                message = "(%s) %s : %s - %s" % ( date, mark[7], father_name, mother_name )
            else:
                descr = evt.get_description()
                if descr == "":
                    descr = _('No description')
                message = "(%s) %s => %s" % ( date, mark[11], descr)
            prevmark = mark
        add_item = Gtk.MenuItem(label=message)
        add_item.show()
        menu.append(add_item)
        self.itemoption = Gtk.Menu()
        itemoption = self.itemoption
        itemoption.set_title(message)
        itemoption.show()
        add_item.set_submenu(itemoption)
        modify = Gtk.MenuItem(label=_("Edit Event"))
        modify.show()
        modify.connect("activate", self.edit_event, event, lat, lon, prevmark)
        itemoption.append(modify)
        center = Gtk.MenuItem(label=_("Center on this place"))
        center.show()
        center.connect("activate", self.center_here, event, lat, lon, prevmark)
        itemoption.append(center)
        menu.show()
        menu.popup(None, None,
                   lambda menu, data: (event.get_root_coords()[0],
                                       event.get_root_coords()[1], True),
                   None, event.button, event.time)
        return 1

    def add_specific_menu(self, menu, event, lat, lon): 
        """ 
        Add specific entry to the navigation menu.
        """ 
        add_item = Gtk.MenuItem()
        add_item.show()
        menu.append(add_item)
        add_item = Gtk.MenuItem(label=_("Animate"))
        add_item.connect("activate", self.animate, self.sort, 0, 0)
        add_item.show()
        menu.append(add_item)
        return

    def get_default_gramplets(self):
        """
        Define the default gramplets for the sidebar and bottombar.
        """
        return (("Person Filter",),
                ())

    def specific_options(self, configdialog):
        """
        Add specific entry to the preference menu.
        Must be done in the associated view.
        """
        table = Gtk.Table(2, 2)
        table.set_border_width(12)
        table.set_col_spacings(6)
        table.set_row_spacings(6)
        configdialog.add_text(table,
                _('Animation speed in milliseconds (big value means slower)'),
                1, line_wrap=False)
        configdialog.add_slider(table, 
                "", 
                2, 'geography.speed',
                (100, 1000))
        configdialog.add_text(table,
                _('How many steps between two markers when we are on large move ?'),
                3, line_wrap=False)
        configdialog.add_slider(table, 
                "", 
                4, 'geography.steps',
                (10, 100))
        configdialog.add_text(table,
                _('The minimum latitude/longitude to select large move.\n'
                  'The value is in tenth of degree.'),
                5, line_wrap=False)
        configdialog.add_slider(table, 
                "", 
                6, 'geography.maximum_lon_lat',
                (5, 50))
        return _('The animation parameters'), table
