package de.dhbwstuttgart.syntaxtree.visual;

import de.dhbwstuttgart.parser.SyntaxTreeGenerator.AssignToLocal;
import de.dhbwstuttgart.syntaxtree.*;
import de.dhbwstuttgart.syntaxtree.statement.*;
import de.dhbwstuttgart.syntaxtree.type.*;

import java.lang.reflect.Modifier;
import java.util.Iterator;

public class OutputGenerator implements ASTVisitor{
    private static final String TAB = "  ";
    String tabs = "";
    protected final StringBuilder out;

    public OutputGenerator(StringBuilder out){
        this.out = out;
    }

    public void tab() {
        tabs += TAB;
    }

    public void untab() {
        tabs = tabs.substring(0,tabs.length()-TAB.length());
    }

    @Override
    public void visit(SourceFile sourceFile) {
        for(ClassOrInterface cl : sourceFile.getClasses()){
            cl.accept(this);
        }
    }

    @Override
    public void visit(ArgumentList argumentList) {
        out.append("(");
        Iterator<Expression> expressionIterator = argumentList.getArguments().iterator();
        while(expressionIterator.hasNext()){
            expressionIterator.next().accept(this);
            if(expressionIterator.hasNext())out.append(", ");
        }
        out.append(")");
    }

    @Override
    public void visit(GenericTypeVar genericTypeVar) {
        out.append(genericTypeVar.getName().toString());
    }

    @Override
    public void visit(FormalParameter formalParameter) {
        formalParameter.getType().accept(this);
        out.append(" ");
        out.append(formalParameter.getName());
    }

    @Override
    public void visit(GenericDeclarationList genericTypeVars) {
        Iterator<GenericTypeVar> genericIterator = genericTypeVars.iterator();
        if(genericIterator.hasNext()){
            out.append("<");
            while(genericIterator.hasNext()){
                genericIterator.next().accept(this);
                if(genericIterator.hasNext())out.append(", ");
            }
            out.append(">");
        }
    }

    @Override
    public void visit(Field field) {
        field.getType().accept(this);
        out.append(" ");
        out.append(field.getName());
        out.append(";");
    }

    @Override
    public void visit(Method method) {
        method.getReturnType().accept(this);
        out.append(" " + method.getName());
        method.getParameterList().accept(this);
        if(method.block != null)
            method.block.accept(this);
        out.append("\n");
    }

    @Override
    public void visit(Constructor method) {
        out.append(method.getName());
        method.getParameterList().accept(this);
        method.block.accept(this);
        out.append("\n");
    }

    @Override
    public void visit(ParameterList formalParameters) {
        out.append("(");
        Iterator<FormalParameter> genericIterator = formalParameters.getFormalparalist().iterator();
        if(genericIterator.hasNext()){
            while(genericIterator.hasNext()){
                genericIterator.next().accept(this);
                if(genericIterator.hasNext())out.append(", ");
            }
        }
        out.append(")");
    }

    @Override
    public void visit(ClassOrInterface classOrInterface) {
        if((Modifier.INTERFACE & classOrInterface.getModifiers()) == 1){
            out.append("interface ");
        }else{
            out.append("class ");
        }
        out.append(classOrInterface.getClassName().toString());
        classOrInterface.getGenerics().accept(this);
        out.append(" {\n\n");
        tab();
        for(Field f : classOrInterface.getFieldDecl()){
            out.append(tabs);
            f.accept(this);
            out.append("\n");
        }
        for(Method m : classOrInterface.getMethods()){
            out.append(tabs);
            m.accept(this);
            out.append("\n");
        }
        for(Constructor m : classOrInterface.getConstructors()){
            out.append(tabs);
            m.accept(this);
            out.append("\n");
        }
        untab();
        out.append("}");
    }

    @Override
    public void visit(RefType refType) {
        out.append(refType.getName().toString());
        Iterator<RefTypeOrTPHOrWildcardOrGeneric> genericIterator = refType.getParaList().iterator();
        if(genericIterator.hasNext()){
            out.append("<");
            while(genericIterator.hasNext()){
                genericIterator.next().accept(this);
                if(genericIterator.hasNext())out.append(", ");
            }
            out.append(">");
        }
    }

    @Override
    public void visit(SuperWildcardType superWildcardType) {
        out.append("? super ");
        superWildcardType.getInnerType().accept(this);
    }

    @Override
    public void visit(TypePlaceholder typePlaceholder) {
        out.append("TPH "+ typePlaceholder.getName());
    }

    @Override
    public void visit(ExtendsWildcardType extendsWildcardType) {
        out.append("? extends ");
        extendsWildcardType.getInnerType().accept(this);
    }

    @Override
    public void visit(GenericRefType genericRefType) {
        out.append(genericRefType.getParsedName().toString());
    }

    @Override
    public void visit(LambdaExpression lambdaExpression) {
        lambdaExpression.params.accept(this);
        out.append(" -> ");
        lambdaExpression.methodBody.accept(this);
    }

    @Override
    public void visit(Assign assign) {
        assign.lefSide.accept(this);
        out.append(" = ");
        assign.rightSide.accept(this);
    }

    @Override
    public void visit(BinaryExpr binary) {
        binary.lexpr.accept(this);
        out.append(" op ");
        binary.rexpr.accept(this);
    }

    @Override
    public void visit(Block block) {
        tab();
        out.append("{\n");
        for(Statement stmt : block.getStatements()){
            out.append(tabs);
            stmt.accept(this);
            out.append(";\n");
        }
        untab();
        out.append(tabs);
        out.append("}");
    }

    @Override
    public void visit(CastExpr castExpr) {

    }

    @Override
    public void visit(EmptyStmt emptyStmt) {

    }

    @Override
    public void visit(FieldVar fieldVar) {
        fieldVar.receiver.accept(this);
        out.append("." + fieldVar.fieldVarName);
    }

    @Override
    public void visit(ForStmt forStmt) {

    }

    @Override
    public void visit(IfStmt ifStmt) {
        out.append("if(");
        ifStmt.expr.accept(this);
        out.append(")\n");
        tab();
        out.append(tabs);
        ifStmt.then_block.accept(this);
        untab();
        if(ifStmt.else_block != null){
            out.append("\n" + tabs + "else\n");
            tab();
            out.append(tabs);
            ifStmt.else_block.accept(this);
            untab();
        }
    }

    @Override
    public void visit(InstanceOf instanceOf) {

    }

    @Override
    public void visit(LocalVar localVar) {
        out.append(localVar.name);
    }

    @Override
    public void visit(LocalVarDecl localVarDecl) {
        localVarDecl.getType().accept(this);
        out.append(" " + localVarDecl.getName());
    }

    @Override
    public void visit(MethodCall methodCall) {
        methodCall.receiver.accept(this);
        out.append("."+methodCall.name);
        methodCall.getArgumentList().accept(this);
    }

    @Override
    public void visit(NewClass methodCall) {
        out.append("new ");
        out.append(methodCall.name);
        methodCall.getArgumentList().accept(this);
    }

    @Override
    public void visit(NewArray newArray) {

    }

    @Override
    public void visit(Return aReturn) {
        out.append("return ");
        aReturn.retexpr.accept(this);
    }

    @Override
    public void visit(ReturnVoid aReturn) {
        out.append("return");
    }

    @Override
    public void visit(StaticClassName staticClassName) {

    }

    @Override
    public void visit(Super aSuper) {

    }

    @Override
    public void visit(This aThis) {
        out.append("this");
    }

    @Override
    public void visit(WhileStmt whileStmt) {
        out.append("while(");
        whileStmt.expr.accept(this);
        out.append(")");
        whileStmt.loopBlock.accept(this);
    }

    @Override
    public void visit(DoStmt whileStmt) {
        out.append("do ");
        whileStmt.loopBlock.accept(this);
        out.append("while(");
        whileStmt.expr.accept(this);
        out.append(");");
    }

    @Override
    public void visit(AssignToField assignLeftSide) {
        assignLeftSide.field.accept(this);
    }

    @Override
    public void visit(AssignToLocal assignLeftSide) {
        assignLeftSide.localVar.accept(this);
    }

    @Override
    public void visit(SuperCall superCall) {
        out.append("super(");
        superCall.arglist.accept(this);
        out.append(")");
    }

    @Override
    public void visit(ExpressionReceiver receiver) {
        receiver.expr.accept(this);
    }

    @Override
    public void visit(UnaryExpr unaryExpr) {
        if(unaryExpr.operation == UnaryExpr.Operation.MINUS){
            out.append("-");
        }
        if(unaryExpr.operation == UnaryExpr.Operation.PLUS){
            out.append("+");
        }
        if(unaryExpr.operation == UnaryExpr.Operation.PREDECREMENT){
            out.append("--");
        }
        if(unaryExpr.operation == UnaryExpr.Operation.PREINCREMENT){
            out.append("++");
        }
        unaryExpr.expr.accept(this);
        if(unaryExpr.operation == UnaryExpr.Operation.POSTDECREMENT){
            out.append("--");
        }
        if(unaryExpr.operation == UnaryExpr.Operation.POSTINCREMENT){
            out.append("++");
        }
    }

    @Override
    public void visit(de.dhbwstuttgart.syntaxtree.statement.Literal literal) {
        out.append(literal.value);
    }
}