#!/usr/bin/env php
<?php

/**
 * Copyright (C) 2017 Benjamin Heisig
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
 *
 * @author Benjamin Heisig <https://benjamin.heisig.name/>
 * @copyright Copyright (C) 2016-17 Benjamin Heisig
 * @license http://www.gnu.org/licenses/agpl-3.0 GNU Affero General Public License (AGPL)
 * @link https://github.com/bheisig/i-doit-cli
 */

namespace bheisig\idoitcli;

$config = [];

try {
    /**
     * Load classes
     */

    require_once __DIR__ . '/../vendor/autoload.php';

    spl_autoload_register(
        function ($class) {
            if (strpos($class, 'bheisig\\idoitcli\\') === 0) {
                $file = str_replace(
                    ['bheisig\\idoitcli\\', '\\'],
                    ['', '/'],
                    $class
                );

                require_once __DIR__ . '/../src/' . $file . '.php';
            }
        },
        true
    );

    /**
     * The real(tm) recursive array merge.
     *
     * @param array $array1 First array
     * @param array $array2 Second array
     * @param array $array (Optional) more arrays
     *
     * @return array Combined array
     */
    function array_merge_recursive_overwrite(array $array1, array $array2, array $array = []) {
        $arrays = func_get_args();
        $merged = array();
        while ($arrays) {
            $array = array_shift($arrays);
            assert('is_array($array)');
            if (!$array) {
                continue;
            }
            foreach ($array as $key => $value) {
                if (is_string($key)) {
                    if (is_array($value) && array_key_exists($key, $merged) && is_array($merged[$key])) {
                        $merged[$key] = call_user_func(__FUNCTION__, $merged[$key], $value);
                    } else {
                        $merged[$key] = $value;
                    }
                } else {
                    $merged[] = $value;
                }
            }
        }
        return $merged;
    }

    /**
     * Load configuration settings
     */

    $configFiles = [
        __DIR__ . '/../src/default.json',
        '/etc/idoitcli/config.json',
        $_SERVER['HOME'] . '/.idoitcli/config.json'
    ];

    foreach ($configFiles as $configFile) {
        if (is_readable($configFile)) {
            $settings = json_decode(
                trim(
                    file_get_contents(
                        $configFile
                    )
                ),
                true
            );

            if ($settings === false) {
                throw new \Exception(sprintf(
                    'Configuration file "%s" contains invalid JSON data.',
                    500
                ));
            }

            $config = array_merge_recursive_overwrite(
                $config,
                $settings
            );
        }
    }

    $config['baseDir'] = $_SERVER['HOME'] . '/.idoitcli';
    $config['userConfig'] = $config['baseDir'] . '/config.json';
    $config['dataDir'] = $config['baseDir'] . '/data';

    /**
     * Process command line options
     */

    $config['basename'] = basename(__FILE__);

    $config['options'] = getopt(
        'c:o:h',
        [
            'config:',
            'output:',
            'help',
            'version'
        ]
    );

    $config['commands'] = [
        'init' => 'Create configuration settings and create cache',
        'status' => 'Current status information',
        'read' => 'Fetch information from your CMDB',
        'search' => 'Find your needle in the haystack called CMDB',
        'random' => 'Create randomized data',
        'nextip' => 'Fetch the next free IP address for a given subnet',
        'help' => 'Show this help'
    ];

    $config['args'] = $GLOBALS['argv'];

    if (count($config['args']) < 2) {
        throw new \Exception('Too few arguments', 400);
    }

    /**
     * Load additional configuration files
     */

    $additionalConfigFiles = [];

    foreach ($config['options'] as $option => $value) {
        if (in_array($option, ['c', 'config'])) {
            switch(gettype($value)) {
                case 'string':
                    $additionalConfigFiles[] = $value;
                    break;
                case 'array':
                    foreach ($value as $item) {
                        if (!is_string($item)) {
                            throw new \Exception(sprintf(
                                'Unknown value "%s" for option "%s"',
                                $item,
                                $option
                            ));
                        }

                        $additionalConfigFiles[] = $item;
                    }
                    break;
                default:
                    throw new \Exception(sprintf(
                        'Unknown value "%s" for option "%s"',
                        $value,
                        $option
                    ));
            }
        }
    }

    foreach ($additionalConfigFiles as $additionalConfigFile) {
        if (!is_readable($additionalConfigFile)) {
            throw new \Exception(sprintf(
                'Unable to read onfiguration file "%s"',
                $additionalConfigFile
            ), 400);
        }

        $settings = json_decode(
            trim(
                file_get_contents(
                    $additionalConfigFile
                )
            ),
            true
        );

        if ($settings === false) {
            throw new \Exception(sprintf(
                'Configuration file "%s" contains invalid JSON data.',
                500
            ));
        }

        $config = array_merge_recursive_overwrite(
            $config,
            $settings
        );
    }

    /**
     * Read important JSON files
     */

    $files = [
        'composer',
        'project'
    ];

    foreach ($files as $file) {
        $path = __DIR__ . '/../' . $file . '.json';

        if (!is_readable($path)) {
            throw new \Exception(sprintf('%s.json not found', $file));
        }

        $config[$file] = json_decode(trim(file_get_contents($path)), true);
    }

    /**
     * Show version information
     */

    if (array_key_exists('version', $config['options'])) {
        $projectFile = __DIR__ . '/../project.json';

        if (!is_readable($projectFile)) {
            throw new \Exception('project.json not found');
        }

        $projectInfo = json_decode(trim(file_get_contents($projectFile)), true);

        IO::out('%s %s', $projectInfo['title'], $projectInfo['version']);
        exit(0);
    }

    /**
     * Call command
     */

    foreach ($config['args'] as $arg) {
        if (array_key_exists($arg, $config['commands'])) {
            $class = __NAMESPACE__ . '\\' . ucfirst($arg);

            $config['command'] = $arg;

            if (class_exists($class) &&
                is_subclass_of($class, '\bheisig\idoitcli\Executes')
            ) {
                /** @var Command $command */
                $command = new $class($config);

                foreach ($config['args'] as $help) {
                    if (in_array($help, ['-h', '--help'])) {
                        $command->showUsage();
                        exit(0);
                    }
                }

                $command->setup()->execute()->tearDown();

                exit(0);
            }
        }
    }

    /**
     * Show help
     */

    if (array_key_exists('h', $config['options']) ||
        array_key_exists('help', $config['options'])) {
        $help = new Help($config);
        $help->setup()->execute()->tearDown();
        exit(0);
    }

    /**
     * Ooops, went to far…
     */

    throw new \Exception('Bad request', 400);
} catch (\Exception $e) {
    IO::err($e->getMessage());

    if ($e->getCode() === 400) {
        $help = new Help($config);
        $help->execute();
    }

    exit(1);
}
