<?
/**
 * 1kLOC Templater v0.1
 * (c) 2009 by Benny Baumann (BenBE)
 *
 * @author Benny Baumann
 * @copyright (c) 2009
 * @licence GPLv3
 * @version 0.1
 */

class TTPLStyler {
    /**
     * The location to look for templates
     */
    private $dir;

    public function __construct($options = array('dir' => 'tpl/')) {
        $this->dir = $options['dir'];
    }

    public function getTPLUID($name) {
        return sha1($this->dir . $name);
    }

    public function getTPLData($name) {
        return file_get_contents($this->dir . $name);
    }

    public function getTPLTime($name) {
        return filemtime($this->dir . $name);
    }
}

class TTPLParser {
    /**
     * The special char sequence used to start a special block in the template
     */
    private $parserBlockStart = '<!--#';

    /**
     * The special char sequence used to end a special block in the template
     */
    private $parserBlockEnd = '#-->';

    /**
     * The char or sequence used to markup the end of a block tag
     */
    private $parserTagCloser = '/';

    public function parse($data, $tokenCB) {
        if (!is_callable($tokenCB)) {
            return false;
        } elseif(!is_string($data)) {
            return false;
        }

        $VarRegexp = "/{\\\$(\w+(?::\w+)*)?\}/i";
        $BlockRegexp = "/" . preg_quote($this->parserBlockStart, '/') .
            "(" . preg_quote($this->parserTagCloser, '/') . ")?" .
            "(\w+)" . "((?:\s+\w+(?:=\"[^\"\\n\\x00-\\x1F]*\")?)*)" .
            "\s*(" . preg_quote($this->parserTagCloser, '/') . ")?" .
            preg_quote($this->parserBlockEnd, '/') . "/i";

        $l = strlen($data);
        $p = 0;

        $m = array('b' => - 1, 'v' => -1);
        $ml = array('b' => 0, 'v' => 0);
        $mf = - 1;
        while ($p < $l) {
            // Need to perform position updates
            if ($mf < $p) {
                if ($m['b'] < $p) {
                    $ub = preg_match($BlockRegexp, $data, $ubm, PREG_OFFSET_CAPTURE, $p);
                    list($m['b'], $ml['b']) = $ub ? array($ubm[0][1], $ubm) : array($l, 0);
                }
                if ($m['v'] < $p) {
                    $uv = preg_match($VarRegexp, $data, $uvm, PREG_OFFSET_CAPTURE, $p);
                    list($m['v'], $ml['v']) = $uv ? array($uvm[0][1], $uvm) : array($l, 0);
                }
                $mf = $m['b'] < $m['v'] ? $m['b'] : $m['v'];
            }
            // Process any remaining text tokens
            if ($mf > $p) {
                call_user_func($tokenCB, array('offset' => $p, 'type' => 'text', 'data' => substr($data, $p, $mf - $p)));
                $p = $mf;
            }
            // Process markup for variables and blocks
            if ($m['v'] < $m['b'] && is_array($ml['v'])) {
                // Got a variable to match
                if (!empty($ml['v'][1][0])) {
                    call_user_func($tokenCB, array('offset' => $p, 'type' => 'var', 'data' => $ml['v'][0][0], 'name' => $ml['v'][1][0]));
                } else {
                    call_user_func($tokenCB, array('offset' => $p, 'type' => 'text', 'data' => '$'));
                }
                $p += strlen($ml['v'][0][0]);
            } elseif ($m['b'] < $m['v'] && is_array($ml['b'])) {
                // Got a block tag to match
                $start = empty($ml['b'][1][0]);
                $stop = !empty($ml['b'][4][0]);
                $tag = $ml['b'][2][0];
                $param = trim($ml['b'][3][0]);

                if ($start && $stop) {
                    // Non-block tag
                    call_user_func($tokenCB, array('offset' => $p, 'type' => 'tag', 'data' => $ml['b'][0][0], 'tag' => $tag, 'param' => $param));
                } elseif ($start && !$stop) {
                    // block tag (start)
                    call_user_func($tokenCB, array('offset' => $p, 'type' => 'block_start', 'data' => $ml['b'][0][0], 'tag' => $tag, 'param' => $param));
                } elseif (!$start && !$stop) {
                    // block tag (start)
                    call_user_func($tokenCB, array('offset' => $p, 'type' => 'block_stop', 'data' => $ml['b'][0][0], 'tag' => $tag, 'param' => $param));
                } else {
                    trigger_error('Syntax error in Template: Non-block end of tag', E_USER_WARNING);
                    return false;
                }
                $p += strlen($ml['b'][0][0]);
            } elseif ($mf >= $l) {
                // Ignore this case; we reached EOF
            } else {
                trigger_error('BUG!!! Internal Parser Error: This shouldn\'t ever happen!!!', E_USER_ERROR);
                return false;
            }
        }
        return true;
    }
}

class TTPLCompiler {
    private $parser;
    private $parseTree;
    private $parseStack;
    private $compileOut;
    private $contextStack = array();

    public function __construct(TTPLParser $parser = null) {
        $this->parser = is_object($parser) ? $parser : new TTPLParser();
    }

    public function compile($tpldata) {
        // Do a syntax check on the template
        if (!$this->parse($tpldata)) {
            return false;
        }

        $this->compileOut = '';
        $this->compileOut .= "<?php\n";

        $res = TTPLCompiler_Block::getCode(array($this, '_addCode'), null, $this->parseTree);
        if(!$res) {
            return false;
        }

        $this->compileOut .= "?>\n";

        return $this->compileOut;
    }

    protected function parse($tpldata) {
        $this->parseTree = array('type' => 'block', 'name' => '', 'sub' => array());
        $this->parseStack = array();

        return $this->parser->parse($tpldata, array($this, '_addToken')) && !count($this->parseStack);
    }

    public function _addToken($token) {
        $ct = &$this->parseTree;
        foreach($this->parseStack as $si) {
            $ct = &$ct['sub'][$si];
        }

        if ('text' == $token['type']) {
            $ct['sub'][] = $token;
        } elseif ('var' == $token['type']) {
            $ct['sub'][] = $token;
        } elseif ('block_start' == $token['type']) {
            $si = count($ct['sub']);
            $token['type'] = 'block';
            $ct['sub'][] = $token;
            array_push($this->parseStack, $si);
        } elseif ('block_stop' == $token['type']) {
            if ($ct['tag'] != $token['tag']) {
                // Block Token Mismatch!
                trigger_error('Block Token mismatch!', E_USER_ERROR);
                return false;
            }
            if (!count($this->parseStack)) {
                trigger_error('No opening tag for closer!', E_USER_ERROR);
                return false;
            }
            array_pop($this->parseStack);
        } else {
            trigger_error('Invalid Token Type!', E_USER_ERROR);
            return false;
        }

        return true;
    }
    public function _addCode($code) {
        $this->compileOut .= $code;
    }
    private function _pushExecCtx($tpl, $context, $out) {
        array_push($this->contextStack, array($tpl, $context, $out));
    }
    private function _popExecCtx() {
        return array_pop($this->contextStack);
    }
    private function _pushCaptureHook() {
        return array(new TTPLCompiler_StringBuffer(), 'add');
    }
    private function _popCaptureHook($hook) {
        return $hook[0]->data;
    }
    public function execFile($name, $tpl, $context, $out) {
        $subcontext = null;
        include($name);
        if(null !== $subcontext) {
            trigger_error('Subcontext Cookie mismatch!', E_USER_ERROR);
        }
    }
    public function execMem($src, $tpl, $context, $out) {
        $subcontext = null;
        eval($src);
        if(null !== $subcontext) {
            trigger_error('Subcontext Cookie mismatch!', E_USER_ERROR);
        }
    }
}

class TTPLCompiler_StringBuffer {
    public $data = '';
    public function add($string) {
        $this->data .= $string;
    }
}

class TTPLCompiler_Text {
    public static function getCode($outhook, array $parent = null, $entry) {
        call_user_func($outhook, "call_user_func(\$out, base64_decode(\"" . base64_encode($entry['data']) . "\"));\n");
        return true;
    }
}

class TTPLCompiler_Var extends TTPLCompiler_Text {
    public static function getCode($outhook, array $parent = null, $entry) {
        call_user_func($outhook, "call_user_func(\$out, \$context->getVarData(base64_decode('".base64_encode($entry['name'])."')));\n");
        return true;
    }
}

class TTPLCompiler_Tag extends TTPLCompiler_Text {
    public static function getTagParam($entry, $name, $index = null) {
        if(null!==$index) {
            $a = self::getTagParam($entry, $name);
            return isset($a[$index]) ? $a[$index] : '';
        }
        if('' == $name) {
            preg_match_all("/(?:^|\s+)(\w+)(?:=\"([^\"\\n\\x00-\\x1F]*)\")?(?=$|\s)/", $entry['param'], $m, PREG_SET_ORDER);
            $a = array();
            //var_dump(debug_backtrace());
            foreach($m as $e) {
                $a[] = array($e[1], $e[2]);
            }
            return $a;
        }
        $name = strtolower($name);
        $m = self::getTagParam($entry, '');
        $a = array();
        foreach($m as $e) {
            if(strtolower($e[0]) == $name) {
                $a[] = $e[1];
            }
        }
        return $a;
    }

    public static function getContextClass($entry) {
        if ('text' == $entry['type']) {
            return 'TTPLCompiler_Text';
        } elseif ('var' == $entry['type']) {
            return 'TTPLCompiler_Var';
        } elseif ('block' == $entry['type']) {
            return 'TTPLCompiler_Block' . $entry['tag'];
        } elseif ('tag' == $entry['type']) {
            return 'TTPLCompiler_Tag' . $entry['tag'];
        } else {
            return false;
        }
    }

    public static function getCode($outhook, array $parent = null, $entry) {
        if (!is_array($entry)) {
            return false; //Invalid context!
        }
        if (!isset($entry['type'])) {
            return false;
        }
        return true;
    }
}

class TTPLCompiler_Block extends TTPLCompiler_Tag {
    public static function getCode($outhook, array $parent = null, $entry) {
        if (!is_array($entry)) {
            return false; //Invalid context!
        }

        call_user_func($outhook, "{\n");

        if (isset($entry['type'])) {
            list($entry, $parent) = array($entry['sub'], $entry);
        }

        // We are in a list of contexts ...
        foreach($entry as $sub) {
            $subclass = self::getContextClass($sub);
            if (false === $subclass || !class_exists($subclass)) {
                // Unknown Context class
                return false;
            } elseif (!call_user_func(array($subclass, 'getCode'), $outhook, $parent, $sub)) {
                return false;
            }
        }

        call_user_func($outhook, "}\n");

        return true;
    }
}

class TTPLCompiler_BlockIf extends TTPLCompiler_Block {
    protected static function genCondition($entry) {
        if('' != ($var = self::getTagParam($entry, 'isset', 0))) {
            return "\$context->issetVar(base64_decode('".base64_encode($var)."'))";
        } elseif('' != ($var = self::getTagParam($entry, 'count', 0))) {
            return "\$context->getVarCount(base64_decode('".base64_encode($var)."'))";
        } elseif('' != ($var = self::getTagParam($entry, 'odd', 0))) {
            return "\$context->getVarPos(base64_decode('".base64_encode($var)."')) % 2";
        } elseif('' != ($var = self::getTagParam($entry, 'even', 0))) {
            return "!(\$context->getVarPos(base64_decode('".base64_encode($var)."')) % 2)";
        }
        return "true";
    }

    public static function getCode($outhook, array $parent = null, $entry) {
        call_user_func($outhook, "if(".self::genCondition($entry).")\n");
        return parent::getCode($outhook, $parent, $entry);
    }
}

class TTPLCompiler_TagElse extends TTPLCompiler_Block {
    public static function getCode($outhook, array $parent = null, $entry) {
        if(!$parent || !is_array($parent) || 'block' != $parent['type'] ||
            'if' != $parent['tag'] || isset($parent['elsepresent'])) {
            return false;
        }

        call_user_func($outhook, "} else {\n");
        $parent['elsepresent'] = true;

        return true;
    }
}

class TTPLCompiler_TagElseIf extends TTPLCompiler_BlockIf {
    public static function getCode($outhook, array $parent = null, $entry) {
        if(!$parent || !is_array($parent) || 'block' != $parent['type'] ||
            'if' != $parent['tag'] || isset($parent['elsepresent'])) {
            return false;
        }

        call_user_func($outhook, "} elseif(".self::genCondition($entry).") {\n");

        return true;
    }
}

class TTPLCompiler_BlockTPL extends TTPLCompiler_Block {
    public static function getCode($outhook, array $parent = null, $entry) {
        if (!is_array($entry) || !isset($entry['type'])) {
            return false; //Invalid context!
        }

        call_user_func($outhook, "\$this->_pushExecCtx(\$tpl, \$subcontext, \$out);\n");
        call_user_func($outhook, "\$subcontext = \$tpl->getSubtemplate(base64_decode('".base64_encode(self::getTagParam($entry, 'name', 0))."'));\n");
        call_user_func($outhook, "\$this->_pushExecCtx(\$tpl, \$context, \$out);\n");
        call_user_func($outhook, "{\n");

        // We are in a blocklevel statement ... Do some default workings ...
        foreach($entry['sub'] as $sub) {
            $subclass = self::getContextClass($sub);
            if (false === $subclass || !class_exists($subclass)) {
                // Unknown Context class
                return false;
            } elseif ('text' == $sub['type']) {
                if('' != trim($sub['data'])) {
                    return false;
                }
            } elseif (('block' == $sub['type']) && ('tplparam' != strtolower($sub['tag']))) {
                return false;
            } elseif (!call_user_func(array($subclass, 'getCode'), $outhook, $entry, $sub)) {
                return false;
            }
        }

        call_user_func($outhook, "}\n");
        call_user_func($outhook, "list(\$tpl, \$context, \$out) = \$this->_popExecCtx();\n");
        call_user_func($outhook, "\$subcontext->aliasContext(\$context);\n");
        call_user_func($outhook, "\$subcontext->render(\$subcontext, \$out);\n");
        call_user_func($outhook, "list(\$tpl, \$subcontext, \$out) = \$this->_popExecCtx();\n");

        return true;
    }
}

class TTPLCompiler_BlockTPLParam extends TTPLCompiler_Block {
    public static function getCode($outhook, array $parent = null, $entry) {
        if(!$parent || !is_array($parent) || 'block' != $parent['type'] ||
            'tpl' != strtolower($parent['tag'])) {
            return false;
        }

        call_user_func($outhook, "\$out = \$this->_pushCaptureHook();\n");
        $res = parent::getCode($outhook, $parent, $entry);
        call_user_func($outhook, "\$subcontext->assignVar(base64_decode('".base64_encode(self::getTagParam($entry, 'name', 0))."'), \$this->_popCaptureHook(\$out));\n");
        return $res;
    }
}

class TTPLCompiler_BlockFor extends TTPLCompiler_Block {
    public static function getCode($outhook, array $parent = null, $entry) {
        if (!is_array($entry) || !isset($entry['type'])) {
            return false; //Invalid context!
        }
        $VarName = "base64_decode('".base64_encode(self::getTagParam($entry, 'name', 0))."')";
        call_user_func($outhook, "\$context->resetNext($VarName);\n");
        call_user_func($outhook, "while (\$context->hasNext($VarName)) {\n");
        $res = parent::getCode($outhook, $parent, $entry);
        call_user_func($outhook, "\$context->doNext($VarName);\n");
        call_user_func($outhook, "}\n");
        return $res;
    }
}

class TTPLCache {
    private $dir;

    public function __construct($options = array('dir' => 'cache/')) {
        $this->dir = $options['dir'];
    }

    public function isCurrent($key, $mtime) {
        return file_exists($this->getFileName($key)) &&
            filemtime($this->getFileName($key)) > $mtime;
    }

    public function store($key, $content) {
        $file = $this->getFilename($key);
        return file_put_contents($file, $content);
    }

    public function getFileName($key) {
        return $this->dir . $key . '.php';
    }

    public function getFileContents($key) {
        return file_get_contents($this->getFileName($key));
    }
}

interface ITPLContext {
    function clear();
    function aliasContext(ITPLContext $context);
    function assignVar($Name, $Value);
    function assignBlock($Name, array $Value);
    function issetVar($Name);
    function getVarData($Name);
    function getVarPos($Name);
    function getVarCount($Name);
    function resetNext($Name);
    function hasNext($Name);
    function doNext($Name);
}

class TTPLContext implements ITPLContext {
    protected $data = array();
    protected $alias = null;
    public function clear() {
        $this->data = array();
    }
    public function aliasContext(ITPLContext $context) {
        return $this->alias = $context;
    }
    protected function isRemoteContext($Ctx) {
        return is_object($Ctx) && is_subclass_of($Ctx, 'ITPLContext');
    }
    protected function &getContext($Name, $block = false) {
        $Ctx =& $this->data;
        if(empty($Name)) {
            return $Ctx;
        }
        $Parts = explode(':', $Name);
        foreach($Parts as $P) {
            $CtxVar =& $Ctx[$P];
            $Ctx =& $CtxVar['data'][$CtxVar['pos']];
            if($this->isRemoteContext($Ctx)) {
                break;
            }
        }
        return $Ctx;
    }
    protected function getLatestContextName($Name) {
        $Parts = explode(':', $Name);
        array_pop($Parts);
        $a = array();
        foreach($Parts as $P) {
            array_push($a, $P);
            $CtxVar =& $Ctx[$P];
            $Ctx =& $CtxVar['data'][$CtxVar['pos']];
            if($this->isRemoteContext($Ctx)) {
                break;
            }
        }
        return implode(':', $a);
    }
    protected function splitVarName($Name) {
        $CtxName = $this->getLatestContextName($Name);
        $VarName = '' == $CtxName ? $Name : substr($Name, strlen($CtxName) + 1);
        return array($CtxName, $VarName);
    }
    public function assignVar($Name, $Value) {
        if(is_array($Value) && count($Value)) {
            return $this->assignBlock($Name, $Value);
        }
        list($CtxName, $VarName) = $this->splitVarName($Name);
        $Ctx =& $this->getContext($CtxName, true);
        if(is_array($Ctx)) {
            if(!isset($Ctx[$VarName])) {
                $Ctx[$VarName] = array('pos' => -1, 'data' => array());
            }
            $Ctx[$VarName]['data'][] = $Value;
            $Ctx[$VarName]['pos']++;
        } elseif($this->isRemoteContext($Ctx)) {
            return $Ctx->assignVar($VarName, $Value);
        }
        return true;
    }
    public function assignBlock($Name, array $Value) {
        $this->assignVar($Name, array());
        foreach($Value as $K => $V) {
            $this->assignVar("$Name:$K", $V);
        }
        return true;
    }
    public function issetVar($Name) {
        return null !== $this->getVarData($Name);
    }
    public function getVarData($Name) {
        list($CtxName, $VarName) = $this->splitVarName($Name);
        $Ctx = $this->getContext($CtxName, true);
        if(is_array($Ctx) && isset($Ctx[$VarName])) {
            return $Ctx[$VarName]['data'][$Ctx[$VarName]['pos']];
        } elseif($this->isRemoteContext($Ctx)) {
            return $Ctx->getVarData($VarName);
        }
        return $this->alias ? $this->alias->getVarData($Name) : null;
    }
    public function getVarPos($Name) {
        list($CtxName, $VarName) = $this->splitVarName($Name);
        $Ctx = $this->getContext($CtxName, true);
        if(is_array($Ctx) && isset($Ctx[$VarName])) {
            return $Ctx[$VarName]['pos'];
        } elseif($this->isRemoteContext($Ctx)) {
            return $Ctx->getVarPos($VarName);
        }
        return $this->alias ? $this->alias->getVarPos($Name) : 0;
    }
    public function getVarCount($Name) {
        list($CtxName, $VarName) = $this->splitVarName($Name);
        $Ctx = $this->getContext($CtxName, true);
        if(is_array($Ctx) && isset($Ctx[$VarName])) {
            return count($Ctx[$VarName]['data']);
        } elseif($this->isRemoteContext($Ctx)) {
            return $Ctx->getVarCount($VarName);
        }
        return $this->alias ? $this->alias->getVarCount($Name) : 0;
    }
    public function resetNext($Name) {
        list($CtxName, $VarName) = $this->splitVarName($Name);
        $Ctx =& $this->getContext($CtxName, true);
        if(is_array($Ctx) && isset($Ctx[$VarName])) {
            $Ctx[$VarName]['pos'] = 0;
        } elseif($this->isRemoteContext($Ctx)) {
            $Ctx->resetNext($VarName);
        } elseif ($this->alias) {
            $this->alias->resetNext($Name);
        }
    }
    public function hasNext($Name) {
        list($CtxName, $VarName) = $this->splitVarName($Name);
        $Ctx =& $this->getContext($CtxName, true);
        if(is_array($Ctx) && isset($Ctx[$VarName])) {
            return $Ctx[$VarName]['pos'] < count($Ctx[$VarName]['data']);
        } elseif($this->isRemoteContext($Ctx)) {
            return $Ctx->hasNext($VarName);
        }
        return $this->alias ? $this->alias->hasNext($Name) : false;
    }
    public function doNext($Name) {
        list($CtxName, $VarName) = $this->splitVarName($Name);
        $Ctx =& $this->getContext($CtxName, true);
        if(is_array($Ctx) && isset($Ctx[$VarName])) {
            $Ctx[$VarName]['pos']++;
        } elseif($this->isRemoteContext($Ctx)) {
            $Ctx->doNext($VarName);
        } elseif ($this->alias) {
            $this->alias->doNext($Name);
        }
    }
}

class TTemplate implements ITPLContext {
    private $name;
    private $templater;
    private $styler;
    private $cache;
    private $compiler;
    private $forceCompile;
    private $context;

    public function __construct(TTemplater $templater, $name, TTPLStyler $styler = null, TTPLCache $cache = null, TTPLCompiler $compiler = null, $forceCompile = false) {
        $this->name = $name;
        $this->templater = $templater;
        $this->styler = $styler;
        $this->cache = $cache;
        $this->compiler = $compiler;
        $this->forceCompile = $forceCompile;
        $this->context = new TTPLContext();
    }

    public function _echo($data) {
        echo $data;
    }

    public function render(ITPLContext $context = null, $outputHook = null) {
        if (null == $context) {
            $context = $this->context;
        }

        if (!is_callable($outputHook)) {
            $outputHook = array($this, '_echo');
        }

        $tpl_guid = $this->styler->getTPLUID($this->name);
        if (!$this->cache->isCurrent($tpl_guid, $this->styler->getTPLTime($this->name))) {
            $tpl_src = $this->styler->getTPLData($this->name);
            $tpl_bin = $this->compiler->compile($tpl_src);
            if (false !== $tpl_bin) {
                $this->cache->store($tpl_guid, $tpl_bin);
            } else {
                return false;
            }
        }

        if ($filename = $this->cache->getFileName($tpl_guid)) {
            $res = $this->compiler->execFile($filename, $this, $context, $outputHook);
        } else {
            $res = $this->compiler->execMem($this->cache->getFileContents($tpl_guid), $this, $context, $outputHook);
        }

        return $res;
    }

    public function clear() {
        return $this->context->clear();
    }
    public function aliasContext(ITPLContext $context) {
        return $this->context->aliasContext($context);
    }
    public function assignVar($Name, $Value) {
        return $this->context->assignVar($Name, $Value);
    }
    public function assignBlock($Name, array $Value) {
        return $this->context->assignBlock($Name, $Value);
    }
    public function issetVar($Name) {
        return $this->context->issetVar($Name);
    }
    public function getVarData($Name) {
        return $this->context->getVarData($Name);
    }
    public function getVarPos($Name) {
        return $this->context->getVarPos($Name);
    }
    public function getVarCount($Name) {
        return $this->context->getVarCount($Name);
    }
    public function resetNext($Name) {
        return $this->context->hasNext($Name);
    }
    public function hasNext($Name) {
        return $this->context->hasNext($Name);
    }
    public function doNext($Name) {
        return $this->context->doNext($Name);
    }

    public function getSubtemplate($name) {
        return $this->templater->LoadTemplate($name, $this->styler, $this->cache, $this->compiler, $this->forceCompile);
    }
    public function __toString() {
        $cb = array(new TTPLCompiler_StringBuffer(), 'add');
        $this->render($this->context, $cb);
        return $cb[1]->data;
    }
}

class TTemplater {
    /**
     * The list of template objects known to this templater
     */
    private $templates = array();

    /**
     * The template contexts known to this templater
     */
    private $contexts = array();

    /**
     * File System lock for writing compiled cache files
     */
    private $compilerLock = false;

    public function __construct(TTPLStyler $styler = null, TTPLCache $cache = null, TTPLCompiler $compiler = null) {
        $this->styler = is_object($styler) ? $styler : new TTPLStyler();
        $this->cache = is_object($cache) ? $cache : new TTPLCache();
        $this->compiler = is_object($compiler) ? $compiler : new TTPLCompiler();
    }

    public function Init() {
        $this->InitLock();
    }

    public function Free() {
        $this->FreeLock();
    }

    /**
     * TTemplater::InitLock()
     *
     * @param mixed $p
     * @return
     */
    public function InitLock() {
        $this->compilerLock = fopen($this->tplCache . '!tplCompile.lock', 'a');
        if (!is_resource($this->compilerLock)) {
            return false;
        }
        // Add checks to whether we could lock the file here as necessary
        return true;
    }

    /**
     * TTemplater::FreeLock()
     *
     * @param mixed $p
     * @return
     */
    public function FreeLock() {
        if (!is_resource($this->compilerLock)) {
            return true;
        }

        $this->CompileLock_Release();
        fclose($this->compilerLock);
    }

    /**
     * Wait to get the compiler lock
     */
    private function CompileLock_Acquire() {
        if (!is_resource($this->compilerLock)) {
            return false;
        }
        while (!flock($this->compilerLock, LOCK_EX)) {
            // If lock not obtained sleep for 0 - 100 milliseconds, to avoid collision and CPU load
            usleep(round(rand(0, 100) * 1000));
        }

        return true;
    }

    /**
     * Wait to get the compiler lock for a given time at most
     */
    private function CompileLock_AcquireTimeout($timeout = 5000) {
        if (!is_resource($this->compilerLock)) {
            return false;
        }

        $startTime = microtime();
        do {
            $canWrite = flock($fp, LOCK_EX);

            if (!$canWrite) usleep(round(rand(0, 100) * 1000));
        } while (!$canWrite and ((microtime() - $startTime) < $timeout));

        return $canWrite;
    }

    /**
     * Release the compiler lock
     */
    private function CompileLock_Release() {
        if (!is_resource($this->compilerLock)) {
            return false;
        }

        return flock($this->compilerLock, LOCK_UN);
    }

    /**
     * Return whether the compiler lock is locked or available
     */
    private function CompileLock_GetState() {
        $check = false;
        flock($this->compilerLock, LOCK_EX, $check);
        return $check;
    }

    public function LoadTemplate($name, TTPLStyler $styler = null, TTPLCache $cache = null, TTPLCompiler $compiler = null, $forceCompile = false) {
        $styler = is_object($styler) ? $styler : $this->styler;
        $cache = is_object($cache) ? $cache : $this->cache;
        $compiler = is_object($compiler) ? $compiler : $this->compiler;
        return $tpl = new TTemplate($this, $name, $styler, $cache, $compiler);
    }
}

?>