#!/usr/bin/env python
# -*- coding: utf-8 mode: python -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8

u"""%(scriptname)s: afficher les contacts téléphoniques

USAGE
    %(scriptname)s [patterns...]"""

import os, sys, re, string
from os import path

from ulib.all import *

class ContactsrcFile(ShConfigFile):
    CONTACTSRC = "contactsrc"
    DEFAULT_CONTACTSFILE =  "Contacts.txt"

    def __init__(self, file=None, raise_exception=True):
        if file is None:
            testdir = path.join(scriptdir, "../../test")
            utoolsrcdir = path.join(path.expanduser("~"), ".utools")
            if path.isdir(testdir): file = path.join(testdir, self.CONTACTSRC)
            else: file = path.join(utoolsrcdir, self.CONTACTSRC)
            raise_exception = False
        ShConfigFile.__init__(self, file=file, raise_exception=raise_exception)

    CONTACTSDIR = "contactsdir"
    CONTACTSFILE = "contacts"

    def __p(self, p, refdir=None):
        if refdir is not None:
            if path.isfile(refdir): refdir = path.split(refdir)[0]
        return abspath2(path.expanduser(p), refdir)
    def __pcd(self, p):
        return self.__p(p, self.get_contactsdir())

    def __get(self, name, default, list=False, strip=False, fix_path=True):
        if fix_path is True: fix_path = self.__pcd
        if self.has_key(name):
            if list:
                values = self.get_list(name, strip)
                if fix_path: values = [fix_path(value) for value in values]
                value = values
            else:
                value = self[name]
                if fix_path: value = fix_path(value)
        else:
            value = default
        return value

    def __join(self, values):
        if isseq(values): values = u"\n".join(map(lambda v: _u(v), values))
        else: values = _u(values)
        return values

    def __set(self, name, value, list=False):
        if value is None:
            if self.has_key(name): del self[name]
        else:
            self[name] = self.__join(value)

    def get_contactsdir(self):
        """Retourner le répertoire de base pour les fichiers de contact
        Par défaut, il s'agit de ~/.utools
        """
        return self.__get(self.CONTACTSDIR, self.absfiledir, list=True, strip=True, fix_path=self.__p)
    def set_contactsdir(self, contactsdir=None):
        self.__set(self.CONTACTSDIR, contactsdir, list=True)

    def get_default_contactsfile(self):
        return self.__pcd(self.DEFAULT_CONTACTSFILE)

    def get_contactsfile(self, default=True):
        """Retourner la valeur contactsfile si elle est définie, default sinon.
        Il s'agit du fichier qui contient les tâches par défaut.
        Si default==True, retourner la première valeur de get_stores(), sinon
        la valeur par défaut ~/.utools/Contacts.txt
        """
        if default is True:
            default = self.get_default_contactsfile()
        return self.__get(self.CONTACTSFILE, default)
    def set_contactsfile(self, contactsfile):
        self.__set(self.CONTACTSFILE, contactsfile)


def compile_pattern(pattern):
    if isstr(pattern): pattern = re.compile(pattern)
    return pattern

TEL = u"Tél personnel"
FAX = u"Fax personnel"
GSM = u"GSM personnel"
EMAIL = u"E-mail"
ADRESSE = u"Adresse personnelle"
PROFTEL = u"Tél professionnel"
PROFFAX = u"Fax professionnel"
PROFGSM = u"GSM Professionnel"
PROFEMAIL = u"E-mail professionnel"
PROFADRESSE = u"Adresse professionnelle"

class Telephone(object):
    _tel = None
    _prefix = None

    RE_SPACES = re.compile(r'\s+')
    PREFIXES = {None: TEL, u"tel": TEL, u"fax": FAX, u"gsm": GSM,
                u"ptel": PROFTEL, u"pfax": PROFFAX, u"pgsm": PROFGSM,
                u"email": EMAIL, u"mail": EMAIL,
                u"pemail": PROFEMAIL, u"pmail": PROFEMAIL,
                u"adresse": ADRESSE, u"padresse": PROFADRESSE,
                u"adr": ADRESSE, u"padr": PROFADRESSE,
                }
    LITERALS = [EMAIL, PROFEMAIL, ADRESSE, PROFADRESSE]

    def is_literal(cls, prefix):
        prefix = cls.PREFIXES.get(prefix, prefix)
        return prefix in cls.LITERALS
    is_literal = classmethod(is_literal)

    def __init__(self, tel, prefix=None):
        tel = self.RE_SPACES.sub(u"", tel)
        prefix = self.PREFIXES.get(prefix, prefix)
        self._tel = _u(tel)
        self._prefix = _u(prefix)

    prefix = property(lambda self: self._prefix)

    def get_tel(self, cooked=True):
        if not cooked or self._prefix in self.LITERALS:
            return self._tel
        else:
            tel = self._tel
            nums = []
            while tel:
                if len(tel) > 4:
                    num = tel[-2:]
                    tel = tel[:-2]
                    nums.insert(0, num)
                else:
                    nums.insert(0, tel)
                    tel = None
            return u" ".join(nums)
    tel = property(get_tel)

    def __str__(self):
        s = _s(self.tel)
        if self._prefix is not None:
            s = "%s (%s)" % (s, _s(self._prefix))
        return s
    def __repr__(self):
        tel = self.tel
        prefix = self._prefix
        if prefix is None: prefix = "None"
        else: prefix = "'%s'" % prefix
        return "%s('%s', %s)" % (self.__class__.__name__, tel, prefix)
    def __unicode__(self):
        u = _u(self.tel)
        if self._prefix is not None:
            u = u"%s (%s)" % (u, _u(self._prefix))
        return u

    def matches(self, pattern):
        return compile_pattern(pattern).search(self._tel) is not None

class Contact(object):
    _nom = None
    _prenom = None
    _tels = None

    RE_COMMA = re.compile(r'\s*,\s*')
    RE_PREF = re.compile(r'(\S+)=')
    RE_PREF_TEL = re.compile(r'(?:(\S+)=)?((?:\d|\s)+)$')
    RE_PREF_ANY = re.compile(r'(?:(\S+)=)?\s*(.+)\s*$')

    def __init__(self, line):
        parts = self.RE_COMMA.split(line.strip())
        self._nom = parts[0:1] and parts[0] or u""
        self._prenom = parts[1:2] and parts[1] or u""
        self._tels = []
        for part in filter(None, parts[2:]):
            if not part: continue
            mo = self.RE_PREF.match(part)
            if mo is not None:
                prefix = mo.group(1)
                if Telephone.is_literal(prefix):
                    mo = self.RE_PREF_ANY.match(part)
                    if mo is None: raise ValueError("inconsistent match: %s" % part)
                    prefix, tel = mo.group(1), mo.group(2)
                    self._tels.append(Telephone(tel, prefix))
                    continue
            mo = self.RE_PREF_TEL.match(part)
            if mo is None:
                raise ValueError("Invalid telephone number: %s" % part)
            prefix, tel = mo.group(1), mo.group(2)
            self._tels.append(Telephone(tel, prefix))

    nom = property(lambda self: self._nom)
    prenom = property(lambda self: self._prenom)
    tels = property(lambda self: self._tels)

    def get_np(self):
        np = []
        if self._nom: np.append(self._nom)
        if self._prenom: np.append(self._prenom)
        return u" ".join(np)
    np = property(get_np)

    def get_pn(self):
        pn = []
        if self._prenom: pn.append(self._prenom)
        if self._nom: pn.append(self._nom)
        return u" ".join(pn)
    pn = property(get_pn)

    def _tel(self, name, cooked=True):
        name = Telephone.PREFIXES.get(name, name)
        for tel in self.tels:
            if tel.prefix == name:
                return tel.get_tel(cooked)
        return None
    tel = property(lambda self: self._tel("tel"))
    fax = property(lambda self: self._tel("fax"))
    gsm = property(lambda self: self._tel("gsm"))
    mail = property(lambda self: self._tel("mail"))
    ptel = property(lambda self: self._tel("ptel"))
    pfax = property(lambda self: self._tel("pfax"))
    pgsm = property(lambda self: self._tel("pgsm"))
    pmail = property(lambda self: self._tel("pmail"))
    xtel = property(lambda self: self._tel("tel", False))
    xfax = property(lambda self: self._tel("fax", False))
    xgsm = property(lambda self: self._tel("gsm", False))
    xmail = property(lambda self: self._tel("mail", False))
    xptel = property(lambda self: self._tel("ptel", False))
    xpfax = property(lambda self: self._tel("pfax", False))
    xpgsm = property(lambda self: self._tel("pgsm", False))
    xpmail = property(lambda self: self._tel("pmail", False))

    def matches(self, patterns):
        if not isseq(patterns): patterns = (patterns,)
        patterns = map(compile_pattern, patterns)
        nom = self._nom
        prenom = self._prenom
        tels = self._tels
        for pattern in patterns:
            if pattern.search(nom) is None:
                if pattern.search(prenom) is None:
                    for tel in tels:
                        if tel.matches(pattern): return True
                    return False
        return True

RE_COMMENT = re.compile(r'^#|^\s*$')
def load_contacts(infdn):
    lines = Lines()
    lines.readlines(infdn)
    lines.filter(lambda line: RE_COMMENT.match(line) is None)
    return [Contact(line) for line in lines]

def show_contacts(contacts):
    for contact in contacts:
        etitle(contact.np)
        for tel in contact.tels:
            uprint(tel)

def display_help():
    uprint(__doc__ % globals())

def run_Contacts():
    options, longoptions = build_options([
        ('h', 'help', "Afficher l'aide"),
        ('o:', 'output=', "Exporter au format csv pour Outlook"),
        ])
    options, args = get_args(None, options, longoptions)
    action = "search"
    outf = None
    for option, value in options:
        if option in ('-h', '--help'):
            display_help()
            sys.exit(0)
        elif option in ('-o', '--output'):
            action = "export"
            outf = value

    cf = ContactsrcFile()
    contacts = load_contacts(cf.get_contactsfile())
    if action == "search":
        if args:
            patterns = map(compile_pattern, map(_u, args))
            contacts = [contact for contact in contacts
                        if contact.matches(patterns)]
        show_contacts(contacts)
    elif action == "export":
        lines = []
        lines.append(u'"Nom","Téléphone (domicile)","Télécopie (domicile)","Tél. mobile","Adresse de messagerie","Téléphone (bureau)","Télécopie (bureau)","Téléphone 2 (bureau)","Adresse de messagerie 2"\r\n')
        for contact in contacts:
            values = [contact.pn or u"",
                      contact.xtel or u"",
                      contact.xfax or u"",
                      contact.xgsm or u"",
                      contact.xmail or u"",
                      contact.xptel or u"",
                      contact.xpfax or u"",
                      contact.xpgsm or u"",
                      contact.xpmail or u"",
                      ]
            if values[7]:
                if not values[5]:
                    values[5] = values[7]
                    values[7] = u""
                elif not values[3]:
                    values[3] = values[7]
                    values[7] = u""

            lines.append(u",".join(values) + u"\r\n")
        cp1252 = UnicodeIO("cp1252")
        lines = map(cp1252.s, lines)

        close = True
        if outf != "-":
            outf = open(outf, "wb")
        else:
            outf = sys.stdout
            close = False
        outf.writelines(lines)
        if close: outf.close()

if __name__ == '__main__':
    run_Contacts()
