package org.implab.gradle.common.utils;

import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Objects;
import java.util.function.Function;
import java.util.stream.Stream;

import groovy.lang.Closure;
import groovy.lang.GroovyObjectSupport;

public class JsonDelegate extends GroovyObjectSupport {
    private final Map<String, Object> content;

    private JsonDelegate() {
        content = new LinkedHashMap<>();
    }

    private JsonDelegate(Map<String, Object> content) {
        this.content = new LinkedHashMap<>(content);
    }

    public Object invokeMethod(String name, Object args) {
        Object val = null;
        if (args != null && Object[].class.isAssignableFrom(args.getClass())) {
            Object[] arr = (Object[]) args;
            if (arr.length == 1) {
                val = processValue(arr[0], content.get(name));
            } else if (arr.length == 2 && arr[1] instanceof Closure<?> c) {
                if (arr[0] instanceof Iterable<?> iter) {
                    val = processStream(Values.stream(iter.iterator()), c);
                } else if(arr[0] != null && arr[0].getClass().isArray()) {
                    val = processStream(Stream.of((Object)arr[0]), c);
                } else {
                    val = arr;
                }
            } else {
                val = arr;
            }
        }

        this.content.put(name, val);
        return val;
    }

    private Object processValue(Object value, Object original) {
        if (value instanceof Closure<?> c) {
            return Objects.nonNull(original) ? of(c, original) : of(c);
        } else {
            return value;
        }
    }

    private Object processStream(Stream<?> stream, Closure<?> c) {
        return stream.map(transform(c)).toArray();
    }

    public static Map<String, Object> of(Closure<?> c) {
        return invoke((Closure<?>) c.clone(), new JsonDelegate());
    }

    public static Function<Object, Map<String, Object>> transform(Closure<?> c) {
        return o -> of(c, o);
    }

    public static Map<String, Object> of(Closure<?> c, Object o) {
        return invoke((Closure<?>) c.curry(o), new JsonDelegate());
    }

    public static Map<String, Object> with(Closure<?> c, Map<String, Object> m) {
        return invoke((Closure<?>) c.clone(), new JsonDelegate(m));
    }

    private static Map<String, Object> invoke(Closure<?> c, JsonDelegate d) {
        c.setDelegate(d);
        c.setResolveStrategy(1);
        c.call();
        return d.getContent();
    }

    public Map<String, Object> getContent() {
        return this.content;
    }
}
