<?php
/**
 * Copyright 2018 Kacper Donat
 *
 * @author Kacper "Kadet" Donat <kacper@kadet.net>
 *
 * Full license available in separate LICENSE file
 */

use Kadet\Functional as f;
use const Kadet\Functional\_;

class CurriedFunctionTest extends \PHPUnit\Framework\TestCase
{
    /**
     * @var \Kadet\Functional\Utils\CurriedFunction
     */
    private $function;
    private $variadic;
    private $optional;

    protected function setUp()
    {
        $this->function = function ($a, $b, $c, $d) {
            return $a . $b . $c . $d;
        };

        $this->variadic = function (...$args) {
            return implode('', $args);
        };

        $this->optional = function($a, $b = 'b', $c = 'c') {
            return $a . $b . $c;
        };
    }

    public function testCallAfterAllArguments()
    {
        $f = f\curry($this->function);

        $this->assertSame('abcd', $f('a')('b')('c')('d'));
        $this->assertSame('abcd', $f('a')('b', 'c')('d'));
        $this->assertSame('abcd', $f('a')('b', 'c', 'd'));
        $this->assertSame('abcd', $f('a', 'b')('c', 'd'));
        $this->assertSame('abcd', $f('a', 'b', 'c')('d'));
        $this->assertSame('abcd', $f('a', 'b', 'c', 'd'));
    }

    public function testPartialApplication()
    {
        $f = f\curry($this->function);
        $g = $f('a', 'b');

        $this->assertSame(['a', 'b'], $g->getArguments());
    }

    public function testPartialApplicationWithPlaceholder()
    {
        $f = f\curry($this->function);
        $g = $f('a', _, 'c');

        $this->assertInstanceOf(f\Utils\PartiallyAppliedFunction::class, $g);
        $this->assertSame([0 => 'a', 2 => 'c'], $g->getArguments());
        $this->assertSame('abcd', $g('b', 'd'));
    }

    public function testOptionalArgumentsCall()
    {
        $f = f\curry($this->optional);

        $this->assertSame('abc', $f('a'));
        $this->assertSame('aBc', $f('a', 'B'));
        $this->assertSame('aBC', $f('a', 'B', 'C'));
    }

    public function testVariadicThreshold()
    {
        $f = f\curry($this->variadic, 3);
        $g = $f('a', _, 'c');

        $this->assertSame('abc', $f('a', 'b', 'c'));

        $this->assertInstanceOf(f\Utils\PartiallyAppliedFunction::class, $g);
        $this->assertSame([0 => 'a', 2 => 'c'], $g->getArguments());
        $this->assertSame('abc', $g('b'));
    }

    public function testForceApply()
    {
        $f = f\curry($this->variadic);
        $g = $f->apply('a', 'b');

        $this->assertSame('', $f());
        $this->assertSame('abc', $f('a', 'b', 'c'));

        $this->assertInstanceOf(f\Utils\PartiallyAppliedFunction::class, $g);
        $this->assertSame(['a', 'b'], $g->getArguments());
        $this->assertSame('abc', $g('c'));
    }

    public function testForceCall()
    {
        $f = f\curry($this->variadic, 10);
        $g = $f->apply('a', 'b');

        $this->assertSame('', $f->call());
        $this->assertSame('abc', $f->call('a', 'b', 'c'));

        $this->assertInstanceOf(f\Utils\PartiallyAppliedFunction::class, $g);
        $this->assertSame(['a', 'b'], $g->getArguments());
        $this->assertSame('abc', $g->call('c'));
    }

    public function testForceCallIfNoArgs()
    {
        $f = f\curry($this->variadic, f\Utils\CurriedFunction::INF);

        $this->assertSame('abcdef', $f('a', 'b', 'c')('d', 'e')('f')());
    }

    public function testUncurry()
    {
        $f = f\curry($this->variadic, 10);
        $f = $f('a', 'b');
        $f = $f->uncurry();

        $this->assertInstanceOf(f\Utils\PartiallyAppliedFunction::class, $f);
        $this->assertSame(['a', 'b'], $f->getArguments());
        $this->assertSame('abc', $f('c'));
    }

    public function testUncurryAlias()
    {
        $f = f\curry($this->variadic, 10);
        $f = $f('a', 'b');
        $f = f\uncurry($f);

        $this->assertInstanceOf(f\Utils\PartiallyAppliedFunction::class, $f);
        $this->assertSame(['a', 'b'], $f->getArguments());
        $this->assertSame('abc', $f('c'));
    }

    public function testThresholdChange()
    {
        $f = f\curry($this->variadic, 10);
        $g = $f->apply('a', 'b')->withThreshold(4);

        $this->assertSame(4, $g->getThreshold());

        $this->assertInstanceOf(f\Utils\PartiallyAppliedFunction::class, $g);
        $this->assertInstanceOf(f\Utils\PartiallyAppliedFunction::class, $g('c'));
        $this->assertSame('abcd', $g('c', 'd'));
    }

    public function testThreshold()
    {
        $f = f\curry($this->function);
        $this->assertSame(4, $f->getThreshold());

        $g = f\curry($this->optional);
        $this->assertSame(1, $g->getThreshold());

        $h = f\curry($this->variadic);
        $this->assertSame(0, $h->getThreshold());
    }
}