import org.antlr.v4.runtime.ParserRuleContext;
import org.antlr.v4.runtime.tree.ParseTree;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;

public class CacheVisitor extends Visitor {

    public CacheVisitor(Cache cache, String targetLanguage) {
        super();
        this.cache = cache;
        this.targetLanguage = targetLanguage;
    }

    @Override public String visitPattern_initializer(SwiftParser.Pattern_initializerContext ctx) {
        String varName = ctx.pattern().identifier_pattern().getText();
        Instance varType =
                ctx.pattern().type_annotation() != null && ctx.pattern().type_annotation().type() != null ? TypeUtil.fromDefinition(ctx.pattern().type_annotation().type(), this)
                : TypeUtil.infer(ctx.initializer().expression(), this);
        cache(varName, varType, ctx);
        visitChildren(ctx.initializer());
        return null;
    }

    @Override public String visitProperty_declaration(SwiftParser.Property_declarationContext ctx) {
        String varName = ctx.variable_name().getText();
        Instance varType = TypeUtil.fromDefinition(ctx.type_annotation().type(), this);
        cache(varName, varType, ctx);
        visit(ctx.property_declaration_body());
        return null;
    }

    private void cache(String varName, Object/*Definition/Instance*/ varType, ParseTree ctx) {
        if(varType instanceof FunctionDefinition) varName += FunctionUtil.nameAugment(((FunctionDefinition)varType).parameterExternalNames, ((FunctionDefinition)varType).parameterTypes);
        cache.cacheOne(varName, varType, ctx);
    }

    @Override public String visitFunction_declaration(SwiftParser.Function_declarationContext ctx) {
        visitFunctionDeclaration(ctx);
        return null;
    }
    @Override public String visitInitializer_declaration(SwiftParser.Initializer_declarationContext ctx) {
        visitFunctionDeclaration(ctx);
        return null;
    }
    private void visitFunctionDeclaration(ParserRuleContext ctx) {

        FunctionDefinition functionDefinition = new FunctionDefinition(ctx, this);
        cache.cacheOne(functionDefinition.name, functionDefinition, ctx);

        SwiftParser.Code_blockContext codeBlockCtx = FunctionUtil.codeBlockCtx(ctx);
        ArrayList<String> parameterLocalNames = FunctionUtil.parameterLocalNames(FunctionUtil.parameters(ctx));
        for(int i = 0; i < parameterLocalNames.size(); i++) {
            cache.cacheOne(parameterLocalNames.get(i), functionDefinition.parameterTypes.get(i), codeBlockCtx);
        }

        visit(codeBlockCtx);
    }

    public void visitExplicit_closure_expression(PrefixElem elem, SwiftParser.Explicit_closure_expressionContext ctx, int paramPos) {

        List<Instance> parameterTypes = FunctionUtil.closureParameterTypes(elem.type, elem.typeBeforeCall, paramPos);
        List<String> parameterNames = FunctionUtil.closureParameterNames(parameterTypes, ctx);

        for(int i = 0; i < parameterNames.size(); i++) cache.cacheOne(parameterNames.get(i), parameterTypes.get(i), ctx);
    }

    @Override public String visitSetter_clause(SwiftParser.Setter_clauseContext ctx) {
        this.visitPropertyClause(ctx);
        return null;
    }
    @Override public String visitWillSet_clause(SwiftParser.WillSet_clauseContext ctx) {
        this.visitPropertyClause(ctx);
        return null;
    }
    @Override public String visitDidSet_clause(SwiftParser.DidSet_clauseContext ctx) {
        this.visitPropertyClause(ctx);
        return null;
    }
    private void visitPropertyClause(ParserRuleContext ctx) {
        SwiftParser.Property_declarationContext propertyDeclaration = (SwiftParser.Property_declarationContext) ctx.parent.parent.parent;
        Instance propertyType = ((Instance)cache.find(propertyDeclaration.variable_name().getText(), ctx).object).withoutPropertyInfo();
        //propertyType.isGetterSetter = null;
        SwiftParser.Code_blockContext blockContext =
            ctx instanceof SwiftParser.Setter_clauseContext ? ((SwiftParser.Setter_clauseContext)ctx).code_block() :
            ctx instanceof SwiftParser.WillSet_clauseContext ? ((SwiftParser.WillSet_clauseContext)ctx).code_block() :
            ((SwiftParser.DidSet_clauseContext)ctx).code_block();
        String argumentName =
            ctx instanceof SwiftParser.Setter_clauseContext ? AssignmentUtil.setterArgumentName((SwiftParser.Setter_clauseContext) ctx) :
            ctx instanceof SwiftParser.WillSet_clauseContext ? AssignmentUtil.willSetArgumentName((SwiftParser.WillSet_clauseContext)ctx) :
            AssignmentUtil.didSetArgumentName((SwiftParser.DidSet_clauseContext)ctx);

        cache.cacheOne(argumentName, propertyType, blockContext);

        visit(blockContext);
    }

    /*@Override public String visitClosure_expression(SwiftParser.Closure_expressionContext ctx) {
        SwiftParser.Parameter_listContext parameterList = ctx.closure_signature().parameter_clause().parameter_list();
        List<SwiftParser.ParameterContext> parameters = parameterList != null ? parameterList.parameter() : null;
        ArrayList<Instance> parameterTypes = FunctionUtil.parameterTypes(parameters, this);
        for(int i = 0; parameterTypes != null && i < parameterTypes.size(); i++) {
            cache.cacheOne(FunctionUtil.parameterLocalName(parameters.get(i)), parameterTypes.get(i), ctx);
        }

        return null;
    }*/

    @Override public String visitClass_declaration(SwiftParser.Class_declarationContext ctx) {
        visitClassOrStructDeclaration(ctx);
        return null;
    }
    @Override public String visitStruct_declaration(SwiftParser.Struct_declarationContext ctx) {
        visitClassOrStructDeclaration(ctx);
        return null;
    }
    private void visitClassOrStructDeclaration(ParserRuleContext ctx) {
        String className =
                ctx instanceof SwiftParser.Class_declarationContext ? ((SwiftParser.Class_declarationContext)ctx).class_name().getText() :
                ((SwiftParser.Struct_declarationContext)ctx).struct_name().getText();

        Cache.CacheBlockAndObject superClass = null;
        SwiftParser.Type_inheritance_clauseContext typeInheritanceClauseCtx =
                ctx instanceof SwiftParser.Class_declarationContext ? ((SwiftParser.Class_declarationContext)ctx).type_inheritance_clause() :
                ((SwiftParser.Struct_declarationContext)ctx).type_inheritance_clause();
        if(typeInheritanceClauseCtx != null) {
            String superClassName = typeInheritanceClauseCtx.type_inheritance_list().type_identifier().getText();
            superClass = this.cache.find(superClassName, ctx);
        }

        ClassDefinition classDefinition = new ClassDefinition(className, superClass, new LinkedHashMap<String, Instance>(), new ArrayList<String>());
        if(ctx instanceof SwiftParser.Struct_declarationContext) {
            classDefinition.cloneOnAssignmentReplacement = new HashMap<String, Boolean>();
            classDefinition.cloneOnAssignmentReplacement.put("ts", true);
            classDefinition.cloneOnAssignmentReplacement.put("java", true);
        }
        cache.cacheOne(className, classDefinition, ctx);

        visit(ctx instanceof SwiftParser.Class_declarationContext ? ((SwiftParser.Class_declarationContext)ctx).class_body() : ((SwiftParser.Struct_declarationContext)ctx).struct_body());

        if(ctx instanceof SwiftParser.Struct_declarationContext) Initializer.addMemberwiseInitializer(classDefinition, ctx, this);
        Initializer.addDefaultInitializer(classDefinition, ctx, this);
    }

    @Override public String visitFor_in_statement(SwiftParser.For_in_statementContext ctx) {

        if(ctx.expression() != null && ctx.expression().binary_expressions() != null) {
            String varName = ctx.pattern().getText().equals("_") ? "$" : ctx.pattern().getText();
            cache.cacheOne(varName, new Instance("Int", ctx, cache), ctx.code_block());
        }
        else {
            Instance iteratedType = new Expression(ctx.expression(), null, this).type;
            String indexVar = "$", valueVar;
            if(ctx.pattern().tuple_pattern() != null) {
                indexVar = ctx.pattern().tuple_pattern().tuple_pattern_element_list().tuple_pattern_element(0).getText();
                valueVar = ctx.pattern().tuple_pattern().tuple_pattern_element_list().tuple_pattern_element(1).getText();
            }
            else {
                valueVar = ctx.pattern().identifier_pattern().getText();
            }
            cache.cacheOne(indexVar, iteratedType.typeName().equals("Dictionary") ? iteratedType.generics.get("Key") : new Instance("Int", ctx, cache), ctx.code_block());
            cache.cacheOne(valueVar, iteratedType.typeName().equals("String") ? new Instance("String", ctx, cache) : iteratedType.generics.get("Value"), ctx.code_block());
        }

        visit(ctx.code_block());

        return null;
    }

    private void cacheIfLet(ParserRuleContext ctx, SwiftParser.Code_blockContext codeBlock) {
        IfLet ifLet = new IfLet(ctx, this);
        for(int i = 0; i < ifLet.varNames.size(); i++) {
            cache.cacheOne(ifLet.varNames.get(i), ifLet.varTypes.get(i), codeBlock);
        }
        visit(codeBlock);
    }

    @Override public String visitIf_statement(SwiftParser.If_statementContext ctx) {
        cacheIfLet(ctx, ctx.code_block());
        if(ctx.else_clause() != null) visit(ctx.else_clause());
        return null;
    }

    @Override public String visitGuard_statement(SwiftParser.Guard_statementContext ctx) {
        cacheIfLet(ctx, ctx.code_block());
        return null;
    }
}