<?php

declare(strict_types=1);

namespace openvk\Web\Models\Entities;

use openvk\Web\Util\Shell\Shell;
use openvk\Web\Util\Shell\Exceptions\{ShellUnavailableException, UnknownCommandException};
use openvk\Web\Models\VideoDrivers\VideoDriver;
use Nette\InvalidStateException as ISE;

define("VIDEOS_FRIENDLY_ERROR", "Uploads are disabled on this instance :<", false);

class Video extends Media
{
    public const TYPE_DIRECT = 0;
    public const TYPE_EMBED  = 1;

    protected $tableName     = "videos";
    protected $fileExtension = "mp4";

    protected $processingPlaceholder = "video/rendering";

    protected function saveFile(string $filename, string $hash): bool
    {
        if (!Shell::commandAvailable("ffmpeg") || !Shell::commandAvailable("ffprobe")) {
            exit(VIDEOS_FRIENDLY_ERROR);
        }

        $error     = null;
        $streams   = Shell::ffprobe("-i", $filename, "-show_streams", "-select_streams v", "-loglevel error")->execute($error);
        if ($error !== 0) {
            throw new \DomainException("$filename is not a valid video file");
        } elseif (empty($streams) || ctype_space($streams)) {
            throw new \DomainException("$filename does not contain any video streams");
        }

        $durations = [];
        preg_match_all('%duration=([0-9\.]++)%', $streams, $durations);
        if (sizeof($durations[1]) === 0) {
            throw new \DomainException("$filename does not contain any meaningful video streams");
        }

        $length = 0;
        foreach ($durations[1] as $duration) {
            $duration = floatval($duration);
            if ($duration < 1.0) {
                throw new \DomainException("$filename does not contain any meaningful video streams");
            } else {
                $length = max($length, $duration);
            }
        }

        $this->stateChanges("length", (int) round($length, 0, PHP_ROUND_HALF_EVEN));

        preg_match('%width=([0-9\.]++)%', $streams, $width);
        preg_match('%height=([0-9\.]++)%', $streams, $height);
        if (!empty($width) && !empty($height)) {
            $this->stateChanges("width", $width[1]);
            $this->stateChanges("height", $height[1]);
        }

        try {
            if (!is_dir($dirId = dirname($this->pathFromHash($hash)))) {
                mkdir($dirId);
            }

            $dir = $this->getBaseDir();
            $ext = Shell::isPowershell() ? "ps1" : "sh";
            $cmd = Shell::isPowershell() ? "powershell" : "bash";
            Shell::$cmd(__DIR__ . "/../shell/processVideo.$ext", OPENVK_ROOT, $filename, $dir, $hash)->start(); #async :DDD
        } catch (ShellUnavailableException $suex) {
            exit(OPENVK_ROOT_CONF["openvk"]["debug"] ? "Shell is unavailable" : VIDEOS_FRIENDLY_ERROR);
        } catch (UnknownCommandException $ucex) {
            exit(OPENVK_ROOT_CONF["openvk"]["debug"] ? "bash is not installed" : VIDEOS_FRIENDLY_ERROR);
        }

        usleep(200100);
        return true;
    }

    protected function checkIfFileIsProcessed(): bool
    {
        if ($this->getType() != Video::TYPE_DIRECT) {
            return true;
        }

        if (!file_exists($this->getFileName())) {
            if ((time() - $this->getRecord()->last_checked) > 3600) {
                # TODO notify that video processor is probably dead
            }

            return false;
        }

        return true;
    }

    public function getName(): string
    {
        return $this->getRecord()->name;
    }

    public function getType(): int
    {
        if (!is_null($this->getRecord()->hash)) {
            return Video::TYPE_DIRECT;
        } elseif (!is_null($this->getRecord()->link)) {
            return Video::TYPE_EMBED;
        }
    }

    public function getVideoDriver(): ?VideoDriver
    {
        if ($this->getType() !== Video::TYPE_EMBED) {
            return null;
        }

        [$videoDriver, $pointer] = explode(":", $this->getRecord()->link);
        $videoDriver = "openvk\\Web\\Models\\VideoDrivers\\$videoDriver" . "VideoDriver";
        if (!class_exists($videoDriver)) {
            return null;
        }

        return new $videoDriver($pointer);
    }

    public function getThumbnailURL(): string
    {
        if ($this->getType() === Video::TYPE_DIRECT) {
            if (!$this->isProcessed()) {
                return "/assets/packages/static/openvk/video/rendering.apng";
            }

            return preg_replace("%\.[A-z0-9]++$%", ".gif", $this->getURL());
        } else {
            return $this->getVideoDriver()->getThumbnailURL();
        }
    }

    public function getOwnerVideo(): int
    {
        return $this->getRecord()->owner;
    }

    public function getApiStructure(?User $user = null): object
    {
        $fromYoutube = $this->getType() == Video::TYPE_EMBED;
        $dimensions  = $this->getDimensions();
        $res = (object) [
            "type" => "video",
            "video" => [
                "can_comment" => 1,
                "can_like" => 1,  // we don't h-have wikes in videos
                "can_repost" => 1,
                "can_subscribe" => 1,
                "can_add_to_faves" => 0,
                "can_add" => 0,
                "comments" => $this->getCommentsCount(),
                "date" => $this->getPublicationTime()->timestamp(),
                "description" => $this->getDescription(),
                "duration" => $this->getLength(),
                "image" => [
                    [
                        "url" => $this->getThumbnailURL(),
                        "width" => 320,
                        "height" => 240,
                        "with_padding" => 1,
                    ],
                ],
                "width" => $dimensions ? $dimensions[0] : 640,
                "height" => $dimensions ? $dimensions[1] : 480,
                "id" => $this->getVirtualId(),
                "owner_id" => $this->getOwner()->getId(),
                "user_id" => $this->getOwner()->getId(),
                "title" => $this->getName(),
                "is_favorite" => false,
                "player" => !$fromYoutube ? $this->getURL() : $this->getVideoDriver()->getURL(),
                "files" => !$fromYoutube ? [
                    "mp4_480" => $this->getURL(),
                ] : null,
                "platform" => $fromYoutube ? "youtube" : null,
                "added" => 0,
                "repeat" => 0,
                "type" => "video",
                "views" => 0,
                "is_processed" => $this->isProcessed(),
                "reposts" => [
                    "count" => 0,
                    "user_reposted" => 0,
                ],
            ],
        ];

        if (!is_null($user)) {
            $res->video["likes"] = [
                "count" => $this->getLikesCount(),
                "user_likes" => $this->hasLikeFrom($user),
            ];
        }

        return $res;
    }

    public function toVkApiStruct(?User $user): object
    {
        return $this->getApiStructure($user);
    }

    public function setLink(string $link): string
    {
        if (preg_match(file_get_contents(__DIR__ . "/../VideoDrivers/regex/youtube.txt"), $link, $matches)) {
            $pointer = "YouTube:$matches[1]";
            /*} else if(preg_match(file_get_contents(__DIR__ . "/../VideoDrivers/regex/vimeo.txt"), $link, $matches)) {
                $pointer = "Vimeo:$matches[1]";*/
        } else {
            throw new ISE("Invalid link");
        }

        $this->stateChanges("link", $pointer);

        return $pointer;
    }

    public function isDeleted(): bool
    {
        if ($this->getRecord()->deleted == 1) {
            return true;
        } else {
            return false;
        }
    }

    public function deleteVideo(): void
    {
        $this->setDeleted(1);
        $this->unwire();
        $this->save();
    }

    public static function fastMake(int $owner, string $name = "Unnamed Video.ogv", string $description = "", array $file, bool $unlisted = true, bool $anon = false): Video
    {
        if (OPENVK_ROOT_CONF['openvk']['preferences']['videos']['disableUploading']) {
            exit(VIDEOS_FRIENDLY_ERROR);
        }

        $video = new Video();
        $video->setOwner($owner);
        $video->setName(ovk_proc_strtr($name, 61));
        $video->setDescription(ovk_proc_strtr($description, 300));
        $video->setAnonymous($anon);
        $video->setCreated(time());
        $video->setFile($file);
        $video->setUnlisted($unlisted);
        $video->save();

        return $video;
    }

    public function fillDimensions()
    {
        $hash  = $this->getRecord()->hash;
        $path  = $this->pathFromHash($hash);
        if (!file_exists($path)) {
            $this->stateChanges("width", 0);
            $this->stateChanges("height", 0);
            $this->stateChanges("length", 0);
            $this->save();
            return false;
        }

        $streams   = Shell::ffprobe("-i", $path, "-show_streams", "-select_streams v", "-loglevel error")->execute($error);
        $durations = [];
        preg_match_all('%duration=([0-9\.]++)%', $streams, $durations);

        $length = 0;
        foreach ($durations[1] as $duration) {
            $duration = floatval($duration);
            if ($duration < 1.0) {
                continue;
            } else {
                $length = max($length, $duration);
            }
        }
        $this->stateChanges("length", (int) round($length, 0, PHP_ROUND_HALF_EVEN));

        preg_match('%width=([0-9\.]++)%', $streams, $width);
        preg_match('%height=([0-9\.]++)%', $streams, $height);

        if (!empty($width) && !empty($height)) {
            $this->stateChanges("width", $width[1]);
            $this->stateChanges("height", $height[1]);
        }

        $this->save();

        return true;
    }

    public function getDimensions()
    {
        if ($this->getType() == Video::TYPE_EMBED) {
            return [320, 180];
        }

        $width = $this->getRecord()->width;
        $height = $this->getRecord()->height;

        if (!$width) {
            return null;
        }
        return $width != 0 ? [$width, $height] : null;
    }

    public function getLength()
    {
        return $this->getRecord()->length;
    }

    public function getFormattedLength(): string
    {
        $len  = $this->getLength();
        if (!$len) {
            return "00:00";
        }
        $mins = floor($len / 60);
        $secs = $len - ($mins * 60);
        return (
            str_pad((string) $mins, 2, "0", STR_PAD_LEFT)
            . ":" .
            str_pad((string) $secs, 2, "0", STR_PAD_LEFT)
        );
    }

    public function getPageURL(): string
    {
        return "/video" . $this->getPrettyId();
    }

    public function canBeViewedBy(?User $user = null): bool
    {
        if ($this->isDeleted() || $this->getOwner()->isDeleted()) {
            return false;
        }

        if (get_class($this->getOwner()) == "openvk\\Web\\Models\\Entities\\User") {
            return $this->getOwner()->canBeViewedBy($user) && $this->getOwner()->getPrivacyPermission('videos.read', $user);
        } else {
            # Groups doesn't have videos but ok
            return $this->getOwner()->canBeViewedBy($user);
        }
    }

    public function toNotifApiStruct()
    {
        $fromYoutube = $this->getType() == Video::TYPE_EMBED;
        $res = (object) [];

        $res->id          = $this->getVirtualId();
        $res->owner_id    = $this->getOwner()->getId();
        $res->title       = $this->getName();
        $res->description = $this->getDescription();
        $res->duration    = $this->getLength();
        $res->link        = "/video" . $this->getOwner()->getId() . "_" . $this->getVirtualId();
        $res->image       = $this->getThumbnailURL();
        $res->date        = $this->getPublicationTime()->timestamp();
        $res->views       = 0;
        $res->player      = !$fromYoutube ? $this->getURL() : $this->getVideoDriver()->getURL();

        return $res;
    }
}