<?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 QueryBuilder
{
    /**
     * @var SelectQueryComponent[]
     */
    protected $selectComponents = [];

    /**
     * @var string|null
     */
    protected $fromTableName;

    /**
     * @var AbstractJoinQueryComponent[]
     */
    protected $joinComponents = [];

    /**
     * @var QueryComponent|null
     */
    protected $whereComponent;

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

    /**
     * @param SelectQueryComponent ...$selectComponents
     * @return self
     */
    public function select(SelectQueryComponent ...$selectComponents)
    {
        $this->selectComponents = $selectComponents;

        return $this;
    }

    /**
     * @param SelectQueryComponent ...$selectComponents
     * @return self
     */
    public function addSelect(SelectQueryComponent ...$selectComponents)
    {
        $this->selectComponents = array_merge($this->selectComponents, $selectComponents);

        return $this;
    }

    /**
     * @param string $fromTableName
     * @return self
     */
    public function from($fromTableName)
    {
        $this->fromTableName = trim($fromTableName);

        return $this;
    }

    /**
     * @param AbstractJoinQueryComponent ...$joinComponents
     * @return self
     */
    public function join(AbstractJoinQueryComponent ...$joinComponents)
    {
        $this->joinComponents = array_merge($this->joinComponents, $joinComponents);

        return $this;
    }

    /**
     * @param QueryComponent $whereComponent
     * @return self
     */
    public function where(QueryComponent $whereComponent)
    {
        $this->whereComponent = $whereComponent;

        return $this;
    }

    /**
     * @param GroupByQueryComponent ...$groupByComponents
     * @return self
     */
    public function groupBy(GroupByQueryComponent ...$groupByComponents)
    {
        $this->groupByComponents = $groupByComponents;

        return $this;
    }

    /**
     * Constructs and returns the SQL string based in the internal state of the query builder. The returned SQL string
     * might be executed in the database without further processing.
     *
     * @return string
     * @throws QueryBuilderException if the called query biulder is incomplete.
     */
    public function getSql()
    {
        // SELECT
        if (count($this->selectComponents) === 0) {
            throw QueryBuilderException::missingSelectComponent();
        }
        $sql = "SELECT\n\t" . implode(",\n\t", self::createQueryComponentStrings($this->selectComponents));

        // FROM
        if (empty($this->fromTableName)) {
            throw QueryBuilderException::missingFromTableName();
        }
        $sql .= sprintf("\nFROM `%s`", $this->fromTableName);

        // JOIN
        if (count($this->joinComponents) > 0) {
            $sql .= "\n" . implode("\n", self::createQueryComponentStrings($this->joinComponents));
        }

        // WHERE
        if ($this->whereComponent) {
            $sql .= "\nWHERE " . $this->whereComponent->createQueryString();
        }

        // GROUP BY
        if (count($this->groupByComponents) > 0) {
            $sql .= "\nGROUP BY\n\t" . implode(",\n\t", self::createQueryComponentStrings($this->groupByComponents));
        }

        return $sql;
    }

    /**
     * @return string[] The names of all tables that are required by any SELECT, GROUP BY or WHERE component contained
     *     in the query builder.
     */
    public function getRequiredTableNames()
    {
        $componentsRequiringTables = array_merge($this->selectComponents, $this->groupByComponents);
        if ($this->whereComponent) {
            $componentsRequiringTables[] = $this->whereComponent;
        }
        if (count($componentsRequiringTables) === 0) {
            return [];
        }

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

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

    /**
     * @return string[] The names of all tables that are joined within the query builder. This includes the table name
     *     set by calling {@link self::from()}.
     */
    public function getJoinedTableNames()
    {
        $joinedTableNames = array_map(
            function (AbstractJoinQueryComponent $component) {
                return $component->getJoiningTableName();
            },
            $this->joinComponents
        );
        if ($this->fromTableName) {
            array_unshift($joinedTableNames, $this->fromTableName);
        }

        return $joinedTableNames;
    }

    /**
     * @param QueryComponent[] $components
     * @return string[]
     */
    private static function createQueryComponentStrings(array $components)
    {
        return array_map(
            function (QueryComponent $component) {
                return $component->createQueryString();
            },
            $components
        );
    }
}
