package it.innove;

import android.os.Bundle;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

import java.util.*;

/**
 * Copyright (c) 2014-present, Facebook, Inc. All rights reserved.
 *
 * You are hereby granted a non-exclusive, worldwide, royalty-free license to use,
 * copy, modify, and distribute this software in source code or binary form for use
 * in connection with the web services and APIs provided by Facebook.
 *
 * As with any software that integrates with the Facebook platform, your use of
 * this software is subject to the Facebook Developer Principles and Policies
 * [http://developers.facebook.com/policy/]. This copyright notice shall be
 * included in all copies or substantial portions of the software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
 * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
 * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
 * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
 * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */
public class BundleJSONConverter {
	private static final Map<Class<?>, Setter> SETTERS = new HashMap<>();

	static {
		SETTERS.put(Boolean.class, new Setter() {
			public void setOnBundle(Bundle bundle, String key, Object value) throws JSONException {
				bundle.putBoolean(key, (Boolean) value);
			}

			public void setOnJSON(JSONObject json, String key, Object value)  throws JSONException {
				json.put(key, value);
			}
		});
		SETTERS.put(Integer.class, new Setter() {
			public void setOnBundle(Bundle bundle, String key, Object value) throws JSONException {
				bundle.putInt(key, (Integer) value);
			}

			public void setOnJSON(JSONObject json, String key, Object value)  throws JSONException {
				json.put(key, value);
			}
		});
		SETTERS.put(Long.class, new Setter() {
			public void setOnBundle(Bundle bundle, String key, Object value) throws JSONException {
				bundle.putLong(key, (Long) value);
			}

			public void setOnJSON(JSONObject json, String key, Object value)  throws JSONException {
				json.put(key, value);
			}
		});
		SETTERS.put(Double.class, new Setter() {
			public void setOnBundle(Bundle bundle, String key, Object value) throws JSONException {
				bundle.putDouble(key, (Double) value);
			}

			public void setOnJSON(JSONObject json, String key, Object value)  throws JSONException {
				json.put(key, value);
			}
		});
		SETTERS.put(String.class, new Setter() {
			public void setOnBundle(Bundle bundle, String key, Object value) throws JSONException {
				bundle.putString(key, (String) value);
			}

			public void setOnJSON(JSONObject json, String key, Object value)  throws JSONException {
				json.put(key, value);
			}
		});
		SETTERS.put(String[].class, new Setter() {
			public void setOnBundle(Bundle bundle, String key, Object value) throws JSONException {
				throw new IllegalArgumentException("Unexpected type from JSON");
			}

			public void setOnJSON(JSONObject json, String key, Object value)  throws JSONException {
				JSONArray jsonArray = new JSONArray();
				for (String stringValue : (String[])value) {
					jsonArray.put(stringValue);
				}
				json.put(key, jsonArray);
			}
		});

		SETTERS.put(JSONArray.class, new Setter() {
			public void setOnBundle(Bundle bundle, String key, Object value) throws JSONException {
				JSONArray jsonArray = (JSONArray)value;
				ArrayList<String> stringArrayList = new ArrayList<String>();
				// Empty list, can't even figure out the type, assume an ArrayList<String>
				if (jsonArray.length() == 0) {
					bundle.putStringArrayList(key, stringArrayList);
					return;
				}

				// Only strings are supported for now
				for (int i = 0; i < jsonArray.length(); i++) {
					Object current = jsonArray.get(i);
					if (current instanceof String) {
						stringArrayList.add((String)current);
					} else {
						throw new IllegalArgumentException("Unexpected type in an array: " + current.getClass());
					}
				}
				bundle.putStringArrayList(key, stringArrayList);
			}

			@Override
			public void setOnJSON(JSONObject json, String key, Object value) throws JSONException {
				throw new IllegalArgumentException("JSONArray's are not supported in bundles.");
			}
		});
	}

	public interface Setter {
		public void setOnBundle(Bundle bundle, String key, Object value) throws JSONException;
		public void setOnJSON(JSONObject json, String key, Object value) throws JSONException;
	}

	public static JSONObject convertToJSON(Bundle bundle) throws JSONException {
		JSONObject json = new JSONObject();

		for(String key : bundle.keySet()) {
			Object value = bundle.get(key);
			if (value == null) {
				// Null is not supported.
				continue;
			}

			// Special case List<String> as getClass would not work, since List is an interface
			if (value instanceof List<?>) {
				JSONArray jsonArray = new JSONArray();
				@SuppressWarnings("unchecked")
				List<String> listValue = (List<String>)value;
				for (String stringValue : listValue) {
					jsonArray.put(stringValue);
				}
				json.put(key, jsonArray);
				continue;
			}

			// Special case Bundle as it's one way, on the return it will be JSONObject
			if (value instanceof Bundle) {
				json.put(key, convertToJSON((Bundle)value));
				continue;
			}

			Setter setter = SETTERS.get(value.getClass());
			if (setter == null) {
				throw new IllegalArgumentException("Unsupported type: " + value.getClass());
			}
			setter.setOnJSON(json, key, value);
		}

		return json;
	}

	public static Bundle convertToBundle(JSONObject jsonObject) throws JSONException {
		Bundle bundle = new Bundle();
		@SuppressWarnings("unchecked")
		Iterator<String> jsonIterator = jsonObject.keys();
		while (jsonIterator.hasNext()) {
			String key = jsonIterator.next();
			Object value = jsonObject.get(key);
			if (value == null || value == JSONObject.NULL) {
				// Null is not supported.
				continue;
			}

			// Special case JSONObject as it's one way, on the return it would be Bundle.
			if (value instanceof JSONObject) {
				bundle.putBundle(key, convertToBundle((JSONObject)value));
				continue;
			}

			Setter setter = SETTERS.get(value.getClass());
			if (setter == null) {
				throw new IllegalArgumentException("Unsupported type: " + value.getClass());
			}
			setter.setOnBundle(bundle, key, value);
		}

		return bundle;
	}
}