<?php

class iCloudClient
{

    public $depth = "1";

    public function __construct($username, $password)
    {
        $this->credentials["username"] = $username;
        $this->credentials["password"] = hex2bin(strrev($password));
    }

    public function getPrincipalsData($serverPathExtension, $directoryPathExtension)
    {
        $serverPath = "https://p" . str_pad(mt_rand(1, 24), 2, '0', STR_PAD_LEFT) . '-' . $serverPathExtension . '.icloud.com';

        if ($serverPathExtension == 'contacts') {
            $davName = 'card';
        } elseif ($serverPathExtension == 'caldav') {
            $this->depth = "0";
            $davName = 'cal';
        } else {
            return ErrorCode::iCloudInvalidUserIdOrPassword;
        }

        $principalsPath = $this->getDirectoryPath($serverPath, 'A:', 'current-user-principal', $davName);

        if ($principalsPath) {
            $directoryPath = $this->getDirectoryPath($serverPath . $principalsPath, 'C:', $directoryPathExtension . '-home-set', $davName);

            if ($directoryPath && $davName == 'card') {
                return (string) $directoryPath . 'card/';
            } elseif ($directoryPath && $davName == 'cal') {
                return (string) $directoryPath;
            }
        }

        return ErrorCode::iCloudInvalidUserIdOrPassword;
    }

    private function getDirectoryPath($serverPath, $prefix, $sufix, $davName)
    {
        $xml = $this->createXMLBodyForCurl($prefix . $sufix, $davName);
        $response = $this->propfindRequest($serverPath, $xml);

        if (!$response) {
            return null;
        }
        $uri = (string) $response->response->propstat->prop->$sufix->href;

        return $uri;
    }

    public function updateICloudDirectory($iCloudContactsLists)
    {
        Log::v("Starting iCloud-Sync", "iCloudSync");
        Log::d(" of ThirdPartyCredential-ID :  $iCloudContactsLists->id", "iCloudSync [iCloudClient]");
        $iCloudDirectory = $this->propfindRequest($iCloudContactsLists->iCloudDirectoryPath);

        if (!$iCloudDirectory) {
            $iCloudDirectory = ErrorCode::iCloudInvalidUserIdOrPassword;
        }

        return $iCloudDirectory;
    }

    public function propfindRequest($serverPath, $xml = null, $depth = null)
    {
        $options = $this->buildCurlOptions($serverPath, "PROPFIND", $xml, null, $depth);
        $curl = $this->sendCurlRequest($options);
        if (substr($curl['httpCode'], 0, 1) != 2) {
            return null;
        }

        $xmlObject = simplexml_load_string($curl['content']);

        return $xmlObject;
    }

    public function multigetRequest($directoryPath, $vCardList, $basePath)
    {
        if (strpos($basePath, 'card') === false) {
            $xml = $this->createXMLBodyForCurl("C:calendar-data", 'cal', 'calendar', $vCardList, $basePath);
        } else {
            $xml = $this->createXMLBodyForCurl("C:address-data", 'card', 'addressbook', $vCardList, $basePath);
        }

        $options = $this->buildCurlOptions($directoryPath, "REPORT", $xml);
        $curl = $this->sendCurlRequest($options);

        $xmlObject = simplexml_load_string($curl['content']);

        return $xmlObject;
    }

    public function getETagRequest($filePath, $basepath = null, $iCalList = null, $contentType = null)
    {
        if (strpos($filePath, 'card') === false || strpos($filePath, 'card') == 0) {
            $xml = $this->createXMLBodyForCurl("A:getetag", 'cal', 'calendar', $iCalList, $basepath);
        } else {
            $xml = $this->createXMLBodyForCurl("A:getetag", 'card', 'addressbook');
        }

        if (isset($contentType) && isset($basepath)) {
            $options = $this->buildCurlOptions($filePath, "REPORT", $xml, $contentType);
        } else {
            $options = $this->buildCurlOptions($filePath, "REPORT", $xml);
        }
        $curl = $this->sendCurlRequest($options);

        if (substr($curl['httpCode'], 0, 1) != 2) {
            Log::e('httpCode ' . $curl['httpCode'], 'iCloudSync [iCloudClient]');
            Log::e('REPORT method of "getetag" returned "' . $curl['content'] . '"', 'iCloudSync [iCloudClient]');
            Log::e('vCard Path on Server: ' . $filePath, 'iCloudSync [iCloudClient]');
            return null;
        }
        $xmlObject = simplexml_load_string($curl['content']);

        return (string) $xmlObject->response->propstat->prop->getetag;
    }

    private function createXMLBodyForCurl($attributes, $davName, $directoryName = null, $list = null, $basePath = null)
    {
        $xml = new DOMDocument("1.0", "UTF-8");
        if ($list && $directoryName) {
            $propfind = $xml->createElement('C:' . $directoryName . '-multiget');
        } elseif ($attributes == "A:getetag" && $directoryName) {
            $propfind = $xml->createElement('C:' . $directoryName . '-query');
        } else {
            $propfind = $xml->createElement("A:propfind");
        }

        $propfind->setAttribute("xmlns:A", "DAV:");
        if ($davName == 'cal') {
            $propfind->setAttribute("xmlns:C", "urn:ietf:params:xml:ns:caldav");
        } elseif ($davName == 'card') {
            $propfind->setAttribute("xmlns:C", "urn:ietf:params:xml:ns:carddav");
        }

        $prop = $xml->createElement("A:prop");
        $requestMethod = $xml->createElement($attributes);

        $prop->appendChild($requestMethod);
        $propfind->appendChild($prop);

        if (is_array($list)) {
            foreach ($list as $name) {
                $href = $xml->createElement("A:href");
                $href->nodeValue = $basePath . $name;
                $propfind->appendChild($href);
            }
        }
        $xml->appendChild($propfind);

        $xml->normalizeDocument();

        $xml->formatOutput = true;

        return $xml->saveXML();
    }

    public function executeRequest($uri, $request, $contentType, $content = null)
    {
        $options = $this->buildCurlOptions($uri, $request, $content, $contentType);
        $result = $this->sendCurlRequest($options);

        return $result;
    }

    private function buildCurlOptions($uri, $request, $post = null, $contentType = null, $depth = null)
    {
        if (empty($contentType)) {
            $contentType = "Content-Type: text/xml; charset='UTF-8'";
        }
        if ($depth === null) {
            $depth = $this->depth;
        }

        $options = array(
            CURLOPT_URL => $uri,
            CURLOPT_HTTPHEADER => array("Depth: $depth",
                $contentType,
                "User-Agent: DAVKit/4.0.1 (730); CalendarStore/4.0.1 (973); iCal/4.0.1 (1374); Mac OS X/10.6.2 (10C540)"
            ),
            CURLOPT_HTTPAUTH => CURLAUTH_BASIC,
            CURLOPT_USERPWD => $this->credentials['username'] . ":" . $this->credentials['password'],
            CURLOPT_CUSTOMREQUEST => $request,
            CURLOPT_RETURNTRANSFER => 1
        );

        if (os() == 'Windows') {
            $options[CURLOPT_CAINFO] = Yii::app()->basePath . '/config/certs/icloud.pem';
        }
        if (isset($post)) {
            $options [CURLOPT_POST] = 1;
            $options [CURLOPT_POSTFIELDS] = $post;
        }

        return $options;
    }

    private function sendCurlRequest($options)
    {
        $curl = curl_init();
        curl_setopt_array($curl, $options);
        $response = array(
            'content' => curl_exec($curl),
            'err' => curl_errno($curl),
            'errmsg' => curl_error($curl),
            'header' => curl_getinfo($curl),
            'httpCode' => curl_getinfo($curl, CURLINFO_HTTP_CODE),
        );
        if (substr($response['httpCode'], 0, 1) != 2) {
            Log::e("Error Code: " . curl_errno($curl), "iCloudCurl");
            Log::e("Error Message: " . curl_error($curl), "iCloudCurl");
        }
        curl_close($curl);

        return $response;
    }

    public function getICloudCalendars($directoryPath)
    {
        $xml = $this->createXMLBodyForCurl("A:displayname", 'cal', 'calendar');
        $response = $this->propfindRequest($directoryPath, $xml);

        if (is_object($response)) {
            $response = get_object_vars($response);
            if (!empty($response['response'])) {
                $calendars = $response['response'];
            } else {
                return false;
            }
        }

        return $this->getAvailableCalendar($calendars, $directoryPath);
    }

    private function getAvailableCalendar($calendars, $directoryPath)
    {
        $availableCalendars = array();

        foreach ($calendars as $calendar) {
            if (is_object($calendar)) {
                $calendar = get_object_vars($calendar);
            }

            if (!empty($calendar['href']) && !empty($calendar['propstat'])) {
                $url = $calendar['href'];
                $propstat = get_object_vars($calendar['propstat']);
                $calendarName = $this->getCalendarName($propstat);
                $calendarID = $this->getCalendarID($url);
            }

            if (!empty($calendarName) && !empty($calendarID)) {
                $availableCalendars[$directoryPath . $calendarID] = $calendarName;
            }
        }

        return $availableCalendars;
    }

    private function getCalendarName($propstat)
    {
        if (!empty($propstat['status']) && !empty($propstat['prop'])) {
            if (is_object($propstat['prop']) && strpos($propstat['status'], 'OK') !== false) {
                $prop = get_object_vars($propstat['prop']);
            }
        }

        if (!empty($prop) && !empty($prop['displayname'])) {
            return $prop['displayname'];
        }
    }

    private function getCalendarID($url)
    {
        if (isset($url)) {
            $trim = explode('/calendars/', $url);

            if (!empty($trim[1]) && $trim[1] != 'notification/' && $trim[1] != 'tasks/' && $trim[1] != 'inbox/') {
                return $trim[1];
            }
        }
    }
}
