#!/usr/bin/env php
<?php
/**
 * Contain Project
 *
 * Compiles a fully qualified namespace that points to an entity definition
 * into a ready-to-use entity class with an optional filter class.
 *
 * This source file is subject to the BSD license bundled with
 * this package in the LICENSE.txt file. It is also available
 * on the world-wide-web at http://www.opensource.org/licenses/bsd-license.php.
 * If you are unable to receive a copy of the license or have
 * questions concerning the terms, please send an email to
 * me@andrewkandels.com.
 *
 * @category    akandels
 * @package     contain
 * @author      Andrew Kandels (me@andrewkandels.com)
 * @copyright   Copyright (c) 2012 Andrew P. Kandels (http://andrewkandels.com)
 * @license     http://www.opensource.org/licenses/bsd-license.php BSD License
 * @link        http://andrewkandels.com/contain
 */

require_once(__DIR__ . '/abstract-script.php');

$argLine = implode(' ', $argv);

define('IS_DEBUG', strpos($argLine, '-v') !== false);
define('DELETE_UNUSED', strpos($argLine, '-d') !== false);

$config       = include(APPLICATION_CONFIG_FILE);
$modules      = $config['modules'];
$basePaths    = $config['module_listener_options']['module_paths'];
$entities     = array();
$dependencies = array();

foreach ($modules as $module) {
    foreach ($basePaths as $basePath) {
        $entityPath = sprintf('%s/%s/%s/src/%s/Entity',
            ZF2_APPLICATION_PATH,
            $basePath,
            $module,
            $module
        );

        if (!is_dir($entityPath = str_replace('./', '', $entityPath))) {
            continue;
        }

        $entities = array_merge($entities, get_definitions($entityPath));
    }
}

fprintf(STDERR, 'Found %s entity definition classes.%s', $num = count($entities), PHP_EOL);

if (!$num) {
    exit(0);
}

if (DELETE_UNUSED) {
    clear_unused_module_files($entities);
}

compile_entities($entities, new \Contain\Entity\Compiler\Compiler());

function compile_entities(array $entities, \Contain\Entity\Compiler\Compiler $compiler) {
    $dependencies = array();
    $compiled     = 0;

    foreach ($entities as $className => $fileName) {
        require_once($fileName);

        fprintf(STDERR, '%-60s ... ', sprintf('Compiling %s', $className));

        try {
            $definition = new $className();
        } catch (Exception $e) {
            if (false !== strpos($e->getMessage(), '$type invalid as type alias or class name.')) {
                $dependencies[$className] = $fileName;
                fprintf(STDERR, "[ Dependency -- will retry ]\n");
                continue;
            }

            if (IS_DEBUG) {
                fprintf(STDERR, "[ Failed ]\n%s Exception (%s) %s\n--\n%s\n--\n\n",
                    $fileName,
                    get_class($e),
                    $e->getMessage(),
                    $e->getTraceAsString()
                );
                continue;
            }

            fprintf(STDERR, '[ Failed ]%s', PHP_EOL);
            continue;
        }

        if (!$definition->getTargets()) {
            fprintf(STDERR, '[ Skipping - no targets ]%s', PHP_EOL);
            continue;
        }

        try {
            $compiler->compile($className);
        } catch (Exception $e) {
            if (IS_DEBUG) {
                fprintf(STDERR, "[ Failed ]\n%s Exception (%s) %s\n--\n%s\n--\n\n",
                    $fileName,
                    get_class($e),
                    $e->getMessage(),
                    $e->getTraceAsString()
                );

                continue;
            }

            fprintf(STDERR, '[ Failed ]%s', PHP_EOL);

            continue;
        }

        $compiled++;

        fprintf(STDERR, '[ Ok ]%s', PHP_EOL);
    }

    if ($compiled && $dependencies) {
        compile_entities($dependencies);
    }
}

function clear_unused_module_files(array $entities) {
    $modulePaths = array();
    $definitions = array();

    foreach ($entities as $className => $fileName) {
        if (!preg_match('!^(.*/Entity)/Definition/(.*)\.php$!', $fileName, $matches)) {
            continue;
        }

        $modulePaths[$matches[1]] = true;
        $definitions[] = $matches[2];
    }

    foreach ($modulePaths as $modulePath => $definition) {
        $di = new DirectoryIterator($modulePath);

        foreach ($di as $item) {
            if (!$item->isDir() || $item->isDot()) {
                continue;
            }

            $name = $item->getBasename();

            if ($name == 'Definition') {
                continue;
            }

            clear_unused_classes($modulePath . '/' . $name, $definitions);
        }
    }
}

function clear_unused_classes($path, array $definitions) {
    $di = new DirectoryIterator($path);

    foreach ($di as $item) {
        if (!$item->isFile() || $item->getExtension() != 'php') {
            continue;
        }

        $name = $item->getBasename('.php');

        if (!in_array($name, $definitions)) {
            fprintf(STDERR, 'Deleting %s, no matching definition.%s',
                $item->getPathname(),
                PHP_EOL
            );

            unlink($item->getPathname());
        }
    }
}

function get_definitions($path) {
    if (!is_dir($path .= '/Definition')) {
        return array();
    }

    $di = new DirectoryIterator($path);
    $entities = array();

    foreach ($di as $item) {
        if (!$item->isFile() || $item->getExtension() != 'php') {
            continue;
        }
        
        if (!preg_match('!/([^/]+)/Entity/Definition/(.*)$!', $item->getPathname(), $matches)) {
            continue;
        }

        $module = $matches[1];

        $entities[sprintf('%s\\Entity\\Definition\\%s',
            $matches[1],
            $item->getBasename('.php')
        )] = $item->getPathname();
    }

    return $entities;
}
