#!/usr/bin/python
# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*-
### BEGIN LICENSE
# Copyright (C) 2010 Allen Lowe <lallenlowe@gmail.com>
# This program is free software: you can redistribute it and/or modify it 
# under the terms of the GNU General Public License version 3, as published 
# by the Free Software Foundation.
# 
# This program is distributed in the hope that it will be useful, but 
# WITHOUT ANY WARRANTY; without even the implied warranties of 
# MERCHANTABILITY, SATISFACTORY QUALITY, 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, see <http://www.gnu.org/licenses/>.
### END LICENSE

import os
import sys
import logging
import gtk
import glib
import dbus
import dbus.service
from dbus.mainloop.glib import DBusGMainLoop

DBusGMainLoop(set_as_default=True)

PROJECT_ROOT_DIRECTORY = os.path.abspath(
    os.path.dirname(os.path.dirname(os.path.realpath(sys.argv[0]))))

if (os.path.exists(os.path.join(PROJECT_ROOT_DIRECTORY, 'dexter'))
    and PROJECT_ROOT_DIRECTORY not in sys.path):
    sys.path.insert(0, PROJECT_ROOT_DIRECTORY)
    os.putenv('PYTHONPATH', PROJECT_ROOT_DIRECTORY)  # for subprocesses

import dexter.backend.models as models
import dexter.backend.serverutils as utilities

# this is for testing the switch to a fully modular system
class DexterServer(dbus.service.Object):
    def __init__(self):

        if os.path.exists(sys.path[0]+"/data/ui/dexter-main.ui"):
            logging.info("Using data from sys.path[0] dir")
            self.datadir = sys.path[0]+"/data"
        elif os.path.exists("../data/ui/dexter-main.ui"):
            logging.info("Using data from current dir")
            self.datadir = "../data"
        elif os.path.exists("./data/ui/dexter-main.ui"):
            logging.info("Running locally")
            self.datadir = "./data"
        else:
            self.datadir = "/usr/share/dexter/"

        session_bus = dbus.SessionBus()
        is_running = session_bus.request_name("org.elementary.dexterserver") !=  \
                                        dbus.bus.REQUEST_NAME_REPLY_PRIMARY_OWNER

        if is_running:
            sys.exit(0)
        else:
            bus_name = dbus.service.BusName('org.elementary.dexterserver',
                                            bus=dbus.SessionBus())
            dbus.service.Object.__init__(self, bus_name,
                                         '/org/elementary/dexterserver')
            self.store = models.setup_db(models.DATA_DIR)
            models.create_tables(self.store)
            models.update_tables(self.store)

    #service methods
    @dbus.service.method('org.elementary.dexterserver', in_signature='ssssssa(ss)a(ss)aa{ss}a(ss)as', out_signature='i')
    def CreateContact(self, first_name, middle_name, last_name,
                      organization, birthday, photo_path, emails,
                      phones, addresses, imnicks, notes):
        if first_name == "None":
            first_name = None
        if middle_name == "None":
            middle_name = None
        if last_name == "None":
            last_name = None
        if organization == "None":
            organization = None
        if birthday == "None":
            birthday = None
        if photo_path == "None":
            photo_path = None
        for address in addresses:
            if address["street"] == "None":
                address["street"] = None
            if address["city"] == "None":
                address["city"] = None
            if address["state"] == "None":
                address["state"] = None
            if address["zip"] == "None":
                address["zip"] = None
            if address["type"] == "None":
                address["type"] = None
        for note in notes:
            if note.strip() == u"None" or not note.strip():
                note = None
        new_id = utilities.create_new_contact(first_name, middle_name, last_name,
                      organization, birthday, photo_path, emails,
                      phones, addresses, imnicks, notes, self.store)
        self.ContactAdded(self.convert_to_small_dict(self.store.find(models.Contact, models.Contact.id == new_id).one()))
        return new_id

    @dbus.service.method('org.elementary.dexterserver')
    def DeleteContact(self, id_number):
        utilities.delete_by_id(self.store, id_number)
        self.ContactsDeleted([id_number])

    @dbus.service.method('org.elementary.dexterserver')
    def DeleteContacts(self, id_numbers):
        for id_number in id_numbers:
            utilities.delete_by_id(self.store, id_number)
        self.ContactsDeleted(id_numbers)

    @dbus.service.method('org.elementary.dexterserver', out_signature='aa{ss}')
    def GetFullList(self):
        full_list = []
        contacts = self.store.find(models.Contact)
        for contact in contacts:
            new = self.convert_to_small_dict(contact)
            full_list.append(new)
        return full_list

    @dbus.service.method('org.elementary.dexterserver', out_signature='a{ss}')
    def GetContactByID(self, id_number):
        contact = self.store.find(models.Contact, models.Contact.id == id_number).one()
        if contact:
            contact_dict = self.convert_contact_to_dict(contact)
        else:
            contact_dict = dict()
        return contact_dict

    @dbus.service.method('org.elementary.dexterserver', out_signature='a(ss)')
    def GetPhones(self, id_number):
        phone_list = []
        phones = self.store.find(models.Phone, models.Phone.contact_id == id_number)
        for phone in phones:
            phone_list.append((self.none_to_string(phone.number), self.none_to_string(phone.phone_type)))
        return phone_list

    @dbus.service.method('org.elementary.dexterserver', out_signature='a(ss)')
    def GetEmails(self, id_number):
        email_list = []
        emails = self.store.find(models.Email, models.Email.contact_id == id_number)
        for email in emails:
            email_list.append((self.none_to_string(email.address), self.none_to_string(email.email_type)))
        return email_list

    @dbus.service.method('org.elementary.dexterserver', out_signature='aa{ss}')
    def GetAddresses(self, id_number):
        address_list = []
        addresses = self.store.find(models.Address, models.Address.contact_id == id_number)
        for address in addresses:
            adict = dict()
            adict["type"] = self.none_to_string(address.address_type)
            adict["street"] = self.none_to_string(address.street)
            adict["city"] = self.none_to_string(address.city)
            adict["state"] = self.none_to_string(address.state)
            adict["zip"] = self.none_to_string(address.zip)
            address_list.append(adict)
        return address_list

    @dbus.service.method('org.elementary.dexterserver', out_signature='a(ss)')
    def GetIMNicks(self, id_number):
        imnick_list = []
        imnicks = self.store.find(models.IMNick, models.IMNick.contact_id == id_number)
        for imnick in imnicks:
            imnick_list.append((self.none_to_string(imnick.nick), self.none_to_string(imnick.nick_type)))
        return imnick_list

    @dbus.service.method('org.elementary.dexterserver', out_signature='as')
    def GetNotes(self, id_number):
        text = ""
        for note in self.store.find(models.Note, models.Note.contact_id == id_number):
            text += note.text
        return [text]

    @dbus.service.method('org.elementary.dexterserver')
    def GetName(self, emailstring):
        """take an email address and return the full name associated with it"""
        try:
            email = self.store.find(models.Email,
                                    models.Email.address == emailstring).one()
            name = email.contact.full_name
        except:
            name = ""
        return name

    @dbus.service.method('org.elementary.dexterserver')
    def GetEmailAddresses(self, full_name):
        """take a full name and return a list of emails associated with it"""
        emails = []
        name_pieces = full_name.split()
        contact = self.store.find(models.Contact, 
                          models.Contact.first_name == name_pieces[0],
                          models.Contact.last_name == name_pieces[-1]).any()
        for email in contact.emails:
            emails.append(email.address)
        return emails

    @dbus.service.method('org.elementary.dexterserver', in_signature='s', out_signature='aa{ss}')
    def SearchContacts(self, search_string):
        """search for contacts that contain a string
           
        takes a search string, and returns a list of name/email strings.
        """
        results = []
        resultlist = []
        results = utilities.search_store(search_string, self.store)
        for contact in results:
            new = dict()
            new["full_name"] = contact.full_name
            new["id"] = str(contact.id)
            if contact.last_name:
                new["last_name"] = contact.last_name
            else:
                new["last_name"] = contact.first_name
            resultlist.append(new)
        return resultlist

    @dbus.service.method('org.elementary.dexterserver', in_signature='i', out_signature='aas')
    def GetBirthdays(self, month):
        """get a list of birthdays for a given month
           
        takes a month integer, and returns a list of lists containing name/birthday strings.
        """
        results = []
        results = utilities.get_birthdays(month, self.store)
        return results

    @dbus.service.method('org.elementary.dexterserver', in_signature='s', out_signature='as')
    def AutocompleteContact(self, search_string):
        """search for contacts that contain a string
           
        takes a search string, and returns a list of name/email strings.
        """
        results = []
        resultlist = []
        results = utilities.search_store(search_string, self.store)
        for contact in results:
            for email in contact.emails:
                resultlist.append(contact.full_name + " <" + email.address + ">")
        return resultlist

    @dbus.service.method('org.elementary.dexterserver')
    def ShowWindow(self):
        """present the running dexter window, or start one"""
        session_bus = dbus.SessionBus()
        try:
            dexter_object = session_bus.get_object("org.elementary.dexterapp",
                                        "/org/elementary/dexterapp")
            is_running = True
        except:
            is_running = False
        if is_running:
            self.WindowRequested()
        else:
            try:
                glib.spawn_async([self.datadir + "/../bin/dexter"])
            except glib.GError:
                glib.spawn_async(["/usr/bin/dexter"])

    @dbus.service.method('org.elementary.dexterserver')
    def NewContactDialog(self):
        """present a new contact window"""
        session_bus = dbus.SessionBus()
        try:
            dexter_object = session_bus.get_object("org.elementary.dexterapp",
                                        "/org/elementary/dexterapp")
            is_running = True
        except:
            is_running = False
        if is_running:
            self.ContactDialogRequested()
        else:
            glib.spawn_async(["/usr/bin/dexter", "-n"])

    @dbus.service.method('org.elementary.dexterserver', in_signature='ss')
    def EditContact(self, full_name, email):
        session_bus = dbus.SessionBus()
        try:
            dexter_object = session_bus.get_object("org.elementary.dexterapp",
                                        "/org/elementary/dexterapp")
            is_running = True
        except:
            is_running = False
        results = utilities.get_by_name_email(full_name, email, self.store)
        if len(results):
            new_id = results[0].id
        else:
            name_pieces = full_name.split()
            if len(name_pieces) == 2:
                new_id = utilities.create_new_contact(name_pieces[0], None, name_pieces[1],
                      None, None, None, [(email, None)], [], [], [], [], self.store)
            elif len(name_pieces) == 1:
                 new_id = utilities.create_new_contact(name_pieces[0], None, None,
                      None, None, None, [(email, None)], [], [], [], [], self.store)
            else:
                new_id = utilities.create_new_contact(name_pieces[0], name_pieces[1], name_pieces[2],
                      None, None, None, [(email, None)], [], [], [], [], self.store)
            self.ContactAdded(self.convert_to_small_dict(self.store.find(models.Contact, models.Contact.id == new_id).one()))
        if is_running:
            self.EditContactRequested(new_id)
        else:
            glib.spawn_async(["/usr/bin/dexter", "-e", str(new_id)])

    @dbus.service.method('org.elementary.dexterserver')
    def CancelImport(self):
        self.CancelImportRequested()

    @dbus.service.method('org.elementary.dexterserver', in_signature='as')
    def ImportVcardPath(self, paths):
        self.VcardImportRequested(paths)

    @dbus.service.method('org.elementary.dexterserver')
    def DBCommit(self):
        self.store.commit()

    @dbus.service.method('org.elementary.dexterserver')
    def QuitServer(self):
        self.store.commit()
        self.ServerClosed()
        gtk.main_quit()

    #signals
    @dbus.service.signal('org.elementary.dexterserver')
    def ContactAdded(self, contact):
        pass

    @dbus.service.signal('org.elementary.dexterserver')
    def ContactsDeleted(self, contact_ids):
        if not self.store.find(models.Contact).count():
            self.ContactsCleared()

    @dbus.service.signal('org.elementary.dexterserver')
    def ContactsCleared(self):
        pass

    @dbus.service.signal('org.elementary.dexterserver')
    def WindowRequested(self):
        pass

    @dbus.service.signal('org.elementary.dexterserver')
    def ContactDialogRequested(self):
        pass

    @dbus.service.signal('org.elementary.dexterserver')
    def EditContactRequested(self, uid):
        pass

    @dbus.service.signal('org.elementary.dexterserver')
    def CancelImportRequested(self):
        pass

    @dbus.service.signal('org.elementary.dexterserver')
    def VcardImportRequested(self, paths):
        pass

    @dbus.service.signal('org.elementary.dexterserver')
    def ServerClosed(self):
        pass

    #helper methods
    def convert_contact_to_dict(self, contact):
        new = dict()
        new["id"] = str(contact.id)
        new["photo"] = self.none_to_string(contact.photo)
        new["first_name"] = contact.first_name
        new["middle_name"] = self.none_to_string(contact.middle_name)
        new["last_name"] = self.none_to_string(contact.last_name)
        new["full_name"] = contact.full_name
        new["organization"] = self.none_to_string(contact.organization)
        new["birthday"] = str(self.none_to_string(contact.birthday))
        return new

    def convert_to_small_dict(self, contact):
        new = dict()
        new["full_name"] = contact.full_name
        new["id"] = str(contact.id)
        if contact.last_name:
            new["last_name"] = contact.last_name
        else:
            new["last_name"] = contact.first_name
        return new

    def none_to_string(self, data):
        if not data:
            data = "None"
        return data

if __name__ == "__main__":
    service = DexterServer()
    try:
        gtk.main()
    except:
        service.ServerClosed()
        service.store.commit()
