<?php

namespace app\foreignone;

use engine\Model;
use app\ThemesUtil;

class Upload
    extends Model
{
    const DB_TABLE = 'uploads';

    const array ALLOWED_EXTENSIONS = [
        'jpg', 'png', 'git', 'mp4', 'mp3', 'ogg', 'diff', 'txt', 'gz', 'tar',
        'icc', 'icm', 'patch', 'zip', 'brd', 'pdf', 'lua', 'xpi', 'rar', '7z',
        'tgz', 'bin', 'py', 'pac', 'yaml', 'toml', 'xml', 'json', 'yml',
    ];

    const array IMAGE_EXTENSIONS = ['jpg', 'jpeg', 'png', 'gif'];
    const array VIDEO_EXTENSIONS = ['mp4', 'ogg'];

    public int $id;
    public string $randomId;
    public int $ts;
    public string $name;
    public int $size;
    public int $downloads; // TODO: remove
    public int $image; // TODO: remove
    public int $imageW;
    public int $imageH;
    public string $noteRu;
    public string $noteEn;
    public string $sourceUrl;

    public function getDirectory(): string {
        global $config;
        return $config['uploads_dir'].'/'.$this->randomId;
    }

    public function getDirectUrl(): string {
        global $config;
        return $config['uploads_path'].'/'.$this->randomId.'/'.$this->name;
    }

    public function getDirectPreviewUrl(int $w, int $h, bool $retina = false): string {
        global $config;
        if ($w == $this->imageW && $this->imageH == $h)
            return $this->getDirectUrl();

        if ($retina) {
            $w *= 2;
            $h *= 2;
        }

        $prefix = $this->imageMayHaveAlphaChannel() ? 'a' : 'p';
        return $config['uploads_path'].'/'.$this->randomId.'/'.$prefix.$w.'x'.$h.'.jpg';
    }

    public function getSize(): string {
        return sizeString($this->size);
    }

    public function getMarkdown(?string $options = null): string {
        if ($this->isImage()) {
            $md = '{image:'.$this->randomId.',w='.$this->imageW.',h='.$this->imageH.($options ? ','.$options : '').'}{/image}';
        } else if ($this->isVideo()) {
            $md = '{video:'.$this->randomId.($options ? ','.$options : '').'}{/video}';
        } else {
            $md = '{fileAttach:'.$this->randomId.($options ? ','.$options : '').'}{/fileAttach}';
        }
        $md .= ' <!-- '.$this->name.' -->';
        return $md;
    }

    public function setNote(PostLanguage $lang, string $note) {
        $db = getDB();
        $db->query("UPDATE uploads SET note_{$lang->value}=? WHERE id=?", $note, $this->id);
    }

    public function isImage(): bool {
        return in_array(extension($this->name), self::IMAGE_EXTENSIONS);
    }

    // assume all png images have alpha channel
    // i know this is wrong, but anyway
    public function imageMayHaveAlphaChannel(): bool {
        return strtolower(extension($this->name)) == 'png';
    }

    public function isVideo(): bool {
        return in_array(extension($this->name), self::VIDEO_EXTENSIONS);
    }

    public function getImageRatio(): float {
        return $this->imageW / $this->imageH;
    }

    public function getImagePreviewSize(int|string|null $w = null, int|string|null $h = null): array {
        if (is_string($w)) $w = (int)$w;
        if (is_string($h)) $h = (int)$h;

        if (is_null($w) && is_null($h))
            throw new \Exception(__METHOD__.': both width and height can\'t be null');

        if (is_null($h)) $h = round($w / $this->getImageRatio());
        if (is_null($w)) $w = round($h * $this->getImageRatio());

        return [$w, $h];
    }

    public function createImagePreview(?int $w = null,
                                       ?int $h = null,
                                       bool $force_update = false,
                                       bool $may_have_alpha = false): bool {
        global $config;

        $orig = $config['uploads_dir'].'/'.$this->randomId.'/'.$this->name;
        $updated = false;

        foreach (ThemesUtil::getThemes() as $theme) {
            if (!$may_have_alpha && $theme == 'dark')
                continue;

            for ($mult = 1; $mult <= 2; $mult++) {
                $dw = $w * $mult;
                $dh = $h * $mult;

                $prefix = $may_have_alpha ? 'a' : 'p';
                $dst = $config['uploads_dir'].'/'.$this->randomId.'/'.$prefix.$dw.'x'.$dh.($theme == 'dark' ? '_dark' : '').'.jpg';

                if (file_exists($dst)) {
                    if (!$force_update)
                        continue;
                    unlink($dst);
                }

                $img = imageopen($orig);
                imageresize($img, $dw, $dh, ThemesUtil::getThemeAlphaColorAsRGB($theme));
                imagejpeg($img, $dst, $mult == 1 ? 93 : 67);
                imagedestroy($img);

                setperm($dst);
                $updated = true;
            }
        }

        return $updated;
    }

    /**
     * @return int  Number of deleted files
     */
    public function deleteAllImagePreviews(): int {
        global $config;
        $dir = $config['uploads_dir'].'/'.$this->randomId;
        $files = scandir($dir);
        $deleted = 0;
        foreach ($files as $f) {
            if (preg_match('/^[ap](\d+)x(\d+)(?:_dark)?\.jpg$/', $f)) {
                if (is_file($dir.'/'.$f))
                    unlink($dir.'/'.$f);
                else
                    logError(__METHOD__.': '.$dir.'/'.$f.' is not a file!');
                $deleted++;
            }
        }
        return $deleted;
    }

    public function getJSONEncodedHtmlSafeNote(string $lang): string {
        $value = $lang == 'en' ? $this->noteEn : $this->noteRu;
        return jsonEncode(preg_replace('/(\r)?\n/', '\n', addslashes($value)));
    }


    /**
     * Static methods
     */

    public static function getCount(): int {
        $db = getDB();
        return (int)$db->result($db->query("SELECT COUNT(*) FROM uploads"));
    }

    public static function isExtensionAllowed(string $ext): bool {
        return in_array($ext, self::ALLOWED_EXTENSIONS);
    }

    public static function add(string $tmp_name,
                               string $name,
                               string $note_en = '',
                               string $note_ru = '',
                               string $source_url = ''): ?int {
        global $config;

        $name = sanitizeFilename($name);
        if (!$name)
            $name = 'file';

        $random_id = self::getNewUploadRandomId();
        $size = filesize($tmp_name);
        $is_image = detectImageType($tmp_name) !== false;
        $image_w = 0;
        $image_h = 0;
        if ($is_image) {
            list($image_w, $image_h) = getimagesize($tmp_name);
        }

        $db = getDB();
        if (!$db->insert('uploads', [
            'random_id' => $random_id,
            'ts' => time(),
            'name' => $name,
            'size' => $size,
            'image' => (int)$is_image,
            'image_w' => $image_w,
            'image_h' => $image_h,
            'note_ru' => $note_ru,
            'note_en' => $note_en,
            'downloads' => 0,
            'source_url' => $source_url,
        ])) {
            return null;
        }

        $id = $db->insertId();

        $dir = $config['uploads_dir'].'/'.$random_id;
        $path = $dir.'/'.$name;

        mkdir($dir);
        chmod($dir, 0775); // g+w

        rename($tmp_name, $path);
        setperm($path);

        return $id;
    }

    public static function delete(int $id): bool {
        $upload = self::get($id);
        if (!$upload)
            return false;

        $db = getDB();
        $db->query("DELETE FROM uploads WHERE id=?", $id);

        rrmdir($upload->getDirectory());
        return true;
    }

    /**
     * @return Upload[]
     */
    public static function getAllUploads(): array {
        $db = getDB();
        $q = $db->query("SELECT * FROM uploads ORDER BY id DESC");
        return array_map(static::create_instance(...), $db->fetchAll($q));
    }

    public static function get(int $id): ?Upload {
        $db = getDB();
        $q = $db->query("SELECT * FROM uploads WHERE id=?", $id);
        if ($db->numRows($q)) {
            return new Upload($db->fetch($q));
        } else {
            return null;
        }
    }

    /**
     * @param string[] $ids
     * @param bool $flat
     * @return Upload[]
     */
    public static function getUploadsByRandomId(array $ids, bool $flat = false): array {
        if (empty($ids)) {
            return [];
        }

        $db = getDB();
        $uploads = array_fill_keys($ids, null);

        $q = $db->query("SELECT * FROM uploads WHERE random_id IN('".implode('\',\'', array_map([$db, 'escape'], $ids))."')");

        while ($row = $db->fetch($q)) {
            $uploads[$row['random_id']] = new Upload($row);
        }

        if ($flat) {
            $list = [];
            foreach ($ids as $id) {
                $list[] = $uploads[$id];
            }
            unset($uploads);
            return $list;
        }

        return $uploads;
    }

    public static function getUploadByRandomId(string $random_id): ?Upload {
        $db = getDB();
        $q = $db->query("SELECT * FROM uploads WHERE random_id=? LIMIT 1", $random_id);
        if ($db->numRows($q)) {
            return new Upload($db->fetch($q));
        } else {
            return null;
        }
    }

    public static function getUploadBySourceUrl(string $source_url): ?Upload {
        $db = getDB();
        $q = $db->query("SELECT * FROM uploads WHERE source_url=? LIMIT 1", $source_url);
        if ($db->numRows($q)) {
            return new Upload($db->fetch($q));
        } else {
            return null;
        }
    }

    protected static function getNewUploadRandomId(): string {
        $db = getDB();
        do {
            $random_id = strgen(8);
        } while ($db->numRows($db->query("SELECT id FROM uploads WHERE random_id=?", $random_id)) > 0);
        return $random_id;
    }
}