package com.priusis.client.util;

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.fasterxml.jackson.databind.node.ObjectNode;
import com.priusis.client.data.kv.*;
import lombok.extern.slf4j.Slf4j;

import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.*;

/**
 * Created by priusis on 19.01.17.
 */
@Slf4j
public class JsonTools {

    private static final ObjectMapper JSON = new ObjectMapper();

    public static ObjectNode newNode() {
        return JSON.createObjectNode();
    }

    public static byte[] toBytes(ObjectNode node) {
        return toString(node).getBytes(StandardCharsets.UTF_8);
    }

    public static byte[] toNodeBytes(ObjectNode node) {
        return toString2(node).getBytes(StandardCharsets.UTF_8);
    }

    private static String toString2(ObjectNode node) {
        try {
            Map<String, Object> nodeData = new HashMap<>();
            for (Iterator<Map.Entry<String, JsonNode>> it = node.fields(); it.hasNext(); ) {
                Map.Entry<String, JsonNode> field = it.next();
                String key = field.getKey();
                JsonNode value = field.getValue();
                nodeData.put(key, value);
            }
            return JSON.writeValueAsString(nodeData);
        } catch (JsonProcessingException e) {
            throw new RuntimeException(e);
        }
    }

    public static JsonNode fromString(String data) {
        try {
            return JSON.readTree(data);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    public static <T> T fromString(String data, TypeReference<T> type) {
        try {
            return JSON.readValue(data, type);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    public static String toString(JsonNode node) {
        try {
            return JSON.writeValueAsString(node);
        } catch (JsonProcessingException e) {
            throw new RuntimeException(e);
        }
    }

    public static String toString(Map<String, String> map) {
        try {
            return JSON.writeValueAsString(map);
        } catch (JsonProcessingException e) {
            throw new RuntimeException(e);
        }
    }

    public static void putToNode(ObjectNode node, KvEntry kv) {
        switch (kv.getDataType()) {
            case BOOLEAN:
                node.put(kv.getKey(), kv.getBooleanValue().get());
                break;
            case STRING:
                node.put(kv.getKey(), kv.getStrValue().get());
                break;
            case LONG:
                node.put(kv.getKey(), kv.getLongValue().get());
                break;
            case DOUBLE:
                node.put(kv.getKey(), kv.getDoubleValue().get());
                break;
            case JSON:
                try {
                    node.put(kv.getKey(), JSON.readTree(kv.getJsonValue().get()));
                } catch (JsonProcessingException e) {
                    log.error("json parse exception ", e);
                }
                break;
        }
    }

    public static List<KvEntry> getKvEntries(JsonNode data) {
        List<KvEntry> attributes = new ArrayList<>();
        for (Iterator<Map.Entry<String, JsonNode>> it = data.fields(); it.hasNext(); ) {
            Map.Entry<String, JsonNode> field = it.next();
            String key = field.getKey();
            JsonNode value = field.getValue();
            if (value.isObject()) {
                attributes.add(new JsonDataEntry(key, value.toPrettyString()));
            } else if (value.isBoolean()) {
                attributes.add(new BooleanDataEntry(key, value.asBoolean()));
            } else if (value.isDouble()) {
                attributes.add(new DoubleDataEntry(key, value.asDouble()));
            } else if (value.canConvertToLong()) {
                attributes.add(new LongDataEntry(key, value.asLong()));
            } else {
                attributes.add(new StringDataEntry(key, value.toString()));
            }
        }
        return attributes;
    }
}
