import Foundation

public class JsonUtil {
	
	public static func toModel<T>(_ type: T.Type, value: Any?) -> T? where T: Decodable {
		guard let value = value else {
			return nil
		}
		return toModel(type, value: value)
	}
	
	public static func toModel<T>(_ type: T.Type, value: Any) -> T? where T: Decodable {
		guard let data = try? JSONSerialization.data(withJSONObject: value) else {
			return nil
		}
		let decoder = JSONDecoder()
		decoder.nonConformingFloatDecodingStrategy = .convertFromString(positiveInfinity: "+Infinity", negativeInfinity: "-Infinity", nan: "NaN")
		return try? decoder.decode(type, from: data)
	}
	
	public static func getDictionaryFromJSONString(jsonString: String) -> [String: Any] {
		
		let jsonData: Data = jsonString.data(using: .utf8)!
		
		let dict = try? JSONSerialization.jsonObject(with: jsonData, options: .mutableContainers)
		if dict != nil {
			return (dict as! NSDictionary) as! [String: Any]
		}
		return NSDictionary() as! [String: Any]
	}
	
	public static func toJson(_ object: Any) -> Any {
		if let array = object as? [Any] {
			let isStringArray = object is [String];
			var result = "[";
			for item in array {
				let data = isStringArray ? "\"\(toJsonByObj(item))\"" : toJsonByObj(item);
				result += "\(data),";
			}
			if result.hasSuffix(",") {
				result = String(result.dropLast());
			}
			return result + "]";
		}
		
		return toJsonByObj(object);
	}
	
	private static func toJsonByObj(_ object: Any) -> Any {
		
		if object is String {
			return "\(object)";
		}
		
		if object is Int32 || object is Int || object is UInt32 || object is UInt64 || object is Bool || object is Double || object is time_t || object is Date || object is Data || object is Dictionary<AnyHashable, Any> {
			return vHandler(object);
		}
		
		var result = "{";
		let morror = Mirror.init(reflecting: object)
		let superMorror = morror.superclassMirror
		var dict: Dictionary<String?, Any> = [:];
		
		if superMorror != nil {
			for (name, value) in (superMorror?.children)! {
				dict[name!] = value;
			}
		}
		
		for (name, value) in morror.children {
			dict[name!] = value;
		}
		
		for (name, value) in dict {
			if let n = name {
				let v = unwrap(value);
				if !("\(type(of: v))".hasPrefix("Optional")) {
					result += kv(n, v);
					result += ",";
				}
			}
		}
		
		if result.hasSuffix(",") {
			result = String(result.dropLast());
		}
		
		return result + "}";
	}
	
	private static func unwrap<T>(_ any: T) -> Any {
		let mirror = Mirror(reflecting: any)
		guard mirror.displayStyle == .optional, let first = mirror.children.first else {
			return any
		}
		return first.value
	}
	
	private static func kv(_ k: Any, _ v: Any) -> String {
		return "\"\(k)\":\(vHandler(v))";
	}
	
	private static func vHandler(_ v: Any) -> Any {
		if v is String {
			return "\"\(stringReplace(source: "\(v)"))\"";
		} else if v is Int32 || v is Int || v is UInt || v is UInt32 || v is UInt64 || v is Bool || v is Double || v is time_t {
			return v;
		} else if v is Date {
			return Int((v as! Date).timeIntervalSince1970);
		} else if v is Data {
			return "\"\(stringReplace(source: String(data: v as! Data, encoding: String.Encoding.utf8)!))\"";
		} else if v is Dictionary<AnyHashable, Any> {
			var result = "{";
			for (key, value) in v as! Dictionary<AnyHashable, Any> {
				result += "\(kv(key, value)),";
			}
			if result.hasSuffix(",") {
				result = String(result.dropLast());
			}
			return result + "}";
		} else if v is NSObject {
			return toJson(v);
		} else {
			return "\"\(v)\"";
		}
	}

	private static func stringReplace(source: String) -> String {
		var result = source;
		
		result = result.replacingOccurrences(of: "\\", with: "\\\\");
		result = result.replacingOccurrences(of: "\"", with: "\\\"");
		result = result.replacingOccurrences(of: "/", with: "\\/");
		result = result.replacingOccurrences(of: "\\\\b", with: "\\b");
		result = result.replacingOccurrences(of: "\\\\f", with: "\\f");
		result = result.replacingOccurrences(of: "\n", with: "\\n");
		result = result.replacingOccurrences(of: "\r", with: "\\r");
		result = result.replacingOccurrences(of: "\t", with: "\\t");
		result = result.replacingOccurrences(of: "\0", with: "");
		
		return result;
	}
}
