<?php

namespace Fifa\ConnectId\Sdk\Util\Serialization;

use Exception;
use Fifa\ConnectId\Sdk\Exception\XmlDeserializationException;
use Fifa\ConnectId\Sdk\Exception\XmlSerializationException;
use Fifa\ConnectId\Sdk\Util\XmlDeserializer\XmlDeserializer;
use Fifa\ConnectId\Sdk\Util\XsdSchemaValidator\XsdSchemaValidator;
use XML_Serializer;

abstract class ValidatingXmlSerializerBase implements XmlSerializerInterface
{
    /**
     * @var XsdSchemaValidator
     */
    protected $xsdSchemaValidator;

    /**
     * @var XML_Serializer
     */
    private $serializer;

    /**
     * @var XmlDeserializer
     */
    private $deserializer;

    /**
     * XmlSerializerBase constructor.
     * @param XML_Serializer $serializer - internal serializer
     * @param XmlDeserializer $deserializer - internal deserializer
     */
    public function __construct(XML_Serializer $serializer, XmlDeserializer $deserializer)
    {
        $this->xsdSchemaValidator = new XsdSchemaValidator();
        $this->serializer = $serializer;
        $this->deserializer = $deserializer;
    }

    /**
     * Iterates over all array-typed properties and sets them to null if they are empty
     *
     * @param $item
     * @return void
     */
    protected function nullifyUnsetArrays($item)
    {
    }

    /**
     * @inheritDoc
     * @throws XmlSerializationException
     */
    public function serialize($item): ?string
    {
        $this->nullifyUnsetArrays($item);
        $this->serializer->serialize($item);
        $xmlContent = $this->serializer->getSerializedData();

        $this->validateAgainstXSD($xmlContent, function($validationErrors) use ($xmlContent, $item) {
            throw new XmlSerializationException(get_class($item) . ' to XML serialization failed. ',$validationErrors, $xmlContent);
        });

        return $xmlContent;
    }

    /**
     * @inheritDoc
     * @throws XmlDeserializationException
     */
    public function deserialize(string $xml)
    {
        try {
            $this->validateAgainstXSD($xml, function($validationErrors) use ($xml) {
                throw new XmlDeserializationException($xml, 'Failed to deserialize given XML',null, $validationErrors);
            });
            return $this->deserializer->deserialize($xml);
        } catch (Exception $e) {
            throw new XmlDeserializationException($xml, 'Failed to deserialize given XML.', $e );
        }
    }

    private function validateAgainstXSD($xml, $errorCallback): void
    {
        $validationResult = $this->xsdSchemaValidator->validate($xml);
        if (!$validationResult->isValid()) {
            $errorCallback($validationResult->getErrors());
        }
    }
}