text:
<?php

declare(strict_types=1);

use Arrayy\Arrayy;
use voku\db\DB;
use voku\db\Helper;
use voku\db\Result;
use voku\helper\UTF8;

/**
 * Class SimpleDoctrinePdoTest
 *
 * @internal
 */
final class SimpleDoctrinePdoTest extends \PHPUnit\Framework\TestCase
{
    /**
     * @var DB
     */
    protected $db;

    /**
     * @var string
     */
    protected $tableName = 'test_page';

    protected function setUpNonVoid()
    {
        $connectionParams = [
            'dbname'   => 'mysql_test',
            'user'     => 'root',
            'password' => '',
            'host'     => 'localhost',
            'driver'   => 'pdo_mysql',
            'charset'  => 'utf8mb4',
        ];
        $config = new \Doctrine\DBAL\Configuration();
        $doctrineConnection = \Doctrine\DBAL\DriverManager::getConnection(
            $connectionParams,
            $config
        );
        $doctrineConnection->connect();

        $this->db = DB::getInstanceDoctrineHelper(
            $doctrineConnection,
            '',
            false,
            false
        );
    }

    public function testLogQuery()
    {
        $this->setUpNonVoid();

        $db_1 = DB::getInstance('localhost', 'root', '', 'mysql_test', '', '', false, true, '', 'debug');
        static::assertInstanceOf('\\voku\\db\\DB', $db_1);

        // sql - true
        $pageArray = [
            'page_template' => 'tpl_new_中',
            'page_type'     => 'lall',
        ];
        $tmpId = $db_1->insert($this->tableName, $pageArray);
        static::assertTrue($tmpId > 0);

        // sql - true v2
        $pageArray = [
            'page_template' => 'this_is_a_new_test',
            'page_type'     => 'fooooo',
        ];
        $tmpId = $db_1->insert($this->tableName, $pageArray);
        static::assertTrue($tmpId > 0);

        // update - true (affected_rows === 1)
        $pageArray = [
            'page_template' => 'this_is_a_new_test__update',
        ];
        $affected_rows = $this->db->update($this->tableName, $pageArray, 'page_id = ' . (int) $tmpId);
        static::assertSame(1, $affected_rows);

        // update - true (affected_rows === 0)
        $pageArray = [
            'page_template' => 'this_is_a_new_test__update',
        ];
        $affected_rows = $this->db->update($this->tableName, $pageArray, 'page_id = -1');
        static::assertSame(0, $affected_rows);

        // update - false
        $false = $this->db->update($this->tableName, [], 'page_id = ' . (int) $tmpId);
        static::assertFalse($false);
    }

    public function testEchoOnError1()
    {
        $this->setUpNonVoid();

        $db_1 = DB::getInstance('localhost', 'root', '', 'mysql_test', '', '', false, true);
        static::assertInstanceOf('\\voku\\db\\DB', $db_1);

        // insert - false
        $false = $db_1->insert($this->tableName, []);
        $this->expectOutputRegex('/(.)*Invalid data for INSERT(.)*/');
        static::assertFalse($false);
    }

    public function testEchoOnError4()
    {
        $this->setUpNonVoid();

        $db_1 = DB::getInstance('localhost', 'root', '', 'mysql_test', '', '', false, true);
        static::assertInstanceOf('\\voku\\db\\DB', $db_1);

        // sql - false
        $false = $db_1->query();
        $this->expectOutputRegex('/.*SimpleDoctrinePdoTest::testEchoOnError.*/');
        static::assertFalse($false);
    }

    public function testEchoOnError3()
    {
        $this->setUpNonVoid();

        $db_1 = DB::getInstance('localhost', 'root', '', 'mysql_test', '', '', false, true);
        static::assertInstanceOf('\\voku\\db\\DB', $db_1);

        // sql - false
        $false = $db_1->query();
        $this->expectOutputRegex('/Error:/');
        $this->expectOutputRegex('/testEchoOnError3:/');
        static::assertFalse($false);
    }

    public function testEchoOnError2()
    {
        $this->setUpNonVoid();

        $db_1 = DB::getInstance('localhost', 'root', '', 'mysql_test', '', '', false, true);
        static::assertInstanceOf('\\voku\\db\\DB', $db_1);

        // sql - false
        $false = $db_1->query();
        $this->expectOutputRegex('/.*SimpleDoctrinePdoTest::testEchoOnError.*/');
        static::assertFalse($false);

        // close db-connection
        static::assertTrue($this->db->isReady());
        static::assertTrue($this->db->close());
        static::assertFalse($this->db->isReady());
        static::assertFalse($this->db->close());
        static::assertFalse($this->db->isReady());

        // insert - false
        $false = $db_1->query('INSERT INTO lall SET false = 1');
        static::assertFalse($false);
    }

    public function testExitOnError1()
    {
        $this->setUpNonVoid();

        $db_1 = DB::getInstance('localhost', 'root', '', 'mysql_test', '', '', true, false);
        static::assertInstanceOf('\\voku\\db\\DB', $db_1);

        // insert - false
        $pageArray = [
            'page_template' => 'tpl_new_中',
            'page_type'     => 'lall',
        ];
        $false = $db_1->insert('', $pageArray);
        static::assertFalse($false);
    }

    public function testExitOnError2()
    {
        $this->setUpNonVoid();

        $db_1 = DB::getInstance('localhost', 'root', '', 'mysql_test', '', '', true, false);
        static::assertInstanceOf('\\voku\\db\\DB', $db_1);

        // insert - false
        $false = $db_1->insert($this->tableName, []);
        static::assertFalse($false);
    }

    public function testGetFalseInstanceV1()
    {
        $this->setUpNonVoid();

        $this->expectException(\Exception::class);
        $this->expectExceptionMessage('Error connecting to mysql server: Access denied for user \'root\'@\'localhost\' (using password: YES)');

        DB::getInstance('localhost', 'root', 'test', 'mysql_test', '', '', false, false);
    }

    public function testGetFalseInstanceV2()
    {
        $this->setUpNonVoid();

        $this->expectException(\Exception::class);
        if (method_exists($this, 'expectExceptionMessageMatches')) {
            $this->expectExceptionMessageMatches('#Error connecting to mysql server:.*#');
        } else {
            $this->expectExceptionMessageRegExp('#Error connecting to mysql server:.*#');
        }

        DB::getInstance('localhost_lall', 'root123', '', 'mysql_test', '', '', true, false);
    }

    public function testGetFalseInstanceV3()
    {
        $this->setUpNonVoid();

        $this->expectException(\Exception::class);
        if (method_exists($this, 'expectExceptionMessageMatches')) {
            $this->expectExceptionMessageMatches('#Error connecting to mysql server: Unknown database \'mysql_test_foo\'#');
        } else {
            $this->expectExceptionMessageRegExp('#Error connecting to mysql server: Unknown database \'mysql_test_foo\'#');
        }

        DB::getInstance('localhost', 'root', '', 'mysql_test_foo', null, '', true, false);
    }

    public function testGetInstance()
    {
        $this->setUpNonVoid();

        $db_1 = DB::getInstance('localhost', 'root', '', 'mysql_test', '', '', false, false);
        static::assertInstanceOf('\\voku\\db\\DB', $db_1);

        $db_2 = DB::getInstance('localhost', 'root', '', 'mysql_test', '', '', true, false);
        static::assertInstanceOf('\\voku\\db\\DB', $db_2);

        $db_3 = DB::getInstance('localhost', 'root', '', 'mysql_test', null, '', true, false);
        static::assertInstanceOf('\\voku\\db\\DB', $db_3);

        $db_4 = DB::getInstance();
        static::assertInstanceOf('\\voku\\db\\DB', $db_4);
        $db_4_serial = \serialize($db_4);
        unset($db_4);
        $db_4 = \unserialize($db_4_serial);
        static::assertInstanceOf('\\voku\\db\\DB', $db_4);

        $true = $this->db->connect();
        static::assertTrue($true);

        $true = $this->db->connect();
        static::assertTrue($true);

        $true = $this->db->reconnect(false);
        static::assertTrue($true);

        $true = $this->db->reconnect(true);
        static::assertTrue($true);
    }

    public function testInsertOnlyAndSimple()
    {
        $this->setUpNonVoid();

        // insert - true
        $pageArray = [
            'page_template' => '
foo
',
            'page_type'     => 'lallll',
        ];
        $tmpId = $this->db->insert($this->tableName, $pageArray);
        static::assertTrue($tmpId > 0);
    }

    public function testInsertOnlyViaGetContent()
    {
        $this->setUpNonVoid();

        $html = \file_get_contents(__DIR__ . '/fixtures/sample-simple-html.txt');

        // insert - true
        $pageArray = [
            'page_template' => $html,
            'page_type'     => 'lallll',
        ];
        $tmpId = $this->db->insert($this->tableName, $pageArray);
        static::assertTrue($tmpId > 0);
    }

    public function testInsertBugPregReplace()
    {
        $this->setUpNonVoid();

        // insert - true
        $pageArray = [
            'page_template' => '$2y$10$HURk5OhFbsJV5GmLHtBgKeD1Ul86Saa4YnWE4vhlc79kWlCpeiHBC',
            'page_type'     => 'lall',
        ];
        $tmpId = $this->db->insert($this->tableName, $pageArray);
        static::assertTrue($tmpId > 0);

        // select - true
        $result = $this->db->select($this->tableName, 'page_id = ' . (int) $tmpId);
        $tmpPage = $result->fetchObject();
        static::assertSame('$2y$10$HURk5OhFbsJV5GmLHtBgKeD1Ul86Saa4YnWE4vhlc79kWlCpeiHBC', $tmpPage->page_template);

        // select - true
        $result->reset();
        foreach ($result as $resultItem) {
            static::assertSame('$2y$10$HURk5OhFbsJV5GmLHtBgKeD1Ul86Saa4YnWE4vhlc79kWlCpeiHBC', $resultItem['page_template']);
        }

        $tmpPage = $result->fetchObject('', null, true);
        static::assertSame('$2y$10$HURk5OhFbsJV5GmLHtBgKeD1Ul86Saa4YnWE4vhlc79kWlCpeiHBC', $tmpPage->page_template);

        // --

        $sql = 'INSERT INTO ' . $this->tableName . '
      SET
        page_template = ?,
        page_type = ?
    ';
        $tmpId = $this->db->query(
            $sql,
            [
                '$2y$10$HURk5OhFbsJV5G?mLHtBgKeD1Ul86Saa4YnWE4vhlc79kWlCpeiHBC',
                '$0y$10$HURk5OhFbsJV5GmLHtBgKeD1Ul86Saa4YnWE4v?hlc79kWlCpeiHBC$',
            ]
        );

        // select - true
        $result = $this->db->select($this->tableName, 'page_id = ' . (int) $tmpId);
        $tmpPage = $result->fetchObject();
        static::assertSame('$2y$10$HURk5OhFbsJV5G?mLHtBgKeD1Ul86Saa4YnWE4vhlc79kWlCpeiHBC', $tmpPage->page_template);
        static::assertSame('$0y$10$HURk5OhFbsJV5GmLHtBgKeD1Ul86Saa4YnWE4v?hlc79kWlCpeiHBC$', $tmpPage->page_type);
    }

    public function testInsertAndSelectOnlyUtf84mbV1()
    {
        $this->setUpNonVoid();

        $html = UTF8::clean(\file_get_contents(__DIR__ . '/fixtures/sample-html.txt'), true, true, true);

        // insert - true
        $pageArray = [
            'page_template' => $html,
            'page_type'     => 'lallll',
        ];
        $tmpId = $this->db->insert($this->tableName, $pageArray);
        static::assertTrue($tmpId > 0);

        // select - true
        $this->db->select($this->tableName, 'page_id = ' . (int) $tmpId);
    }

    public function testInsertAndSelectOnlyUtf84mbV2()
    {
        $this->setUpNonVoid();

        $html = UTF8::clean(\file_get_contents(__DIR__ . '/fixtures/sample-html.txt'), true, true, true);

        // insert - true
        $pageArray = [
            'page_template' => $html,
            'page_type'     => 'lallll',
        ];
        $tmpId = $this->db->insert($this->tableName, $pageArray);
        static::assertTrue($tmpId > 0);

        // select - true
        $result = $this->db->select($this->tableName, 'page_id = ' . (int) $tmpId);
        $result->fetchArray();
    }

    public function testInsertAndSelectOnlyUtf84mbV3()
    {
        $this->setUpNonVoid();

        $html = UTF8::clean(\file_get_contents(__DIR__ . '/fixtures/sample-html.txt'), true, true, true);

        // insert - true
        $pageArray = [
            'page_template' => $html,
            'page_type'     => 'lallll',
        ];
        $tmpId = $this->db->insert($this->tableName, $pageArray);
        static::assertTrue($tmpId > 0);

        // select - true
        $result = $this->db->select($this->tableName, 'page_id = ' . (int) $tmpId);
        $tmpPage = $result->fetchArray();
        static::assertSame('
鼦վͼ
' . "\n", $tmpPage['page_template']);
    }

    public function testGetInstanceHostnameException()
    {
        $this->setUpNonVoid();

        $this->expectException(\Exception::class);
        $this->expectExceptionMessage('no-sql-hostname');

        DB::getInstance('', 'root', '', 'mysql_test', 3306, 'utf8', false, false);
    }

    public function testGetInstanceUsernameException()
    {
        $this->setUpNonVoid();

        $this->expectException(\Exception::class);
        $this->expectExceptionMessage('no-sql-username');

        DB::getInstance('localhost', '', '', 'mysql_test', 3306, 'utf8', false, false);
    }

    public function testGetInstanceDatabaseException()
    {
        $this->setUpNonVoid();

        $this->expectException(\Exception::class);
        $this->expectExceptionMessage('no-sql-database');

        DB::getInstance('localhost', 'root', '', '', 3306, 'utf8', false, false);
    }

    public function testCharset()
    {
        $this->setUpNonVoid();

        if (Helper::isUtf8mb4Supported($this->db) === true) {
            static::assertSame('utf8mb4', $this->db->get_charset());
        } else {
            static::assertSame('utf8', $this->db->get_charset());
        }

        $return = $this->db->set_charset('utf8');
        static::assertTrue($return);

        if (Helper::isUtf8mb4Supported($this->db) === true) {
            static::assertSame('utf8mb4', $this->db->get_charset());
        } else {
            static::assertSame('utf8', $this->db->get_charset());
        }
    }

    public function testInsertUtf84mb()
    {
        $this->setUpNonVoid();

        $html = UTF8::clean(\file_get_contents(__DIR__ . '/fixtures/sample-html.txt'), true, true, true);

        // insert - true
        $pageArray = [
            'page_template' => $html,
            'page_type'     => 'lall',
        ];
        $tmpId = $this->db->insert($this->tableName, $pageArray);
        static::assertTrue($tmpId > 0);

        // select - true
        $result = $this->db->select($this->tableName, 'page_id = ' . (int) $tmpId);
        $tmpPage = $result->fetchObject();
        static::assertSame('
鼦վͼ
' . "\n", $tmpPage->page_template);
    }

    public function testBasics()
    {
        $this->setUpNonVoid();

        // insert - true
        $pageArray = [
            'page_template' => 'tpl_new_中',
            'page_type'     => 'lall',
        ];
        $tmpId = $this->db->insert($this->tableName, $pageArray);

        // insert - false
        $false = $this->db->insert($this->tableName);
        static::assertFalse($false);

        // insert - false
        $false = $this->db->insert('', $pageArray);
        static::assertFalse($false);

        // select - true
        $result = $this->db->select($this->tableName, 'page_id = ' . (int) $tmpId);
        $tmpPage = $result->fetchObject();
        static::assertSame('tpl_new_中', $tmpPage->page_template);

        // select - true (but 0 results)
        $result = $this->db->select($this->tableName, 'page_id = -1');
        static::assertSame(0, $result->num_rows);

        // select - true
        $result = $this->db->select($this->tableName, 'page_id = ' . (int) $tmpId);
        $tmpPage = $result->fetchObject('stdClass');
        static::assertSame('tpl_new_中', $tmpPage->page_template);

        // select - true (but 0 results)
        $result = $this->db->select($this->tableName, 'page_id = -1');
        $result->fetchObject('stdClass');
        static::assertSame(0, $result->num_rows);

        // select - true
        $result = $this->db->select($this->tableName, 'page_id = ' . (int) $tmpId);

        /* @var $tmpPage Foobar */
        $tmpPage = $result->fetchObject(
            'Foobar',
            [
                [
                    'foo' => 1,
                    'bar' => 2,
                ],
            ]
        );

        static::assertSame(1, $tmpPage->foo);
        static::assertSame(2, $tmpPage->bar);
        static::assertTrue($tmpPage->test);
        static::assertNull($tmpPage->nothing);
        static::assertSame('tpl_new_中', $tmpPage->page_template);

        $tmpFooBar = new Foobar();
        $tmpFooBar->test = null;
        $tmpPage = $result->fetchObject($tmpFooBar, null, true);

        static::assertNull($tmpPage->foo);
        static::assertNull($tmpPage->bar);
        static::assertNull($tmpPage->test);
        static::assertNull($tmpPage->nothing);
        static::assertSame('tpl_new_中', $tmpPage->page_template);

        /* @var $tmpPages Foobar[] */
        $tmpPages = $result->fetchAllObject(
            'Foobar',
            [
                [
                    'foo' => 1,
                    'bar' => 2,
                ],
            ]
        );

        static::assertSame(1, $tmpPages[0]->foo);
        static::assertSame(2, $tmpPages[0]->bar);
        static::assertTrue($tmpPages[0]->test);
        static::assertNull($tmpPages[0]->nothing);
        static::assertSame('tpl_new_中', $tmpPages[0]->page_template);

        /* @var $tmpPages [] Foobar */
        $tmpPages = $result->fetchAllObject('Foobar');

        static::assertNull($tmpPages[0]->foo);
        static::assertNull($tmpPages[0]->bar);
        static::assertTrue($tmpPages[0]->test);
        static::assertSame('lall', $tmpPages[0]->page_type);
        static::assertSame('tpl_new_中', $tmpPages[0]->page_template);

        // update - true
        $pageArray = [
            'page_template' => 'tpl_update',
        ];
        $this->db->update($this->tableName, $pageArray, 'page_id = ' . (int) $tmpId);

        // update - false
        $false = $this->db->update($this->tableName, [], 'page_id = ' . (int) $tmpId);
        static::assertFalse($false);

        // update - false
        $false = $this->db->update($this->tableName, $pageArray, '');
        static::assertFalse($false);

        // update - false
        $false = $this->db->update($this->tableName, $pageArray, null);
        static::assertFalse($false);

        // update - false
        $false = $this->db->update('', $pageArray, 'page_id = ' . (int) $tmpId);
        static::assertFalse($false);

        // check (select)
        $result = $this->db->select($this->tableName, 'page_id = ' . (int) $tmpId);
        $tmpPage = $result->fetchAllObject();
        static::assertSame('tpl_update', $tmpPage[0]->page_template);

        // replace - true
        $data = [
            'page_id'       => 2,
            'page_template' => 'tpl_test',
            'page_type'     => 'öäü123',
        ];
        $tmpId = $this->db->replace($this->tableName, $data);

        // replace - false
        $false = $this->db->replace($this->tableName);
        static::assertFalse($false);

        // replace - false
        $false = $this->db->replace('', $data);
        static::assertFalse($false);

        $result = $this->db->select($this->tableName, 'page_id = ' . (int) $tmpId);
        $tmpPage = $result->fetchAllObject();
        static::assertSame('tpl_test', $tmpPage[0]->page_template);

        // delete - true
        $affected_rows = $this->db->delete($this->tableName, ['page_id' => $tmpId]);
        static::assertSame(1, $affected_rows);

        // delete - true (but 0 affected_rows)
        $affected_rows = $this->db->delete($this->tableName, ['page_id' => -1]);
        static::assertSame(0, $affected_rows);

        // delete - false
        $false = $this->db->delete('', ['page_id' => $tmpId]);
        static::assertFalse($false);

        // insert - true
        $pageArray = [
            'page_template' => 'tpl_new_中',
            'page_type'     => 'lall',
        ];
        $tmpId = $this->db->insert($this->tableName, $pageArray);

        // delete - nothing
        $false = $this->db->delete($this->tableName, '');
        static::assertFalse($false);

        // delete - false
        $false = $this->db->delete($this->tableName, null);
        static::assertFalse($false);

        // delete - true
        $false = $this->db->delete($this->tableName, 'page_id = ' . $this->db->escape($tmpId));
        static::assertSame(1, $false);

        // select - true
        $result = $this->db->select($this->tableName, ['page_id' => 2]);
        static::assertSame(0, $result->num_rows);

        $resultArray = $result->fetchArray();
        static::assertSame([], $resultArray);

        $resultArray = $result->fetchArrayy();
        static::assertSame([], $resultArray->getArray());

        // select - true
        $result = $this->db->select($this->tableName);
        static::assertTrue($result->num_rows > 0);

        // select - true (but empty)
        $result = $this->db->select($this->tableName, ['page_id' => -1]);
        static::assertSame(0, $result->num_rows);

        // select - false
        $false = $this->db->select($this->tableName, null);
        static::assertFalse($false);

        // select - false
        $false = $this->db->select('', ['page_id' => 2]);
        static::assertFalse($false);

        // insert - true (float)
        $pageArray = [
            'page_template' => 'tpl_new_中',
            'page_type'     => (float) 21.3123,
        ];
        $tmpId = $this->db->insert($this->tableName, $pageArray);

        // delete - true
        $delete = $this->db->delete($this->tableName, 'page_id = ' . $this->db->escape($tmpId));
        static::assertSame(1, $delete);

        // insert - true (int)
        $pageArray = [
            'page_template' => 'tpl_new_中',
            'page_type'     => (float) 213123,
        ];
        $tmpId = $this->db->insert($this->tableName, $pageArray);

        // delete - true
        $delete = $this->db->delete($this->tableName, 'page_id = ' . $this->db->escape($tmpId));
        static::assertSame(1, $delete);
    }

    public function testQry()
    {
        $this->setUpNonVoid();

        $sql = 'UPDATE ' . $this->db->escape($this->tableName) . "
      SET
        page_template = '?'
      WHERE page_id = ?
    ";
        /** @noinspection StaticInvocationViaThisInspection */
        /** @noinspection PhpStaticAsDynamicMethodCallInspection */
        $result = $this->db->qry($sql, 'tpl_test_?', 1);
        static::assertSame(0, $result);

        $sql = 'SELECT * FROM ' . $this->db->escape($this->tableName) . '
      WHERE page_id = 1
    ';
        /** @noinspection StaticInvocationViaThisInspection */
        /** @noinspection PhpStaticAsDynamicMethodCallInspection */
        $result = $this->db->qry($sql);
        static::assertSame('tpl_test_?', $result[0]['page_template']);

        $sql = 'SELECT * FROM ' . $this->db->escape($this->tableName) . '
      WHERE page_id_lall = 1
    ';
        /** @noinspection StaticInvocationViaThisInspection */
        /** @noinspection PhpStaticAsDynamicMethodCallInspection */
        $result = $this->db->qry($sql);
        static::assertFalse($result);
    }

    public function testTableExists()
    {
        $this->setUpNonVoid();

        $result = $this->db->table_exists($this->tableName);
        static::assertTrue($result);

        // ---------

        $result = $this->db->table_exists('no_table_name');
        static::assertFalse($result);
    }

    public function testNumRows()
    {
        $this->setUpNonVoid();

        $sql = 'SELECT * FROM ' . $this->db->escape($this->tableName) . '
      WHERE page_id = 1
    ';
        $num_rows = $this->db->num_rows($sql);
        static::assertSame(1, $num_rows);

        // ---------

        $sql = 'SELECT * FROM ' . $this->db->escape($this->tableName) . '
      WHERE page_id = -1
    ';
        $num_rows = $this->db->num_rows($sql);
        static::assertSame(0, $num_rows);
    }

    public function testEscape()
    {
        $this->setUpNonVoid();

        $date = new DateTimeImmutable('2016-08-15 09:22:18');

        static::assertSame($date->format('Y-m-d H:i:s'), $this->db->escape($date));

        // ---

        $object = new stdClass();
        $object->bar = 'foo';

        $errorCatch = false;

        try {
            $this->db->secure($object);
        } catch (InvalidArgumentException $e) {
            $errorCatch = true;
        }
        static::assertTrue($errorCatch);

        // ---

        $object = new Arrayy(['foo', 123, 'öäü']);

        static::assertSame('foo,123,öäü', $this->db->escape($object));

        // ---

        static::assertSame('', $this->db->escape(''));

        // ---

        static::assertSame('NULL', $this->db->escape(null));

        // ---

        $testArray = [
            'NOW()'                                  => 'NOW()',
            'fooo'                                   => 'fooo',
            123                                      => 123,
            'κόσμε'                                  => 'κόσμε',
            '<abcd>\'$1\'("&2")' => '<abcd>\\\'$1\\\'("&2")',
            'öäü'                     => 'öäü',
        ];

        foreach ($testArray as $before => $after) {
            static::assertSame($after, $this->db->escape($before));
        }

        static::assertSame(\array_values($testArray), $this->db->escape(\array_keys($testArray)));

        static::assertSame('NOW(),fooo,123,κόσμε,<abcd>\\\'$1\\\'(\"&2\"),öäü', $this->db->escape(\array_keys($testArray), false, true, true));
        static::assertSame('NOW(),fooo,123,κόσμε,<abcd>\\\'$1\\\'("&2"),öäü', $this->db->escape(\array_keys($testArray), true, false, true));
        static::assertSame('NOW(),fooo,123,κόσμε,<abcd>\\\'$1\\\'("&2"),öäü', $this->db->escape(\array_keys($testArray), false, false, true));
        static::assertSame('NOW(),fooo,123,κόσμε,<abcd>\\\'$1\\\'(\"&2\"),öäü', $this->db->escape(\array_keys($testArray), true, true, true));

        static::assertSame(
            [
                0 => 'NOW()',
                1 => 'fooo',
                2 => 123,
                3 => 'κόσμε',
                4 => '<abcd>\\\'$1\\\'(\"&2\")',
                5 => 'öäü',
            ],
            $this->db->escape(\array_keys($testArray), false, true, false)
        );
        static::assertSame(
            [
                0 => 'NOW()',
                1 => 'fooo',
                2 => 123,
                3 => 'κόσμε',
                4 => '<abcd>\\\'$1\\\'("&2")',
                5 => 'öäü',
            ],
            $this->db->escape(\array_keys($testArray), true, false, false)
        );
        static::assertSame(
            [
                0 => 'NOW()',
                1 => 'fooo',
                2 => 123,
                3 => 'κόσμε',
                4 => '<abcd>\\\'$1\\\'("&2")',
                5 => 'öäü',
            ],
            $this->db->escape(\array_keys($testArray), false, false, false)
        );
        static::assertSame(
            [
                0 => 'NOW()',
                1 => 'fooo',
                2 => 123,
                3 => 'κόσμε',
                4 => '<abcd>\\\'$1\\\'(\"&2\")',
                5 => 'öäü',
            ],
            $this->db->escape(\array_keys($testArray), true, true, false)
        );

        static::assertSame('NULL', $this->db->escape(\array_keys($testArray), false, true, null));
        static::assertSame('NULL', $this->db->escape(\array_keys($testArray), true, false, null));
        static::assertSame('NULL', $this->db->escape(\array_keys($testArray), false, false, null));
        static::assertSame('NULL', $this->db->escape(\array_keys($testArray), true, true, null));

        // ---

        $this->db = DB::getInstance();

        $data = [
            'page_template' => "tpl_test_'new2",
            'page_type'     => 1.1,
        ];

        $newData = (array) $this->db->escape($data);

        static::assertSame('tpl_test_\\\'new2', $newData['page_template']);
        static::assertSame(1.10000000, $newData['page_type']);

        // ---

        $data = [
            'page_template' => "tpl_test_'new2",
            'page_type'     => 1.1,
        ];

        $newData = $this->db->escape($data, true, true, true);

        static::assertSame('tpl_test_\\\'new2,1.1', $newData);

        // ---

        $data = [
            'page_template' => "tpl_test_'new2",
            'page_type'     => '0111',
        ];

        $newData = $this->db->escape($data, true, false, true);

        static::assertSame('tpl_test_\\\'new2,0111', $newData);

        // ---

        $data = [
            'page_template' => "tpl_test_'new2",
            'page_type'     => '111',
        ];

        $newData = $this->db->escape($data, true, false, true);

        static::assertSame('tpl_test_\\\'new2,111', $newData);

        // ---

        $data = [];

        $tested = $this->db->escape($data);

        static::assertSame([], $tested);

        // ---

        $data = ['foo\'', 'bar"'];

        $tested = $this->db->escape($data);

        static::assertSame(['foo\\\'', 'bar\"'], $tested);

        // ---

        $data = 123;

        $tested = $this->db->escape($data);

        static::assertSame(123, $tested);

        // ---

        $data = true;

        $tested = $this->db->escape($data);

        static::assertSame(1, $tested);

        // ---

        $data = false;

        $tested = $this->db->escape($data);

        static::assertSame(0, $tested);

        // ---

        $data = [true, false];

        $tested = $this->db->escape($data);

        static::assertSame([1, 0], $tested);

        // ---

        $data = 'http://foobar.com?test=1';

        $tested = $this->db->escape($data);

        static::assertSame('http://foobar.com?test=1', $tested);
    }

    public function testFormatQuery()
    {
        $this->setUpNonVoid();

        $result = $this->invokeMethod(
            $this->db,
            '_parseQueryParamsByName',
            [
                'SELECT * FROM post WHERE id = :id',
                ['id' => 1],
            ]
        );

        static::assertSame(
            'SELECT * FROM post WHERE id = 1',
            $result['sql']
        );

        static::assertSame(
            [],
            $result['params']
        );

        $result = $this->invokeMethod(
            $this->db,
            '_parseQueryParamsByName',
            [
                'SELECT * FROM post WHERE id=:id',
                ['id' => 1],
            ]
        );
        static::assertSame(
            'SELECT * FROM post WHERE id=1',
            $result['sql']
        );
        static::assertSame(
            [],
            $result['params']
        );

        $result = $this->invokeMethod(
            $this->db,
            '_parseQueryParamsByName',
            [
                'SELECT * FROM post WHERE id = ' . "\n" . '  :id;',
                ['id' => 1],
            ]
        );
        static::assertSame(
            'SELECT * FROM post WHERE id = ' . "\n" . '  1;',
            $result['sql']
        );
        static::assertSame(
            [],
            $result['params']
        );

        $result = $this->invokeMethod(
            $this->db,
            '_parseQueryParamsByName',
            [
                'SELECT * FROM post WHERE id = ' . "\n" . '  :id;',
                ['id' => 1, 'foo' => 'bar'],
            ]
        );
        static::assertSame(
            'SELECT * FROM post WHERE id = ' . "\n" . '  1;',
            $result['sql']
        );
        static::assertSame(
            ['foo' => 'bar'],
            $result['params']
        );
    }

    public function testConnector()
    {
        $this->setUpNonVoid();

        $data = [
            'page_template' => 'tpl_test_new',
        ];
        $where = [
            'page_id LIKE' => '1',
        ];

        // will return the number of effected rows
        $resultUpdate = $this->db->update($this->tableName, $data, $where);
        static::assertSame(1, $resultUpdate);

        $data = [
            'page_template' => 'tpl_test_new2',
            'page_type'     => 'öäü',
        ];

        // will return the auto-increment value of the new row
        $resultInsert = $this->db->insert($this->tableName, $data);
        static::assertGreaterThan(1, $resultInsert);

        $where = [
            'page_type ='        => 'öäü',
            'page_type NOT LIKE' => '%öäü123',
            'page_id ='          => $resultInsert,
        ];

        $resultSelect = $this->db->select($this->tableName, $where);
        $resultSelectArray = $resultSelect->fetchArray();
        static::assertSame('öäü', $resultSelectArray['page_type']);

        $resultSelect = $this->db->select($this->tableName, $where);
        foreach ($resultSelect->fetchYield() as $resultSelectArray) {
            static::assertSame('öäü', $resultSelectArray->page_type);
        }

        $resultSelect = $this->db->select($this->tableName, $where);
        $resultSelectArray = $resultSelect->fetchArrayy();
        static::assertSame('öäü', $resultSelectArray['page_type']);

        $resultSelect = $this->db->select($this->tableName, $where);
        $resultSelectArray = $resultSelect->fetchArrayy()->clean()->getArray();
        static::assertSame('öäü', $resultSelectArray['page_type']);

        $where = [
            'page_type ='  => 'öäü',
            'page_type <>' => 'öäü123',
            'page_id >'    => 0,
            'page_id >='   => 0,
            'page_id <'    => 1000000,
            'page_id <='   => 1000000,
            'page_id ='    => $resultInsert,
        ];

        $resultSelect = $this->db->select($this->tableName, $where);
        $resultSelectArray = $resultSelect->fetchArrayPair('page_type', 'page_type');
        static::assertSame('öäü', $resultSelectArray['öäü']);

        $where = [
            'page_type LIKE'     => 'öäü',
            'page_type NOT LIKE' => 'öäü123',
            'page_id ='          => $resultInsert,
        ];

        $resultSelect = $this->db->select($this->tableName, $where);
        $resultSelectArray = $resultSelect->fetch();
        $getDefaultResultType = $resultSelect->getDefaultResultType();
        static::assertSame('object', $getDefaultResultType);
        static::assertSame(Result::RESULT_TYPE_OBJECT, $getDefaultResultType);
        static::assertSame('öäü', $resultSelectArray->page_type);

        $resultSelect = $this->db->select($this->tableName, $where);
        $resultSelect->setDefaultResultType('array'); // switch default result-type
        $resultSelectArray = $resultSelect->fetch();
        $getDefaultResultType = $resultSelect->getDefaultResultType();
        static::assertSame('array', $getDefaultResultType);
        static::assertSame(Result::RESULT_TYPE_ARRAY, $getDefaultResultType);
        /** @noinspection OffsetOperationsInspection */
        static::assertSame('öäü', $resultSelectArray['page_type']);

        $resultSelect = $this->db->select($this->tableName, $where);
        $resultSelectArray = $resultSelect->fetchArray();
        static::assertSame('öäü', $resultSelectArray['page_type']);

        $resultSelect = $this->db->select($this->tableName, $where);
        $resultSelectArray = $resultSelect->get();
        static::assertSame('öäü', $resultSelectArray->page_type);

        $resultSelect = $this->db->select($this->tableName, $where);
        $resultSelectArray = $resultSelect->getAll();
        static::assertSame('öäü', $resultSelectArray[0]->page_type);

        $resultSelect = $this->db->select($this->tableName, $where);
        $resultSelectArray = $resultSelect->getArray();
        static::assertSame('öäü', $resultSelectArray[0]['page_type']);

        $resultSelect = $this->db->select($this->tableName, $where);
        $resultSelectArray = $resultSelect->getObject();
        static::assertSame('öäü', $resultSelectArray[0]->page_type);

        $resultSelect = $this->db->select($this->tableName, $where);
        $resultSelectTmp = $resultSelect->getColumn('page_type');
        static::assertSame('öäü', $resultSelectTmp);

        $resultSelect = $this->db->select($this->tableName, $where);
        static::assertInstanceOf('\\voku\\db\\Result', $resultSelect);
    }

    public function testTransactionFalse()
    {
        $this->setUpNonVoid();

        $data = [
            'page_template' => 'tpl_test_new3',
            'page_type'     => 'öäü',
        ];

        // will return the auto-increment value of the new row
        $resultInsert = $this->db->insert($this->tableName, $data);
        static::assertGreaterThan(1, $resultInsert);

        // start - test a transaction - true
        $beginTransaction = $this->db->beginTransaction();
        static::assertTrue($beginTransaction);

        $data = [
            'page_type' => 'lall',
        ];
        $where = [
            'page_id' => $resultInsert,
        ];
        $this->db->update($this->tableName, $data, $where);

        $data = [
            'page_type' => 'lall',
            'page_lall' => 'öäü',
            // this will produce a mysql-error and a mysqli-rollback via "db->endTransaction()"
        ];
        $where = [
            'page_id' => $resultInsert,
        ];
        $this->db->update($this->tableName, $data, $where);

        // end - test a transaction
        $this->db->endTransaction();

        $where = [
            'page_id' => $resultInsert,
        ];

        $resultSelect = $this->db->select($this->tableName, $where);
        $resultSelectArray = $resultSelect->fetchAllArray();
        static::assertSame('öäü', $resultSelectArray[0]['page_type']);

        $resultSelect = $this->db->select($this->tableName, $where);
        $resultSelectArray = $resultSelect->fetchAllArrayy()->filterBy('page_type', 'öäü')->first();
        static::assertSame('öäü', $resultSelectArray['page_type']);

        $resultSelect = $this->db->select($this->tableName, $where);
        foreach ($resultSelect->fetchAllYield('Foobar') as $tmpResult) {
            static::assertSame('öäü', $tmpResult->page_type);
        }

        $resultSelect = $this->db->select($this->tableName, $where);
        foreach ($resultSelect->fetchAllYield() as $tmpResult) {
            static::assertSame('öäü', $tmpResult->page_type);
        }
    }

    public function testTransactionTrue()
    {
        $this->setUpNonVoid();

        $data = [
            'page_template' => 'tpl_test_new3',
            'page_type'     => 'öäü',
        ];

        // will return the auto-increment value of the new row
        $resultInsert = $this->db->insert($this->tableName, $data);
        static::assertGreaterThan(1, $resultInsert);

        // start - test a transaction
        $this->db->startTransaction();

        $data = [
            'page_type' => 'lall',
        ];
        $where = [
            'page_id' => $resultInsert,
        ];
        $this->db->update($this->tableName, $data, $where);

        $data = [
            'page_type'     => 'lall',
            'page_template' => 'öäü',
        ];
        $where = [
            'page_id' => $resultInsert,
        ];
        $this->db->update($this->tableName, $data, $where);

        // end - test a transaction
        $this->db->endTransaction();

        $where = [
            'page_id' => $resultInsert,
        ];

        $resultSelect = $this->db->select($this->tableName, $where);
        $resultSelectArray = $resultSelect->fetchAllArray();
        static::assertSame('lall', $resultSelectArray[0]['page_type']);
    }

    public function testRollback()
    {
        $this->setUpNonVoid();

        // start - test a transaction
        $this->db->beginTransaction();

        $data = [
            'page_template' => 'tpl_test_new4',
            'page_type'     => 'öäü',
        ];

        // will return the auto-increment value of the new row
        $resultInsert = $this->db->insert($this->tableName, $data);
        static::assertGreaterThan(1, $resultInsert);

        $data = [
            'page_type' => 'lall',
        ];
        $where = [
            'page_id' => $resultInsert,
        ];
        $this->db->update($this->tableName, $data, $where);

        $data = [
            'page_type' => 'lall',
            'page_lall' => 'öäü',
            // this will produce a mysql-error and a mysqli-rollback
        ];
        $where = [
            'page_id' => $resultInsert,
        ];
        $this->db->update($this->tableName, $data, $where);

        // end - test a transaction, with a rollback!
        $this->db->rollback();

        $where = [
            'page_id' => $resultInsert,
        ];
        $resultSelect = $this->db->select($this->tableName, $where);
        static::assertSame(0, $resultSelect->num_rows);
    }

    /**
     * @depends testRollback
     */
    public function testGetErrors()
    {
        $this->setUpNonVoid();

        // INFO: run all previous tests and generate some errors

        $error = $this->db->lastError();
        if (method_exists($this, 'assertInternalType')) {
            static::assertInternalType('string', $error);
        } else {
            static::assertIsString($error);
        }
        if (method_exists($this, 'assertStringContainsString')) {
            static::assertStringContainsString('Unknown column \'page_lall\' in \'field list', $error);
        } else {
            static::assertContains('Unknown column \'page_lall\' in \'field list', $error);
        }

        $errors = $this->db->getErrors();
        if (method_exists($this, 'assertInternalType')) {
            static::assertInternalType('array', $errors);
        } else {
            static::assertIsArray($errors);
        }
        if (method_exists($this, 'assertStringContainsString')) {
            static::assertStringContainsString('Unknown column \'page_lall\' in \'field list', $errors[0]);
        } else {
            static::assertContains('Unknown column \'page_lall\' in \'field list', $errors[0]);
        }
    }

    public function testCommit()
    {
        $this->setUpNonVoid();

        // start - test a transaction
        $this->db->beginTransaction();

        $data = [
            'page_template' => 'tpl_test_new4',
            'page_type'     => 'öäü',
        ];

        // will return the auto-increment value of the new row
        $resultInsert = $this->db->insert($this->tableName, $data);
        static::assertGreaterThan(1, $resultInsert);

        $data = [
            'page_type' => 'lall',
        ];
        $where = [
            'page_id' => $resultInsert,
        ];
        $this->db->update($this->tableName, $data, $where);

        $data = [
            'page_type' => 'lall',
            'page_lall' => 'öäü',
            // this will produce a mysql-error and a mysqli-rollback
        ];
        $where = [
            'page_id' => $resultInsert,
        ];
        $this->db->update($this->tableName, $data, $where);

        // end - test a transaction, with a commit!
        $this->db->commit();

        $where = [
            'page_id' => $resultInsert,
        ];
        $resultSelect = $this->db->select($this->tableName, $where);
        static::assertSame(1, $resultSelect->num_rows);
    }

    public function testTransactionException()
    {
        $this->setUpNonVoid();

        // start - test a transaction - true
        $beginTransaction = $this->db->beginTransaction();
        static::assertTrue($beginTransaction);

        // start - test a transaction - false
        $beginTransaction = $this->db->beginTransaction();
        static::assertFalse($beginTransaction);

        // reset
        $this->db->endTransaction();
    }

    public function testFetchColumn()
    {
        $this->setUpNonVoid();

        $data = [
            'page_template' => 'tpl_test_new5',
            'page_type'     => 'öäü',
        ];

        // will return the auto-increment value of the new row
        $resultInsert = $this->db->insert($this->tableName, $data);
        static::assertGreaterThan(1, $resultInsert);

        $dataV2 = [
            'page_template' => 'tpl_test_new5V2',
            'page_type'     => 'öäüV2',
        ];

        // will return the auto-increment value of the new row (v2)
        $resultInsertV2 = $this->db->insert($this->tableName, $dataV2);
        static::assertGreaterThan($resultInsert, $resultInsertV2);

        // ---

        $resultSelect = $this->db->select($this->tableName, ['page_id' => $resultInsert]);

        $columnResult = $resultSelect->fetchColumn('page_template');
        static::assertSame('tpl_test_new5', $columnResult);

        $columnResult = $resultSelect->fetchColumn('page_template', false, false);
        static::assertSame('tpl_test_new5', $columnResult);

        $columnResult = $resultSelect->fetchColumn('page_template', true, true);
        static::assertSame(['tpl_test_new5'], $columnResult);

        $columnResult = $resultSelect->fetchColumn('page_template', false, true);
        static::assertSame(['tpl_test_new5'], $columnResult);

        $columnResult = $resultSelect->fetchColumn('page_template_foo');
        static::assertSame('', $columnResult);

        $columnResult = $resultSelect->fetchColumn('page_template_foo', false, false);
        static::assertSame('', $columnResult);

        $columnResult = $resultSelect->fetchColumn('page_template_foo', true, true);
        static::assertSame([], $columnResult);

        $columnResult = $resultSelect->fetchColumn('page_template_foo', false, true);
        static::assertSame([], $columnResult);

        // ---

        $resultSelect = $this->db->select($this->tableName, ['page_id AND' => [$resultInsert, $resultInsert]]);

        $columnResult = $resultSelect->fetchColumn('page_template');
        static::assertSame('tpl_test_new5', $columnResult);

        $columnResult = $resultSelect->fetchColumn('page_template', false, false);
        static::assertSame('tpl_test_new5', $columnResult);

        $columnResult = $resultSelect->fetchColumn('page_template', true, true);
        static::assertSame(['tpl_test_new5'], $columnResult);

        $columnResult = $resultSelect->fetchColumn('page_template', false, true);
        static::assertSame(['tpl_test_new5'], $columnResult);

        $columnResult = $resultSelect->fetchColumn('page_template', true, false);
        static::assertSame('tpl_test_new5', $columnResult);

        // ---

        $resultSelect = $this->db->select($this->tableName, ['page_id OR' => [$resultInsert, $resultInsertV2]]);

        $columnResult = $resultSelect->fetchColumn('page_template');
        static::assertSame('tpl_test_new5V2', $columnResult);

        $columnResult = $resultSelect->fetchColumn('page_template', false, false);
        static::assertSame('tpl_test_new5V2', $columnResult);

        $columnResult = $resultSelect->fetchColumn('page_template', true, true);
        static::assertSame(['tpl_test_new5', 'tpl_test_new5V2'], $columnResult);

        $columnResult = $resultSelect->fetchColumn('page_template', true, false);
        static::assertSame('tpl_test_new5V2', $columnResult);

        $columnResult = $resultSelect->fetchColumn('page_template', false, true);
        static::assertSame(['tpl_test_new5', 'tpl_test_new5V2'], $columnResult);

        // ---

        $columnResult = $resultSelect->fetchColumn('page_template_foo');
        static::assertSame('', $columnResult);

        $columnResult = $resultSelect->fetchColumn('page_template_foo', false, false);
        static::assertSame('', $columnResult);

        $columnResult = $resultSelect->fetchColumn('page_template_foo', true, true);
        static::assertSame([], $columnResult);

        $columnResult = $resultSelect->fetchColumn('page_template_foo', false, true);
        static::assertSame([], $columnResult);

        $columnResult = $resultSelect->fetchColumn('page_template_foo', true, false);
        static::assertSame('', $columnResult);

        // ---

        $columnResult = $resultSelect->fetchAllColumn('page_template', false);
        static::assertSame(['tpl_test_new5', 'tpl_test_new5V2'], $columnResult);

        $columnResult = $resultSelect->fetchAllColumn('page_template', true);
        static::assertSame(['tpl_test_new5', 'tpl_test_new5V2'], $columnResult);

        $columnResult = $resultSelect->fetchAllColumn('page_template', true);
        static::assertSame(['tpl_test_new5', 'tpl_test_new5V2'], $columnResult);
    }

    public function testIsEmpty()
    {
        $this->setUpNonVoid();

        $data = [
            'page_template' => 'tpl_test_new5',
            'page_type'     => 'öäü',
        ];

        // will return the auto-increment value of the new row
        $resultInsert = $this->db->insert($this->tableName, $data);
        static::assertGreaterThan(1, $resultInsert);

        $resultSelect = $this->db->select($this->tableName, ['page_id' => $resultInsert]);
        static::assertFalse($resultSelect->is_empty());

        $resultSelect = $this->db->select($this->tableName, ['page_id' => 999999]);
        static::assertTrue($resultSelect->is_empty());
    }

    public function testJson()
    {
        $this->setUpNonVoid();

        $data = [
            'page_template' => 'tpl_test_new6',
            'page_type'     => 'öäü',
        ];

        // will return the auto-increment value of the new row
        $resultInsert = $this->db->insert($this->tableName, $data);
        static::assertGreaterThan(1, $resultInsert);

        $resultSelect = $this->db->select($this->tableName, ['page_id' => $resultInsert]);
        $columnResult = $resultSelect->json();
        $columnResultDecode = \json_decode($columnResult, true);
        static::assertSame('tpl_test_new6', $columnResultDecode[0]['page_template']);
    }

    public function testFetchObject()
    {
        $this->setUpNonVoid();

        $data = [
            'page_template' => 'tpl_test_new7',
            'page_type'     => 'öäü',
        ];

        // will return the auto-increment value of the new row
        $resultInsert = $this->db->insert($this->tableName, $data);
        static::assertGreaterThan(1, $resultInsert);

        $resultSelect = $this->db->select($this->tableName, ['page_id' => $resultInsert]);
        $columnResult = $resultSelect->fetchObject();
        static::assertSame('tpl_test_new7', $columnResult->page_template);
    }

    public function testDefaultResultType()
    {
        $this->setUpNonVoid();

        $data = [
            'page_template' => 'tpl_test_new8',
            'page_type'     => 'öäü',
        ];

        // will return the auto-increment value of the new row
        $resultInsert = $this->db->insert($this->tableName, $data);
        static::assertGreaterThan(1, $resultInsert);

        $resultSelect = $this->db->select($this->tableName, ['page_id' => $resultInsert]);

        // array
        $resultSelect->setDefaultResultType('array');

        $columnResult = (array) $resultSelect->fetch(true);
        static::assertSame('tpl_test_new8', $columnResult['page_template']);

        $columnResult = (array) $resultSelect->fetchAll();
        static::assertSame('tpl_test_new8', $columnResult[0]['page_template']);

        $columnResult = (array) $resultSelect->fetchAllArray();
        static::assertSame('tpl_test_new8', $columnResult[0]['page_template']);

        // object
        $resultSelect->setDefaultResultType('object');

        $columnResult = $resultSelect->fetch(true);
        static::assertSame('tpl_test_new8', $columnResult->page_template);

        $columnResult = $resultSelect->fetchAll();
        static::assertSame('tpl_test_new8', $columnResult[0]->page_template);

        $columnResult = $resultSelect->fetchAllObject();
        static::assertSame('tpl_test_new8', $columnResult[0]->page_template);
    }

    public function testGetAllTables()
    {
        $this->setUpNonVoid();

        $tableArray = $this->db->getAllTables();

        $return = false;
        foreach ($tableArray as $table) {
            assert($table instanceof \Arrayy\Arrayy);

            if ($table->contains($this->tableName)) {
                $return = true;

                break;
            }
        }

        static::assertTrue($return);
    }

    public function testPing()
    {
        $this->setUpNonVoid();

        $ping = $this->db->ping();
        static::assertTrue($ping);
    }

    public function testMultiQuery()
    {
        $this->setUpNonVoid();

        $sql = '
    INSERT INTO ' . $this->tableName . "
      SET
        page_template = 'lall1',
        page_type = 'test1';
    INSERT INTO " . $this->tableName . "
      SET
        page_template = 'lall2',
        page_type = 'test2';
    INSERT INTO " . $this->tableName . "
      SET
        page_template = 'lall3',
        page_type = 'test3';
    ";
        // multi_query - true
        $result = $this->db->multi_query($sql);
        static::assertTrue($result);

        $sql = '
    SELECT * FROM ' . $this->tableName . ';
    SELECT * FROM ' . $this->tableName . ';
    ';
        // multi_query - true
        $result = $this->db->multi_query($sql);
        if (method_exists($this, 'assertInternalType')) {
            static::assertInternalType('array', $result);
        } else {
            static::assertIsArray($result);
        }
        /** @noinspection ForeachSourceInspection */
        foreach ($result as $resultForEach) {
            /* @var $resultForEach Result */
            $tmpArray = $resultForEach->fetchArray();

            if (method_exists($this, 'assertInternalType')) {
                static::assertInternalType('array', $tmpArray);
            } else {
                static::assertIsArray($tmpArray);
            }
            static::assertTrue(\count($tmpArray) > 0);
        }

        // multi_query - false
        $false = $this->db->multi_query('');
        static::assertFalse($false);

        // multi_query - false
        $sql = '
    INSERT INTO ' . $this->tableName . "
      SET
        page_template_no = 'lall1',
        page_type = 'test1';
    INSERT INTO " . $this->tableName . "
      SET
        page_template = 'lall2',
        page_type = 'test2';
    INSERT INTO " . $this->tableName . "
      SET
        page_template = 'lall3',
        page_type = 'test3';
    ";
        // multi_query - true
        $result = $this->db->multi_query($sql);
        static::assertFalse($result);
    }

    public function testEscapeData()
    {
        $this->setUpNonVoid();

        $dbTmp = DB::getInstance('localhost', 'root', '', 'mysql_test', 3306, 'utf8', false, false);

        static::assertSame('NULL', $this->db->escape(null, true));
        static::assertSame(\mysqli_real_escape_string($dbTmp->getLink(), "O'Toole"), $dbTmp->escape("O'Toole"));
        static::assertSame(\mysqli_real_escape_string($dbTmp->getLink(), "O'Toole"), $dbTmp->escape("O'Toole", true));
        static::assertSame(1, $this->db->escape(true));
        static::assertSame(0, $this->db->escape(false));
        static::assertSame(1, $this->db->escape(true, false));
        static::assertSame(0, $this->db->escape(false, false));
        static::assertSame('NOW()', $this->db->escape('NOW()'));
        static::assertSame(
            [
                \mysqli_real_escape_string($dbTmp->getLink(), "O'Toole"),
                1,
                'NULL',
            ],
            $this->db->escape(["O'Toole", true, null])
        );
        static::assertSame(
            [
                \mysqli_real_escape_string($dbTmp->getLink(), "O'Toole"),
                1,
                'NULL',
            ],
            $this->db->escape(["O'Toole", true, null], false)
        );
    }

    public function testInvoke()
    {
        $this->setUpNonVoid();

        $db = $this->db;
        static::assertInstanceOf('\\voku\\db\\DB', $db());
        static::assertInstanceOf('\\voku\\db\\Result', $db('SELECT * FROM ' . $this->tableName));
    }

    public function testConnector2()
    {
        $this->setUpNonVoid();

        // select - true
        $where = [
            'page_type ='         => 'öäü',
            'page_type NOT LIKE'  => '%öäü123',
            'page_id >='          => 0,
            'page_id NOT BETWEEN' => [
                '99997',
                '99999',
            ],
            'page_id NOT IN' => [
                'test',
                'test123',
            ],
            'page_type IN' => [
                'öäü',
                '123',
                'abc',
            ],
            'page_type OR' => [
                'öäü',
                '123',
                'abc',
            ],
        ];
        $resultSelect = $this->db->select($this->tableName, $where);
        static::assertNotFalse($resultSelect, 'tested: ' . \print_r($where, true));
        static::assertTrue($resultSelect->num_rows > 0);

        // select - false
        $where = [
            'page_type IS NOT' => 'lall',
            'page_type IS'     => 'öäü',
        ];
        $resultSelect = $this->db->select($this->tableName, $where);
        static::assertFalse($resultSelect);
    }

    public function testExecSQL()
    {
        $this->setUpNonVoid();

        // execSQL - false
        $sql = 'INSERT INTO ' . $this->tableName . "
      SET
        page_template_lall = '" . $this->db->escape('tpl_test_new7') . "',
        page_type = " . $this->db->secure('öäü') . '
    ';
        $return = DB::execSQL($sql);
        static::assertFalse($return);

        // execSQL - true
        $sql = 'INSERT INTO ' . $this->tableName . "
      SET
        page_template = '" . $this->db->escape('tpl_test_new7') . "',
        page_type = " . $this->db->secure('öäü') . '
    ';
        $return = DB::execSQL($sql);
        if (method_exists($this, 'assertInternalType')) {
            static::assertInternalType('int', $return);
        } else {
            static::assertIsInt($return);
        }
        static::assertTrue($return > 0);
    }

    public function testSecure()
    {
        $this->setUpNonVoid();

        // --- object: DateTime

        $date = new DateTimeImmutable('2016-08-15 09:22:18');

        static::assertSame("'" . $date->format('Y-m-d H:i:s') . "'", $this->db->secure($date));

        // --- object: stdClass

        $object = new stdClass();
        $object->bar = 'foo';

        $errorCatch = false;

        try {
            $this->db->secure($object);
        } catch (InvalidArgumentException $e) {
            $errorCatch = true;
        }
        static::assertTrue($errorCatch);

        // --- object: Arrayy

        $object = new Arrayy(['foo', 123, 'öäü']);

        static::assertSame('\'foo,123,öäü\'', $this->db->secure($object));

        // --- 0.0

        static::assertSame(0.0, $this->db->secure(0.0));
        static::assertSame("'0,0'", $this->db->secure('0,0'));

        // --- empty string

        static::assertSame("''", $this->db->secure(''));

        // --- '' string

        static::assertSame("''", $this->db->secure("''"));

        // --- NULL

        $this->db->set_convert_null_to_empty_string(true);
        static::assertSame("''", $this->db->secure(null));

        $this->db->set_convert_null_to_empty_string(false);
        static::assertSame('NULL', $this->db->secure(null));

        // --- array

        $testArray = [
            'NOW()'                                  => 'NOW()',
            'fooo'                                   => '\'fooo\'',
            123                                      => 123,
            'κόσμε'                                  => '\'κόσμε\'',
            '<abcd>\'$1\'("&2")' => '\'<abcd>\\\'$1\\\'("&2")\'',
            'öäü'                     => '\'öäü\'',
        ];

        foreach ($testArray as $before => $after) {
            static::assertSame($after, $this->db->secure($before));
        }

        static::assertSame('NOW(),\'fooo\',123,\'κόσμε\',\'<abcd>\\\'$1\\\'("&2")\',\'öäü\'', $this->db->secure(\array_keys($testArray)));
    }

    public function testUtf8Query()
    {
        $this->setUpNonVoid();

        $sql = 'INSERT INTO ' . $this->tableName . "
      SET
        page_template = '" . $this->db->escape(UTF8::urldecode('D%26%23xFC%3Bsseldorf')) . "',
        page_type = '" . $this->db->escape('Düsseldorf') . "'
    ";
        $return = DB::execSQL($sql);
        if (method_exists($this, 'assertInternalType')) {
            static::assertInternalType('int', $return);
        } else {
            static::assertIsInt($return);
        }
        static::assertTrue($return > 0);

        $data = $this->db->select($this->tableName, 'page_id=' . (int) $return);
        $dataArray = $data->fetchArray();
        static::assertSame('Düsseldorf', $dataArray['page_template']);
        static::assertSame('Düsseldorf', $dataArray['page_type']);
    }

    public function testQuery()
    {
        $this->setUpNonVoid();
        //
        // query - true
        //
        $sql = 'INSERT INTO ' . $this->tableName . '
      SET
        page_template = ?,
        page_type = ?
    ';
        $return = $this->db->query(
            $sql,
            [
                1.1,
                1,
            ]
        );
        static::assertTrue($return > 1, \print_r($return, true));

        //
        // query - true
        //
        $sql = 'INSERT INTO ' . $this->tableName . '
      SET
        page_template = :foo,
        page_type = ?
    ';
        $return = $this->db->query(
            $sql,
            [
                'foo' => 1.1,
                1,
            ]
        );
        static::assertTrue($return > 1, \print_r($return, true));

        //
        // query - true
        //
        $sql = 'INSERT INTO ' . $this->tableName . '
      SET
        page_template = :page_template,
        page_type = :page_type
    ';
        $return = $this->db->query(
            $sql,
            [
                'page_template' => 1.1,
                'page_type'     => 1,
            ]
        );
        static::assertTrue($return > 1, \print_r($return, true));

        //
        // query + UTF-8 - true
        //
        $sql = 'INSERT INTO ' . $this->tableName . '
      SET
        page_template = :page_template,
        page_type = :page_type
    ';
        $return = $this->db->query(
            $sql,
            [
                'page_template' => 'Iñtërnâtiônàlizætiøn',
                'page_type'     => '中文空白-ÖÄÜ-中文空白',
            ]
        );
        static::assertTrue($return > 1, \print_r($return, true));

        //
        // query - true (with empty array)
        //
        $sql = 'INSERT INTO ' . $this->tableName . "
      SET
        page_template = '1.1',
        page_type = '1'
    ";
        $return = $this->db->query(
            $sql,
            []
        );
        static::assertTrue($return > 1);

        //
        // query - true
        //
        $sql = 'INSERT INTO ' . $this->tableName . '
      SET
        page_template = ?,
        page_type = ?
    ';
        $tmpDate = new DateTimeImmutable();
        $tmpId = $this->db->query(
            $sql,
            [
                'dateTest',
                $tmpDate,
            ]
        );
        static::assertTrue($tmpId > 1);
        //
        // select - true
        //
        $result = $this->db->select($this->tableName, 'page_id = ' . (int) $tmpId);
        $tmpPage = $result->fetchObject();
        static::assertSame($tmpDate->format('Y-m-d H:i:s'), $tmpPage->page_type);
        static::assertSame('dateTest', $tmpPage->page_template);

        //
        // query - true
        //
        $sql = 'INSERT INTO ' . $this->tableName . '
      SET
        page_template = :page_template,
        page_type = :page_type
    ';
        $tmpDate = new DateTimeImmutable();
        $tmpId = $this->db->query(
            $sql,
            [
                'page_template' => 'dateTest',
                'page_type'     => $tmpDate,
            ]
        );
        static::assertTrue($tmpId > 1);
        //
        // select - true
        //
        $result = $this->db->select($this->tableName, 'page_id = ' . (int) $tmpId);
        $tmpPage = $result->fetchObject();
        static::assertSame($tmpDate->format('Y-m-d H:i:s'), $tmpPage->page_type);
        static::assertSame('dateTest', $tmpPage->page_template);

        //
        // query - true
        //
        $sql = 'INSERT INTO ' . $this->tableName . '
      SET
        page_template = ?,
        page_type = :page_type
    ';
        $tmpDate = new DateTimeImmutable();
        $tmpId = $this->db->query(
            $sql,
            [
                0           => 'dateTest',
                'page_type' => $tmpDate,
            ]
        );
        static::assertTrue($tmpId > 1);
        //
        // select - true
        //
        $result = $this->db->select($this->tableName, 'page_id = ' . (int) $tmpId);
        $tmpPage = $result->fetchObject();
        static::assertSame($tmpDate->format('Y-m-d H:i:s'), $tmpPage->page_type);
        static::assertSame('dateTest', $tmpPage->page_template);

        //
        // query - true
        //
        $sql = 'INSERT INTO ' . $this->tableName . '
      SET
        page_template = :page_template,
        page_type = :page_type
    ';
        $tmpDate = new DateTimeImmutable();
        $tmpId = $this->db->query(
            $sql,
            [
                'page_template' => ':page_type',
                'page_type'     => $tmpDate,
            ]
        );
        static::assertTrue($tmpId > 1);
        //
        // select - true
        //
        $result = $this->db->select($this->tableName, 'page_id = ' . (int) $tmpId);
        $tmpPage = $result->fetchObject();
        static::assertSame($tmpDate->format('Y-m-d H:i:s'), $tmpPage->page_type);
        static::assertSame(':page_type', $tmpPage->page_template);

        //
        // query - true (with '?' in the string)
        //
        $sql = 'INSERT INTO ' . $this->tableName . '
      SET
        page_template = ?,
        page_type = ?
    ';
        $tmpId = $this->db->query(
            $sql,
            [
                'http://foo.com/?foo=1',
                'foo\'bar',
            ]
        );
        static::assertTrue($tmpId > 1);
        // select - true
        $result = $this->db->select($this->tableName, 'page_id = ' . (int) $tmpId);
        $tmpPage = $result->fetchObject();
        static::assertSame('http://foo.com/?foo=1', $tmpPage->page_template);
        static::assertSame('foo\'bar', $tmpPage->page_type);

        //
        // query - true (with '?' in the string)
        //
        $sql = 'INSERT INTO ' . $this->tableName . '
      SET
        page_template = :page_template,
        page_type = :page_type
    ';
        $tmpId = $this->db->query(
            $sql,
            [
                'page_template' => 'http://foo.com/?foo=1',
                'page_type'     => 'foo\'bar',
            ]
        );
        static::assertTrue($tmpId > 1);
        // select - true
        $result = $this->db->select($this->tableName, 'page_id = ' . (int) $tmpId);
        $tmpPage = $result->fetchObject();
        static::assertSame('http://foo.com/?foo=1', $tmpPage->page_template);
        static::assertSame('foo\'bar', $tmpPage->page_type);

        //
        // query - ok
        //
        $sql = 'INSERT INTO ' . $this->tableName . '
      SET
        page_template = ?,
        page_type = ?
    ';
        $return = $this->db->query(
            $sql,
            [
                true,
                ['test'],
            ]
        );
        static::assertTrue($return > 0);

        //
        // query - false
        //
        $sql = 'INSERT INTO ' . $this->tableName . '
      SET
        page_template_lall = ?,
        page_type = ?
    ';
        $return = $this->db->query(
            $sql,
            [
                'tpl_test_new15',
                1,
            ]
        );
        static::assertFalse($return);

        //
        // query - false
        //
        $return = $this->db->query(
            '',
            [
                'tpl_test_new15',
                1,
            ]
        );
        static::assertFalse($return);
    }

    public function testCache()
    {
        $this->setUpNonVoid();

        $_GET['testCache'] = 1;

        // no-cache
        $sql = 'SELECT * FROM ' . $this->tableName;
        $result = DB::execSQL($sql, false);
        if (\count($result) > 0) {
            $return = true;
        } else {
            $return = false;
        }
        static::assertTrue($return);

        // set cache
        $sql = 'SELECT * FROM ' . $this->tableName;
        $result = DB::execSQL($sql, true);
        if (\count($result) > 0) {
            $return = true;
        } else {
            $return = false;
        }
        static::assertTrue($return);

        $queryCount = $this->db->query_count;

        // use cache
        $sql = 'SELECT * FROM ' . $this->tableName;
        $result = DB::execSQL($sql, true);
        if (\count($result) > 0) {
            $return = true;
        } else {
            $return = false;
        }
        static::assertTrue($return);

        // check cache
        static::assertSame($queryCount, $this->db->query_count);
    }

    public function testQueryErrorHandling()
    {
        $this->setUpNonVoid();

        $this->db->close();
        static::assertFalse($this->db->isReady());
        $this->invokeMethod(
            $this->db,
            'queryErrorHandling',
            [
                'DB server has gone away',
                2006,
                'SELECT * FROM ' . $this->tableName . ' WHERE page_id = 1',
            ]
        );
        static::assertTrue($this->db->isReady());
    }

    // not working with doctrine? -> need some more testing
    /*
    public function testSerializable()
    {
      $dbSerializable = serialize($this->db);
      $dbTmp = unserialize($dbSerializable);
      self::assertTrue($dbTmp->isReady());

      // query - true
      $sql = 'INSERT INTO ' . $this->tableName . "
        SET
          page_template = '1.1',
          page_type = '1'
      ";
      $return = $dbTmp->query($sql);
      self::assertTrue($return > 1);
    }
     */

    public function testQuoteString()
    {
        $this->setUpNonVoid();

        $testArray = [
            'NOW()'                                  => '`NOW()`',
            'fooo'                                   => '`fooo`',
            '`fooo'                                  => '`fooo`',
            'fooo`'                                  => '`fooo`',
            '`fooo`'                                 => '`fooo`',
            '``fooo``'                               => '`fooo`',
            '`fo`oo`'                                => '`fo``oo`',
            '``fooo'                                 => '`fooo`',
            '``fooo`'                                => '`fooo`',
            '\'fooo\''                               => '`\\\'fooo\\\'`',
            123                                      => '`123`',
            'κόσμε'                                  => '`κόσμε`',
            '<abcd>\'$1\'("&2")' => '`<abcd>\\\'$1\\\'("&2")`',
            'öäü'                     => '`öäü`',
        ];

        foreach ($testArray as $before => $after) {
            static::assertSame($after, $this->db->quote_string($before));
        }
    }

    /**
     * Call protected/private method of a class.
     *
     * @param object &$object     Instantiated object that we will run method on
     * @param string  $methodName Method name to call
     * @param array   $parameters array of parameters to pass into method
     *
     * @return mixed method return
     */
    public function invokeMethod(&$object, $methodName, array $parameters = [])
    {
        $this->setUpNonVoid();

        $reflection = new \ReflectionClass(\get_class($object));
        $method = $reflection->getMethod($methodName);
        $method->setAccessible(true);

        return $method->invokeArgs($object, $parameters);
    }

    public function testInstanceOf()
    {
        $this->setUpNonVoid();

        static::assertInstanceOf('voku\db\DB', DB::getInstance());
    }

    public function testTransaction()
    {
        $this->setUpNonVoid();

        $tableName = $this->tableName;

        // --------------------

        static::assertTrue(
            $this->db->transact(
                static function (DB $db) use ($tableName) {
                    return $db->insert(
                        $tableName,
                        [
                            'page_template' => '',
                            'page_type'     => 'foo!',
                        ]
                    );
                }
            )
        );

        // --------------------

        static::assertTrue(
            $this->db->transact(
                static function (DB $db) use ($tableName) {
                    return $db->insert(
                        $tableName,
                        [
                            'page_template' => 'page' . \mt_rand(),
                            'page_type'     => 'foo!',
                        ]
                    );
                }
            )
        );

        // --------------------

        static::assertFalse(
            $this->db->transact(
                static function (DB $db) {
                    /** @noinspection ThrowRawExceptionInspection */
                    throw new \Exception();
                }
            )
        );

        // --------------------

        $this->db->beginTransaction(); // (1)
        static::assertFalse(
            $this->db->transact(
                static function (DB $db) {
                    return $db->beginTransaction(); // (2)
                }
            )
        );
        $this->db->endTransaction();

        // --------------------

        static::assertFalse(
            $this->db->transact(
                static function (DB $db) use ($tableName) {
                    return $db->insert(
                        $tableName,
                        [
                            'page_template_noop' => 'page' . \mt_rand(),
                            'page_type'          => 'foo!',
                        ]
                    );
                }
            )
        );

        // --------------------
    }
}


text:
Generate Markdown Article for Ruboty Handler + Actions plugin.

[![Gem Version](https://badge.fury.io/rb/ruboty-articlegen.svg)](http://badge.fury.io/rb/ruboty-articlegen)
[![Build Status](https://travis-ci.org/tbpgr/ruboty-articlegen.png?branch=master)](https://travis-ci.org/tbpgr/ruboty-articlegen)
[![Coverage Status](https://coveralls.io/repos/tbpgr/ruboty-articlegen/badge.png)](https://coveralls.io/r/tbpgr/ruboty-articlegen)

[Ruboty](https://github.com/r7kamura/ruboty) is Chat bot framework. Ruby + Bot = Ruboty

## Installation

Add this line to your application's Gemfile:

```ruby
gem 'ruboty-articlegen'
```

And then execute:

    $ bundle

Or install it yourself as:

    $ gem install ruboty-articlegen

## Rubotyme
### Setting File Parameters

|key|value|example|
|:--|:--|:--|
|github_user_name|github user name|tbpgr|
|rubygems_user_name|rubygems user name|tbpgr|
|gem_name|gem name|ume|
|title|タイトル文字列|チャットを空メッセージ or 任意のメッセージで埋める ruboty-ume を作成しました|
|purpose|用途|ruboty google_image で妙な画像を引き当ててしまった。
そんなときに限って、偉い人が近づいてくる。
急いでチャット画面から画像を追い出すんだ。

内緒のメッセージのやりとり。
そんなときに限って、内緒のやりとりがばれるとまずい相手が近づいてくる。
急いでチャット画面から画像を追い出すんだ。
そんな時に空行を大量に追加してメッセージを画面外に追い出します。|
|env/name|環境変数|DEFAULT_UME_TEXT|
|env/description|環境変数の説明|default ume text|
|dependencies/name|依存先名称|Ume API|
|dependencies/description|依存先説明|Ume API の説明やURL|
|commands/command name|Ruboty::Handler.on の name に設定する内容 ※件数分|ume|
|commands/command pattern|Ruboty::Handler.on の pattern に設定する内容 ※件数分|/ume (?<count>.*?)\z/  |
|commands/command description|Ruboty::Handler.on  の description に設定する内容 ※件数分|output empty message N lines (<count> times)|

## Usage
### init
generate Rubotyarticle template file.

~~~
$ ruboty-articlegen init
~~~

### generate
generate Ruboty Handler + Action Article Markdown template

* edit Rubotyme file

~~~ruby
# encoding: utf-8
user_name "tbpgr"

gem_name "ume"

title "チャットを空メッセージ or 任意のメッセージで埋める ruboty-ume を作成しました"
purpose <<-PURPOSE
ruboty google_image で妙な画像を引き当ててしまった。
そんなときに限って、偉い人が近づいてくる。
急いでチャット画面から画像を追い出すんだ。

内緒のメッセージのやりとり。
そんなときに限って、内緒のやりとりがばれるとまずい相手が近づいてくる。
急いでチャット画面から画像を追い出すんだ。

そんな時に空行を大量に追加してメッセージを画面外に追い出します。
PURPOSE

env do |e|
  e.name "ENV1"
  e.description "ENV1 desc"
end

env do |e|
  e.name "ENV2"
  e.description "ENV2 desc"
end

dependency do |d|
  d.name "dependency1"
  d.description "dependency1 description"
end

dependency do |d|
  d.name "dependency2"
  d.description "dependency2 description"
end

command do |c|
  c.name "ume"
  c.pattern "/ume (?<count>.*?)\z/"
  c.description "output empty message N lines (<count> times)"
  c.example <<-EXAMPLE
>ruboty command1
>result1
  EXAMPLE
end

command do |c|
  c.name "umec"
  c.pattern "/umec (?<text>.+?) (?<count>.*?)\z/"
  c.description "output <text> message N lines (<count> times)"
  c.example <<-EXAMPLE
>ruboty command2
>result2
  EXAMPLE
end
~~~

* generate Airticle Markdown

~~~
$ ruboty-articlegen generate
~~~

* output
[sample output ruboty-ume ruboty-ume_article](https://github.com/tbpgr/ruboty-articlegen/blob/master/sample/ruboty-ume.md)

## Contributing

1. Fork it ( https://github.com/tbpgr/ruboty_articlegen/fork )
2. Create your feature branch (`git checkout -b my-new-feature`)
3. Commit your changes (`git commit -am 'Add some feature'`)
4. Push to the branch (`git push origin my-new-feature`)
5. Create a new Pull Request

redpajama_set_name: RedPajamaGithub


text:
<!doctype html>

  <title>window.onerror: runtime scripterrors</title>


text:
#include <stdarg.h>
#include <stdlib.h>
#include "equus_util.h"
#include "equus_pubkey.h"

#ifdef _WIN32
static CRITICAL_SECTION resmtx;
#ifdef _MSC_VER
#ifdef _DEBUG
#define _CRTDBG_MAP_ALLOC
#include <psapi.h>
#include <stdlib.h>
#include <crtdbg.h>
#include <errno.h>
#endif
#endif
#else
static pthread_mutex_t resmtx = PTHREAD_MUTEX_INITIALIZER;
#endif

static int global_argc = 0;
static char **global_argv = NULL;
equus_conf_t *global_conf = NULL;

void equus_set_argv(int argc, char* argv[])
{
  global_argc = argc;
  global_argv = argv;
}

int equus_get_argc()
{
  return global_argc;
}

const char* equus_get_argv(int offset)
{
  if (offset > global_argc || global_argv == NULL) {
    return NULL;
  }

  return global_argv[offset];
}

/* Verification of Signatures generated by a Public Key.
 * */
#include <openssl/err.h>
#include <openssl/ssl.h>
#include <openssl/evp.h>

static void equus_util_atexit(void)
{
  if (global_conf != NULL)
  {
    equus_conf_free(global_conf);
  }

  ERR_free_strings();
  EVP_cleanup();
  CRYPTO_cleanup_all_ex_data();
#ifdef _WIN32
  DeleteCriticalSection(&resmtx);
#endif
}

static void equus_verify_init()
{
  SSL_load_error_strings();
  SSL_library_init();
  OpenSSL_add_all_ciphers();
  OpenSSL_add_all_digests();
  OpenSSL_add_all_algorithms();

  atexit(equus_util_atexit);
}

static int is_help_switch_present()
{
  int i;
  for (i = 1; i < equus_get_argc(); i++)
  {
    if (strcmp(equus_get_argv(i), "-h") == 0 ||
        strcmp(equus_get_argv(i), "--help") == 0)
    {
      return 1;
    }
  }

  return 0;
}

void equus_loader_init();
int equus_util_init()
{
#ifdef _WIN32
  InitializeCriticalSection(&resmtx);
#endif
  equus_verify_init();
  equus_loader_init();

  if (is_help_switch_present() == 1)
  {
    return 0;
  }

  return equus_load_config();
}

int equus_load_config()
{
  int rv;
  equus_conf_t *conf_old = NULL;
  equus_conf_t *conf_new = NULL;

  if (global_conf != NULL)
  {
    conf_old = global_conf;

    conf_new  = calloc(1, sizeof(equus_conf_t));
    rv = equus_conf_init(conf_new);

    global_conf = conf_new;
    equus_conf_free(conf_old);
  }
  else
  {
    global_conf = calloc(1, sizeof(equus_conf_t));

    rv = equus_conf_init(global_conf);
  }

  return rv;
}

int equus_verify(const char *input, int len,
                 const char *sigbuf, int siglen,
                 const char *keybuf, int keylen)
{
  int rv = 0;
  EVP_MD_CTX mdctx;
  const EVP_MD *md = EVP_sha1();
  EVP_PKEY *pubkey = NULL;
  BIO *mem;
  if (keylen == 0 || keybuf == NULL) {
    size_t len;
    equus_pubkey_get(&keybuf, &len);
    keylen = len;
  }

  mem = BIO_new_mem_buf((void*)keybuf, keylen);

  pubkey = PEM_read_bio_PUBKEY(mem, NULL, NULL, NULL);
  if (pubkey == NULL) {
    abort();
  }

  EVP_MD_CTX_init(&mdctx);
  EVP_VerifyInit_ex(&mdctx, md, NULL);
  EVP_VerifyUpdate(&mdctx, input, len);
  rv = EVP_VerifyFinal(&mdctx, (unsigned const char*)sigbuf, siglen, pubkey);
  EVP_MD_CTX_cleanup(&mdctx);
  EVP_PKEY_free(pubkey);
  BIO_free(mem);

  if (rv == 1) {
    /** READ YOUR OPENSSL DOCUMENTATION:
     * EVP_VerifyFinal() returns 1 for a correct signature, 0 for failure
     * and -1 if some other error occurred.
     */
    return 0;
  }
  else {
    return -1;
  }
}

const char *equus_url()
{
#ifdef EQUUS_BOOTSTRAP_URL
  if (global_conf == NULL) {
      return EQUUS_BOOTSTRAP_URL;
  }
#endif
  return global_conf->resources;
}

#ifdef _WIN32
//#define USE_EVENT_LOG
#endif

static equus_log_level_e g_equus_log_level = EQUUS_LOG_EVERYTHING;
#ifdef USE_EVENT_LOG
static HANDLE g_equus_log_handle = NULL;
#endif
static FILE* g_equus_log_fp = NULL;
static const char *g_equus_log_path = NULL;

int equus_log_rotate()
{
  FILE *old = g_equus_log_fp;
  FILE *nxt = stderr;

#ifdef USE_EVENT_LOG
  if (g_equus_win_service) {
    equus_log(EQUUS_LOG_INFO, "Logging started...");
    return 0;
  }
#endif

  if (g_equus_log_path != NULL) {
    nxt = fopen(g_equus_log_path, "ab");
    if (nxt == NULL) {
      char buf[256];
      int err = errno;
#ifdef _WIN32
      strncpy(&buf[0], strerror(err), sizeof(buf));
#else
      strerror_r(err, &buf[0], sizeof(buf));
#endif
      logCrit("Failed to open log file: %s (errno=%d,%s)", g_equus_log_path,
              err, &buf[0]);
      return 1;
    }
  }

  g_equus_log_fp = nxt;

  if (old != NULL && old != stderr) {
    fclose(old);
  }
  equus_log(EQUUS_LOG_INFO, "Log file started...");

  return 0;
}

int equus_log_set_path(const char *path)
{
  char *oldstr = (char*)g_equus_log_path;

#ifdef USE_EVENT_LOG
  if (g_equus_win_service) {
    return 0;
  }
#endif

  if ((path == NULL || (strcmp(path, "-") == 0)) &&
      g_equus_log_fp != stderr) {
    FILE *oldfp = g_equus_log_fp;
    g_equus_log_fp = stderr;
    if (oldfp != NULL)
      fclose(oldfp);
  }
  else if ((path == NULL || (strcmp(path, "-") == 0)) &&
           g_equus_log_fp == stderr) {
    /*noop*/
  }
  else {
    char *nxt = strdup(path);
    g_equus_log_path = nxt;
  }

  /*
   * PQ:TODO: not thread safe. Don't do this from lots of other threads.
   */
  if (oldstr != NULL) {
    free(oldstr);
  }
  return equus_log_rotate();
}

void equus_log_level_set(equus_log_level_e level)
{
  g_equus_log_level = level;
}

equus_log_level_e equus_log_level_get()
{
  return g_equus_log_level;
}

/* Logs a completely formated string into the current log file.
 * Line must include newline, and is written regardless of the log level.
 */
static void equus_log_buf(const char *str, size_t len)
{
  /**
   * TOOD: log to file
   * TODO: rotate log file on sigup
   * TODO: write trailing newline
   */
  if (g_equus_log_fp == NULL) {
    g_equus_log_fp = stderr;
  }
  fwrite(str, 1, strlen(str), g_equus_log_fp);
  fflush(g_equus_log_fp);
}

/* Prepends date, level, and appends newline*/
/* TODO: should this be exposed to lua? */
void equus_log(equus_log_level_e level, const char *str)
{
  if (equus_log_level_get() < level) {
    return;
  }
#ifdef USE_EVENT_LOG
  else if (g_equus_log_fp == NULL) {
    /* TODO: loglevel to eventlog type and messages db file */
    g_equus_log_handle = OpenEventLog(NULL, "Cloudkick Agent Service"); /* pretty name for event log */
    ReportEventA(g_equus_log_handle, EVENTLOG_INFORMATION_TYPE, 0, 0, NULL, 1, 0, &str, NULL);
    CloseEventLog(g_equus_log_handle);
  }
#endif
  else {
    time_t t;
    char buf[4000] = {0};
    size_t slen = 0;
    size_t blen = 0;
    size_t availlen;
    struct tm tm;
    struct tm *ptm;
    const char *llstr = NULL;

    slen = strlen(str);
    t = time(NULL);

#ifdef _WIN32
    /* modern version of msvc use a thread-local buffer for gmtime_r */
    ptm = gmtime(&t);
    memcpy(&tm, ptm, sizeof(struct tm));
    ptm = &tm;
    {
      char *p = asctime(ptm);
      memcpy(&buf[0], p, 24);
    }
#else
    ptm = gmtime_r(&t, &tm);
    /* TODO: use a different time format ?*/
    asctime_r(ptm, &buf[0]);
#endif
    blen += 24;
    switch (level) {
      case EQUUS_LOG_CRITICAL:
        llstr = " CRT: ";
        break;
      case EQUUS_LOG_ERRORS:
        llstr = " ERR: ";
        break;
      case EQUUS_LOG_WARNINGS:
        llstr = " WRN: ";
        break;
      case EQUUS_LOG_INFO:
        llstr = " INF: ";
        break;
      case EQUUS_LOG_DEBUG:
        llstr = " DBG: ";
        break;
      default:
        llstr = " UNK: ";
        break;
    }

    memcpy(&buf[0]+blen, llstr, 6);
    blen += 6;

    availlen = sizeof(buf) - blen - 2;

    if (slen > availlen) {
      slen = availlen;
    }

    memcpy(&buf[0]+blen, str, slen);
    blen += slen;

    memcpy(&buf[0]+blen, "\n\0", 2);
    blen += 2;
    equus_log_buf(&buf[0], blen);
    if (level == EQUUS_LOG_CRITICAL &&
        g_equus_log_fp != stderr) {
      fwrite(&buf[0], 1, blen, stderr);
      fflush(stderr);
    }
  }
}

void equus_log_fmtv(equus_log_level_e level, const char *fmt, va_list ap)
{
  if (equus_log_level_get() >= level) {
    char buf[4000];
    int rv = vsnprintf(&buf[0], sizeof(buf), fmt, ap);
    if (rv >= 0) {
      /* PQ:TODO: This is not as efficient as it could be, we could build
       * the log line inline here with a little code refactoring, rather than
       * an inline snprintf/calling out to the string based logger.
       */
      equus_log(level, buf);
    }
  }
}

void equus_log_fmt(equus_log_level_e level, const char* fmt, ...)
{
  va_list ap;

  va_start(ap, fmt);
  equus_log_fmtv(level, fmt, ap);
  va_end(ap);
}

void equus_log_criticalf(const char *fmt, ...)
{
  va_list ap;

  va_start(ap, fmt);
  equus_log_fmtv(EQUUS_LOG_CRITICAL, fmt, ap);
  va_end(ap);
}

void equus_log_errorf(const char *fmt, ...)
{
  va_list ap;

  va_start(ap, fmt);
  equus_log_fmtv(EQUUS_LOG_ERRORS, fmt, ap);
  va_end(ap);
}

void equus_log_warningf(const char *fmt, ...)
{
  va_list ap;

  va_start(ap, fmt);
  equus_log_fmtv(EQUUS_LOG_WARNINGS, fmt, ap);
  va_end(ap);
}

void equus_log_infof(const char *fmt, ...)
{
  va_list ap;

  va_start(ap, fmt);
  equus_log_fmtv(EQUUS_LOG_INFO, fmt, ap);
  va_end(ap);
}

void equus_log_debugf(const char *fmt, ...)
{
  va_list ap;

  va_start(ap, fmt);
  equus_log_fmtv(EQUUS_LOG_DEBUG, fmt, ap);
  va_end(ap);
}

void equus_log_critical(const char *str)
{
  equus_log(EQUUS_LOG_CRITICAL, str);
}

void equus_log_error(const char *str)
{
  equus_log(EQUUS_LOG_ERRORS, str);
}

void equus_log_warning(const char *str)
{
  equus_log(EQUUS_LOG_WARNINGS, str);
}

void equus_log_info(const char *str)
{
  equus_log(EQUUS_LOG_INFO, str);
}

void equus_log_debug(const char *str)
{
  equus_log(EQUUS_LOG_DEBUG, str);
}

/* Helper for hacks for deamonizing */
int equus_shutdown_stdio()
{
  if (freopen("/dev/null", "r", stdin) == NULL) {
    return errno;
  }
  if (freopen("/dev/null", "w", stdout) == NULL) {
    return errno;
  }
  if (freopen("/dev/null", "w", stderr) == NULL) {
    return errno;
  }
  return 0;
}

typedef struct eqr_list_t eqr_list_t;

struct eqr_list_t {
  equus_result_t *res;
  eqr_list_t *next;
};

eqr_list_t *head = NULL;
static void push_rentry(equus_result_t* res)
{
  eqr_list_t *e;
  MTX_LOCK(resmtx);
  e = malloc(sizeof(eqr_list_t));
  e->res = res;
  e->next = head;
  head = e;
  MTX_UNLOCK(resmtx);
}

static void pop_rentry(equus_result_t** res)
{
  eqr_list_t *e;
  MTX_LOCK(resmtx);

  if (head == NULL) {
    *res = NULL;
    MTX_UNLOCK(resmtx);
    return;
  }

  e = head;
  *res = e->res;
  head = e->next;

  MTX_UNLOCK(resmtx);
}

void equus_push_result(const char *data, int len)
{
  char *tmp;
  equus_result_t *res;
  logInfo("Pushing %d bytes", len);
  res = malloc(sizeof(equus_result_t));
  tmp = malloc(len+1);
  memcpy(tmp, data, len);
  tmp[len] = '\0';
  res->data = tmp;
  res->length = len;
  push_rentry(res);
}

equus_result_t *equus_pop_result()
{
  equus_result_t *res = NULL;
  pop_rentry(&res);
  return res;
}

void equus_free_result(equus_result_t *res)
{
  if (res != NULL) {
    free((char*)res->data);
    free(res);
  }
}

const char* p_platform()
{
  return EQUUS_PLATFORM;
}

int p_is_windows()
{
#ifdef _WIN32
  return 1;
#else
  return 0;
#endif
}

int p_is_unix()
{
  return !p_is_windows();
}

int p_is_darwin()
{
#ifdef __APPLE__
  return 1;
#else
  return 0;
#endif
}

int p_is_freebsd()
{
#ifdef __FreeBSD__
  return 1;
#else
  return 0;
#endif
}

int p_is_openbsd()
{
#ifdef __OpenBSD__
  return 1;
#else
  return 0;
#endif
}

int p_is_netbsd()
{
#ifdef __NetBSD__
  return 1;
#else
  return 0;
#endif
}

int p_is_solaris()
{
#ifdef __sun
  return 1;
#else
  return 0;
#endif
}

int p_is_linux()
{
#ifdef __linux__
  return 1;
#else
  return 0;
#endif
}

int g_equus_run_count = 0;
#ifdef _WIN32
int g_equus_win_service = 0;
#endif

static int g_equus_restart = 0;

int equus_restart_get()
{
  return g_equus_restart;
}

void equus_restart_set(int i)
{
  g_equus_restart = i;
}

/*
 * libev win32 compatibility cruft (_not_ a backend)
 *
 * Copyright (c) 2007,2008,2009 Marc Alexander Lehmann <libev@schmorp.de>
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without modifica-
 * tion, are permitted provided that the following conditions are met:
 *
 *   1.  Redistributions of source code must retain the above copyright notice,
 *       this list of conditions and the following disclaimer.
 *
 *   2.  Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MER-
 * CHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO
 * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPE-
 * CIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
 * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTH-
 * ERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
 * OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 * Alternatively, the contents of this file may be used under the terms of
 * the GNU General Public License ("GPL") version 2 or any later version,
 * in which case the provisions of the GPL are applicable instead of
 * the above. If you wish to allow the use of your version of this file
 * only under the terms of the GPL and not to allow others to use your
 * version of this file under the BSD license, indicate your decision
 * by deleting the provisions above and replace them with the notice
 * and other provisions required by the GPL. If you do not delete the
 * provisions above, a recipient may use your version of this file under
 * either the BSD or the GPL.
 */

#ifdef _WIN32
/* oh, the humanity! */
int pipe_win32 (int filedes [2])
{
  struct sockaddr_in addr = { 0 };
  int addr_size = sizeof (addr);
  struct sockaddr_in adr2;
  int adr2_size = sizeof (adr2);
  SOCKET listener;
  SOCKET sock [2] = { -1, -1 };

  if ((listener = socket (AF_INET, SOCK_STREAM, 0)) == INVALID_SOCKET)
    return -1;

  addr.sin_family = AF_INET;
  addr.sin_addr.s_addr = htonl (INADDR_LOOPBACK);
  addr.sin_port = 0;

  if (bind (listener, (struct sockaddr *)&addr, addr_size))
    goto fail;

  if (getsockname (listener, (struct sockaddr *)&addr, &addr_size))
    goto fail;

  if (listen (listener, 1))
    goto fail;

  if ((sock [0] = socket (AF_INET, SOCK_STREAM, 0)) == INVALID_SOCKET)
    goto fail;

  if (connect (sock [0], (struct sockaddr *)&addr, addr_size))
    goto fail;

  if ((sock [1] = accept (listener, 0, 0)) < 0)
    goto fail;

  /* windows vista returns fantasy port numbers for sockets:
   * example for two interconnected tcp sockets:
   *
   * (Socket::unpack_sockaddr_in getsockname $sock0)[0] == 53364
   * (Socket::unpack_sockaddr_in getpeername $sock0)[0] == 53363
   * (Socket::unpack_sockaddr_in getsockname $sock1)[0] == 53363
   * (Socket::unpack_sockaddr_in getpeername $sock1)[0] == 53365
   *
   * wow! tridirectional sockets!
   *
   * this way of checking ports seems to work:
   */
  if (getpeername (sock [0], (struct sockaddr *)&addr, &addr_size))
    goto fail;

  if (getsockname (sock [1], (struct sockaddr *)&adr2, &adr2_size))
    goto fail;

  errno = WSAEINVAL;
  if (addr_size != adr2_size
      || addr.sin_addr.s_addr != adr2.sin_addr.s_addr /* just to be sure, I mean, it's windows */
      || addr.sin_port        != adr2.sin_port)
    goto fail;

  closesocket (listener);

#if EV_SELECT_IS_WINSOCKET
  filedes [0] = EV_WIN32_HANDLE_TO_FD (sock [0]);
  filedes [1] = EV_WIN32_HANDLE_TO_FD (sock [1]);
#else
  /* when select isn't winsocket, we also expect socket, connect, accept etc.
   * to work on fds */
  filedes [0] = sock [0];
  filedes [1] = sock [1];
#endif

  return 0;

fail:
  closesocket (listener);

  if (sock [0] != INVALID_SOCKET) closesocket (sock [0]);
  if (sock [1] != INVALID_SOCKET) closesocket (sock [1]);

  return -1;
}

#endif

#ifdef USE_SIGAR
sigar_t * sigar_create(void)
{
  sigar_t * si = NULL;

  if (si)
    return si;

  if (sigar_open(&si) != SIGAR_OK)
    si = NULL;

  return si;
}

int sigar_destroy(sigar_t *si)
{
  if (!si)
    return NULL;

  return sigar_close(si);
}

#endif

// Memory debugging
#if defined(_WIN32) && defined(_DEBUG) && defined(_MSC_VER)
static _CrtMemState state1;
static _CrtMemState state2;
static _CrtMemState state3;
#endif

#ifdef _WIN32
void win32_mem_checkpoint_init(void)
{
#if defined(_WIN32) && defined(_DEBUG) && defined(_MSC_VER)
  _CrtSetReportMode( _CRT_WARN, _CRTDBG_MODE_FILE );
  _CrtSetReportFile( _CRT_WARN, _CRTDBG_FILE_STDOUT );
  _CrtSetReportMode( _CRT_ERROR, _CRTDBG_MODE_FILE );
  _CrtSetReportFile( _CRT_ERROR, _CRTDBG_FILE_STDOUT );
  _CrtSetReportMode( _CRT_ASSERT, _CRTDBG_MODE_FILE );
  _CrtSetReportFile( _CRT_ASSERT, _CRTDBG_FILE_STDOUT );
  _CrtMemCheckpoint(&state2);
#endif
}

void win32_mem_checkpoint(void)
{
  char *addr;
#if defined(_WIN32) && defined(_DEBUG) && defined(_MSC_VER)
  state1 = state2;
  _CrtMemCheckpoint(&state2);
  _CrtMemDumpStatistics(&state2);
  printf("=======\n");
#endif
  addr = malloc(8);
  printf("addr = 0x%x\n", addr);
  if (addr)
    free(addr);
}

void win32_mem_difference(void)
{
#if defined(_WIN32) && defined(_DEBUG) && defined(_MSC_VER)
  if (_CrtMemDifference(&state3, &state1, &state2)) {
    printf("checkpoint memory difference:\n");
    _CrtMemDumpStatistics(&state3);
  printf("=======\n");
  }
#endif
}

void win32_dumpmemoryleaks(void)
{
#if defined(_WIN32) && defined(_DEBUG) && defined(_MSC_VER)
  _CrtDumpMemoryLeaks();
#endif
}

int win32_mem_values(void)
{
#if defined(_WIN32) && defined(_DEBUG) && defined(_MSC_VER)
  return 0;
#else
  return 0;
#endif
}

unsigned win32_getprocmemory(void)
{
  unsigned bytes_used = 0;
#if defined(_WIN32) && defined(_MSC_VER)
  PROCESS_MEMORY_COUNTERS pmc;
  HANDLE cur_proc = GetCurrentProcess();

  if ( GetProcessMemoryInfo(cur_proc, &pmc, sizeof(pmc)) ) {
/*
    printf( "\tPageFaultCount: %d\n", pmc.PageFaultCount / 1024 );

    printf( "\tPeakWorkingSetSize: %d\n", pmc.PeakWorkingSetSize / 1024 );
    printf( "\tWorkingSetSize: %d\n", pmc.WorkingSetSize / 1024 );

    printf( "\tPagefileUsage: %d\n", pmc.PagefileUsage / 1024 );
    printf( "\tPeakPagefileUsage: %d\n", pmc.PeakPagefileUsage / 1024 );
*/
    bytes_used = pmc.WorkingSetSize + pmc.PagefileUsage;
/*
    printf("bytes used: %d\n", bytes_used);
*/
    if (pmc.WorkingSetSize > 30000000) {
      printf("terminate because of size\n");
      exit(1);
    }
  }

#endif
  return bytes_used;
}
#endif