<?php

/**
 * Lifestyle Webconsulting GmbH
 *
 * LICENSE: This Software is the property of Lifestyle Webconsulting GmbH (Aschaffenburg, Germany)
 * and is private by copyright law - it is NOT Freeware.
 *
 * Any unauthorized use of this software without a valid license
 * is a violation of the license agreement and will be prosecuted by
 * civil and criminal law.
 *
 * @copyright  2018 Lifestyle Webconsulting GmbH
 * @link       http://www.life-style.de
 */

namespace Sso\WebserviceBundle\Api\Controller;

use Sso\WebserviceBundle\Api\Error\Type\External as ExternalError;
use Sso\WebserviceBundle\Api\Response\Builder as XmlResponse;
use Sso\WebserviceBundle\Model\Request\UserApplicationAttribute\Add;

/**
 * Class UserApplicationAttributeController
 * @package Sso\WebserviceBundle\Api\Controller
 */
class UserApplicationAttributeController extends AbstractController
{
    /**
     * @return array
     */
    protected function _getActions()
    {
        return array(
            'Add',
            'Delete',
            'Show',
        );
    }

    /**
     * Show users application attribute values
     *
     * @param \SimpleXMLElement $xmlRequest
     * @param XmlResponse $xmlResponse
     */
    protected function show(\SimpleXMLElement $xmlRequest, XmlResponse $xmlResponse)
    {
        $requestModel = $this->modelFactory->request()->userApplicationAttribute()->show($xmlRequest);
        $userModel = $requestModel->getUserModel();
        $applicationModel = $requestModel->getApplicationModel();

        if (!$requestModel->hasErrors() && !($user = $this->dbM->user()->getUserByUser($userModel))) {
            $requestModel->addError(new ExternalError('uat011', 'UserNotFound'));
        }
        if (!$requestModel->hasErrors() && !$this->apiM->userAuthorization()->canUpdate($user)) {
            $requestModel->addError(new ExternalError('uat013', 'UserNotAuthorized'));
        }
        if (!$requestModel->hasErrors() && !($application = $this->dbM->application()->getApplicationByName($applicationModel->getName()))) {
            $requestModel->addError(new ExternalError('uat012', 'ApplicationNotFound'));
        }
        //check read on application
        if (!$requestModel->hasErrors() && !in_array($applicationModel->getName(),
                $this->apiM->userRolesApplicationRead())
        ) {
            $requestModel->addError(new ExternalError('ac001', 'ApplicationAccessDenied',
                'You are not allowed to read this Application'));
        }
        if (!$requestModel->hasErrors() && !($userApplication = $this->dbM->userApplication()->getUserApplication($user,
                $application))
        ) {
            $requestModel->addError(new ExternalError('uat013', 'UserApplicationNotFound'));
        }
        if (!$requestModel->hasErrors()) {
            $requestModel->setUserModel($user);
            $requestModel->setApplicationModel($application);
            $requestModel->setUserApplicationModel($userApplication);
        }
        $this->modelFactory->response($xmlResponse)->userApplicationAttribute()->show($requestModel);
    }

    /**
     * Add users application attribute values
     *
     * Existing attributes will be overwritten
     *
     * @param \SimpleXMLElement $xmlRequest
     * @param XmlResponse $xmlResponse
     */
    protected function add(\SimpleXMLElement $xmlRequest, XmlResponse $xmlResponse)
    {
        $requestModel = $this->modelFactory->request()->userApplicationAttribute()->add($xmlRequest);
        $userModel = $requestModel->getUserModel();
        $applicationModel = $requestModel->getApplicationModel();
        $userApplicationAttributes = $requestModel->getUserApplicationAttributes();

        if ($requestModel->hasErrors()) {
            return $this->setAddResponse($xmlResponse, $requestModel);
        }

        if (!($user = $this->dbM->user()->getUserByUser($userModel))) {
            $requestModel->addError(new ExternalError('uat021', 'UserNotFound'));
            return $this->setAddResponse($xmlResponse, $requestModel);
        }
        if (!$requestModel->hasErrors() && !$this->apiM->userAuthorization()->canUpdate($user)) {
            $requestModel->addError(new ExternalError('uat023', 'UserNotAuthorized'));
        }

        if (!($application = $this->dbM->application()->getApplicationByName($applicationModel->getName()))) {
            $requestModel->addError(new ExternalError('uat022', 'ApplicationNotFound'));
            return $this->setAddResponse($xmlResponse, $requestModel);
        }

        // Check write permissions for application
        if (!in_array($applicationModel->getName(), $this->apiM->userRolesApplicationWrite())) {
            $requestModel->addError(new ExternalError(
                'ac001',
                'ApplicationAccessDenied',
                'You are not allowed to write this Application'
            ));
            return $this->setAddResponse($xmlResponse, $requestModel);
        }

        if (!($userApplication = $this->dbM->userApplication()->getUserApplication($user, $application))) {
            $requestModel->addError(new ExternalError('uat023', 'UserApplicationNotFound'));
            return $this->setAddResponse($xmlResponse, $requestModel);
        }

        //
        // Merge attributes
        //

        // Build indexed list of existing attributes
        $existingAttributes = [];
        foreach ($userApplication->getAttributes() as $attribute) {
            $attributeName = $attribute->getLowername();
            $index = $attribute->getObjectHash();
            $existingAttributes[$attributeName][$index] = $attribute;
        }

        // Build indexed list of attributes from request
        $attributeCount = [];
        $requestAttributes = [];
        foreach ($userApplicationAttributes as $attribute) {
            $attributeName = $attribute->getLowername();
            $index = $attribute->getObjectHash();
            $attributeCount[$attributeName][$index] =
                isset($attributeCount[$attributeName][$index]) ? $attributeCount[$attributeName][$index] + 1 : 1;
            $requestAttributes[$attributeName][$index] = $attribute;
        }

        $newUserApplicationAttributes = [];
        $deprecatedUserApplicationAttributes = [];
        foreach (array_intersect_key($requestAttributes, $existingAttributes) as $attributeName => $attributes) {

            // Collect existing attributes for update
            foreach (array_diff_key($attributes, $existingAttributes[$attributeName]) as $attribute) {

                $applicationAttribute = $application->getAttribute($attribute->getName());

                // Attributes of type 'one' should only be set once
                if (!$applicationAttribute->isArray() && $attributeCount[$attribute->getLowername()][$attribute->getObjectHash()] > 1) {
                    $requestModel->addError(new ExternalError(
                        'uat023',
                        'UserApplicationAttributeMultiple',
                        'Attribute '.$attribute->getName().' is not a multi-value and may not be set more than once in a request.'
                    ));
                }

                // Connect new attribute-value to application and attribute
                $attribute->setAttribute($applicationAttribute);
                $attribute->setUserApplication($userApplication);
                $newUserApplicationAttributes[] = $attribute;
            }

            // Collect attributes for delete
            foreach (array_diff_key($existingAttributes[$attributeName], $attributes) as $attribute) {
                $deprecatedUserApplicationAttributes[] = $attribute;
                $userApplication->removeAttribute($attribute);
            }
        }

        // Collect new attributes
        $newApplicationAttributes = [];
        foreach (array_diff_key($requestAttributes, $existingAttributes) as $attributes) {
            $attributeExists = null;
            foreach ($attributes as $attribute) {

                // Create application attribute if not exists
                if (null === $attributeExists && !($attributeExists = $application->hasAttribute($attribute))) {
                    $applicationAttribute = $requestModel->getAttributeByName($attribute->getName());
                    $applicationAttribute->setApplication($application);
                    $application->addAttribute($applicationAttribute);
                    $newApplicationAttributes[] = $applicationAttribute;
                }

                // Connect new attribute-value to application and attribute
                $attribute->setAttribute($application->getAttribute($attribute->getName()));
                $attribute->setUserApplication($userApplication);
                $newUserApplicationAttributes[] = $attribute;
            }
        }

        if ($requestModel->hasErrors()) {
            return $this->setAddResponse($xmlResponse, $requestModel);
        }

        // Save new attributes
        if (!$this->dbM->attribute()->saveAttributes($newApplicationAttributes)) {
            foreach ($newApplicationAttributes as $attribute) {
                $requestModel->addErrors($attribute->errors()->getErrors());
            }
        }
        if ($requestModel->hasErrors()) {
            return $this->setAddResponse($xmlResponse, $requestModel);
        }

        // Delete old attribute values
        if (!$this->dbM->userApplicationAttribute()->deleteUserApplicationAttributes($deprecatedUserApplicationAttributes)) {
            foreach ($deprecatedUserApplicationAttributes as $attribute) {
                $requestModel->addErrors($attribute->errors()->getErrors());
            }
        }
        if ($requestModel->hasErrors()) {
            return $this->setAddResponse($xmlResponse, $requestModel);
        }

        // Save attribute values
        if (!$this->dbM->userApplicationAttribute()->saveUserApplicationAttributes($newUserApplicationAttributes)) {
            foreach ($newUserApplicationAttributes as $attribute) {
                $requestModel->addErrors($attribute->errors()->getErrors());
            }
        }
        if ($requestModel->hasErrors()) {
            return $this->setAddResponse($xmlResponse, $requestModel);
        }

        $userApplication->checkObjectUpdate();
        $this->dbM->userApplication()->saveUserApplication($userApplication);

        return $this->setAddResponse($xmlResponse, $requestModel);
    }

    /**
     * @param XmlResponse $xmlResponse
     * @param Add $requestModel
     */
    private function setAddResponse(XmlResponse $xmlResponse, Add $requestModel)
    {
        $this->modelFactory->response($xmlResponse)->userApplicationAttribute()->add($requestModel);
    }

    /**
     * Delete users application attribute values
     *
     * @param \SimpleXMLElement $xmlRequest
     * @param XmlResponse $xmlResponse
     */
    protected function delete(\SimpleXMLElement $xmlRequest, XmlResponse $xmlResponse)
    {
        $requestModel = $this->modelFactory->request()->userApplicationAttribute()->delete($xmlRequest);
        $userModel = $requestModel->getUserModel();
        $applicationModel = $requestModel->getApplicationModel();
        $attributeModel = $requestModel->getApplicationAttributeModel();

        if (!$requestModel->hasErrors() && !($user = $this->dbM->user()->getUserByUser($userModel))) {
            $requestModel->addError(new ExternalError('uat041', 'UserNotFound'));
        }
        if (!$requestModel->hasErrors() && !$this->apiM->userAuthorization()->canUpdate($user)) {
            $requestModel->addError(new ExternalError('uat043', 'UserNotAuthorized'));
        }
        if (!$requestModel->hasErrors() && !($application = $this->dbM->application()->getApplicationByName($applicationModel->getName()))) {
            $requestModel->addError(new ExternalError('uat042', 'ApplicationNotFound'));
        }
        //check write on application
        if (!$requestModel->hasErrors() && !in_array($applicationModel->getName(),
                $this->apiM->userRolesApplicationWrite())
        ) {
            $requestModel->addError(new ExternalError('ac001', 'ApplicationAccessDenied',
                'You are not allowed to write this Application'));
        }
        if (!$requestModel->hasErrors() && !($attribute = $application->getAttribute($attributeModel->getName()))) {
            $requestModel->addError(new ExternalError('uat043', 'AttributeNotFound'));
        }
        if (!$requestModel->hasErrors() && !($userApplication = $this->dbM->userApplication()->getUserApplication($user,
                $application))
        ) {
            $requestModel->addError(new ExternalError('uat044', 'UserApplicationNotFound'));
        }

        if (!$requestModel->hasErrors()) {
            $attributeId = $attribute->getId();
            $userApplicationAttributes = array();
            foreach ($userApplication->getAttributes() as $userApplicationAttribute) {
                if ($userApplicationAttribute->getAttribute()->getId() == $attributeId) {
                    $userApplicationAttributes[$userApplicationAttribute->getId()] = $userApplicationAttribute;
                }
            }
        }

        // Delete attribute value for users application
        if (!$requestModel->hasErrors() && !$this->dbM->userApplicationAttribute()->deleteUserApplicationAttributes($userApplicationAttributes)) {
            foreach ($userApplicationAttributes as $userApplicationAttribute) {
                $requestModel->addErrors($userApplicationAttribute->errors()->getErrors());
            }
        }

        $this->modelFactory->response($xmlResponse)->userApplicationAttribute()->delete($requestModel);
    }
}
