<?php
declare(strict_types=1);

namespace Battles\Database;

use Battles\GameConfigs;
use PDO, PDOException;

class Db
{

    private PDO $pdo;
    private static ?self $_instance = null;

    private function __construct()
    {
        $this->connect();
    }

    private function connect(): void
    {
        $dsn = 'mysql:dbname=' . GameConfigs::DATABASE_NAME . ';host=' . GameConfigs::DATABASE_HOST . ';port=' . GameConfigs::DATABASE_PORT . ';charset=utf8;';
        $user = GameConfigs::DATABASE_USER;
        $password = GameConfigs::DATABASE_PASS;
        try {
            $this->pdo = new PDO($dsn, $user, $password, array(PDO::ATTR_PERSISTENT => true));
            $this->pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_WARNING);
        } catch (PDOException $e) {
            die($e->getMessage());
        }
    }

    public static function getInstance(): self
    {
        if (is_null(self::$_instance)) {
            self::$_instance = new self();
        }
        return self::$_instance;
    }

    public function execute($query, $values = null)
    {
        if (is_null($values)) {
            $values = [];
        } elseif (!is_array($values)) {
            $values = [$values];
        }
        $stmt = $this->pdo->prepare($query);
        $stmt->execute($values);
        return $stmt;
    }

    public function fetch($query, $values = null)
    {
        if (is_null($values)) {
            $values = [];
        } elseif (!is_array($values)) {
            $values = [$values];
        }
        $stmt = $this->execute($query, $values);
        return $stmt->fetch(PDO::FETCH_ASSOC);
    }

    public function fetchAll($query, $values = null, $key = null): array
    {
        if (is_null($values)) {
            $values = [];
        } elseif (!is_array($values)) {
            $values = [$values];
        }
        $stmt = $this->execute($query, $values);
        $results = $stmt->fetchAll(PDO::FETCH_ASSOC);

        // Allows the user to retrieve results using a
        // column from the results as a key for the array
        if (!is_null($key) && $results[0][$key]) {
            $keyed_results = array();
            foreach ($results as $result) {
                $keyed_results[$result[$key]] = $result;
            }
            $results = $keyed_results;
        }
        return $results;
    }

    public function ofetch($query, $values = null)
    {
        if (is_null($values)) {
            $values = [];
        } elseif (!is_array($values)) {
            $values = [$values];
        }
        $stmt = $this->execute($query, $values);
        return $stmt->fetch(PDO::FETCH_OBJ);
    }

    public function ofetchAll($query, $values = null, $key = null): object
    {
        if (is_null($values)) {
            $values = [];
        } elseif (!is_array($values)) {
            $values = [$values];
        }
        $stmt = $this->execute($query, $values);
        $results = $stmt->fetchAll(PDO::FETCH_OBJ);

        // Allows the user to retrieve results using a
        // column from the results as a key for the array
        if (!is_null($key) && $results[0][$key]) {
            $keyed_results = (object)[];
            foreach ($results as $result) {
                $keyed_results->$result[$key] = $result;
            }
            $results = $keyed_results;
        }
        return $results;
    }

    public function lastInsertId()
    {
        return $this->pdo->lastInsertId();
    }

    public function fetchColumn($query, $values = null)
    {
        if (is_null($values)) {
            $values = [];
        } elseif (!is_array($values)) {
            $values = [$values];
        }
        $stmt = $this->execute($query, $values);
        return $stmt->fetchColumn();
    }
}