<?php
namespace Misuzu\Counters;

use InvalidArgumentException;
use Index\Data\DbStatementCache;
use Index\Data\DbTools;
use Index\Data\IDbConnection;
use Misuzu\Pagination;

// insert increment and decrement calls in places someday

class Counters {
    private DbStatementCache $cache;

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

    private const GET_COUNTERS_SORT = [
        'name' => 'counter_name',
        'value' => 'counter_value',
        'updated' => 'counter_updated',
    ];

    public function getCounters(
        ?string $orderBy = null,
        ?Pagination $pagination = null
    ): array {
        $hasOrderBy = $orderBy !== null;
        $hasPagination = $pagination !== null;

        $query = 'SELECT counter_name, counter_value, UNIX_TIMESTAMP(counter_updated) FROM msz_counters';
        if($hasOrderBy) {
            if(!array_key_exists($orderBy, self::GET_COUNTERS_SORT))
                throw new InvalidArgumentException('Invalid sort specified.');

            $query .= ' ORDER BY ' . self::GET_COUNTERS_SORT[$orderBy];
        }
        if($hasPagination)
            $query .= ' LIMIT ? OFFSET ?';

        $args = 0;
        $stmt = $this->cache->get($query);
        if($hasPagination) {
            $stmt->addParameter(++$args, $pagination->getRange());
            $stmt->addParameter(++$args, $pagination->getOffset());
        }
        $stmt->execute();

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

        while($result->next())
            $counters[] = new CounterInfo($result);

        return $counters;
    }

    public function get(array|string $names): array|int {
        if(is_string($names)) {
            $returnFirst = true;
            $names = [$names];
        } else $returnFirst = false;

        $args = 0;
        $stmt = $this->cache->get(sprintf(
            'SELECT counter_name, counter_value FROM msz_counters WHERE counter_name IN (%s)',
            DbTools::prepareListString($names)
        ));
        foreach($names as $name)
            $stmt->addParameter(++$args, (string)$name);
        $stmt->execute();

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

        while($result->next())
            $values[$result->getString(0)] = $result->getInteger(1);

        return $returnFirst ? $values[array_key_first($values)] : $values;
    }

    public function set(string|array $nameOrValues, ?int $value = null): void {
        if(empty($nameOrValues))
            throw new InvalidArgumentException('$nameOrValues may not be empty.');

        if(is_string($nameOrValues)) {
            if($value === null)
                throw new InvalidArgumentException('$value may not be null.');
            $values = [$nameOrValues => $value];
        } else $values = $nameOrValues;


        $args = 0;
        $stmt = $this->cache->get(sprintf(
            'REPLACE INTO msz_counters (counter_name, counter_value) VALUES %s',
            DbTools::prepareListString($values, '(?, ?)')
        ));

        foreach($values as $name => $value) {
            $stmt->addParameter(++$args, (string)$name);
            $stmt->addParameter(++$args, (int)$value);
        }

        $stmt->execute();
    }

    public function reset(string|array $names): void {
        if(empty($names))
            throw new InvalidArgumentException('$names may not be empty.');
        if(is_string($names))
            $names = [$names];

        $args = 0;
        $stmt = $this->cache->get(sprintf(
            'DELETE FROM msz_counters WHERE counter_name IN (%s)',
            DbTools::prepareListString($names)
        ));
        foreach($names as $name)
            $stmt->addParameter(++$args, (string)$name);
        $stmt->execute();
    }

    public function increment(string $name, int $step = 1): void {
        $stmt = $this->cache->get('INSERT INTO msz_counters (counter_name, counter_value) VALUES (?, ?) ON DUPLICATE KEY UPDATE counter_value = counter_value + ?');
        $stmt->addParameter(1, $name);
        $stmt->addParameter(2, $step);
        $stmt->addParameter(3, $step);
        $stmt->execute();
    }

    public function decrement(string $name, int $step = 1): void {
        $stmt = $this->cache->get('INSERT INTO msz_counters (counter_name, counter_value) VALUES (?, ?) ON DUPLICATE KEY UPDATE counter_value = counter_value - ?');
        $stmt->addParameter(1, $name);
        $stmt->addParameter(2, -$step);
        $stmt->addParameter(3, $step);
        $stmt->execute();
    }
}
