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

using namespace goose;
using namespace goose::eir;
using namespace goose::parse;
using namespace goose::builtins;

namespace
{
    // "ref" is actually an infix operator, which can take either
    // a constant value of type "AccessLevel" or a TVar on the lhs,
    // and a type or a TExpr on the rhs.
    optional< uint32_t > RefOpPrecedence( const Parser& p )
    {
        const auto& leftVal = p.peekLastValue();
        if( !leftVal )
            return nullopt;

        if( leftVal->type() != GetValueType< AccessLevel >() && !IsTVar( *leftVal ) )
            return nullopt;

        return precedence::RefType;
    }

    bool ParseRefOp( Parser& p, LocationId locationId, uint32_t prec )
    {
        auto& dm = DiagnosticsManager::GetInstance();

        auto accessLevel = p.popValue();

        auto np = p.makeNestedParser();
        if( !np.parseExpression( precedence::RefType ) )
        {
            dm.emitSyntaxErrorMessage( p.resolver()->currentLocation(),
                "expected a type.", 0 );
            p.pushValue( PoisonValue() );
            return true;
        }

        auto type = np.popType();
        if( !type || ( !IsTExpr( *type ) && !IsType( p.context(), *type ) ) )
        {
            dm.emitSyntaxErrorMessage( p.resolver()->currentLocation(),
                "expected a type.", 0 );
            p.pushValue( PoisonValue() );
            return true;
        }

        if( !type->isType() && !IsTExpr( *type ) )
            type = ToType( p.context(), *type );

        // If the accessLevel is a TVar, turn it into a TDecl, so we can constrain it to
        // the AccessLevel type. Otherwise, it woud be possible to do all kind of
        // funky stuff to create refs with pretty much any old value of any type as
        // the access level.
        if( IsTVar( *accessLevel ) )
        {
            auto tv = *FromValue< TVar >( *accessLevel );
            accessLevel = ToValue( TDecl( GetValueType< AccessLevel >(), tv.name() ) )
                .setLocationId( accessLevel->locationId() );
        }

        auto loc = Location::CreateSpanningLocation( accessLevel->locationId(), type->locationId() );

        ReferenceType rt( ValueToEIR( *type ), ValueToEIR( *accessLevel ) );
        p.pushValue( ToDeclValue( rt ).setLocationId( loc ) );
        return true;
    }
}

namespace goose::builtins
{
    void SetupRefTypeParsingRule( Env& e )
    {
        DefineConstant( e, "mut"_sid, MutAccessLevel() );
        DefineConstant( e, "const"_sid, ConstAccessLevel() );
        DefineConstant( e, "temp"_sid, TempAccessLevel() );

        RegisterRule( e, "ref"_sid, Rule( RefOpPrecedence, ParseRefOp ) );
    }
}
