<?php
/**
 * MtcPdo
 *
 * @package mtc ecommerce
 * @author Nick <nick.edwards@mtcmedia.co.uk>
 * @copyright 2013 mtc. http://www.mtcmedia.co.uk/
 * @version 1.3
 * @access public
 * @deprecated
 */
class MtcPdo extends PDO
{
    // statement handler
    public $sth;
    private static $db = '';

    /**
     * @var \PDO
     */
    private static $PDOInstance;

    /**
     * @return PDO
     */
    public static function getPDOInstance()
    {
        return self::$PDOInstance;
    }

    /**
     * mtcpdo::__construct()
     *
     * Opens up PDO database connection.
     * Sets default charset to UTF-8.
     * Sets error reporting on if in development mode.
     *
     * @param  string $db allows you to set different databases to connect to,
     * e.g. the postcode database
     * @return bool   true if successful, false if not
     */
    public function __construct($db = '')
    {
        if (self::$db != $db || !self::$PDOInstance) {
            return self::connect($db);
        }
        return true;
    }

    /**
     * mtcpdo::connect()
     *
     * Opens up PDO database connection.
     * Sets default charset to UTF-8.
     * Sets error reporting on if in development mode.
     *
     * @param  string $db allows you to set different databases to connect to, e.g. the postcode database
     * @return bool   true if successful, false if not
     */
    private function connect($db = '')
    {

        self::$db = $db;

        try {
            // Get details
            if ($db == '') {
                $db_environment = config('database.default');
                // Normal DB
                $host = config("database.connections.{$db_environment}.host");
                $db = config("database.connections.{$db_environment}.database");
                $user = config("database.connections.{$db_environment}.username");
                $pass = config("database.connections.{$db_environment}.password");
            } elseif ($db == 'paf') {
                // Postcode address finder
                $host = config('database.connections.paf.host');
                $db = config('database.connections.paf.database');
                $user = config('database.connections.paf.username');
                $pass = config('database.connections.paf.password');
            } elseif ($db == 'global') {
                // Global db used across multi-sites.
                $host = GLOBAL_MYSQL_HOST;
                $db = GLOBAL_MYSQL_DB;
                $user = GLOBAL_MYSQL_USER;
                $pass = GLOBAL_MYSQL_PASS;
            } elseif (is_array($db)) {
                $db_credentials = $db;
                $host = $db_credentials['host'];
                $db = $db_credentials['db'];
                $user = $db_credentials['user'];
                $pass = $db_credentials['pass'];
            } else {
                if (DEV_MODE) {
                    throw new Exception('No MySQL Details provided');
                }
            }

            // Connect to the DB
            self::$PDOInstance = new \PDO("mysql:host={$host};dbname={$db}", $user, $pass);

        } catch (PDOException $e) {
            if (DEV_MODE) {
                throw new Exception($e->getMessage());
            }
            return false;
        }

        // Set error mode
        self::$PDOInstance->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
        self::run("SET NAMES utf8");

        return true;
    }

    /**
     * mtcpdo::run()
     *
     * Main query function which will build up the query you pass in and bind all
     * the correct parameters.
     *
     * @param  mixed $sql       - sql statement, using :variable where
     * appropriate
     * @param  mixed $params    - key => value array of :variable => value
     * @param  mixed $fetchmode - set what to return, defaults to associative
     * array, allows an array with arguments for setFetchMode()
     *
     * @throws Exception If setting fetch mode with the new method did not work
     * @return PDOStatement The resulting PDO statement that you can run
     * ->fetch() on disregarding the given $fetchmode parameter.
     */
    public function run($sql, $params = array(), $fetchmode = array(PDO::FETCH_ASSOC))
    {
        //echo $GLOBALS['queries'];
        //echo $sql."<br />";
        //$GLOBALS['queries']++;

        self::prepareValues($sql, $params);

        try {
            $this->sth->execute();
        } catch (PDOException $e) {
            if ($e->getCode() === 2006) { // check if MySQL server has gone away
                try{
                    //reconnect
                    self::connect(self::$db);
                    self::prepareValues($sql, $params);
                    $this->sth->execute();
                }catch(PDOException  $e){
                    if (DEV_MODE) {
                        throw new Exception($e->getMessage());
                    }
                    return false;
                }
            } else {
                if (DEV_MODE) {
                    throw new Exception($e->getMessage());
                }
                return false;
            }
        }

        // Allow proper fetchmode setup, e.g. PDO::FETCH_CLASS
        // unpacks the array and passes arguments to the function
        // @author Georgi Boiko <georgi.boiko@mtcmedia.co.uk>
        if (is_array($fetchmode)) {
            if (call_user_func_array(array(&$this->sth, 'setFetchMode'), $fetchmode) == false) {
                // throw an exception
                if (DEV_MODE) {
                    throw new Exception('Failed to set fetch mode');
                }
                return false;
            }
        } else {
            // Support for legacy code which uses a constant instead of an array as an argument
            $this->sth->setFetchMode($fetchmode);
        }
        return $this->sth;
    }

    /**
     * mtcpdo::prepare_values()
     *
     * Secondary query function that prepare sql values and params.
     *
     * @param  mixed  $sql       - sql statement
     * @param  mixed  $params    - sql params
     * @return nothing
     */
    private function prepareValues($sql, $params)
    {
        $this->sth = self::$PDOInstance->prepare($sql);
        if (!empty($params)) {
            foreach ($params as $field => $value) {
                // PDO casts everything as a string, unless you typecast the value
                if (is_int($value) || $value === "0") { // turns out is_int("0") evaluates as false?
                    $type = PDO::PARAM_INT;
                    $value = (int) $value;
                } elseif (is_bool($value)) {
                    $type = PDO::PARAM_BOOL;
                    $value = (bool) $value;
                } elseif (is_null($value)) {
                    $type = PDO::PARAM_STR;
                    $value = (string) '';
                } else {
                    $type = PDO::PARAM_STR;
                    if (is_float($value)) {
                        // apparently there is no PDO::PARAM_* for floats, so best to treat them as strings
                        $value = (float) $value;
                    }
                }
                // Add support for executing a prepared statement with question mark placeholders
                //  where param values are added via $array[] = "value";
                if (is_numeric($field)) {
                    $field++;
                }
                $this->sth->bindValue($field, $value, $type);
            }
        }
    }

    /**
     * mtcpdo::query()
     *
     * Secondary query function that does not support prepared statements but it
     * does support multi query.
     *
     * @param  mixed  $sql       - sql statement
     * @return object statement handler
     */
    public function query(string $query, ?int $fetchMode = null, mixed ...$fetchModeArgs)
    {
        $this->sth = self::$PDOInstance;
        $this->sth->query($query);

        return $this->sth;
    }

    /**
     * mtcpdo::fetch_array()
     *
     * Alternative function for PDOStatement::fetch allowing backwards code
     * compatibility for old mysql_* class
     *
     * @return object statement handler
     */
    public function fetch_array()
    {
        return $this->sth->fetch();
    }

    /**
     * mtcpdo::num_rows()
     *
     * Alternative function for PDOStatement::rowCount allowing backwards code
     * compatibility for old mysql_* class
     *
     * @return int number of rows
     */
    public function num_rows()
    {
        return $this->sth->rowCount();
    }

    /**
     * mtcpdo::affected_rows()
     *
     * Alternative function for PDOStatement::rowCount allowing backwards code
     * compatibility for old mysql_* class
     *
     * @return int number of rows
     */
    public function affected_rows()
    {
        return $this->sth->rowCount();
    }

    /**
     * mtcpdo::insert_id()
     *
     * Alternative function for PDOStatement::lastInsertId allowing backwards
     * code compatibility for old mysql_* class
     *
     * @return int id of last insert
     */
    public function insert_id()
    {
        return self::$PDOInstance->lastInsertId();
    }

    /**
     * mtcpdo::insert()
     *
     * Builds query from field => value array
     * Allows mysql keywords to be used in value - e.g. now() or NULL
     *
     * @param  mixed $table
     * @param  mixed $data
     * @return mixed insert id on success, false on fail
     */
    public function insert($table, $data)
    {
        $q = "INSERT INTO `$table` ";
        $v = '';
        $n = '';

        $params = array();

        foreach ($data as $key => $val) {
            $n .= "`$key`, ";
            if (strtolower($val) == 'null') {
                $v .= "NULL, ";
            } elseif (strtolower($val) == 'now()') {
                $v .= "NOW(), ";
            } else {
                $v .= ":" . $key . ", ";
                $params[':' . $key] = $val;
            }
        }

        $q .= "(" . rtrim($n, ', ') . ") VALUES (" . rtrim($v, ', ') . ");";

        if ($this->run($q, $params)) {
            return self::$PDOInstance->lastInsertId();
        } else {
            return false;
        }
    }

    /**
     * mtcpdo::update()
     *
     * Builds update query from field => value array
     * Accepts a number of keywords as values:
     *     null         -> NULL
     *     now()        -> NOW()
     *     increment(i) -> increment value by i where i is an integer
     *     decrement(i) -> decrement value by i where i is an integer
     *
     * Where clause default so you don't go accidentally updating whole table
     *
     * @param  mixed  $table
     * @param  mixed  $data
     * @param  string $where
     * @param  mixed  $params
     * @return bool   true on success, false on fail
     */
    public function update($table, $data, $where = '`id` = :id', $params = array(':id' => 0))
    {
        $q = "UPDATE `$table` SET ";

        foreach ($data as $key => $val) {
            if (strtolower($val) == 'null') {
                $q .= "`$key` = NULL, ";
            } elseif (strtolower($val) == 'now()') {
                $q .= "`$key` = NOW(), ";
            } elseif (preg_match("/^increment\((\-?\d+)\)$/i", $val, $m)) {
                $q .= "`$key` = `$key` + $m[1], ";
            } elseif (preg_match("/^decrement\((\-?\d+)\)$/i", $val, $m)) {
                $q .= "`$key` = `$key` - $m[1], ";
            } else {
                $q .= "`$key`=:" . $key . ", ";
                $params[':' . $key] = $val;
            }
        }

        $q = rtrim($q, ', ') . ' WHERE ' . $where;

        return $this->run($q, $params);
    }

    /**
     * mtcpdo::delete()
     *
     * Builds delete query
     * Where clause default so you don't delete whole table's data
     *
     * @param  mixed  $table
     * @param  string $where
     * @param  mixed  $params
     * @return bool   true on success, false on fail
     */
    public function delete($table, $where = '`id` = :id', $params = array(':id' => 0))
    {
        $q = "DELETE FROM `$table` ";
        $q .= " WHERE " . $where;

        return $this->run($q, $params);
    }

    /**
     * Quote every element of the array for safe use in IN() queries
     *
     * Usage: $array = $db->quoteArray($ids)
     *
     * @param $array
     * @param int $type
     * @return mixed
     */
    public function quoteArray($array, $type = PDO::PARAM_STR)
    {
        foreach ($array as &$value) {
            $value = static::$PDOInstance->quote($value, $type);
        }
        return $array;
    }
}
