<?php

    /**
     * @author David J. Malan <dmalan@harvard.edu>
     * @link http://wiki.cs50.net/render50
     * @package render50 
     * @version 0.93
     *
     * GNU General Public License, version 2
     * http://www.gnu.org/licenses/gpl-2.0.html
     */

    // report all errors
    error_reporting(E_ALL);
    ini_set("display_errors", true);

    // GeSHi
    require_once(dirname(__FILE__) . "/../lib/geshi/geshi.php");
     
    // mPDF
    require_once(dirname(__FILE__) . "/../lib/mpdf50/mpdf.php");

    // override mPDF's error handling
    class _mPDF extends mPDF
    {
        function Error($msg)
        {
            die("Error: $msg\n");
        }
    }

    // ensure proper usage
    if ($argc < 2 || in_array("-h", $argv) || in_array("--help", $argv))
        die("Usage: php /path/to/render50.php output [inputs] [-i includes] [-x excludes]\n");

    // filename for output
    $output = preg_match("/\.pdf$/i", $argv[1]) ? $argv[1] : $argv[1] . ".pdf";

    // check for includes
    for ($i = 2; $i < $argc; $i++)
    {
        if ($argv[$i] == "-i")
            $includes = array();
        else if ($argv[$i][0] == "-" && isset($includes))
            break;
        else if (isset($includes))
            array_push($includes, str_replace("*", ".*", preg_replace("/({|}|\||\.)/", '\\\$1', $argv[$i])));
    }
    if (isset($includes)) 
        $includes = "{^" . join("|", $includes) . "$}";

    // check for excludes
    for ($i = 2; $i < $argc; $i++)
    {
        if ($argv[$i] == "-x")
            $excludes = array();
        else if ($argv[$i][0] == "-" && isset($excludes))
            break;
        else if (isset($excludes))
            array_push($excludes, str_replace("*", ".*", preg_replace("/({|}|\||\.)/", '\\\$1', $argv[$i])));
    }
    if (isset($excludes)) 
        $excludes = "{^" . join("|", $excludes) . "$}";

    // check STDIN else command line for inputs
    if ($argc == 2 || ($argc > 2 && $argv[2][0] == "-"))
    {
        echo "Taking inputs from STDIN, one per line...  Hit Ctrl-D when done else Ctrl-C to cancel.\n";
        $patterns = file("php://stdin", FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
    }
    else
    {
        $patterns = array();
        for ($i = 2; $i < $argc; $i++)
        {
            if ($argv[$i][0] == "-")
                break;
            array_push($patterns, $argv[$i]);
        }
    }

    // glob patterns
    $inputs = array();
    foreach ($patterns as $pattern)
        $inputs = array_merge($inputs, glob($pattern, GLOB_BRACE));

    // files to render
    $files = array();

    // directories into which to descend
    $directories = array();

    // parse command line for files and directories
    foreach ($inputs as $input)
    {
        // ensure file (or directory) exists
        if (!file_exists($input))
            die("File does not exist: {$input}\n");

        // ensure file (or directory) is readable
        if (!is_readable($input))
            die("File cannot be read: {$input}\n");

        // push file or directory onto array
        if (is_dir($input))
            array_push($directories, $input);
        else
            array_push($files, $input);
    }

    // descend into directories
    while (count($directories) > 0)
    {
        $directory = array_pop($directories);
        foreach (scandir($directory) as $child)
        {
            // ignore . and ..
            if ($child == "." || $child == "..")
                continue;
    
            // prepare path
            $path = rtrim($directory, "/") . DIRECTORY_SEPARATOR . $child;

            // push path onto array
            if (is_dir($path))
                array_push($directories, $path);
            else
                array_push($files, $path);
        }
    }

    // create PDF
    $mpdf = new _mPDF("c", "Letter-L", 8);

    // create highlighter
    $geshi = new GeSHi();
    $geshi->enable_classes();
    $geshi->enable_keyword_links(false);
    $geshi->enable_line_numbers(GESHI_NORMAL_LINE_NUMBERS);
    $geshi->set_header_type(GESHI_HEADER_DIV);
    $geshi->set_tab_width(4);
    
    // prepare to count pages
    $pages = 0;

    // iterate over files
    foreach ($files as $file)
    {
        // decide whether to exclude
        if (isset($includes) && !preg_match($includes, $file) || (isset($excludes) && preg_match($excludes, $file)))
            continue;

        // infer language
        $language = $geshi->get_language_name_from_extension(pathinfo($file, PATHINFO_EXTENSION));

        // ignore unknown languages
        if (!$language)
        {
            // whitelist Makefile, README, and *.txt
            if (preg_match("/^(?:Makefile|README|.*\.txt)$/i", pathinfo($file, PATHINFO_FILENAME)))
                $language = "txt";
            else
                continue;
        }

        // load file
        echo "Rendering $file...\n";
        $geshi->set_source(file_get_contents($file));
        $geshi->set_language($language, true);

        // set header
        $properties = array(
         "border-bottom: 1px solid #808080",
         "color: #808080",
         "font-family: monospace",
         "text-align: right"
        );
        $mpdf->SetHTMLHeader("<div style='" . join(";", $properties) . "'>" . htmlspecialchars($file) . "</div>");

        // generate HTML
        $html = 
         "<html>" .
         "<head><style type='text/css'>\n<!--\n" . ".txt { font-family: monospace; }\n" . $geshi->get_stylesheet() . "\n-->\n</style></head>" .
         "<body>" . $geshi->parse_code() . "</body>" .
         "</html>";

print $html;

        // add file to PDF
        $mpdf->AddPage();
        $mpdf->Bookmark($file);
        $mpdf->WriteHTML($html);
        $pages++;
    }

    // output PDF
    if ($pages > 0)
    {
        @$mpdf->Output($output, "F");
        echo "PDF saved as $output.\n";
    }
    else
        echo "Nothing to render.\n";

?>