<?php

class iCloudSync
{

    const MAX_NUMBER_OF_ATTEMPTS = 3;

    public $limit;
    public $offset;
    public $totalItemCount;
    public $ThirdPartyCredential;
    public $downloadList;
    public $uploadList;
    public $deletedICloudContacts;
    public $deletedLocalContacts;
    public $iCloudContactsList;

    public function __construct($meta, $model)
    {
        $this->limit = $meta->limit;
        $this->offset = $meta->offset;
        $this->totalItemCount = $meta->totalItemCount;
        $this->ThirdPartyCredential = $model;
    }

    public function synchronize($contactsList)
    {
        $this->iCloudContactsList = $contactsList;

        if (!empty($this->iCloudContactsList->iCloudType)) {
            Log::d("Synchronize iCloud Data $this->offset of $this->totalItemCount.", 'iCloudSync [iCloudSync]');

            $this->setICloudClient();
            $contactListItems = array('downloadList', 'uploadList', 'deletedICloudContacts', 'deletedLocalContacts');
            foreach ($contactListItems as $list) {
                if (!empty($this->iCloudContactsList->$list)) {
                    $this->filterICloudContactsList($list);
                    Log::d("Finished processing $list.", 'iCloudSync [iCloudSync]');
                }
            }
            $this->updateUploadList();
            $this->updateModel($this->iCloudContactsList);
        } else {
            Log::e("Invalid iCloud Type", "iCloudSync [iCloudSync]");
            Log::e("Could not execute iCloudContactsList.", "iCloudSync [iCloudSync]");
        }
        return $this->offset + $this->limit;
    }

    private function setICloudClient()
    {
        $client = new iCloudClient($this->ThirdPartyCredential->username, $this->ThirdPartyCredential->password);
        $this->iCloudClient = $client;
    }

    private function filterICloudContactsList($case)
    {
        if ($case == 'downloadList') {
            $this->importICloudContacts();
        } elseif ($case == 'uploadList') {
            if ($this->iCloudContactsList->iCloudType == iCloudTypes::contacts) {
                $this->exportFiles('.vcf');
            } elseif ($this->iCloudContactsList->iCloudType == iCloudTypes::calendars) {
                $this->exportFiles('.ics');
            }
        } elseif ($case == 'deletedICloudContacts') {
            $this->deleteLocalEntries();
        } elseif ($case == 'deletedLocalContacts') {
            $this->deleteContactsOnICloud();
        }
    }

    private function importICloudContacts()
    {
        $list = $this->extractItemsFromList('downloadList');
        $files = $this->iCloudClient->multigetRequest($this->iCloudContactsList->iCloudDirectoryPath, $list, $this->iCloudContactsList->directoryBasePath);

        if (!($files)) {
            Log::e('No files found!', "importICloudContacts [iCloudSync]");
            return;
        }

        foreach ($files->response as $file) {
            $fileName = (string) str_replace($this->iCloudContactsList->directoryBasePath, '', $file->href);
            if ($file->status != 'HTTP/1.1 404 Not Found') {
                if (strpos($fileName, '.ics') !== false || strpos($fileName, '.ics') == 0) {
                    $key = "calendar-data";
                } else {
                    $key = "address-data";
                }
                $this->checkFile($fileName, (string) $file->propstat->prop->$key);
            } else {
                $this->writeErrorLog('addressbook-multiget', $file->status, 'Not Found', $this->iCloudContactsList->iCloudDirectoryPath . $fileName);
            }
        }
    }

    private function checkFile($fileName, $file)
    {
        if ($file == 'Not Found' || $file == '') {
            $file = $this->sendRequest($fileName, "GET");
        }
        if ($file) {
            if (strpos($fileName, '.ics') == false || strpos($fileName, '.ics') == 0) {
                $this->initializeImport($fileName, $file, 'vCard');
            } else {
                $this->initializeImport($fileName, $file, 'iCal');
            }
        }
    }

    private function initializeImport($fileName, $file, $type)
    {
        if ($type == 'iCal') {
            $converter = new iCalConverter();
            if ($converter->convertICalToEvent($file)) {
                $this->importFile($fileName, $type, $converter);
            }
        } else {
            $converter = new vCardConverter();
            if ($converter->convertVCardToAddresses($file)) {
                $this->importFile($fileName, $type, $converter);
            }
        }
    }

    private function importFile($fileName, $type, $converter)
    {
        $iCloudDataModel = $this->getICloudDataModel('fileName', $fileName, $type);

        if ($type == 'iCal') {
            $file = empty($iCloudDataModel->id) ? new CalendarEvent('iCloud') : $this->getCalendarEventModel($iCloudDataModel->fileId);
            $this->updateCalendarEvent($file, $converter);
            $path = $this->iCloudContactsList->iCloudDirectoryPath;
            $eTag = (string) str_replace('"', '', $this->iCloudClient->getETagRequest($path . $fileName, $path, array($fileName), "Content-Type: urn:ietf:params:xml:ns:caldav'"));
        } else {
            $file = empty($iCloudDataModel->id) ? new Addresses('Import') : $this->getAddressesModel($iCloudDataModel->fileId);
            $this->updateAddressbookEntry($file, $converter);
            $eTag = (string) str_replace('"', '', $this->iCloudClient->getETagRequest($this->ThirdPartyCredential->addressBookURI . $fileName));
        }

        $data = $this->createDataArray($fileName, $file, $converter->Data, $eTag);
        $this->updateICloudDataModel($iCloudDataModel, $data);
    }

    private function getICloudDataModel($key, $iCloudName, $type)
    {
        if ($type == 'iCal' || $type == '.ics') {
            $iCloudDataModel = iCloudData::model()->findByAttributes(array($key => $iCloudName));
        } else {
            $iCloudDataModel = iCloudData::model()->findByAttributes(
                    array(
                        $key => $iCloudName,
                        'directoryId' => $this->iCloudContactsList->plDirectoryId
                    )
            );
        }

        if (!isset($iCloudDataModel)) {
            $iCloudDataModel = new iCloudData();
        }

        return $iCloudDataModel;
    }

    private function getCalendarEventModel($id)
    {
        $event = CalendarEvent::model()->findByPk($id);

        if (!isset($event)) {
            $event = new CalendarEvent();
        }

        return $event;
    }

    private function updateCalendarEvent($event, $converter)
    {
        $event->attributes = $converter->attributes;
        $event->calendar = $this->iCloudContactsList->plDirectoryId;
        $event->owner = $this->ThirdPartyCredential->owner;
        $event->scenario = 'iCloud';
        $event->type = $this->getEventType($event);
        $this->updateModel($event);
    }

    private function getEventType($calendarEvent)
    {
        if (empty($calendarEvent->type)) {
            $typeName = 'iCloud Sync';

            $type = EventType::model()->findByAttributes(array('name' => $typeName));

            if (isset($type)) {
                return $type->id;
            }
            return $this->getNewEventType($typeName);
        }
        return $calendarEvent->type;
    }

    private function getNewEventType($typeName)
    {
        $eventType = new EventType();
        $eventType->name = $typeName;

        if ($eventType->save()) {
            return $eventType->id;
        }
    }

    private function getAddressesModel($id)
    {
        $address = Addresses::model()->findByPk($id);

        if (!isset($address)) {
            $address = new Addresses();
        }

        return $address;
    }

    private function updateAddressbookEntry($address, $converter)
    {
        $address->attributes = $converter->attributes;
        $address->contact_tid = $this->ThirdPartyCredential->addressBookId;
        $address->contact_owner = $this->ThirdPartyCredential->owner;

        if (isset($converter->photo_url)) {
            $photo = $this->iCloudClient->executeRequest($converter->photo_url, "GET", 'card');

            if (substr($photo['httpCode'], 0, 1) == 2) {
                $address = $converter->downloadPhoto($address, $photo, $this->ThirdPartyCredential->id);
            } else {
                $this->writeErrorLog("PHOTO-GET", $photo['httpCode'], $photo['content'], $converter->photo_url);
                $address->contact_jpegphoto = '0';
            }
        } else {
            $address->contact_jpegphoto = '0';
        }
        $address->scenario = 'Import';
        $this->updateModel($address);
    }

    private function exportFiles($type)
    {
        $fileIDs = $this->extractItemsFromList('uploadList');
        foreach ($fileIDs as $id) {
            if ($type == '.ics') {
                $localFile = CalendarEvent::model()->findByPk($id);
            } else {
                $localFile = Addresses::model()->findByPk($id);
            }

            if ($localFile) {
                $this->createUploadFile($localFile, $type);
            } else {
                Log::e("AddressID $id used for iCloud PUT Request was not found.", 'iCloudSync [iCloudSync]');
            }
        }
    }

    private function createUploadFile($file, $fileExtension)
    {
        $fileDataModel = $this->getICloudDataModel('fileId', $file->id, $fileExtension);
        $fileName = empty($fileDataModel->fileName) ? $file->id . $fileExtension : $fileDataModel->fileName;

        if ($fileExtension == '.vcf') {
            $converter = new vCardConverter();
            $uploadFile = $converter->convertAddressesToVCard($file, $fileDataModel);
        } else {
            $converter = new iCalConverter();
            $uploadFile = $converter->convertEventToICal($file, $fileDataModel);
        }

        if (!$uploadFile) {
            Log::e("An Error occured while creating an Upload File", '[iCloudSync]');
            Log::e("Filename: $fileName", '[iCloudSync]');
            return;
        }

        if ($this->sendRequest($fileName, "PUT", $uploadFile)) {
            if ($fileExtension == '.vcf') {
                $eTag = (string) str_replace('"', '', $this->iCloudClient->getETagRequest($this->ThirdPartyCredential->addressBookURI . $fileName));
                $data = $this->createDataArray($fileName, $file, $converter->vCard, $eTag);
            } else {
                $path = $this->iCloudContactsList->iCloudDirectoryPath;
                $eTag = (string) str_replace('"', '', $this->iCloudClient->getETagRequest($path . $fileName, $path, array($fileName), "Content-Type: urn:ietf:params:xml:ns:caldav'"));
                $data = $this->createDataArray($fileName, $file, $converter->Data, $eTag);
            }

            if ($data) {
                $this->updateICloudDataModel($fileDataModel, $data);
                $this->uploadList[$fileName] = $eTag;
            }
        }
    }

    private function createDataArray($fileName, $file, $fileData, $eTag)
    {
        if (isset($file->contact_tid)) {
            $directoryId = $file->contact_tid;
            $provider = 'iCloud';
        } elseif (isset($file->calendar)) {
            $directoryId = $file->calendar;
            $provider = 'iCalendar';
        } else {
            Log::e("Could not find directory", "[iCloudSync]");
            Log::e("Can't create Data Array", "[iCloudSync]");
            Log::e("Filename: $fileName", "[iCloudSync]");
            return;
        }

        return array(
            'owner' => $this->iCloudContactsList->owner,
            'listId' => $this->iCloudContactsList->id,
            'fileName' => $fileName,
            'fileId' => $file->id,
            'directoryId' => $directoryId,
            'data' => $fileData,
            'eTag' => $eTag,
            'provider' => $provider,
        );
    }

    private function updateICloudDataModel($iCloudDataModel, $data)
    {
        if (!isset($iCloudDataModel)) {
            $iCloudDataModel = new iCloudData();
        }

        foreach ($data as $key => $value) {
            if ($key == 'data') {
                $iCloudDataModel->$key = json_encode($value);
            } elseif ($key == 'listId') {
                $iCloudDataModel->$key = $this->manageMultipleAccounts($iCloudDataModel->$key, $value);
            } else {
                $iCloudDataModel->$key = $value;
            }
        }
        $this->updateModel($iCloudDataModel);
    }

    private function manageMultipleAccounts($iCloudContactsLists, $iCloudContactsListID)
    {
        if ($iCloudContactsLists) {
            $contactLists = json_decode($iCloudContactsLists, true);
            if (!in_array($iCloudContactsListID, $contactLists)) {
                $contactLists[] = $iCloudContactsListID;
            }
        } else {
            return json_encode(array($iCloudContactsListID));
        }

        return json_encode($contactLists);
    }

    private function deleteContactsOnICloud()
    {
        $list = $this->extractItemsFromList('deletedLocalContacts');
        foreach ($list as $contact) {
            $this->sendRequest($contact, "DELETE");
        }
    }

    private function sendRequest($vCardName, $requestType, $vCard = null)
    {
        $uri = $this->iCloudContactsList->iCloudDirectoryPath . $vCardName;
        for ($i = 1; $i <= iCloudSync::MAX_NUMBER_OF_ATTEMPTS; $i++) {
            $response = $this->iCloudClient->executeRequest($uri, $requestType, "Content-Type: urn:ietf:params:xml:ns:caldav'", $vCard);
            if (strpos($response['content'], '.ics with same UID in a different collection') !== false || strpos($response['content'], '.ics with same UID in a different collection') == 0) {
                if ($i == iCloudSync::MAX_NUMBER_OF_ATTEMPTS) {
                    Log::e("Calendar Event is already in an other iCloud Calendar");
                    return false;
                }
            }
            if (substr($response['httpCode'], 0, 1) != 2 && $i == iCloudSync::MAX_NUMBER_OF_ATTEMPTS) {
                $this->writeErrorLog($requestType, $response['httpCode'], $response['content'], $uri);
                return false;
            } elseif (substr($response['httpCode'], 0, 1) == 2) {
                return $requestType == "PUT" ? true : $response['content'];
            }
        }
    }

    private function deleteLocalEntries()
    {
        $list = $this->extractItemsFromList('deletedICloudContacts');
        foreach ($list as $contact) {
            if ($this->iCloudContactsList->iCloudType == 'calendars') {
                $iCloudData = iCloudData::model()->findByAttributes(array('fileName' => $contact, 'directoryId' => $this->iCloudContactsList->plDirectoryId));
            } else {
                $iCloudData = iCloudData::model()->findByAttributes(array('fileName' => $contact));
            }
            if ($iCloudData) {
                if (!$iCloudData->delete()) {
                    Log::e("An error has occurred deleting vCard-ID: $iCloudData->id", 'iCloudSync [iCloudSync]');
                }
            }
        }
    }

    private function extractItemsFromList($case)
    {
        $list = json_decode($this->iCloudContactsList->$case, true);

        if (!is_array($list)) {
            $list = array($list);
        }

        $itemsToProcess = array_slice($list, 0, $this->limit);

        for ($i = 0; $i < $this->limit; $i++) {
            unset($list[$i]);
        }

        $this->iCloudContactsList->$case = empty(array_values($list)) ? null : json_encode(array_values($list));

        return $itemsToProcess;
    }

    private function updateUploadList()
    {
        if ($this->uploadList) {
            $uploadList = json_decode($this->iCloudContactsList->lastUploadList, true);
            foreach ($this->uploadList as $key => $value) {
                $uploadList[$key] = $value;
            }
            $this->iCloudContactsList->lastUploadList = json_encode($uploadList);
        }
    }

    private function updateModel($model)
    {
        $model->save(false);
    }

    private function writeErrorLog($case, $httpCode, $message, $uri)
    {
        Log::e("An error has occurred sending $case Curl", 'iCloudSync [iCloudSync]');
        Log::e("HTTP-Code: $httpCode", 'iCloudSync [iCloudSync]');
        Log::e("Error Message: $message", 'iCloudSync [iCloudSync]');
        Log::e("For CURL used URL  $uri", 'iCloudSync [iCloudSync]');
    }
}
