<?php
/**
 * Phergie 
 *
 * PHP version 5
 *
 * LICENSE
 *
 * This source file is subject to the new BSD license that is bundled
 * with this package in the file LICENSE.
 * It is also available through the world-wide-web at this URL:
 * http://phergie.org/license
 *
 * @category  Phergie 
 * @package   Phergie_Plugin_Php
 * @author    Phergie Development Team <team@phergie.org>
 * @copyright 2008-2010 Phergie Development Team (http://phergie.org)
 * @license   http://phergie.org/license New BSD License
 * @link      http://pear.phergie.org/package/Phergie_Plugin_Php
 */

/**
 * Data source for {@see Phergie_Plugin_Php}. This source reads function 
 * descriptions from a file and stores them in a SQLite database. When a 
 * function description is requested, the function is retrieved from the 
 * local database.
 *
 * @category Phergie 
 * @package  Phergie_Plugin_Php
 * @author   Phergie Development Team <team@phergie.org>
 * @license  http://phergie.org/license New BSD License
 * @link     http://pear.phergie.org/package/Phergie_Plugin_Php
 * @uses     extension pdo 
 * @uses     extension pdo_sqlite 
 * @uses     Phergie_Plugin_Command pear.phergie.org
 */
class Phergie_Plugin_Php_Source_Local implements Phergie_Plugin_Php_Source
{
    /**
     * Local database for storage
     *
     * @var PDO 
     */
    protected $database;

    /**
     * Source of the PHP function summary
     *
     * @var string
     */
    protected $url = 'http://cvs.php.net/viewvc.cgi/phpdoc/funcsummary.txt?revision=HEAD';

    /**
     * Constructor to initialize the data source.
     *
     * @return void
     */
    public function __construct()
    {
        $path = dirname(__FILE__);

        try {
            $this->database = new PDO('sqlite:' . $path . '/functions.db');
            $this->buildDatabase();
            // @todo Modify this to be rethrown as an appropriate 
            //       Phergie_Plugin_Exception and handled in Phergie_Plugin_Php
        } catch (PDOException $e) {
            echo 'PDO failure: '.$e->getMessage();
        } 
    }

    /**
     * Searches for a description of the function.
     * 
     * @param string $function Search pattern to match against the function 
     *        name, wildcards supported using %
     *
     * @return array|null Associative array containing the function name and 
     *         description or NULL if no results are found
     */
    public function findFunction($function)
    {
        // Remove possible parentheses
        $split = preg_split('{\(|\)}', $function);
        $function = (count($split)) ? array_shift($split) : $function;

        // Prepare the database statement
        $stmt = $this->database->prepare('SELECT `name`, `description` FROM `functions` WHERE `name` LIKE :function');
        $stmt->execute(array(':function' => $function));

        // Check the results
        if (count($stmt) > 0) {
            $result = $stmt->fetch(PDO::FETCH_ASSOC);
            /**
             * @todo add class and function URLS
             * class methods: http://php.net/manual/en/classname.methodname.php
             * functions: http://php.net/manual/en/function.functionname.php
             * where '_' is replaced with '-'
             */
            return $result;
        }

        // No results found, return
        return null;
    }

    /**
     * Build the database and parses the function summary file into it.
     *
     * @param bool $rebuild TRUE to force a rebuild of the table used to 
     *        house function information, FALSE otherwise, defaults to FALSE
     *
     * @return void
     */
    protected function buildDatabase($rebuild = false)
    {
        // Check to see if the functions table exists
        $checkstmt = $this->database->query("SELECT COUNT(*) FROM `sqlite_master` WHERE `name` = 'functions'");
        $checkstmt->execute();
        $result = $checkstmt->fetch(PDO::FETCH_ASSOC);
        unset( $checkstmt );
        $table = $result['COUNT(*)'];
        unset( $result );
        // If the table doesn't exist, create it
        if (!$table) {
                $this->database->exec('CREATE TABLE `functions` (`name` VARCHAR(255), `description` TEXT)');
                $this->database->exec('CREATE UNIQUE INDEX `functions_name` ON `functions` (`name`)');
        }

        // If we created a new table, fill it with data
        if (!$table || $rebuild) {
            // Get the contents of the source file
            // @todo Handle possible error cases better here; the @ operator 
            //       shouldn't be needed
            $contents = @file($this->url, FILE_IGNORE_NEW_LINES + FILE_SKIP_EMPTY_LINES);

            if (!$contents) {
                return;
            }

            // Parse the contents
            $valid = array();
            $firstPart = '';
            $lineNumber = 0;
            foreach ($contents as $line) {
                // Clean the current line
                $line = trim($line);

                // Skip comment lines
                if (0 === strpos($line, '#')) {
                    // reset the line if the current line is odd
                    if (($lineNumber % 2) !== 0) {
                        $lineNumber--;
                    }
                    continue;
                }

                /*
                 * If the current line is even, it's the first part of the
                 * complete function description ...
                 */
                if (($lineNumber % 2) === 0) {
                    $firstPart = $line;
                } else {
                    // ... it's the last part of the complete function description
                    $completeLine = $firstPart . ' ' . $line;
                    $firstPart = '';
                    if (preg_match('{^([^\s]*)[\s]?([^)]*)\(([^\)]*)\)[\sU]+([\sa-zA-Z0-9\.,\-_()]*)$}', $completeLine, $matches)) {
                        $valid[] = $matches;
                    }
                }
                // Up the line number before going to the next line
                $lineNumber++;
            }
            // free up some memory
            unset($contents);

            // Process the valid matches
            if (count($valid) > 0) {
                // Clear the database
                $this->database->exec('DELETE * FROM `functions`');

                // Prepare the sql statement
                $stmt = $this->database->prepare('INSERT INTO `functions` (`name`, `description`) VALUES (:name, :description)');
                $this->database->beginTransaction();

                // Insert the data
                foreach ($valid as $function) {
                    // Extract function values
                    list( , $retval, $name, $params, $desc) = $function;
                    if (empty($name)) {
                        $name = $retval;
                        $retval = '';
                    }
                    // Reconstruct the complete function line
                    $line = trim($retval . ' ' . $name . '(' . $params . ') - ' . $desc);
                    // Execute the statement
                    $stmt->execute(array(':name' => $name, ':description' => $line));
                }
                
                // Commit the changes to the database
                $this->database->commit();
            }
            // free up some more memory
            unset($valid);
        }
    }
}
