<?php

namespace Hura8\System;

class FileSystem
{
    /**
     * The cache directory.
     *
     * @var string
     */
    protected $directory;

    /** @var int */
    private $umask;

    /**
     * @param string $directory The cache directory.
     * @param int    $umask
     *
     * @throws \Exception
     */
    public function __construct($directory, $umask = 0002)
    {
        // YES, this needs to be *before* createPathIfNeeded()
        if (! is_int($umask)) {
            throw new \Exception(sprintf(
                'The umask parameter is required to be integer, was: %s',
                gettype($umask)
            ));
        }

        $this->umask = $umask;

        if (! $this->createPathIfNeeded($directory)) {
            throw new \Exception(sprintf(
                'The directory "%s" does not exist and could not be created.',
                $directory
            ));
        }

        if (! is_writable($directory)) {
            throw new \Exception(sprintf(
                'The directory "%s" is not writable.',
                $directory
            ));
        }

        // YES, this needs to be *after* createPathIfNeeded()
        $this->directory = realpath($directory);

        //$this->directoryStringLength = strlen($this->directory);
        //$this->isRunningOnWindows    = defined('PHP_WINDOWS_VERSION_BUILD');
    }


    /**
     * @description extract a zip file after it has been uploaded to the server
     * @param string $filename name of the zip file (abc.zip)
     * @param string $uploaded_zip_path path where it's uploaded to i.e /usr/.../tmp_upload/
     * @param string $extract_target_path path where the zip files will be extracted to , if not provided the $uploaded_zip_path will be used
     * @return array [extracted_folder, test_folder_exist]
     */
    public static function unzip(string $filename, string $uploaded_zip_path, string $extract_target_path = '') {
        $extract_path = $extract_target_path ?: $uploaded_zip_path;

        $zip = new \ZipArchive();
        $x = $zip->open($uploaded_zip_path . DIRECTORY_SEPARATOR . $filename);
        if ($x === true) {
            $zip->extractTo($extract_path); // change this to the correct site path
            $zip->close();
        }
        @unlink($uploaded_zip_path . DIRECTORY_SEPARATOR . $filename);

        $expected_result = $extract_path . DIRECTORY_SEPARATOR . str_replace(".zip", "", $filename);

        return array($expected_result, file_exists($expected_result));
    }


    /**
     * @description scan a folder recursively and return all files with sub-path
     * @param string $folder full path /usr/local/.../
     * @param array $file_list
     */
    public static function scanDirRecursive(string $folder, array &$file_list = []) {
        $dir = opendir($folder);
        while(( $file = readdir($dir)) ) {
            if (( $file != '.' ) && ( $file != '..' )) {
                if ( is_dir($folder. '/' . $file) ) {
                    FileSystem::scanDirRecursive($folder. '/' . $file, $file_list);
                }
                else {
                    //$file_list[] = str_replace(PUBLIC_DIR . "/", "", $folder .'/'. $file);
                    $file_list[] = $folder .'/'. $file;
                }
            }
        }
        closedir($dir);
    }

    //remove directory recursively
    public static function removeDir($dir) {
        if (is_dir($dir)) {
            $objects = scandir($dir);
            foreach ($objects as $object) {
                if ($object != "." && $object != "..") {
                    if (is_dir($dir."/".$object)) {
                        self::removeDir($dir."/".$object);
                    }
                    else {
                        @unlink($dir."/".$object);
                    }
                }
            }
            @rmdir($dir);
        }
    }

    public function getFile(string $filename)
    {
        $full_filepath = $this->getFilename($filename);
        if(file_exists($full_filepath)) {
            return file_get_contents($full_filepath);
        }

        return false;
    }

    public function getFileLastModifiedTime(string $filename)
    {
        $full_filepath = $this->getFilename($filename);
        if(file_exists($full_filepath)) {
            return filemtime($full_filepath);
        }

        return 0;
    }

    /**
     * Writes a string content to file in an atomic way.
     *
     * @param string $filename Path to the file where to write the data.
     * @param string $content  The content to write
     *
     * @return bool TRUE on success, FALSE if path cannot be created, if path is not writable or an any other error.
     */
    public function writeFile(string $filename, string $content)
    {
        $full_filepath = $this->getFilename($filename);
        $filepath = pathinfo($full_filepath, PATHINFO_DIRNAME);

        if (! $this->createPathIfNeeded($filepath)) {
            return false;
        }

        if (! is_writable($filepath)) {
            return false;
        }

        $tmpFile = tempnam($filepath, 'swap');
        @chmod($tmpFile, 0666 & (~$this->umask));

        if (file_put_contents($tmpFile, $content) !== false) {

            @chmod($tmpFile, 0666 & (~$this->umask));
            if (@rename($tmpFile, $full_filepath)) {
                return true;
            }

            @unlink($tmpFile);
        }

        return false;
    }

    public function delete($filename)
    {
        $full_filepath = $this->getFilename($filename);

        return @unlink($full_filepath) || ! file_exists($full_filepath);
    }

    /**
     * @param string $filename
     *
     * @return string
     */
    public function getFilename($filename)
    {
        return $this->directory
            . DIRECTORY_SEPARATOR
            . $filename ;
    }

    /**
     * Create path if needed.
     *
     * @return bool TRUE on success or if path already exists, FALSE if path cannot be created.
     */
    private function createPathIfNeeded(string $path)
    {
        if (! is_dir($path)) {
            if (@mkdir($path, 0755 & (~$this->umask), true) === false && ! is_dir($path)) {
                return false;
            }
        }

        return true;
    }

}
