/*********************************************************************** 
MultiSync Plugin for KDE 3.x
Copyright (C) 2004 Stewart Heitmann <sheitmann@users.sourceforge.net>

This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License version 2 as
published by the Free Software Foundation;

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF THIRD PARTY RIGHTS.
IN NO EVENT SHALL THE COPYRIGHT HOLDER(S) AND AUTHOR(S) BE LIABLE FOR ANY
CLAIM, OR ANY SPECIAL INDIRECT OR CONSEQUENTIAL DAMAGES, OR ANY DAMAGES 
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.

ALL LIABILITY, INCLUDING LIABILITY FOR INFRINGEMENT OF ANY PATENTS, 
COPYRIGHTS, TRADEMARKS OR OTHER RIGHTS, RELATING TO USE OF THIS 
SOFTWARE IS DISCLAIMED.
*************************************************************************/

extern "C"
{
#include <multisync.h>
#include "kaddrbook.h"
}

#include <kapp.h>
#include <kabc/stdaddressbook.h>
#include <kabc/vcardconverter.h>
#include <glib.h>
#include <qsignal.h>
#include <qfile.h> 


static
bool load_UIDlist(QString filename, QDateTime& syncdate, QStringList& uidlist)
    {
    //printf("%s: %s(..)\n", __FILE__, __FUNCTION__);

    //clear the UIDlist of any existing entries and set a sensible default syncdate 
    uidlist.clear();
    syncdate.setTime_t(0);

    //open the file and read into UIDlist
    QFile syncfile(filename);
    if ( !syncfile.open(IO_ReadOnly) )
        {
        //printf("%s: %s : couldnt open %s for reading\n", __FILE__, __FUNCTION__, filename.latin1());
        return false;
        }

    QTextStream stream( &syncfile );
    QString line;

    //read the syncdate from the first line of the file
    if ( !stream.atEnd() )
        {
        QString line = stream.readLine();
        syncdate = QDateTime::fromString(line,Qt::ISODate);
        //printf("%s: %s : datestring %s converted to %s\n", __FILE__, __FUNCTION__,
        //       line.latin1(), syncdate.toString().latin1());
        }
    else
        {
        //printf("%s: %s : couldnt read syncdate\n", __FILE__, __FUNCTION__);
        return false;
        }
    
    //read one UID per line
    while ( !stream.atEnd() )
       {
       QString line = stream.readLine();
       //printf( "%s\n", line.latin1() );
       
       //accumulate UID entries in UIDlist
       uidlist += line;
       }

    syncfile.close();

    //printf( "read %d UID entries dated %s\n", uidlist.count(), syncdate.toString().latin1());
    return true;
    }


static
bool save_UIDlist(QString filename, QDateTime syncdate, QStringList uidlist)
    {
    //printf("%s: %s(..)\n", __FILE__, __FUNCTION__);

    QFile syncfile(filename);
    if ( syncfile.open( IO_WriteOnly ) )
        {
        QTextStream stream( &syncfile );

        //write the sync date
        stream << syncdate.toString(Qt::ISODate) << endl;

        //write one UID per line
        for ( QStringList::Iterator it = uidlist.begin(); it!=uidlist.end(); ++it )
                stream << *it << endl;

        syncfile.close();
        return true;
        }
    return false;
    }
  

static
void unfold_vcard(char *vcard)
    {
    char* in  = vcard;
    char* out = vcard;
    while ( *in )
        {
        /* remove any occurrences of "=[CR][LF]"                */
        /* these denote folded line markers in VCARD format.    */
        /* Dont know why, but Evolution uses the leading "="    */
        /* character to (presumably) denote a control sequence. */
        /* This is not quite how I interpret the VCARD RFC2426  */
        /* spec (section 2.6 line delimiting and folding).      */
        /* This seems to work though, so thats the main thing!  */
        if (in[0]=='=' && in[1]==13 && in[2]==10)
            in+=3;
        else
            *out++ = *in++;
        }
    *out=0;
    }

class kaddrbook
    {
    private:
        KABC::AddressBook* addressbookptr;   
        KABC::Ticket* addressbookticket;
        QString uidfilename; 
        QStringList uidlist;
        QDateTime syncdate, newsyncdate;

    public:        
        kaddrbook(const char* datapath)
            : uidfilename(datapath)
            {
            //printf("kdepim_plugin: %s(%s)\n", __FUNCTION__, datapath);

            //get a handle to the standard KDE addressbook
            addressbookptr = KABC::StdAddressBook::self();

            //ensure a NULL Ticket ptr
            addressbookticket=NULL;

            //complete the uidfilename which until now is only a dir name
            uidfilename.append("/kdeplugin");
            
            //Load the UID list and syncdate from the uid file.
            //They represent the UIDs as last seen by the sync-engine (on syncdate).
            //Note: Dont worry if the load fails, it is probably just the first time
            //the plugin was executed and there is no uid file to read, in
            //which case we want an empty prevUIDlist anyway.
            load_UIDlist(uidfilename, syncdate, uidlist);
            };


        int get_changes(GList **listptr, int newdbs) 
            {
            //printf("kdepim_plugin: kaddrbook::%s(newdbs=%d)\n", __FUNCTION__, newdbs);

            const char* multisync_debug = getenv("MULTISYNC_DEBUG");
            GList *list = *listptr;

            //remember when we started this current sync
            newsyncdate = QDateTime::currentDateTime();

            // We must reload the KDE addressbook in order to retrieve the latest changes.
            if (!addressbookptr->load())
                {
                if (multisync_debug)
                    printf("kdepim_plugin: couldnt reload KDE addressbook, aborting sync.\n");
                return -1;
                }
            if (multisync_debug)
                printf("kdepim_plugin: KDE addressbook reloaded OK.\n");

            //Lock the addressbook
            addressbookticket = addressbookptr->requestSaveTicket();
            if (!addressbookticket)
                {
                if (multisync_debug)
                    printf("kdepim_plugin: couldnt lock KDE addressbook, aborting sync.\n");
                return -1;
                }
            if (multisync_debug)
                printf("kdepim_plugin: KDE addressbook locked OK.\n");

            //Clear the current UID list if this is a database resync.
            //This will cause all of our KDE addressbook entries to be returned as ADDED
            if (newdbs!=0)
                uidlist.clear();
            //printf("%s: %s : plugin UID list has %d entries\n", __FILE__, __FUNCTION__, uidlist.count());

            //Check the entries of the KDE addressbook against the last entries seen by the sync-engine
            for (KABC::AddressBook::Iterator it=addressbookptr->begin(); it!=addressbookptr->end(); it++ )
                {
                int change_type;

                //Get the revision date of the KDE addressbook entry.
                //Regard entries with invalid revision dates as having just been changed.
                QDateTime revdate = it->revision();
                if (!revdate.isValid())
                    {
                    revdate = newsyncdate;      //use date of this sync as the revision date.
                    it->setRevision(revdate);   //update the Addressbook entry for future reference.
                    }     

                //Find matching record in our list of UIDs seen by the sync-engine
                QStringList::Iterator seen_uid = uidlist.find(it->uid());
                if (seen_uid == uidlist.end())
                    {
                    //No match found, so this KDE addressbook entry is new to the sync-engine
                    change_type = SYNC_OBJ_ADDED;

                    //Add a corresponding entry to our UID list
                    uidlist.append(it->uid());

                    if (multisync_debug)
                        printf("kdepim_plugin: ADDED    UID=%s (%s)\n", it->uid().latin1(),it->assembledName().latin1());
                    }
                else
                    {
                    //match found, the KDE addressbook entry has been synchronised before,
                    //but it could still have been altered since then so we check its
                    //revision date against the date of the last synchronisation.
                    //If it has not been altered then skip to the next KDE addresbook entry
                    if (revdate <= syncdate)
                        continue;
                    else
                        {
                        change_type = SYNC_OBJ_MODIFIED;

                        if (multisync_debug)
                             printf("kdepim_plugin: MODIFIED UID=%s (%s)\n", it->uid().latin1(),it->assembledName().latin1());
                        }
                    }

                //At this point change_type is either SYNC_OBJ_ADDED or SYNC_OBJ_MODIFIED
                //but we treat both cases the same so it doesnt matter.

                // gmalloc a changed_object for this phonebook entry             
                changed_object *chobj = (changed_object *)g_malloc0(sizeof(changed_object));

                // gmalloc a copy of the UID string
                chobj->uid = g_strdup(it->uid().latin1());
                //printf("%s: %s : UID=%s\n",__FILE__,__FUNCTION__,chobj->uid);
        
                // Convert the VCARD data into a string
                KABC::VCardConverter converter;
                QString card = converter.createVCard(*it);

                // gmalloc a copy of the VCARD string
                chobj->comp = g_strdup(card.latin1());
                if (multisync_debug)
                     printf("%s",chobj->comp);

                // set the remaining fields
                chobj->removepriority = NULL;
                chobj->change_type    = change_type; 
                chobj->object_type    = SYNC_OBJECT_TYPE_PHONEBOOK;

                //Append the changed_object to the return list
                list = g_list_append(list,chobj);
                }

            //Now find any UIDs in the old list that are missing from the KDE addressbook,
            //these are records that have been deleted.
            for (QStringList::Iterator it=uidlist.begin(); it!=uidlist.end(); it++)
                {
                //Find matching record in KDE Addressbook.
                //If not found then we have detected an entry that had been deleted from the KDE
                //adressbook since the last syunchronisation.
                KABC::Addressee addressee = addressbookptr->findByUid(*it);
                if (!addressee.isEmpty())
                    continue;

                //this prevUID is missing from currUID, it represents a DELETED entry
                if (multisync_debug)
                    printf("kdepim_plugin: DELETED  UID=%s\n", (*it).latin1());

                // gmalloc a changed_object for this phonebook entry             
                changed_object *chobj = (changed_object *)g_malloc0(sizeof(changed_object));

                // gmalloc a copy of the UID string
                chobj->uid = g_strdup((*it).latin1());
        
                // set the remaining details
                chobj->comp           = NULL;
                chobj->removepriority = NULL;
                chobj->change_type    = SYNC_OBJ_HARDDELETED;
                chobj->object_type    = SYNC_OBJECT_TYPE_PHONEBOOK;

                // Append the changed_object to the return list
                list = g_list_append(list,chobj);                        

                //Remove the corresponding UID from our own UID list
                uidlist.remove(it);
                }

            //update caller's listptr and return success
            *listptr = list;
            return 0;
            }


        int modify_list(GList *changes, GList **resultsptr) 
            {
            //printf("kdepim_plugin: kaddrbook::%s()\n",__FUNCTION__);

            const char* multisync_debug = getenv("MULTISYNC_DEBUG");
            GList *results = *resultsptr;
    
            // Ensure we still have a lock on the KDE addressbook (we ought to)
            if (addressbookticket==NULL)
                {
                //This should never happen, but just in case....
                if (multisync_debug)
                    printf("kdepim_plugin: lock on KDE addressbook was lost, aborting sync.\n");
                return -1;
                }

            KABC::VCardConverter converter;
            while (changes)
                {
                changed_object *obj = (changed_object *)(changes->data);
                syncobj_modify_result *modify_result;
        
                /* allocate and initialize return struct for this change entry */
                modify_result = (syncobj_modify_result*)g_malloc0(sizeof(syncobj_modify_result));
                modify_result->result = SYNC_MSG_MODIFYERROR;
                modify_result->returnuid = NULL;
        
                // Do database modifications according to object type
                if (obj->object_type & SYNC_OBJECT_TYPE_PHONEBOOK)
                    {
                    /* treat modified objects without UIDs as if they were newly added objects */
                    if (obj->change_type==SYNC_OBJ_MODIFIED && obj->uid==NULL)
                        obj->change_type=SYNC_OBJ_ADDED;
        
                    switch(obj->change_type)
                        {
                        case SYNC_OBJ_MODIFIED:
                            {
                            // convert VCARD string from obj->comp into an Addresse object.
                            // KABC::VCardConverter doesnt do VCARD unfolding so we must do it ourselves first.
                            unfold_vcard(obj->comp);
                            KABC::Addressee addressee = converter.parseVCard(QString(obj->comp));

                            // ensure it has the correct UID
                            addressee.setUid(QString(obj->uid));

                            // replace the current addressbook entry (if any) with the new one
                            addressbookptr->insertAddressee(addressee);

                            if (multisync_debug)
                                {
                                printf("kdepim_plugin: KDE ADDRESSBOOK ENTRY UPDATED (UID=%s)\n", obj->uid); 
                                printf("%s",obj->comp);
                                }

                            modify_result->result = SYNC_MSG_REQDONE;
                            break;
                            }
        
                        case SYNC_OBJ_ADDED:
                            {
                            // convert VCARD string from obj->comp into an Addresse object
                            // KABC::VCardConverter doesnt do VCARD unfolding so we must do it ourselves first.
                            unfold_vcard(obj->comp);
                            KABC::Addressee addressee = converter.parseVCard(QString(obj->comp));

                            // ensure it has a NULL UID
                            addressee.setUid(QString(NULL));

                            // add the new address to the addressbook
                            addressbookptr->insertAddressee(addressee);

                            if (multisync_debug)
                                {
                                printf("kdepim_plugin: KDE ADDRESSBOOK ENTRY ADDED (UID=%s)\n", addressee.uid().latin1());
                                printf("%s",obj->comp);
                                }

                            // return the UID of the new entry along with the result
                            modify_result->returnuid = g_strdup(addressee.uid().latin1());
                            modify_result->result = SYNC_MSG_REQDONE;
                            break;
                            }
        
                        case SYNC_OBJ_SOFTDELETED:
                        case SYNC_OBJ_HARDDELETED:
                            {
                            if (obj->uid==NULL)
                                {
                                modify_result->result = SYNC_MSG_DONTEXISTERROR;
                                break;
                                }

                            //find addressbook entry with matching UID and delete it
                            KABC::Addressee addressee = addressbookptr->findByUid(QString(obj->uid));
                            if(!addressee.isEmpty())
                               addressbookptr->removeAddressee(addressee);

                            if (multisync_debug)
                                printf("kdepim_plugin: KDE ADDRESSBOOK ENTRY DELETED (UID=%s)\n", obj->uid);

                            modify_result->result = SYNC_MSG_REQDONE;
                            break;
                            }
                        }    
                    }
        
                results = g_list_append(results, modify_result);        
                changes = changes->next;
                }

            //update caller's resultsptr and return success
            *resultsptr = results;
            return 0;
            }


       void sync_done(bool success) 
            {
            //printf("kdepim_plugin: kaddrbook::%s(%d)\n", __FUNCTION__, success);
            const char* multisync_debug = getenv("MULTISYNC_DEBUG");

            if (!addressbookticket)
                {
                //This should never happen, but just in case....
                if (multisync_debug)
                    printf("kdepim_plugin: lock on KDE addressbook was lost, aborting sync.\n");

                //Reload the UID list from file as ours has been corrupted
                load_UIDlist(uidfilename, syncdate, uidlist);
                return;
                }

            if (success)
                {
                // Save and unlock the KDE addressbook
                addressbookptr->save(addressbookticket);
                if (multisync_debug)
                    printf("kdepim_plugin: KDE addressbook saved and unlocked.\n");
                
                // Save our plugin's UID list with the latest sync date
                // (ignore a failed save, its not critical)
                save_UIDlist(uidfilename,newsyncdate,uidlist);
            
                //update the syncdate                   
                syncdate = newsyncdate;       
                }
            else
                {
                // Unlock the KDE addressbook and discard all changes
                addressbookptr->releaseSaveTicket(addressbookticket);
                if (multisync_debug)
                    printf("kdepim_plugin: KDE addressbook unlocked and changes discarded.\n");

                //Reload the UID list from file as ours has been corrupted
                load_UIDlist(uidfilename, syncdate, uidlist);
                }
 
            addressbookticket=NULL;
            }
   
    };




extern "C"
void* kaddrbook_connect(const char* datapath) 
    {
    //printf("kdepim_plugin: %s(%s)\n", __FUNCTION__, datapath);
    kaddrbook *pkaddrbook = new kaddrbook(datapath);
    return (void*)pkaddrbook; 
    }

extern "C"
int kaddrbook_disconnect(void* pkaddrbook) 
    {
    //printf("kdepim_plugin: %s(%p)\n", __FUNCTION__, pkaddrbook);
    kaddrbook *p = (kaddrbook*)pkaddrbook;
    delete p;
    return 0;
    }


extern "C"
int kaddrbook_get_changes(void *pkaddrbook, GList **listptr, int newdbs) 
    {
    //printf("kdepim_plugin: %s(%p,%p,%d)\n", __FUNCTION__, pkaddrbook, listptr, newdbs);
    return ((kaddrbook*)(pkaddrbook))->get_changes(listptr,newdbs);
    }

extern "C"
int kaddrbook_modify_list(void *pkaddrbook, GList *changes, GList **resultsptr) 
    {
    //printf("kdepim_plugin: %s(%p,%p,%p)\n", __FUNCTION__, pkaddrbook, changes, resultsptr);
    return ((kaddrbook*)(pkaddrbook))->modify_list(changes,resultsptr);
    }

extern "C"
void kaddrbook_sync_done(void *pkaddrbook, int success) 
    {
    //printf("kdepim_plugin: %s(%p,%d)\n", __FUNCTION__, pkaddrbook, success);
    ((kaddrbook*)(pkaddrbook))->sync_done(success);
    }


static KApplication *applicationptr=NULL;

extern "C" 
void kaddrbook_init(int argc, char **argv)
    {
    //printf("kdepim_plugin: %s(%d,%p)\n", __FUNCTION__, argc, argv);
    applicationptr = new KApplication(argc,argv,argv[0]);
    }

extern "C" 
void kaddrbook_quit(void)
    {
    //printf("kdepim_plugin: %s()\n", __FUNCTION__);
    if (applicationptr!=NULL)
        delete applicationptr;
    }

