<?php

namespace Jackalope\Query\QOM;

use InvalidArgumentException;
use Jackalope\ObjectManager;
use Jackalope\FactoryInterface;
use PHPCR\Query\QOM\QueryObjectModelConstantsInterface;
use PHPCR\Query\QOM\QueryObjectModelFactoryInterface;
use PHPCR\Query\QOM\SourceInterface;
use PHPCR\Query\QOM\ConstraintInterface;
use PHPCR\Query\QOM\JoinConditionInterface;
use PHPCR\Query\QOM\DynamicOperandInterface;
use PHPCR\Query\QOM\StaticOperandInterface;
use PHPCR\Query\QOM\PropertyValueInterface;

/**
 * {@inheritDoc}
 *
 * @license http://www.apache.org/licenses Apache License Version 2.0, January 2004
 * @license http://opensource.org/licenses/MIT MIT License
 *
 * @api
 */
class QueryObjectModelFactory implements QueryObjectModelFactoryInterface
{
    /**
     * The factory to instantiate objects
     *
     * @var FactoryInterface
     */
    protected $factory;

    /**
     * @var ObjectManager
     */
    protected $objectManager;

    /**
     * Create the query object model factory - get this from the QueryManager
     *
     * @param FactoryInterface $factory       the object factory
     * @param ObjectManager    $objectManager only used to create the query (can
     *      be omitted if you do not want to execute the query but just use it
     *      with a parser)
     */
    public function __construct(FactoryInterface $factory, ObjectManager $objectManager = null)
    {
        $this->factory = $factory;
        $this->objectManager = $objectManager;
    }

    /**
     * {@inheritDoc}
     *
     * @api
     */
    public function createQuery(
        SourceInterface $source,
        ConstraintInterface $constraint = null,
        array $orderings = [],
        array $columns = []
    ) {
        return $this->factory->get(
            QueryObjectModel::class,
            [$this->objectManager, $source, $constraint, $orderings, $columns]
        );
    }

    // TODO: should we use the factory ->get here? but this would mean all of them need to expect the factory as first parameter
    // or refactor the factory to make the first param optional.

    /**
     * {@inheritDoc}
     *
     * @api
     */
    public function selector($selectorName, $nodeTypeName)
    {
        return new Selector($selectorName, $nodeTypeName);
    }

    /**
     * {@inheritDoc}
     *
     * @api
     */
    public function join(SourceInterface $left, SourceInterface $right, $joinType, JoinConditionInterface $joinCondition)
    {
        return new Join($left, $right, $joinType, $joinCondition);
    }

    /**
     * {@inheritDoc}
     *
     * @api
     */
    public function equiJoinCondition($selector1Name, $property1Name, $selector2Name, $property2Name)
    {
        return new EquiJoinCondition($selector1Name, $property1Name, $selector2Name, $property2Name);
    }

    /**
     * {@inheritDoc}
     *
     * @api
     */
    public function sameNodeJoinCondition($selector1Name, $selector2Name, $selector2Path = null)
    {
        return new SameNodeJoinCondition($selector1Name, $selector2Name, $selector2Path);
    }

    /**
     * {@inheritDoc}
     *
     * @api
     */
    public function childNodeJoinCondition($childSelectorName, $parentSelectorName)
    {
        return new ChildNodeJoinCondition($childSelectorName, $parentSelectorName);
    }

    /**
     * {@inheritDoc}
     *
     * @api
     */
    public function descendantNodeJoinCondition($descendantSelectorName, $ancestorSelectorName)
    {
        return new DescendantNodeJoinCondition($descendantSelectorName, $ancestorSelectorName);
    }

    /**
     * {@inheritDoc}
     *
     * @api
     */
    public function andConstraint(ConstraintInterface $constraint1, ConstraintInterface $constraint2)
    {
        return new AndConstraint($constraint1, $constraint2);
    }

    /**
     * {@inheritDoc}
     *
     * @api
     */
    public function orConstraint(ConstraintInterface $constraint1, ConstraintInterface $constraint2)
    {
        return new OrConstraint($constraint1, $constraint2);
    }

    /**
     * {@inheritDoc}
     *
     * @api
     */
    public function notConstraint(ConstraintInterface $constraint)
    {
        return new NotConstraint($constraint);
    }

    /**
     * {@inheritDoc}
     *
     * @api
     */
    public function comparison(DynamicOperandInterface $operand1, $operator, StaticOperandInterface $operand2)
    {
        return new ComparisonConstraint($operand1, $operator, $operand2);
    }

    /**
     * {@inheritDoc}
     *
     * @api
     */
    public function propertyExistence($selectorName, $propertyName)
    {
        return new PropertyExistence($selectorName, $propertyName);
    }

    /**
     * {@inheritDoc}
     *
     * @throws InvalidArgumentException
     *
     * @api
     */
    public function fullTextSearch($selectorName, $propertyName, $fullTextSearchExpression)
    {
        return new FullTextSearchConstraint($selectorName, $propertyName, $fullTextSearchExpression);
    }

    /**
     * {@inheritDoc}
     *
     * @api
     */
    public function sameNode($selectorName, $path)
    {
        return new SameNode($selectorName, $path);
    }

    /**
     * {@inheritDoc}
     *
     * @api
     */
    public function childNode($selectorName, $path)
    {
        return new ChildNodeConstraint($selectorName, $path);
    }

    /**
     * {@inheritDoc}
     *
     * @api
     */
    public function descendantNode($selectorName, $path)
    {
        return new DescendantNodeConstraint($selectorName, $path);
    }

    /**
     * {@inheritDoc}
     *
     * @throws InvalidArgumentException
     *
     * @api
     */
    public function propertyValue($selectorName, $propertyName)
    {
        return new PropertyValue($selectorName, $propertyName);
    }

    /**
     * {@inheritDoc}
     *
     * @api
     */
    public function length(PropertyValueInterface $propertyValue)
    {
        return new Length($propertyValue);
    }

    /**
     * {@inheritDoc}
     *
     * @throws InvalidArgumentException
     *
     * @api
     */
    public function nodeName($selectorName)
    {
        return new NodeName($selectorName);
    }

    /**
     * {@inheritDoc}
     *
     * @api
     */
    public function nodeLocalName($selectorName)
    {
        return new NodeLocalName($selectorName);
    }

    /**
     * {@inheritDoc}
     *
     * @throws InvalidArgumentException
     *
     * @api
     */
    public function fullTextSearchScore($selectorName)
    {
        return new FullTextSearchScore($selectorName);
    }

    /**
     * {@inheritDoc}
     *
     * @api
     */
    public function lowerCase(DynamicOperandInterface $operand)
    {
        return new LowerCase($operand);
    }

    /**
     * {@inheritDoc}
     *
     * @api
     */
    public function upperCase(DynamicOperandInterface $operand)
    {
        return new UpperCase($operand);
    }

    /**
     * {@inheritDoc}
     *
     * @api
     */
    public function bindVariable($bindVariableName)
    {
        return new BindVariableValue($bindVariableName);
    }

    /**
     * {@inheritDoc}
     *
     * @api
     */
    public function literal($literalValue)
    {
        return new Literal($literalValue);
    }

    /**
     * {@inheritDoc}
     *
     * @api
     */
    public function ascending(DynamicOperandInterface $operand)
    {
        return new Ordering($operand, QueryObjectModelConstantsInterface::JCR_ORDER_ASCENDING);
    }

    /**
     * {@inheritDoc}
     *
     * @api
     */
    public function descending(DynamicOperandInterface $operand)
    {
        return new Ordering($operand, QueryObjectModelConstantsInterface::JCR_ORDER_DESCENDING);
    }

    /**
     * {@inheritDoc}
     *
     * @throws \InvalidArgumentException
     *
     * @api
     */
    public function column($selectorName, $propertyName = null, $columnName = null)
    {
        return new Column($selectorName, $propertyName, $columnName);
    }
}
