<?php

namespace Blended\lang;

$config = parse_ini_file('config.ini');

include dirname(__FILE__) . '/Blended_functions.php';
include dirname(__FILE__) . '/Blended_filters.php';

class Blended_Tags extends \Twig_Extension
{
    /* ---  tags and macros added here  ----  */
    public function getTokenParsers()
    {
        $token_parsers = array(
            new Blended_ParentTokenParser(),
            new Blended_PrintTokenParser(),
            new Blended_Ifblock_TokenParser(),
            new Blended_MacroTokenParser(),
            new Blended_Autoescape_TokenParser()
        );
        return $token_parsers;
    }

    /* Blended filters added to the twig environment. */
    public function getFilters()
    {
        return array(
            new \Twig_SimpleFilter('e', 'blended_escape_filter', array(
                'needs_environment' => true,
                'is_safe_callback' => 'twig_escape_filter_is_safe'
            )),
            new \Twig_SimpleFilter('escape', 'blended_escape_filter', array(
                'needs_environment' => true,
                'is_safe_callback' => 'twig_escape_filter_is_safe'
            )),
            new \Twig_SimpleFilter('items', 'blended_items'),
            new \Twig_SimpleFilter('number', 'blended_number'),
            new \Twig_SimpleFilter('datetime', 'blended_datetime'),
            new \Twig_SimpleFilter('string', 'blended_string'),
            new \Twig_SimpleFilter('template', 'blended_template', array(
                'needs_environment' => true
            )),
            new \Twig_SimpleFilter('render', 'blended_render'),
            new \Twig_SimpleFilter('safe', 'blended_safe', array(
                'needs_environment' => true,
                'is_safe' => array(
                    'all'
                )
            ))

        );
    }

    /* Blended functions added to the twig environment. */
    public function getFunctions()
    {
        return array(
            new \Twig_SimpleFunction('first', 'blended_first'),
            new \Twig_SimpleFunction('series', 'blended_series'),
            new \Twig_SimpleFunction('intersection', 'blended_intersection'),
            new \Twig_SimpleFunction('union', 'blended_union'),
            new \Twig_SimpleFunction('complement', 'blended_complement'),
            new \Twig_SimpleFunction('product', 'blended_cartProd'),
            new \Twig_SimpleFunction('difference', 'blended_difference'),
            new \Twig_SimpleFunction('last', 'blended_last'),
            new \Twig_SimpleFunction('join', 'blended_join'),
            new \Twig_SimpleFunction('ceil', 'blended_ceil'),
            new \Twig_SimpleFunction('floor', 'blended_floor'),
            new \Twig_SimpleFunction('concat', 'blended_concat'),
            new \Twig_SimpleFunction('length', 'blended_length'),
            new \Twig_SimpleFunction('lower', 'blended_lower'),
            new \Twig_SimpleFunction('upper', 'blended_upper'),
            new \Twig_SimpleFunction('title', 'blended_title'),
            new \Twig_SimpleFunction('round', 'blended_round'),
            new \Twig_SimpleFunction('rgbcolor', 'blended_rgbcolor'),
            new \Twig_SimpleFunction('hexcolor', 'blended_hexcolor'),
            new \Twig_SimpleFunction('mean', 'blended_mean'),
            new \Twig_SimpleFunction('now', 'blended_now'),
            new \Twig_SimpleFunction('image', 'image'),
            new \Twig_SimpleFunction('hash', 'blended_hash'),
            new \Twig_SimpleFunction('get_block_data', 'blended_block_data'),
            new \Twig_SimpleFunction('commonmark', 'blended_common_mark', array('needs_environment' => true, 'is_safe_callback' => 'twig_escape_filter_is_safe'))
        );
    }

    public function getName()
    {
        return 'blended';
    }
}

/* A helper function for Blended_MacroTokenParser */
class Blended_Tree
{
    var $nodes;

    /***  Add a node   ***/

    public function AddNode($node, $owner)
    {
        $insert = "yes";
        $insert = $this->isUnique($node);
        if ($insert != "no") {
            $this->nodes[$node] = $owner;
        }
    }

    /***  Check if the node name is unique  ***/

    public function isUnique($nodename)
    {

        $UNIQUE = "yes";
        if (is_Array($this->nodes)) {
            while (list($key14, $value14) = each($this->nodes)) {
                if (trim($key14) == trim($nodename))
                    $UNIQUE = "no";
                return $UNIQUE;
            }
            $d = reset($this->nodes);
        }
    }

    /*** Return Children of a node ***/

    public function GetChild($owner)
    {
        $d      = reset($this->nodes);
        $m_list = array();
        while (list($key, $value) = each($this->nodes)) {
            if (trim($value) == trim($owner) && !empty($key))
            //echo "-" . $key . "</BR>";
                $m_list[] = $key;
        }
        return $m_list;
    }

    /***  Return the parent of a node ***/

    public function getFather($node)
    {
        $d = reset($this->nodes);
        while (list($key4, $value4) = each($this->nodes)) {
            if ($key4 == $node) {
                return $value4;
            }
        }
    }

}

/*Newly added parser method for autoescape tag
 * */
class Blended_Autoescape_TokenParser extends \Twig_TokenParser
{
    public function parse(\Twig_Token $token)
    {
        $lineno = $token->getLine();
        $stream = $this->parser->getStream();

        if ($stream->test(\Twig_Token::BLOCK_END_TYPE)) {
            $value = 'html';
        } else {
            $expr = $this->parser->getExpressionParser()->parseExpression();
            if (!$expr instanceof \Twig_Node_Expression_Constant) {
                throw new \Twig_Error_Syntax('An escaping strategy must be a string or a bool.', $stream->getCurrent()->getLine(), $stream->getSourceContext());
            }
            $value = $expr->getAttribute('value');

            $compat = true === $value || false === $value;

            if (true === $value) {
                $value = 'html';
            }

            if ($compat && $stream->test(\Twig_Token::NAME_TYPE)) {
                if (false === $value) {
                    throw new \Twig_Error_Syntax('Unexpected escaping strategy as you set autoescaping to false.', $stream->getCurrent()->getLine(), $stream->getSourceContext());
                }

                $value = $stream->next()->getValue();
            }
        }

        $stream->expect(\Twig_Token::BLOCK_END_TYPE);
        $body = $this->parser->subparse(array($this, 'decideBlockEnd'), true);
        $stream->expect(\Twig_Token::BLOCK_END_TYPE);

        return new \Twig_Node_AutoEscape($value, $body, $lineno, $this->getTag());
    }

    public function decideBlockEnd(\Twig_Token $token)
    {
        return $token->test('endautoescape');
    }

    public function getTag()
    {
        return 'autoescape';
    }
}

//---- Parent tag Defination to add in Twig Environment-----
class Blended_ParentTokenParser extends \Twig_TokenParser
{
    public function parse(\Twig_Token $token)
    {

        $stream = $this->parser->getStream();
        $stream->expect(\Twig_Token::BLOCK_END_TYPE);
        $line = $stream->getCurrent()->getLine();

        $parent_expr = new \Twig_Node_Expression_Parent($this->parser->peekBlockStack(), $line);
        return new \Twig_Node_Print($parent_expr, $line);
    }

    public function getTag()
    {
        return 'parent';
    }
}

//------ Print Tag Defination to add in Twig Environment ------
class Blended_PrintTokenParser extends \Twig_TokenParser
{

    public function parse(\Twig_Token $token)
    {

        $stream = $this->parser->getStream();
        $line   = $stream->getCurrent()->getLine();
        $expr   = $this->parser->getExpressionParser()->parseExpression();
        $stream->expect(\Twig_Token::BLOCK_END_TYPE);

        return new \Twig_Node_Print($expr, $line);
    }

    public function getTag()
    {
        return 'print';
    }
}

//------ Ifblock Tag Defination to add in Twig Environment ------
class Blended_Ifblock_TokenParser extends \Twig_TokenParser
{

    public function parse(\Twig_Token $token)
    {

        $stream = $this->parser->getStream();
        $lineno = $token->getLine();

        $params = array_merge(array (), $this->getInlineParams($token));
        $args_count = count($params);
        if($args_count == 1) {
            $exprs = $this->getTest($params[0], $lineno);
        }
        else {
            $exprs = $this->getOrExpr($params, $args_count-1, $lineno);
        }

        $body  = $this->parser->subparse(array(
            $this,
            'decideIfFork'
        ));
        $tests = array($exprs, $body);
        $else  = null;

        $token = $stream->next();
        if ($token->getValue() == 'else') {
            $stream->expect(\Twig_Token::BLOCK_END_TYPE);
            $else  = $this->parser->subparse(array(
                $this,
                'decideIfEnd'
            ));
            $token = $stream->next();
        }

        if ($token->getValue() != 'endifblock') {
            throw new \Twig_Error_Syntax(sprintf('Unexpected end of template. Twig was looking for the following tags "else", or "endifblock" to close the "ifblock" started at line %d).', $lineno), $stream->getCurrent()->getLine(), $stream->getFilename());
        }

        $stream->expect(\Twig_Token::BLOCK_END_TYPE);
        return new \Twig_Node_If(new \Twig_Node($tests), $else, $lineno, $this->getTag());

    }

    private function getOrExpr($params, $index, $lineno) {
        if($index == 0) {
            return $this->getTest($params[0], $lineno);
        }
        return new \Twig_Node_Expression_Binary_Or($this->getOrExpr($params, $index-1, $lineno), $this->getTest($params[$index], $lineno), $lineno);
    }

    private function getInlineParams(\Twig_Token $token)
    {
        $stream = $this->parser->getStream();
        $params = array ();
        while (!$stream->test(\Twig_Token::BLOCK_END_TYPE))
        {
            $name_token = $stream->expect(\Twig_Token::NAME_TYPE);
            array_push($params, $name_token->getValue());
        }
        $stream->expect(\Twig_Token::BLOCK_END_TYPE);
        return $params;
    }

    private function getTest($block_name, $lineno)
    {
        $const_name = new \Twig_Node_Expression_Constant($block_name, $lineno);
        $block_ref  = new \Twig_Node_Expression_BlockReference($const_name, null, $lineno);
        $flter_name = new \Twig_Node_Expression_Constant('trim', $lineno);
        $args       = new \Twig_Node(array());
        $filter     = new \Twig_Node_Expression_Filter($block_ref, $flter_name, $args, $lineno);
        return $filter;
    }

    public function decideIfFork(\Twig_Token $token)
    {
        return $token->test(array(
            'else',
            'endifblock'
        ));
    }

    public function decideIfEnd(\Twig_Token $token)
    {
        return $token->test(array(
            'endifblock'
        ));
    }

    public function getTag()
    {
        return 'ifblock';
    }
}

/* ------   Macro added to Twig Environment  --------*/
class Blended_MacroTokenParser extends \Twig_TokenParser
{
    // This is a mini-parser method to extract macro name in each parse.
    public function getMacroNames($parsed_token_list)
    {

        $parser_token_array = explode(")", $parsed_token_list);

        foreach ($parser_token_array as $parser_token_array_key => $parser_token_array_value) {
            if (strpos($parser_token_array_value, 'NAME_TYPE(macro')) {
                $macro_names_data[$parser_token_array_key] = $parser_token_array[$parser_token_array_key + 1];
            }
        }

        foreach ($macro_names_data as $macro_names_data_key => $macro_names_data_value) {
            if (strpos($macro_names_data_value, 'NAME_TYPE(')) {
                $macro_names[$macro_names_data_key] = trim(str_replace("NAME_TYPE(", "", str_replace("PUNCTUATION_TYPE((", "", $macro_names_data_value)));
            }
        }
        return $macro_names;
    }

    // Stream object is passed to this function to built a tree so that macro names can be taken from here for further import work.
    public function node_tree($parent = "ROOT", $macro_list)
    {

        $tree_object        = new Blended_Tree;
        $parser_token_array = explode(")", $macro_list);

        foreach ($parser_token_array as $parser_token_array_key => $parser_token_array_value) {
            if ((strstr($parser_token_array_value, 'NAME_TYPE(macro')) && !(strstr($parser_token_array_value, 'NAME_TYPE(macros'))) {

                $tree_object->AddNode(trim(str_replace("NAME_TYPE(", "", $parser_token_array[$parser_token_array_key + 1])), $parent);
                $parent = trim(str_replace("NAME_TYPE(", "", $parser_token_array[$parser_token_array_key + 1]));
                $father = $tree_object->getFather($parent);
            } else if ((strstr($parser_token_array_value, 'PUNCTUATION_TYPE(.')) && !(strstr($parser_token_array_value, 'PUNCTUATION_TYPE(.'))) {

                $tree_object->AddNode(trim(str_replace("NAME_TYPE(", "", $parser_token_array[$parser_token_array_key + 1])), $parent);
                $parent = trim(str_replace("NAME_TYPE(", "", $parser_token_array[$parser_token_array_key + 1]));
                $father = $tree_object->getFather($parent);
            } else if (strstr($parser_token_array_value, 'NAME_TYPE(endmacro')) {

                $parent = $father;
                $father = $tree_object->getFather($parent);
            }
        }

        return $tree_object;
    }

    // Once tree is build it's passed to this function for getting the siblings, childs nad parent macros.
    public function import_list($find_macros, $current_macro)
    {
        $data = array();
        if ($current_macro == "ROOT") {
            $macro_childs = $find_macros->GetChild($current_macro);
        } else {
            $data         = array_merge((array) $this->import_list($find_macros, $find_macros->getFather($current_macro)), $data);
            $data         = array_merge($data, (array) $find_macros->getFather($current_macro));
            $macro_childs = $find_macros->GetChild($current_macro);
        }

        if (($key = array_search("ROOT", $data)) !== false) {
            unset($data[$key]);
        }
        $data = array_unique(array_merge($data, $macro_childs));
        return $data;
    }

    public function parse(\Twig_Token $token)
    {
        $version = \Twig_Environment::VERSION;
        $version = explode('.', $version);
        $version = $version[0];
        $macro_func_prefix = (($version>1) ? 'macro_' : 'get');
        $lineno   = $token->getLine();
        $stream   = $this->parser->getStream();
        $name     = $stream->expect(\Twig_Token::NAME_TYPE)->getValue();
        $funcname = $macro_func_prefix . $name;

        /***** Call to node tree to bulid a macro tree *****/
        $macro_tree = $this->node_tree("ROOT", $stream . $token);

        /***** Building list of macros to be imported *****/
        $macro_names = $this->import_list($macro_tree, $name);
        /**********************************
        code from Twig's original 'From' import parser
        ***********************************/

        // first node to insert into the beginning of the macro body
        $macro            = new \Twig_Node_Expression_Name("_self", $lineno);
        $assign           = new \Twig_Node_Expression_AssignName($this->parser->getVarName(), $lineno);
        $import_node_body = new \Twig_Node_Import($macro, $assign, $lineno, "from");
        $var_node_body    = $import_node_body->getNode('var');
        //$this->parser->addImportedSymbol('function', $name, $funcname, $var_node_body);

        // Adding all macro names in each parse to ImportedSymbol list.
        foreach ($macro_names as $macro_name) {
            $this->parser->addImportedSymbol('function', $macro_name, $macro_func_prefix . $macro_name, $var_node_body);
        }

        // second node to return from this function
        $macro              = new \Twig_Node_Expression_Name("_self", $lineno);
        $assign             = new \Twig_Node_Expression_AssignName($this->parser->getVarName(), $lineno);
        $import_node_return = new \Twig_Node_Import($macro, $assign, $lineno, "from");

        /**********************************
        code from Twig's original 'Macro' parser
        ***********************************/

        $arguments = $this->parser->getExpressionParser()->parseArguments(true, true);

        $stream->expect(\Twig_Token::BLOCK_END_TYPE);
        $this->parser->pushLocalScope();
        $body = $this->parser->subparse(array(
            $this,
            'decideBlockEnd'
        ), true);
        if ($token = $stream->nextIf(\Twig_Token::NAME_TYPE)) {
            $value = $token->getValue();

            if ($value != $name) {
                throw new \Twig_Error_Syntax(sprintf("Expected endmacro for macro '$name' (but %s given)", $value), $stream->getCurrent()->getLine(), $stream->getFilename());
            }
        }
        $this->parser->popLocalScope();
        $stream->expect(\Twig_Token::BLOCK_END_TYPE);

        // we are inserting the import node right before the body of the macro
        $node_body  = new \Twig_Node_Body(array(
            $import_node_body,
            $body
        ));
        $tag        = $this->getTag();
        $node_macro = new \Twig_Node_Macro($name, $node_body, $arguments, $lineno, $tag);
        $this->parser->setMacro($name, $node_macro);

        /**********************************
        code from Twig's original 'From' import parser;
        for some reason it needs to be after the macro code
        ***********************************/

        $var_node_return = $import_node_return->getNode('var');
        $this->parser->addImportedSymbol('function', $name, $funcname, $var_node_return);
        foreach ($macro_names as $macro_name) {
            $this->parser->addImportedSymbol('function', $macro_name, $macro_func_prefix . $macro_name, $var_node_return);
        }

        return $import_node_return;
    }

    public function decideBlockEnd(\Twig_Token $token)
    {
        return $token->test('endmacro');
    }

    public function getTag()
    {
        return 'macro';
    }
}
