<?php
namespace nur\cli;

use nur\cli\impl\AbcdDest;
use nur\cli\impl\SimpleDest;
use nur\t\TestCase;
use stdClass;

class ArgsParserTest extends TestCase {
  const SIMPLE_DEFS = [
    "autohelp" => false,
    "autoremains" => false,
    ["-a", "key" => "enabled"],
    ["-b", "key" => "enabled", "value" => 52],
    ["-z", "key" => "enabled", "inverse" => true],
    ["--keep", "key" => "keep"],
    ["-s", "--long", "key" => "option"],
    ["-v", "--value", "arg" => "value", "key" => "value"],
    ["-f", "--file", "arg" => "path", "key" => "file"],
    ["-h", "--host", "arg" => "host", "key" => "host"],
  ];
  const DEBUG_ARGS = ["--keep", "-asvvalue"];
  const SIMPLE_ARGS0 = [
    "--keep",
    "--long",
    "--value", "value",
    "--value=value",
    "--file", "file",
    "--file=file",
    "--host", "host",
    "--host=host",
  ];
  const SIMPLE_ARGS1 = [
    "-a", "-aa",
    "-s", "-ss",
    "-aass",
    "-v", "value",
    "-vvalue",
    "-f", "file",
    "-ffile",
    "-h", "host",
    "-hhost",
    "-asv", "value",
    "-asvvalue",
  ];

  function testNormArgs() {
    $parser = new ArgsParser(self::SIMPLE_DEFS);

    $args = $parser->normArgs(self::DEBUG_ARGS);
    #var_dump($args);
    self::assertCount(6, $args);

    $args = $parser->normArgs(self::SIMPLE_ARGS0);
    #var_dump($args);
    self::assertCount(15, $args);

    $args = $parser->normArgs(self::SIMPLE_ARGS1);
    #var_dump($args);
    self::assertCount(31, $args);

    self::assertSame(["--"], $parser->normArgs([]));
    self::assertSame(["--"], $parser->normArgs(["--"]));
    self::assertSame(["--", "a"], $parser->normArgs(["--", "a"]));
    self::assertSame(["--", "--"], $parser->normArgs(["--", "--"]));
    self::assertSame(["--", "--", "a"], $parser->normArgs(["--", "--", "a"]));
    self::assertSame(["--", "--", "--"], $parser->normArgs(["--", "--", "--"]));
    self::assertSame(["--", "--", "--", "a"], $parser->normArgs(["--", "--", "--", "a"]));
    self::assertSame(["--", "a"], $parser->normArgs(["a"]));
    self::assertSame(["--", "a"], $parser->normArgs(["a", "--"]));
    self::assertSame(["--", "a", "b"], $parser->normArgs(["a", "--", "b"]));
    self::assertSame(["--", "a", "--", "b"], $parser->normArgs(["a", "--", "--", "b"]));
    self::assertSame(["--", "a", "--", "--", "b"], $parser->normArgs(["a", "--", "--", "--", "b"]));
  }

  function testNormArgs2() {
    $parser = new ArgsParser(self::SIMPLE_DEFS);

    self::assertSame(["-a", "--", "a"],
      $parser->normArgs(["a", "-a"]));
    self::assertSame(["-a", "--", "a"],
      $parser->normArgs(["a", "-a", "//"]));
    self::assertSame(["-a", "--", "a", "//", "--", "b"],
      $parser->normArgs(["a", "-a", "//", "b"]));
    self::assertSame(["-a", "--", "a", "//", "-b", "--", "b"],
      $parser->normArgs(["a", "-a", "//", "b", "-b"]));

    self::assertSame(["-a", "--", "a", "\\//"],
      $parser->normArgs(["a", "-a", "\\//"]));
    self::assertSame(["-a", "--", "a", "\\//", "b"],
      $parser->normArgs(["a", "-a", "\\//", "b"]));
    self::assertSame(["-a", "-b", "--", "a", "\\//", "b"],
      $parser->normArgs(["a", "-a", "\\//", "b", "-b"]));
  }

  function testParseArgs() {
    $parser = new ArgsParser(self::SIMPLE_DEFS);

    $dest = [];
    $parser->parseArgs($parser->normArgs(["-asvvalue", "--keep"]));
    #var_dump($dest);
    self::assertSame(1, $dest["enabled"]);
    self::assertSame(1, $dest["keep"]);
    self::assertSame(1, $dest["option"]);
    self::assertSame("value", $dest["value"]);

    $dest = [];
    $parser->parseArgs($parser->normArgs(["-aa"]));
    self::assertSame(2, $dest["enabled"]);

    $dest = [];
    $parser->parseArgs($parser->normArgs(["-aazz"]));
    self::assertSame(0, $dest["enabled"]);

    $dest = [];
    $parser->parseArgs($parser->normArgs(["-b"]));
    self::assertSame(52, $dest["enabled"]);

    $dest = [];
    $parser->parseArgs($parser->normArgs(["-bb"]));
    self::assertSame(52, $dest["enabled"]);

    self::assertException(ArgsException::class, function()
      use ($parser, $dest) {
      $parser->parseArgs($parser->normArgs(["-a", "arg"]));
    });
  }

  function testParseArgs2() {
    $defs = self::SIMPLE_DEFS;
    $defs["autoremains"] = true;
    $parser = new ArgsParser($defs);

    $dest = []; $parser->parseArgs($parser->normArgs([]));
    self::assertSame([], $dest["args"]);

    $dest = []; $parser->parseArgs($parser->normArgs(["--"]));
    self::assertSame([], $dest["args"]);

    $dest = []; $parser->parseArgs($parser->normArgs(["--", "a"]));
    self::assertSame(["a"], $dest["args"]);

    $dest = []; $parser->parseArgs($parser->normArgs(["--", "--"]));
    self::assertSame(["--"], $dest["args"]);

    $dest = []; $parser->parseArgs($parser->normArgs(["--", "--", "a"]));
    self::assertSame(["--", "a"], $dest["args"]);

    $dest = []; $parser->parseArgs($parser->normArgs(["--", "--", "--"]));
    self::assertSame(["--", "--"], $dest["args"]);

    $dest = []; $parser->parseArgs($parser->normArgs(["--", "--", "--", "a"]));
    self::assertSame(["--", "--", "a"], $dest["args"]);

    $dest = []; $parser->parseArgs($parser->normArgs(["a"]));
    self::assertSame(["a"], $dest["args"]);

    $dest = []; $parser->parseArgs($parser->normArgs(["a", "--"]));
    self::assertSame(["a"], $dest["args"]);

    $dest = []; $parser->parseArgs($parser->normArgs(["a", "--", "b"]));
    self::assertSame(["a", "b"], $dest["args"]);

    $dest = []; $parser->parseArgs($parser->normArgs(["a", "--", "--", "b"]));
    self::assertSame(["a", "--", "b"], $dest["args"]);

    $dest = []; $parser->parseArgs($parser->normArgs(["a", "--", "--", "--", "b"]));
    self::assertSame(["a", "--", "--", "b"], $dest["args"]);
  }

  function testParseArgs3() {
    $defs = self::SIMPLE_DEFS;
    $defs["autoremains"] = true;
    $parser = new ArgsParser($defs);

    $dest = []; $parser->parseArgs($parser->normArgs(["a", "-a"]));
    self::assertSame(["a"], $dest["args"]);
    self::assertSame(1, $dest["enabled"]);

    $dest = []; $parser->parseArgs($parser->normArgs(["a", "-a", "//"]));
    self::assertSame(["a"], $dest["args"]);
    self::assertSame(1, $dest["enabled"]);

    $dest = []; $parser->parseArgs($parser->normArgs(["a", "-a", "//", "b"]));
    self::assertSame(["a", "b"], $dest["args"]);
    self::assertSame(1, $dest["enabled"]);

    $dest = []; $parser->parseArgs($parser->normArgs(["a", "-a", "//", "b", "-b"]));
    self::assertSame(["a", "b"], $dest["args"]);
    self::assertSame(52, $dest["enabled"]);

    $dest = []; $parser->parseArgs($parser->normArgs(["a", "-a", "\\//"]));
    self::assertSame(["a", "//"], $dest["args"]);
    self::assertSame(1, $dest["enabled"]);

    $dest = []; $parser->parseArgs($parser->normArgs(["a", "-a", "\\//", "b"]));
    self::assertSame(["a", "//", "b"], $dest["args"]);
    self::assertSame(1, $dest["enabled"]);

    $dest = []; $parser->parseArgs($parser->normArgs(["a", "-a", "\\//", "b", "-b"]));
    self::assertSame(["a", "//", "b"], $dest["args"]);
    self::assertSame(52, $dest["enabled"]);

    $dest = []; $parser->parseArgs($parser->normArgs(["a", "-a", "\\//", "\\\\//", "b", "-b"]));
    self::assertSame(["a", "//", "\\//", "b"], $dest["args"]);
    self::assertSame(52, $dest["enabled"]);
  }

  private static function parse(&$dest, array $defs, ?array $args=null): void {
    $parser = new ArgsParser($defs);
    $parser->parse($dest, $args);
  }

  function testAutoprop() {
    unset($dest);
    self::parse($dest, [
      ["-a", "--enable"],
      ["-b", "--b", "--before", "--before-date", "args" => "value"],
    ], [
      "-aa",
      "-b", "15/02/2002",
    ]);
    self::assertSame(2, $dest->enable);
    self::assertSame("15/02/2002", $dest->beforeDate);

    $dest = new AbcdDest();
    self::parse($dest, [
      ["-a", "--a"],
      ["-b", "--b"],
      ["-c", "--c"],
    ], [
      "-abbccc"
    ]);
    self::assertSame(1, $dest->getA());
    self::assertSame(2, $dest->getB());
    self::assertSame(3, $dest->getC());
  }

  function testArgs() {
    $defs = [
      ["-a", "--enable"],
    ];

    unset($dest);
    self::parse($dest, $defs, []);
    self::assertObjectNotHasProperty("enable", $dest);
    self::assertObjectHasProperty("args", $dest);
    self::assertSame([], $dest->args);

    unset($dest);
    self::parse($dest, $defs, ["x"]);
    self::assertObjectNotHasProperty("enable", $dest);
    self::assertObjectHasProperty("args", $dest);
    self::assertSame(["x"], $dest->args);

    unset($dest);
    self::parse($dest, $defs, ["x", "-a"]);
    self::assertObjectHasProperty("enable", $dest);
    self::assertObjectHasProperty("args", $dest);
    self::assertSame(1, $dest->enable);
    self::assertSame(["x"], $dest->args);

    $dest = [];
    self::parse($dest, $defs, []);
    self::assertArrayNotHasKey("enable", $dest);
    self::assertArrayHasKey("args", $dest);
    self::assertSame([], $dest["args"]);

    $dest = [];
    self::parse($dest, $defs, ["x"]);
    self::assertArrayNotHasKey("enable", $dest);
    self::assertArrayHasKey("args", $dest);
    self::assertSame(["x"], $dest["args"]);

    $dest = [];
    self::parse($dest, $defs, ["x", "-a"]);
    self::assertArrayHasKey("enable", $dest);
    self::assertArrayHasKey("args", $dest);
    self::assertSame(1, $dest["enable"]);
    self::assertSame(["x"], $dest["args"]);
  }

  function testOpts() {
    $defs = [
      ["-o", "--o", "args" => [["value"]]],
      ["--mm", "args" => ["value", "value"]],
      ["--mo", "args" => ["value", ["value"]]],
      ["--oo", "args" => [["value", "value"]]],
      ["--ma", "args" => ["value", null]],
      ["--a", "args" => [null]],
    ];

    unset($dest); self::parse($dest, $defs, ["-o"]);
    self::assertSame(null, $dest->o);
    self::assertSame([], $dest->args);
    unset($dest); self::parse($dest, $defs, ["-o", "1"]);
    self::assertSame(null, $dest->o);
    self::assertSame(["1"], $dest->args);
    unset($dest); self::parse($dest, $defs, ["-o1"]);
    self::assertSame("1", $dest->o);
    self::assertSame([], $dest->args);
    unset($dest); self::parse($dest, $defs, ["-o1", "2"]);
    self::assertSame("1", $dest->o);
    self::assertSame(["2"], $dest->args);
    unset($dest); self::parse($dest, $defs, ["--o"]);
    self::assertSame(null, $dest->o);
    self::assertSame([], $dest->args);
    unset($dest); self::parse($dest, $defs, ["--o", "1"]);
    self::assertSame(null, $dest->o);
    self::assertSame(["1"], $dest->args);
    unset($dest); self::parse($dest, $defs, ["--o=1"]);
    self::assertSame("1", $dest->o);
    self::assertSame([], $dest->args);
    unset($dest); self::parse($dest, $defs, ["--o=1", "2"]);
    self::assertSame("1", $dest->o);
    self::assertSame(["2"], $dest->args);

    unset($dest); self::parse($dest, $defs, ["--mm", "1", "2"]);
    self::assertSame(["1", "2"], $dest->mm);
    self::assertSame([], $dest->args);

    unset($dest); self::parse($dest, $defs, ["--mo", "1"]);
    self::assertSame(["1"], $dest->mo);
    self::assertSame([], $dest->args);
    unset($dest); self::parse($dest, $defs, ["--mo", "1", "2"]);
    self::assertSame(["1", "2"], $dest->mo);
    self::assertSame([], $dest->args);
    unset($dest); self::parse($dest, $defs, ["--mo", "1", "2", "3"]);
    self::assertSame(["1", "2"], $dest->mo);
    self::assertSame(["3"], $dest->args);

    unset($dest); self::parse($dest, $defs, ["--oo"]);
    self::assertSame([], $dest->oo);
    self::assertSame([], $dest->args);
    unset($dest); self::parse($dest, $defs, ["--oo", "1"]);
    self::assertSame(["1"], $dest->oo);
    self::assertSame([], $dest->args);
    unset($dest); self::parse($dest, $defs, ["--oo", "1", "2"]);
    self::assertSame(["1", "2"], $dest->oo);
    self::assertSame([], $dest->args);
    unset($dest); self::parse($dest, $defs, ["--oo", "1", "2", "3"]);
    self::assertSame(["1", "2"], $dest->oo);
    self::assertSame(["3"], $dest->args);
  }

  function testDefaultOptArgs() {
    $defs = [
      ["-o", "--o", "args" => [["value"]],
        "value" => "1",
      ],
    ];

    unset($dest); self::parse($dest, $defs, ["-o"]);
    self::assertSame("1", $dest->o);
    self::assertSame([], $dest->args);
    unset($dest); self::parse($dest, $defs, ["-o2"]);
    self::assertSame("2", $dest->o);
    self::assertSame([], $dest->args);
  }

  function testParseDefs() {
    $defs = [
      ["-a", "--a"],
      ["x",
        "cmd_args" => [
          ["-b", "--b"],
        ],
      ],
    ];
    $parser = new ArgsParser($defs);
    $parser->select(null);
    $args = $parser->normArgs(["-a", "x", "-b"]);
    self::assertSame(["-a", "x", "-b", "--"], $args);
    $dest = new stdClass();
    $parser->select(null);
    $parser->parseArgs($args);
    self::assertSame(["x"], $dest->command);
    self::assertSame(1, $dest->a);
    self::assertSame(1, $dest->b);

    $defs = [
      ["-a", "--a"],
      ["x",
        "cmd_args" => [
          ["-b", "--b"],
          ["y",
            "cmd_args" => [
              ["-c", "--c"],
            ],
          ],
        ],
      ],
    ];
    $parser = new ArgsParser($defs);
    $parser->select(null);
    $args = $parser->normArgs(["-a", "x", "-b", "y", "-c"]);
    self::assertSame(["-a", "x", "-b", "y", "-c", "--"], $args);
    $dest = new stdClass();
    $parser->select(null);
    $parser->parseArgs($args);
    self::assertSame(["x", "y"], $dest->command);
    self::assertSame(1, $dest->a);
    self::assertSame(1, $dest->b);
    self::assertSame(1, $dest->c);
  }

  function testTypes() {
    $parser = new ArgsParser([
      ["-a:", "type" => "int"],
      ["-b:", "type" => "bool"],
      ["-c:", "type" => "string"],
      ["-d:", "type" => "array"],
    ]);

    $dest = new AbcdDest();
    $parser->parse($dest, ["-a0", "-b0", "-c0", "-d", ""]);
    self::assertSame(0, $dest->getA());
    self::assertSame(false, $dest->getB());
    self::assertSame("0", $dest->getC());
    self::assertSame([""], $dest->getD());

    $dest = new AbcdDest();
    $parser->parse($dest, ["-a1", "-b1", "-c1", "-d1"]);
    self::assertSame(1, $dest->getA());
    self::assertSame(true, $dest->getB());
    self::assertSame("1", $dest->getC());
    self::assertSame(["1"], $dest->getD());

    $dest = new AbcdDest();
    $parser->parse($dest, [
      "-a1", "-a2",
      "-b1", "-b2",
      "-c1", "-c2",
      "-d1", "-d2",
    ]);
    self::assertSame(2, $dest->getA());
    self::assertSame(true, $dest->getB());
    self::assertSame("2", $dest->getC());
    self::assertSame(["2"], $dest->getD());

    $parser = new ArgsParser([
      ["-a:", "type" => "array", "action" => "--add"],
      ["-b:", "type" => "array", "action" => "--merge"],
      ["-c:", "type" => "int", "action" => "--add"],
      ["-d:", "type" => "int", "action" => "--merge"],
    ]);

    $dest = new AbcdDest();
    $parser->parse($dest, [
      "-a1", "-a2",
      "-b1", "-b2",
      "-c1", "-c2",
      "-d1", "-d2",
    ]);
    self::assertSame([["1"], ["2"]], $dest->getA());
    self::assertSame(["1", "2"], $dest->getB());
    self::assertSame([1, 2], $dest->getC());
    self::assertSame([1, 2], $dest->getD());

    $parser = new ArgsParser([
      ["-a:", "type" => "array", "action" => "--merge"],
      ["-b:", "args" => ["value", [null]], "type" => "array", "action" => "--merges"],
    ]);

    $dest = new AbcdDest();
    $parser->parse($dest, [
      "-a1", "-a2,3", "-a4,5,6",
      "-b1", "2,3", "4,5,6", "--",
    ]);
    self::assertSame(["1", "2", "3", "4", "5", "6"], $dest->getA());
    self::assertSame(["1", "2", "3", "4", "5", "6"], $dest->getB());
  }

  function testRemainArgs() {
    $dest = new SimpleDest();

    ## nombre quelconque d'arguments
    $parser = new ArgsParser([
      ["args" => [null], "name" => "args"],
    ]);
    # 0
    $dest->reset();
    $parser->parse($dest, []);
    self::assertSame([], $dest->args);
    # 1
    $dest->reset();
    $parser->parse($dest, ["a"]);
    self::assertSame(["a"], $dest->args);
    # 2
    $dest->reset();
    $parser->parse($dest, ["a", "b"]);
    self::assertSame(["a", "b"], $dest->args);

    # un argument
    $parser = new ArgsParser([
      ["args" => ["value"], "name" => "args"],
    ]);
    # 0
    $dest->reset();
    self::assertException(ArgsException::class, function () use ($parser, &$dest) {
      $parser->parse($dest, []);
    });
    # 1
    $dest->reset();
    $parser->parse($dest, ["a"]);
    self::assertSame(["a"], $dest->args);
    # 2
    $dest->reset();
    self::assertException(ArgsException::class, function () use ($parser, &$dest) {
      $parser->parse($dest, ["a", "b"]);
    });

    # deux arguments
    $parser = new ArgsParser([
      ["args" => ["value", "value"], "name" => "args"],
    ]);
    # 0
    $dest->reset();
    self::assertException(ArgsException::class, function () use ($parser, &$dest) {
      $parser->parse($dest, []);
    });
    # 1
    $dest->reset();
    self::assertException(ArgsException::class, function () use ($parser, &$dest) {
      $parser->parse($dest, ["a"]);
    });
    # 2
    $dest->reset();
    $parser->parse($dest, ["a", "b"]);
    self::assertSame(["a", "b"], $dest->args);
    # 3
    $dest->reset();
    self::assertException(ArgsException::class, function () use ($parser, &$dest) {
      $parser->parse($dest, ["a", "b", "c"]);
    });

    # un arguments obligatoire, un argument facultatif
    $parser = new ArgsParser([
      ["args" => ["value", ["value"]], "name" => "args"],
    ]);
    # 0
    $dest->reset();
    self::assertException(ArgsException::class, function () use ($parser, &$dest) {
      $parser->parse($dest, []);
    });
    # 1
    $dest->reset();
    $parser->parse($dest, ["a"]);
    self::assertSame(["a"], $dest->args);
    # 2
    $dest->reset();
    $parser->parse($dest, ["a", "b"]);
    self::assertSame(["a", "b"], $dest->args);
    # 3
    $dest->reset();
    self::assertException(ArgsException::class, function () use ($parser, &$dest) {
      $parser->parse($dest, ["a", "b", "c"]);
    });

    # un arguments obligatoire, nombre infini d'arguments facultatifs
    $parser = new ArgsParser([
      ["args" => ["value", null], "name" => "args"],
    ]);
    # 0
    $dest->reset();
    self::assertException(ArgsException::class, function () use ($parser, &$dest) {
      $parser->parse($dest, []);
    });
    # 1
    $dest->reset();
    $parser->parse($dest, ["a"]);
    self::assertSame(["a"], $dest->args);
    # 2
    $dest->reset();
    $parser->parse($dest, ["a", "b"]);
    self::assertSame(["a", "b"], $dest->args);
    # 3
    $dest->reset();
    $parser->parse($dest, ["a", "b", "c"]);
    self::assertSame(["a", "b", "c"], $dest->args);
  }

  function testOverride() {
    $parser = new ArgsParser([
      ["-a", "--o1"],
      ["-a", "--o2"],
      ["-b", "--v1", "args" => 1],
      ["-b", "--v2", "args" => 1],
      ["-c", "--x1"],
      ["-c", "--x2", "args" => 1],
    ]);
    $object = new stdClass();
    $object->o1 = 0;
    $object->o2 = 0;
    $object->v1 = null;
    $object->v2 = null;
    $object->x1 = 0;
    $object->x2 = null;
    $parser->parse($object, ["-a", "-b", "valb", "-c", "valc"]);
    self::assertSame(0, $object->o1);
    self::assertSame(1, $object->o2);
    self::assertNull($object->v1);
    self::assertSame("valb", $object->v2);
    self::assertSame(0, $object->x1);
    self::assertSame("valc", $object->x2);

    $parser = new ArgsParser([
      "sections" => [
        [
          ["-a", "--o1"],
          ["-b", "--v1", "args" => 1],
          ["-c", "--x1"],
        ],
        [
          ["-a", "--o2"],
          ["-b", "--v2", "args" => 1],
          ["-c", "--x2", "args" => 1],
        ],
      ],
    ]);
    $object = new stdClass();
    $object->o1 = 0;
    $object->o2 = 0;
    $object->v1 = null;
    $object->v2 = null;
    $object->x1 = 0;
    $object->x2 = null;
    $parser->parse($object, ["-a", "-b", "valb", "-c", "valc"]);
    self::assertSame(0, $object->o1);
    self::assertSame(1, $object->o2);
    self::assertNull($object->v1);
    self::assertSame("valb", $object->v2);
    self::assertSame(0, $object->x1);
    self::assertSame("valc", $object->x2);

    $parser = new ArgsParser([
      "merge" => [
        "sections" => [
          [
            "title" => "une section",
            ["group",
              ["-A", "--g1"],
              ["-a", "--g2"],
            ],
          ],
        ],
      ],
      ["-a", "--o1"],
      ["-b", "--v1", "args" => 1],
      ["-c", "--x1"],
      ["-a", "--o2"],
      ["-b", "--v2", "args" => 1],
      ["-c", "--x2", "args" => 1],
    ]);
    $object = new stdClass();
    $object->g1 = 0;
    $object->g2 = 0;
    $object->o1 = 0;
    $object->o2 = 0;
    $object->v1 = null;
    $object->v2 = null;
    $object->x1 = 0;
    $object->x2 = null;
    $parser->parse($object, ["-a", "-b", "valb", "-c", "valc"]);
    self::assertSame(0, $object->g1);
    self::assertSame(0, $object->g2);
    self::assertSame(0, $object->o1);
    self::assertSame(1, $object->o2);
    self::assertNull($object->v1);
    self::assertSame("valb", $object->v2);
    self::assertSame(0, $object->x1);
    self::assertSame("valc", $object->x2);
  }
}
