<?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\QueryBuilder;

class BooleanCompositionQueryComponent implements QueryComponent
{
    const BOOLEAN_OPERATOR_AND = 'AND';
    const BOOLEAN_OPERATOR_OR = 'OR';

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

    /**
     * @var QueryComponent[]
     */
    protected $compositionComponents = [];

    /**
     * @param string $booleanOperator
     * @param QueryComponent[] $compositionComponents
     */
    public function __construct($booleanOperator, array $compositionComponents)
    {
        $this->booleanOperator = $booleanOperator;
        $this->compositionComponents = $compositionComponents;
    }

    /**
     * @param QueryComponent ...$conjunctionComponents
     * @return self
     */
    public static function createConjunction(QueryComponent ...$conjunctionComponents)
    {
        return new self(self::BOOLEAN_OPERATOR_AND, $conjunctionComponents);
    }

    /**
     * @param QueryComponent ...$disjunctionComponents
     * @return self
     */
    public static function createDisjunction(QueryComponent ...$disjunctionComponents)
    {
        return new self(self::BOOLEAN_OPERATOR_OR, $disjunctionComponents);
    }

    /**
     * @return string
     */
    public function getBooleanOperator()
    {
        return $this->booleanOperator;
    }

    /**
     * @param string $booleanOperator
     */
    public function setBooleanOperator($booleanOperator)
    {
        $this->booleanOperator = $booleanOperator;
    }

    /**
     * @return QueryComponent[]
     */
    public function getCompositionComponents()
    {
        return $this->compositionComponents;
    }

    /**
     * @param QueryComponent[] $compositionComponents
     */
    public function setCompositionComponents(array $compositionComponents)
    {
        $this->compositionComponents = $compositionComponents;
    }

    /**
     * @param QueryComponent ...$components
     */
    public function addCompositionComponents(QueryComponent ...$components)
    {
        $this->compositionComponents = array_merge($this->compositionComponents, $components);
    }

    /**
     * @inheritdoc
     */
    public function createQueryString()
    {
        if (count($this->compositionComponents) === 0) {
            // An empty composition is always 'true'
            return '1';
        }

        $compositionComponentQueries = array_map(
            function (QueryComponent $component) {
                return sprintf('(%s)', $component->createQueryString());
            },
            $this->compositionComponents
        );
        $operator = sprintf(' %s ', $this->booleanOperator);
        $queryString = implode($operator, $compositionComponentQueries);

        return sprintf('(%s)', $queryString);
    }

    /**
     * @inheritdoc
     */
    public function getRequiredTables()
    {
        if (count($this->compositionComponents) === 0) {
            return [];
        }

        $requiredTables = array_map(
            function (QueryComponent $component) {
                return $component->getRequiredTables();
            },
            $this->compositionComponents
        );

        return array_unique(array_merge(...$requiredTables));
    }

    /**
     * Recursively creates a flat map of the receiving component and all its composition components, using the passed
     * `$componentKey` for indexing the receiving component in the map. All added composition components' keys are
     * prefixed with the receiving component's key.
     *
     * @param string $componentKey The key used for indexing the receicing component.
     * @return array An associative array of components.
     */
    public function flatMap($componentKey)
    {
        $componentMap = [$componentKey => $this];
        foreach ($this->getCompositionComponents() as $index => $childComponent) {
            $childComponentKey = $componentKey . '_' . $index;
            if ($childComponent instanceof self) {
                $componentMap = array_merge(
                    $componentMap,
                    $childComponent->flatMap($childComponentKey)
                );
            } else {
                $componentMap[$childComponentKey] = $childComponent;
            }
        }

        return $componentMap;
    }
}
