<?php
/*
 * @package     Parsing
 * @author      Frank Wikström <frank@mossadal.se>
 * @copyright   2015 Frank Wikström
 * @license     http://www.opensource.org/licenses/lgpl-license.php LGPL
 *
 */

/**
 * @namespace MathParser::Parsing::Nodes
 *
 * Node classes for use in the generated abstract syntax trees (AST).
 */
namespace MathParser\Parsing\Nodes;

use MathParser\Interpreting\ASCIIPrinter;
use MathParser\Interpreting\Evaluator;
use MathParser\Interpreting\Visitors\Visitable;
use MathParser\Lexing\Token;
use MathParser\Lexing\TokenType;

/**
 * Abstract base class for nodes in the abstract syntax tree
 * generated by the Parser (and some AST transformers).
 *
 */
abstract class Node implements Visitable
{
    const NumericInteger = 1;
    const NumericRational = 2;
    const NumericFloat = 3;

    /**
     * Node factory, creating an appropriate Node from a Token.
     *
     * Based on the provided Token, returns a TerminalNode if the
     * token type is PosInt, Integer, RealNumber, Identifier or Constant
     * otherwise returns null.
     *
     * @retval Node|null
     * @param Token $token Provided token
     */
    public static function rationalFactory(Token $token)
    {
        switch ($token->getType()) {
            case TokenType::PosInt:
            case TokenType::Integer:
                $x = intval($token->getValue());

                return new IntegerNode($x);
            case TokenType::RealNumber:
                $x = floatval(str_replace(',', '.', $token->getValue()));

                return new NumberNode($x);
            case TokenType::Identifier:
                return new VariableNode($token->getValue());
            case TokenType::Constant:
                return new ConstantNode($token->getValue());

            case TokenType::FunctionName:
                return new FunctionNode($token->getValue(), null);
            case TokenType::OpenParenthesis:
                return new SubExpressionNode($token->getValue());

            case TokenType::AdditionOperator:
            case TokenType::SubtractionOperator:
            case TokenType::MultiplicationOperator:
            case TokenType::DivisionOperator:
            case TokenType::ExponentiationOperator:
                return new ExpressionNode(null, $token->getValue(), null);

            case TokenType::FactorialOperator:
                return new PostfixOperatorNode($token->getValue(), null);

            default:
                // echo "Node factory returning null on $token\n";
                return null;
        }
    }

    /**
     * Node factory, creating an appropriate Node from a Token.
     *
     * Based on the provided Token, returns a TerminalNode if the
     * token type is PosInt, Integer, RealNumber, Identifier or Constant
     * otherwise returns null.
     *
     * @retval Node|null
     * @param Token $token Provided token
     */
    public static function factory(Token $token)
    {
        switch ($token->getType()) {
            case TokenType::PosInt:
            case TokenType::Integer:
                $x = intval($token->getValue());

                return new NumberNode($x);
            case TokenType::RealNumber:
                $x = floatval(str_replace(',', '.', $token->getValue()));

                return new NumberNode($x);
            case TokenType::Identifier:
                return new VariableNode($token->getValue());
            case TokenType::Constant:
                return new ConstantNode($token->getValue());

            case TokenType::FunctionName:
                return new FunctionNode($token->getValue(), null);
            case TokenType::OpenParenthesis:
                return new SubExpressionNode($token->getValue());

            case TokenType::AdditionOperator:
            case TokenType::SubtractionOperator:
            case TokenType::MultiplicationOperator:
            case TokenType::DivisionOperator:
            case TokenType::ExponentiationOperator:
                return new ExpressionNode(null, $token->getValue(), null);

            case TokenType::FactorialOperator:
                return new PostfixOperatorNode($token->getValue());

            default:
                // echo "Node factory returning null on $token\n";
                return null;
        }
    }

    /**
     * Helper function, comparing two ASTs. Useful for testing
     * and also for some AST transformers.
     *
     * @retval boolean
     * @param Node|null $other Compare to this tree
     */
    abstract public function compareTo($other);

    /**
     * Convenience function for evaluating a tree, using
     * the Evaluator class.
     *
     * Example usage:
     *
     * ~~~{.php}
     * $parser = new StdMathParser();
     * $node = $parser->parse('sin(x)cos(y)');
     * $functionValue = $node->evaluate( array( 'x' => 1.3, 'y' => 1.4 ) );
     * ~~~
     *
     * @retval floatval
     * @param array $variables key-value array of variable values
     */
    public function evaluate($variables)
    {
        $evaluator = new Evaluator($variables);

        return $this->accept($evaluator);
    }

    /**
     * Rough estimate of the complexity of the AST.
     *
     * Gives a rough measure of the complexity of an AST. This can
     * be useful to choose between different simplification rules
     * or how to print a tree ("e^{...}" or ("\exp(...)") for example.
     *
     * More precisely, the complexity is computed as the sum of
     * the complexity of all nodes of the AST, and
     *
     *  NumberNodes, VariableNodes and ConstantNodes have complexity 1,
     *  FunctionNodes have complexity 5 (plus the complexity of its operand),
     *  ExpressionNodes have complexity 1 (for `+`, `-`, `*`), 2 (for `/`),
     *  or 8 (for `^`)
     *
     */
    public function complexity()
    {
        if ($this instanceof IntegerNode || $this instanceof VariableNode || $this instanceof ConstantNode) {
            return 1;
        } elseif ($this instanceof RationalNode || $this instanceof NumberNode) {
            return 2;
        } elseif ($this instanceof FunctionNode) {
            return 5 + $this->getOperand()->complexity();
        } elseif ($this instanceof ExpressionNode) {
            $operator = $this->getOperator();
            $left = $this->getLeft();
            $right = $this->getRight();
            switch ($operator) {
                case '+':
                case '-':
                case '*':
                    return 1 + $left->complexity() + (($right === null) ? 0 : $right->complexity());

                case '/':
                    return 3 + $left->complexity() + (($right === null) ? 0 : $right->complexity());

                case '^':
                    return 8 + $left->complexity() + (($right === null) ? 0 : $right->complexity());
            }
        }
        // This shouldn't happen under normal circumstances

        return 1000;
    }

    /**
     * Returns true if the node is a terminal node, i.e.
     * a NumerNode, VariableNode or ConstantNode.
     *
     * @retval boolean
     *
     */
    public function isTerminal()
    {
        if ($this instanceof NumberNode) {
            return true;
        }

        if ($this instanceof IntegerNode) {
            return true;
        }

        if ($this instanceof RationalNode) {
            return true;
        }

        if ($this instanceof VariableNode) {
            return true;
        }

        if ($this instanceof ConstantNode) {
            return true;
        }

        return false;
    }

    public function getOperator()
    {
        return '';
    }

    public function __toString()
    {
        $printer = new ASCIIPrinter();

        return $this->accept($printer);
    }
}
