#!/usr/bin/env python
# coding: utf-8
# vim: set ts=4 sw=4 expandtab sts=4:
# ----------------------------------------------------------------------------
# "THE BEER-WARE LICENSE" (Revision 42):
# <geier@lostpackets.de> wrote this file. As long as you retain this notice you
# can do whatever you want with this stuff. If we meet some day, and you think
# this stuff is worth it, you can buy me a beer in return Christian Geier
# ----------------------------------------------------------------------------


##############################
# imports {{{

#from IPython.Debugger import Tracer; debug_here = Tracer()
import StringIO
import sys
import getopt
import signal
from os import path

from ConfigParser import SafeConfigParser

try:
    import vobject
except ImportError:
    print "py-vobject not installed"
    sys.exit(1)

try:
    import lxml.etree as ET
except:
    print "py-lxml not installad"
    sys.exit(1)

try:
    import pycurl
except:
    print "pycurl not installed"
    sys.exit(1)


try:
    import sqlite3
except:
    print "pysqlite3 not installed"
    sys.exit(1)

# /imports
###########################}}}

##############################################
# database creation foo {{{

def makeTables():
    conn = sqlite3.connect(dbPath)
    c = conn.cursor()
    try:
            c.execute('''CREATE TABLE version        (
                    version INTEGER
                    )''')
            if DEBUG:
                    print "created version table"
    except sqlite3.OperationalError as detail:
        if DEBUG:
            print detail
    except:
            sys.stderr.write('Failed to connect to database, Unknown Error')
    conn.commit()

    # VERSION information
    database_version = 1
    c.execute('SELECT version FROM version')
    result = c.fetchone()
    if result == None:
        t = (database_version,)
        c.execute('INSERT INTO version (version) VALUES (?)',t)
        conn.commit()
    elif not result[0] == database_version:
         sys.exit(str(dbPath) + " is probably not a valid or an "
            "outdated database.\nYou should consider to remove it and "
            "sync again using pycardsyncer.\n")

    try:
            c.execute('''CREATE TABLE vcardtable        (
                    href TEXT PRIMARY KEY NOT NULL,
                    etag TEXT,
                    name TEXT
                    )''')
            if DEBUG:
                    print "created vcards table"
    except sqlite3.OperationalError as detail:
        if DEBUG:
            print detail
    except:
            sys.stderr.write('Failed to connect to database, Unknown Error')
    conn.commit()

    # properties table
    try:
            c.execute('''CREATE TABLE properties(
            id INTEGER PRIMARY KEY NOT NULL,
            property TEXT NOT NULL,
            value TEXT,
            href TEXT NOT NULL,
            FOREIGN KEY(href) REFERENCES vcardtable(href)
            )''')
            if DEBUG:
                    print "created properties table"
    except sqlite3.OperationalError as detail:
            if DEBUG:
                    print detail
    except:
            sys.stderr.write('Failed to connect to database, Unknown Error')
    conn.commit()

    # parameter table
    try:
            c.execute('''CREATE TABLE parameters(
            parameter TEXT NOT NULL,
            value TEXT,
            href TEXT NOT NULL,
            property_id INTEGER NOT NULL,
            FOREIGN KEY(href) REFERENCES vcardtable(href),
            FOREIGN KEY(property_id) REFERENCES properties(id)
            )''')
            if DEBUG:
                    print "created parameters table"
    except sqlite3.OperationalError as detail:
            if DEBUG:
                    print detail
    except:
            sys.stderr.write('Failed to connect to database, Unknown Error')
    conn.commit()

    c.close()

    c.close()

# /database foo }}}
##############################################

def smartencode(s):
    unicode(s).encode("utf-8", "strict")

def usage():
    print """usage: pycardsyncer [options]
       pycardsyncer --help/-h       show this help text
       pycardsyncer --version/-v    show version
options: 
  -c <file>/--config=<file> location of config file
                            default: ~/.pycard/pycard.config
  --debug show some debugging output"""

def version():
    print "0.3.4"

def getXMLProps():
    response=StringIO.StringIO()
    header=StringIO.StringIO()
    c = pycurl.Curl()
    c.setopt(pycurl.WRITEFUNCTION, response.write)
    c.setopt(pycurl.HTTPHEADER, ['depth:1'])

    c.setopt(pycurl.HEADERFUNCTION, header.write)
    c.setopt(pycurl.USERPWD, user+":"+passwd)
    c.setopt(pycurl.URL, resource)
    if (insecureSSL == 1):
        c.setopt(pycurl.SSL_VERIFYPEER, 0)
    c.setopt(pycurl.SSLVERSION, pycurl.SSLVERSION_SSLv3)
    c.setopt(pycurl.CUSTOMREQUEST, "PROPFIND")
    c.perform()
    c.close
    header =  header.getvalue()
    xml = response.getvalue()
    if (header.find("addressbook") == -1):
        print "URL is not a CardDAV resource"
        sys.exit(1)
    return xml

def processXMLProps(xml):
    namespace="{DAV:}"
    element = ET.XML(xml)
    abook = dict()
    for response in element.iterchildren():
        if (response.tag == namespace+"response"):
            href = ""
            etag = ""
            insert = False
            text = {
                'davical' : "text/vcard",
                'sabredav' : "text/x-vcard"
            }[davserver]
            for refprop in response.iterchildren():
                if (refprop.tag == namespace+"href"):
                    href = refprop.text
                for prop in refprop.iterchildren():
                    for props in prop.iterchildren():
                        if (props.tag == namespace+"getcontenttype" and props.text == text):
                            insert = True
                        if (props.tag == namespace+"getetag"):
                            etag = props.text
                        #print("%s - %s" % (props.tag, props.text))
                    if insert:
                        abook[href] = etag
    return abook

def checkNewEtag(vRef, vEtag):
    """returns True when the etag has been updated, otherwise False"""
    conn = sqlite3.connect(dbPath)
    c = conn.cursor()
    t = (vRef,)
    c.execute('SELECT etag FROM vcardtable WHERE href=(?);',t)
    if vEtag==c.fetchall()[0][0]:
        returnCode=False
    else:
        returnCode=True
    conn.commit()
    c.close()
    return returnCode

def checkVRefExists(vRef):
    conn = sqlite3.connect(dbPath)
    c = conn.cursor()
    t=(vRef,)
    c.execute('SELECT count(*) FROM vcardtable WHERE href=(?);',t)
    if c.fetchall()==[(1,)]:
        returnCode=False
    else:
        returnCode=True
    conn.commit()
    c.close()
    return returnCode

def insertVRef(vRef):
    """inserts vRef into the vcardtable"""
    conn = sqlite3.connect(dbPath)
    c = conn.cursor()
    t =(vRef,)
    c.execute('INSERT INTO vcardtable (href) VALUES (?);', t)
    conn.commit()
    c.close()

def updateEtag(vRef, vEtag):
    conn = sqlite3.connect(dbPath)
    c = conn.cursor()
    t =(vEtag,vRef,)
    c.execute('UPDATE vcardtable SET etag=(?) WHERE href=(?);',t)
    conn.commit()
    c.close()
    return True

def insertName(vRef,name):
    conn = sqlite3.connect(dbPath)
    c = conn.cursor()
    t =(name, vRef)
    c.execute('UPDATE vcardtable SET name=(?) WHERE href=(?)',t) 
    conn.commit()
    c.close()

def deleteVcardFromDb(vRef):
    conn = sqlite3.connect(dbPath)
    c = conn.cursor()
    t =(vRef,)
    c.execute('DELETE from properties WHERE href=(?)',t) 
    c.execute('DELETE from parameters WHERE href=(?)',t) 
    conn.commit()
    c.close()

def getVcard(vRef):
    response=StringIO.StringIO()
    header=StringIO.StringIO()
    c = pycurl.Curl()
    c.setopt(pycurl.SSLVERSION, pycurl.SSLVERSION_SSLv3)
    c.setopt(pycurl.WRITEFUNCTION, response.write)
    c.setopt(pycurl.HEADERFUNCTION, header.write)
    c.setopt(pycurl.USERPWD, user+":"+passwd)
    c.setopt(pycurl.URL, baseUrl+vRef)
    if (insecureSSL == 1):
        c.setopt(pycurl.SSL_VERIFYPEER, 0)
    c.perform()
    c.close

    header =  header.getvalue()
    vCard = response.getvalue()
    
    find = {
        'davical' : "addressbook",
        'sabredav' : "text/x-vcard"
    }[davserver]

    if (header.find(find) == -1):
        print "URL is not a CardDAV resource"
        sys.exit(1)

    return vCard

def insertVcardInDb(vRef,v):
    if v.name == "VCARD":
    #    v.prettyPrint()
        for line in v.getChildren():
            try:
                line.transformFromNative()
            except:
                pass

            propertyName = line.name
            propertyValue = line.value
            if (propertyName == "PHOTO"): # for now, we cannot handle photos
                pass
            else:
                conn = sqlite3.connect(dbPath)
                c = conn.cursor()
                t =(unicode(propertyName), unicode(propertyValue),vRef,)
                #if DEBUG:
                    #print "unicode(propertyName): ", unicode(propertyName), " type: ", type(unicode(propertyName))
                    #print "unicode(propertyValue): ", unicode(propertyValue), " type: ", type(unicode(propertyValue))
                    #print "vRef: ", vRef, " type: ", type(vRef)
                    #print "t: ", t, " type: ", type(t)

                c.execute('INSERT INTO properties (property, value, href) VALUES (?,?,?);', t)
                lastrowid = c.lastrowid
                if line.params:
                    for key in line.params.keys():
                        for keyValue in line.params[key]:
                            t =(key, keyValue,vRef,lastrowid)
                            c.execute('INSERT INTO parameters (parameter, value, href,property_id) VALUES (?,?,?,?);', t)
                            #print key + ": " + i
                conn.commit()
                c.close()
    else:
        return -1 # this is not a vcard


def signal_handler(signal, frame):
    sys.exit(0)

def main(argv):
    configfile="~/.pycard/pycard.conf"
    global user, passwd, resource, baseUrl, insecureSSL, dbPath, davserver, DEBUG
    DEBUG=0
    dbPath="~/.pycard/pycard.conf"
    try:
        opts, args = getopt.getopt(argv, "hc:v", ["help", "config=","version","debug"])
    except getopt.GetoptError:
        usage()
        sys.exit(1)

    for opt, arg in opts:
        if opt in ("-h","--help"):
            usage()
            sys.exit()
        elif opt in ("-v","--version"):
            version()
            sys.exit()
        elif opt == "--debug":
            DEBUG=1
        elif opt in ("-c","--config"):
            configfile=arg


    # trying to hide some ugly python code on pressing Ctrl-C
    signal.signal(signal.SIGINT, signal_handler)

    #config file foo 
    configfile = path.expanduser(configfile)
    if DEBUG:
        print "reading config from ", configfile
    parser = SafeConfigParser()
    parser.read(configfile)
    user = parser.get('default', 'user')
    passwd = parser.get('default', 'passwd')
    resource = parser.get('default', 'resource')
    baseUrl = parser.get('default', 'base_url')
    insecureSSL = parser.getint('default', 'insecure_ssl')
    dbPath = path.expanduser(parser.get('default', 'db_path'))
    davserver = parser.get('default', 'davserver')
    DEBUG = parser.getint('default', 'DEBUG')
    if DEBUG:
        print "using config:"
        print "user: ", user
        print "passwd: ", passwd
        print "resource: ", resource
        print "baseUrl: ", baseUrl
        print "insecureSSL: ", insecureSSL
        print "dbPath: ", dbPath
        print "\n"

    makeTables()

    # primitive XML processing
    xml = getXMLProps()

    abook = processXMLProps(xml)

    for vRef,vEtag  in abook.iteritems():
        if checkVRefExists(vRef):
            insertVRef(vRef)
        
        if checkNewEtag(vRef,vEtag):
            deleteVcardFromDb(vRef)
            if DEBUG:
                print "getting ", vRef, " etag: ", vEtag

            vcard = getVcard(vRef)
            v = vobject.readOne(vcard)

            # DEBUG
            #print v.prettyPrint()
            #print v.contents
            # /DEBUG
            

            # this is the important part
            try:
                insertVcardInDb(vRef,v)
                updateEtag(vRef, vEtag)
            except Exception, err:
                sys.stderr.write("ERROR: something went wrong while inserting VCard %s into the db" % str(vRef))
                sys.stderr.write('%s\n' % str(err))




if __name__ == "__main__":
    main(sys.argv[1:])

