package com.eactive.eai.common.util;

import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.regex.Pattern;

import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.math.NumberUtils;
import org.json.simple.JSONArray;
import org.json.simple.JSONObject;
import org.json.simple.JSONValue;
import org.json.simple.parser.JSONParser;
import org.json.simple.parser.ParseException;

public class JSONUtils {

	private JSONUtils() {
		throw new IllegalStateException("Utility class");
	}
	
	public static Object parseJsonGeneric(Object jsonData) {
		if (jsonData == null) {
			return new JSONObject();
		}
		if (jsonData instanceof JSONObject) {
			return (JSONObject) jsonData;
		} else if (jsonData instanceof String) {
			if (StringUtils.isBlank((String) jsonData)) {
				return new JSONObject();
			}
			try {
				JSONParser parser = new JSONParser();
				Object parsedObj = parser.parse((String) jsonData);
				return parsedObj;
			} catch (ParseException e) {
				e.printStackTrace();
				return null;
			}
		} else
			throw new IllegalArgumentException("not support json type. " + jsonData.getClass());
	}

	

	public static JSONObject parseJson(Object message) {
		if (message == null) {
			return new JSONObject();
		}
		if (message instanceof JSONObject)
			return (JSONObject) message;
		else if (message instanceof String)
			try {
				return (JSONObject) JSONValue.parseWithException((String) message);
			} catch (ParseException e) {
				throw new RuntimeException("json parsing exception : " + message, e);
			}
		else
			throw new IllegalArgumentException("not support json type. " + message.getClass());
	}
	
	public static JSONArray parseJsonArray(Object message) {
		if (message == null) {
			return new JSONArray();
		}
		if (message instanceof JSONArray)
			return (JSONArray) message;
		else if (message instanceof String)
			try {
				return (JSONArray) JSONValue.parseWithException((String) message);
			} catch (ParseException e) {
				throw new RuntimeException("json parsing exception : " + message, e);
			}
		else
			throw new IllegalArgumentException("not support json type. " + message.getClass());
	}
	
	@SuppressWarnings("unchecked")
	public static Set<Map.Entry<String, Object>> getField(JSONObject json) {
		return json.entrySet();
	}

	public static JSONObject getChildJson(JSONObject json, String childName) {
		return (JSONObject)json.get(childName);
	}
	
	@SuppressWarnings("unchecked")
	public static void setChild(JSONObject json, String childName, Object value) {
			json.put(childName, value);
	}
	
	@SuppressWarnings("unchecked")
	public static void setChildWithPath(JSONObject json, String path, Object value) {
		String[] keys = path.split("\\.");
		JSONObject current = json;
		for(int i = 0; i < keys.length - 1; i++) {
			current = (JSONObject) current.get(keys[i]);
			if(current == null)
				return;
		}
		current.put(keys[keys.length - 1], value);
	}
	
	/**
	 * json array의 String 값의 ',' 구분자
	 * @param jsonArray
	 * @return
	 */
	public static String toString(JSONArray jsonArray) {
		StringBuilder sb = new StringBuilder();
		boolean first = true;
		for(Object ele : jsonArray ) {
			if(ele instanceof String || ele instanceof Number) {
				if(first)
					first = false;
				else
					sb.append(",");
				sb.append(ele);
			}
			else
				throw new IllegalArgumentException("not support type - " + ele.getClass().getName());
		}
		
		return sb.toString();
	}
	
	public static String toString2(JSONArray jsonArray) {
		String strJsonArray = jsonArray.toJSONString();
		strJsonArray = replace(strJsonArray);
		
		return strJsonArray;
	}

	private static String replace(String strJsonArray) {
		strJsonArray = StringUtils.replace(strJsonArray, "\"", "");
		strJsonArray = StringUtils.replace(strJsonArray, "[", "");
		strJsonArray = StringUtils.replace(strJsonArray, "]", "");
		return strJsonArray;
	}
	
	public static JSONArray getJSONArray(JSONObject jsonObject, String path) {
		if(jsonObject == null || path == null || path.isEmpty())
			return null;
		
		String[] keys = path.split("\\.");
		JSONObject current = jsonObject;
		for(int i = 0; i < keys.length - 1; i++) {
			current = (JSONObject) current.get(keys[i]);
			if(current == null)
				return null;
		}
		
		return (JSONArray)current.get(keys[keys.length - 1]);
	}
	
	public static String getStringWithPath(JSONObject jsonObject, String path) {
		if(jsonObject == null || path == null || path.isEmpty())
			return null;
		
		String[] keys = path.split("\\.");
		JSONObject current = jsonObject;
		for(int i = 0; i < keys.length - 1; i++) {
			current = (JSONObject) current.get(keys[i]);
			if(current == null)
				return null;
		}
		
		return (String)current.get(keys[keys.length - 1]);
	}
	
	public static String removeStringWithPath(JSONObject jsonObject, String path) {
		if(jsonObject == null || path == null || path.isEmpty())
			return null;
		
		String[] keys = path.split("\\.");
		JSONObject current = jsonObject;
		for(int i = 0; i < keys.length - 1; i++) {
			current = (JSONObject) current.get(keys[i]);
			if(current == null)
				return null;
		}
		
		return (String)current.remove(keys[keys.length - 1]);
	}
	
	public static void removeEmptyWithPath(JSONObject jsonObject, String path) {
		if(jsonObject == null || path == null || path.isEmpty())
			return;
		
		String[] keys = path.split("\\.");
		JSONObject current = jsonObject;
		for(int i = 0; i < keys.length - 1; i++) {
			current = (JSONObject) current.get(keys[i]);
			if(current == null)
				return;
		}
		String value = (String) current.get(keys[keys.length - 1]);
		if (StringUtils.isEmpty(value)) {
			current.remove(keys[keys.length - 1]);
			return;
		} else {
			return;
		}
	}
	
	protected static com.eactive.eai.common.util.Logger logger = com.eactive.eai.common.util.Logger
			.getLogger(com.eactive.eai.common.util.Logger.LOGGER_DEFAULT);

	public static void removeEmptyWithPath2(JSONObject jsonObject, String path) {
		if (jsonObject == null || path == null || path.isEmpty())
			return;

		String[] keys = path.split("\\.");
		Object current = jsonObject;
		for (int i = 0; i < keys.length; i++) {
			String key = keys[i];
			if (current instanceof JSONObject) {
				JSONObject currentObj = (JSONObject) current;
				if (currentObj.get(key) == null) {
					if (i == keys.length - 1) {
						// 마지막
						currentObj.remove(key);
						return;
					} else {
						logger.debug("경로를 찾을 수 없음. {} of {}", path, key);
						return;
					}
				} else {
					if (i == keys.length - 1) {
						if (currentObj.get(key) instanceof String && StringUtils.isEmpty((String)currentObj.get(key))) {
							// 마지막
							currentObj.remove(key);
							return;
						} else {
							logger.debug("경로 값이 빈값이 아님. {} of {}'s value [{}]", path, key, currentObj.get(key));
							return;
						}
					} else {
						current = currentObj.get(key);
					}
				}
			} else if (current instanceof JSONArray) {
				JSONArray currentArray = (JSONArray) current;
				for (int j = 0; j < currentArray.size(); j++) {
					Object arrayElement = currentArray.get(j);
					if (arrayElement instanceof JSONObject) {
						List<String> lastPath = new ArrayList<>();
						for (int k = i; k < keys.length; k++)
							lastPath.add(keys[k]);

						removeEmptyWithPath2((JSONObject) arrayElement, String.join(".", lastPath));
					}
				}
			}
		}
	}
	
	public static void removeZeroWithPath2(JSONObject jsonObject, String path) {
		if (jsonObject == null || path == null || path.isEmpty())
			return;

		String[] keys = path.split("\\.");
		Object current = jsonObject;
		for (int i = 0; i < keys.length; i++) {
			String key = keys[i];
			if (current instanceof JSONObject) {
				JSONObject currentObj = (JSONObject) current;
				if (currentObj.get(key) == null) {
					if (i == keys.length - 1) {
						logger.debug("경로 값이 null. {} of {}", path, key);
						return;
					} else {
						logger.debug("경로를 찾을 수 없음. {} of {}", path, key);
						return;
					}
				} else {
					if (i == keys.length - 1) {
						if (isZero(key, currentObj)) {
							// 마지막
							currentObj.remove(key);
							return;
						} else {
							logger.debug("경로 값이 0이 아님. {} of {}'s value [{}]", path, key, currentObj.get(key));
							return;
						}
					} else {
						current = currentObj.get(key);
					}
				}
			} else if (current instanceof JSONArray) {
				JSONArray currentArray = (JSONArray) current;
				for (int j = 0; j < currentArray.size(); j++) {
					Object arrayElement = currentArray.get(j);
					if (arrayElement instanceof JSONObject) {
						List<String> lastPath = new ArrayList<>();
						for (int k = i; k < keys.length; k++)
							lastPath.add(keys[k]);

						removeZeroWithPath2((JSONObject) arrayElement, String.join(".", lastPath));
					}
				}
			}
		}
	}

	private static final Pattern numberPattern = Pattern.compile("[+-]?\\d*(\\.\\d+)?");

	private static boolean isZero(String key, JSONObject currentObj) {
		Object value = currentObj.get(key);
		if (value instanceof Number && ((Number) value).doubleValue() == 0) {
			return true;
		} else if (value instanceof String && StringUtils.isNotEmpty((String) value)
				&& numberPattern.matcher((String) value).matches()) {
			try {
				return Double.parseDouble((String) value) == 0.0;
			} catch (NumberFormatException e) {
				logger.debug("숫자 형식이 아님. [{}]", value);
				return false;
			}
		}

		return false;
	}
	
	/**
	 * (문자열)'"aaa", "bbb", "ccc"' -> (json array)["aaa", "bbb", "ccc"]
	 * @param doublequotedSeperatedStr
	 * @return
	 */
	public static JSONArray createJSONArray(String doublequotedSeperatedStr) {
		return (JSONArray) JSONValue.parse("[" + doublequotedSeperatedStr + "]");
	}
	
	@SuppressWarnings("unchecked")
	public static JSONArray createJSONArray(String[] strArray ) {
		JSONArray jsonArray = new JSONArray();
		for(String str : strArray) {
			jsonArray.add(str);
		}
		return jsonArray;
	}
	
	@SuppressWarnings("unchecked")
	public static JSONArray createJSONArray(List<String> strList ) {
		JSONArray jsonArray = new JSONArray();
		for(String str : strList) {
			jsonArray.add(str);
		}
		return jsonArray;
	}

	@SuppressWarnings("unchecked")
	public static JSONObject replaceSpacesWith(JSONObject json, String plus) {
		if (json == null || plus == null || plus.isEmpty())
			return json;
		
		JSONObject result = new JSONObject();
		
		for (Object key : json.keySet()) {
			Object value = json.get(key);
			
			if(value instanceof String) {
				result.put(key, ((String)value).replace(" ", plus));
			} else if (value instanceof JSONObject) {
				result.put(key, replaceSpacesWith((JSONObject)value, plus));
			} else if ( value instanceof JSONArray) {
				result.put(key, replaceSpacesInArrayWith((JSONArray)value, plus));
			} else {
				result.put(key, value);
			}
		}
		return result;
	}
	
	@SuppressWarnings("unchecked")
	public static JSONArray replaceSpacesInArrayWith(JSONArray array, String plus) {
		JSONArray result = new JSONArray();
		for(int i =0; i < array.size(); i++) {
			Object value = array.get(i);
			
			if(value instanceof String) {
				result.add(((String)value).replace(" ", plus));
			} else if(value instanceof JSONObject) {
				result.add(replaceSpacesWith((JSONObject)value, plus));
			} else if(value instanceof JSONArray) {
				result.add(replaceSpacesInArrayWith((JSONArray)value, plus));
			} else {
				result.add(value);
			}
		}
		return result;
	}
	
	@SuppressWarnings("unchecked")
	public static JSONObject stripTrailingZeros(JSONObject json, String exceptionHeaderName) {
		if (json == null)
			return json;
		
		JSONObject result = new JSONObject();
		
		for (Object key : json.keySet()) {
			Object value = json.get(key);
			
			if(value instanceof Number) {
				BigDecimal bd = new BigDecimal(value.toString());
				BigDecimal stripped = bd.stripTrailingZeros();
				if(stripped.scale() <= 0)
					result.put(key, stripped.intValue());
				else
					result.put(key, stripped);
				
			} else if (value instanceof JSONObject) {
				result.put(key, stripTrailingZeros((JSONObject)value, exceptionHeaderName));
			} else if ( value instanceof JSONArray) {
				result.put(key, stripTrailingZerosInArrayWith((JSONArray)value, exceptionHeaderName));
			} else {
				result.put(key, value);
			}
		}
		return result;
	}
	
	@SuppressWarnings("unchecked")
	public static JSONArray stripTrailingZerosInArrayWith(JSONArray array, String exceptionHeaderName) {
		JSONArray result = new JSONArray();
		for(int i =0; i < array.size(); i++) {
			Object value = array.get(i);
			
			if(value instanceof Number) {
				BigDecimal bd = new BigDecimal(value.toString());
				BigDecimal stripped = bd.stripTrailingZeros();
				if(stripped.scale() <= 0)
					result.add(stripped.intValue());
				else
					result.add(stripped);
			} else if(value instanceof JSONObject) {
				result.add(stripTrailingZeros((JSONObject)value, exceptionHeaderName));
			} else if(value instanceof JSONArray) {
				result.add(stripTrailingZerosInArrayWith((JSONArray)value, exceptionHeaderName));
			} else {
				result.add(value);
			}
		}
		return result;
	}
}
