<?php

declare(strict_types=1);

namespace PhpMyAdmin\Controllers\Database;

use PhpMyAdmin\Charsets;
use PhpMyAdmin\CheckUserPrivileges;
use PhpMyAdmin\Config\PageSettings;
use PhpMyAdmin\ConfigStorage\Relation;
use PhpMyAdmin\ConfigStorage\RelationCleanup;
use PhpMyAdmin\DatabaseInterface;
use PhpMyAdmin\FlashMessages;
use PhpMyAdmin\Html\Generator;
use PhpMyAdmin\Operations;
use PhpMyAdmin\RecentFavoriteTable;
use PhpMyAdmin\Replication;
use PhpMyAdmin\ReplicationInfo;
use PhpMyAdmin\ResponseRenderer;
use PhpMyAdmin\Sanitize;
use PhpMyAdmin\StorageEngine;
use PhpMyAdmin\Template;
use PhpMyAdmin\Tracker;
use PhpMyAdmin\Url;
use PhpMyAdmin\Util;

use function __;
use function array_search;
use function ceil;
use function count;
use function htmlspecialchars;
use function implode;
use function in_array;
use function is_string;
use function max;
use function mb_substr;
use function md5;
use function preg_match;
use function preg_quote;
use function sprintf;
use function str_replace;
use function strlen;
use function strtotime;
use function urlencode;

/**
 * Handles database structure logic
 */
class StructureController extends AbstractController
{
    /** @var int Number of tables */
    protected $numTables;

    /** @var int Current position in the list */
    protected $position;

    /** @var bool DB is information_schema */
    protected $dbIsSystemSchema;

    /** @var int Number of tables */
    protected $totalNumTables;

    /** @var array Tables in the database */
    protected $tables;

    /** @var bool whether stats show or not */
    protected $isShowStats;

    /** @var Relation */
    private $relation;

    /** @var Replication */
    private $replication;

    /** @var RelationCleanup */
    private $relationCleanup;

    /** @var Operations */
    private $operations;

    /** @var ReplicationInfo */
    private $replicationInfo;

    /** @var DatabaseInterface */
    private $dbi;

    /** @var FlashMessages */
    private $flash;

    public function __construct(
        ResponseRenderer $response,
        Template $template,
        string $db,
        Relation $relation,
        Replication $replication,
        RelationCleanup $relationCleanup,
        Operations $operations,
        DatabaseInterface $dbi,
        FlashMessages $flash
    ) {
        parent::__construct($response, $template, $db);
        $this->relation = $relation;
        $this->replication = $replication;
        $this->relationCleanup = $relationCleanup;
        $this->operations = $operations;
        $this->dbi = $dbi;
        $this->flash = $flash;

        $this->replicationInfo = new ReplicationInfo($this->dbi);
    }

    /**
     * Retrieves database information for further use
     *
     * @param string $subPart Page part name
     */
    private function getDatabaseInfo(string $subPart): void
    {
        [
            $tables,
            $numTables,
            $totalNumTables,,
            $isShowStats,
            $dbIsSystemSchema,,,
            $position,
        ] = Util::getDbInfo($this->db, $subPart);

        $this->tables = $tables;
        $this->numTables = $numTables;
        $this->position = $position;
        $this->dbIsSystemSchema = $dbIsSystemSchema;
        $this->totalNumTables = $totalNumTables;
        $this->isShowStats = $isShowStats;
    }

    public function __invoke(): void
    {
        global $cfg, $db, $errorUrl;

        $parameters = [
            'sort' => $_REQUEST['sort'] ?? null,
            'sort_order' => $_REQUEST['sort_order'] ?? null,
        ];

        Util::checkParameters(['db']);

        $errorUrl = Util::getScriptNameForOption($cfg['DefaultTabDatabase'], 'database');
        $errorUrl .= Url::getCommon(['db' => $db], '&');

        if (! $this->hasDatabase()) {
            return;
        }

        $this->addScriptFiles(['database/structure.js', 'table/change.js']);

        // Gets the database structure
        $this->getDatabaseInfo('_structure');

        // Checks if there are any tables to be shown on current page.
        // If there are no tables, the user is redirected to the last page
        // having any.
        if ($this->totalNumTables > 0 && $this->position > $this->totalNumTables) {
            $this->redirect('/database/structure', [
                'db' => $this->db,
                'pos' => max(0, $this->totalNumTables - $cfg['MaxTableList']),
                'reload' => 1,
            ]);
        }

        $this->replicationInfo->load($_POST['primary_connection'] ?? null);
        $replicaInfo = $this->replicationInfo->getReplicaInfo();

        $pageSettings = new PageSettings('DbStructure');
        $this->response->addHTML($pageSettings->getErrorHTML());
        $this->response->addHTML($pageSettings->getHTML());

        if ($this->numTables > 0) {
            $urlParams = [
                'pos' => $this->position,
                'db' => $this->db,
            ];
            if (isset($parameters['sort'])) {
                $urlParams['sort'] = $parameters['sort'];
            }

            if (isset($parameters['sort_order'])) {
                $urlParams['sort_order'] = $parameters['sort_order'];
            }

            $listNavigator = Generator::getListNavigator(
                $this->totalNumTables,
                $this->position,
                $urlParams,
                Url::getFromRoute('/database/structure'),
                'frame_content',
                $cfg['MaxTableList']
            );

            $tableList = $this->displayTableList($replicaInfo);
        }

        $createTable = '';
        if (empty($this->dbIsSystemSchema)) {
            $checkUserPrivileges = new CheckUserPrivileges($this->dbi);
            $checkUserPrivileges->getPrivileges();

            $createTable = $this->template->render('database/create_table', ['db' => $this->db]);
        }

        $this->render('database/structure/index', [
            'database' => $this->db,
            'has_tables' => $this->numTables > 0,
            'list_navigator_html' => $listNavigator ?? '',
            'table_list_html' => $tableList ?? '',
            'is_system_schema' => ! empty($this->dbIsSystemSchema),
            'create_table_html' => $createTable,
        ]);
    }

    /**
     * @param array $replicaInfo
     */
    protected function displayTableList($replicaInfo): string
    {
        $html = '';

        // filtering
        $html .= $this->template->render('filter', ['filter_value' => '']);

        $i = $sumEntries = 0;
        $overheadCheck = false;
        $createTimeAll = '';
        $updateTimeAll = '';
        $checkTimeAll = '';
        $numColumns = $GLOBALS['cfg']['PropertiesNumColumns'] > 1
            ? ceil($this->numTables / $GLOBALS['cfg']['PropertiesNumColumns']) + 1
            : 0;
        $rowCount = 0;
        $sumSize = 0;
        $overheadSize = 0;

        $hiddenFields = [];
        $overallApproxRows = false;
        $structureTableRows = [];
        $trackedTables = Tracker::getTrackedTables($GLOBALS['db']);
        foreach ($this->tables as $currentTable) {
            // Get valid statistics whatever is the table type

            $dropQuery = '';
            $dropMessage = '';
            $overhead = '';
            $inputClass = ['checkall'];

            // Sets parameters for links
            $tableUrlParams = [
                'db' => $this->db,
                'table' => $currentTable['TABLE_NAME'],
            ];
            // do not list the previous table's size info for a view

            [
                $currentTable,
                $formattedSize,
                $unit,
                $formattedOverhead,
                $overheadUnit,
                $overheadSize,
                $tableIsView,
                $sumSize,
            ] = $this->getStuffForEngineTypeTable($currentTable, $sumSize, $overheadSize);

            $curTable = $this->dbi
                ->getTable($this->db, $currentTable['TABLE_NAME']);
            if (! $curTable->isMerge()) {
                $sumEntries += $currentTable['TABLE_ROWS'];
            }

            $collationDefinition = '---';
            if (isset($currentTable['Collation'])) {
                $tableCollation = Charsets::findCollationByName(
                    $this->dbi,
                    $GLOBALS['cfg']['Server']['DisableIS'],
                    $currentTable['Collation']
                );
                if ($tableCollation !== null) {
                    $collationDefinition = $this->template->render('database/structure/collation_definition', [
                        'valueTitle' => $tableCollation->getDescription(),
                        'value' => $tableCollation->getName(),
                    ]);
                }
            }

            if ($this->isShowStats) {
                $overhead = '-';
                if ($formattedOverhead != '') {
                    $overhead = $this->template->render('database/structure/overhead', [
                        'table_url_params' => $tableUrlParams,
                        'formatted_overhead' => $formattedOverhead,
                        'overhead_unit' => $overheadUnit,
                    ]);
                    $overheadCheck = true;
                    $inputClass[] = 'tbl-overhead';
                }
            }

            if ($GLOBALS['cfg']['ShowDbStructureCharset']) {
                $charset = '';
                if (isset($tableCollation)) {
                    $charset = $tableCollation->getCharset();
                }
            }

            if ($GLOBALS['cfg']['ShowDbStructureCreation']) {
                $createTime = $currentTable['Create_time'] ?? '';
                if ($createTime && (! $createTimeAll || $createTime < $createTimeAll)) {
                    $createTimeAll = $createTime;
                }
            }

            if ($GLOBALS['cfg']['ShowDbStructureLastUpdate']) {
                $updateTime = $currentTable['Update_time'] ?? '';
                if ($updateTime && (! $updateTimeAll || $updateTime < $updateTimeAll)) {
                    $updateTimeAll = $updateTime;
                }
            }

            if ($GLOBALS['cfg']['ShowDbStructureLastCheck']) {
                $checkTime = $currentTable['Check_time'] ?? '';
                if ($checkTime && (! $checkTimeAll || $checkTime < $checkTimeAll)) {
                    $checkTimeAll = $checkTime;
                }
            }

            $truename = $currentTable['TABLE_NAME'];

            $i++;

            $rowCount++;
            if ($tableIsView) {
                $hiddenFields[] = '<input type="hidden" name="views[]" value="'
                    . htmlspecialchars($currentTable['TABLE_NAME']) . '">';
            }

            /*
             * Always activate links for Browse, Search and Empty, even if
             * the icons are greyed, because
             * 1. for views, we don't know the number of rows at this point
             * 2. for tables, another source could have populated them since the
             *    page was generated
             *
             * I could have used the PHP ternary conditional operator but I find
             * the code easier to read without this operator.
             */
            $mayHaveRows = $currentTable['TABLE_ROWS'] > 0 || $tableIsView;

            if (! $this->dbIsSystemSchema) {
                $dropQuery = sprintf(
                    'DROP %s %s',
                    $tableIsView ? 'VIEW' : 'TABLE',
                    Util::backquote(
                        $currentTable['TABLE_NAME']
                    )
                );
                $dropMessage = sprintf(
                    ($tableIsView ? __('View %s has been dropped.') : __('Table %s has been dropped.')),
                    str_replace(
                        ' ',
                        '&nbsp;',
                        htmlspecialchars($currentTable['TABLE_NAME'])
                    )
                );
            }

            if ($numColumns > 0 && $this->numTables > $numColumns && ($rowCount % $numColumns) == 0) {
                $rowCount = 1;

                $html .= $this->template->render('database/structure/table_header', [
                    'db' => $this->db,
                    'db_is_system_schema' => $this->dbIsSystemSchema,
                    'replication' => $replicaInfo['status'],
                    'properties_num_columns' => $GLOBALS['cfg']['PropertiesNumColumns'],
                    'is_show_stats' => $this->isShowStats,
                    'show_charset' => $GLOBALS['cfg']['ShowDbStructureCharset'],
                    'show_comment' => $GLOBALS['cfg']['ShowDbStructureComment'],
                    'show_creation' => $GLOBALS['cfg']['ShowDbStructureCreation'],
                    'show_last_update' => $GLOBALS['cfg']['ShowDbStructureLastUpdate'],
                    'show_last_check' => $GLOBALS['cfg']['ShowDbStructureLastCheck'],
                    'num_favorite_tables' => $GLOBALS['cfg']['NumFavoriteTables'],
                    'structure_table_rows' => $structureTableRows,
                ]);
                $structureTableRows = [];
            }

            [$approxRows, $showSuperscript] = $this->isRowCountApproximated($currentTable, $tableIsView);

            [$do, $ignored] = $this->getReplicationStatus($replicaInfo, $truename);

            $structureTableRows[] = [
                'table_name_hash' => md5($currentTable['TABLE_NAME']),
                'db_table_name_hash' => md5($this->db . '.' . $currentTable['TABLE_NAME']),
                'db' => $this->db,
                'curr' => $i,
                'input_class' => implode(' ', $inputClass),
                'table_is_view' => $tableIsView,
                'current_table' => $currentTable,
                'may_have_rows' => $mayHaveRows,
                'browse_table_label_title' => htmlspecialchars($currentTable['TABLE_COMMENT']),
                'browse_table_label_truename' => $truename,
                'empty_table_sql_query' => 'TRUNCATE ' . Util::backquote($currentTable['TABLE_NAME']),
                'empty_table_message_to_show' => urlencode(
                    sprintf(
                        __('Table %s has been emptied.'),
                        htmlspecialchars(
                            $currentTable['TABLE_NAME']
                        )
                    )
                ),
                'tracking_icon' => $this->getTrackingIcon($truename, $trackedTables[$truename] ?? null),
                'server_replica_status' => $replicaInfo['status'],
                'table_url_params' => $tableUrlParams,
                'db_is_system_schema' => $this->dbIsSystemSchema,
                'drop_query' => $dropQuery,
                'drop_message' => $dropMessage,
                'collation' => $collationDefinition,
                'formatted_size' => $formattedSize,
                'unit' => $unit,
                'overhead' => $overhead,
                'create_time' => isset($createTime) && $createTime
                        ? Util::localisedDate(strtotime($createTime)) : '-',
                'update_time' => isset($updateTime) && $updateTime
                        ? Util::localisedDate(strtotime($updateTime)) : '-',
                'check_time' => isset($checkTime) && $checkTime
                        ? Util::localisedDate(strtotime($checkTime)) : '-',
                'charset' => $charset ?? '',
                'is_show_stats' => $this->isShowStats,
                'ignored' => $ignored,
                'do' => $do,
                'approx_rows' => $approxRows,
                'show_superscript' => $showSuperscript,
                'already_favorite' => $this->checkFavoriteTable($currentTable['TABLE_NAME']),
                'num_favorite_tables' => $GLOBALS['cfg']['NumFavoriteTables'],
                'properties_num_columns' => $GLOBALS['cfg']['PropertiesNumColumns'],
                'limit_chars' => $GLOBALS['cfg']['LimitChars'],
                'show_charset' => $GLOBALS['cfg']['ShowDbStructureCharset'],
                'show_comment' => $GLOBALS['cfg']['ShowDbStructureComment'],
                'show_creation' => $GLOBALS['cfg']['ShowDbStructureCreation'],
                'show_last_update' => $GLOBALS['cfg']['ShowDbStructureLastUpdate'],
                'show_last_check' => $GLOBALS['cfg']['ShowDbStructureLastCheck'],
            ];

            $overallApproxRows = $overallApproxRows || $approxRows;
        }

        $databaseCollation = [];
        $databaseCharset = '';
        $collation = Charsets::findCollationByName(
            $this->dbi,
            $GLOBALS['cfg']['Server']['DisableIS'],
            $this->dbi->getDbCollation($this->db)
        );
        if ($collation !== null) {
            $databaseCollation = [
                'name' => $collation->getName(),
                'description' => $collation->getDescription(),
            ];
            $databaseCharset = $collation->getCharset();
        }

        $relationParameters = $this->relation->getRelationParameters();

        return $html . $this->template->render('database/structure/table_header', [
            'db' => $this->db,
            'db_is_system_schema' => $this->dbIsSystemSchema,
            'replication' => $replicaInfo['status'],
            'properties_num_columns' => $GLOBALS['cfg']['PropertiesNumColumns'],
            'is_show_stats' => $this->isShowStats,
            'show_charset' => $GLOBALS['cfg']['ShowDbStructureCharset'],
            'show_comment' => $GLOBALS['cfg']['ShowDbStructureComment'],
            'show_creation' => $GLOBALS['cfg']['ShowDbStructureCreation'],
            'show_last_update' => $GLOBALS['cfg']['ShowDbStructureLastUpdate'],
            'show_last_check' => $GLOBALS['cfg']['ShowDbStructureLastCheck'],
            'num_favorite_tables' => $GLOBALS['cfg']['NumFavoriteTables'],
            'structure_table_rows' => $structureTableRows,
            'body_for_table_summary' => [
                'num_tables' => $this->numTables,
                'server_replica_status' => $replicaInfo['status'],
                'db_is_system_schema' => $this->dbIsSystemSchema,
                'sum_entries' => $sumEntries,
                'database_collation' => $databaseCollation,
                'is_show_stats' => $this->isShowStats,
                'database_charset' => $databaseCharset,
                'sum_size' => $sumSize,
                'overhead_size' => $overheadSize,
                'create_time_all' => $createTimeAll ? Util::localisedDate(strtotime($createTimeAll)) : '-',
                'update_time_all' => $updateTimeAll ? Util::localisedDate(strtotime($updateTimeAll)) : '-',
                'check_time_all' => $checkTimeAll ? Util::localisedDate(strtotime($checkTimeAll)) : '-',
                'approx_rows' => $overallApproxRows,
                'num_favorite_tables' => $GLOBALS['cfg']['NumFavoriteTables'],
                'db' => $GLOBALS['db'],
                'properties_num_columns' => $GLOBALS['cfg']['PropertiesNumColumns'],
                'dbi' => $this->dbi,
                'show_charset' => $GLOBALS['cfg']['ShowDbStructureCharset'],
                'show_comment' => $GLOBALS['cfg']['ShowDbStructureComment'],
                'show_creation' => $GLOBALS['cfg']['ShowDbStructureCreation'],
                'show_last_update' => $GLOBALS['cfg']['ShowDbStructureLastUpdate'],
                'show_last_check' => $GLOBALS['cfg']['ShowDbStructureLastCheck'],
            ],
            'check_all_tables' => [
                'text_dir' => $GLOBALS['text_dir'],
                'overhead_check' => $overheadCheck,
                'db_is_system_schema' => $this->dbIsSystemSchema,
                'hidden_fields' => $hiddenFields,
                'disable_multi_table' => $GLOBALS['cfg']['DisableMultiTableMaintenance'],
                'central_columns_work' => $relationParameters->centralColumnsFeature !== null,
            ],
        ]);
    }

    /**
     * Returns the tracking icon if the table is tracked
     *
     * @param string     $table        table name
     * @param array|null $trackedTable
     *
     * @return string HTML for tracking icon
     */
    protected function getTrackingIcon(string $table, $trackedTable): string
    {
        $trackingIcon = '';
        if (Tracker::isActive()) {
            if ($trackedTable !== null) {
                $trackingIcon = $this->template->render('database/structure/tracking_icon', [
                    'db' => $this->db,
                    'table' => $table,
                    'is_tracked' => $trackedTable['active'],
                ]);
            }
        }

        return $trackingIcon;
    }

    /**
     * Returns whether the row count is approximated
     *
     * @param array $currentTable array containing details about the table
     * @param bool  $tableIsView  whether the table is a view
     *
     * @return array
     */
    protected function isRowCountApproximated(
        array $currentTable,
        bool $tableIsView
    ): array {
        $approxRows = false;
        $showSuperscript = '';

        // there is a null value in the ENGINE
        // - when the table needs to be repaired, or
        // - when it's a view
        //  so ensure that we'll display "in use" below for a table
        //  that needs to be repaired
        if (isset($currentTable['TABLE_ROWS']) && ($currentTable['ENGINE'] != null || $tableIsView)) {
            // InnoDB/TokuDB table: we did not get an accurate row count
            $approxRows = ! $tableIsView
                && in_array($currentTable['ENGINE'], ['CSV', 'InnoDB', 'TokuDB'])
                && ! $currentTable['COUNTED'];

            if ($tableIsView && $currentTable['TABLE_ROWS'] >= $GLOBALS['cfg']['MaxExactCountViews']) {
                $approxRows = true;
                $showSuperscript = Generator::showHint(
                    Sanitize::sanitizeMessage(
                        sprintf(
                            __(
                                'This view has at least this number of rows. Please refer to %sdocumentation%s.'
                            ),
                            '[doc@cfg_MaxExactCountViews]',
                            '[/doc]'
                        )
                    )
                );
            }
        }

        return [
            $approxRows,
            $showSuperscript,
        ];
    }

    /**
     * Returns the replication status of the table.
     *
     * @param array  $replicaInfo
     * @param string $table       table name
     *
     * @return array
     */
    protected function getReplicationStatus($replicaInfo, string $table): array
    {
        $do = $ignored = false;
        if ($replicaInfo['status']) {
            $nbServReplicaDoDb = count($replicaInfo['Do_DB']);
            $nbServReplicaIgnoreDb = count($replicaInfo['Ignore_DB']);
            $searchDoDBInTruename = array_search($table, $replicaInfo['Do_DB']);
            $searchDoDBInDB = array_search($this->db, $replicaInfo['Do_DB']);

            $searchDb = array_search($this->db, $replicaInfo['Ignore_DB']);
            $searchTable = array_search($table, $replicaInfo['Ignore_Table']);
            $ignored = (is_string($searchTable) && strlen($searchTable) > 0)
                || (is_string($searchDb) && strlen($searchDb) > 0)
                || $this->hasTable($replicaInfo['Wild_Ignore_Table'], $table);

            // Only set do = true if table is not ignored
            if (! $ignored) {
                $do = (is_string($searchDoDBInTruename) && strlen($searchDoDBInTruename) > 0)
                    || (is_string($searchDoDBInDB) && strlen($searchDoDBInDB) > 0)
                    || ($nbServReplicaDoDb == 0 && $nbServReplicaIgnoreDb == 0)
                    || $this->hasTable($replicaInfo['Wild_Do_Table'], $table);
            }
        }

        return [
            $do,
            $ignored,
        ];
    }

    /**
     * Function to check if a table is already in favorite list.
     *
     * @param string $currentTable current table
     */
    protected function checkFavoriteTable(string $currentTable): bool
    {
        $recentFavoriteTables = RecentFavoriteTable::getInstance('favorite');
        foreach ($recentFavoriteTables->getTables() as $value) {
            if ($value['db'] == $this->db && $value['table'] == $currentTable) {
                return true;
            }
        }

        return false;
    }

    /**
     * Find table with truename
     *
     * @param array  $db       DB to look into
     * @param string $truename Table name
     */
    protected function hasTable(array $db, $truename): bool
    {
        foreach ($db as $dbTable) {
            if (
                $this->db == $this->replication->extractDbOrTable($dbTable)
                && preg_match(
                    '@^' .
                    preg_quote(mb_substr($this->replication->extractDbOrTable($dbTable, 'table'), 0, -1), '@') . '@',
                    $truename
                )
            ) {
                return true;
            }
        }

        return false;
    }

    /**
     * Get the value set for ENGINE table,
     *
     * @internal param bool $table_is_view whether table is view or not
     *
     * @param array $currentTable current table
     * @param int   $sumSize      total table size
     * @param int   $overheadSize overhead size
     *
     * @return array
     */
    protected function getStuffForEngineTypeTable(
        array $currentTable,
        $sumSize,
        $overheadSize
    ) {
        $formattedSize = '-';
        $unit = '';
        $formattedOverhead = '';
        $overheadUnit = '';
        $tableIsView = false;

        switch ($currentTable['ENGINE']) {
            // MyISAM, ISAM or Heap table: Row count, data size and index size
            // are accurate; data size is accurate for ARCHIVE
            case 'MyISAM':
            case 'ISAM':
            case 'HEAP':
            case 'MEMORY':
            case 'ARCHIVE':
            case 'Aria':
            case 'Maria':
                [
                    $currentTable,
                    $formattedSize,
                    $unit,
                    $formattedOverhead,
                    $overheadUnit,
                    $overheadSize,
                    $sumSize,
                ] = $this->getValuesForAriaTable(
                    $currentTable,
                    $sumSize,
                    $overheadSize,
                    $formattedSize,
                    $unit,
                    $formattedOverhead,
                    $overheadUnit
                );
                break;
            case 'InnoDB':
            case 'PBMS':
            case 'TokuDB':
            case 'ROCKSDB':
                // InnoDB table: Row count is not accurate but data and index sizes are.
                // PBMS table in Drizzle: TABLE_ROWS is taken from table cache,
                // so it may be unavailable
                [$currentTable, $formattedSize, $unit, $sumSize] = $this->getValuesForInnodbTable(
                    $currentTable,
                    $sumSize
                );
                break;
            case 'CSV':
                [$currentTable, $formattedSize, $unit, $sumSize] = $this->getValuesForCsvTable(
                    $currentTable,
                    $sumSize
                );
                break;
            // Mysql 5.0.x (and lower) uses MRG_MyISAM
            // and MySQL 5.1.x (and higher) uses MRG_MYISAM
            // Both are aliases for MERGE
            case 'MRG_MyISAM':
            case 'MRG_MYISAM':
            case 'MERGE':
            case 'BerkeleyDB':
                // Merge or BerkleyDB table: Only row count is accurate.
                if ($this->isShowStats) {
                    $formattedSize = ' - ';
                    $unit = '';
                }

                break;
            // for a view, the ENGINE is sometimes reported as null,
            // or on some servers it's reported as "SYSTEM VIEW"
            case null:
            case 'SYSTEM VIEW':
                // possibly a view, do nothing
                break;
            case 'Mroonga':
                // The idea is to show the size only if Mroonga is available,
                // in other case the old unknown message will appear
                if (StorageEngine::hasMroongaEngine()) {
                    [$currentTable, $formattedSize, $unit, $sumSize] = $this->getValuesForMroongaTable(
                        $currentTable,
                        $sumSize
                    );
                    break;
                }
                // no break, go to default case
            default:
                // Unknown table type.
                if ($this->isShowStats) {
                    $formattedSize = __('unknown');
                    $unit = '';
                }
        }

        if ($currentTable['TABLE_TYPE'] === 'VIEW' || $currentTable['TABLE_TYPE'] === 'SYSTEM VIEW') {
            // countRecords() takes care of $cfg['MaxExactCountViews']
            $currentTable['TABLE_ROWS'] = $this->dbi
                ->getTable($this->db, $currentTable['TABLE_NAME'])
                ->countRecords(true);
            $tableIsView = true;
        }

        return [
            $currentTable,
            $formattedSize,
            $unit,
            $formattedOverhead,
            $overheadUnit,
            $overheadSize,
            $tableIsView,
            $sumSize,
        ];
    }

    /**
     * Get values for ARIA/MARIA tables
     *
     * @param array  $currentTable      current table
     * @param int    $sumSize           sum size
     * @param int    $overheadSize      overhead size
     * @param int    $formattedSize     formatted size
     * @param string $unit              unit
     * @param int    $formattedOverhead overhead formatted
     * @param string $overheadUnit      overhead unit
     *
     * @return array
     */
    protected function getValuesForAriaTable(
        array $currentTable,
        $sumSize,
        $overheadSize,
        $formattedSize,
        $unit,
        $formattedOverhead,
        $overheadUnit
    ) {
        if ($this->dbIsSystemSchema) {
            $currentTable['Rows'] = $this->dbi
                ->getTable($this->db, $currentTable['Name'])
                ->countRecords();
        }

        if ($this->isShowStats) {
            /** @var int $tblsize */
            $tblsize = $currentTable['Data_length']
                + $currentTable['Index_length'];
            $sumSize += $tblsize;
            [$formattedSize, $unit] = Util::formatByteDown($tblsize, 3, $tblsize > 0 ? 1 : 0);
            if (isset($currentTable['Data_free']) && $currentTable['Data_free'] > 0) {
                [$formattedOverhead, $overheadUnit] = Util::formatByteDown(
                    $currentTable['Data_free'],
                    3,
                    ($currentTable['Data_free'] > 0 ? 1 : 0)
                );
                $overheadSize += $currentTable['Data_free'];
            }
        }

        return [
            $currentTable,
            $formattedSize,
            $unit,
            $formattedOverhead,
            $overheadUnit,
            $overheadSize,
            $sumSize,
        ];
    }

    /**
     * Get values for InnoDB table
     *
     * @param array $currentTable current table
     * @param int   $sumSize      sum size
     *
     * @return array
     */
    protected function getValuesForInnodbTable(
        array $currentTable,
        $sumSize
    ) {
        $formattedSize = $unit = '';

        if (
            (in_array($currentTable['ENGINE'], ['InnoDB', 'TokuDB'])
            && $currentTable['TABLE_ROWS'] < $GLOBALS['cfg']['MaxExactCount'])
            || ! isset($currentTable['TABLE_ROWS'])
        ) {
            $currentTable['COUNTED'] = true;
            $currentTable['TABLE_ROWS'] = $this->dbi
                ->getTable($this->db, $currentTable['TABLE_NAME'])
                ->countRecords(true);
        } else {
            $currentTable['COUNTED'] = false;
        }

        if ($this->isShowStats) {
            /** @var int $tblsize */
            $tblsize = $currentTable['Data_length']
                + $currentTable['Index_length'];
            $sumSize += $tblsize;
            [$formattedSize, $unit] = Util::formatByteDown($tblsize, 3, ($tblsize > 0 ? 1 : 0));
        }

        return [
            $currentTable,
            $formattedSize,
            $unit,
            $sumSize,
        ];
    }

    /**
     * Get values for CSV table
     *
     * https://bugs.mysql.com/bug.php?id=53929
     *
     * @param array $currentTable current table
     * @param int   $sumSize      sum size
     *
     * @return array
     */
    protected function getValuesForCsvTable(
        array $currentTable,
        $sumSize
    ) {
        $formattedSize = $unit = '';

        if ($currentTable['ENGINE'] === 'CSV') {
            $currentTable['COUNTED'] = true;
            $currentTable['TABLE_ROWS'] = $this->dbi
                ->getTable($this->db, $currentTable['TABLE_NAME'])
                ->countRecords(true);
        } else {
            $currentTable['COUNTED'] = false;
        }

        if ($this->isShowStats) {
            // Only count columns that have double quotes
            $columnCount = (int) $this->dbi->fetchValue(
                'SELECT COUNT(COLUMN_NAME) FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA = \''
                . $this->dbi->escapeString($this->db) . '\' AND TABLE_NAME = \''
                . $this->dbi->escapeString($currentTable['TABLE_NAME']) . '\' AND NUMERIC_SCALE IS NULL;'
            );

            // Get column names
            $columnNames = $this->dbi->fetchValue(
                'SELECT GROUP_CONCAT(COLUMN_NAME) FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA = \''
                . $this->dbi->escapeString($this->db) . '\' AND TABLE_NAME = \''
                . $this->dbi->escapeString($currentTable['TABLE_NAME']) . '\';'
            );

            // 10Mb buffer for CONCAT_WS
            // not sure if is needed
            $this->dbi->query('SET SESSION group_concat_max_len = 10 * 1024 * 1024');

            // Calculate data length
            $dataLength = (int) $this->dbi->fetchValue('
                SELECT SUM(CHAR_LENGTH(REPLACE(REPLACE(REPLACE(
                    CONCAT_WS(\',\', ' . $columnNames . '),
                    UNHEX(\'0A\'), \'nn\'), UNHEX(\'22\'), \'nn\'), UNHEX(\'5C\'), \'nn\'
                ))) FROM ' . Util::backquote($this->db) . '.' . Util::backquote($currentTable['TABLE_NAME']));

            // Calculate quotes length
            $quotesLength = $currentTable['TABLE_ROWS'] * $columnCount * 2;

            /** @var int $tblsize */
            $tblsize = $dataLength + $quotesLength + $currentTable['TABLE_ROWS'];

            $sumSize += $tblsize;
            [$formattedSize, $unit] = Util::formatByteDown($tblsize, 3, $tblsize > 0 ? 1 : 0);
        }

        return [$currentTable, $formattedSize, $unit, $sumSize];
    }

    /**
     * Get values for Mroonga table
     *
     * @param array $currentTable current table
     * @param int   $sumSize      sum size
     *
     * @return array
     */
    protected function getValuesForMroongaTable(
        array $currentTable,
        int $sumSize
    ): array {
        $formattedSize = '';
        $unit = '';

        if ($this->isShowStats) {
            /** @var int $tblsize */
            $tblsize = $currentTable['Data_length'] + $currentTable['Index_length'];
            $sumSize += $tblsize;
            [$formattedSize, $unit] = Util::formatByteDown($tblsize, 3, ($tblsize > 0 ? 1 : 0));
        }

        return [
            $currentTable,
            $formattedSize,
            $unit,
            $sumSize,
        ];
    }
}
