<?php

class DefaultController extends Controller
{

    /**
     * @var string the default layout for the views. Defaults to '//layouts/column2', meaning
     * using two-column layout. See 'protected/views/layouts/column2.php'.
     */
    public $layout = '//layouts/column2';

    /**
     * @return array action filters
     */
    public function filters()
    {
        return array(
            'accessControl', // perform access control for CRUD operations
        );
    }

    /**
     * Specifies the access control rules.
     * This method is used by the 'accessControl' filter.
     * @return array access control rules
     */
    public function accessRules()
    {
        return array(
            array('allow', // allow all users to perform 'index' and 'view' actions
                'actions' => array(
                    'index',
                    'confirmCredential',
                    'socialLogin',
                    'startImport',
                    'startSingleAccountImport',
                    'create',
                    'update',
                    'delete',
                    'ValidateICloudCredentials',
                    'ajaxRenderModal',
                    'GetICloudCalendars',
                    'renderTable',
                    'CreateNewCalendar',
                    'DeleteNewCalendar',
                    'SaveCalendars',
                    'ajaxPassword',
                    'GetSaveErrorMessage'
                ),
                'expression' => AccessManager::checkSessionPermission($this->module->name),
            ),
            array("allow",
                "actions" => array(
                    'importAddresses',
                    'startImport',
                    'ValidateICloudCredentials',
                    'ajaxRenderModal',
                    'GetICloudCalendars',
                    'renderTable',
                    'CreateNewCalendar',
                    'DeleteNewCalendar',
                    'SaveCalendars',
                    'ajaxPassword',
                    'GetSaveErrorMessage'
                ),
                'users' => array('*'),
            ),
            array('deny', // deny all users
                'users' => array('*'),
            ),
        );
    }

    /**
     * Lists all models.
     */
    public function actionIndex()
    {
        $model = ThirdPartyCredential::model();
        $this->deleteNotAuthenticatedModels();
        $this->removeXingDataFromSession();
        if (empty($this->getUserInstance()->getAddressbooks('write'))) {
            $this->render('requireAddressBookPermisions');
        } else {
            $this->render(
                    'index', array(
                'model' => $model
                    )
            );
        }
    }

    //search for all models from the owner and delete them except or the one with given id
    public function deleteNotAuthenticatedModels()
    {
        $userID = app()->user->sudoUserInstance->id;
        $models = ThirdPartyCredential::model()->findAllByAttributes(
                array(
                    'owner' => $userID,
                    'authenticated' => ThirdPartyCredentialStatus::notAuthenticated
                )
        );
        if ($models) {
            foreach ($models as $model) {
                $model->delete();
            }
        }
    }

    private function removeXingDataFromSession()
    {
        //prevent Error caused by old session data
        if (isset($_SESSION['HA::STORE'])) {
            unset($_SESSION['HA::STORE']);
        }
        if (isset($_SESSION['HA::CONFIG'])) {
            unset($_SESSION['HA::CONFIG']);
        }
    }

    public function actionCreate()
    {
        try {
            $model = $this->createProviderSpecificModel();

            $this->performAjaxValidation($model);

            if (isset($_POST['ThirdPartyCredential'])) {
                $model->attributes = $_POST["ThirdPartyCredential"];
                $this->checkProviderSpecificCredential($model);
            }

            $this->renderProviderForm($model);
        } catch (Exception $exception) {
            $this->errorHandler($exception);
            $this->redirect($this->createUrl('index'));
        }
    }

    public function actionAjaxRenderModal($provider)
    {
        $model = new ThirdPartyCredential($provider);
        $this->renderProviderForm($model);
    }

    private function renderProviderForm($model, $view = null, $iCloudCalendars = null, $availableCalendars = null)
    {
        $model->provider = $model->scenario;
        $user = $this->getUserInstance();
        if ($user) {
            $availableAddressBooks = $this->getAvailableAddressBooks($user);

            if (empty($view)) {
                $view = $this->getProviderViewName($model->provider);
            }

            $this->renderPartial(
                    $view, array(
                'availableAddressBooks' => $availableAddressBooks,
                'availableCalendars' => $availableCalendars,
                'availableICloudCalendars' => $iCloudCalendars,
                'model' => $model,
                    ), false, true
            );
        }
        Yii::app()->end();
    }

    private function getProviderViewName($provider)
    {
        $views = array(
//            ThirdPartyProvider::XING => '_credential_Xing',
            ThirdPartyProvider::ICLOUD => '_credential_iCloud',
            ThirdPartyProvider::GOOGLE => '_credential_Google'
        );

        return $views[$provider];
    }

    /**
     * Performs the AJAX validation.
     * @param CModel the model to be validated
     */
    protected function performAjaxValidation($model)
    {
        if (isset($_POST['ajax']) && startsWith($_POST['ajax'], 'credential-')) {
            echo CActiveForm::validate($model);
            Yii::app()->end();
        }
    }

    public function actionValidateICloudCredentials()
    {
        $isValid = "false";
        if (isset($_POST['username']) && isset($_POST['password'])) {
            $client = new iCloudClient($_POST['username'], strrev(bin2hex($_POST['password'])));
            $uri = $client->getPrincipalsData('contacts', 'addressbook');
            if ($uri !== ErrorCode::iCloudInvalidUserIdOrPassword) {
                $isValid = $uri;
            }
        }
        echo json_encode($isValid);
        Yii::app()->end();
    }

    public function actionGetICloudCalendars()
    {
        $isValid = "false";
        if (isset($_POST['username']) && isset($_POST['password'])) {
            $client = new iCloudClient($_POST['username'], strrev(bin2hex($_POST['password'])));
            $uri = $client->getPrincipalsData('caldav', 'calendar');
            if ($uri !== ErrorCode::iCloudInvalidUserIdOrPassword) {
                $isValid = $uri;
                $calendars = $client->getICloudCalendars($uri);
            }
        }

        if (!empty($calendars)) {
            echo json_encode($calendars);
        } else {
            echo json_encode($isValid);
        }
        Yii::app()->end();
    }

    public function actionGetSaveErrorMessage()
    {
        echo json_encode(ErrorCode::iCloudInvalidSaveData);
        Yii::app()->end();
    }

    public function actionRenderTable()
    {
        $model = new ThirdPartyCredential();
        $model->owner = $this->getUserInstance();

        $thirdPartyEntrys = $_POST;

        if (isset($thirdPartyEntrys['username'])) {
            $model->username = $thirdPartyEntrys['username'];
        }
        if (isset($thirdPartyEntrys['password'])) {
            $model->password = $thirdPartyEntrys['password'];
        }
        if (isset($thirdPartyEntrys['title'])) {
            $model->title = $thirdPartyEntrys['title'];
        }
        if (isset($thirdPartyEntrys['scenario'])) {
            $model->scenario = $thirdPartyEntrys['scenario'];
        }
        if (isset($thirdPartyEntrys['addressBookURI'])) {
            $model->addressBookURI = $thirdPartyEntrys['addressBookURI'];
        }
        if (isset($thirdPartyEntrys['iCloudCalendars'])) {
            $model->iCloudCalendars = $thirdPartyEntrys['iCloudCalendars'];
        }

        $this->checkCalendars($model);
    }

    private function checkCalendars($model)
    {
//        $calendars = json_decode($model->iCloudCalendars, true);
        $iCloudCalendars = array();
        $plCalendars = array();
//        if (!empty($calendars)) {
//            $iCloudCalendars = $this->checkForAlreadyUsedICloudCalendars($calendars);
//            $phonalisaCalendars = $this->getAvailableCalendars($model->owner);
//            $plCalendars = $this->checkForAlreadyUsedPlCalendars($model, $phonalisaCalendars);
//        }

        $view = "_iCloud_table";
        $this->renderProviderForm($model, $view, $iCloudCalendars, $plCalendars);
    }

    private function checkForAlreadyUsedICloudCalendars($calendars)
    {
        if (!is_array($calendars)) {
            $calendars = array($calendars);
        }
        foreach ($calendars as $url => $name) {
            $iCloudContactList = iCloudContactsList::model()->findByAttributes(array("iCloudDirectoryPath" => $url));
            if (!empty($iCloudContactList)) {
                unset($calendars[$url]);
            }
        }
        return $calendars;
    }

    private function checkForAlreadyUsedPlCalendars($model, $phonalisaCalendars)
    {
        $thirdPartyCredentials = ThirdPartyCredential::model()->findAllByAttributes(array("username" => $model->username));
        foreach ($thirdPartyCredentials as $thirdPartyCredential) {
            foreach ($phonalisaCalendars as $id => $name) {
                $iCloudContactLists = iCloudContactsList::model()->findByAttributes(array(
                    "thirdPartyCredentialId" => $thirdPartyCredential->id,
                    "plDirectoryId" => $id
                ));
                if (!empty($iCloudContactLists)) {
                    unset($phonalisaCalendars[$id]);
                }
            }
        }
        return $phonalisaCalendars;
    }

    public function actionCreateNewCalendar($title)
    {
        $calendar = new Calendar();
        $calendar->title = $title;
        $calendar->type = CalendarType::userPrivate;
        $calendar->owner = app()->user->sudoUserInstance->id;

        if ($calendar->save()) {
            if ($this->savePermissions($calendar)) {
                $permissionModel = new Permissions();
                $permissionModel->createPermissionsFile();
                echo $calendar->id;
                Yii::app()->end();
            } else {
                Log::e('Could not save Permissions', 'ThirdPartyCredential');
                $calendar->delete();
            }
        } else {
            Log::e('Could not save new Calendar', 'ThirdPartyCredential');
        }
        echo 'false';
        Yii::app()->end();
    }

    private function savePermissions($calendar)
    {
        $user = app()->user->sudoUserInstance;

        if ($user->group && $calendar->id) {
            $newPermission = new Permissions();
            $newPermission->type = 'write_calendar';
            $newPermission->groupId = $user->group;
            $newPermission->itemId = $calendar->id;
            if ($newPermission->save()) {
                return true;
            }
        }

        Log::e('Could not save Permissions', 'ThirdPartyCredential');
        return false;
    }

    public function actionDeleteNewCalendar($calendarId)
    {
        $calendar = Calendar::model()->findByPK($calendarId);

        if ($calendar) {
            if ($calendar->delete()) {
                echo 'true';
                Yii::app()->end();
            } else {
                Log::e('Could not delete Calendar', 'ThirdPartyCredential');
            }
        } else {
            Log::e('Could not find Calendar', 'ThirdPartyCredential');
        }

        Log::e('Calendar Id: ' . $calendarId, 'ThirdPartyCredential');
        echo 'false';
        Yii::app()->end();
    }

    private function getUserInstance()
    {
        $user = app()->user->sudoUserInstance;
        if (!$user) {
            app()->user->logout();
            app()->user->loginRequired();
        }
        return $user;
    }

    private function getAvailableAddressBooks($user)
    {
        $addressBooks = $user->getAddressbooks('write');
        return $this->createAvailableList($addressBooks);
    }

    private function getAvailableCalendars($user)
    {
        $calendars = $user->getCalendars('write_calendar');
        $availableList = array();

        foreach ($calendars as $entry) {
            $availableList[$entry->id] = $entry->title;
        }

        return $availableList;
    }

    private function createAvailableList($list)
    {
        $keyPrivate = __('ADDRESSBOOK_MODEL_TYPE_PRIVATE');
        $keyGroup = __('ADDRESSBOOK_MODEL_TYPE_GROUP');
        $availableList = array($keyPrivate => array(), $keyGroup => array());

        foreach ($list as $entry) {
            if ($entry->type == AddressbookType::personal || $entry->type == 1) {
                $availableList[$keyPrivate][$entry->id] = $entry->title;
            }
            if ($entry->type == AddressbookType::group || $entry->type == 2) {
                $availableList[$keyGroup][$entry->id] = $entry->title;
            }
        }
        natcasesort($availableList[$keyPrivate]);
        natcasesort($availableList[$keyGroup]);

        return $availableList;
    }

    private function createProviderSpecificModel()
    {
        if (isset($_POST['ThirdPartyCredential']) && isset($_POST["ThirdPartyCredential"]['provider'])) {
            return new ThirdPartyCredential($_POST["ThirdPartyCredential"]['provider']);
        } else {
            return new ThirdPartyCredential(ThirdPartyProvider::ICLOUD);
        }
    }

    private function checkProviderSpecificCredential($model)
    {
        $controller = ThirdPartyConnectorFactory::generateCredentialControllerOfType($model->provider);
        $controller->confirmCredential($model);
    }

    public function actionConfirmCredential($id, $provider)
    {
        try {
            $haComp = new HybridAuthAdapter($provider);
            if (!$haComp->validateProviderName($provider)) {
                throw new CHttpException(500, '[DefaultController] Invalid Provider. Please try again.');
            }
            $haComp->adapter = $haComp->hybridAuth->authenticate($provider);
            if ($haComp->hybridAuth->isConnectedWith($provider)) {
                $this->saveSessionData($haComp, $id);
            }
            $haComp->processLogin();  //further action based on successful login or re-direct user to the required url
        } catch (Exception $exception) {
            $this->errorHandler($exception);
            $this->redirect($this->createUrl('index'));
        }
    }

    private function saveSessionData($haComp, $id)
    {
        $model = ThirdPartyCredential::model()->findByPk($id);
        if ($model) {
            $controller = ThirdPartyConnectorFactory::generateCredentialControllerOfType($model->provider);
            $controller->setSessionData($haComp, $model);
        } else {
            throw new CHttpException(404, '[DefaultController] ThirdPartyCredential model not found. ID: ' . $id);
        }
    }

    public function actionSocialLogin()
    {
        try {
            $basePath = Yii::getPathOfAlias('ext.HybridAuth');
            $filePath = $basePath . '/hybridauth-' . HybridAuthIdentity::VERSION . '/hybridauth/index.php';
            if (file_exists($filePath)) {
                require_once $filePath;
            } else {
                throw new CHttpException(404, '[DefaultController] No such file or directory: ' . $filePath);
            }
        } catch (Exception $exception) {
            $this->errorHandler($exception);
            $this->createFlashMessages(__('THIRDPARTYCREDENTIAL_ICLOUD_CLIENT_ERROR_FILE_NOT_FOUND'), 'error');
            $this->redirect($this->createUrl('index'));
        }
    }

    public function actionStartImport()
    {
        Log::d("Started automatic iCloud-Sync.", "ThirdPartyCredential");
        $totalItemCount = ThirdPartyCredential::model()->count();
        Log::d("$totalItemCount Accounts will be updated.", "ThirdPartyCredential");
        $offset = 0;
        while ($offset < $totalItemCount) {
            $models = ThirdPartyCredential::model()->findAllByAttributes(
                    array(
                'authenticated' => ThirdPartyCredentialStatus::authenticated,
                'passwordStatus' => ThirdPartyCredentialStatus::valid
                    ), null, 100, $offset);

            foreach ($models as $model) {

                $owner = User::model()->findByPk($model->owner);
                if ($owner->hasPermission($model->addressBookId, "write")) {
                    $this->startProviderSpecificSync($model);
                } else {
                    Log::d("Accounts will not be updated. Permissions denied", "ThirdPartyCredential");
                }
            }

            $offset += 100;
        }
    }

    public function actionStartSingleAccountImport($id)
    {
        try {
            $model = ThirdPartyCredential::model()->findByPk($id);

            $owner = User::model()->findByPk($model->owner);
            if (!$owner->hasPermission($model->addressBookId, "write")) {
                Log::d("Accounts will not be updated. ", "ThirdPartyCredential");
                throw new CHttpException(403, '[DefaultController] ThirdPartyCredential model not found: ID-' . $id);
            }

            Log::v("Starting Sync", "ThirdPartyCredential");
            if (!$model) {
                throw new CHttpException(404, '[DefaultController] ThirdPartyCredential model not found: ID-' . $id);
            }
            if ($model->passwordStatus == ThirdPartyCredentialStatus::valid) {
                $this->startProviderSpecificSync($model);
            }
        } catch (Exception $exception) {
            $this->errorHandler($exception);
        }
        if (isset(app()->user->sudoUserInstance)) {
            $this->redirect($this->createUrl('index'));
        }
    }

    private function startProviderSpecificSync($model)
    {
        $controller = ThirdPartyConnectorFactory::generateCredentialControllerOfType($model->provider);
        $controller->startSync($model);
        if (!$model->save()) {
            throw new CHttpException(500, "StartSingleAccountImport: model->save() failed in model :ThirdPartyCredential-ID : $model->id");
        }
    }

    public function actionImportAddresses($jobId)
    {
        Log::v("Synchronize Accounts...", "ThirdPartyCredential");
        $job = Job::model()->findByPk($jobId);
        try {
            if ($job) {
                $model = $this->getThirdPartyCredentialsModel($job);
                $controller = ThirdPartyConnectorFactory::generateCredentialControllerOfType($model->provider);
                $controller->synchronizeAccounts($job, $model);
            } else {
                throw new CHttpException(404, '[DefaultController] Job model not found. Job-ID: ' . $jobId);
            }
        } catch (Exception $exception) {
            $this->errorHandler($exception);
        }
    }

    public function actionAjaxPassword($id)
    {
        $model = $this->loadModel($id);
        if ($model->provider == ThirdPartyProvider::XING) {
            $this->checkProviderSpecificCredential($model);
        } else {
            $this->updateICloudPassword($model);
        }
    }

    private function updateICloudPassword($model)
    {
        $model->scenario = "changePassword";
        $this->performAjaxValidation($model);
        if (isset($_POST['ThirdPartyCredential'])) {
            $model->attributes = $_POST['ThirdPartyCredential'];
            if ($model->newPassword === $model->confirmPassword) {
                $this->setNewICloudPassword($model);
                $this->redirect($this->createUrl('index'));
            }
        }

        $this->renderPartial('_formPassword', array('model' => $model), false, true);
    }

    private function setNewICloudPassword($model)
    {
        $model->password = strrev(bin2hex($model->newPassword));
        $model->newPassword = null;
        $model->confirmPassword = null;
        $model->scenario = "";
        $model->passwordStatus = ThirdPartyCredentialStatus::valid;
        if (!$model->save()) {
            $this->createErrorLogMessages($model);
        }
    }

    private function getThirdPartyCredentialsModel($job)
    {
        if (!$job->meta) {
            throw new CHttpException(404, 'No Meta data found! Job ID:' . $job->id);
        }
        $meta = json_decode($job->meta);

        $model = ThirdPartyCredential::model()->findByPk($meta->thirdPartyCredentialId);
        if ($model) {
            return $model;
        } else {
            throw new CHttpException(404, 'ThirdPartyCredential model not found: ID-' . $meta->thirdPartyCredentialId);
        }
    }

    private function createFlashMessages($message, $type, $errors = null)
    {
        $flashMessage = '<strong>';
        $flashMessage .= $message;
        $flashMessage .= '</strong><br />';
        if (is_array($errors)) {
            $flashMessage = $this->addMultipleErrorMessages($flashMessage, $errors);
        }
        Yii::app()->user->setFlash($type, $flashMessage);
    }

    private function addMultipleErrorMessages($flashMessage, $errors)
    {
        foreach ($errors as $errorMessage) {
            if (is_array($errorMessage)) {
                $flashMessage .= '<strong> - ';
                $flashMessage .= array_shift($errorMessage);
                $flashMessage .= '</strong><br />';
            }
        }
        return $flashMessage;
    }

    private function errorHandler($exception)
    {
        $file = substr($exception->getFile(), strripos($exception->getFile(), '/') + 1);
        $line = $exception->getLine();
        $message = $exception->getMessage();
        Log::e("An Error occurred in Line $line of $file.", 'ThirdPartyCredential');
        Log::e("Error-Message: $message", 'ThirdPartyCredential');
    }

    /**
     * Deletes a particular model.
     * If deletion is successful, the browser will be redirected to the 'admin' page.
     * @param integer $id the ID of the model to be deleted
     */
    public function actionDelete($id)
    {
        if (Yii::app()->request->isPostRequest) {
            //we only allow deletion via POST request
            $this->loadModel($id)->delete();
            //if AJAX request (triggered by deletion via admin grid view), we should not redirect the browser
            if (!isset($_GET['ajax'])) {
                $this->redirect(isset($_POST['returnUrl']) ? $_POST['returnUrl'] : array('index'));
            }
        } else {
            throw new CHttpException(400, 'Invalid request. Please do not repeat this request again.');
        }
    }

    public function loadModel($id)
    {
        $model = ThirdPartyCredential::model()->findByPk($id);
        if ($model === null) {
            throw new CHttpException(404, 'The requested page does not exist.');
        }

        return $model;
    }

    public function renderAuthenticatedStatus($data, $row, $column)
    {
        $this->renderPartial(
                '_confirmStatus', array(
            'data' => $data,
            'row' => $row,
            'column' => $column
                )
        );
    }

    public function renderTitle($data, $row, $column)
    {
        $icon = '';
        switch ($data->provider) {
            //FIX ME: Will probably be blocked by AdBlocker
            case ThirdPartyProvider::GOOGLE:
                $icon = '<i class="icon-fixed-width icon-google-plus"></i>';
                break;
            case ThirdPartyProvider::XING:
                $icon = '<i class="icon-fixed-width icon-xing"></i> ';
                break;
            case ThirdPartyProvider::ICLOUD:
                $icon = '<i class="icon-fixed-width icon-cloud"></i> ';
                break;

            default:
                return CHtml::encode($data->title);
        }
        echo $icon . ' ' . CHtml::encode($data->title);
    }

    public function renderUpdated($data, $row, $column)
    {
        return PhonalisaDate::userDateTime((int) $data->updated, true);
    }

    public function renderPasswordStatus($data, $row, $column)
    {
        $this->renderPartial(
                '_passwordStatus', array(
            'data' => $data,
            'row' => $row,
            'column' => $column
                )
        );
    }
}
