<?php

namespace nur\sery\php {
  use nulib\tests\TestCase;
  use nur\sery\ValueException;
  use nur\sery\php\impl\C0;
  use nur\sery\php\impl\C1;
  use nur\sery\php\impl\SC;
  use ReflectionException;

  class funcTest extends TestCase {
    const FUNCTION_TESTS = [
      # scalaires
      [null,
        false, null,
        false, null,
      ],
      [false,
        false, null,
        false, null,
      ],
      ["",
        false, null,
        false, null,
      ],
      ["::",
        false, null,
        false, null,
      ],
      ["->",
        false, null,
        false, null,
      ],
      ["tsimple",
        true, [false, "tsimple"],
        true, [false, "tsimple"],
      ],
      ['nur\sery\php\impl\ntsimple',
        true, [false, 'nur\sery\php\impl\ntsimple'],
        true, [false, 'nur\sery\php\impl\ntsimple'],
      ],
      ['tmissing',
        false, null,
        true, [false, 'tmissing'],
      ],
      ["::tstatic",
        false, null,
        false, null,
      ],
      ["->tmethod",
        false, null,
        false, null,
      ],
      ["::tmissing",
        false, null,
        false, null,
      ],
      ["->tmissing",
        false, null,
        false, null,
      ],
      ["xxx::tmissing",
        false, null,
        false, null,
      ],
      ["xxx->tmissing",
        false, null,
        false, null,
      ],
      [SC::class."::tstatic",
        false, null,
        false, null,
      ],
      [SC::class."->tmethod",
        false, null,
        false, null,
      ],
      [SC::class."::tmissing",
        false, null,
        false, null,
      ],
      [SC::class."->tmissing",
        false, null,
        false, null,
      ],
      # tableaux avec un seul scalaire
      [[],
        false, null,
        false, null,
      ],
      [[null],
        false, null,
        false, null,
      ],
      [[false],
        false, null,
        false, null,
      ],
      [[""],
        false, null,
        false, null,
      ],
      [["::"],
        false, null,
        false, null,
      ],
      [["->"],
        false, null,
        false, null,
      ],
      [["tsimple"],
        false, null,
        false, null,
      ],
      [['nur\sery\php\impl\ntsimple'],
        false, null,
        false, null,
      ],
      [["::tstatic"],
        false, null,
        false, null,
      ],
      [["->tmethod"],
        false, null,
        false, null,
      ],
      [["::tmissing"],
        false, null,
        false, null,
      ],
      [["->tmissing"],
        false, null,
        false, null,
      ],
      [["xxx::tmissing"],
        false, null,
        false, null,
      ],
      [["xxx->tmissing"],
        false, null,
        false, null,
      ],
      [[SC::class."::tstatic"],
        false, null,
        false, null,
      ],
      [[SC::class."->tmethod"],
        false, null,
        false, null,
      ],
      [[SC::class."::tmissing"],
        false, null,
        false, null,
      ],
      [[SC::class."->tmissing"],
        false, null,
        false, null,
      ],
      # tableaux avec deux scalaires
      [[null, "tsimple"],
        false, null,
        false, null,
      ],
      [[null, 'nur\sery\php\impl\ntsimple'],
        false, null,
        false, null,
      ],
      [[null, "tmissing"],
        false, null,
        false, null,
      ],
      [[null, "::tstatic"],
        false, null,
        false, null,
      ],
      [[null, "->tmethod"],
        false, null,
        false, null,
      ],
      [[null, "::tmissing"],
        false, null,
        false, null,
      ],
      [[null, "->tmissing"],
        false, null,
        false, null,
      ],
      [[false, "tsimple"],
        true, [false, "tsimple"],
        true, [false, "tsimple"],
      ],
      [[false, 'nur\sery\php\impl\ntsimple'],
        true, [false, 'nur\sery\php\impl\ntsimple'],
        true, [false, 'nur\sery\php\impl\ntsimple'],
      ],
      [[false, "tmissing"],
        false, null,
        true, [false, "tmissing"],
      ],
      [[false, "::tstatic"],
        false, null,
        false, null,
      ],
      [[false, "->tmethod"],
        false, null,
        false, null,
      ],
      [[false, "::tmissing"],
        false, null,
        false, null,
      ],
      [[false, "->tmissing"],
        false, null,
        false, null,
      ],
      [["", "tsimple"],
        false, null,
        false, null,
      ],
      [["", 'nur\sery\php\impl\ntsimple'],
        false, null,
        false, null,
      ],
      [["", "tmissing"],
        false, null,
        false, null,
      ],
      [["", "::tstatic"],
        false, null,
        false, null,
      ],
      [["", "->tmethod"],
        false, null,
        false, null,
      ],
      [["", "::tmissing"],
        false, null,
        false, null,
      ],
      [["", "->tmissing"],
        false, null,
        false, null,
      ],
      [["xxx", "tmissing"],
        false, null,
        false, null,
      ],
      [["xxx", "::tmissing"],
        false, null,
        false, null,
      ],
      [["xxx", "->tmissing"],
        false, null,
        false, null,
      ],
      [[SC::class, "tstatic"],
        false, null,
        false, null,
      ],
      [[SC::class, "::tstatic"],
        false, null,
        false, null,
      ],
      [[SC::class, "tmethod"],
        false, null,
        false, null,
      ],
      [[SC::class, "->tmethod"],
        false, null,
        false, null,
      ],
      [[SC::class, "tmissing"],
        false, null,
        false, null,
      ],
      [[SC::class, "::tmissing"],
        false, null,
        false, null,
      ],
      [[SC::class, "->tmissing"],
        false, null,
        false, null,
      ],
    ];

    function testFunction() {
      foreach (self::FUNCTION_TESTS as $args) {
        [$func,
          $verifix1, $func1,
          $verifix2, $func2,
        ] = $args;
        if ($func === ["", "tsimple"]) {
          //echo "breakpoint";
        }

        $workf = $func;
        $msg = var_export($func, true)." (strict)";
        self::assertSame($verifix1, func::verifix_function($workf, true), "$msg --> verifix");
        if ($verifix1) {
          self::assertSame($func1, $workf, "$msg --> func");
        }

        $workf = $func;
        $msg = var_export($func, true)." (lenient)";
        self::assertSame($verifix2, func::verifix_function($workf, false), "$msg --> verifix");
        if ($verifix2) {
          self::assertSame($func2, $workf, "$msg --> func");
        }
      }
    }

    const STATIC_TESTS = [
      # scalaires
      [null,
        false, null, null,
        false, null, null,
      ],
      [false,
        false, null, null,
        false, null, null,
      ],
      ["",
        false, null, null,
        false, null, null,
      ],
      ["::",
        false, null, null,
        false, null, null,
      ],
      ["->",
        false, null, null,
        false, null, null,
      ],
      ["tsimple",
        false, null, null,
        false, null, null,
      ],
      ['nur\sery\php\impl\ntsimple',
        false, null, null,
        false, null, null,
      ],
      ['tmissing',
        false, null, null,
        false, null, null,
      ],
      ["::tstatic",
        true, false, [null, "tstatic"],
        true, false, [null, "tstatic"],
      ],
      ["->tmethod",
        false, null, null,
        false, null, null,
      ],
      ["::tmissing",
        true, false, [null, "tmissing"],
        true, false, [null, "tmissing"],
      ],
      ["->tmissing",
        false, null, null,
        false, null, null,
      ],
      ["xxx::tmissing",
        false, null, null,
        true, true, ["xxx", "tmissing"],
      ],
      ["xxx->tmissing",
        false, null, null,
        false, null, null,
      ],
      [SC::class."::tstatic",
        true, true, [SC::class, "tstatic"],
        true, true, [SC::class, "tstatic"],
      ],
      [SC::class."->tmethod",
        false, null, null,
        false, null, null,
      ],
      [SC::class."::tmissing",
        false, null, null,
        true, true, [SC::class, "tmissing"],
      ],
      [SC::class."->tmissing",
        false, null, null,
        false, null, null,
      ],
      # tableaux avec un seul scalaire
      [[],
        false, null, null,
        false, null, null,
      ],
      [[null],
        false, null, null,
        false, null, null,
      ],
      [[false],
        false, null, null,
        false, null, null,
      ],
      [[""],
        false, null, null,
        false, null, null,
      ],
      [["::"],
        false, null, null,
        false, null, null,
      ],
      [["->"],
        false, null, null,
        false, null, null,
      ],
      [["tsimple"],
        false, null, null,
        false, null, null,
      ],
      [['nur\sery\php\impl\ntsimple'],
        false, null, null,
        false, null, null,
      ],
      [["::tstatic"],
        true, false, [null, "tstatic"],
        true, false, [null, "tstatic"],
      ],
      [["->tmethod"],
        false, null, null,
        false, null, null,
      ],
      [["::tmissing"],
        true, false, [null, "tmissing"],
        true, false, [null, "tmissing"],
      ],
      [["->tmissing"],
        false, null, null,
        false, null, null,
      ],
      [["xxx::tmissing"],
        false, null, null,
        true, true, ["xxx", "tmissing"],
      ],
      [["xxx->tmissing"],
        false, null, null,
        false, null, null,
      ],
      [[SC::class."::tstatic"],
        true, true, [SC::class, "tstatic"],
        true, true, [SC::class, "tstatic"],
      ],
      [[SC::class."->tmethod"],
        false, null, null,
        false, null, null,
      ],
      [[SC::class."::tmissing"],
        false, null, null,
        true, true, [SC::class, "tmissing"],
      ],
      [[SC::class."->tmissing"],
        false, null, null,
        false, null, null,
      ],
      # tableaux avec deux scalaires
      [[null, "tsimple"],
        true, false, [null, "tsimple"],
        true, false, [null, "tsimple"],
      ],
      [[null, 'nur\sery\php\impl\ntsimple'],
        false, null, null,
        false, null, null,
      ],
      [[null, "tmissing"],
        true, false, [null, "tmissing"],
        true, false, [null, "tmissing"],
      ],
      [[null, "::tstatic"],
        true, false, [null, "tstatic"],
        true, false, [null, "tstatic"],
      ],
      [[null, "->tmethod"],
        false, null, null,
        false, null, null,
      ],
      [[null, "::tmissing"],
        true, false, [null, "tmissing"],
        true, false, [null, "tmissing"],
      ],
      [[null, "->tmissing"],
        false, null, null,
        false, null, null,
      ],
      [[false, "tsimple"],
        false, null, null,
        false, null, null,
      ],
      [[false, 'nur\sery\php\impl\ntsimple'],
        false, null, null,
        false, null, null,
      ],
      [[false, "tmissing"],
        false, null, null,
        false, null, null,
      ],
      [[false, "::tstatic"],
        false, null, null,
        false, null, null,
      ],
      [[false, "->tmethod"],
        false, null, null,
        false, null, null,
      ],
      [[false, "::tmissing"],
        false, null, null,
        false, null, null,
      ],
      [[false, "->tmissing"],
        false, null, null,
        false, null, null,
      ],
      [["", "tsimple"],
        false, null, null,
        false, null, null,
      ],
      [["", 'nur\sery\php\impl\ntsimple'],
        false, null, null,
        false, null, null,
      ],
      [["", "tmissing"],
        false, null, null,
        false, null, null,
      ],
      [["", "::tstatic"],
        false, null, null,
        false, null, null,
      ],
      [["", "->tmethod"],
        false, null, null,
        false, null, null,
      ],
      [["", "::tmissing"],
        false, null, null,
        false, null, null,
      ],
      [["", "->tmissing"],
        false, null, null,
        false, null, null,
      ],
      [["xxx", "tmissing"],
        false, null, null,
        true, true, ["xxx", "tmissing"],
      ],
      [["xxx", "::tmissing"],
        false, null, null,
        true, true, ["xxx", "tmissing"],
      ],
      [["xxx", "->tmissing"],
        false, null, null,
        false, null, null,
      ],
      [[SC::class, "tstatic"],
        true, true, [SC::class, "tstatic"],
        true, true, [SC::class, "tstatic"],
      ],
      [[SC::class, "::tstatic"],
        true, true, [SC::class, "tstatic"],
        true, true, [SC::class, "tstatic"],
      ],
      [[SC::class, "tmethod"],
        true, true, [SC::class, "tmethod"],
        true, true, [SC::class, "tmethod"],
      ],
      [[SC::class, "->tmethod"],
        false, null, null,
        false, null, null,
      ],
      [[SC::class, "tmissing"],
        false, null, null,
        true, true, [SC::class, "tmissing"],
      ],
      [[SC::class, "::tmissing"],
        false, null, null,
        true, true, [SC::class, "tmissing"],
      ],
      [[SC::class, "->tmissing"],
        false, null, null,
        false, null, null,
      ],
    ];

    function testStatic() {
      foreach (self::STATIC_TESTS as $args) {
        [$func,
          $verifix1, $bound1, $func1,
          $verifix2, $bound2, $func2,
        ] = $args;
        if ($func === ["", "tsimple"]) {
          //echo "breakpoint";
        }

        $workf = $func;
        $msg = var_export($func, true)." (strict)";
        self::assertSame($verifix1, func::verifix_static($workf, true, $bound), "$msg --> verifix");
        if ($verifix1) {
          self::assertSame($bound1, $bound, "$msg --> bound");
          self::assertSame($func1, $workf, "$msg --> func");
        }

        $workf = $func;
        $msg = var_export($func, true)." (lenient)";
        self::assertSame($verifix2, func::verifix_static($workf, false, $bound), "$msg --> verifix");
        if ($verifix2) {
          self::assertSame($bound2, $bound, "$msg --> bound");
          self::assertSame($func2, $workf, "$msg --> func");
        }
      }
    }

    const METHOD_TESTS = [
      # scalaires
      [null,
        false, null, null,
        false, null, null,
      ],
      [false,
        false, null, null,
        false, null, null,
      ],
      ["",
        false, null, null,
        false, null, null,
      ],
      ["::",
        false, null, null,
        false, null, null,
      ],
      ["->",
        false, null, null,
        false, null, null,
      ],
      ["tsimple",
        false, null, null,
        false, null, null,
      ],
      ['nur\sery\php\impl\ntsimple',
        false, null, null,
        false, null, null,
      ],
      ['tmissing',
        false, null, null,
        false, null, null,
      ],
      ["::tstatic",
        false, null, null,
        false, null, null,
      ],
      ["->tmethod",
        true, false, [null, "tmethod"],
        true, false, [null, "tmethod"],
      ],
      ["::tmissing",
        false, null, null,
        false, null, null,
      ],
      ["->tmissing",
        true, false, [null, "tmissing"],
        true, false, [null, "tmissing"],
      ],
      ["xxx::tmissing",
        false, null, null,
        false, null, null,
      ],
      ["xxx->tmissing",
        false, null, null,
        true, true, ["xxx", "tmissing"],
      ],
      [SC::class."::tstatic",
        false, null, null,
        false, null, null,
      ],
      [SC::class."->tmethod",
        true, true, [SC::class, "tmethod"],
        true, true, [SC::class, "tmethod"],
      ],
      [SC::class."::tmissing",
        false, null, null,
        false, null, null,
      ],
      [SC::class."->tmissing",
        false, null, null,
        true, true, [SC::class, "tmissing"],
      ],
      # tableaux avec un seul scalaire
      [[],
        false, null, null,
        false, null, null,
      ],
      [[null],
        false, null, null,
        false, null, null,
      ],
      [[false],
        false, null, null,
        false, null, null,
      ],
      [[""],
        false, null, null,
        false, null, null,
      ],
      [["::"],
        false, null, null,
        false, null, null,
      ],
      [["->"],
        false, null, null,
        false, null, null,
      ],
      [["tsimple"],
        false, null, null,
        false, null, null,
      ],
      [['nur\sery\php\impl\ntsimple'],
        false, null, null,
        false, null, null,
      ],
      [["::tstatic"],
        false, null, null,
        false, null, null,
      ],
      [["->tmethod"],
        true, false, [null, "tmethod"],
        true, false, [null, "tmethod"],
      ],
      [["::tmissing"],
        false, null, null,
        false, null, null,
      ],
      [["->tmissing"],
        true, false, [null, "tmissing"],
        true, false, [null, "tmissing"],
      ],
      [["xxx::tmissing"],
        false, null, null,
        false, null, null,
      ],
      [["xxx->tmissing"],
        false, null, null,
        true, true, ["xxx", "tmissing"],
      ],
      [[SC::class."::tstatic"],
        false, null, null,
        false, null, null,
      ],
      [[SC::class."->tmethod"],
        true, true, [SC::class, "tmethod"],
        true, true, [SC::class, "tmethod"],
      ],
      [[SC::class."::tmissing"],
        false, null, null,
        false, null, null,
      ],
      [[SC::class."->tmissing"],
        false, null, null,
        true, true, [SC::class, "tmissing"],
      ],
      # tableaux avec deux scalaires
      [[null, "tsimple"],
        true, false, [null, "tsimple"],
        true, false, [null, "tsimple"],
      ],
      [[null, 'nur\sery\php\impl\ntsimple'],
        false, null, null,
        false, null, null,
      ],
      [[null, "tmissing"],
        true, false, [null, "tmissing"],
        true, false, [null, "tmissing"],
      ],
      [[null, "::tstatic"],
        false, null, null,
        false, null, null,
      ],
      [[null, "->tmethod"],
        true, false, [null, "tmethod"],
        true, false, [null, "tmethod"],
      ],
      [[null, "::tmissing"],
        false, null, null,
        false, null, null,
      ],
      [[null, "->tmissing"],
        true, false, [null, "tmissing"],
        true, false, [null, "tmissing"],
      ],
      [[false, "tsimple"],
        false, null, null,
        false, null, null,
      ],
      [[false, 'nur\sery\php\impl\ntsimple'],
        false, null, null,
        false, null, null,
      ],
      [[false, "tmissing"],
        false, null, null,
        false, null, null,
      ],
      [[false, "::tstatic"],
        false, null, null,
        false, null, null,
      ],
      [[false, "->tmethod"],
        false, null, null,
        false, null, null,
      ],
      [[false, "::tmissing"],
        false, null, null,
        false, null, null,
      ],
      [[false, "->tmissing"],
        false, null, null,
        false, null, null,
      ],
      [["", "tsimple"],
        false, null, null,
        false, null, null,
      ],
      [["", 'nur\sery\php\impl\ntsimple'],
        false, null, null,
        false, null, null,
      ],
      [["", "tmissing"],
        false, null, null,
        false, null, null,
      ],
      [["", "::tstatic"],
        false, null, null,
        false, null, null,
      ],
      [["", "->tmethod"],
        false, null, null,
        false, null, null,
      ],
      [["", "::tmissing"],
        false, null, null,
        false, null, null,
      ],
      [["", "->tmissing"],
        false, null, null,
        false, null, null,
      ],
      [["xxx", "tmissing"],
        false, null, null,
        true, true, ["xxx", "tmissing"],
      ],
      [["xxx", "::tmissing"],
        false, null, null,
        false, null, null,
      ],
      [["xxx", "->tmissing"],
        false, null, null,
        true, true, ["xxx", "tmissing"],
      ],
      [[SC::class, "tstatic"],
        true, true, [SC::class, "tstatic"],
        true, true, [SC::class, "tstatic"],
      ],
      [[SC::class, "::tstatic"],
        false, null, null,
        false, null, null,
      ],
      [[SC::class, "tmethod"],
        true, true, [SC::class, "tmethod"],
        true, true, [SC::class, "tmethod"],
      ],
      [[SC::class, "->tmethod"],
        true, true, [SC::class, "tmethod"],
        true, true, [SC::class, "tmethod"],
      ],
      [[SC::class, "tmissing"],
        false, null, null,
        true, true, [SC::class, "tmissing"],
      ],
      [[SC::class, "::tmissing"],
        false, null, null,
        false, null, null,
      ],
      [[SC::class, "->tmissing"],
        false, null, null,
        true, true, [SC::class, "tmissing"],
      ],
    ];

    function testMethod() {
      foreach (self::METHOD_TESTS as $args) {
        [$func,
          $verifix1, $bound1, $func1,
          $verifix2, $bound2, $func2,
        ] = $args;

        $workf = $func;
        $msg = var_export($func, true)." (strict)";
        self::assertSame($verifix1, func::verifix_method($workf, true, $bound), "$msg --> verifix");
        if ($verifix1) {
          self::assertSame($bound1, $bound, "$msg --> bound");
          self::assertSame($func1, $workf, "$msg --> func");
        }

        $workf = $func;
        $msg = var_export($func, true)." (lenient)";
        self::assertSame($verifix2, func::verifix_method($workf, false, $bound), "$msg --> verifix");
        if ($verifix2) {
          self::assertSame($bound2, $bound, "$msg --> bound");
          self::assertSame($func2, $workf, "$msg --> func");
        }
      }
    }

    function testInvokeFunction() {
      # m1
      self::assertSame([null], func::call("tm1"));
      self::assertSame([null], func::call("tm1", null));
      self::assertSame([null], func::call("tm1", null, null));
      self::assertSame([null], func::call("tm1", null, null, null));
      self::assertSame([null], func::call("tm1", null, null, null, null));
      self::assertSame([1], func::call("tm1", 1));
      self::assertSame([1], func::call("tm1", 1, 2));
      self::assertSame([1], func::call("tm1", 1, 2, 3));
      self::assertSame([1], func::call("tm1", 1, 2, 3, 4));

      # o1
      self::assertSame([9], func::call("to1"));
      self::assertSame([null], func::call("to1", null));
      self::assertSame([null], func::call("to1", null, null));
      self::assertSame([null], func::call("to1", null, null, null));
      self::assertSame([null], func::call("to1", null, null, null, null));
      self::assertSame([1], func::call("to1", 1));
      self::assertSame([1], func::call("to1", 1, 2));
      self::assertSame([1], func::call("to1", 1, 2, 3));
      self::assertSame([1], func::call("to1", 1, 2, 3, 4));

      # v
      self::assertSame([], func::call("tv"));
      self::assertSame([null], func::call("tv", null));
      self::assertSame([null, null], func::call("tv", null, null));
      self::assertSame([null, null, null], func::call("tv", null, null, null));
      self::assertSame([null, null, null, null], func::call("tv", null, null, null, null));
      self::assertSame([1], func::call("tv", 1));
      self::assertSame([1, 2], func::call("tv", 1, 2));
      self::assertSame([1, 2, 3], func::call("tv", 1, 2, 3));
      self::assertSame([1, 2, 3, 4], func::call("tv", 1, 2, 3, 4));

      # m1o1
      self::assertSame([null, 9], func::call("tm1o1"));
      self::assertSame([null, 9], func::call("tm1o1", null));
      self::assertSame([null, null], func::call("tm1o1", null, null));
      self::assertSame([null, null], func::call("tm1o1", null, null, null));
      self::assertSame([null, null], func::call("tm1o1", null, null, null, null));
      self::assertSame([1, 9], func::call("tm1o1", 1));
      self::assertSame([1, 2], func::call("tm1o1", 1, 2));
      self::assertSame([1, 2], func::call("tm1o1", 1, 2, 3));
      self::assertSame([1, 2], func::call("tm1o1", 1, 2, 3, 4));

      # m1v
      self::assertSame([null], func::call("tm1v"));
      self::assertSame([null], func::call("tm1v", null));
      self::assertSame([null, null], func::call("tm1v", null, null));
      self::assertSame([null, null, null], func::call("tm1v", null, null, null));
      self::assertSame([null, null, null, null], func::call("tm1v", null, null, null, null));
      self::assertSame([1], func::call("tm1v", 1));
      self::assertSame([1, 2], func::call("tm1v", 1, 2));
      self::assertSame([1, 2, 3], func::call("tm1v", 1, 2, 3));
      self::assertSame([1, 2, 3, 4], func::call("tm1v", 1, 2, 3, 4));

      # m1o1v
      self::assertSame([null, 9], func::call("tm1o1v"));
      self::assertSame([null, 9], func::call("tm1o1v", null));
      self::assertSame([null, null], func::call("tm1o1v", null, null));
      self::assertSame([null, null, null], func::call("tm1o1v", null, null, null));
      self::assertSame([null, null, null, null], func::call("tm1o1v", null, null, null, null));
      self::assertSame([1, 9], func::call("tm1o1v", 1));
      self::assertSame([1, 2], func::call("tm1o1v", 1, 2));
      self::assertSame([1, 2, 3], func::call("tm1o1v", 1, 2, 3));
      self::assertSame([1, 2, 3, 4], func::call("tm1o1v", 1, 2, 3, 4));

      # o1v
      self::assertSame([9], func::call("to1v"));
      self::assertSame([null], func::call("to1v", null));
      self::assertSame([null, null], func::call("to1v", null, null));
      self::assertSame([null, null, null], func::call("to1v", null, null, null));
      self::assertSame([null, null, null, null], func::call("to1v", null, null, null, null));
      self::assertSame([1], func::call("to1v", 1));
      self::assertSame([1, 2], func::call("to1v", 1, 2));
      self::assertSame([1, 2, 3], func::call("to1v", 1, 2, 3));
      self::assertSame([1, 2, 3, 4], func::call("to1v", 1, 2, 3, 4));
    }

    function testInvokeClass() {
      $func = func::with(SC::class);
      self::assertInstanceOf(SC::class, $func->invoke());
      self::assertInstanceOf(SC::class, $func->invoke([]));
      self::assertInstanceOf(SC::class, $func->invoke([1]));
      self::assertInstanceOf(SC::class, $func->invoke([1, 2]));
      self::assertInstanceOf(SC::class, $func->invoke([1, 2, 3]));

      $func = func::with(C0::class);
      self::assertInstanceOf(C0::class, $func->invoke());
      self::assertInstanceOf(C0::class, $func->invoke([]));
      self::assertInstanceOf(C0::class, $func->invoke([1]));
      self::assertInstanceOf(C0::class, $func->invoke([1, 2]));
      self::assertInstanceOf(C0::class, $func->invoke([1, 2, 3]));

      $func = func::with(C1::class);
      /** @var C1 $i1 */
      $i1 = $func->invoke();
      self::assertInstanceOf(C1::class, $i1); self::assertSame(0, $i1->base);
      $i1 = $func->invoke([]);
      self::assertInstanceOf(C1::class, $i1); self::assertSame(0, $i1->base);
      $i1 = $func->invoke([1]);
      self::assertInstanceOf(C1::class, $i1); self::assertSame(1, $i1->base);
      $i1 = $func->invoke([1, 2]);
      self::assertInstanceOf(C1::class, $i1); self::assertSame(1, $i1->base);
    }
    
    private static function invoke_asserts(): array {
      $inv_ok = function($func) {
        return func::with($func)->invoke();
      };
      $inv_ko = function($func) use ($inv_ok) {
        return function() use ($func, $inv_ok) {
          return $inv_ok($func);
        };
      };
      $bind_ok = function($func, $objet) {
        return func::with($func)->bind($objet)->invoke();
      };
      $bind_ko = function($func, $object) use ($bind_ok) {
        return function() use ($func, $object, $bind_ok) {
          return $bind_ok($func, $object);
        };
      };
      return [$inv_ok, $inv_ko, $bind_ok, $bind_ko];
    }

    function testInvokeStatic() {
      [$inv_ok, $inv_ko, $bind_ok, $bind_ko] = self::invoke_asserts();
      $sc = new SC();

      self::assertSame(10, $inv_ok([SC::class, "tstatic"]));
      self::assertSame(10, $inv_ok([SC::class, "::tstatic"]));
      self::assertSame(10, $inv_ok([SC::class, "->tstatic"]));

      self::assertSame(10, $inv_ok([$sc, "tstatic"]));
      self::assertSame(10, $inv_ok([$sc, "::tstatic"]));
      self::assertSame(10, $inv_ok([$sc, "->tstatic"]));

      self::assertException(ValueException::class, $inv_ko([null, "tstatic"]));
      self::assertException(ValueException::class, $inv_ko([null, "::tstatic"]));
      self::assertException(ValueException::class, $inv_ko([null, "->tstatic"]));

      self::assertSame(10, $bind_ok([null, "tstatic"], SC::class));
      self::assertSame(10, $bind_ok([null, "::tstatic"], SC::class));
      self::assertSame(10, $bind_ok([null, "->tstatic"], SC::class));

      self::assertSame(10, $bind_ok([null, "tstatic"], $sc));
      self::assertSame(10, $bind_ok([null, "::tstatic"], $sc));
      self::assertSame(10, $bind_ok([null, "->tstatic"], $sc));
    }

    function testInvokeMethod() {
      [$inv_ok, $inv_ko, $bind_ok, $bind_ko] = self::invoke_asserts();
      $sc = new SC();

      self::assertException(ReflectionException::class, $inv_ko([SC::class, "tmethod"]));
      self::assertException(ReflectionException::class, $inv_ko([SC::class, "::tmethod"]));
      self::assertException(ReflectionException::class, $inv_ko([SC::class, "->tmethod"]));

      self::assertSame(11, $inv_ok([$sc, "tmethod"]));
      self::assertException(ReflectionException::class, $inv_ko([$sc, "::tmethod"]));
      self::assertSame(11, $inv_ok([$sc, "->tmethod"]));

      self::assertException(ValueException::class, $inv_ko([null, "tmethod"]));
      self::assertException(ValueException::class, $inv_ko([null, "::tmethod"]));
      self::assertException(ValueException::class, $inv_ko([null, "->tmethod"]));

      self::assertException(ReflectionException::class, $bind_ko([null, "tmethod"], SC::class));
      self::assertException(ReflectionException::class, $bind_ko([null, "::tmethod"], SC::class));
      self::assertException(ReflectionException::class, $bind_ko([null, "->tmethod"], SC::class));

      self::assertSame(11, $bind_ok([null, "tmethod"], $sc));
      self::assertException(ReflectionException::class, $bind_ko([null, "::tmethod"], $sc));
      self::assertSame(11, $bind_ok([null, "->tmethod"], $sc));
    }

    function testArgs() {
      $func = function(int $a, int $b, int $c): int {
        return $a + $b + $c;
      };

      self::assertSame(6, func::call($func, 1, 2, 3));
      self::assertSame(6, func::call($func, 1, 2, 3, 4));

      self::assertSame(6, func::with($func)->invoke([1, 2, 3]));
      self::assertSame(6, func::with($func, [1])->invoke([2, 3]));
      self::assertSame(6, func::with($func, [1, 2])->invoke([3]));
      self::assertSame(6, func::with($func, [1, 2, 3])->invoke());
      self::assertSame(6, func::with($func, [1, 2, 3, 4])->invoke());
    }

    function testRebind() {
      $func = func::with([C1::class, "tmethod"]);
      self::assertSame(11, $func->bind(new C1(0))->invoke());
      self::assertSame(12, $func->bind(new C1(1))->invoke());
      self::assertException(ValueException::class, function() use ($func) {
        $func->bind(new C0())->invoke();
      });
    }
  }
}

namespace {
  function tsimple(): int { return 0; }
  function tm1($a): array { return [$a]; }
  function to1($b=9): array { return [$b]; }
  function tv(...$c): array { return [...$c]; }
  function tm1o1($a, $b=9): array { return [$a, $b]; }
  function tm1v($a, ...$c): array { return [$a, ...$c]; }
  function tm1o1v($a, $b=9, ...$c): array { return [$a, $b, ...$c]; }
  function to1v($b=9, ...$c): array { return [$b, ...$c]; }
}

namespace nur\sery\php\impl {
  function ntsimple(): int { return 0; }

  class SC {
    static function tstatic(): int {
      return 10;
    }

    function tmethod(): int {
      return 11;
    }
  }

  class C0 {
    function __construct() {
    }

    static function tstatic(): int {
      return 10;
    }

    function tmethod(): int {
      return 11;
    }
  }

  class C1 {
    function __construct(int $base=0) {
      $this->base = $base;
    }

    public int $base;

    static function tstatic(): int {
      return 10;
    }

    function tmethod(): int {
      return 11 + $this->base;
    }
  }
}
