<?php
namespace Misuzu\Emoticons;

use InvalidArgumentException;
use RuntimeException;
use Index\Data\DbStatementCache;
use Index\Data\IDbConnection;

class Emotes {
    private const EMOTE_ORDER = [
        'order' => 'emote_order',
        'id' => 'emote_id',
        'rank' => 'emote_hierarchy',
    ];

    private IDbConnection $dbConn;
    private DbStatementCache $cache;

    public function __construct(IDbConnection $dbConn) {
        $this->dbConn = $dbConn;
        $this->cache = new DbStatementCache($dbConn);
    }

    public function getEmote(string $emoteId): EmoteInfo {
        $stmt = $this->cache->get('SELECT emote_id, emote_order, emote_hierarchy, emote_url FROM msz_emoticons WHERE emote_id = ?');
        $stmt->addParameter(1, $emoteId);
        $stmt->execute();

        $result = $stmt->getResult();

        if(!$result->next())
            throw new RuntimeException('No emoticon with that ID exists.');

        return new EmoteInfo($result);
    }

    public static function emoteOrderOptions(): array {
        return array_keys(self::EMOTE_ORDER);
    }

    // TODO: pagination
    public function getEmotes(
        ?int $minRank = null,
        ?string $orderBy = null,
        ?bool $reverse = null
    ): array {
        $hasMinRank = $minRank !== null;
        $hasOrderBy = $orderBy !== null;
        $hasReverse = $reverse !== null;

        $query = 'SELECT emote_id, emote_order, emote_hierarchy, emote_url FROM msz_emoticons';
        if($hasMinRank)
            $query .= ' WHERE emote_hierarchy <= ?';
        if($hasOrderBy) {
            if(!array_key_exists($orderBy, self::EMOTE_ORDER))
                throw new InvalidArgumentException('Invalid $orderBy specified.');

            $query .= sprintf(' ORDER BY %s', self::EMOTE_ORDER[$orderBy]);
            if($hasReverse)
                $query .= $reverse ? ' DESC' : ' ASC';
        }

        $stmt = $this->cache->get($query);
        if($hasMinRank)
            $stmt->addParameter(1, $minRank);
        $stmt->execute();

        $emotes = [];
        $result = $stmt->getResult();

        while($result->next())
            $emotes[] = new EmoteInfo($result);

        return $emotes;
    }

    private static function checkEmoteUrlInternal(string $url): string {
        // more checks?
        if(empty($url))
            return 'empty';
        if(trim($url) !== $url)
            return 'spaces';
        return '';
    }

    public function checkEmoteUrl(string $url): string {
        $check = self::checkEmoteUrlInternal($url);
        if($check !== '')
            return $check;

        $stmt = $this->cache->get('SELECT COUNT(*) FROM msz_emoticons WHERE emote_url = ?');
        $stmt->addParameter(1, $url);
        $stmt->execute();
        $result = $stmt->getResult();

        if($result->next() && $result->getInteger(0) > 0)
            return 'used';

        return '';
    }

    public function createEmote(string $url, int $minRank = 0, ?int $order = null): EmoteInfo {
        $check = self::checkEmoteUrlInternal($url);
        if($check !== '')
            throw new InvalidArgumentException('$url is not correctly formatted: ' . $check);

        $stmt = $this->cache->get('INSERT INTO msz_emoticons (emote_url, emote_hierarchy, emote_order) SELECT ?, ?, COALESCE(?, (SELECT FLOOR(MAX(emote_order) / 10) * 10 + 10 FROM msz_emoticons), 10)');
        $stmt->addParameter(1, $url);
        $stmt->addParameter(2, $minRank);
        $stmt->addParameter(3, $order);
        $stmt->execute();

        return $this->getEmote((string)$this->dbConn->getLastInsertId());
    }

    public function deleteEmote(EmoteInfo|string $infoOrId): void {
        if($infoOrId instanceof EmoteInfo)
            $infoOrId = $infoOrId->getId();

        $stmt = $this->cache->get('DELETE FROM msz_emoticons WHERE emote_id = ?');
        $stmt->addParameter(1, $infoOrId);
        $stmt->execute();
    }

    public function updateEmote(
        EmoteInfo|string $infoOrId,
        ?int $order = null,
        ?int $minRank = null,
        ?string $url = null
    ): void {
        if($url !== null) {
            $check = self::checkEmoteUrlInternal($url);
            if($check !== '')
                throw new InvalidArgumentException('$url is not correctly formatted: ' . $check);
        }

        if($infoOrId instanceof EmoteInfo)
            $infoOrId = $infoOrId->getId();

        $stmt = $this->cache->get('UPDATE msz_emoticons SET emote_order = COALESCE(?, emote_order), emote_hierarchy = COALESCE(?, emote_hierarchy), emote_url = COALESCE(?, emote_url) WHERE emote_id = ?');
        $stmt->addParameter(1, $order);
        $stmt->addParameter(2, $minRank);
        $stmt->addParameter(3, $url);
        $stmt->addParameter(4, $infoOrId);
        $stmt->execute();
    }

    public function updateEmoteOrderOffset(EmoteInfo|string $infoOrId, int $offset): void {
        if($offset === 0) return;
        if($infoOrId instanceof EmoteInfo)
            $infoOrId = $infoOrId->getId();

        $stmt = $this->cache->get('UPDATE msz_emoticons SET emote_order = emote_order + ? WHERE emote_id = ?');
        $stmt->addParameter(1, $offset);
        $stmt->addParameter(2, $infoOrId);
        $stmt->execute();
    }

    public function getEmoteStrings(EmoteInfo|string $infoOrId): array {
        if($infoOrId instanceof EmoteInfo)
            $infoOrId = $infoOrId->getId();

        $stmt = $this->cache->get('SELECT emote_id, emote_string_order, emote_string FROM msz_emoticons_strings WHERE emote_id = ? ORDER BY emote_string_order');
        $stmt->addParameter(1, $infoOrId);
        $stmt->execute();

        $strings = [];
        $result = $stmt->getResult();

        while($result->next())
            $strings[] = new EmoteStringInfo($result);

        return $strings;
    }

    private static function checkEmoteStringInternal(string $string): string {
        if(empty($string))
            return 'empty';
        if(trim($string) !== $string)
            return 'spaces';
        if(strtolower($string) !== $string)
            return 'case';
        if(!preg_match('#^[a-z][a-z0-9_-]*[a-z0-9]$#', $string))
            return 'format';

        return '';
    }

    public function checkEmoteString(string $string): string {
        $check = self::checkEmoteStringInternal($string);
        if($check !== '')
            return $check;

        $stmt = $this->cache->get('SELECT COUNT(*) FROM msz_emoticons_strings WHERE emote_string = ?');
        $stmt->addParameter(1, $string);
        $stmt->execute();
        $result = $stmt->getResult();

        if($result->next() && $result->getInteger(0) > 0)
            return 'used';

        return '';
    }

    public function addEmoteString(EmoteInfo|string $infoOrId, string $string, ?int $order = null): void {
        $check = self::checkEmoteStringInternal($string);
        if($check !== '')
            throw new InvalidArgumentException('$string is not correctly formatted: ' . $check);

        if($infoOrId instanceof EmoteInfo)
            $infoOrId = $infoOrId->getId();

        $stmt = $this->cache->get('INSERT INTO msz_emoticons_strings (emote_id, emote_string, emote_string_order) SELECT ? AS target_emote_id, ?, COALESCE(?, (SELECT MAX(emote_string_order) + 1 FROM msz_emoticons_strings WHERE emote_id = target_emote_id), 1)');
        $stmt->addParameter(1, $infoOrId);
        $stmt->addParameter(2, $string);
        $stmt->addParameter(3, $order);
        $stmt->execute();
    }

    public function removeEmoteString(EmoteStringInfo|string $infoOrString): void {
        if($infoOrString instanceof EmoteStringInfo)
            $infoOrString = $infoOrString->getString();

        $stmt = $this->cache->get('DELETE FROM msz_emoticons_strings WHERE emote_string = ?');
        $stmt->addParameter(1, $infoOrString);
        $stmt->execute();
    }
}
