<?php

/**
 * LICENSE: This Software is the property of Lifestyle Webconsulting GmbH (Aschaffenburg, Germany)
 * and is protected 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
 * @version    $Id:$
 * @link       http://www.life-style.de
 */

namespace Sso\ApiBundle\Api\Response;

use Sso\ApiBundle\Api\Exception\Type\Base as BaseException;
use Sso\ApiBundle\Api\Exception\Type\Api as ApiException;
use Sso\ApiBundle\Api\Error\Type\Internal as InternalError;
use Sso\ApiBundle\Api\Error\Type\Base as BaseError;

/**
 * Xml response builder
 */
class Builder
{

    /**
     * Xml-Document
     *
     * @var \DOMDocument
     */
    private $xml;

    /**
     * Xml-Root-Element
     *
     * @var \DOMElement
     */
    private $root;

    /**
     * Xml-ControllerName-Element
     *
     * @var \DOMElement
     */
    private $controller;

    /**
     * Xml-ActionName-Element
     *
     * @var \DOMElement
     */
    private $action;

    /**
     * Global Xml-Status-Element
     *
     * @var \DOMElement
     */
    private $globalStatus;

    /**
     * Xml-Error-Container
     *
     * @var \DOMElement
     */
    private $errors;

    /**
     * Xml-Document
     *
     * @return \DOMDocument
     */
    public function document()
    {
        if (null === $this->xml) {
            $this->xml = new \DOMDocument('1.0', 'utf-8');
            $this->root();
        }
        return $this->xml;
    }

    /**
     * Xml-Root-Element
     *
     * @return \DOMElement
     */
    public function root()
    {
        if (null === $this->root) {
            $document = $this->document();

            // Root response element
            $this->root = $document->createElement('SsoResponse');
            $document->appendChild($this->root);

            // Create standard header elements
            $this->root->appendChild($this->globalStatus = $document->createElement('Status', 'Unknown'));
            $this->root->appendChild($document->createElement('Trackid', ApiException::getTrackId()));
            $this->root->appendChild($document->createElement('Date', date('c')));
        }
        return $this->root;
    }

    /**
     * Create new controller element
     *
     * @return \DOMElement
     */
    public function createController($controllerName)
    {
        $this->controller = $this->document()->createElement($controllerName);
        $this->root()->appendChild($this->controller);
        return $this->controller;
    }

    /**
     * Get current controller element, creates new controller element, if none exists
     *
     * @return \DOMElement
     */
    public function controller()
    {
        return null !== $this->controller ? $this->controller : $this->createController(ApiException::getController());
    }

    /**
     * Create new action element
     *
     * @return \DOMElement
     */
    public function createAction($actionName)
    {
        $this->errors = null;
        $this->action = $this->document()->createElement($actionName);
        $this->controller()->appendChild($this->action);
        return $this->action;
    }

    /**
     * Get current action element, creates new action element, if none exists
     *
     * @return \DOMElement
     */
    public function action()
    {
        return null !== $this->action ? $this->action : $this->createAction(ApiException::getAction());
    }

    /**
     * Get error container, creates new error container, if none exists
     *
     * @return \DOMElement
     */
    private function errors()
    {
        if (null === $this->errors) {
            $document = $this->document();
            $this->errors = $document->createElement('Errors');
            $this->action->appendChild($this->errors);
        }
        return $this->errors;
    }

    /**
     * Set or get global response status and
     * append status element to current action
     *
     * Call without parameter, to get current status.
     * Global status "Failure" is not overwritable
     *
     * @param bool|string|null $status true|false|string|null
     */
    public function status($status = null)
    {
        // To be sure, that document exist!
        $document = $this->document();

        if (null === $status) {
            return $this->globalStatus->nodeValue;
        }

        // Get status from boolean
        if (false === $status) {
            $status = 'Failure';
        } elseif (true === $status) {
            $status = 'Success';
        }

        // Set global status
        if (0 !== strcasecmp($status, $this->globalStatus->nodeValue)) {
            $this->globalStatus->nodeValue = (string)$status;
        }

        // Set status for current record
        $this->action()->appendChild($document->createElement('Status', $status));
    }

    /**
     * Build xml response from exception
     *
     * @param \Exception $exception
     */
    public function exception($exception)
    {
        $this->status(false);
        if ($exception instanceof BaseException) {
            foreach ($exception->getErrors() as $error) {
                $this->addErrorElement($error);
            }
        } else {
            $this->addErrorElement(new InternalError('crit1', 'CriticalError', '', '', $exception));
        }
    }

    /**
     * Add error element
     *
     * @param BaseError $error
     */
    public function addErrorElement(BaseError $error)
    {
        $document = $this->document();

        $errorElement = $document->createElement('Error');
        $this->errors()->appendChild($errorElement);

        $errorCode = $document->createElement("ErrorCode");
        $errorCode->appendChild($document->createCDATASection($error->code));
        $errorElement->appendChild($errorCode);

        $errorRef = $document->createElement("ErrorRef");
        $errorRef->appendChild($document->createCDATASection($error->ref));
        $errorElement->appendChild($errorRef);

        $shortMessage = $document->createElement("ShortMessage");
        $shortMessage->appendChild($document->createCDATASection($error->shortMessage));
        $errorElement->appendChild($shortMessage);

        $longMessage = $document->createElement("LongMessage");
        $longMessage->appendChild($document->createCDATASection($error->longMessage));
        $errorElement->appendChild($longMessage);
    }

    /**
     * Return xml document as string
     *
     * @return string
     */
    public function output()
    {
        return $this->document()->saveXML();
    }

}
