<?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\Filter\Conditional\Eq;
use Datatourisme\Api\Resolver\SparqlResolver\Filter\Conditional\GeoBounding;
use Datatourisme\Api\Resolver\SparqlResolver\Filter\Conditional\GeoDistance;
use Datatourisme\Api\Resolver\SparqlResolver\Filter\Conditional\Gt;
use Datatourisme\Api\Resolver\SparqlResolver\Filter\Conditional\Gte;
use Datatourisme\Api\Resolver\SparqlResolver\Filter\Conditional\In;
use Datatourisme\Api\Resolver\SparqlResolver\Filter\Conditional\Lt;
use Datatourisme\Api\Resolver\SparqlResolver\Filter\Conditional\Lte;
use Datatourisme\Api\Resolver\SparqlResolver\Filter\Conditional\Ne;
use Datatourisme\Api\Resolver\SparqlResolver\Filter\Conditional\Nin;
use Datatourisme\Api\Resolver\SparqlResolver\Filter\Conditional\Text;
use Datatourisme\Api\Resolver\SparqlResolver\Filter\ConditionalFilterInterface;
use Datatourisme\Api\Resolver\SparqlResolver\Filter\FilterInterface;
use Datatourisme\Api\Resolver\SparqlResolver\Filter\Logical\AndOperator;
use Datatourisme\Api\Resolver\SparqlResolver\Filter\Logical\NotOperator;
use Datatourisme\Api\Resolver\SparqlResolver\Filter\Logical\OrOperator;
use Datatourisme\Api\Resolver\SparqlResolver\Filter\LogicalFilterInterface;
use Datatourisme\Api\Resolver\SparqlResolver\Sparql\Functions\Values;
use Datatourisme\Api\Resolver\SparqlResolver\Sparql\Triplet;
use Datatourisme\Api\Resolver\SparqlResolver\Utils\SparqlUtils;
use Youshido\GraphQL\Field\InputFieldInterface;
use Youshido\GraphQL\Type\ListType\ListType;

class FilterGenerator
{
    private $classMap;

    /**
     * QueryFactory constructor.
     */
    public function __construct()
    {
        $this->addFilterClass(new Eq())
            ->addFilterClass(new Gte())
            ->addFilterClass(new Gt())
            ->addFilterClass(new Lte())
            ->addFilterClass(new Lt())
            ->addFilterClass(new Ne())
            ->addFilterClass(new Text())
            ->addFilterClass(new In())
            ->addFilterClass(new Nin())
            ->addFilterClass(new AndOperator())
            ->addFilterClass(new OrOperator())
            ->addFilterClass(new NotOperator())
            ->addFilterClass(new GeoDistance())
            ->addFilterClass(new GeoBounding());
    }

    /**
     * @param $subject
     * @param $filters
     * @param InputFieldInterface $inputFieldDef
     * @param bool                $uri
     *
     * @return FlatCollection
     */
    public function getFilters($subject, $filters, InputFieldInterface $inputFieldDef, $uri = false)
    {
        $set = new FlatCollection();

        $filterType = $inputFieldDef->getType()->getItemType();
        $set->add(new Triplet($subject, 'a', '<'.$filterType->getUri().'>'));
        foreach ($filters as $filter) {
            $set->add($this->consumeFilters($subject, $filter, $inputFieldDef));
        }

        if ($uri) {
            $set->add(new Values($subject, ['<'.$uri.'>']));
        }

        return $set;
    }

    /**
     * @param $subject
     * @param $filter
     * @param InputFieldInterface $fieldDef
     *
     * @return FlatCollection
     */
    public function consumeFilters($subject, $filter, InputFieldInterface $fieldDef)
    {
        $set = new FlatCollection();

        foreach ($filter as $fieldName => $value) {
            $nextFieldDef = $this->getSubFieldDef($fieldDef, $fieldName);
            $filter = $this->getFilter($nextFieldDef->getName());

            // is a filter ?
            if ($filter) {
                // logical filter
                if ($filter instanceof LogicalFilterInterface) {
                    $sets = array();
                    foreach ($value as $subvalue) {
                        $sets[] = $this->consumeFilters($subject, $subvalue, $fieldDef);
                    }
                    $set->add($filter->generate($sets));
                }

                // conditional filter
                if ($filter instanceof ConditionalFilterInterface) {
                    $set->add($filter->generate($subject, $fieldDef, $value));
                }
            } else {
                // not a filter
                //$object = SparqlUtils::uniqVariable($subject, $nextFieldDef->getUri());
                $object = uniqid('?');
                $set->add(new Triplet($subject, '<'.$nextFieldDef->getUri().'>', $object));
                $set->add($this->consumeFilters($object, $value, $nextFieldDef));
            }
        }

        return $set;
    }

    /**
     * @param $field
     *
     * @return InputFieldInterface
     */
    protected function getSubFieldDef(InputFieldInterface $field, string $name)
    {
        $filterType = $field->getType();
        if ($filterType instanceof ListType) {
            $filterType = $filterType->getItemType();
        }

        return $filterType->getField($name);
    }

    /**
     * @param $action
     *
     * @return ConditionalFilterInterface
     */
    public function getFilter($action)
    {
        if (isset($this->classMap[$action])) {
            return $this->classMap[$action];
        }

        return null;
    }

    /**
     * @param $instance
     *
     * @return $this
     */
    public function addFilterClass(FilterInterface $instance)
    {
        $this->classMap[$instance->getName()] = $instance;

        return $this;
    }
}
