<?php
// FileStream.php
// Created: 2021-04-30
// Updated: 2024-07-31

namespace Index\IO;

use ErrorException;

class FileStream extends GenericStream {
    public const OPEN_READ                  = 'rb';  // O  R   E  // Open file for reading, throw if not exists
    public const OPEN_READ_WRITE            = 'r+b'; // O  RW  E  // Open file for reading and writing, throw if not exist
    public const NEW_WRITE                  = 'wb';  //  C  W   T // Create file for writing, truncate if exist
    public const NEW_READ_WRITE             = 'w+b'; //  C RW   T // Create file for reading and writing, truncate if exist
    public const APPEND_WRITE               = 'ab';  // OC   A    // Open file for appending, create if not exist
    public const APPEND_READ_WRITE          = 'a+b'; // OC R A    // Open file for reading and appending, create if not exist
    public const CREATE_WRITE               = 'xb';  //  C  W   T // Create file for writing, throw if exist
    public const CREATE_READ_WRITE          = 'x+b'; //  C RW   T // Create file for reading and writing, throw if exist
    public const OPEN_OR_CREATE_WRITE       = 'cb';  // OC  W     // Opens or creates a file for writing
    public const OPEN_OR_CREATE_READ_WRITE  = 'c+b'; // OC RW     // Opens or creates a file for reading and writing

    public const LOCK_NONE         = 0;
    public const LOCK_READ         = LOCK_SH;
    public const LOCK_WRITE        = LOCK_EX;
    public const LOCK_NON_BLOCKING = LOCK_NB;

    private int $lock;

    public function __construct(string $path, string $mode, int $lock) {
        $this->lock = $lock;

        try {
            $stream = fopen($path, $mode);
        } catch(ErrorException $ex) {
            throw new IOException('An error occurred while trying to open a file.', $ex->getCode(), $ex);
        }

        if($stream === false)
            throw new IOException('An unhandled error occurred while trying to open a file.');

        parent::__construct($stream);

        if($this->lock & self::LOCK_WRITE)
            flock($this->stream, LOCK_EX);
        elseif($this->lock & self::LOCK_READ)
            flock($this->stream, LOCK_SH);
    }

    public function close(): void {
        if($this->lock !== self::LOCK_NONE)
            flock($this->stream, LOCK_UN);
        parent::close();
    }

    public static function openRead(string $path, int $lock = self::LOCK_NONE): FileStream {
        return new FileStream($path, self::OPEN_READ, $lock);
    }
    public static function openReadWrite(string $path, int $lock = self::LOCK_NONE): FileStream {
        return new FileStream($path, self::OPEN_READ_WRITE, $lock);
    }
    public static function newWrite(string $path, int $lock = self::LOCK_NONE): FileStream {
        return new FileStream($path, self::NEW_WRITE, $lock);
    }
    public static function newReadWrite(string $path, int $lock = self::LOCK_NONE): FileStream {
        return new FileStream($path, self::NEW_READ_WRITE, $lock);
    }
    public static function appendWrite(string $path, int $lock = self::LOCK_NONE): FileStream {
        return new FileStream($path, self::APPEND_WRITE, $lock);
    }
    public static function appendReadWrite(string $path, int $lock = self::LOCK_NONE): FileStream {
        return new FileStream($path, self::APPEND_READ_WRITE, $lock);
    }
    public static function createWrite(string $path, int $lock = self::LOCK_NONE): FileStream {
        return new FileStream($path, self::CREATE_WRITE, $lock);
    }
    public static function createReadWrite(string $path, int $lock = self::LOCK_NONE): FileStream {
        return new FileStream($path, self::CREATE_READ_WRITE, $lock);
    }
    public static function openOrCreateWrite(string $path, int $lock = self::LOCK_NONE): FileStream {
        return new FileStream($path, self::OPEN_OR_CREATE_WRITE, $lock);
    }
    public static function openOrCreateReadWrite(string $path, int $lock = self::LOCK_NONE): FileStream {
        return new FileStream($path, self::OPEN_OR_CREATE_READ_WRITE, $lock);
    }
}