<?php
namespace Misuzu\Users\Assets;

use InvalidArgumentException;
use RuntimeException;
use Misuzu\Users\UserInfo;

abstract class UserImageAsset implements UserImageAssetInterface {
    public const PUBLIC_STORAGE = '/msz-storage';

    public const TYPE_PNG = IMAGETYPE_PNG;
    public const TYPE_JPG = IMAGETYPE_JPEG;
    public const TYPE_GIF = IMAGETYPE_GIF;

    public const TYPES_EXT = [
        self::TYPE_PNG => 'png',
        self::TYPE_JPG => 'jpg',
        self::TYPE_GIF => 'gif',
    ];

    protected string $userId;

    public function __construct(UserInfo $userInfo) {
        $this->userId = (string)$userInfo->getId();
    }

    public function getUserId(): string {
        return $this->userId;
    }

    public abstract function getMaxWidth(): int;
    public abstract function getMaxHeight(): int;
    public abstract function getMaxBytes(): int;

    public function getAllowedTypes(): array {
        return [self::TYPE_PNG, self::TYPE_JPG, self::TYPE_GIF];
    }
    public function isAllowedType(int $type): bool {
        return in_array($type, $this->getAllowedTypes());
    }

    private function getImageSize(): array {
        return $this->isPresent() && ($imageSize = getimagesize($this->getPath())) ? $imageSize : [];
    }
    public function getWidth(): int {
        return $this->getImageSize()[0] ?? -1;
    }
    public function getHeight(): int {
        return $this->getImageSize()[1] ?? -1;
    }
    public function getIntType(): int {
        return $this->getImageSize()[2] ?? -1;
    }
    public function getMimeType(): string {
        return mime_content_type($this->getPath());
    }
    public function getFileExtension(): string {
        return self::TYPES_EXT[$this->getIntType()] ?? 'img';
    }

    public abstract function getFileName(): string;

    public abstract function getRelativePath(): string;
    public function isPresent(): bool {
        return is_file($this->getPath());
    }

    public function getPublicPath(): string {
        return self::PUBLIC_STORAGE . '/' . $this->getRelativePath();
    }

    public function delete(): void {
        if($this->isPresent())
            unlink($this->getPath());
    }

    public function getStoragePath(): string {
        return MSZ_ROOT . DIRECTORY_SEPARATOR . 'store';
    }

    public function getPath(): string {
        return $this->getStoragePath() . DIRECTORY_SEPARATOR . $this->getRelativePath();
    }

    public function setFromPath(string $path): void {
        if(!is_file($path))
            throw new InvalidArgumentException('$path not found.');

        $imageInfo = getimagesize($path);
        if($imageInfo === false || $imageInfo[0] < 1 || $imageInfo[1] < 1)
            throw new InvalidArgumentException('$path is not a valid image.');

        if(!self::isAllowedType($imageInfo[2]))
            throw new InvalidArgumentException('$path is not an allowed image file.');

        if($imageInfo[0] > $this->getMaxWidth() || $imageInfo[1] > $this->getMaxHeight())
            throw new InvalidArgumentException('Dimensions of $path are too large.');

        if(filesize($path) > $this->getMaxBytes())
            throw new InvalidArgumentException('File size of $path is too large.');

        $this->delete();

        $targetPath = $this->getPath();
        $targetDir = dirname($targetPath);
        if(!is_dir($targetDir))
            mkdir($targetDir, 0775, true);

        if(is_uploaded_file($path) ? !move_uploaded_file($path, $targetPath) : !copy($path, $targetPath))
            throw new RuntimeException('Was unable to move $path to the storage destination.');
    }

    public function setFromData(string $data): void {
        $file = tempnam(sys_get_temp_dir(), 'msz');
        if($file === false || !is_file($file))
            throw new RuntimeException('Failed to apply user asset data.');
        chmod($file, 0664);
        file_put_contents($file, $data);
        self::setFromPath($file);
        unlink($file);
    }
}
