package chocopy.pa2;

import chocopy.common.analysis.AbstractNodeAnalyzer;
import chocopy.common.analysis.SymbolTable;
import chocopy.common.analysis.types.ClassValueType;
import chocopy.common.analysis.types.FuncType;
import chocopy.common.analysis.types.SymbolType;
import chocopy.common.analysis.types.ValueType;
import chocopy.common.astnodes.*;

/**
 * Analyzes declarations to create a top-level symbol table.
 * Recolic: This class will ONLY fill the symTable. Every `Program` and `FuncDef` has a sym table.
 */
public class DeclarationAnalyzer extends AbstractNodeAnalyzer<SymbolType> {

    /** Current symbol table.  Changes with new declarative region. */
    private SymbolTable<SymbolType> sym = new SymbolTable<>();
    /** Global symbol table. */
    private final SymbolTable<SymbolType> globals = sym;
    /** Receiver for semantic error messages. */
    private final Errors errors;

    /** A new declaration analyzer sending errors to ERRORS0. */
    public DeclarationAnalyzer(Errors errors0) {
        errors = errors0;
    }


    public SymbolTable<SymbolType> getGlobals() {
        return globals;
    }

    @Override
    public SymbolType analyze(Program program) {
        for (Declaration decl : program.declarations) {
            Identifier id = decl.getIdentifier();
            String name = id.name;

            SymbolType type = decl.dispatch(this);
            System.out.println("decl type " + type.toString());

            // recolic: what's the teacher willing to do???
            if (type == null) {
                continue;
            }

            sym.put(name, type);
        }

        return null;
    }

    @Override
    public SymbolType analyze(VarDef varDef) {
        System.out.println("debug");
        return analyze(varDef.var);
    }

    @Override
    public SymbolType analyze(ClassDef classDef) {
        System.out.println("debug: classdef");
        // TODO: check how do I create a obj type.
        return new ClassValueType(classDef.name.name);
    }

    @Override
    public SymbolType analyze(FuncDef funcDef) {
        System.out.println("debug: start funcDef: " + funcDef.getClass().getName());
        assert funcDef.symTable == null;
        sym = funcDef.symTable = new SymbolTable<SymbolType>(sym);

        // Func parameter list
        for(TypedVar param : funcDef.params) {
            String name = param.identifier.name;
            sym.put(name, analyze(param));
        }

        for(Declaration decl : funcDef.declarations) {
            String name = decl.getIdentifier().name;
            SymbolType type = decl.dispatch(this);
            sym.put(name, type);
        }

        return new FuncType(ValueType.annotationToValueType(funcDef.returnType));
    }

    @Override
    public SymbolType analyze(TypedVar node) {
        return ValueType.annotationToValueType(node.type);
    }


    /*
    @Override
    public SymbolType analyze(BinaryExpr node) {
        String op = node.operator;

        // Check operand type.
        SymbolType left_type = node.left.getInferredType();
        SymbolType right_type = node.left.getInferredType();
        switch(op) {
            case "-":
            case "*":
            case "//":
            case "%":
            case ">":
            case "<":
            case ">=":
            case "<=":
                if(left_type != ValueType.INT_TYPE || right_type != ValueType.INT_TYPE)
                    node.setErrorMsg("Syntax Error: operand should be INT");
                break;
            case "and":
            case "or":
                if(left_type != ValueType.BOOL_TYPE || right_type != ValueType.BOOL_TYPE)
                    node.setErrorMsg("Syntax Error: operand should be BOOL");
                break;
            case "+":
                if(left_type != ValueType.INT_TYPE && left_type != ValueType.STR_TYPE && ! left_type.isListType())
                    node.setErrorMsg("Syntax Error: operand of + should be INT or STR or LIST");
                // fallthrough
            case "==":
            case "!=":
            case "is":
                if(node.left.getInferredType() != node.right.getInferredType())
                    node.setErrorMsg("Syntax Error: binary operator operand type mismatch");
                break;
            default:
                node.setErrorMsg("Syntax Error: binary operator operand type not supported.");
        }

        // Now set target type.
        SymbolType result_type = ValueType.NONE_TYPE;
        switch(op) {
            case "-":
            case "*":
            case "//":
            case "%":
            case "and":
            case "or":
            case "+":
                result_type = left_type;
                break;
            case ">":
            case "<":
            case ">=":
            case "<=":
            case "==":
            case "!=":
            case "is":
                result_type = ValueType.BOOL_TYPE;
                break;
            default:
                throw new RuntimeException("Logic error");
        }

        return result_type;
    }
    */


}
