<?php
declare(strict_types=1);

namespace FiloBlu\Esb\Test\Unit;

use FiloBlu\Esb\Core\Expression\Expression;
use FiloBlu\Esb\Core\Expression\Operator\EndsWithOperator;
use FiloBlu\Esb\Core\Expression\Operator\EqualOperator;
use FiloBlu\Esb\Core\Expression\Operator\GreatEqualThanOperator;
use FiloBlu\Esb\Core\Expression\Operator\GreatThanOperator;
use FiloBlu\Esb\Core\Expression\Operator\InOperator;
use FiloBlu\Esb\Core\Expression\Operator\LessThanEqualOperator;
use FiloBlu\Esb\Core\Expression\Operator\LessThanOperator;
use FiloBlu\Esb\Core\Expression\Operator\NotEndsWithOperator;
use FiloBlu\Esb\Core\Expression\Operator\NotEqualOperator;
use FiloBlu\Esb\Core\Expression\Operator\NotInOperator;
use FiloBlu\Esb\Core\Expression\Operator\NotStartsWithOperator;
use FiloBlu\Esb\Core\Expression\Operator\StartsWithOperator;
use FiloBlu\Esb\Core\Expression\Operator\StrictEqualOperator;
use FiloBlu\Esb\Core\Expression\VariableExpander;
use Magento\Framework\DataObject;
use Magento\Framework\TestFramework\Unit\Helper\ObjectManager;
use PHPUnit\Framework\TestCase;

/**
 * Class ExpressionTest
 * @package FiloBlu\Esb\Test\Unit
 */
class ExpressionTest extends TestCase
{
    /**
     * @var ObjectManager
     */
    protected $objectManager;

    /**
     * @var Expression
     */
    protected $expression;


    public function setUp(): void
    {
        parent::setUp();
        $this->objectManager = new ObjectManager($this);
        /** @var Expression $expression */
        $this->expression = $this->objectManager->getObject(Expression::class, [
            'variableExpander' => $this->objectManager->getObject(VariableExpander::class),
            'operators'        => [
                'in'          => $this->objectManager->getObject(InOperator::class),
                'nin'         => $this->objectManager->getObject(NotInOperator::class),
                'eq'          => $this->objectManager->getObject(EqualOperator::class),
                'seq'         => $this->objectManager->getObject(StrictEqualOperator::class),
                'lt'          => $this->objectManager->getObject(LessThanOperator::class),
                'lte'         => $this->objectManager->getObject(LessThanEqualOperator::class),
                'gt'          => $this->objectManager->getObject(GreatThanOperator::class),
                'gte'         => $this->objectManager->getObject(GreatEqualThanOperator::class),
                'neq'         => $this->objectManager->getObject(NotEqualOperator::class),
                'startswith'  => $this->objectManager->getObject(StartsWithOperator::class),
                'nstartswith' => $this->objectManager->getObject(NotStartsWithOperator::class),
                'endswith'    => $this->objectManager->getObject(EndsWithOperator::class),
                'nendswith'   => $this->objectManager->getObject(NotEndsWithOperator::class)
            ]
        ]);
    }

    /**
     * @covers       \FiloBlu\Esb\Core\Expression\Expression
     * @covers       \FiloBlu\Esb\Core\Expression\ExpressionInterface
     * @covers       \FiloBlu\Esb\Core\Expression\Operator\EndsWithOperator
     * @covers       \FiloBlu\Esb\Core\Expression\Operator\EqualOperator
     * @param string $operator
     * @param $field
     * @param $value
     * @param DataObject $data
     * @param int $expectedResult
     *
     * @dataProvider expressionsProvider
     */
    public function testExpressions(string $operator, $field, $value, DataObject $data, int $expectedResult)
    {
        $result = $this->expression
            ->setOperator($operator)
            ->setField($field)
            ->setValue($value)
            ->evaluate($data);

        static::assertTrue($expectedResult === $result);
    }


    /**
     * @return array[]
     */
    public function expressionsProvider()
    {
        return array_merge(
            [],
            self::inProvider(),
            self::notInProvider(),
            self::eqProvider(),
            self::seqProvider(),
            self::ltProvider(),
            self::lteProvider(),
            self::gtProvider(),
            self::gteProvider(),
            self::neqProvider(),
            self::startswithProvider(),
            self::nstartswithProvider(),
            self::endswithProvider()
        );
    }


    /**
     * @return array[]
     */
    public static function inProvider()
    {
        return [
            ['in', 'a', 'b', new DataObject(), 0],
            ['in', 'a', 'a', new DataObject(), 1],
            ['in', '{a}', '{b}', new DataObject(['a' => 1, 'b' => [1]]), 1],
            ['in', '{a}', '{b}', new DataObject(['a' => 1, 'b' => 1]), 1],
            ['in', '{a}', '{b}', new DataObject(['a' => '1', 'b' => 1]), 1]
        ];
    }

    /**
     * @return array[]
     */
    public static function notInProvider()
    {
        return [
            ['nin', 'a', 'b', new DataObject(), 1],
            ['nin', 'a', 'a', new DataObject(), 0],
            ['nin', '{a}', '{b}', new DataObject(['a' => 1, 'b' => [1]]), 0],
            ['nin', '{a}', '{b}', new DataObject(['a' => 1, 'b' => 1]), 0],
            ['nin', '{a}', '{b}', new DataObject(['a' => '1', 'b' => 1]), 0]
        ];
    }

    /**
     * @return array[]
     */
    public static function eqProvider()
    {
        return [
            ['eq', 'a', 'b', new DataObject(), 0],
            ['eq', 'a', 'a', new DataObject(), 1],
            ['eq', '{a}', '{b}', new DataObject(['a' => 1, 'b' => [1]]), 0],
            ['eq', '{a}', '{b}', new DataObject(['a' => 1, 'b' => 1]), 1],
            ['eq', '{a}', '{b}', new DataObject(['a' => '1', 'b' => 1]), 1]
        ];
    }

    /**
     * @return array[]
     */
    public static function seqProvider()
    {
        return [
            ['seq', 'a', 'b', new DataObject(), 0],
            ['seq', 'a', 'a', new DataObject(), 1],
            ['seq', '{a}', '{b}', new DataObject(['a' => 1, 'b' => [1]]), 0],
            ['seq', '{a}', '{b}', new DataObject(['a' => 1, 'b' => 1]), 1],
            ['seq', '{a}', '{b}', new DataObject(['a' => '1', 'b' => 1]), 0]
        ];
    }

    /**
     * @return array[]
     */
    public static function ltProvider()
    {
        return [
            ['lt', 'a', 'b', new DataObject(), 1],
            ['lt', 'a', 'a', new DataObject(), 0],
            ['lt', '{a}', '{b}', new DataObject(['a' => 1, 'b' => 1]), 0],
            ['lt', '{a}', '{b}', new DataObject(['a' => '1', 'b' => 1]), 0]
        ];
    }

    /**
     * @return array[]
     */
    public static function lteProvider()
    {
        return [
            ['lte', 'a', 'b', new DataObject(), 1],
            ['lte', 'a', 'a', new DataObject(), 1],
            ['lte', '{a}', '{b}', new DataObject(['a' => 1, 'b' => 1]), 1],
            ['lte', '{a}', '{b}', new DataObject(['a' => '1', 'b' => 1]), 1]
        ];
    }

    /**
     * @return array[]
     */
    public static function gtProvider()
    {
        return [
            ['gt', 'a', 'b', new DataObject(), 0],
            ['gt', 'a', 'a', new DataObject(), 0],
            ['gt', '{a}', '{b}', new DataObject(['a' => 1, 'b' => 1]), 0],
            ['gt', '{a}', '{b}', new DataObject(['a' => '1', 'b' => 1]), 0],
            ['gt', 3, 2, new DataObject(), 1]
        ];
    }

    /**
     * @return array[]
     */
    public static function gteProvider()
    {
        return [
            ['gte', 'a', 'b', new DataObject(), 0],
            ['gte', 'a', 'a', new DataObject(), 1],
            ['gte', '{a}', '{b}', new DataObject(['a' => 1, 'b' => 1]), 1],
            ['gte', '{a}', '{b}', new DataObject(['a' => '1', 'b' => 1]), 1],
            ['gte', 3, 2, new DataObject(), 1],
            ['gte', 3, 3, new DataObject(), 1]
        ];
    }

    /**
     * @return array[]
     */
    public static function neqProvider()
    {
        return [
            ['neq', 'a', 'b', new DataObject(), 1],
            ['neq', 'a', 'a', new DataObject(), 0],
            ['neq', '{a}', '{b}', new DataObject(['a' => 1, 'b' => [1]]), 1],
            ['neq', '{a}', '{b}', new DataObject(['a' => 1, 'b' => 1]), 0],
            ['neq', '{a}', '{b}', new DataObject(['a' => '1', 'b' => 1]), 0]
        ];
    }

    /**
     * @return array[]
     */
    public static function startswithProvider()
    {
        return
            [
                ['startswith', 'a', 'b', new DataObject(), 0],
                ['startswith', 'a', 'a', new DataObject(), 1],
                ['startswith', 'abcdefg', 'abc', new DataObject(), 1],
                ['startswith', 'abc', 'abcdefg', new DataObject(), 0]
            ];
    }

    /**
     * @return array[]
     */
    public static function nstartswithProvider()
    {
        return
            [
                ['nstartswith', 'a', 'b', new DataObject(), 1],
                ['nstartswith', 'a', 'a', new DataObject(), 0],
                ['nstartswith', 'abcdefg', 'abc', new DataObject(), 0],
                ['nstartswith', 'abc', 'abcdefg', new DataObject(), 1]
            ];
    }

    /**
     * @return array[]
     */
    public static function endswithProvider()
    {
        return
            [
                ['endswith', 'a', 'b', new DataObject(), 0],
                ['endswith', 'a', 'a', new DataObject(), 1],
                ['endswith', 'abcdefg', 'efg', new DataObject(), 1],
                ['endswith', 'abc', 'abcdefg', new DataObject(), 0]
            ];
    }
}
