<?php

class iCloudContactListUpdater
{

    private $timestampLastSync;
    private $downloadList;
    private $deletedICloudContacts;
    private $lastUploadList;
    private $currentICloudImage;
    private $lastICloudImage;
    private $iCloudContactsList;
    private $uploadList;

    public function __construct($contactsList, $id)
    {
        if (empty($contactsList)) {
            $contactsList = $this->initializeNewContactLists($id);
        }
        $this->iCloudContactsList = $contactsList;
        $this->uploadList = json_decode($this->iCloudContactsList->uploadList, true);
        $this->timestampLastSync = PhonalisaDate::newServerTimestamp();
    }

    private function initializeNewContactLists($id)
    {
        $model = ThirdPartyCredential::model()->findByPk($id);
        if (!$model) {
            throw new CHttpException(404, "Can not find ThirdPartyCredential model ID: $id");
        }
        return $this->createContactList($model);
    }

    private function createContactList($model, $iCloudCalendarId = null, $calendarId = null)
    {
        $contactsList = new iCloudContactsList();
        $contactsList->owner = $model->owner;
        $contactsList->thirdPartyCredentialId = $model->id;

        if (!empty($model->addressBookId)) {
            $contactsList->plDirectoryId = $model->addressBookId;
            $contactsList->directoryBasePath = $model->addressBookURI;
            $contactsList->iCloudType = iCloudTypes::contacts;
        } elseif (!empty($iCloudCalendarId) && !empty($calendarId)) {
            $contactsList->plDirectoryId = $calendarId;
            $contactsList->directoryBasePath = $iCloudCalendarId;
            $contactsList->iCloudType = iCloudTypes::calendars;
        }

        if ($contactsList->save()) {
            return $contactsList;
        }
    }

    public function updateICloudContactsList($iCloudDirectory)
    {
        Log::d("...updating iCloudContactsList-ID :" . $this->iCloudContactsList->id, 'iCloudSync [ContactListUpdater]');
        $this->readICloudContactsListData();
        $this->checkForChanges($iCloudDirectory);
        if (empty($this->downloadList) && empty($this->deletedICloudContacts) && empty($this->uploadList)) {
            Log::d(get_class($this->iCloudContactsList) . 'is already up to date.', 'iCloudSync [ContactListUpdater]');
            return null;
        }

        $this->updateLists();
        $this->updateModel($this->iCloudContactsList);

        return $this->iCloudContactsList;
    }

    private function readICloudContactsListData()
    {
        $contactListKeys = $this->returnContactListKeys();
        foreach ($contactListKeys as $key) {
            if ($this->iCloudContactsList->$key) {
                $this->$key = json_decode($this->iCloudContactsList->$key, true);
            } else {
                $this->$key = null;
            }
        }
    }

    private function returnContactListKeys()
    {
        return array(
            'lastICloudImage',
            'currentICloudImage',
            'lastUploadList',
            'downloadList',
            'deletedICloudContacts',
        );
    }

    private function checkForChanges($iCloudDirectory)
    {
        $newICloudCtag = null;
        $i = 0;

        foreach ($iCloudDirectory->response as $response) {
             if (@$response->propstat->prop->getctag != null) {
                $newICloudCtag = (string) $response->propstat->prop->getctag;
                break;
            } elseif (!empty($response->propstat->prop->invite) && !empty($response->propstat->prop->getlastmodified)) {
                $newICloudCtag = $this->getNewICloudCTag();
                break;
            }
            $i++;
        }

        if (!empty($newICloudCtag) && strcmp($this->iCloudContactsList->cTag, $newICloudCtag) != 0) {
            $this->iCloudContactsList->cTag = $newICloudCtag;
            $this->filterICloudDirectory($iCloudDirectory, $i);
        } else {
            $this->lastICloudImage = $this->currentICloudImage;
        }
    }

    private function getNewICloudCTag()
    {
        $thirdParty = ThirdPartyCredential::model()->findByPk($this->iCloudContactsList->thirdPartyCredentialId);
        $client = new iCloudClient($thirdParty->username, $thirdParty->password);
        $newResponse = $client->propfindRequest($this->iCloudContactsList->iCloudDirectoryPath, null, "0");

        if (!empty((string) $newResponse->response->propstat->prop->getctag)) {
            return (string) $newResponse->response->propstat->prop->getctag;
        } else {
            Log::e("Could not get cTag from iCloud", "iCloudContactListUpdater");
            return null;
        }
    }

    private function filterICloudDirectory($iCloudDirectory, $i)
    {
        if (empty($this->iCloudContactsList->directoryBasePath)) {
            $this->iCloudContactsList->directoryBasePath = (string) $iCloudDirectory->response[$i]->href;
        }
        unset($iCloudDirectory->response[$i]);

        $this->updateICloudContactsListData($iCloudDirectory);
    }

    private function updateICloudContactsListData($iCloudDirectory)
    {
        $this->lastICloudImage = $this->currentICloudImage;
        if (empty($iCloudDirectory)) {
            $this->emptyICloudContactsLists();
            Log::d('iCloud address book is empty. Adding all contacts to the list of deleted iCloud contacts', 'iCloudSync [ContactListUpdater]');
        } else {
            $this->readICloudAddressBookData($iCloudDirectory);
            $this->filterChangedAndDeletedContacts();
        }
    }

    private function emptyICloudContactsLists()
    {
        $this->currentICloudImage = null;
        if ($this->lastICloudImage) {
            $this->deletedICloudContacts = $this->getNames($this->lastICloudImage);
        }
    }

    private function readICloudAddressBookData($iCloudDirectory)
    {
        $iCloudFiles = array();
        foreach ($iCloudDirectory as $files) {
            $iCloudFiles[] = array(
                'fileName' => (string) str_replace($this->iCloudContactsList->directoryBasePath, '', $files->href),
                'eTag' => (string) str_replace('"', '', $files->propstat->prop->getetag),
                'created' => $this->returnTimestamp((string) $files->propstat->prop->creationdate),
                'changed' => $this->returnTimestamp((string) $files->propstat->prop->getlastmodified)
            );
        }

        $this->currentICloudImage = $iCloudFiles;
    }

    private function filterChangedAndDeletedContacts()
    {
        $this->createDownloadList();
        if (is_array($this->currentICloudImage)) {
            if (is_array($this->lastICloudImage)) {
                $this->deletedICloudContacts = $this->getDeletedICloudContacts();
            }
            if (is_array($this->lastUploadList)) {
                $deletedContacts = array_diff(array_keys($this->lastUploadList), $this->getNames($this->currentICloudImage));
                if (is_array($this->deletedICloudContacts)) {
                    $this->deletedICloudContacts = array_merge($this->deletedICloudContacts, $deletedContacts);
                } else {
                    $this->deletedICloudContacts = $deletedContacts;
                }
            }
        }
        Log::d('Created list of deleted iCloud contacts.', 'iCloudSync [ContactListUpdater]');
    }

    private function createDownloadList()
    {
        if (empty($this->lastICloudImage)) {
            $downloadList = $this->getNames($this->currentICloudImage);
        } else {
            $downloadList = $this->compareLists();
        }
        $this->checkForAlreadyUploadedFiles($downloadList);
    }

    private function compareLists()
    {
        $downloadList = array();
        $oldList = $this->getNames($this->lastICloudImage);
        $newList = $this->getNames($this->currentICloudImage);
        $differences = array_values(array_diff($newList, $oldList));

        foreach ($this->lastICloudImage as $oldListContactData) {
            foreach ($this->currentICloudImage as $newListContactData) {
                if ($this->filesChanged($oldListContactData, $newListContactData)) {
                    $downloadList[] = $newListContactData['fileName'];
                }
            }
        }

        return array_merge($differences, $downloadList);
    }

    private function checkForAlreadyUploadedFiles($downloadList)
    {
        if (is_array($this->lastUploadList)) {
            $downloadList = $this->checkIfFileChangedSinceUpload($downloadList);
        }
        $this->downloadList = $downloadList;

        Log::d('Created download list.', 'iCloudSync [ContactListUpdater]');
    }

    private function checkIfFileChangedSinceUpload($downloadList)
    {
        foreach ($downloadList as $key => $fileName) {
            if (array_key_exists($fileName, $this->lastUploadList)) {
                $downloadList = $this->compareETags($downloadList, $key, $fileName);
            }
        }

        return array_values($downloadList);
    }

    private function compareETags($downloadList, $key, $fileName)
    {
        foreach ($this->currentICloudImage as $value) {
            if (($value['fileName'] == $fileName) && ($value['eTag'] == $this->lastUploadList[$fileName])) {
                unset($downloadList[$key]);
            }
        }

        return $downloadList;
    }

    private function filesChanged($oldContact, $newContact)
    {
        if ($oldContact['fileName'] == $newContact['fileName']) {
            if (strcmp($oldContact['eTag'], $newContact['eTag']) != 0) {
                return true;
            }
        }
        return false;
    }

    private function getDeletedICloudContacts()
    {
        $lastICloudImage = $this->getNames($this->lastICloudImage);
        $currentICloudImage = $this->getNames($this->currentICloudImage);
        $deletedItems = array_diff($lastICloudImage, $currentICloudImage);

        return array_values($deletedItems);
    }

    private function getNames($iCloudAddressBookImage)
    {
        $list = array();
        if (is_array($iCloudAddressBookImage)) {
            foreach ($iCloudAddressBookImage as $contactData) {
                $list[] = $contactData['fileName'];
            }
        }
        return $list;
    }

    public function returnTimestamp($date)
    {
        $serverTimeZone = new DateTimeZone('GMT');
        $timestamp = new DateTime($date, $serverTimeZone);

        return $timestamp->format('U');
    }

    private function updateLists()
    {
        $keys = $this->returnContactListKeys();
        foreach ($keys as $key) {
            if ($this->$key && ($key != 'lastUploadList')) {
                $this->iCloudContactsList->$key = json_encode($this->$key);
            } else {
                $this->iCloudContactsList->$key = null;
            }
        }
        $this->iCloudContactsList->timestampLastSync = $this->timestampLastSync;
    }

    private function updateModel($model)
    {
        if (!$model->save()) {
            $this->createErrorLogMessages($model);
            throw new CHttpException('500', "iCloudContactsList-ID  $model->id  model->save() failed. [ContactListUpdater]");
        }
        Log::d("Finished update of iCloudContactsList-ID: $model->id", 'iCloudSync [ContactListUpdater]');
    }

    private function createErrorLogMessages($model)
    {
        Log::e("An error has occurred saving " . get_class($model) . "-ID $model->id", 'iCloudSync [iCloudContactsList]');
        foreach ($model->errors as $key => $errorMsg) {
            Log::e("Error in $key ");
            if (is_array($errorMsg)) {
                Log::e("ErrorMessage in " . array_shift($errorMsg));
            }
        }
    }
}
