<?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\RestBundle\Worker\MultipleWs\Version1\Scope\GetScopeTreeObjects;

use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use FOS\RestBundle\View\View as RestView;
use LifeStyle\Tools\RestErrorBundle\Api\Manager as ErrorApi;
use LifeStyle\Tools\RestErrorBundle\Api\Error\Errors\Index as Errors;
use Sso\RestBundle\ModelRest\Request\MultipleWs\Version1\ScopeTreeObjects\Get\Request as RequestModel;
use Sso\RestBundle\Api\Manager as ApiManager;
use Sso\RestBundle\ModelRest\Response\UserWs\Version1\UserApplicationAttribute\Show\Item as AppAttrItem;
use Sso\RestBundle\ModelRest\Response\ObjectWs\Version1\Object\Show\Tree\Success\Response as ObjectResponse;
use Sso\RestBundle\ModelRest\Response\ObjectWs\Version1\Object\Show\Tree\Success\ObjectType as ResponseObject;
use Sso\RestBundle\ModelRest\Response\UserWs\Version1\UserApplicationAttribute\Show\Item as UserApplicationAttributesModel;

/**
 * Class Handler
 * @package Sso\RestBundle\Worker\MultipleWs\Version1\Scope\GetScopeTreeObjects
 */
class Handler
{
    /**
     * @var string
     */
    const SCOPE_ATTRIBUTE_TYPE_MANY = 'many';

    /**
     * @var string
     */
    const SCOPE_ATTRIBUTE_TYPE_ONE = 'one';

    /**
     * @var ApiManager
     */
    protected $apiM;

    /**
     * @var ErrorApi
     */
    protected $errorApi;

    /**
     * @var Errors
     */
    protected $errors;

    /**
     * @var string
     */
    protected $format;

    /**
     * @var RequestModel
     */
    protected $requestModelRest;

    /**
     * Temporary storage of the complete object tree, used for filtering out children of given objects
     *
     * @var array|null
     */
    private $fullObjectTreeCache;

    /**
     *
     * @param ApiManager $apiM
     */
    public function __construct(ApiManager $apiM)
    {
        $this->apiM = $apiM;
        $this->errorApi = $this->apiM->errorManager();
        $this->errors = $this->errorApi->error()->errors();
        $this->format = 'json';
        $this->fullObjectTreeCache = null;
    }


    public function init(Request $request)
    {
        $this->requestModelRest = $this->validateRestRequest($request);
        $returnData = null;
        if ($this->errors->hasErrors()) {
            $returnData = $this->errorApi->error()->view()->getErrorsView($this->errors->getErrors());
        }

        return $returnData;
    }


    /**
     * @param Request $request
     * @return RestView
     */
    public function getUserApplicationAttributes(Request $request)
    {
        $scopeApplicationName = $this->apiM->configuration()->scopeConfig()->getScopeApplicationName();
        $request->query->add(['applicationName' => $scopeApplicationName]);

        return $this->apiM->worker()->userWs()->version1()->userApplicationAttribute()->show()->init($request);
    }


    /**
     * @param Request $request
     * @param Response $userApplicationAttrResponse
     * @return RestView[]
     */
    public function getObjectsByAttributes(
        Request $request,
        UserApplicationAttributesModel $userApplicationAttrResponse
    ) {
        $attributesConfig = $this->apiM->configuration()->scopeConfig()->getScopeAttributes();
        $resultObjects = [];

        foreach ($userApplicationAttrResponse->getAttributeTypes() as $attributeType) {
            if (in_array($attributeType->getName(), $attributesConfig)) {
                $request->query->set('referenceId', $attributeType->getValue());
                $responseView = $this->apiM->worker()->objectWs()->version1()->object()->show()->tree()->init($request);

                /** @var ObjectResponse $objectResponse */
                $objectResponse = $responseView->getData();
                if ($objectResponse->getCode() == 200) {
                    foreach ($objectResponse->getObjects() as $object) {
                        $scopeType = $attributeType->getValue() == $object->getReferenceId() ? 'rw' : 'r';

                        if ('r' !== $scopeType) {
                            $resultObjects[(string)$object->getTreeId()] = $this->buildResponseObject($object,
                                $scopeType);

                            $childs = $this->getChildObjects($request, $object->getTreeId());

                            // fugly fix for PHP fuglieness, no matter how much the array index is explicitly casted
                            // to a string, "1" will always be interpreted as integer, causing array_merge to not
                            // overwrite but reindex and append the root object entry - so we make sure the root object
                            // index is treated as string
                            for ($i = 0; $i <= 50; $i++) {
                                $strI = '_' . (string)$i;

                                if (isset($resultObjects[$i])) {
                                    $resultObjects[$strI] = $resultObjects[$i];
                                    unset($resultObjects[$i]);
                                }
                            }

                            $resultObjects = array_merge($childs, $resultObjects);

                            for ($i = 0; $i <= 20; $i++) {
                                // put the root object back to the index where it belongs
                                if (isset($resultObjects[$strI])) {
                                    $resultObjects[$i] = $resultObjects[$strI];
                                    unset($resultObjects[$strI]);
                                }
                            }
                        }
                    }
                }
            }
        }

        ksort($resultObjects);

        return $this->buildResponse($resultObjects);
    }

    /**
     * @param Request $request
     * @param $treeId
     * @return array
     */
    private function getChildObjects(Request $request, $treeId)
    {
        // we need the full object tree to filter out the required childs, so if don't have it already, fetch it now
        if (null === $this->fullObjectTreeCache) {
            // make sure we're not modifying the actual request object
            $tempRequest = clone $request;
            $this->fullObjectTreeCache = [];

            if ($tempRequest->query->has('referenceId')) {
                $tempRequest->query->remove('referenceId');
            }
            $responseView = $this->apiM->worker()->objectWs()->version1()->object()->show()->tree()->init($tempRequest);
            /** @var ObjectResponse $responseData */
            $responseData = $responseView->getData();
            if ($responseData->getCode() == 200) {
                foreach ($responseData->getObjects() as $object) {
                    $this->fullObjectTreeCache[(string)$object->getTreeId()] = $object;
                }
            }
        }

        $childObjects = [];

        // filter out all child nodes of the given one, i.e. all objects whose tree ID begins with the ID of the
        // given one.
        foreach ($this->fullObjectTreeCache as $id => $childObject) {
            if ((strlen($id) > strlen($treeId)) && (substr($id . '.', 0, strlen($treeId) + 1) == $treeId . '.')) {
                $childObjects[(string)$id] = $this->buildResponseObject($childObject, 'rw');
            }
        }

        return $childObjects;
    }

    /**
     * @param ResponseObject $objectIn
     * @param $scopeType
     * @return \Sso\RestBundle\ModelRest\Response\MultipleWs\Version1\Object\Scope\Tree\Success\ObjectType
     */
    protected function buildResponseObject(ResponseObject $objectIn, $scopeType)
    {
        $object = $this->apiM->modelRest()->response()->MultipleWs()->version1()->object()->scope()->tree()->success()->objectType();
        $object->setName($objectIn->getName());
        $object->setGuid($objectIn->getGuid());
        $object->setReferenceId($objectIn->getReferenceId());
        $object->setTreeId($objectIn->getTreeId());
        $object->setTypeName($objectIn->getTypeName());
        $object->setScopeType($scopeType);

        return $object;
    }


    /**
     * @param ResponseObject[] $resultObjects
     * @return RestView
     */
    public function buildResponse(array $resultObjects)
    {
        $responseModel = $this->apiM->modelRest()->response()->MultipleWs()->version1()->object()->scope()->tree()->success()->response();

        foreach ($resultObjects as $resultObject) {
            $objectType = $this->apiM->modelRest()->response()->MultipleWs()->version1()->object()->scope()->tree()->success()->objectType();
            $objectType = $this->apiM->helper()->mapper()->modelInToModelOut($resultObject, $objectType);
            $responseModel->addObject($objectType);
        }

        $responseModel->setCode(200);
        $responseModel->setScriptTimeSec($this->apiM->scriptTimeSeconds());
        $responseModel->setTrackId($this->apiM->trackId());
        $responseModel->setCount($responseModel->getObjects()->count());
        $view = RestView::create();
        $view->setData($responseModel);
        $view->setStatusCode(200);

        return $view;
    }


    /**
     * @param Request $request
     * @return RequestModel
     */
    private function validateRestRequest(Request $request)
    {
        $this->format = preg_match('/\.xml/i', $request->getUri()) ? 'xml' : 'json';

        $restRequestModel = $this->apiM->modelRest()->request()->multipleWs()->version1()->scopeTreeObjects()->get();

        $restRequestModel->setIdentifier($request->query->get('identifier'));

        $validationErrors = $this->apiM->validator()->validate($restRequestModel);

        if (count($validationErrors) > 0) {
            foreach ($validationErrors as $error) {
                $message = $error->getPropertyPath() . ": " . $error->getMessage();
                $this->errors->addError(400, $message, 'external')->setCode(400);
            }
        }

        return $restRequestModel;
    }
}
