<?php

if (!defined('AOWOW_REVISION'))
    die('illegal access');

if (!CLI)
    die('not in cli mode');


class CLISetup
{
    public  static $locales       = [];
    public  static $localeIds     = [];

    public  static $srcDir        = 'setup/mpqdata/';

    private static $mpqFiles      = [];
    public  static $expectedPaths = array(                  // update paths [yes, you can have en empty string as key]
        ''     => LOCALE_EN,    'enGB' => LOCALE_EN,    'enUS' => LOCALE_EN,
        'frFR' => LOCALE_FR,
        'deDE' => LOCALE_DE,
        'zhCN' => LOCALE_CN,    'enCN' => LOCALE_CN,
        'esES' => LOCALE_ES,    'esMX' => LOCALE_ES,
        'ruRU' => LOCALE_RU
    );

    public  const LOCK_OFF        = 0;
    public  const LOCK_ON         = 1;
    public  const LOCK_RESTORE    = 2;

    private static $lock          = 1;

    private const ARGV_REQUIRED   = 0x01;
    private const ARGV_OPTIONAL   = 0x02;
    private const ARGV_ARRAY      = 0x10;

    private static $opts          = [];
    private static $optGroups     = ['AoWoW Setup', 'Utility Functions', 'Additional Options', 'Additional arguments specific to --build=simpleImg', 'Additional arguments specific to --build=complexImg'];
    private static $optDefs       = array(                  // cmd => [groupId, aliasses[], flags, description, appendix]
        'setup'            => [0, ['s', 'firstrun'], 0x00, 'Step by step initial setup. Resumes if interrupted.',                                                       ''                  ],
        'update'           => [0, ['u'],             0x00, 'Apply new sql updates fetched from Github and run --sync as needed.',                                       ''                  ],
        'dbconfig'         => [1, [],                0x00, 'Set up DB connection.',                                                                                     ''                  ],
        'siteconfig'       => [1, [],                0x00, 'Set up site variables.',                                                                                    ''                  ],
        'account'          => [1, [],                0x00, 'Create an account with admin privileges.',                                                                  ''                  ],
        'sql'              => [1, [],                0x12, 'Generate DB content from your world tables.',                                                               '=<subScriptList,>' ],
        'build'            => [1, [],                0x12, 'Compile image files and data dumps.',                                                                       '=<subScriptList,>' ],
        'sync'             => [1, [],                0x12, 'Regenerate tables/files that depend on given world DB table.',                                              '=<worldTableList,>'],
        'dbc'              => [1, [],                0x11, 'Extract dbc files from mpqDataDir into sql table. Structure must be defined in setup/dbc.class.php.',       '=<dbcfileList,>'   ],
        'delete'           => [2, ['d'],             0x00, 'Delete dbc_* tables generated by this prompt when done.',                                                   ''                  ],
        'log'              => [2, [],                0x01, 'Write CLI ouput to file.',                                                                                  '=logfile'          ],
        'help'             => [2, ['h'],             0x00, 'Display contextual help, if available.',                                                                    ''                  ],
        'force'            => [2, ['f'],             0x00, 'Force existing files to be overwritten.',                                                                   ''                  ],
        'locales'          => [2, [],                0x12, 'Limit setup to enUS, frFR, deDE, zhCN, esES and/or ruRU. (does not override config settings)',              '=<regionCodes,>'   ],
        'mpqDataDir'       => [2, [],                0x02, 'Manually point to directory with extracted mpq files. This is limited to setup/ (default: setup/mpqData/)', '=path/'            ],
        'icons'            => [3, ['1'],             0x00, 'Generate icons for spells, items, classes, races, ect.',                                                    ''                  ],
        'glyphs'           => [3, ['2'],             0x00, 'Generate decorative glyph symbols displayed on related item and spell pages.',                              ''                  ],
        'pagetexts'        => [3, ['3'],             0x00, 'Generate images contained in text on readable items and gameobjects.',                                      ''                  ],
        'loadingscreens'   => [3, ['4'],             0x00, 'Generate loading screen images (not used on page; skipped by default)',                                     ''                  ],
        'talentbgs'        => [4, ['1'],             0x00, 'Generate backgrounds for the talent calculator.',                                                           ''                  ],
        'maps'             => [4, ['2'],             0x00, 'Generate zone and continental maps.',                                                                       ''                  ],
        'spawn-maps'       => [4, ['3'],             0x00, 'Fallback to generate alpha masks for each zone to match creature and gameobject spawn points.',             ''                  ],
        'artwork'          => [4, ['4'],             0x00, 'Generate images from /glues/credits (not used on page; skipped by default))',                               ''                  ],
        'area-maps'        => [4, ['5'],             0x00, 'Generate additional area maps with highlighting for subzones (optional; skipped by default)',               ''                  ]
    );


    /**************************/
    /* command line arguments */
    /**************************/

    public static function init() : void
    {
        $short = '';
        $long  = [];
        $alias = [];

        foreach (self::$optDefs as $opt => [$idx, $aliasses, $flags, , ])
        {
            if ($flags & self::ARGV_REQUIRED)
                $opt .= ':';
            else if ($flags & self::ARGV_OPTIONAL)
                $opt .= '::';

            $long[] = $opt;
            foreach ($aliasses as $a)
            {
                if ($flags & self::ARGV_REQUIRED)           // neither should be set with shortOpts
                    $_a = $a.':';
                else if ($flags & self::ARGV_OPTIONAL)
                    $_a = $a.'::';
                else
                    $_a = $a;

                $alias[$a] = $opt;
                if (strlen($a) == 1)
                    $short .= $_a;
                else
                    $long[] = $_a;
            }
        }

        if ($opts = getopt($short, $long))
            foreach ($opts as $o => $v)
                self::$opts[$alias[$o] ?? $o] = (self::$optDefs[$alias[$o] ?? $o][2] & self::ARGV_ARRAY) ? ($v ? explode(',', $v) : []) : ($v ?: true);

        // optional logging
        if (isset(self::$opts['log']))
            CLI::initLogFile(trim(self::$opts['log']));

        // alternative data source (no quotes, use forward slash)
        if (isset(self::$opts['mpqDataDir']))
            self::$srcDir = CLI::nicePath(self::$opts['mpqDataDir']);

        // optional limit handled locales
        if (isset(self::$opts['locales']))
        {
            // engb and enus are identical for all intents and purposes
            $from = ['engb', 'esmx', 'encn'];
            $to   = ['enus', 'eses', 'zhcn'];
            $opts['locales'] = str_ireplace($from, $to, strtolower($opts['locales']));

            self::$locales = array_intersect(Util::$localeStrings, explode(',', $opts['locales']));
        }
        if (!self::$locales)
            self::$locales = array_filter(Util::$localeStrings);

        // restrict actual locales
        foreach (self::$locales as $idx => $str)
            if (!defined('CFG_LOCALES') || CFG_LOCALES & (1 << $idx))
                self::$localeIds[] = $idx;

        // get site status
        if (DB::isConnectable(DB_AOWOW))
            self::$lock = (int)DB::Aowow()->selectCell('SELECT `value` FROM ?_config WHERE `key` = "maintenance"');
        else
            self::$lock = self::LOCK_ON;
    }

    public static function getOpt(...$args)
    {
        if (!$args)
            return false;

        $result = [];

        // groupMask case
        if (is_int($args[0]))
        {
            foreach (self::$optDefs as $o => [$group, , , , ])
                if (((1 << $group) & $args[0]) && isset(self::$opts[$o]))
                    $result[] = $o;

            return $result;
        }

        // single key case
        if (count($args) == 1)
            return self::$opts[$args[0]] ?? false;

        // multiple keys case
        foreach ($args as $a)
            if (isset(self::$optDefs[$a]))
                $result[$a] = self::$opts[$a] ?? false;

        return $result;
    }

    public static function optHelp(int $groupMask = 0x0) : void
    {
        $lines = [];

        foreach (self::$optGroups as $idx => $og)
        {
            if ($groupMask && !($groupMask & (1 << $idx)))
                continue;

            $lines[] = [$og, ''];

            foreach (self::$optDefs as $opt => [$group, $alias, , $desc, $app])
            {
                if ($group != $idx)
                    continue;

                $cmd = '  --'.$opt;
                foreach ($alias as $a)
                    $cmd .= ' | '.(strlen($a) == 1 ? '-'.$a : '--'.$a);

                $lines[] = [$cmd.$app, $desc];
            }
        }

        CLI::writeTable($lines);
    }


    /*******************/
    /* web page access */
    /*******************/

    public static function siteLock(int $mode = self::LOCK_RESTORE) : void
    {
        if (DB::isConnectable(DB_AOWOW))
            DB::Aowow()->query('UPDATE ?_config SET `value` = ?d WHERE `key` = "maintenance"', (int)!!($mode == self::LOCK_RESTORE ? self::$lock : $mode));
    }


    /*******************/
    /* MPQ-file access */
    /*******************/

    /*  the problem
        1) paths provided in dbc files are case-insensitive and random
        2) paths to the actual textures contained in the mpq archives are case-insensitive and random
        unix systems will throw a fit if you try to get from one to the other, so lets save the paths from 2) and cast it to lowercase
        lookups will be done in lowercase. A successfull match will return the real path.
    */
    private static function buildFileList() : bool
    {
        CLI::write();
        CLI::write('indexing game data from '.self::$srcDir.' for first time use...');

        $setupDirs = glob('setup/*');
        foreach ($setupDirs as $sd)
        {
            if (mb_substr(self::$srcDir, -1) == '/')
                self::$srcDir = mb_substr(self::$srcDir, 0, -1);

            if (mb_substr($sd, -1) == '/')
                $sd = mb_substr($sd, 0, -1);

            if (Util::lower($sd) == Util::lower(self::$srcDir))
            {
                self::$srcDir = $sd.'/';
                break;
            }
        }

        try
        {
            $iterator = new RecursiveDirectoryIterator(self::$srcDir);
            $iterator->setFlags(RecursiveDirectoryIterator::SKIP_DOTS);

            foreach (new RecursiveIteratorIterator($iterator, RecursiveIteratorIterator::SELF_FIRST) as $path)
            {
                $_ = str_replace('\\', '/', $path->getPathname());
                self::$mpqFiles[strtolower($_)] = $_;
            }

            CLI::write('done');
            CLI::write();
        }
        catch (UnexpectedValueException $e)
        {
            CLI::write('- mpqData dir '.self::$srcDir.' does not exist', CLI::LOG_ERROR);
            return false;
        }

        return true;
    }

    public static function fileExists(&$file)
    {
        // read mpq source file structure to tree
        if (!self::$mpqFiles)
            if (!self::buildFileList())
                return false;

        // backslash to forward slash
        $_ = strtolower(str_replace('\\', '/', $file));

        // remove trailing slash
        if (mb_substr($_, -1, 1) == '/')
            $_ = mb_substr($_, 0, -1);

        if (isset(self::$mpqFiles[$_]))
        {
            $file = self::$mpqFiles[$_];
            return true;
        }

        return false;
    }

    public static function filesInPath($path, $useRegEx = false)
    {
        $result = [];

        // read mpq source file structure to tree
        if (!self::$mpqFiles)
            if (!self::buildFileList())
                return [];

        // backslash to forward slash
        $_ = strtolower(str_replace('\\', '/', $path));

        foreach (self::$mpqFiles as $lowerFile => $realFile)
        {
            if (!$useRegEx && strstr($lowerFile, $_))
                $result[] = $realFile;
            else if ($useRegEx && preg_match($path, $lowerFile))
                $result[] = $realFile;
        }

        return $result;
    }


    /*****************/
    /* file handling */
    /*****************/

    public static function writeFile($file, $content)
    {
        if (Util::writeFile($file, $content))
        {
            CLI::write(sprintf(ERR_NONE, CLI::bold($file)), CLI::LOG_OK);
            return true;
        }

        $e = error_get_last();
        CLI::write($e['message'].' '.CLI::bold($file), CLI::LOG_ERROR);
        return false;
    }

    public static function writeDir($dir)
    {
        if (Util::writeDir($dir))
            return true;

        CLI::write(error_get_last()['message'].' '.CLI::bold($dir), CLI::LOG_ERROR);
        return false;
    }

    public static function loadDBC($name)
    {
        if (DB::Aowow()->selectCell('SHOW TABLES LIKE ?', 'dbc_'.$name) && DB::Aowow()->selectCell('SELECT count(1) FROM ?#', 'dbc_'.$name))
            return true;

        $dbc = new DBC($name, ['temporary' => self::getOpt('delete')]);
        if ($dbc->error)
        {
            CLI::write('CLISetup::loadDBC() - required DBC '.$name.'.dbc not found!', CLI::LOG_ERROR);
            return false;
        }

        if (!$dbc->readFile())
        {
            CLI::write('CLISetup::loadDBC() - DBC '.$name.'.dbc could not be written to DB!', CLI::LOG_ERROR);
            return false;
        }

        return true;
    }
}

?>
