<?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 LifeStyle\Tools\WebserviceBundle\Response;

use LifeStyle\Tools\WebserviceBundle\Error\TrackId;
use LifeStyle\Tools\WebserviceBundle\Exception\BaseException;
use LifeStyle\Tools\WebserviceBundle\Error\Type\Internal as InternalError;
use LifeStyle\Tools\WebserviceBundle\Error\Type\Base as BaseError;

/**
 * Class Builder
 * @package LifeStyle\Tools\WebserviceBundle\Response
 */
class Builder
{

    /**
     * Xml-Document
     *
     * @var \LifeStyle\Tools\WebserviceBundle\Response\Document
     */
    private $xml;

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

    /**
     * Controller name
     *
     * @var string
     */
    private $controllerName = 'Unknown';

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

    /**
     * Action name
     *
     * @var string
     */
    private $actionName = 'Unknown';

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

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

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

    /**
     * Debug mode on/off
     *
     * @var bool
     */
    private $debug = false;

    /**
     * Track-id
     *
     * @var string
     */
    private $trackId = '';

    /**
     * Constructor
     *
     * @param TrackId $trackId
     */
    public function __construct(TrackId $trackId = null)
    {
        if (null !== $trackId) {
            $this->trackId = $trackId->getTrackId();
        }
    }

    /**
     * Set controller name
     *
     * @param string $controllerName
     */
    public function setController($controllerName)
    {
        $this->controllerName = trim($controllerName);
        $this->document()->setNodeName($this->controller(), $this->controllerName);
    }

    /**
     * Set action name
     *
     * @param string $actionName
     */
    public function setAction($actionName)
    {
        $this->actionName = trim($actionName);
        $this->document()->setNodeName($this->action(), $this->actionName);
    }

    /**
     * Xml-Document
     *
     * @return Document
     */
    public function document()
    {
        if (null === $this->xml) {
            $this->xml = new Document('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->createTextElement('Status', 'Unknown'));
            $this->root->appendChild($document->createTextElement('Trackid', $this->trackId));
            $this->root->appendChild($document->createDateTimeElement('Date', date('c')));
        }
        return $this->root;
    }

    /**
     * Get current controller element, creates new controller element, if none exists
     *
     * @return \DOMElement
     */
    public function controller()
    {
        if (null === $this->controller) {
            $this->controller = $this->document()->createElement($this->controllerName ? $this->controllerName : 'test');
            $this->root()->appendChild($this->controller);
        }
        return $this->controller;
    }

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

    /**
     * Mark current action as finished
     */
    public function endAction()
    {
        $this->action = null;
    }

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

    /**
     * Get error container, creates new error container, if none exists
     *
     * @return \DOMElement
     */
    private function errors()
    {
        if (null === $this->errors) {
            $document = $this->document();
            $errors = $document->createElement('Errors');
            $this->action()->appendChild($errors);
            $this->errors = $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) && 'Failure' !== $this->globalStatus->nodeValue) {
            $this->globalStatus->nodeValue = (string)$status;
        }

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

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

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

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

        $errorElement->appendChild($document->createCDATAElement('ErrorCode', $error->code));
        $errorElement->appendChild($document->createCDATAElement('ErrorRef', $error->ref));
        $errorElement->appendChild($document->createCDATAElement('ShortMessage', $error->shortMessage));
        $errorElement->appendChild($document->createCDATAElement('LongMessage', $error->longMessage));
    }

    /**
     * Append exception trace
     *
     * @param \Exception $exception
     */
    protected function addExceptionTraceElement(\Exception $exception)
    {
        $document = $this->document();

        $exceptionElement = $document->createElement('ExceptionTrace');
        $this->errors()->appendChild($exceptionElement);

        $exceptionElement->appendChild($document->createCDATAElement('File', $exception->getFile()));
        $exceptionElement->appendChild($document->createCDATAElement('Line', $exception->getLine()));
        $exceptionElement->appendChild($document->createCDATAElement('Code', $exception->getCode()));
        $exceptionElement->appendChild($document->createCDATAElement('Message', $exception->getMessage()));
        $exceptionElement->appendChild($document->createCDATAElement('ExceptionClass', get_class($exception)));
        $exceptionElement->appendChild($document->createCDATAElement('Trace', $exception->getTraceAsString()));
    }

    /**
     * Set debug mode - trace will be appended
     *
     * @param bool $debug
     */
    public function setDebug($debug = true)
    {
        $this->debug = (bool)$debug;
    }

    /**
     * Add duration for debugging
     *
     * @param int $time Duration in ms
     */
    public function addDuration($time)
    {
        $this->action()->appendChild($this->document()->createTextElement('Duration', (string)$time));
    }

    /**
     * Add duration for debugging
     *
     * @param int $time Duration in ms
     */
    public function addCount($count)
    {
        $document = $this->document();
        $this->action()->appendChild($this->document()->createTextElement('Count', (int)$count));
    }

    /**
     * @param integer $offset
     */
    public function addOffset($offset)
    {
        $document = $this->document();
        $this->action()->appendChild($this->document()->createTextElement('Offset', (int)$offset));
    }

    /**
     * @param integer $limit
     */
    public function addLimit($limit)
    {
        $document = $this->document();
        $this->action()->appendChild($this->document()->createTextElement('Limit', (int)$limit));
    }

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