<?php
namespace nur\b\io;

use nur\sery\file\FileReader;
use nur\v\http;

/**
 * Class TmpfileWriter: écriture dans un fichier temporaire
 */
class TmpfileWriter extends FileWriter {
  /** @var string */
  private $output;

  /** @var bool */
  private $delete;

  /** @throws IOException */
  function __construct(?string $destdir=null, string $mode="w+") {
    if ($destdir === null) $destdir = sys_get_temp_dir();
    if (is_dir($destdir)) {
      # si on spécifie un répertoire, créer un fichier temporaire dedans
      $output = tempnam($destdir, "tmp_nur-base_");
      $this->delete = true;
    } elseif (is_file($destdir)) {
      # si on spécifie un fichier qui existe le prendre comme "fichier
      # temporaire" mais ne pas le supprimer automatiquement
      $output = $destdir;
      $this->delete = false;
    } else {
      # un chemin qui n'existe pas: ne le sélectionner que si le répertoire
      # existe. dans ce cas, le fichier sera créé automatiquement, mais pas
      # supprimé
      if (!is_dir(dirname($destdir))) {
        throw new IOException("$destdir: no such file or directory");
      }
      $output = $destdir;
      $this->delete = false;
    }
    $this->output = $output;
    parent::__construct($output, $mode);
  }

  function __destruct() {
    $this->close();
    if ($this->delete) $this->delete();
  }

  /**
   * obtenir une nouvelle instance de {@link FileReader} sur le fichier
   *
   * cette méthode est utile si le flux a été fermé, à condition que le fichier
   * n'aie pas été supprimé ni renommé
   *
   * @throws IOException
   */
  function getReader(): FileReader {
    return new FileReader($this->output);
  }

  /**
   * obtenir une nouvelle instance de FileWriter sur le fichier
   *
   * cette méthode est utile si le flux a été fermé, à condition que le fichier
   * n'aie pas été supprimé ni renommé
   *
   * @throws IOException
   */
  function getWriter(): FileWriter {
    return new FileWriter($this->output);
  }

  /** supprimer le fichier. NB: le flux **n'est pas** fermé au préalable */
  function delete(): self {
    if (file_exists($this->output)) unlink($this->output);
    return $this;
  }

  /**
   * renommer le fichier. le flux est fermé d'abord
   *
   * @param int|null $default_mode mode par défaut si le fichier destination
   * n'existe pas. sinon, changer le mode du fichier temporaire à la valeur du
   * fichier destination après renommage
   * @param bool $set_owner si le propriétaire et/ou le groupe du fichier
   * temporaire ne sont pas les mêmes que le fichier destination, tenter de
   * changer le propriétaire et le groupe du fichier temporaire à la valeur
   * du fichier destination après le renommage (nécessite les droits de root)
   * @throws IOException
   */
  function rename(string $dest, ?int $default_mode=0644, bool $set_owner=true): void {
    $this->close();
    $output = $this->output;
    if (file_exists($dest)) {
      $mode = fileperms($dest);
      if ($set_owner) {
        $tmpowner = fileowner($output);
        $owner = fileowner($dest);
        $tmpgroup = filegroup($output);
        $group = filegroup($dest);
      } else {
        $owner = $group = null;
      }
    } else {
      $mode = $default_mode;
      $owner = $group = null;
    }
    if (!rename($output, $dest)) {
      throw new IOException("$output: unable to rename to $dest");
    }
    if ($mode !== null) chmod($dest, $mode);
    if ($owner !== null) {
      if ($owner !== $tmpowner) chown($dest, $owner);
      if ($group !== $tmpgroup) chgrp($dest, $group);
    }
    if ($mode !== null || $owner !== null) clearstatcache();
  }

  /** streamer le contenu du fichier en sortie */
  function readfile(?string $content_type=null, ?string $charset=null, ?string $filename=null, string $disposition=null): bool {
    if ($content_type !== null) http::content_type($content_type, $charset);
    if ($filename !== null) http::download_as($filename, $disposition);
    return readfile($this->output) !== false;
  }
}