package customsocket;

import java.io.FileWriter;
import java.io.IOException;
import java.io.Writer;
import java.util.Iterator;

import org.json.JSONException;
import org.json.JSONObject;

import anywheresoftware.b4a.BA;
import anywheresoftware.b4a.objects.collections.List;
import anywheresoftware.b4a.objects.collections.Map;
import anywheresoftware.b4a.objects.collections.Map.MyMap;
import anywheresoftware.b4a.objects.streams.File;
import anywheresoftware.b4a.objects.streams.File.InputStreamWrapper;



/**
 * JsonObject is almost like a Map
 * it contain key and value pair
 * used mainly in SocketIO javascript
 * 
 * Source : https://github.com/stleary/JSON-java
 * @author rdkartono
 *
 */
@BA.ShortName("JsonObject")
public class JsonObject {
	private JSONObject jo;

	
	@BA.Hide
	/**
	 * Get Native JSONObject object from org.json java
	 * @return JSONObject
	 */
	public JSONObject getObject() {
		return jo;
	}
	
	@BA.Hide
	/**
	 * Set Native JSONObject
	 * @param vv : other value
	 */
	public void setObject(JSONObject vv) {
		jo = vv;
	}
	
	/**
	 * Initialize empty JsonObject
	 */
	public void InitializeEmpty() {
		jo = new JSONObject();
	}
	
	/**
	 * Initialize JsonObject from B4X Map
	 * @param value : B4X Map
	 * @return true if value can be translated to JsonObject
	 */
	public boolean InitializeFromMap(Map value) {
		if (value instanceof Map) {
			if (value.IsInitialized()) {
				MyMap mm = value.getObject();
				if (mm instanceof MyMap) {
					try {
						jo = new JSONObject(mm);
						return (!jo.isEmpty());		// kalau empty, return false. kalau gak empty return true		
					} catch(JSONException | NullPointerException e) {
						BA.Log("Unable to Initialize JsonObject from Map, Msg : "+e.getMessage());
					}
					
				}
			}
		}
		return false;
	}
	
	/**
	 * Initialize JsonObject from JSON String
	 * @param value : String
	 * @return true if value can be translated to JsonObject
	 */
	public boolean InitializeFromString(String value) {
		if (value instanceof String) {
			if (!value.isEmpty()) {
				try {
					jo = new JSONObject(value);
					return (!jo.isEmpty());
				} catch(JSONException e) {
					BA.Log("Unable to Initialize JsonObject from String, Msg : "+e.getMessage());
				}
				
			}
		}
		return false;
	}
	
	/**
	 * Initialize JsonObject from a file containing plain string
	 * Reading result from file, will be used in InitializeFromString function
	 * @param directory : directory path
	 * @param filename : filename
	 * @return true if value can be translated to JsonObject
	 */
	public boolean InitializeFromFile(String directory, String filename) {
		try {
			if (File.Exists(directory, filename)) {
				InputStreamWrapper isw = File.OpenInput(directory, filename);
				if (isw.IsInitialized()) {
					int bytecount = isw.BytesAvailable();
					if (bytecount>0) {
						byte[] bb = new byte[bytecount];
						isw.ReadBytes(bb, 0, bytecount);
						isw.Close();
						String value = new String(bb);
						return InitializeFromString(value);
					}
				}
			}
		} catch (IOException e) {
			BA.Log("InitializeFromFile failed, Msg : "+e.getMessage());
		}
		return false;
	}
	
	/***
	 * Initialize JsonObject from another JsonObject . 
	 * @param othervalue : other JsonObject as copy source
	 * @param keys : array of string, containing keys to copy from other JsonObject
	 * @return true if othervalue can be copied
	 */
	public boolean InitializeFromOtherJsonObject(JsonObject othervalue, String... keys) {
		if (keys!=null) {
			if (keys.length>0) {
				if (othervalue instanceof JsonObject) {
					if (othervalue.HasSomeValue()) {
						jo = new JSONObject(othervalue.getObject(), keys);
						return true;
					}
				}
			}
		}
		return false;
	}
	
	/**
	 * Check if JsonObject is initialized
	 * @return true if inited
	 */
	public boolean IsInitialized() {
		if (jo instanceof JSONObject) {
			return true;
		}
		return false;
	}
	
	/**
	 * Check if JsonObject has been initialized and has some value 
	 * @return true if has value
	 */
	public boolean HasSomeValue() {
		if (jo instanceof JSONObject) {
			if (!jo.isEmpty()) {
				return true;
			}
		}
		return false;
	}
	
	/**
	 * Get List of Keys in this JsonObject
	 * @return B4X List
	 */
	public List GetListofKeys() {
		List result = new List();
		result.Initialize();
		if (HasSomeValue()) {
			Iterator<String> iter = jo.keys();
			iter.forEachRemaining(value->{
				if (value instanceof String) {
					if (!value.isEmpty()) {
						result.Add(value);
					}
				}
				
			});
		}
		return result;
	}
	
	/**
	 * Check if this JsonObject contain spesific key
	 * @param value : String key to search
	 * @return true if it has
	 */
	public boolean ContainKey(String value) {
		if (HasSomeValue()) {
			return jo.has(value);
		}
		return false;
	}
	
	/**
	 * Get JsonObject size
	 * @return -1 if not initialized yet, 0 if empty, positive numbers if has value
	 */
	public int Size() {
		if (jo instanceof JSONObject) {
			return jo.length();
		}
		return -1;
	}
	
	/**
	 * Clear JsonObject contents and keys
	 * @return true if can be cleared
	 */
	public boolean Clear() {
		if (IsInitialized()) {
			jo.clear();
			return true;
		}
		return false;
	}
	
	/**
	 * Remove a content with specific key
	 * @param key : String key to remove
	 * @return Object removed from JsonObject, or null if not available or not initialized
	 */
	public Object Remove(String key) {
		if (IsInitialized()) {
			if (key instanceof String) {
				if (!key.isEmpty()) {
					return jo.remove(key);	
				}
			}
			
		}
		return null;
	}
	
	/**
	 * Get JsonObject key-value in string
	 * @return empty string if JsonObject not initialized, or is empty
	 */
	public String ToString() {
		if (HasSomeValue()) {
			return jo.toString();
		}
		return "";
	}
	
	/**
	 * Get JsonObject key-value in pretty string
	 * @param indentvalue : how many spaces for indentation to be added
	 * @return empty string if JsonObject not initialized or is empty
	 */
	public String ToStringWithIndent(int indentvalue) {
		if (HasSomeValue()) {
			return jo.toString(indentvalue);
		}
		return "";
	}
	
	/**
	 * Write JsonObject to a file , in plain string value
	 * @param directory : directory path
	 * @param filename : filename
	 * @return true if write operation succesful
	 */
	public boolean WriteToFile(String directory, String filename) {
		if (HasSomeValue()) {
			try {
				Writer ww = new FileWriter(File.Combine(directory, filename));
				jo.write(ww);
				ww.close();
				return true;
			} catch(IOException | JSONException e) {
				BA.Log("WriteToFile failed, Msg : "+e.getMessage());
			}
			
		}
		return false;
	}
	
	/**
	 * Write JsonObject to a file, in plain string value
	 * @param directory : directory path
	 * @param filename : filename
	 * @param indentvalue : how many spaces for indentation to be added
	 * @return true if write operation succesful
	 */
	public boolean WriteToFileWithIndent(String directory, String filename, int indentvalue) {
		if (HasSomeValue()) {
			
			try {
				Writer ww = new FileWriter(File.Combine(directory, filename));
				jo.write(ww, indentvalue, indentvalue);
				ww.close();
				return true;
			} catch (IOException | JSONException e) {
				BA.Log("WriteToFileWithIndent failed, Msg : "+e.getMessage());
			}
		}
		return false;
	}
	
	/**
	 * Put a boolean value in JsonObject with specific key
	 * @param key : String key 
	 * @param value : boolean value
	 * @return true if can be added
	 */
	public boolean PutBoolean(String key, boolean value) {
		if (IsInitialized()) {
			if (key instanceof String) {
				if (!key.isEmpty()) {
					try {
						jo.put(key, value);
						return true;
					} catch(JSONException | NullPointerException  e) {
						BA.Log("Unable to PutBoolean, Msg : "+e.getMessage());
					}
					
				}
			}
			
		}
		return false;
	}
	
	/**
	 * Put a double value in JsonObject with specific key
	 * @param key : String key 
	 * @param value : double value
	 * @return true if can be added
	 */
	public boolean PutDouble(String key, double value) {
		if (IsInitialized()) {
			if (key instanceof String) {
				if (!key.isEmpty()) {
					try {
						jo.put(key, value);
						return true;
					} catch(JSONException | NullPointerException  e) {
						BA.Log("Unable to PutDouble, Msg : "+e.getMessage());
					}
					
				}
			}
			
		}
		return false;
	}
	
	/**
	 * Put a float value in JsonObject with specific key
	 * @param key : String key 
	 * @param value : float value
	 * @return true if can be added
	 */
	public boolean PutFloat(String key, float value) {
		if (IsInitialized()) {
			if (key instanceof String) {
				if (!key.isEmpty()) {
					try {
						jo.put(key, value);
						return true;
					} catch(JSONException | NullPointerException  e) {
						BA.Log("Unable to PutFloat, Msg : "+e.getMessage());
					}
					
				}
			}
			
		}
		return false;
	}
	
	/**
	 * Put an Int value in JsonObject with specific key
	 * @param key : String key 
	 * @param value : Int value
	 * @return true if can be added
	 */
	public boolean PutInt(String key, int value) {
		if (IsInitialized()) {
			if (key instanceof String) {
				if (!key.isEmpty()) {
					try {
						jo.put(key, value);
						return true;
					} catch(JSONException | NullPointerException  e) {
						BA.Log("Unable to PutInt, Msg : "+e.getMessage());
					}
					
				}
			}
			
		}
		return false;
	}
	
	/**
	 * Put a long value in JsonObject with specific key
	 * @param key : String key 
	 * @param value : long value
	 * @return true if can be added
	 */
	public boolean PutLong(String key, long value) {
		if (IsInitialized()) {
			if (key instanceof String) {
				if (!key.isEmpty()) {
					try {
						jo.put(key, value);
						return true;
					} catch(JSONException | NullPointerException  e) {
						BA.Log("Unable to PutLong, Msg : "+e.getMessage());
					}
					
				}
			}
			
		}
		return false;
	}
	
	/**
	 * Put an Object value in JsonObject with specific key
	 * @param key : String key 
	 * @param value : Object value
	 * @return true if can be added
	 */
	public boolean PutObject(String key, Object value) {
		if (IsInitialized()) {
			if (key instanceof String) {
				if (!key.isEmpty()) {
					try {
						jo.put(key, value);
						return true;
					} catch(JSONException | NullPointerException  e) {
						BA.Log("Unable to PutObject, Msg : "+e.getMessage());
					}
					
				}
			}
			
		}
		return false;
	}
	
	/**
	 * Put a String value in JsonObject with specific key
	 * @param key : String key 
	 * @param value : String value
	 * @return true if can be added
	 */
	public boolean PutString(String key, String value) {
		if (IsInitialized()) {
			if (key instanceof String) {
				if (!key.isEmpty()) {
					if (value instanceof String) {
						try {
							jo.put(key, value);
							return true;
						} catch(JSONException | NullPointerException  e) {
							BA.Log("Unable to PutString, Msg : "+e.getMessage());
						}	
					}
				}
			}
			
		}
		return false;
	}
	
	/**
	 * Put a B4X Map value to JsonObject with specific kety
	 * @param key : String key
	 * @param value : B4X Map value
	 * @return true if can be added
	 */
	public boolean PutMap(String key, Map value) {
		if (IsInitialized()) {
			if (key instanceof String) {
				if (!key.isEmpty()) {
					if (value instanceof Map) {
				
						MyMap obj = value.getObject();
						if (obj instanceof MyMap) {
							try {
								
								jo.put(key, obj);
								return true;
							} catch(JSONException | NullPointerException  e) {
								BA.Log("Unable to PutMap, Msg : "+e.getMessage());
							}	
						}
					}
				}
			}
		}
		
		return false;
	}
	
	/**
	 * Put a B4X List value to JsonObject with specific key
	 * @param key : String key
	 * @param value  : B4X List value
	 * @return true if can be added
	 */
	public boolean PutList(String key, List value) {
		if (IsInitialized()) {
			if (key instanceof String) {
				if (!key.isEmpty()) {
					if (value instanceof List) {
						if (value.IsInitialized()) {
							if (value.getSize()>0) {
								try {
									jo.put(key, value.getObject());
									return true;
								} catch(JSONException | NullPointerException  e) {
									BA.Log("Unable to PutList, Msg : "+e.getMessage());
								}	
							}
						}
					}
				}
			}
		}
		return false;
	}
	
	/**
	 * Put other JsonObject to this JsonObject with specific key
	 * @param key : String key
	 * @param value : other JsonObject
	 * @return true if can be added
	 */
	public boolean PutJsonObject(String key, JsonObject value) {
		if (IsInitialized()) {
			if (key instanceof String) {
				if (!key.isEmpty()) {
					if (value instanceof JsonObject) {
						if (value.HasSomeValue()) {
							try {
								jo.put(key, value.getObject());
								return true;
							}catch(JSONException | NullPointerException  e) {
								BA.Log("Unable to PutJsonObject, Msg : "+e.getMessage());
							}	
						}
					}
				}
			}
		}
		return false;
	}
	
	/**
	 * Check if this JsonObject is similar key-value with other JsonObject
	 * @param othervalue : other JsonObject to compare
	 * @return true if similar
	 */
	public boolean IsSimilar(JsonObject othervalue) {
		if (IsInitialized()) {
			return jo.similar(othervalue);
		}
		return false;
	}
	
	/**
	 * Get a String value matched with the key
	 * @param key : key linked to value
	 * @param defaultvalue : if specific key not found, return this value
	 * @return String value
	 */
	public String GetStringValue(String key, String defaultvalue) {
		if (ContainKey(key)) {
			try {
				String value =  jo.getString(key);
				return value;
			} catch(JSONException e) {
				BA.Log("Unable to GetStringValue, Msg : "+e.getMessage());
			}
							
		}
		return defaultvalue;
	}
	
	/**
	 * Get a Long value matched with the key
	 * @param key : key linked to value
	 * @param defaultvalue : if specific key not found, return this value
	 * @return long value
	 */
	public long GetLongValue(String key, long defaultvalue) {
		if (ContainKey(key)) {
			try {
				long value = jo.getLong(key);
				return value;
			} catch(JSONException e) {
				BA.Log("Unable to GetLongValue, Msg : "+e.getMessage());
			}
						
		}
		return defaultvalue;
	}
	
	/**
	 * Get a JsonObject value matched with the key
	 * @param key : key linked to value
	 * @param defaultvalue : if specific key not found, return this value
	 * @return JsonObject value
	 */
	public JsonObject GetJsonObject(String key, JsonObject defaultvalue) {
		if (ContainKey(key)) {
			try {
				JSONObject value = jo.getJSONObject(key);
				JsonObject jvalue = new JsonObject();
				jvalue.setObject(value);
				return jvalue;
			} catch(JSONException e) {
				BA.Log("Unable to GetJsonObject, Msg : "+e.getMessage());
			}
			
		}
		return defaultvalue;
	}
	
	/**
	 * Get an int value matched with the key
	 * @param key : key linked to value
	 * @param defaultvalue : if specific key not found, return this value
	 * @return int value
	 */
	public int GetInt(String key, int defaultvalue) {
		if (ContainKey(key)) {
			try {
				int value = jo.getInt(key);
				return value;
			} catch(JSONException e) {
				BA.Log("Unable to GetInt, Msg : "+e.getMessage());
			}
		}
		return defaultvalue;
	}
	
	/**
	 * Get an float value matched with the key
	 * @param key : key linked to value
	 * @param defaultvalue : if specific key not found, return this value
	 * @return float value
	 */
	public float GetFloat(String key, float defaultvalue) {
		if (ContainKey(key)) {
			try {
				float value = jo.getFloat(key);
				return value;
			} catch(JSONException e) {
				BA.Log("Unable to GetFloat, Msg : "+e.getMessage());
			}
		}
		return defaultvalue;
	}
	
	/**
	 * Get an double value matched with the key
	 * @param key : key linked to value
	 * @param defaultvalue : if specific key not found, return this value
	 * @return double value
	 */
	public double GetDouble(String key, double defaultvalue) {
		if (ContainKey(key)) {
			try {
				double value = jo.getDouble(key);
				return value;
			} catch(JSONException e) {
				BA.Log("Unable to GetDouble, Msg : "+e.getMessage());
			}
		}
		return defaultvalue;
	}
	
	/**
	 * Get an boolean value matched with the key
	 * @param key : key linked to value
	 * @param defaultvalue : if specific key not found, return this value
	 * @return boolean value
	 */
	public boolean GetBoolean(String key, boolean defaultvalue) {
		if (ContainKey(key)) {
			try {
				boolean value = jo.getBoolean(key);
				return value;
			} catch(JSONException e) {
				BA.Log("Unable to GetBoolean, Msg : "+e.getMessage());
			}
		}
		return defaultvalue;
	}
	
	/**
	 * Get an Object value matched with the key
	 * @param key : key linked to value
	 * @param defaultvalue : if specific key not found, return this value
	 * @return Object value
	 */
	public Object GetObject(String key, Object defaultvalue) {
		if (ContainKey(key)) {
			try {
				Object value = jo.get(key);
				return value;
			} catch(JSONException e) {
				BA.Log("Unable to GetObject, Msg : "+e.getMessage());
			}
		}
		return defaultvalue;
	}
}
