<?php
/**
 * This file is part of the DATAtourisme project.
 *
 *  @author Conjecto <contact@conjecto.com>
 *
 * For the full copyright and license information, please view the LICENSE file that was distributed with this source code.
 */

namespace Datatourisme\Api\Resolver\SparqlResolver\QueryBuilder;

use Datatourisme\Api\Resolver\SparqlResolver\Collection\FlatCollection;
use Datatourisme\Api\Resolver\SparqlResolver\Collection\UnionCollection;
use Datatourisme\Api\Resolver\SparqlResolver\Sparql\Triplet;
use Datatourisme\Api\Resolver\SparqlResolver\Utils\SparqlUtils;
use Youshido\GraphQL\Field\InputFieldInterface;
use Youshido\GraphQL\Type\AbstractType;

class QueryGenerator
{
    private $_queryBuilderFactory;
    private $_filterGenerator;
    private $_sortGenerator;

    /**
     * QueryFactory constructor.
     *
     * @param QueryBuilderFactory $queryBuilderFactory
     * @param FilterGenerator     $filterGenerator
     * @param SortGenerator       $sortGenerator
     */
    public function __construct(QueryBuilderFactory $queryBuilderFactory, FilterGenerator $filterGenerator, SortGenerator $sortGenerator)
    {
        $this->_queryBuilderFactory = $queryBuilderFactory;
        $this->_filterGenerator = $filterGenerator;
        $this->_sortGenerator = $sortGenerator;
    }

    /**
     * @param $entryPoint
     * @param $arguments
     * @param array $astFields
     *
     * @return QueryBuilder
     *
     * @throws \Exception
     */
    public function getQuery($arguments, array $astFields, InputFieldInterface $inputFieldDef, AbstractType $outputTypeDef)
    {
        $subject = '?res';

        $tabConstruct = $this->getConstruct($subject, $astFields, $outputTypeDef);
        $tabWhere = $this->getWhere($subject, $astFields, $outputTypeDef);
        $orderClause = $this->_sortGenerator->getSort($subject, $arguments, $outputTypeDef);
        $subQuery = $this->getSubQuery($subject, $arguments, $orderClause, $inputFieldDef);

        $queryBuilder = $this->_queryBuilderFactory->createQueryBuilder();
        $queryBuilder->construct($tabConstruct);
        $queryBuilder->where('{'.$subQuery.'}');
        $queryBuilder->addWhere($tabWhere);
        $queryBuilder->orderBy($orderClause->getConditions());
        $queryBuilder->offset('0');

//        dump($queryBuilder->getQuery());
//        die;

        return $queryBuilder;
    }

    /**
     * @param $subject
     * @param $arguments
     * @param OrderClause         $orderClause
     * @param InputFieldInterface $inputFieldDef
     *
     * @return QueryBuilder
     *
     * @throws \Exception
     */
    public function getSubQuery($subject, $arguments, OrderClause $orderClause, InputFieldInterface $inputFieldDef)
    {
        $uri = (isset($arguments['uri'])) ? $arguments['uri'] : null;
        $filters = (isset($arguments['filters'])) ? $arguments['filters'] : [];
        $size = (isset($arguments['size'])) ? $arguments['size'] : 10;
        $from = (isset($arguments['from'])) ? $arguments['from'] : 0;

        $setFilters = $this->_filterGenerator->getFilters($subject, $filters, $inputFieldDef, $uri);
        $setFilters->add($orderClause->getPatterns());

        $queryBuilder = $this->_queryBuilderFactory->createQueryBuilder();
        $queryBuilder->select('DISTINCT '.$subject);
        $queryBuilder->where($setFilters);
        $queryBuilder->orderBy($orderClause->getConditions());
        $queryBuilder->limit($size);
        $queryBuilder->offset($from);

        return $queryBuilder;
    }

    /**
     * @param $subject
     * @param array        $fields
     * @param AbstractType $type
     * @param int          $idx
     *
     * @return UnionCollection
     */
    private function getWhere($subject, $fields, AbstractType $type, $idx = 0)
    {
        $union = new UnionCollection();
        if (count($fields)) {
            if (1 == count($fields) && '_uri' == $fields[0]->getName()) {
                return $union;
            }
            $union->add(new Triplet($subject, '?p'.$idx, '?o'.$idx));

            foreach ($fields as $field) {
                if ($field->hasFields()) {
                    $set = new FlatCollection();

                    $fieldDef = $type->getField($field->getName());
                    $object = SparqlUtils::uniqVariable($subject, $fieldDef->getUri());

                    $union2 = $this->getWhere($object, $field->getFields(), $fieldDef->getType()->getItemType(), ++$idx);
                    if ($union2->count()) {
                        $set->add(new Triplet($subject, '<'.$fieldDef->getUri().'>', $object));
                        $set->add($union2);
                    }

                    $union->add($set);
                }
            }
        }

        return $union;
    }

    /**
     * @param $subject
     * @param array        $fields
     * @param AbstractType $type
     *
     * @return FlatCollection
     */
    private function getConstruct($subject, $fields, AbstractType $type, $idx = 0)
    {
        $set = new FlatCollection();

        if (!$idx) {
            $set->add(new Triplet($subject, 'a', '<urn:resource>'));
        }

        if (count($fields)) {
            if (1 == count($fields) && '_uri' == $fields[0]->getName()) {
                return $set;
            }
            $set->add(new Triplet($subject, '?p'.$idx, '?o'.$idx));
            foreach ($fields as $field) {
                if ($field->hasFields()) {
                    $fieldDef = $type->getField($field->getName());
                    $object = SparqlUtils::uniqVariable($subject, $fieldDef->getUri());
                    $set->add($this->getConstruct($object, $field->getFields(), $fieldDef->getType()->getItemType(), ++$idx));
                }
            }
        }

        return $set;
    }

    /**
     * @param $arguments
     * @param InputFieldInterface $inputFieldDef
     *
     * @return QueryBuilder
     *
     * @throws \Exception
     */
    public function getCountQuery($arguments, InputFieldInterface $inputFieldDef)
    {
        $subject = '?res';

        $uri = (isset($arguments['uri'])) ? $arguments['uri'] : null;
        $filters = (isset($arguments['filters'])) ? $arguments['filters'] : [];
        $setFilters = $this->_filterGenerator->getFilters($subject, $filters, $inputFieldDef, $uri);

        $queryBuilder = $this->_queryBuilderFactory->createQueryBuilder();
        $queryBuilder->select('(COUNT(DISTINCT '.$subject.') as ?c)');
        $queryBuilder->where($setFilters);

        return $queryBuilder;
    }
}
