package com.yvan.platform;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.base.Joiner;
import lombok.NonNull;
import lombok.SneakyThrows;
import lombok.val;
import org.joda.time.DateTime;

import java.io.IOException;
import java.io.InputStream;
import java.math.BigDecimal;
import java.sql.Timestamp;
import java.util.*;

/**
 * 帮助生成和访问Json-Map对象的帮助类 Created by luoyifan on 2016/3/30.
 */
@SuppressWarnings({"unchecked", "rawtypes"})
public class JsonWapper {

    private static final ObjectMapper mapper = YvanUtil.objectMapper;
    private Map innerMap;

    public JsonWapper(@NonNull Map innerMap) {
        this.innerMap = innerMap;
    }

    public JsonWapper(@NonNull Object obj) throws JsonProcessingException {
        this(mapper.writeValueAsString(obj));
    }

    public JsonWapper() {
        this(new LinkedHashMap());
    }

    public JsonWapper(InputStream stream) throws IOException {
        this(mapper.readValue(stream, LinkedHashMap.class));
    }

    public JsonWapper(@NonNull String jsonString) {
        this(JsonToMap(jsonString));
    }

    public JsonWapper resetInnerMap(@NonNull Map innerMap) {
        this.innerMap = innerMap;
        return this;
    }

    @SneakyThrows
    public JsonWapper resetInnerMap(@NonNull Object obj) {
        return resetInnerMap(mapper.writeValueAsString(obj));
    }

    @SneakyThrows
    public JsonWapper resetInnerMap(@NonNull JsonNode jsonNode) {
        this.innerMap = JsonToMap(mapper.writeValueAsString(jsonNode));
        return this;
    }

    public JsonWapper resetInnerMap(@NonNull String jsonString) {
        this.innerMap = JsonToMap(jsonString);
        return this;
    }

    @SneakyThrows
    private static Map JsonToMap(String jsonString) {
        return mapper.readValue(jsonString, LinkedHashMap.class);
    }

    public Map<String, ?> getInnerMap() {
        return (Map<String, ?>) innerMap;
    }

    public boolean contains(String... args) {
        assert (args.length >= 1);
        List<String> lst = Arrays.asList(args);
        Map cnode = this.innerMap;
        for (int i = 0; i < lst.size() - 1; i++) {
            String v = lst.get(i);
            if (!cnode.containsKey(v) || !(cnode.get(v) instanceof Map)) {
                return false;
            }
            cnode = (Map) cnode.get(v);
        }
        return (cnode.containsKey(lst.get(lst.size() - 1)));
    }

    public Object get(String... args) {
        assert (args.length >= 1);
        if (args.length == 1 && args[0].startsWith("$.")) {
            return getByPath(args[0]);
        }
        List<String> lst = Arrays.asList(args);
        Map cnode = this.innerMap;
        for (int i = 0; i < lst.size() - 1; i++) {
            String v = lst.get(i);
            if (!cnode.containsKey(v)) {
                return null;
            }
            cnode = (Map) cnode.get(v);
        }
        if (cnode == null || !cnode.containsKey(lst.get(lst.size() - 1))) {
            return null;
        }
        return cnode.get(lst.get(lst.size() - 1));
    }

    public Object getByPath(String path) {
        //throw new NotImplementedException();
        return null;
    }

    public boolean remove(String... args) {
        assert (args.length >= 1);
        List<String> lst = Arrays.asList(args);
        Map cnode = this.innerMap;
        for (int i = 0; i < lst.size() - 1; i++) {
            String v = lst.get(i);
            if (!cnode.containsKey(v)) {
                return false;
            }
            cnode = (Map) cnode.get(v);
        }
        if (cnode == null || !cnode.containsKey(lst.get(lst.size() - 1))) {
            return false;
        }
        cnode.remove(lst.get(lst.size() - 1));
        return true;
    }


    public Map<String, Object> asMap(String... args) {
        assert (args.length >= 1);
        List<String> lst = Arrays.asList(args);
        Map jb = null;
        if (lst.size() >= 2) {
            jb = buildPath(lst.subList(0, lst.size() - 1));
        } else {
            jb = innerMap;
        }
        Object v = lst.get(lst.size() - 1);
        Map lr = null;
        if (!jb.containsKey(v)) {
            lr = new LinkedHashMap<String, Object>();
            jb.put(v, lr);
        } else {
            lr = (Map) jb.get(v);
        }
        return lr;
    }

    public String asStr(String... args) {
        return Conv.NS(get(args));
    }

    public BigDecimal asBigDec(String... args) {
        return Conv.NDec(get(args));
    }

    public <T> T asObject(Class<T> clazz) {
        try {
            String jsonStr = mapper.writeValueAsString(getInnerMap());
            return mapper.readValue(jsonStr, clazz);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    public <T> T asObject(Class<T> clazz, String... args) {
        try {
            String jsonStr = mapper.writeValueAsString(get(args));
            return mapper.readValue(jsonStr, clazz);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    public <T> T asObject(TypeReference valueTypeRef, String... args) {
        try {
            String jsonStr = mapper.writeValueAsString(get(args));
            return mapper.readValue(jsonStr, valueTypeRef);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    public int asInt(String... args) {
        return Conv.NI(get(args));
    }

    public long asLong(String... args) {
        return Conv.NL(get(args));
    }

    public boolean asBoolean(String... args) {
        return Conv.NB(get(args));
    }

    public Collection<String> keys(String... args) {
        if (args == null || args.length == 0) {
            return innerMap.keySet();
        }
        return ((Map) get(args)).keySet();
    }

    public int size() {
        return innerMap.size();
    }

    public JsonWapper asDic(String... args) {
        val map = asMap(args);
        if (map == null) {
            return new JsonWapper();
        }
        return new JsonWapper(map);
    }

    @SneakyThrows
    public Iterable<JsonWapper> asJsonWapperList(String... args) {
        val list = asList(args);

        return new Iterable<JsonWapper>() {
            @Override
            public Iterator<JsonWapper> iterator() {
                val itor = list.iterator();
                return new Iterator<JsonWapper>() {
                    @Override
                    public boolean hasNext() {
                        return itor.hasNext();
                    }

                    @Override
                    @SneakyThrows
                    public JsonWapper next() {
                        val it = itor.next();
                        if (it == null || "".equals(it)) {
                            return null;
                        }
                        return new JsonWapper(it);
                    }
                };
            }
        };
    }

    public <T> List<T> asList(String... args) {
        assert (args.length >= 1);
        List<String> lst = Arrays.asList(args);
        Map jb = null;
        if (lst.size() >= 2) {
            jb = buildPath(lst.subList(0, lst.size() - 1));
        } else {
            jb = innerMap;
        }
        Object v = lst.get(lst.size() - 1);
        List lr = null;
        if (!jb.containsKey(v)) {
            lr = new ArrayList();
            jb.put(v, lr);
        } else {
            lr = (List) jb.get(v);
        }
        return lr;
    }

    private Map buildPath(List<?> lst) {
        Map cnode = this.innerMap;
        for (Object obj : lst) {
            assert ((obj instanceof String) && obj.toString().length() > 0);
            String v = (String) obj;
            if (!cnode.containsKey(v)) {
                cnode.put(v, new LinkedHashMap<String, Object>());
            }
            Object objTarget = cnode.get(v);
            if (objTarget instanceof JsonWapper) {
                cnode = ((JsonWapper) objTarget).innerMap;
            } else if (objTarget instanceof Map) {
                cnode = (Map) objTarget;
            } else {
                throw new RuntimeException(String.format(
                        "node ClassCastException [%s] Class is %s ", Joiner.on(".").join(lst), objTarget.getClass().getName()
                ));
            }
        }
        return cnode;
    }

    @SneakyThrows
    public JsonWapper set(Object... args) {
        assert (args.length >= 2);
        List<Object> lst = Arrays.asList(args);
        Map jb = buildPath(lst.subList(0, lst.size() - 2));
        Object v = lst.get(lst.size() - 1);

        if (v == null ||
                v instanceof String || v instanceof Double ||
                v instanceof Integer || v instanceof BigDecimal ||
                v instanceof Long || v instanceof Short ||
                v instanceof Character || v instanceof Boolean ||
                v instanceof Date || v instanceof DateTime) {
            //igorn

        } else if (v instanceof JsonWapper) {
            v = ((JsonWapper) v).innerMap;

        } else if (v instanceof Timestamp) {
            v = ((Timestamp) v).getTime();

        } else if (v instanceof Date) {
            v = ((Date) v).getTime();

        } else if (v.getClass().isArray() || v instanceof Collection) {
            v = YvanUtil.jsonToObj(YvanUtil.toJson(v), List.class);

        } else {
            v = new JsonWapper(v).innerMap;

        }

        jb.put(lst.get(lst.size() - 2).toString(), v);
        return this;
    }

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

    public String toString(boolean pretty) {
        try {
            if (pretty) {
                return YvanUtil.toJsonPretty(innerMap);
            } else {
                return YvanUtil.toJson(innerMap);
            }
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    public String toYaml() {
        return YvanUtil.toYaml(innerMap);
    }

    public boolean isArray(String... key) {
        Object obj = get(key);
        if (obj == null) {
            return false;
        }
        if (obj.getClass().isArray()) return true;
        if (obj instanceof Collection) return true;
        return false;
    }

    public boolean isObject(String... key) {
        Object obj = get(key);
        if (obj == null) {
            return false;
        }
        if (obj instanceof String) return false;
        if (obj instanceof Character) return false;

        if (obj instanceof BigDecimal) return false;
        if (obj instanceof Double) return false;
        if (obj instanceof Float) return false;
        if (obj instanceof Long) return false;
        if (obj instanceof Integer) return false;
        if (obj instanceof Short) return false;
        if (obj instanceof Boolean) return false;

        if (obj.getClass().isArray()) return false;
        if (obj instanceof Collection) return false;

        return true;
    }

    public JsonWapper merge(Map updateMap) {
        return this.merge(updateMap, "");
    }

    public JsonWapper merge(Map updateMap, String prefix) {
        for (val entryObj : updateMap.entrySet()) {
            val entry = (Map.Entry<String, Object>) entryObj;
            innerMap.put(prefix + entry.getKey(), entry.getValue());
        }
        return this;
    }

}
