<?php
namespace nur\mapper\app;

use nur\A;
use nur\b\ValueException;
use nur\mapper\base\mappers_command;
use nur\mapper\csv\CsvReader;
use nur\mapper\csv\CsvReader_command;
use nur\mapper\csv\CsvWriter;
use nur\mapper\csv\CsvWriter_command;
use nur\mapper\fsv\FsvReader;
use nur\mapper\fsv\FsvReader_command;
use nur\mapper\fsv\FsvWriter;
use nur\mapper\fsv\FsvWriter_command;
use nur\mapper\json\JsonReader;
use nur\mapper\json\JsonReader_command;
use nur\mapper\json\JsonWriter;
use nur\mapper\json\JsonWriter_command;
use nur\mapper\json\YamlReader;
use nur\mapper\json\YamlReader_command;
use nur\mapper\json\YamlWriter;
use nur\mapper\json\YamlWriter_command;
use nur\mapper\line\LineReader;
use nur\mapper\line\LineReader_command;
use nur\mapper\line\LineWriter;
use nur\mapper\line\LineWriter_command;
use nur\str;

class datareader_command extends mappers_command {
  const PRODUCER_CLASSES = [
    LineReader_command::class,
    CsvReader_command::class,
    FsvReader_command::class,
    JsonReader_command::class,
    YamlReader_command::class,
  ];
  const CONSUMER_CLASSES = [
    LineWriter_command::class,
    CsvWriter_command::class,
    FsvWriter_command::class,
    JsonWriter_command::class,
    YamlWriter_command::class,
  ];
  const MAPPER_CLASSES = [
  ];

  protected function DYNAMIC_COMMAND_CLASSES(): array {
    return array_merge(
      self::PRODUCER_CLASSES, parent::PRODUCER_CLASSES,
      self::CONSUMER_CLASSES, parent::CONSUMER_CLASSES,
      self::MAPPER_CLASSES, parent::MAPPER_CLASSES
    );
  }

  const DEFS = [
    "csv" => [
      "exts" => [".csv"],
      "formats" => ["csv", "c"],
      "reader" => [CsvReader_command::class, CsvReader::class],
      "writer" => [CsvWriter_command::class, CsvWriter::class],
      "set_input_encoding" => "setEncodingFilter",
      "set_output_encoding" => "setEncodingFilter",
    ],
    "fsv" => [
      "exts" => [".fsv"],
      "formats" => ["fsv", "v"],
      "reader" => [FsvReader_command::class, FsvReader::class],
      "writer" => [FsvWriter_command::class, FsvWriter::class],
      "set_input_encoding" => "setInputEncoding",
      "set_output_encoding" => "setOutputEncoding",
    ],
    "json" => [
      "exts" => [".json"],
      "formats" => ["json", "j"],
      "reader" => [JsonReader_command::class, JsonReader::class],
      "writer" => [JsonWriter_command::class, JsonWriter::class],
      "set_input_encoding" => false,
      "set_output_encoding" => false,
    ],
    "yaml" => [
      "exts" => [".yaml", ".yml"],
      "formats" => ["yaml", "l"],
      "reader" => [YamlReader_command::class, YamlReader::class],
      "writer" => [YamlWriter_command::class, YamlWriter::class],
      "set_input_encoding" => false,
      "set_output_encoding" => false,
    ],
    "line" => [
      "exts" => [],
      "formats" => ["line"],
      "reader" => [LineReader_command::class, LineReader::class],
      "writer" => [LineWriter_command::class, LineWriter::class],
      "set_input_encoding" => "setEncodingFilter",
      "set_output_encoding" => "setEncodingFilter",
    ],
  ];

  const IF_OPTION = ["-f", "--input", "args" => "file",
    "action" => [self::class, "set_input_file"],
  ];
  const IFO_OPTION = ["-F", "--input-format", "args" => "value",
    "action" => [self::class, "set_input_format"],
  ];
  const IE_OPTION = ["-e", "--input-encoding", "args" => "value",
    "action" => [self::class, "set_input_encoding"],
  ];
  const OF_OPTION = ["-o", "--output", "args" => "file",
    "action" => [self::class, "set_output_file"],
  ];
  const OFO_OPTION = ["-O", "--output-format", "args" => "value",
    "action" => [self::class, "set_output_format"],
  ];
  const OE_OPTION = ["-t", "--output-encoding", "args" => "value",
    "action" => [self::class, "set_output_encoding"],
  ];

  private static $input_file;

  static function set_input_file($file): void {
    self::$input_file = A::last(A::with($file));
  }

  private static $input_format;

  static function set_input_format(string $input_format) {
    self::$input_format = $input_format;
  }

  private static $input_encoding;
  
  static function set_input_encoding(string $input_encoding) {
    self::$input_encoding = $input_encoding;
  }

  private static $output_file;

  static function set_output_file($file): void {
    self::$output_file = A::last(A::with($file));
  }

  private static $output_format;

  static function set_output_format(string $output_format) {
    self::$output_format = $output_format;
  }

  private static $output_encoding;

  static function set_output_encoding(string $output_encoding) {
    self::$output_encoding = $output_encoding;
  }

  static function get(): array {
    $producer = null;
    $input_file = self::$input_file;
    $input_format = self::$input_format;
    if ($input_file !== null || $input_format !== null) {
      if ($input_file !== null && $input_format === null) {
        foreach (self::DEFS as $def) {
          foreach (A::with($def["exts"]) as $ext) {
            if (str::_ends_with($ext, $input_file)) {
              $input_format = A::first(A::with($def["formats"]));
              break 2;
            }
          }
        }
        if ($input_format === null) $input_format = "line";
      }
      $found = false;
      foreach (self::DEFS as $def) {
        foreach (A::with($def["formats"]) as $format) {
          if ($input_format == $format) {
            $found = true;
            $producerClass = $def["reader"][1];
            $producer = new $producerClass($input_file);
            $input_encoding = self::$input_encoding;
            if ($input_encoding !== null) {
              $setInputEncoding = $def["set_input_encoding"];
              if ($setInputEncoding) {
                $producer->$setInputEncoding($input_encoding);
              }
            }
            break 2;
          }
        }
      }
      if (!$found) {
        throw new ValueException("$input_format: format invalide");
      }
    }
    if ($producer === null) {
      foreach (self::DEFS as $def) {
        $commandClass = $def["reader"][0];
        $producer = $commandClass::get(false);
        if ($producer !== null) break;
      }
    }

    $consumer = null;
    $output_file = self::$output_file;
    $output_format = self::$output_format;
    if ($output_file !== null || $output_format !== null) {
      if ($output_file !== null && $output_format === null) {
        foreach (self::DEFS as $def) {
          foreach (A::with($def["exts"]) as $ext) {
            if (str::_ends_with($ext, $output_file)) {
              $output_format = A::first(A::with($def["formats"]));
              break 2;
            }
          }
        }
        if ($output_format === null) $output_format = "line";
      }
      $found = false;
      foreach (self::DEFS as $def) {
        foreach (A::with($def["formats"]) as $format) {
          if ($output_format == $format) {
            $found = true;
            $consumerClass = $def["writer"][1];
            $consumer = new $consumerClass($output_file);
            $output_encoding = self::$output_encoding;
            if ($output_encoding !== null) {
              $setOutputEncoding = $def["set_output_encoding"];
              if ($setOutputEncoding) {
                $consumer->$setOutputEncoding($output_encoding);
              }
            }
            break 2;
          }
        }
      }
      if (!$found) {
        throw new ValueException("$output_format: format invalide");
      }
    }
    if ($consumer === null) {
      foreach (self::DEFS as $def) {
        $commandClass = $def["writer"][0];
        $consumer = $commandClass::get(false);
        if ($consumer !== null) break;
      }
    }
    if ($consumer === null) {
      foreach (self::DEFS as $def) {
        $producerClass = $def["reader"][1];
        $commandClass = $def["writer"][0];
        if ($producer instanceof $producerClass) {
          $consumer = $commandClass::get();
          break;
        }
      }
    }

    if ($producer === null) $producer = LineReader_command::get();
    if ($consumer === null) $consumer = LineWriter_command::get();

    return [$producer, $consumer];
  }
}