<?php

class db
{
    protected $connection;
    protected $query;
    protected $show_errors = true;
    protected $query_closed = true;

    public $query_count = 0;

    public function __construct(
        $dbhost = "localhost",
        $dbuser = "root",
        $dbpass = "",
        $dbname = "",
        $charset = "utf8"
    ) {
        /*
         * PHP 8 can throw mysqli_sql_exception directly from new mysqli().
         * Handle it here so a temporary hosting/database connection failure
         * never produces an uncaught fatal error or a blank page.
         */
        mysqli_report(MYSQLI_REPORT_OFF);

        $this->connection = null;
        $lastError = "Unknown database connection error";
        $hosts = [$dbhost];

        /*
         * "localhost" uses the MySQL socket and "127.0.0.1" uses TCP.
         * On a local database either route may be temporarily unavailable,
         * so keep the configured host first and use the other as fallback.
         */
        if (strtolower((string)$dbhost) === "localhost") {
            $hosts[] = "127.0.0.1";
        } elseif ((string)$dbhost === "127.0.0.1") {
            $hosts[] = "localhost";
        }

        foreach (array_unique($hosts) as $host) {
            for ($attempt = 1; $attempt <= 2; $attempt++) {
                $connection = null;

                try {
                    $connection = @new mysqli(
                        $host,
                        $dbuser,
                        $dbpass,
                        $dbname
                    );

                    if (
                        $connection instanceof mysqli &&
                        $connection->connect_errno === 0
                    ) {
                        $this->connection = $connection;
                        break 2;
                    }

                    if ($connection instanceof mysqli) {
                        $lastError = "[" .
                            $connection->connect_errno .
                            "] " .
                            $connection->connect_error;

                        @$connection->close();
                    }
                } catch (Throwable $exception) {
                    $lastError = $exception->getMessage();
                }

                if ($attempt < 2) {
                    usleep(250000);
                }
            }
        }

        if (!($this->connection instanceof mysqli)) {
            error_log(
                "MySQL connection failed: " . $lastError
            );

            $this->connectionUnavailable();
        }

        if (!@$this->connection->set_charset($charset)) {
            $this->error(
                "Unable to set MySQL charset - " .
                $this->connection->error
            );
        }
    }

    private function connectionUnavailable()
    {
        if (!headers_sent()) {
            http_response_code(503);
            header("Content-Type: text/html; charset=UTF-8");
            header("Retry-After: 5");
        }

        echo '<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Service Temporarily Unavailable</title>
    <style>
        *{box-sizing:border-box}
        body{margin:0;min-height:100vh;padding:20px;display:flex;align-items:center;justify-content:center;font-family:Arial,sans-serif;background:#f1f5f9}
        .box{width:100%;max-width:430px;padding:34px 28px;border-radius:18px;background:#fff;text-align:center;box-shadow:0 18px 45px rgba(15,23,42,.14)}
        .icon{width:72px;height:72px;margin:0 auto 18px;display:flex;align-items:center;justify-content:center;border-radius:50%;background:#fef3c7;color:#d97706;font-size:38px;font-weight:700}
        h1{margin:0 0 12px;color:#0f172a;font-size:26px}
        p{margin:0;color:#64748b;font-size:15px;line-height:1.6}
        a{display:inline-block;margin-top:22px;padding:11px 22px;border-radius:9px;background:#2563eb;color:#fff;text-decoration:none;font-weight:700}
    </style>
</head>
<body>
    <div class="box">
        <div class="icon">!</div>
        <h1>Server Temporarily Busy</h1>
        <p>The database connection is temporarily unavailable.<br>Please wait a few seconds and try again.</p>
        <a href="javascript:location.reload()">Try Again</a>
    </div>
</body>
</html>';

        exit;
    }

    public function query($query)
    {
        if (!$this->query_closed && $this->query) {
            $this->query->close();
        }

        $this->query = $this->connection->prepare($query);

        if (!$this->query) {
            $this->error(
                "Unable to prepare MySQL statement - " .
                $this->connection->error
            );

            return $this;
        }

        if (func_num_args() > 1) {
            $arguments = func_get_args();
            $arguments = array_slice($arguments, 1);

            $types = "";
            $argumentsReference = [];

            foreach ($arguments as &$argument) {
                if (is_array($argument)) {
                    foreach ($argument as &$arrayValue) {
                        $types .= $this->_gettype($arrayValue);
                        $argumentsReference[] = &$arrayValue;
                    }
                } else {
                    $types .= $this->_gettype($argument);
                    $argumentsReference[] = &$argument;
                }
            }

            array_unshift($argumentsReference, $types);

            call_user_func_array(
                [$this->query, "bind_param"],
                $argumentsReference
            );
        }

        $this->query->execute();

        if ($this->query->errno) {
            $this->error(
                "Unable to process MySQL query - " .
                $this->query->error
            );
        }

        $this->query_closed = false;
        $this->query_count++;

        return $this;
    }

    public function fetchAll($callback = null)
    {
        $params = [];
        $row = [];
        $result = [];

        $meta = $this->query->result_metadata();

        if (!$meta) {
            return $result;
        }

        while ($field = $meta->fetch_field()) {
            $params[] = &$row[$field->name];
        }

        call_user_func_array(
            [$this->query, "bind_result"],
            $params
        );

        while ($this->query->fetch()) {
            $currentRow = [];

            foreach ($row as $key => $value) {
                $currentRow[$key] = $value;
            }

            if ($callback !== null && is_callable($callback)) {
                $callbackResult = call_user_func(
                    $callback,
                    $currentRow
                );

                if ($callbackResult === "break") {
                    break;
                }
            } else {
                $result[] = $currentRow;
            }
        }

        $this->query->close();
        $this->query_closed = true;

        return $result;
    }

    public function fetchArray()
    {
        $params = [];
        $row = [];
        $result = [];

        $meta = $this->query->result_metadata();

        if (!$meta) {
            return $result;
        }

        while ($field = $meta->fetch_field()) {
            $params[] = &$row[$field->name];
        }

        call_user_func_array(
            [$this->query, "bind_result"],
            $params
        );

        while ($this->query->fetch()) {
            foreach ($row as $key => $value) {
                $result[$key] = $value;
            }
        }

        $this->query->close();
        $this->query_closed = true;

        return $result;
    }

    public function close()
    {
        if (!$this->query_closed && $this->query) {
            $this->query->close();
            $this->query_closed = true;
        }

        return $this->connection->close();
    }

    public function numRows()
    {
        $this->query->store_result();

        return $this->query->num_rows;
    }

    public function affectedRows()
    {
        return $this->query->affected_rows;
    }

    public function lastInsertID()
    {
        return $this->connection->insert_id;
    }

    public function error($error)
    {
        if ($this->show_errors) {
            die($error);
        }
    }

    public function escape_string($value)
    {
        return $this->connection->real_escape_string($value);
    }

    private function _gettype($value)
    {
        if (is_string($value)) {
            return "s";
        }

        if (is_float($value)) {
            return "d";
        }

        if (is_int($value)) {
            return "i";
        }

        return "b";
    }
}

?>