package com.wizzardo.tools.evaluation;

import com.wizzardo.tools.collections.CollectionTools;
import com.wizzardo.tools.misc.Supplier;
import com.wizzardo.tools.misc.Unchecked;
import com.wizzardo.tools.reflection.FieldInfo;
import com.wizzardo.tools.reflection.FieldReflection;
import com.wizzardo.tools.reflection.Fields;

import java.util.HashMap;
import java.util.Map;

/**
 * Created by wizzardo on 22.12.15.
 */
public class Config extends HashMap<String, Object> implements CollectionTools.Closure<Object, ClosureExpression> {

    protected Config root = this;

    public Config() {
    }

    public Config(Config root) {
        this.root = root;
    }

    @Override
    public Object get(Object key) {
        Object value = superGet(key);
        if (value != null)
            return value;

        if (root != this && (value = root.superGet(key)) != null)
            return value;

        put((String) key, value = new Config(root));
        return value;
    }

    protected Object superGet(Object key) {
        return super.get(key);
    }

    @Override
    public Object execute(ClosureExpression it) {
        it.get(this);
        return this;
    }

    public Config config(String key) {
        return (Config) get(key);
    }

    public <T> T get(String key, T def) {
        Object value = get(key);

        if (value instanceof Config && ((Config) value).isEmpty() && !(def instanceof Config))
            return def;

        return (T) value;
    }

    public <T> T get(String key, Supplier<T> def) {
        Object value = get(key);

        if (value instanceof Config && ((Config) value).isEmpty())
            return def.supply();

        return (T) value;
    }

    public void merge(Map<String, Object> scr) {
        merge(this, scr);
    }

    private void merge(Map into, Map<?, ?> from) {
        for (Map.Entry<?, ?> entry : from.entrySet()) {
            if (entry.getValue() instanceof Map) {
                Object in = into.get(entry.getKey());
                if (in instanceof Map) {
                    merge(((Map) in), ((Map) entry.getValue()));
                } else {
                    into.put(entry.getKey(), entry.getValue());
                }
            } else {
                into.put(entry.getKey(), entry.getValue());
            }
        }
    }

    public <T> T bind(Class<T> clazz) {
        return bind(creteInstance(clazz));
    }

    public <T> T bind(T t) {
        Fields<FieldInfo> fields = new Fields<FieldInfo>(t.getClass());
        for (FieldInfo fieldInfo : fields) {
            FieldReflection reflection = fieldInfo.reflection;
            String name = fieldInfo.field.getName();

            try {
                switch (reflection.getType()) {
                    case BOOLEAN:
                        reflection.setBoolean(t, get(name, Boolean.FALSE));
                        break;
                    case BYTE:
                        reflection.setByte(t, get(name, (byte) 0));
                        break;
                    case CHAR:
                        reflection.setChar(t, get(name, (char) 0));
                        break;
                    case DOUBLE:
                        reflection.setDouble(t, get(name, 0.0));
                        break;
                    case FLOAT:
                        reflection.setFloat(t, get(name, 0.0f));
                        break;
                    case INTEGER:
                        reflection.setInteger(t, get(name, 0));
                        break;
                    case LONG:
                        reflection.setLong(t, get(name, 0l));
                        break;
                    case SHORT:
                        reflection.setShort(t, get(name, (short) 0));
                        break;
                    case OBJECT: {
                        Object o = get(name);

                        if (fieldInfo.generic.clazz.isAssignableFrom(o.getClass())) {
                            reflection.setObject(t, o);
                            break;
                        }
                        Config config = (Config) o;
                        if (config.isEmpty()) {
                            reflection.setObject(t, null);
                            break;
                        }

                        reflection.setObject(t, config.bind(fieldInfo.generic.clazz));
                        break;
                    }
                    default:
                        throw new IllegalStateException("Unknown type of field " + fieldInfo.field);
                }
            } catch (ClassCastException e) {
                Object o = get(name);
                throw new IllegalStateException("Cannot bind '" + o + "' of class " + o.getClass() + " to " + fieldInfo.field);
            }
        }
        return t;
    }

    protected <T> T creteInstance(Class<T> clazz) {
        T t;
        try {
            t = clazz.newInstance();
        } catch (InstantiationException e) {
            throw Unchecked.rethrow(e);
        } catch (IllegalAccessException e) {
            throw Unchecked.rethrow(e);
        }
        return t;
    }
}

@@ -858,32 +858,34 @@
 t(key)) != null)
+ %7B
 %0A            ret
@@ -877,37 +877,211 @@
             
-return value;
+if (value instanceof Config) %7B%0A                return new ProxyConfig(this, (String) key, root, (Config) value);%0A            %7D else %7B%0A                return value;%0A            %7D%0A        %7D
 %0A%0A        pu
@@ -5332,11 +5332,1182 @@
 t;%0A    %7D
+%0A%0A    static class ProxyConfig extends Config %7B%0A        protected Config parent;%0A        protected String key;%0A        protected Config proxy;%0A        protected Config config;%0A%0A        public ProxyConfig(Config parent, String key, Config root, Config proxy) %7B%0A            super(root);%0A            this.parent = parent;%0A            this.key = key;%0A            this.proxy = proxy;%0A        %7D%0A%0A        @Override%0A        public Object get(Object key) %7B%0A            Object o = proxy.get(key);%0A            if (o instanceof Config)%0A                return new ProxyConfig(this, (String) key, root, (Config) o);%0A            else%0A                return o;%0A        %7D%0A%0A        @Override%0A        public Object execute(ClosureExpression it) %7B%0A            it.get(getConfig());%0A            return config;%0A        %7D%0A%0A        protected Config getConfig() %7B%0A            if (config != null)%0A                return config;%0A            Config config = new Config(root);%0A            parent.put(key, this.config = config);%0A            return config;%0A        %7D%0A%0A        @Override%0A        public Object put(String key, Object value) %7B%0A            return getConfig().put(key, value);%0A        %7D%0A    %7D
 %0A%7D%0A

fix tests