<?php

namespace ApiQL;

use Doctrine\DBAL\Query\QueryBuilder;
use ApiQL\Language\AbstractLanguage;
use ApiQL\Language\ApiQueryBuilder;
use ApiQL\Language\LanguageFactory;

/**
 * Class ApiQL
 *
 * @package ApiQL
 */
class ApiQL extends AbstractLanguage
{
    public function __construct(array $expression, QueryBuilder $builder, array $specs = [])
    {
        $this->expression = $expression;
        self::$builder = new ApiQueryBuilder($builder);
        self::$specs = $specs;
    }

    /**
     * Execute specification root expression
     */
    public function execute()
    {
        $this->executeFields();
        $this->executeOffset();
        $this->executeLimit();
        $this->executeOrder();
        $this->executeWhere();
    }

    /**
     * Execute fields part of expression
     */
    private function executeFields()
    {
        $isProjection = false;
        if (array_key_exists('fields', $this->expression)) {
            $isProjection = true;
            $fieldsName = 'fields';
        } elseif (array_key_exists('select', $this->expression)) {
            $isProjection = true;
            $fieldsName = 'select';
        }
        if ($isProjection) {
            $fields = !is_array($this->expression[$fieldsName]) ? explode(',', $this->expression[$fieldsName]) :
                        $this->expression[$fieldsName];
            if (!empty($fields)) {
                foreach ($fields as $field) {
                    self::$builder->addSelect($field);
                }
            } else {
                self::$builder->addSelect('*');
            }
            unset($this->expression[$fieldsName]);
        } else {
            self::$builder->addSelect('*');
        }
    }

    /**
     * Execute offset part of expression
     */
    private function executeOffset()
    {
        if (array_key_exists('offset', $this->expression)) {
            self::$builder->setFirstResult($this->expression['offset']);
            unset($this->expression['offset']);
        }
    }

    /**
     * Execute limit part of expression
     */
    private function executeLimit()
    {
        if (array_key_exists('limit', $this->expression)) {
            self::$builder->setMaxResults($this->expression['limit']);
            unset($this->expression['limit']);
        }
    }

    /**
     * Execute fields part of expression
     */
    private function executeOrder()
    {
        if (array_key_exists('order', $this->expression)) {
            $fields = !is_array($this->expression['order']) ? explode(',', $this->expression['order']) :
                        $this->expression['order'];
            if (!empty($fields)) {
                foreach ($fields as $field) {
                    $pair = explode(':', $field);
                    $fieldName = $pair[0];
                    $orderType = (count($pair) == 2) ? $pair[1] : null;
                    self::$builder->addOrderBy($fieldName, $orderType);
                }
            }
            unset($this->expression['order']);
        }
    }

    /**
     * Execute where part of expression
     */
    private function executeWhere()
    {
        if (!empty($this->expression)) {
            if (array_key_exists('where', $this->expression)) {
                $expression = $this->expression['where'];
                $operator = array_key_first($expression);
                $data_ = $expression[$operator];
                if (self::isLogicOperator($operator)) {
                    $interpreters = array_map(function ($item) {
                        return LanguageFactory::build($item);
                    }, $data_);
                    self::$builder->andWhere($this->method($operator, ...$interpreters));
                } elseif (self::isRelationOperator($operator) || self::isSpecification($operator)) {
                    $interpreter = LanguageFactory::build($expression);
                    self::$builder->andWhere($interpreter->execute());
                }
            } else {
                if (array_key_exists(self::SPEC, $this->expression)) {
                    $specName = $this->expression[self::SPEC];
                    unset($this->expression[self::SPEC]);
                    $this->expression = array_merge([self::SPEC => $specName], $this->expression);
                    $interpreter = LanguageFactory::build($this->expression);
                    self::$builder->andWhere($interpreter->execute());
                } else {
                    $logic = self::getLogicOperator($this->expression);
                    $rel = self::getRelationOperator($this->expression);
                    if (array_key_exists(self::RELATION, $this->expression)) {
                        unset($this->expression[self::RELATION]);
                    }
                    if (array_key_exists(self::LOGIC, $this->expression)) {
                        unset($this->expression[self::LOGIC]);
                    }
                    $expression = [$logic => []];
                    foreach ($this->expression as $key => $value) {
                        $expression[$logic][] = [$rel => [$key => $value]];
                    }
                    $interpreters = array_map(function ($item) {
                        return LanguageFactory::build($item);
                    }, $expression[$logic]);
                    self::$builder->andWhere($this->method($logic, ...$interpreters));
                }
            }
        }
    }
}