import re

token_patterns = [
    (r'\bspell\sbegin\b', 'SPELL_BEGIN'),  # Update this line
    (r'\bspell\send\b', 'SPELL_END'),      # Update this line
    (r'\bspell\b', 'SPELL'),  # Move SPELL before other identifiers
    (r'\buse\b', 'USE'),
    (r'\bparameter\b', 'PARAMETER'),
    (r'\beffect\b', 'EFFECT'),
    (r'\bloop\sbegin\b', 'LOOP_BEGIN'),
    (r'\bloop\send\b', 'LOOP_END'),
    (r'\bspell\scast\b', 'SPELL_CAST'),
    (r'\bany\smana\sunspent\b', 'ANY_MANA_UNSPENT'),
    (r'=\s*\(', 'ASSIGN'),
    (r'\d+', 'NUMBER'),  # Matches numbers
    (r'\w+', 'IDENTIFIER'),  # Matches identifiers
    (r';', 'SEMICOLON'),
    (r',', 'COMMA'),
    (r'[+\-*/]', 'OPERATOR'),
    (r'=', 'EQUAL'),
    (r'\(', 'LPAREN'),
    (r'\)', 'RPAREN'),
    (r'\'[^\'\n]*\'', 'STRING'),  # Matches single-quoted strings
    (r'\/\/[^\n]*', 'COMMENT'),  # Matches single-line comments
    (r'\s+', 'WHITESPACE')  # Matches whitespace
]

# Combine the regular expression patterns into a single pattern
token_pattern = '|'.join(r'(?P<%s>%s)' % pair for pair in token_patterns)

def lexer(input_code):
    keywords = set(['use', 'parameter', 'effect', 'spell', 'loop'])
    symbols = set([';', ',', '+', '-', '*', '/', '=', '(', ')'])

    def get_token(token_str):
        if token_str in keywords:
            return token_str.upper(), token_str
        elif token_str.isdigit():
            return 'NUMBER', int(token_str)
        elif token_str[0] == '\'' and token_str[-1] == '\'':
            return 'STRING', token_str[1:-1]
        elif token_str in symbols:
            return token_str, token_str
        elif token_str.strip() == '':
            return 'WHITESPACE', None
        else:
            return 'IDENTIFIER', token_str

    tokens = []
    lines = input_code.split('\n')
    line_number = 1

    for line in lines:
        words = line.split()
        for word in words:
            token_type, token_value = get_token(word)
            print(f"Word: '{word}', Token: {token_type}, Value: {token_value}")
            tokens.append((token_type, token_value, line_number))

        line_number += 1

    return tokens

class ASTNode:
    pass

class SpellNode(ASTNode):
    def __init__(self):
        self.parameters = []
        self.effects = []
        self.loop = None

class ParameterNode(ASTNode):
    def __init__(self, name, value):
        self.name = name
        self.value = value

class EffectNode(ASTNode):
    def __init__(self, name):
        self.name = name

class LoopNode(ASTNode):
    def __init__(self, condition):
        self.condition = condition
        self.statements = []

def parse(tokens):
    position = 0
    program = SpellNode()

    def get_next_token():
        nonlocal position
        if position < len(tokens):
            token = tokens[position]
            position += 1
            return token
        return None

    def parse_loop():
        loop = LoopNode(parse_expression())
        if get_next_token()[1] != 'LOOP_BEGIN':
            raise SyntaxError("Expected 'loop begin'")
        while get_next_token()[1] != 'LOOP_END':
            statement = parse_statement()
            if statement:
                loop.statements.append(statement)
        return loop

    def parse_parameter():
        name = get_next_token()[1]
        if get_next_token()[1] != 'EQUAL':
            raise SyntaxError("Expected '=' after parameter name")
        value = parse_expression()
        return ParameterNode(name, value)

    def parse_effect():
        name = get_next_token()[1]
        return EffectNode(name)

    def parse_expression():
        token = get_next_token()
        if token[0] in ['NUMBER', 'STRING', 'IDENTIFIER']:
            return token[1]
        elif token[0] == 'LPAREN':
            expr = parse_expression()
            if get_next_token()[0] != 'RPAREN':
                raise SyntaxError("Expected ')' after expression")
            return expr
        else:
            raise SyntaxError(f"Unexpected token: {token[1]}")

    def parse_statement():
        token = get_next_token()
        if not token:
            return None
        elif token[1] == 'PARAMETER':
            return parse_parameter()
        elif token[1] == 'EFFECT':
            return parse_effect()
        elif token[1] == 'LOOP':
            return parse_loop()
        elif token[1] == 'USE':
            # Ignore 'use' statements for simplicity in this example
            return None
        else:
            raise SyntaxError(f"Unexpected token: {token[1]}")

    while position < len(tokens):
        token = get_next_token()
        if token[1] == 'SPELL_BEGIN':
            while (token := get_next_token())[1] != 'SPELL_END':
                if token[1] == 'PARAMETER':
                    program.parameters.append(parse_parameter())
                elif token[1] == 'EFFECT':
                    program.effects.append(parse_effect())
                elif token[1] == 'LOOP':
                    program.loop = parse_loop()
                elif token[1] == 'USE':
                    # Ignore 'use' statements for simplicity in this example
                    pass
                else:
                    raise SyntaxError(f"Unexpected token: {token[1]}")
        elif token[1] == 'SPELL_CAST':
            return program
        else:
            # We don't raise an error for 'SPELL' token
            pass

    return program

class ASTNode:
    def execute(self):
        pass

class SpellNode(ASTNode):
    def __init__(self):
        self.parameters = []
        self.effects = []
        self.loop = None

    def execute(self):
        for param in self.parameters:
            param.execute()
        for effect in self.effects:
            effect.execute()
        if self.loop:
            self.loop.execute()

class ParameterNode(ASTNode):
    def __init__(self, name, value):
        self.name = name
        self.value = value

    def execute(self):
        # In the interpreter, we'll store the parameters in a dictionary
        # for later use during execution of the effects and loop.
        parameters[self.name] = self.value

class EffectNode(ASTNode):
    def __init__(self, name):
        self.name = name

    def execute(self):
        # In this simple example, we'll just print the effects.
        print(f"Executing effect: {self.name}")

class LoopNode(ASTNode):
    def __init__(self, condition):
        self.condition = condition
        self.statements = []

    def execute(self):
        while self.condition:
            for statement in self.statements:
                statement.execute()

# Interpreter function to execute the program represented by the AST
def interpret(ast):
    ast.execute()
# Test the parser with the tokens generated by the lexer
sample_code = """
spell begin;
use mana = 10;
loop begin;
parameter color = 'blue';
parameter shape = 'circle';
effect glow;
use mana = (mana - 1);
loop while = (any mana unspent);
spell end;
spell cast;
"""

tokens = lexer(sample_code)
ast = parse(tokens)

# Initialize a dictionary to store parameters during execution
parameters = {}

# Execute the program
interpret(ast)