package it.gotoandplay.smartfoxclient.data;

/**
 * 
 * @author Lapo
 */
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.json.JSONObject;

/**
 * 
 * @author Lapo
 */
public class SFSObject {
	private final Map objData;

	/**
	 * Default constructor
	 * 
	 */
	public SFSObject () {
		objData = new HashMap ();
	}

	/**
	 * Constructs the ActionscriptObject populating it with the passed
	 * collection
	 * 
	 * @param collection a Collection
	 */
	public SFSObject (final Collection collection) {
		this ();
		putCollection (this, "", collection);
	}

	/**
	 * Constructs the ActionscriptObject populating it with the passed map
	 * 
	 * @param map a Map
	 */
	public SFSObject (final Map map) {
		this ();
		putMap (this, "", map);
	}

	/**
	 * Get an object from an index key
	 * 
	 * @param key the key
	 * @return the object
	 */
	public Object get (final int key) {
		return objData.get (String.valueOf (key));
	}

	/**
	 * Get an object from a string key
	 * 
	 * @param key the string key
	 * @return the object
	 */
	public Object get (final String key) {
		return objData.get (key);
	}

	/**
	 * Get a boolean from an index key
	 * 
	 * @param key the key
	 * @return the boolean
	 */
	public boolean getBool (final int key) {
		final Boolean var = (Boolean) objData
				.get (String.valueOf (key));

		if (var == null) {
			return false;
		}

		return var.booleanValue ();
	}

	/**
	 * Get a boolean from a String key
	 * 
	 * @param key the key
	 * @return the boolean
	 */
	public boolean getBool (final String key) {
		final Boolean var = (Boolean) objData.get (key);

		if (var == null) {
			return false;
		}

		return var.booleanValue ();
	}

	/**
	 * Get a number from an index key
	 * 
	 * @param key the key
	 * @return the number (as double)
	 */
	public double getNumber (final int key) {
		final Double var = (Double) objData.get (String.valueOf (key));

		if (var == null) {
			return 0; // ??? == NULL == Does this make any sense ???
		}

		return var.doubleValue ();
	}

	/**
	 * Get a number from a string key
	 * 
	 * @param key the key
	 * @return the number (as double)
	 */
	public double getNumber (final String key) {
		final Double var = (Double) objData.get (key);

		if (var == null) {
			return 0; // ??? == NULL == Does this make any sense ???
		}

		return var.doubleValue ();
	}

	/**
	 * Get an ActionscriptObject from an index key
	 * 
	 * @param key the index key
	 * @return the ActionscriptObject
	 */
	public SFSObject getObj (final int key) {
		return (SFSObject) objData.get (String.valueOf (key));
	}

	/**
	 * Get an ActionscriptObject from a string key
	 * 
	 * @param key the key
	 * @return the ActionscriptObject
	 */
	public SFSObject getObj (final String key) {
		return (SFSObject) objData.get (key);
	}

	/**
	 * Get a String from and index key
	 * 
	 * @param key the key
	 * @return the string
	 */
	public String getString (final int key) {
		final String s = (String) objData.get (String.valueOf (key));

		if (s == null) {
			return "";
		}
		return s;
	}

	/**
	 * Get a String from a string key
	 * 
	 * @param key the key
	 * @return the string
	 */
	public String getString (final String key) {
		final String s = objData.get (key).toString ();
		if (s == null) {
			return "";
		}
		return s;
	}

	/**
	 * Get a Set of keys
	 * 
	 * @return the key set
	 */
	public Set keySet () {
		return objData.keySet ();
	}

	/**
	 * Put an object with a numeric key (Indexed Array)
	 * 
	 * @param key the index key
	 * @param o the object
	 */
	public void put (final int key, final Object o) {
		objData.put (String.valueOf (key), o);
	}

	/**
	 * Put an object with a String key (Associative Array)
	 * 
	 * @param key the string key
	 * @param o the object
	 */
	public void put (final String key, final Object o) {
		objData.put (key, o);
	}

	/**
	 * Put a Boolean value with a numeric key (Indexed Array)
	 * 
	 * @param key the index key
	 * @param b the boolean
	 */
	public void putBool (final int key, final boolean b) {
		objData.put (String.valueOf (key), new Boolean (b));
	}

	/**
	 * Put a Boolean value with a string key (Indexed Array)
	 * 
	 * @param key the string key
	 * @param b the boolean
	 */
	public void putBool (final String key, final boolean b) {
		objData.put (key, new Boolean (b));
	}

	/**
	 * Adds the content of a Collection, which may also include other
	 * collections or maps
	 * 
	 * @param key the numeric key
	 * @param collection the Collection
	 * 
	 * @since 1.6.0
	 */
	public void putCollection (final int key,
			final Collection collection) {
		putCollection (String.valueOf (key), collection);
	}

	private void putCollection (final SFSObject aObj, final String key,
			final Collection collection) {
		Object collectionItem;
		int count = 0;

		// We're nesting a new object
		if (aObj != this) {
			put (key, aObj);
		}

		for (final Iterator it = collection.iterator (); it.hasNext ();) {
			collectionItem = it.next ();

			if (collectionItem instanceof Collection) {
				aObj.putCollection (count, (Collection) collectionItem);
			} else if (collectionItem instanceof Map) {
				aObj.putMap (count, (Map) collectionItem);
			} else {
				aObj.put (count, collectionItem);
			}

			count++ ;
		}
	}

	/**
	 * Adds the content of a Collection, which may also include other
	 * collections or maps
	 * 
	 * @param key the String key
	 * @param collection the Collection
	 * 
	 * @since 1.6.0
	 */
	public void putCollection (final String key,
			final Collection collection) {
		putCollection (new SFSObject (), key, collection);
	}

	/**
	 * Add the content of a Map, which can also contain other maps or
	 * collections
	 * 
	 * @param key the numeric key
	 * @param map the Map object
	 * 
	 * @since 1.6.0
	 */
	public void putMap (final int key, final Map map) {
		putMap (String.valueOf (key), map);
	}

	private void putMap (final SFSObject aObj, final String key,
			final Map map) {
		Map.Entry entryItem;
		String itemKey;
		Object itemVal;

		// We're nesting a new object
		if (aObj != this) {
			put (key, aObj);
		}

		for (final Iterator it = map.entrySet ().iterator (); it
				.hasNext ();) {
			entryItem = (Map.Entry) it.next ();
			itemKey = (String) entryItem.getKey ();
			itemVal = entryItem.getValue ();

			if (itemVal instanceof Collection) {
				aObj.putCollection (itemKey, (Collection) itemVal);
			} else if (itemVal instanceof Map) {
				aObj.putMap (itemKey, (Map) itemVal);
			} else {
				aObj.put (itemKey, itemVal);
			}
		}
	}

	/**
	 * Add the content of a Map, which can also contain other maps or
	 * collections
	 * 
	 * @param key the String key
	 * @param map the Map object
	 * 
	 * @since 1.6.0
	 */
	public void putMap (final String key, final Map map) {
		putMap (new SFSObject (), key, map);
	}

	/**
	 * Put a Number with a numeric key (Indexed Array)
	 * 
	 * @param key the index key
	 * @param d the number (treated as double)
	 */
	public void putNumber (final int key, final double d) {
		objData.put (String.valueOf (key), new Double (d));
	}

	/**
	 * Put a Number with a string key (Associative Array)
	 * 
	 * @param key the string key
	 * @param d the number (treated as double)
	 */
	public void putNumber (final String key, final double d) {
		objData.put (key, new Double (d));
	}

	/**
	 * Remove an element
	 * 
	 * @param key the index key
	 * @return the element removed
	 */
	public Object removeElement (final int key) {
		return objData.remove (String.valueOf (key));
	}

	/**
	 * Remove an element
	 * 
	 * @param key the string key
	 * @return the element removed
	 */
	public Object removeElement (final String key) {
		return objData.remove (key);
	}

	/**
	 * Get the current number of elements
	 * 
	 * @return the current size
	 */
	public int size () {
		return objData.size ();
	}

	@Override
	public String toString () {
		final StringBuilder str = new StringBuilder ();

		str.append ("{ ");

		final Iterator i = objData.keySet ().iterator ();

		while (i.hasNext ()) {
			final String key = (String) i.next ();

			str.append (JSONObject.quote (key));
			str.append (" : ");

			final Object o = objData.get (key);
			if (o == null) {
				str.append ("null");
			} else if (o instanceof String) {
				str.append (JSONObject.quote ((String) o));
			} else if (o instanceof String []) {
				str.append ("[ ");
				for (final String s : (String []) o) {
					str.append (JSONObject.quote (s));
					str.append (" , ");
				}
				if (str.lastIndexOf (" , ") == str.length () - 3) {
					str.delete (str.length () - 3, str.length ());
				}
				str.append (" ]");
			} else if (o instanceof int []) {
				str.append ("[ ");
				for (final int n : (int []) o) {
					str.append (n);
					str.append (" , ");
				}
				if (str.lastIndexOf (" , ") == str.length () - 3) {
					str.delete (str.length () - 3, str.length ());
				}
				str.append (" ]");
			} else if (o instanceof Map) {
				final Map map = (Map) o;
				str.append ("{ ");
				for (final Object k : map.keySet ()) {
					str.append (JSONObject.quote (k.toString ()));
					str.append (" : ");
					final Object value = map.get (k);
					if (value instanceof String) {
						str.append (JSONObject.quote ((String) value));
					} else {
						str.append (value.toString ());
					}
					str.append (" , ");
				}
				if (str.lastIndexOf (" , ") == str.length () - 3) {
					str.delete (str.length () - 3, str.length ());
				}
				str.append (" }");
			} else if (o instanceof List) {
				final List list = (List) o;
				str.append ("[ ");
				for (final Object v : list) {
					if (v instanceof String) {
						str.append (JSONObject.quote ((String) v));
					} else {
						str.append (v.toString ());
					}
				}
				if (str.lastIndexOf (" , ") == str.length () - 3) {
					str.delete (str.length () - 3, str.length ());
				}
				str.append (" ]");
			} else if (o instanceof Set) {
				final Set set = (Set) o;
				str.append ("[ ");
				for (final Object v : set) {
					if (v instanceof String) {
						str.append (JSONObject.quote ((String) v));
					} else {
						str.append (v.toString ());
					}
				}
				if (str.lastIndexOf (" , ") == str.length () - 3) {
					str.delete (str.length () - 3, str.length ());
				}
				str.append (" ]");
			} else {
				str.append (o.toString ());
			}

			str.append (" , ");
		}

		if (str.lastIndexOf (" , ") == str.length () - 3) {
			str.delete (str.length () - 3, str.length ());
		}
		str.append (" }");

		return str.toString ();
	}
}
