package de.peeeq.wurstscript.attributes;

import de.peeeq.wurstscript.ast.*;
import de.peeeq.wurstscript.attributes.names.DefLink;
import de.peeeq.wurstscript.attributes.names.FuncLink;
import de.peeeq.wurstscript.validation.WurstValidator;

public class OverriddenFunctions {


    public static FunctionDefinition getRealFuncDef(ExtensionFuncDef f) {
        // extension functions cannot be overridden
        return f;
    }

    public static FunctionDefinition getRealFuncDef(NativeFunc f) {
        // native functions cannot be overridden
        return f;
    }

    public static FunctionDefinition getRealFuncDef(FuncDef f) {
        if (f.attrIsPrivate()) {
            // private functions cannot be overridden
            return f;
        }
        if (!isInModuleInstantiation(f)) {
            return f;
        }
        if (f.attrNearestNamedScope() == null) {
            return f;
        }
        if (f.attrNearestNamedScope().getParent() == null) {
            return f;
        }
        WScope scope = f.attrNearestNamedScope().getParent().attrNearestScope();
        return getRealFuncDef(f, scope);
    }

    private static boolean isInModuleInstantiation(Element e) {
        while (e != null) {
            if (e instanceof ModuleInstanciation) {
                return true;
            }
            e = e.getParent();
        }
        return false;
    }

    private static FunctionDefinition getRealFuncDef(FuncDef f, WScope scope) {
        if (scope instanceof StructureDef) {
            StructureDef c = (StructureDef) scope;

            FuncLink fNameLink = FuncLink.create(f, f.attrNearestScope());

            if (c.attrNameLinks().containsKey(f.getName())) {
                for (DefLink nl : c.attrNameLinks().get(f.getName())) {
                    if (nl.getLevel() == c.attrLevel()
                            && nl.getDef() instanceof FunctionDefinition
                            && nl instanceof FuncLink
                            && WurstValidator.canOverride((FuncLink) nl, fNameLink, true)
                            ) {
                        return ((FuncLink) nl).getDef().attrRealFuncDef();
                    }
                }
            }
            if (scope.getParent() != null) {
                return getRealFuncDef(f, scope.getParent().attrNearestNamedScope());
            }
        }
        return f;
    }

    public static FunctionDefinition getRealFuncDef(TupleDef t) {
        return t;
    }

}
