<?php

class FileUpload
{
    private $_destination,
            $_errors = [],
            $_validation,
            $_uploadedFiles,
            $_multiple,
            $_prefix = '',
            $_suffix = '',
            $_name = [];

    public function __construct($destination, $validation = [], $uploadedFiles = [], $multiple = false)
    {
        if (!is_dir($destination)) {
            if (!mkdir($destination, 0777, true)) {
                $this->setError("Failed to create destination directory.");
                return false;
            }
        }

        if (!is_writable($destination)) {
            if (!chmod($destination, 0777)) {
                $this->setError("Upload directory is not writable and cannot set permissions.");
                return false;
            }
        }

        if (empty($validation)) {
            $this->setError("Validation not supplied.");
            return false;
        }

        $this->_multiple = $multiple;
        $this->_destination = rtrim($destination, '/') . '/';
        $this->_validation = $validation;
        $this->_uploadedFiles = $uploadedFiles;
    }

    public function setPrefix($string = "")
    {
        $this->_prefix = $string;
    }

    public function setSuffix($string = "")
    {
        $this->_suffix = $string;
    }

    public function upload()
    {
        if (!empty($this->_errors)) return false;

        return $this->_multiple ? $this->multiCheck() : $this->singleCheck();
    }

    private function getExtension($filename)
    {
        return strtolower(pathinfo($filename, PATHINFO_EXTENSION));
    }

    private function sanitizeFilename($name)
    {
        return preg_replace("/[^a-zA-Z0-9_\.-]/", "_", pathinfo($name, PATHINFO_FILENAME));
    }

    private function getRandomName($originalName)
    {
        return $this->_prefix . $this->sanitizeFilename($originalName) . '-' . uniqid() . $this->_suffix;
    }

    private function validate($file)
    {
        foreach ($this->_validation as $key => $value) {
            switch ($key) {
                case "type":
                    $allowedTypes = explode(",", $value[0]);
                    $ext = $this->getExtension($file["name"]);
                    $finfo = finfo_open(FILEINFO_MIME_TYPE);
                    $mime = finfo_file($finfo, $file["tmp_name"]);
                    finfo_close($finfo);

                    $mimeAllowed = ['image/jpeg', 'image/png', 'image/gif', 'image/webp'];
                    if (!in_array($ext, $allowedTypes) || !in_array($mime, $mimeAllowed)) {
                        $this->setError("Invalid file format.");
                        return false;
                    }
                    break;

                case "size":
                    if (($file["size"] / 1024 / 1024) > $value) {
                        $this->setError("File size must be under {$value}MB.");
                        return false;
                    }
                    break;
            }
        }
        return true;
    }

    private function checkUploaded($file)
    {
        return is_uploaded_file($file);
    }

    private function singleCheck()
    {
        if (!$this->checkUploaded($this->_uploadedFiles["tmp_name"])) {
            $this->setError("Please upload a file.");
            return false;
        }

        if (!$this->validate($this->_uploadedFiles)) return false;

        $ext = $this->getExtension($this->_uploadedFiles["name"]);
        $filename = $this->getRandomName($this->_uploadedFiles["name"]) . "." . $ext;

        if (!move_uploaded_file($this->_uploadedFiles["tmp_name"], $this->_destination . $filename)) {
            $this->setError("Could not move the uploaded file.");
            return false;
        }

        $this->setImagename($filename);
        return true;
    }

    private function multiCheck()
    {
        $files = $this->rearrangeFiles($this->_uploadedFiles);

        foreach ($files as $file) {
            if (!$this->checkUploaded($file["tmp_name"]) || !$this->validate($file)) {
                return false;
            }

            $ext = $this->getExtension($file["name"]);
            $filename = $this->getRandomName($file["name"]) . "." . $ext;

            if (!move_uploaded_file($file["tmp_name"], $this->_destination . $filename)) {
                $this->setError("Could not move file: " . $file["name"]);
                return false;
            }

            $this->setImagename($filename);
        }

        return true;
    }

    private function rearrangeFiles($files)
    {
        $result = [];
        foreach ($files as $key => $list) {
            foreach ($list as $index => $value) {
                $result[$index][$key] = $value;
            }
        }
        return $result;
    }

    private function setImagename($filename)
    {
        $this->_name[] = $filename;
    }

    private function setError($error)
    {
        $this->_errors[] = $error;
    }

    public function passed()
    {
        return empty($this->_errors);
    }

    public function displayErrors()
    {
        return $this->_errors ?: false;
    }

    public function getName()
    {
        return $this->_name ?: false;
    }
}
