
#include "parse.h"
#include "builtins/builtins.h"
#include "precedence.h"

using namespace empathy;
using namespace empathy::parse;
using namespace empathy::builtins;

optional< uint32_t > Parser::getPrecedence( const Term& t, const Delimiter& d )
{
    if( d == Delimiter::OpenParen )
        return getPostfixParenBlockPrecedence();

    return nullopt;
}

bool Parser::parsePrefix( const Delimiter& d, uint32_t prec )
{
    switch( d )
    {
        case Delimiter::OpenParen:
        {
            auto result = parseParenBlock( d );
            if( result )
                push( *result );
            return !!result;
        }

        case Delimiter::OpenBrace:
            return parseBraceBlock( d );

        case Delimiter::OpenBracket:
            // TODO
            break;

        case Delimiter::CloseParen:
            // TODO ERROR MGMT
            if( !m_introDelimiter || m_introDelimiter != Delimiter::OpenParen )
                cout << "mismatched ')'.\n";
            break;

        case Delimiter::CloseBrace:
            // TODO ERROR MGMT
            if( !m_introDelimiter || m_introDelimiter != Delimiter::OpenBrace )
                cout << "mismatched '}'.\n";
            break;

        case Delimiter::CloseBracket:
            // TODO
            break;
    }

    return false;
}

bool Parser::parseInfix( const Delimiter& d, uint32_t prec )
{
    switch( d )
    {
        case Delimiter::OpenParen:
            return parsePostfixParenBlock( d, prec );

        case Delimiter::OpenBrace:
            // TODO
            break;

        case Delimiter::OpenBracket:
            // TODO
            break;

        case Delimiter::CloseParen:
            // TODO ERROR MGMT
            if( !m_introDelimiter || m_introDelimiter != Delimiter::OpenParen )
                cout << "mismatched ')'.\n";
            break;

        case Delimiter::CloseBrace:
            // TODO ERROR MGMT
            if( !m_introDelimiter || m_introDelimiter != Delimiter::OpenBrace )
                cout << "mismatched '}'.\n";
            break;

        case Delimiter::CloseBracket:
            // TODO
            break;
    }

    return false;
}

optional< Value > Parser::parseParenBlock( Delimiter delim )
{
    m_resolver->consume();

    Parser p( resolver(), delim );
    p.parseExpression();
    auto content = p.result();

    auto next = m_resolver->consume();
    if( !next )
    {
        // TODO ERROR MGMT
        cout << "')' expected.\n";
        return nullopt;
    }

    auto decomp = Decompose( *next, Val< Delimiter >() );
    if( !decomp || *decomp != Delimiter::CloseParen )
    {
        // TODO ERROR MGMT
        cout << "')' expected.\n";
        return nullopt;
    }

    // If the paren block was empty, return an empty closed tuple.
    if( !content )
        return EmptyClosedTuple();

    // If the content is an open tuple, turn it into a closed (parenthesized) tuple.
    if( IsOpenTuple( *content ) )
        return CloseTuple( *content );

    // Otherwise wrap the content into a tuple: we prefer to treat single elements enclosed in parentheses
    // as tuples for consistency. An unification rule takes care of peeling off single element tuples
    // where applicable.
    auto tup = EmptyClosedTuple();
    return AppendToTuple( tup, *content );
}

// If the left value is invocable or is a type, the postfix paren block is a call or
// a function type expression and have application precedence.
// Otherwise, it is an implicit separator and have separator precedence.
optional< uint32_t > Parser::getPostfixParenBlockPrecedence()
{
    const auto& leftVal = lastValue();
    if( !leftVal )
        return nullopt;

    if( leftVal->type() == TSID( type ) || IsDecl( *leftVal )
        || IsTNamedDecl( *leftVal ) || IsTExpr( *leftVal ) )
        return precedence::Application;

    if( sema::CanBeInvoked( m_resolver->context(), *leftVal ) )
        return precedence::Application;

    return nullopt;
}

// Essentially the same thing as a prefix paren block, except that after parsing the block
// we check if this is actually an invocation or a function type expression, depending on
// whether the left value is invocable or if it is a type.
bool Parser::parsePostfixParenBlock( Delimiter delim, uint32_t prec )
{
    auto val = parseParenBlock( delim );
    if( !val )
    {
        push();
        return false;
    }

    const auto& leftVal = lastValue();
    if( !leftVal )
        return false;

    // If leftVal is a type, this is a func type or function expression (aka lambda).
    if( leftVal->type() == TSID( type ) )
        return parseFunctionExpression( *pop(), *val );

    // If leftVal is a decl, this is a func declaration.
    if( IsDecl( *leftVal ) )
        return parseFunctionDeclaration( *pop(), *val );

    // If leftVal is a TNamedDecl, this is a template func declaration.
    if( IsTNamedDecl( *leftVal ) )
        return parseTemplateFunctionTNamedDecl( *pop(), *val );

    // If leftVal is a TExpr, this is a template type or a template function expression.
    if( IsTExpr( *leftVal ) )
        return parseTemplateFunctionExpression( *pop(), *val );

    // If leftVal is something that can be invoked, resolve the invocation.
    if( sema::CanBeInvoked( m_resolver->context(), *leftVal ) )
    {
        auto call = sema::ResolveInvocation( m_resolver->context(), *pop(), *val );
        if( !call )
        {
            push();
            return false;
        }

        push( *call );
        return true;
    }

    push( *val );
    return true;
}

bool Parser::parseBraceBlock( Delimiter delim )
{
    m_resolver->consume();

    Parser p( resolver(), delim );
    p.parseSequence();
    auto content = p.result();

    auto next = m_resolver->consumeUnresolved();
    if( !next )
    {
        // TODO ERROR MGMT
        cout << "'}' expected.\n";
        return false;
    }

    auto decomp = Decompose( *next, Val< Delimiter >() );
    if( !decomp || *decomp != Delimiter::CloseBrace )
    {
        // TODO ERROR MGMT
        cout << "'}' expected.\n";
        return false;
    }

    if( content )
        push( *content );
    else
        push();
    return true;
}
