<?php
// Copyright (c) Pickware GmbH. All rights reserved.
// This file is part of software that is released under a proprietary license.
// You must not copy, modify, distribute, make publicly available, or execute
// its contents or parts thereof without express permission by the copyright
// holder, unless otherwise permitted by law.

namespace Shopware\Plugins\ViisonPickwareMobile\Components\QueryComponentArrayCoding;

use Shopware\Plugins\ViisonPickwareMobile\Components\QueryBuilder\QueryComponent;

class QueryComponentArrayCodingService
{
    /**
     * @var array
     */
    protected $arrayCoders;

    public function __construct()
    {
        // Prepare all available array coders
        $arrayCoders = [
            new BooleanCompositionQueryComponentArrayCoder(),
            new ComparisonQueryComponentArrayCoder(),
            new FieldDescriptorQueryComponentArrayCoder(),
            new FunctionQueryComponentArrayCoder(),
            new ScalarValueQueryComponentArrayCoder(),
            new UnaryOperationQueryComponentArrayCoder(),
        ];
        $arrayCoderTypes = array_map(
            function (QueryComponentArrayCoder $arrayCoder) {
                return $arrayCoder->getCodableType();
            },
            $arrayCoders
        );
        $this->arrayCoders = array_combine($arrayCoderTypes, $arrayCoders);
    }

    /**
     * Encodes the passed `$queryComponent` to its array representation, which is returned.
     *
     * @param QueryComponent $queryComponent
     * @return array
     * @throws QueryComponentArrayEncodingException if passed `$queryComponent` is not encodable.
     */
    public function encode(QueryComponent $queryComponent)
    {
        $compatibleArrayCoders = array_filter(
            $this->arrayCoders,
            function (QueryComponentArrayCoder $arrayCoder) use ($queryComponent) {
                return $arrayCoder->canEncodeQueryComponent($queryComponent);
            }
        );
        $arrayCoder = array_shift($compatibleArrayCoders);
        if (!$arrayCoder) {
            throw QueryComponentArrayEncodingException::componentNotEncodable($queryComponent);
        }

        return $arrayCoder->encodeToArray(
            $queryComponent,
            function (QueryComponent $childComponent) {
                return $this->encode($childComponent);
            }
        );
    }

    /**
     * Decodes the passed `$encodedQueryComponent` array into a {@link QueryComponent}, which is returned.
     *
     * @param array $encodedQueryComponent
     * @param QueryComponentArrayDecodingPreprocessor[] $preprocessors (optional)
     * @return QueryComponent
     * @throws QueryComponentArrayDecodingException if the passed `$encodedQueryComponent` is invalid or it is
     *         not decodable.
     */
    public function decode(array $encodedQueryComponent, array $preprocessors = [])
    {
        if (!isset($encodedQueryComponent['type'])) {
            throw QueryComponentArrayDecodingException::missingField('type');
        }

        // Apply all preprocessors
        $preprocessedEncodedQueryComponent = array_reduce(
            $preprocessors,
            function (array $carry, QueryComponentArrayDecodingPreprocessor $preprocessor) {
                return $preprocessor->process($carry);
            },
            $encodedQueryComponent
        );

        // Select the array coder after applying the preprocessors, because they might change the component type
        $codableType = $preprocessedEncodedQueryComponent['type'];
        if (!isset($this->arrayCoders[$codableType])) {
            throw QueryComponentArrayDecodingException::componentNotDecodable($codableType);
        }

        return $this->arrayCoders[$codableType]->decodeFromArray(
            $preprocessedEncodedQueryComponent,
            function (array $encodedChildComponent) use ($preprocessors) {
                return $this->decode($encodedChildComponent, $preprocessors);
            }
        );
    }
}
