<?php
namespace Site\Lib;

enum Delimiter: int {
  case COMMA = 1;
  case SEMICOLON = 2;
  case TAB = 3;
  case PIPE = 4;

  public static function default(): self {
    return self::COMMA;
  }

  public function getChar(): string {
    return match ($this) {
      self::COMMA => ",",
      self::SEMICOLON => ";",
      self::TAB => "\t",
      self::PIPE => "|",
    };
  }
}

/**
 * CSVパーシングクラス
 */
class Csv {
  // リクエスト関連のプロパティ
  private bool $isHeader = false;
  private int $length = 8192;
  private Delimiter $delimiter;
  private string $filename;
  private string $enclosure = "\"";
  private string $escape = "\\";
  private $fp;

  public function __construct(string $filename, int $length = 8192) {
    $this->length = $length;
    $this->filename = $filename;
    $this->delimiter = Delimiter::default();

    $this->fp = fopen($this->filename, 'r');
    if ($this->fp === false) {
      $msg = "ファイルを開けられません。";
      logger(\LogType::Csv, $msg);
      throw new \Exception($msg);
    }
  }

  public function __destruct() {
    if ($this->fp !== false) {
      fclose($this->fp);
    }
  }

  public function parse(?Delimiter $delimiter = null, bool $isHeader = false): array {
    $res = [];
    $this->isHeader = $isHeader;
    $this->delimiter = $delimiter ?? $this->delimiter;

    $delim = ($delimiter ?? $this->delimiter)->getChar();
    rewind($this->fp);

    if ($this->isHeader) {
      $res = ['header' => [], 'body' => []];
      $head = fgets($this->fp, $this->length);
      if ($head !== false) {
        $res['header'] = str_getcsv($head, $delim, $this->enclosure, $this->escape);
      }
    }

    while (($buffer = fgets($this->fp, $this->length)) !== false) {
      $row = str_getcsv($buffer, $delim, $this->enclosure, $this->escape);
      if ($this->isHeader) $res['body'][] = $row;
      else $res[] = $row;
    }

    if (!feof($this->fp)) {
      $msg = "エラー:fgets()の失敗";
      logger(\LogType::Csv, $msg);
      throw new \Exception($msg);
    }

    return $res;
  }

  public function write(array $list, ?Delimiter $delimiter = null): bool {
    $fp = fopen($this->filename, 'w');
    if ($fp === false) {
      $msg = "ファイルに書き込めません。";
      logger(\LogType::Csv, $msg);
      throw new \Exception($msg);
    }

    $delim = ($delimiter ?? $this->delimiter)->getChar();

    foreach ($list as $l) {
      if (fputcsv($fp, $l, $delim, $this->enclosure, $this->escape) === false) {
        fclose($fp);
        $msg = "CSVデータの書き込みに失敗";
        logger(\LogType::Csv, $msg);
        throw new \Exception($msg);
      }
    }

    return fclose($fp);
  }
}