/*******************************************************************************
 * Copyright 2018 572682
 * 
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
 * use this file except in compliance with the License.  You may obtain a copy
 * of the License at
 * 
 *   http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
 * License for the specific language governing permissions and limitations under
 * the License.
 ******************************************************************************/
package us.dot.its.jpo.ode.util;

import java.io.IOException;
import java.math.BigDecimal;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map.Entry;

import org.json.JSONObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.annotation.JsonAutoDetect.Visibility;
import com.fasterxml.jackson.annotation.JsonInclude.Include;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.cfg.CoercionAction;
import com.fasterxml.jackson.databind.cfg.CoercionInputShape;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.fasterxml.jackson.databind.type.LogicalType;

public class JsonUtils {

   public static class JsonUtilsException extends Exception {

      private static final long serialVersionUID = 1L;

      public JsonUtilsException(String string, Exception e) {
         super(string, e);
      }

   }

   private static ObjectMapper mapper;
   private static ObjectMapper mapper_noNulls;
   private static Logger logger;

   private JsonUtils() {
      logger = LoggerFactory.getLogger(JsonUtils.class);
   }

   static {
      mapper = new ObjectMapper();
      mapper.setVisibility(PropertyAccessor.FIELD, Visibility.ANY);
      mapper.coercionConfigFor(LogicalType.Enum)
            .setCoercion(CoercionInputShape.EmptyString, CoercionAction.AsNull);

      mapper_noNulls = new ObjectMapper();
      mapper_noNulls.setVisibility(PropertyAccessor.FIELD, Visibility.ANY);
      mapper_noNulls.setSerializationInclusion(Include.NON_NULL);
   }

   public static String toJson(Object o, boolean verbose) {
      // convert java object to JSON format,
      // and returned as JSON formatted string
      try {
         return verbose ? mapper.writeValueAsString(o) : mapper_noNulls.writeValueAsString(o);
      } catch (JsonProcessingException e) {
         e.printStackTrace();
         return "";
      }
   }

   public static Object fromJson(String s, Class<?> clazz) {
      try {
         return jacksonFromJson(s, clazz);
      } catch (JsonUtilsException e) {
         e.printStackTrace();
         return null;
      }
   }

   public static Object jacksonFromJson(String s, Class<?> clazz) throws JsonUtilsException {
      try {
         return mapper.readValue(s, clazz);
      } catch (IOException e) {
         throw new JsonUtilsException("Error deserializing JSON tree to " + clazz.getName(), e);
      }
   }

   public static Object jacksonFromJson(String s, Class<?> clazz, boolean allowUnknownProperties)
         throws JsonUtilsException {
      try {
         mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, !allowUnknownProperties);
         return mapper.readValue(s, clazz);
      } catch (IOException e) {
         throw new JsonUtilsException("Error deserializing JSON tree to " + clazz.getName(), e);
      } finally {
         mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, allowUnknownProperties);
      }
   }

   public static String newJson(String key, Object value) {
      return newObjectNode(key, value).toString();
   }

   public static ObjectNode cloneObjectNode(ObjectNode src) {
      return src.deepCopy();
   }

   public static ObjectNode newObjectNode(String key, Object value) {
      ObjectNode json = mapper.createObjectNode();
      json.putPOJO(key, value);
      return json;
   }

   public static ObjectNode addNode(ObjectNode tree, String fieldName, Object fieldValue) {
      tree.putPOJO(fieldName, fieldValue);
      return tree;
   }

   public static JsonNode getJsonNode(String tree, String fieldName) {
      JsonNode node = null;
      try {
         JsonNode jsonNode = mapper.readTree(tree);
         node = jsonNode.get(fieldName);

      } catch (IOException e) {
         logger.error("IOException", e);
      }
      return node;
   }

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

   public static ArrayNode newArrayNode() {
      return mapper.createArrayNode();
   }

   public static ObjectNode toObjectNode(String tree) throws JsonUtilsException {
      ObjectNode jsonNode;
      try {
         jsonNode = (ObjectNode) mapper.readTree(tree);
      } catch (Exception e) {
         throw new JsonUtilsException("Error converting JSON tree to ObjectNode", e);
      }
      return jsonNode;
   }

   public static JSONObject toJSONObject(String json) throws JsonUtilsException {
      try {
         return new JSONObject(json);
      } catch (Exception e) {
         throw new JsonUtilsException("Error decoding " + json + "to JSONObject", e);
      }
   }

   public static boolean isValid(String tree) throws IOException {
      try {
         ObjectNode jsonNode = (ObjectNode) mapper.readTree(tree);
         return jsonNode != null;
      } catch (JsonProcessingException jpe) {
         return false;
      }
   }

   public static HashMap<String, JsonNode> jsonNodeToHashMap(JsonNode jsonNode) {
      HashMap<String, JsonNode> nodeProps = new HashMap<String, JsonNode>();
      Iterator<Entry<String, JsonNode>> iter = jsonNode.fields();

      while (iter.hasNext()) {
         Entry<String, JsonNode> element = iter.next();
         nodeProps.put(element.getKey(), element.getValue());
      }
      return nodeProps;
   }

   /**
    * Takes in a key, value pair and returns a valid JSON string such as
    * {"error":"message"}
    * 
    * @param key
    * @param value
    * @return
    */
   public static String jsonKeyValue(String key, String value) {
      return "{\"" + key + "\":\"" + value + "\"}";
   }

   public static BigDecimal decimalValue(JsonNode v) {
      BigDecimal result;
      if (v.isTextual()) {
         result = new BigDecimal(v.textValue());
      } else {
         result = v.decimalValue();
      }
      return result;
   }
}
