package hu.swankey.ammo.common.script.yunits;

import hu.swankey.ammo.common.yggdrasil.Yggdrasil;
import hu.swankey.ammo.common.yggdrasil.basics.Reference;
import hu.swankey.ammo.common.yggdrasil.basics.YObject;
import hu.swankey.ammo.common.yggdrasil.definition.ClassDefinition;
import hu.swankey.ammo.common.yggdrasil.definition.Definition;
import hu.swankey.ammo.common.yggdrasil.definition.ClassDefinition.ClassDefinitionClass;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class YClass extends YUnit implements Wrappable {

    private static final String CLASS_NAME = "Object";
    private static final String[] PACKAGE_PATH = { Yggdrasil.PACKAGE_BASICS };
    private static final YPackage rootPackage = new YPackage(
            Yggdrasil.PACKAGE_ROOT);
    private static YClass singleton;

    private String[] path;
    private final List<YClass> superiors;
    private final Map<String, ArrayList<YMethod>> methods = new HashMap<String, ArrayList<YMethod>>();

    protected ClassDefinition definition;

    public static YClass singleton() {
        if (singleton == null) {
            singleton = new YClass(CLASS_NAME, PACKAGE_PATH);
            placeClass(singleton);
        }
        return singleton;
    }

    protected YClass(String name, String[] path) {
        super(name);
        this.superiors = new ArrayList<YClass>();

        if (path != null)
            this.path = path;
        else
            this.path = PACKAGE_PATH;
    }

    public static void placeClass(YClass yclass) {

        YPackage ypackage = rootPackage;
        String[] path = yclass.getPackagePath();

        for (int i = 0; i < path.length; i++) {
            YPackage parent = ypackage;
            ypackage = (YPackage) parent.get(path[i]);

            if (ypackage == null) {
                ypackage = new YPackage(path[i]);
                parent.add(ypackage);
            }
        }

        ypackage.add(yclass);
    }

    public static YPackage getRootPackage() {
        return rootPackage;
    }

    public static YClass getYClass(Reference link) {
        return (YClass) get(link);
    }

    public static YUnit get(Reference unitLink) {
        return get(unitLink.getPathStrArray());
    }

    public static YClass getYClass(String[] path) {
        return (YClass) get(path);
    }

    public static YUnit get(String[] pathStrArray) {

        YUnit yunit = rootPackage;

        int i = (pathStrArray[0].equals(Yggdrasil.PACKAGE_ROOT) ? 1 : 0);
        while (i < pathStrArray.length) {
            yunit = ((YPackage) yunit).get(pathStrArray[i]);
            if (yunit == null)
                return null;
            i++;
        }

        return yunit;
    }

    public YMethod getMethod(String name, Map<String, YObject> parameters) {
        HashMap<String, YClass> signature = new HashMap<String, YClass>();

        if (parameters != null)
            for (String parameterName : parameters.keySet())
                signature.put(parameterName, parameters.get(parameterName)
                        .getYClass());

        return getMethodBySignature(name, signature);
    }

    public YMethod getMethodBySignature(String name,
            Map<String, YClass> signature) {

        List<YMethod> list = getAllMethods(name);

        YMethod bestFit = null;
        int minDist = -1;

        if (list != null)
            for (YMethod method : list) {
                int distance = method.calculateDistance(signature);
                if (distance >= 0 && (distance < minDist || minDist == -1)) {
                    bestFit = method;
                    minDist = distance;
                }
            }

        // // TODO: Megoldani normálisan:
        // for (YClass superior: getSuperiors()) {
        //          
        // // if (!(superior instanceof ComplexClass))
        // // continue;
        //          
        // Method method = superior.getMethodBySignature(name, signature);
        //          
        // if (method == null)
        // continue;
        //          
        // int distance = method.calculateDistance(signature);
        // if (distance >= 0 && (distance < minDist || minDist == -1)) {
        // bestFit = method;
        // minDist = distance;
        // }
        // }

        return bestFit;
    }

    // public Method getMethodBySignature(String name, Map<String, YClass>
    // signature) {
    //
    // ArrayList<Method> list = methods.get(name);
    //
    // Method bestFit = null;
    // int minDist = -1;
    //
    // if (list != null)
    // for (Method method : list) {
    // int distance = method.calculateDistance(signature);
    // if (distance >= 0 && (distance < minDist || minDist == -1)) {
    // bestFit = method;
    // minDist = distance;
    // }
    // }
    //      
    //      
    // // TODO: Megoldani normálisan:
    // for (YClass superior: getSuperiors()) {
    //          
    // // if (!(superior instanceof ComplexClass))
    // // continue;
    //          
    // Method method = superior.getMethodBySignature(name, signature);
    //          
    // if (method == null)
    // continue;
    //          
    // int distance = method.calculateDistance(signature);
    // if (distance >= 0 && (distance < minDist || minDist == -1)) {
    // bestFit = method;
    // minDist = distance;
    // }
    // }
    //      
    // return bestFit;
    // }

    public boolean hasMethod(YMethod method) {
        ArrayList<YMethod> list = methods.get(method.getName());

        if (list == null)
            return false;
        else
            return list.contains(method);
    }

    public void addMethod(YMethod method) {

        ArrayList<YMethod> list = methods.get(method.getName());
        if (list == null) {
            list = new ArrayList<YMethod>();
            methods.put(method.getName(), list);
        } else if (list.contains(method))
            throw new RuntimeException("'" + getName()
                    + "' already contains method '" + method.getName() + "'");

        list.add(method);
        // method.setOwnerClass(this);

        if (hasWrapperElement())
            getWrapperElement().synchronize();
    }

    public void removeMethod(YMethod method) {
        methods.get(method.getName()).remove(method);
        if (hasWrapperElement())
            getWrapperElement().synchronize();
    }

    public Map<String, ArrayList<YMethod>> getMethods() {
        return Collections.unmodifiableMap(methods);
    }

    public Map<String, ArrayList<YMethod>> getAllMethods() {
        Map<String, ArrayList<YMethod>> returnMap = new HashMap<String, ArrayList<YMethod>>();

        List<YClass> classes = getAllSuperiors();
        classes.add(this);

        for (YClass yclass : classes)
            for (String methodName : yclass.getMethods().keySet()) {

                if (!returnMap.containsKey(methodName))
                    returnMap.put(methodName, new ArrayList<YMethod>(yclass
                            .getMethods(methodName)));
                else {
                    List<YMethod> list2 = returnMap.get(methodName);
                    for (YMethod method : yclass.getMethods(methodName)) {
                        list2.add(method);
                    }
                }
            }

        return returnMap;
    }

    public List<YMethod> getMethods(String name) {
        List<YMethod> list = methods.get(name);

        if (list == null)
            return null;
        else
            return Collections.unmodifiableList(list);
    }

    public List<YMethod> getAllMethods(String name) {
        List<YMethod> list = new ArrayList<YMethod>();

        if (methods.containsKey(name))
            list.addAll(methods.get(name));

        for (YClass superior : getSuperiors())
            list.addAll(superior.getAllMethods(name));

        return list;
    }

    public YPackage getPackage() {
        return (YPackage) getParent();
    }

    public String[] getPackagePath() {
        return path;
    }

    // public String getPathString(){
    // String[] path = getPackagePath();
    // String pathStr = path[0];
    //      
    // for(int i=1; i<path.length; i++)
    // pathStr += DELIMITER + path[i];
    //      
    // pathStr += DELIMITER + getName();
    //      
    // return pathStr;
    // }

    public boolean isTypeOf(YClass type) {
        // TODO: Más, világosabb nevet adni a metódusnak

        if (type == this)
            return true;

        for (YClass superior : type.getSuperiors()) {
            if (isTypeOf(superior))
                return true;
        }

        return false;
    }
    
    public boolean isSuperiorOf(YClass type){
        return isTypeOf(type);
    }

    public boolean isTypeOf(YObject element) {

        if (element == null)
            return true;

        return isTypeOf(element.getYClass());
    }

    public void addSuperior(YClass yclass) {

        if (yclass == null)
            throw new RuntimeException("'yclass' cannot be null");

        // Exception if yclass is already a superior:
        if (superiors.contains(yclass))
            throw new RuntimeException(getName() + " already has "
                    + yclass.getName() + " as superior.");
        for (YClass superior : superiors)
            if (superior.distanceFromSuperior(yclass) >= 0)
                throw new RuntimeException(getName() + " already has "
                        + yclass.getName() + " as a superior (throught "
                        + superior + ")");

        // Remove yclass' superiors
        ArrayList<YClass> toRemove = new ArrayList<YClass>();
        for (YClass superior : superiors)
            if (yclass.distanceFromSuperior(superior) >= 0)
                toRemove.add(superior);
        superiors.removeAll(toRemove);

        // Add yclass
        superiors.add(yclass);

        if (hasWrapperElement())
            getWrapperElement().synchronize();
    }

    public void removeSuperior(YClass yclass) {
        superiors.remove(yclass);

        if (getWrapperElement() != null)
            getWrapperElement().synchronize();
    }

    public List<YClass> getSuperiors() {
        return Collections.unmodifiableList(superiors);
    }

    public List<YClass> getAllSuperiors() {
        List<YClass> list = new ArrayList<YClass>();
        getAllSuperiors(list);
        return list;
    }

    private void getAllSuperiors(List<YClass> list) {
        for (YClass yclass : getSuperiors())
            if (!list.contains(yclass)) {
                list.add(yclass);
                yclass.getAllSuperiors(list);
            }
    }

    // public void removeSuperiors(){
    // superiors.clear();
    // }

    public YObject create(String name) {
        return new YObject(name, this);
    }

    @Override
    public String toString() {
        return getName();
    }

    public int distanceFromSuperior(YClass type) {

        if (type == this)
            return 0;

        int minDist = -1;
        for (YClass superior : superiors) {
            int dist = superior.distanceFromSuperior(type);
            if (minDist == -1 || dist < minDist)
                minDist = dist;
        }

        return minDist;
    }

    // @Override
    public void setWrapperElement(Definition definition) {
        this.definition = (ClassDefinition) definition;
    }

    @Override
    public boolean hasWrapperElement() {
        return definition != null;
    }

    @Override
    public Definition getWrapperElement() {
        if (definition == null)
            definition = ClassDefinitionClass.singleton().create(this);
        return definition;
    }
}