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.SimpleObject;
import hu.swankey.ammo.common.yggdrasil.basics.StringField;
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 = "YObject";
    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;
                }
            }

        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 boolean isSuperiorOf(YClass type) {
        if (type == this)
            return true;

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

        return false;
    }

    public boolean isTypeOf(YObject element) {

        if (element == null)
            return true;

        return isSuperiorOf(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;
    }
    
    
    private static class SetName extends YMethod {

        private static final String FUNCTION_NAME = "=";
        private static final String KEY_VALUE = "newName";
        private static final YClass YCLASS_VALUE = StringField.yclass();

        public static final SetName function = new SetName();

        protected SetName() {
            super(FUNCTION_NAME);
            addParameter(KEY_VALUE, YCLASS_VALUE, null);
        }

        @Override
        public YObject run(Map<String, YObject> params, YObject thisElement) {
            
            String newName = ((StringField)params.get(KEY_VALUE)).getValue();
            ((SimpleObject)thisElement).setName( newName );

            return null;
        }
    }       
}