package org.yinwang.pysonar.ast;

import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.yinwang.pysonar.Analyzer;
import org.yinwang.pysonar.Binding;
import org.yinwang.pysonar.State;
import org.yinwang.pysonar.types.FunType;
import org.yinwang.pysonar.types.InstanceType;
import org.yinwang.pysonar.types.Type;
import org.yinwang.pysonar.types.UnionType;

import java.util.Set;

import static org.yinwang.pysonar.Binding.Kind.ATTRIBUTE;


public class Attribute extends Node {

    @Nullable
    public Node target;
    @NotNull
    public Name attr;


    public Attribute(@Nullable Node target, @NotNull Name attr, String file, int start, int end) {
        super(file, start, end);
        this.target = target;
        this.attr = attr;
        addChildren(target, attr);
    }


    public void setAttr(State s, @NotNull Type v) {
        Type targetType = transformExpr(target, s);
        if (targetType instanceof UnionType) {
            Set<Type> types = ((UnionType) targetType).types;
            for (Type tp : types) {
                setAttrType(tp, v);
            }
        } else {
            setAttrType(targetType, v);
        }
    }


    private void setAttrType(@NotNull Type targetType, @NotNull Type v) {
        if (targetType.isUnknownType()) {
            Analyzer.self.putProblem(this, "Can't set attribute for UnknownType");
            return;
        }
        targetType.table.insert(attr.id, attr, v, ATTRIBUTE);
    }


    @NotNull
    @Override
    public Type transform(State s) {
        // the form of ::A in ruby
        if (target == null) {
            return transformExpr(attr, s);
        }

        Type targetType = transformExpr(target, s);
        if (targetType instanceof UnionType) {
            Set<Type> types = ((UnionType) targetType).types;
            Type retType = Type.UNKNOWN;
            for (Type tt : types) {
                retType = UnionType.union(retType, getAttrType(tt));
            }
            return retType;
        } else {
            return getAttrType(targetType);
        }
    }


    private Type getAttrType(@NotNull Type targetType) {
        Set<Binding> bs = targetType.table.lookupAttr(attr.id);
        if (bs == null) {
            Analyzer.self.putProblem(attr, "attribute not found in type: " + targetType);
            Type t = Type.UNKNOWN;
            t.table.setPath(targetType.table.extendPath(attr.id));
            return t;
        } else {
            for (Binding b : bs) {
                Analyzer.self.putRef(attr, b);
                if (parent != null && parent instanceof Call &&
                        b.type instanceof FunType && targetType instanceof InstanceType)
                {  // method call
                    ((FunType) b.type).setSelfType(targetType);
                }
            }

            return State.makeUnion(bs);
        }
    }


    @NotNull
    @Override
    public String toString() {
        return "<Attribute:" + start + ":" + target + "." + attr.id + ">";
    }

}

@@ -1186,32 +1186,451 @@
       %7D%0A    %7D%0A%0A%0A
+    private void addRef(@NotNull Type targetType, @NotNull Set%3CBinding%3E bs) %7B%0A        for (Binding b : bs) %7B%0A            Analyzer.self.putRef(attr, b);%0A            if (parent != null && parent instanceof Call &&%0A                    b.type instanceof FunType && targetType instanceof InstanceType)%0A            %7B  // method call%0A                ((FunType) b.type).setSelfType(targetType);%0A            %7D%0A        %7D%0A    %7D%0A%0A%0A
     private void
@@ -1834,32 +1834,169 @@
 turn;%0A        %7D%0A
+        Set%3CBinding%3E bs = targetType.table.lookupAttr(attr.id);%0A        if (bs != null) %7B%0A            addRef(targetType, bs);%0A        %7D%0A%0A
         targetTy
@@ -3054,361 +3054,31 @@
     
-for (Binding b : bs) %7B%0A                Analyzer.self.putRef(attr, b);%0A                if (parent != null && parent instanceof Call &&%0A                        b.type instanceof FunType && targetType instanceof InstanceType)%0A                %7B  // method call%0A                    ((FunType) b.type).setSelfType(targetType);%0A                %7D%0A            %7D%0A
+addRef(targetType, bs);
 %0A   

Add ref to all previous binds when we put an new Ref