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

foreach (array(__DIR__ . '/../../../autoload.php', __DIR__ . '/../../vendor/autoload.php', __DIR__ . '/../vendor/autoload.php') as $file) {
    if (file_exists($file)) {
        define('BRONTO_LEGACY_INSTALL', $file);
        break;
    }
}

unset($file);

require BRONTO_LEGACY_INSTALL;

class ConvertToLegacy {

    private $_doNotConvert = array('bootstrap.php');
    private $_root;

    /**
     * Creates a generic conversion notice on all files that had to be
     * converted with stripped namespaces. Magento 1.x extensions
     * will have to be shipped with this legacy file.
     *
     * @param SplFileInfo $phpFile
     * @return string
     */
    public function getConversionNote($phpFile)
    {
        $parts = explode('/', $phpFile->getRealPath());
        $index = array_search('Bronto', $parts, true);
        $newParts = array_slice($parts, $index - 1);
        $fullPath = implode('/', $newParts);
        return <<<RAW
/**
 * This file was generated by the ConvertToLegacy class in bronto-legacy.
 * The purpose of the conversion was to maintain PSR-0 compliance while
 * the main development focuses on modern styles found in PSR-4.
 *
 * For the original:
 * @see {$fullPath}
 */
RAW;
    }

    /**
     * The path pointing to the library source code
     *
     * @return string
     */
    public function getSourcePath()
    {
        return $this->_root . '/src/';
    }

    /**
     * The path pointing to the library test code
     *
     * @return string
     */
    public function getTestsPath()
    {
        return $this->_root . '/tests/';
    }

    /**
     * The path pointing to the converted destination source
     *
     * @return string
     */
    public function getDestSourcePath()
    {
        return $this->_root . '/legacy/src';
    }

    /**
     * The path pointing to the converted destination test
     *
     * @return string
     */
    public function getDestTestPath()
    {
        return $this->_root . '/legacy/tests';
    }

    /**
     * Iterator that scans a directory structure based on the root path
     *
     * @param string $path
     * @return RecursiveIteratorIterator
     */
    public function getFileIterator($path)
    {
        return new RecursiveIteratorIterator(new RecursiveDirectoryIterator($path));
    }

    /**
     * Gathers a hastable of imports to convert random class names
     *
     * @param SplFileInfo $phpFile
     * @param boolean $duped (Optional)
     * @return array($className => $namespaceAndClass)
     */
    protected function _gatherImpliedImports($phpFile, $duped = false)
    {
        $parent = $phpFile->getPathInfo();
        $self = $phpFile->getFilename();
        $imports = array();
        foreach (scandir($parent->getRealPath()) as $file) {
            if (preg_match('/^\.\.?/', $file)) {
                continue;
            }
            if (is_dir("{$parent->getRealPath()}/$file")) {
                $imports[$file] = true;
            } else if (preg_match('/([^\\.]+)\.php$/', $file, $match)) {
                $imports[$match[1]] = true;
            }
        }
        if (!$duped && strpos($phpFile->getPathname(), $this->getTestsPath()) !== false) {
            $duplicate = new SplFileInfo(str_replace($this->getTestsPath(), $this->getSourcePath(), $phpFile->getPathname()));
            $imports += $this->_gatherImpliedImports($duplicate, true);
        }
        return $imports;
    }

    protected function _classManager()
    {
        return <<<PHP
<?php

class Bronto_ImportManager
{
    public static \$_fileCache = array();
}
PHP;
    }

    protected function _convertClassExists($namespace, &$lines, $line)
    {
        $matcher = '/class_exists\(([^\)]+)\)/';
        if (preg_match($matcher, $line, $match)) {
            $arg = $match[1];
            $legacyLookup = <<<PHP
        // Note: This snippet was generated with legacy conversion
        if (is_string({$arg}) && !class_exists({$arg}, false) && !array_key_exists({$arg}, Bronto_ImportManager::\$_fileCache)) {
            \$dir = preg_replace('|' . str_replace("_", "/", "{$namespace}") . '$|', '', dirname(__FILE__), 1);
            \$file = \$dir . str_replace("_", "/", {$arg}) . '.php';
            if (file_exists(\$file)) {
                require_once \$file;
                Bronto_ImportManager::\$_fileCache[{$arg}] = true;
            } else {
                Bronto_ImportManager::\$_fileCache[{$arg}] = false;
            }
        }
        // End Conversion Snippet\n
PHP;
            return $legacyLookup . preg_replace($matcher, '(class_exists(' . $arg . ', false) || Bronto_ImportManager::$_fileCache[' . $arg . '])', $line);
        }
        return $line;
    }

    /**
     * Converts a given source file to the PSR-0 compliant source file
     *
     * @param SplFileInfo $phpFile
     * @return array($namespace => $contents)
     */
    protected function _convertNamespaces($phpFile)
    {
        $namespace = '';
        $converted = array();
        $lines = file($phpFile->getRealPath());
        $imports = $this->_gatherImpliedImports($phpFile);
        $definition = preg_replace('/\.php$/', '', $phpFile->getFilename());
        foreach ($lines as $index => $line) {
            $parentClass = false;
            $popLine = false;

            if ($index == 1) {
                $line = $this->getConversionNote($phpFile) . $line;
                $converted[] = $line;
                continue;
            }

            if (empty($namespace) && preg_match('/namespace\s+([^\s;]+)/', $line, $match)) {
                $namespace = str_replace("\\", '_', $match[1]);
                foreach ($imports as $lookup => $replacement) {
                    $imports[$lookup] = str_replace('\\', '_', $namespace . '_' . $lookup);
                }
                $popLine = true;
            } else if (preg_match('/^use\s+([^\s;]+)/', $line, $match)) {
                $pieces = explode('\\', $match[1]);
                $lookup = end($pieces);
                $imports[$lookup] = str_replace('\\', '_', ltrim($match[1], '\\'));
                $popLine = true;
            } else if (preg_match('/^(?:abstract\s+)?(?:class|interface)/', $line)) {
                $parentClass = true;
            }

            if ($popLine) {
                continue;
            }

            foreach ($imports as $lookup => $replacement) {
                $line = preg_replace("/([^A-Za-z0-9\\|\\\\_]+)$lookup/", '${1}' . $replacement, $line);
                if ($parentClass && ($lookup == $definition)) {
                    unset($imports[$lookup]);
                    $parentClass = false;
                    // TODO: I hate this special case, but it appears to only happen once
                    if ($phpFile->getFilename() == 'DeliveryGroup.php') {
                        unset($imports['Delivery']);
                    }
                }
            }

            // TODO: temp hack to get around preg escapes
            // The better solution is to do a class_exist
            // check on the derived name, but that has to
            // come later
            if (!preg_match('/preg_/', $line) && !preg_match("/\\\\x/", $line)) {
                $line = preg_replace("/([^A-Za-z0-9\\\\]+)\\\\(?:\\\\*)/", '${1}', $line);
                $line = preg_replace("/\\\\(?:\\\\)?/", "_", $line);
            }
            $line = $this->_convertClassExists($namespace, $converted, $line);
            $converted[] = $line;
        }
        return array($namespace, implode('', $converted));
    }

    /**
     * Converts the source file to the "no-namespace" variety
     *
     * @param SplFileInfo $phpFile
     * @param string $destPrefx
     * @return void
     */
    protected function _convertSource($phpFile, $destPrefx)
    {
        list($namespace, $content) = $this->_convertNamespaces($phpFile);
        $destPath = $destPrefx . '/' . str_replace('_', '/', $namespace);
        if (!is_dir($destPath)) {
            mkdir($destPath, 0777, true);
        }
        file_put_contents($destPath . '/' . $phpFile->getFilename(), $content);
    }

    /**
     * Entry point to converter.
     *
     * @param array $args
     * @return void
     */
    public function run($args = array())
    {
        if (array_key_exists('root', $args)) {
            $dir = new SplFileInfo($args['root']);
            $this->_root = $dir->getRealPath();
        } else {
            $this->_root = __DIR__ . '/..';
        }
        $sources = array(
            $this->getSourcePath() => $this->getDestSourcePath(),
            $this->getTestsPath() => $this->getDestTestPath()
        );

        foreach ($sources as $source => $dest) {
            $dir = $this->getFileIterator($source);
            $phpFiles = new CallbackFilterIterator($dir, function($file) {
                return preg_match('/\.php$/', $file->getFilename());
            });
            foreach ($phpFiles as $phpFile) {
                if (in_array($phpFile->getFilename(), $this->_doNotConvert)) {
                    continue;
                }
                echo "Converting: {$phpFile->getRealPath()}\n";
                $this->_convertSource($phpFile, $dest);
            }
        }

        file_put_contents($this->getDestSourcePath() . '/Bronto/ImportManager.php', $this->_classManager());
    }
}

$command = new ConvertToLegacy();
$command->run(getopt("", array('root::')));
