<?php
/**
 * Static methods for DbSimple_MyPdo configuration
 * @package Am_Utils
 */

/**
 * Class Am_Db_PDOProxy
 * This class introduced to delay MYSQL initialization until
 * first access to any method of the proxy object
 *
 * First callback must return object, it will then be passed to all following callbacks
 */
class Am_Db_PDOProxy
{
    private $callbacks;

    function __construct(array $callbacks = [])
    {
        $this->callbacks = $callbacks;
    }

    function __call($name, $arguments)
    {
        $ret = null;
        foreach ($this->callbacks as $cb)
        {
            $_ = call_user_func($cb, $ret);
            if (null === $ret)
                $ret = $_;
        }
        if ($name !== '_setup') //magic name
            return call_user_func_array([$ret, $name], $arguments);
    }
}


class Am_Db extends DbSimple_Mypdo
{
    private $_cacheNext = false;
    private $_cacheTime;
    protected $_needSettings = true;
    /**
     * @return DbSimple_Mysql
     */
    static function connect($config, $onlyConnect = false)
    {
        extract($config);
        $database = new self(
                [
                    'scheme' => 'mysql',
                    'user' => @$user,
                    'pass' => @$pass,
                    'host' => @$host,
                    'path' => @$db,
                    'port' => @$port,
                    'persist' => @$persist,
                    'pdo_options' => @$pdo_options,
                ]);
        $database->setErrorHandler([__CLASS__, 'defaultDatabaseErrorHandler']);
        if (!$onlyConnect) {
            $database->setIdentPrefix(@$prefix);
        }
        return $database;
    }

    public function __construct($dsn)
    {
        // if you properly configured MySQL server, you can define this to skip 3 mysql queries on each run
        $this->_needSettings = empty($dsn['skip_utf8_config']);

        $this->PDO = new Am_Db_PDOProxy([function() use ($dsn) {
            parent::__construct($dsn);
            if ($this->_isConnected() && $this->_needSettings)
                $this->setMysqlSessionSettings();
            return $this->PDO;
        }]);
    }

    protected function _performQuery($queryMain, $resultOnly = false)
    {
        if ($this->PDO instanceof Am_Db_PDOProxy)
            $this->PDO->_setup(); // magic call to convert and do not in middle of another query!
        return parent::_performQuery($queryMain, $resultOnly);
    }

    public function setMysqlSessionSettings()
    {
        $this->query("SET NAMES utf8mb4");
        $this->query("SET SESSION sql_mode=''");
        $this->query("SET SESSION group_concat_max_len=CAST(LEAST(@@max_allowed_packet, 1024*1024) as UNSIGNED)");
        $this->_needSettings = false;
    }

    static function defaultDatabaseErrorHandler($message, $info)
    {
        if (!(error_reporting() & E_WARNING))
            return;

        if (!class_exists('Am_Exception_Db'))
            require_once dirname(__FILE__) . '/Exception.php';

        if ($info['code'] == 1062)
            $class = 'Am_Exception_Db_NotUnique';
        else
            $class = 'Am_Exception_Db';
        $e = new $class("$message({$info['code']}) in query: {$info['query']}", @$info['code']);
        $e->setDbMessage(preg_replace('/ at.+$/', '', $message));
        $e->setLogError(true); // already logged
        // try to parse table name
        if (($e instanceof Am_Exception_Db_NotUnique) &&
            preg_match('/insert into (\w+)/i', $info['query'], $regs)) {
            $prefix = Am_Di::getInstance()->db->getPrefix();
            $table = preg_replace('/^' . preg_quote($prefix) . '/', '?_', $regs[1]);
            $e->setTable($table);
        }
        throw $e;
    }

    static function loggerCallback($db, $sql)
    {
        $caller = $db->findLibraryCaller();
        if (preg_match('/phpunit/', @$_SERVER['argv'][0]) || empty($_SERVER['REMOTE_ADDR'])) {
            print_r($sql);
            print "\n";
        } else {
            $tip = "at " . @$caller['file'] . ' line ' . @$caller['line'];
            echo "<xmp title=\"$tip\">";
            print_r($sql);
            echo "</xmp>";
        }
    }

    static function enableLogger($db = null)
    {
        if ($db === null)
            $db = Am_Di::getInstance()->db;
        $db->setLogger([__CLASS__, 'loggerCallback']);
    }

    static function removeLogger($db = null)
    {
        if ($db === null)
            $db = Am_Di::getInstance()->db;
        $db->setLogger(null);
    }

    public function query()
    {
        $deadlock_attempts = 3;
        do {
            try {
                return parent::query(...func_get_args());
            } catch (Exception $ex) {
                // Catch deadlock error and try to re-submit the same query;
                if($ex->getCode() == 1213)
                    $deadlock_attempts--;
                else throw $ex;
            }
        } while ($deadlock_attempts);
        throw $ex;
    }

    function _performTransformQuery(&$queryMain, $how)
    {
        if (AM_HUGE_DB && !AM_HUGE_DB_NO_CACHE && $this->_cacheNext) {
            $queryMain[0] =" -- CACHE: 0h 0m ".$this->_cacheTime."s\n".$queryMain[0];
            $this->_cacheNext = $this->_cacheTime = null;
        }
        return parent::_performTransformQuery($queryMain, $how);
    }

    function cacheNext($time=null)
    {
        $this->_cacheTime = $time?:AM_HUGE_DB_CACHE_TIMEOUT;
        $this->_cacheNext = true;
        return $this;
    }

    function isPersistentConnection()
    {
        return (bool)$this->PDO->getAttribute(PDO::ATTR_PERSISTENT);
    }

    /** @return PDO */
    function getPDO()
    {
        return $this->PDO;
    }
}