﻿using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Events;

public class LeChuckAPI: MonoBehaviour  {

	public class LeChuckAPIException: ApplicationException {
		public LeChuckAPIException(string message): base(message) {}
	}

	public enum LeChuckAPIResponse {Acknowledged, InvalidCall, ApiNotReady};

	public static string unityAPIversion = "0.0.5";
	public static string unityAPILoaderUrl = "https://ssl.minijuegosgratis.com/lechuck/js/unity/loader.js";	
	private static LeChuckAPI instance;
	private static bool simulationMode;

	/**
	 * Access to the LeChuck API methods
	 */
	public Api api;

	public static LeChuckAPI getInstance() {
		return LeChuckAPI.Instance;
	}

	public static void destroyInstance() {
		if (instance) {
			DestroyImmediate(instance.gameObject);
			instance = null;
		}
	}

	public static LeChuckAPI Instance {
		get {
			if (instance == null) {
				GameObject coreGameObject = new GameObject(typeof(LeChuckAPI).Name);
				DontDestroyOnLoad (coreGameObject);
				instance = coreGameObject.AddComponent<LeChuckAPI>();
			}
			return instance;
		}
	}

	protected virtual void Awake() {
		if (instance == null) {
			instance = GetComponent<LeChuckAPI> ();
			instance.api = new Api(instance, false);
			simulationMode = instance.api.isSimulationMode();
			if (simulationMode) {
				StartCoroutine("initSimulationMode"); // Initialize after a little delay
			}
		} else {
			DestroyImmediate (this);
		}
	}	

	public void _onJSMessage(string parameters) {
		instance.api._onJSMessage(parameters);
	}

	private IEnumerator initSimulationMode() {
		yield return new WaitForSecondsRealtime(1.0f);
		_onJSMessage("apiReady"); // Simulate apiReady message reception
	}

	// PUBLIC API METHODS --------------------------------------------------------
	public class Api {

		public class VrHasCampaigns {
			public bool hasCampaigns;
		}
			
		public class InterlevelAd {
			public string adEvent;
		}

		public class User {
			public string errorMessage;
			public string errorType;
			public bool success;
			public string id;
			public string uid;
			public int level;
			public string avatar;
			public string avatarBig;
			public string avatarMini;
			public string avatarBody;
			public string avatarAlpha;
			public string profile;
			public bool isGuest;
			public string token;
		}

		[System.Serializable]
		public class UserFriend {
			public string id;
			public string uid;
			public int level;
			public string avatar;
			public string avatarBig;
			public string avatarMini;
			public string avatarBody;
			public string avatarAlpha;
			public string profile;
			public int timestamp;
			public bool isInstalled;
		}

		public class Info {
			public string unityApiVersion;
			public string apiVersion;
			public string apiId;
			public string locale;
			public string localeLang;
			public bool isExternal;
			public bool isInternal;
			public bool isMobile;
			public string site;
		}

		public class StatPut {
			public string errorMessage;
			public string errorType;
			public bool success;
			public int value;
			public string uid;
		}

		public class StatGet {
			public string errorMessage;
			public string errorType;
			public bool success;
			public int value;
			public string uid;
			public long timestamp;
		}

		public class Challenge {
			public bool success;
			public int[] users;
			public string customParameters;
		}
		public class Share {
			public bool success;
			public int[] users;
		}

		public class ItemDetail {
			public bool success;
			public string errorType;
			public string errorMessage;
			public Item item;
		}
		public class ItemStock {
			public bool success;
			public string errorType;
			public string errorMessage;
			public long timestamp;
			public int stock;
			public Item item;
		}
		public class ItemList {
			public bool success;
			public string errorType;
			public string errorMessage;
			public Item[] items;
		}
		public class ItemInventory {
			public bool success;
			public string errorType;
			public string errorMessage;
			public ItemWithStock[] items;
		}

		[System.Serializable]
		public class Item {
			public int id;
			public string uid;
			public string name;
			public bool is_enabled;
			public bool is_buyable;
			public bool is_usable;
			public int price_minicoins;
			public int max_stock;
		}

		[System.Serializable]
		public class ItemWithStock {
			public long timestamp;
			public int stock;
			public Item item;
		}
		public class ItemPurchaseList {
			public bool success;
			public string errorType;
			public string errorMessage;
			public ItemPurchase[] items;
		}
		[System.Serializable]
		public class ItemPurchase {
			public string uid;
			public int qty;
			public int stock;
			public string dynamic_payload;
			public bool is_dynamic;
			public int total_minicoins;
		}
		public class ItemConsume {
			public bool success;
			public string errorType;
			public string errorMessage;
			public int qty;
			public string uid;
			public string new_stock;
		}

		public class UserFriends {
			public bool success;
			public string errorType;
			public string errorMessage;
			public UserFriend[] friends;
		}
		public class Size {
			public int width;
			public int height;
		}

		public class ApiCall {
			public string method;
			public Func<string, bool> callback;
			public string[] parameters;
			public ApiCall(string method, Func<string, bool> callback, params string[] parameters) {
				this.method = method;
				this.callback = callback;
				this.parameters = parameters;
			}
		}
		
		private bool apiReady = false;
		protected Info _apiInfo = null;
		public Info apiInfo {
			get {
				if (_apiInfo==null) {
					throw new LeChuckAPIException("apiInfo not loaded yet, you should wait for the onReady event before getting this info");
				}
				return _apiInfo;
			}
		}
		protected User _currentUser = null;
		public User currentUser {
			get {	
				if (_currentUser==null) {
					throw new LeChuckAPIException("currentUser not loaded yet, you should wait for the onReady event before getting this info");
				}
				return _currentUser;
			}
		}
		public bool debug = false;
		public bool debugMethodCalls = false;
		public bool getDemoUserInSimulationMode = true;
		private bool simulationMode = false;
		private bool initialized = false;

		private Dictionary<string, Func<string, bool>> callbackStack = new Dictionary<string, Func<string, bool>>();
		private Dictionary<string, Action<bool, Info, User>> callbackApiReadyStack = new Dictionary<string, Action<bool, Info, User>>();

		private Dictionary<string, DateTime> lowPriorityStatPuts = new Dictionary<string, DateTime>();
		
		protected LeChuckAPI instance;

		public Api(LeChuckAPI instance, bool simulationMode) {
			this.instance = instance;
			this.simulationMode = simulationMode;
			this.init();
		}

		public bool isReady() {
			return apiReady;
		}

		public bool isSimulationMode() {
			return simulationMode;
		}

		private void init() {
			if (debugMethodCalls) this.log ("init() called...");
			if (!initialized) {
				initialized = true;
				if (Application.platform != RuntimePlatform.WebGLPlayer) {
					warning ("Not a WEBGL environment, API falling back to SIMULATION MODE");
					simulationMode = true;
				}
				loadJSApi();
			} else {
				warning ("Already initialized!");
			}
		}

		private void loadJSApi() {
			if (!simulationMode) {
				if (Application.platform != RuntimePlatform.WebGLPlayer) {
					throw new LeChuckAPIException("LeChuck JS API can only be loaded on WEBGL Player");
				}
				#if WEBGL_PLAYER
					LeChuckAPI.unityAPILoaderUrl
					Application.ExternalEval("")
				#endif
				// Try to load javascript API
				this.log("Loading LeChuckAPI Unity Javascript and wating for readiness...");
				Application.ExternalEval(
					"var LeChuckUnityScript=document.createElement('script');"+
					"LeChuckUnityScript.src='"+LeChuckAPI.unityAPILoaderUrl+"';"+
					"var LeChuckRegex = new RegExp('[\\\\?&]' + 'mp_api_js_url' + '=([^&#]*)');var LeChuckResults = LeChuckRegex.exec(location.search);if (LeChuckResults != null) LeChuckUnityScript.src=decodeURIComponent(LeChuckResults[1].replace(/\\+/g, ' ')).split('latest.js').join('unity/loader.js');"+
					"if (typeof console!='undefined') console.log('Loading LeChuckAPI from:'+LeChuckUnityScript.src);"+
					"LeChuckUnityScript.id='LeChuckUnityAPIjs';"+
					"LeChuckUnityScript.type='text/javascript';"+
					"document.body.appendChild(LeChuckUnityScript);"
				);
				// If the api is already initialized it will automatically register a listener to send us an onapiready event
			} else {
				// Nothing to do on simulation mode
			}		
		}

		private void markApiReady() {
			this.log ("markApiReady() called...");
			if (!apiReady) {
				if (simulationMode) {
					this.log ("markApiReady() will use SIMULATION MODE");
				} else {
					Application.ExternalCall ("LeChuckAPI.unity.setUnityApiVersion", LeChuckAPI.unityAPIversion); // Send unity api version
				}
				this.log ("markApiReady() retrieving api info...");
				_getInfo((info)=> {
					this.log ("markApiReady() api info received! Requesting user info...");
					_getCurrentUser((user) => {
						apiReady = true;
						this.log ("markApiReady() user info received! Dispatching onReady callbacks...");
						// Call callbacks
						int i=0;
						foreach (KeyValuePair<string,Action<bool, Info, User>> callback in callbackApiReadyStack) {
							i++;
							this.log ("markApiReady() notifying callback #"+i+"...");
							callback.Value(simulationMode, info, user);
						}
						// Clear callbacks
						callbackApiReadyStack.Clear ();
						this.log ("markApiReady() completed. Everything OK!");
					}, getDemoUserInSimulationMode); // Set to true to get a simulated demo user (if the api it's in simulation mode)
				});
			} else {
				this.log ("markApiReady() completed. Already READY!");
			}
		}

		float pauseTimeScale = 0f;
		public void GamePause() {
			AudioListener.pause = true;
			pauseTimeScale = Time.timeScale;
			Time.timeScale = 0f;
		}
		public void GameResume() {
			AudioListener.pause = false;
			Time.timeScale = pauseTimeScale;
		}

		public void _onJSMessage(string parameters) {
			if (parameters=="apiReady") {
				markApiReady();
			} else if(parameters=="gamePause") {
				GamePause();
			} else if(parameters=="gameResume") {
				GameResume();
			} else {
				var aux = parameters.Split(new [] {"~|#|~"}, System.StringSplitOptions.None);
				if (aux.Length >= 2) {
					string callbackIndex = aux [0];
					//string errorMessage = aux [1];
					string payload = aux [1];
					//this.log ("_onJSMessage callback "+callbackIndex+": "+payload);
					Func<string, bool> Handler;
					if (callbackStack.TryGetValue(callbackIndex, out Handler)) {
						this.log ("_onJSMessage callback "+callbackIndex+": "+payload+" will be called");
						bool shouldRemoveCallback = Handler(payload);
						if (shouldRemoveCallback) {
							this.log ("_onJSMessage: dispose callback "+callbackIndex);
							callbackStack.Remove(callbackIndex);
						}
					} else {
						this.log ("_onJSMessage callback "+callbackIndex+": "+payload+" no callback found to be called");
					}
				} else {
					this.warning ("_onJSMessage. ERROR: Invalid parameters received: " + parameters);
				}
			}
		}

		private void log(string msg) {
			if (debug) {
				Debug.Log ("LeChuckUnityAPI: "+msg);
			}
		}

		private void warning(string msg) {
			if (debug) {
				Debug.LogWarning ("LeChuckUnityAPI: "+msg);
			}
		}

		private LeChuckAPIResponse call(ApiCall apiCall, string simulationResponsePayload = "", bool allowApiNotReady = false) {
			if (debugMethodCalls) this.log ("call("+apiCall.method+") called");
			if (apiReady || !allowApiNotReady) {
				if (!simulationMode) {
					// Perform api call
					if (apiCall.callback == null) {
						Application.ExternalCall ("LeChuckAPI.unity." + apiCall.method, apiCall.parameters);
					} else {
						string callbackIndex = UnityEngine.Random.value.ToString();
						if (debugMethodCalls) this.log ("call("+apiCall.method+") registering callback "+callbackIndex+"...");
						callbackStack.Add (callbackIndex, apiCall.callback);
						Application.ExternalCall ("LeChuckAPI.unity." + apiCall.method, callbackIndex, apiCall.parameters);
					}
					if (debugMethodCalls) this.log ("call("+apiCall.method+") Acknowledged!");
					return LeChuckAPIResponse.Acknowledged;
				} else {
					if (debugMethodCalls) this.log ("call("+apiCall.method+") Simulation response: "+simulationResponsePayload);
					if (apiCall.callback != null) {
						apiCall.callback(simulationResponsePayload); // Simulation response!
					}
					return LeChuckAPIResponse.Acknowledged;
				}
			} else {
				if (debugMethodCalls) this.log ("call("+apiCall.method+") ERROR, API NOT READY! API call ignored");
				return LeChuckAPIResponse.ApiNotReady;
			}
		}

		/**
		 * Attach an Action to the API onReady event
		 */
		public void onReady(Action<bool, Info, User> callback) {
			if (!apiReady) {
				this.log("OnReady listener added");
				callbackApiReadyStack.Add (UnityEngine.Random.value.ToString(), callback);
			} else {
				callback(simulationMode, apiInfo, currentUser);
			}
		}
		
		/**
		 * Attach an Actio to the API onUserDisconnect event
		 * If it's on simulation mode, you can simulate a disconnection by providing an autoTriggerInSimulationModeSeconds float number greater than 0f
		 */
		public LeChuckAPIResponse onUserDisconnect(Action callback, float autoTriggerInSimulationModeSeconds = 0f) {
			ApiCall apiCall = new ApiCall("onUserDisconnect", (payload) => {
				callback();
				return false; // Important: Return true to avoid removing the callback
			});	
			if (isSimulationMode()) {
				if (autoTriggerInSimulationModeSeconds>0f) {
					this.instance.StartCoroutine(this.instance.delayedExecution(() => {
						this.call(apiCall, "null");	
					}, autoTriggerInSimulationModeSeconds));
				}
				return LeChuckAPIResponse.Acknowledged;
			} else {
				return this.call(apiCall, "null");	
			}
		}
		
		/**
		 * Attach an Actio to the API onUserDisconnect event
		 * If it's on simulation mode, you can simulate a trigger by providing an autoTriggerInSimulationModeSeconds float number greater than 0f
		 */
		public LeChuckAPIResponse onPlayerResize(Action callback, float autoTriggerInSimulationModeSeconds = 0f) {
			ApiCall apiCall = new ApiCall("onPlayerResize", (payload) => {
				if (!isSimulationMode()) {
					callback();
				}
				return false; // Important: Return true to avoid removing the callback
			});	
			if (isSimulationMode()) {
				if (autoTriggerInSimulationModeSeconds>0f) {
					this.instance.StartCoroutine(this.instance.delayedExecution(() => {
						this.call(apiCall, "null");	
					}, autoTriggerInSimulationModeSeconds));
				}
				return LeChuckAPIResponse.Acknowledged;
			} else {
				return this.call(apiCall, "null");	
			}
		}

		/**
		 * Get api info, protected, it will be storeed in currentUser
		 */
		protected LeChuckAPIResponse _getInfo(Action<Info> callback) {
			ApiCall apiCall = new ApiCall("getInfo", (payload) => {
				Info info = JsonUtility.FromJson<Info>(payload);				
				info.unityApiVersion = LeChuckAPI.unityAPIversion;
				_apiInfo = info; // Store last api info
				callback(info);
				return true; // Important: Return true to remove callback afterwards
			});		
			return this.call(apiCall, "{\"apiId\":0,\"apiVersion\":\"0.0.8\",\"site\":\"http://www.minijuegos.com/\",\"locale\":\"en_US\",\"lang\":\"en\",\"isMobile\":false,\"isInternal\":false,\"isExternal\":true}");
		}

		/**
		 * Synchronous: Get current logged user
		 */
		public User getCurrentUser() {
			if (currentUser==null) {
				throw new LeChuckAPIException("currentUser not loaded yet, you should wait for the onReady event before getting this info");
			}
			return currentUser;
		}

		/**
		 * Get current logged user info
		 */
		protected LeChuckAPIResponse _getCurrentUser(Action<User> callback, bool useSimulationSuccessResponse = true) {
			ApiCall apiCall = new ApiCall("getCurrentUser", (payload) => {
				User user = JsonUtility.FromJson<User>(payload);
				_currentUser = user; // Store last user received
				callback(user);
				return true; // Important: Return true to remove callback afterwards
			});
			if (useSimulationSuccessResponse) {
				return this.call(apiCall, "{\"success\":true,\"errorType\":null,\"errorMessage\":null,\"id\":\"2\",\"uid\":\"ruben\",\"level\":35,\"avatar\":\"https://www.minijuegosgratis.com/users/avatars/2/2/96x96.jpg\",\"avatarBig\":\"https://www.minijuegosgratis.com/users/avatars/2/2/256x256.jpg\",\"avatarMini\":\"https://www.minijuegosgratis.com/users/avatars/2/2/32x32.jpg\",\"avatarBody\":\"https://www.minijuegosgratis.com/users/avatars/2/2/160x220.png\",\"avatarAlpha\":\"https://www.minijuegosgratis.com/users/avatars/2/2/96x96.png\",\"profile\":\"http://www.minijuegos.com/perfil/ruben\",\"isGuest\":false,\"token\":\"814123455a3f20fa1411266976c62\"}");
			} else {
				// Default simulation response = Failure
				return this.call(apiCall, "{\"success\":true,\"errorType\":null,\"errorMessage\":null,\"id\":null,\"uid\":null,\"level\":0,\"avatar\":\"https://www.minijuegosgratis.com/users/avatars/default_boy/head_96x96.jpg\",\"avatarBig\":\"https://www.minijuegosgratis.com/users/avatars/default_boy/head_256x256.jpg\",\"avatarMini\":\"https://www.minijuegosgratis.com/users/avatars/default_boy/head_32x32.jpg\",\"avatarBody\":\"https://www.minijuegosgratis.com/users/avatars/default_boy/body_160x220.png\",\"avatarAlpha\":\"https://www.minijuegosgratis.com/users/avatars/default_boy/head_96x96.png\",\"profile\":null,\"isGuest\":true,\"token\":null}");
			}			
		}
		
		/**
		 * Get current logged user friends
		 */
		public LeChuckAPIResponse getCurrentUserFriends(Action<UserFriends> callback, int limit = 20, int fromTimestamp = 0, bool useSimulationSuccessResponse = true) {
			ApiCall apiCall = new ApiCall("getCurrentUserFriends", (payload) => {
				UserFriends userFriends = null;
				try {
					userFriends = JsonUtility.FromJson<UserFriends>(payload);
					if (userFriends.friends == null) {
						userFriends.friends=new UserFriend[0];
					}
				} catch (Exception e) {
					this.warning("Invalid payload received ("+e.Message+"): "+payload);
				}
				callback(userFriends);
				return true; // Important: Return true to remove callback afterwards
			}, limit.ToString(), fromTimestamp.ToString());
			if (useSimulationSuccessResponse) {
				return this.call(apiCall, "{\"success\":true,\"errorType\":\"\",\"errorMessage\":\"\",\"friends\":[{\"id\":\"1010957\",\"uid\":\"fedeelloco38\",\"avatar\":\"https://www.minijuegosgratis.com/users/avatars/957/1010957/96x96.jpg\",\"avatarBig\":\"https://www.minijuegosgratis.com/users/avatars/957/1010957/256x256.jpg\",\"avatarMini\":\"https://www.minijuegosgratis.com/users/avatars/957/1010957/32x32.jpg\",\"avatarBody\":\"https://www.minijuegosgratis.com/users/avatars/957/1010957/160x220.png\",\"avatarAlpha\":\"https://www.minijuegosgratis.com/users/avatars/957/1010957/96x96.png\",\"isInstalled\":false,\"profile\":\"http://www.minijuegos.com/perfil/fedeelloco38\",\"timestamp\":1482223565844,\"level\":34},{\"id\":\"2814272\",\"uid\":\"ap0calipsi\",\"avatar\":\"https://www.minijuegosgratis.com/users/avatars/4272/2814272/96x96.jpg\",\"avatarBig\":\"https://www.minijuegosgratis.com/users/avatars/4272/2814272/256x256.jpg\",\"avatarMini\":\"https://www.minijuegosgratis.com/users/avatars/4272/2814272/32x32.jpg\",\"avatarBody\":\"https://www.minijuegosgratis.com/users/avatars/4272/2814272/160x220.png\",\"avatarAlpha\":\"https://www.minijuegosgratis.com/users/avatars/4272/2814272/96x96.png\",\"isInstalled\":true,\"profile\":\"http://www.minijuegos.com/perfil/ap0calipsi\",\"timestamp\":1481945436494,\"level\":41}]}");
			} else {
				// Default simulation response = Failure
				return this.call(apiCall, "{\"success\":true,\"errorType\":\"\",\"errorMessage\":\"\",\"friends\":null}");
			}	
		}

		/**
		 * Get public user info by it's id
		 */
		public LeChuckAPIResponse getUser(Action<User> callback, int userId, bool useSimulationSuccessResponse = true) {
			ApiCall apiCall = new ApiCall("getUser", (payload) => {
				User user = JsonUtility.FromJson<User>(payload);
				callback(user);
				return true; // Important: Return true to remove callback afterwards
			}, userId.ToString());
			if (useSimulationSuccessResponse) {
				return this.call(apiCall, "{\"success\":true,\"errorType\":\"\",\"errorMessage\":\"\",\"id\":199,\"uid\":\"blanche\",\"level\":33,\"avatar\":\"https://www.minijuegosgratis.com/users/avatars/199/199/96x96.jpg\",\"avatarBig\":\"https://www.minijuegosgratis.com/users/avatars/199/199/256x256.jpg\",\"avatarMini\":\"https://www.minijuegosgratis.com/users/avatars/199/199/32x32.jpg\",\"avatarBody\":\"https://www.minijuegosgratis.com/users/avatars/199/199/160x220.png\",\"avatarAlpha\":\"https://www.minijuegosgratis.com/users/avatars/199/199/96x96.png\",\"profile\":\"http://www.minijuegos.com/perfil/blanche\",\"isGuest\":true}");
			} else {
				// Default simulation response = Failure
				return this.call(apiCall, "{\"success\":false,\"errorType\":\"NOT_FOUND\",\"errorMessage\":\"Sorry, the page you requested was not found. Exception: User 599 not found\",\"id\":null,\"uid\":null,\"level\":null,\"avatar\":null,\"avatarBig\":null,\"avatarMini\":null,\"avatarBody\":null,\"avatarAlpha\":null,\"profile\":null,\"isGuest\":true}");
			}			
		}

		/**
		 * Writes an stat every-time
		 */
		public LeChuckAPIResponse putStat(Action<StatPut> callback, string statUid, int value, bool useSimulationSuccessResponse = true) {
			ApiCall apiCall = new ApiCall("putStat", (payload) => {
				StatPut statPut = null;
				try {
					statPut = JsonUtility.FromJson<StatPut>(payload);
				} catch (Exception e) {
					this.warning("Invalid payload received ("+e.Message+"): "+payload);
				}
				if (callback!=null) {
					callback(statPut);
				}
				return true; // Important: Return true to remove callback afterwards
			}, statUid, value.ToString());
			if (useSimulationSuccessResponse) {
				return this.call(apiCall, "{\"success\":true,\"uid\":\""+statUid+"\",\"stat\":{\"id\":\"14\",\"uid\":\""+statUid+"\",\"type\":\"REPLACE\",\"description\":\"ASDFASDFASDF\"},\"value\":"+value+",\"errorType\":\"\",\"errorMessage\":\"\"}");
			} else {
				// Default simulation response = Failure
				return  this.call(apiCall, "{\"success\":false,\"uid\":\""+statUid+"\",\"stat\":false,\"value\":false,\"timestamp\":false,\"errorType\":\"NOT_FOUND\",\"errorMessage\":\"Sorry, the page you requested was not found. Exception: Stat '"+statUid+"' not found for game '4'\"}");
			}	
		}

		/**
		 * Writes an stat with low-priority: only is sent if the minSeconds have elapsed since the last write of the stat.
		 * Useful to submit scores periodically to have intermediate achievements.
		 */
		public LeChuckAPIResponse putStatLowPriority(string statUid, int value, double minSeconds = 20, bool useSimulationSuccessResponse = true) {
			DateTime lastWriteDateTime;
			DateTime now = System.DateTime.Now.AddSeconds(-minSeconds);
			lowPriorityStatPuts.TryGetValue(statUid, out lastWriteDateTime);
			if (lastWriteDateTime.Ticks > now.Ticks)  {	
				log("Automatically ignored stat put: "+statUid+". Min seconds not elapsed yet ("+minSeconds+" seconds)");
				return LeChuckAPIResponse.Acknowledged; // Return ACK even if we're not doing anything
			} else {
				// Submit stat
				lowPriorityStatPuts[statUid] = System.DateTime.Now;
				return this.putStat(null, statUid, value, useSimulationSuccessResponse);
			}
		}

		/**
		 * Gets an stat
		 */
		public LeChuckAPIResponse getStat(Action<StatGet> callback, string statUid, bool useSimulationSuccessResponse = true) {
			ApiCall apiCall = new ApiCall("getStat", (payload) => {
				StatGet statGet = null;
				try {
					statGet = JsonUtility.FromJson<StatGet>(payload);
				} catch (Exception e) {
					this.warning("Invalid payload received ("+e.Message+"): "+payload);
				}
				callback(statGet);
				return true; // Important: Return true to remove callback afterwards
			}, statUid);
			if (useSimulationSuccessResponse) {
				return this.call(apiCall, "{\"success\":true,\"uid\":\""+statUid+"\",\"stat\":{\"id\":\"14\",\"uid\":\""+statUid+"\",\"type\":\"REPLACE\",\"description\":\"ASDFASDFASDF\"},\"value\":10,\"timestamp\":1500892229833,\"errorType\":\"\",\"errorMessage\":\"\"}");
			} else {
				// Default simulation response = Failure
				return  this.call(apiCall, "{\"success\":false,\"uid\":\""+statUid+"\",\"stat\":false,\"value\":false,\"timestamp\":false,\"errorType\":\"NOT_FOUND\",\"errorMessage\":\"Sorry, the page you requested was not found. Exception: Stat '"+statUid+"' not found for game '4'\"}");
			}	
		}

		/**
		 * Open login window
		 */
		public LeChuckAPIResponse login() {
			if (apiReady && currentUser!=null && !currentUser.isGuest) {
				return LeChuckAPIResponse.InvalidCall;
			} 
			ApiCall apiCall = new ApiCall("login", null);
			return this.call(apiCall);
		}

		/**
		 * Open register window
		 */
		public LeChuckAPIResponse register() {
			if (apiReady && currentUser!=null && !currentUser.isGuest) {
				return LeChuckAPIResponse.InvalidCall;
			} 
			ApiCall apiCall = new ApiCall("register", null);
			return this.call(apiCall);
		}

		/**
		 * Gets the player size
		 */
		public LeChuckAPIResponse getSize(Action<Size> callback) {
			ApiCall apiCall = new ApiCall("getSize", (payload) => {
				Size size = null;
				try {
					size = JsonUtility.FromJson<Size>(payload);
				} catch (Exception e) {
					this.warning("Invalid payload received ("+e.Message+"): "+payload);
				}
				callback(size);
				return true; // Important: Return true to remove callback afterwards
			});
			return this.call(apiCall, "{\"width\":"+Screen.width+",\"height\":"+Screen.height+"}");
		}

		/**
		 * Show game comments
		 */
		public LeChuckAPIResponse showComments() {
			ApiCall apiCall = new ApiCall("showComments", null);
			return this.call(apiCall);
		}

		/**
		 * Show game controls
		 */
		public LeChuckAPIResponse showControls() {
			ApiCall apiCall = new ApiCall("showControls", null);
			return this.call(apiCall);
		}

		/**
		 * Show game description
		 */
		public LeChuckAPIResponse showDescription() {
			ApiCall apiCall = new ApiCall("showDescription", null);
			return this.call(apiCall);
		}

		/**
		 * Show game achievements
		 */
		public LeChuckAPIResponse showAchievements() {
			ApiCall apiCall = new ApiCall("showAchievements", null);
			return this.call(apiCall);
		}

		/**
		 * Open share in social networks modal window
		 */
		public LeChuckAPIResponse share(Action<Share> callback, int[] userIds = null, bool useSimulationSuccessResponse = true) {
			ApiCall apiCall = new ApiCall("share", (payload) => {
				Share share = null;
				try {
					share = JsonUtility.FromJson<Share>(payload);
				} catch (Exception e) {
					this.warning("Invalid payload received ("+e.Message+"): "+payload);
				}
				if (callback!=null) {
					callback(share);
				}
				return true; // Important: Return true to remove callback afterwards
			}, string.Join(",", Array.ConvertAll(userIds, item => item.ToString())));
			if (useSimulationSuccessResponse) {
				return this.call(apiCall, "{\"success\":true,\"users\":[5,199]}");
			} else {
				// Default simulation response = Failure
				return this.call(apiCall, "{\"success\":false,\"users\":[]}");
			}	
		}

		/**
		 * Opens challenge other user modal window
		 */
		public LeChuckAPIResponse challenge(Action<Challenge> callback, int[] userIds = null, Boolean allowAdd = true, Boolean allowMultiUser = true, string customParametersJsonObjectAsString = "{\"customParameters\":false}", bool useSimulationSuccessResponse = true) {
			ApiCall apiCall = new ApiCall("challenge", (payload) => {
				Challenge challenge = null;
				try {
					challenge = JsonUtility.FromJson<Challenge>(payload);
					challenge.customParameters = customParametersJsonObjectAsString;
				} catch (Exception e) {
					this.warning("Invalid payload received ("+e.Message+"): "+payload);
				}
				callback(challenge);
				return true; // Important: Return true to remove callback afterwards
			}, string.Join(",", Array.ConvertAll(userIds, item => item.ToString())), allowMultiUser ? "1":"0", allowAdd ? "1":"0", customParametersJsonObjectAsString);
			if (useSimulationSuccessResponse) {
				return this.call(apiCall, "{\"success\":true,\"users\":[5,199]}");
			} else {
				// Default simulation response = Failure
				return this.call(apiCall, "{\"success\":false,\"users\":[]}");
			}	
		}

		/**
		 * Show an interlevel video ad for the provided VAST tagUrl:
		 * Set empty tagUrl for a demo tag. Use "[random]" for a random number, "[time]" for currentTimeMillis. 
		 * Do not use demo tags in production!
		 */
		public LeChuckAPIResponse videoRewardHasCampaigns(string vrAppKey, Action callbackOk, Action callbackKo, bool useSimulationSuccessResponse = true) {
			ApiCall apiCall = new ApiCall("vrHasCampaigns", (payload) => {
				VrHasCampaigns hasCampaigns = null;
				try {
					hasCampaigns = JsonUtility.FromJson<VrHasCampaigns>(payload);
				} catch (Exception e) {
					this.warning("Invalid payload received ("+e.Message+"): "+payload);
				}
				if (hasCampaigns.hasCampaigns) {
					callbackOk();
				} else {
					callbackKo();
				}
				return true;
			}, vrAppKey);
			if (isSimulationMode()) {
				if (useSimulationSuccessResponse) {
					return this.call(apiCall, "{\"hasCampaigns\":true}");
				} else {
					return this.call(apiCall, "{\"hasCampaigns\":false}");
				}
 			} else {
				// Default simulation response = Failure
				return this.call(apiCall, "{\"hasCampaigns\":false}");
			}
		}

		public LeChuckAPIResponse videoRewardShowAd(Action callbackOnOpen, Action callbackOnClose, Action callbackOnComplete, bool autoPause = true, bool useSimulationSuccessResponse = true) {
			return this.videoRewardShowAdForced(callbackOnOpen, callbackOnClose, callbackOnComplete, false, "", null, autoPause, useSimulationSuccessResponse);
		}

		/**
		 * Show an interlevel video ad for the provided VAST tagUrl:
		 * Set empty tagUrl for a demo tag. Use "[random]" for a random number, "[time]" for currentTimeMillis. 
		 * Do not use demo tags in production!
		 */
		public LeChuckAPIResponse videoRewardShowAdForced(Action callbackOnOpen, Action callbackOnClose, Action callbackOnComplete, bool forceAd = true, string forceAdTagUrl = "", string forceAdCampaignOptions = null, bool autoPause = true, bool useSimulationSuccessResponse = true) {
			if (forceAdCampaignOptions==null || forceAdCampaignOptions.Length == 0) {
				forceAdCampaignOptions = "{}";
			}
			float timeScale = 0f;
			if (Time.timeScale==0f) {
				autoPause = false;
			}
			ApiCall apiCall = new ApiCall("vrShowAd", (payload) => {
				//this.log("Video Reward callback with payload: "+payload);
				bool returnValue = false;
				if (autoPause) {
					if (payload=="open") {
						AudioListener.pause = true;
						timeScale = Time.timeScale;
						Time.timeScale = 0f;
					}
					if (payload=="close" ) {
						Time.timeScale = timeScale;
						returnValue = true; // Important: Remove callback after this event
						AudioListener.pause = false;
					}
				}
				if (payload=="open") {
					callbackOnOpen();
				} else if (payload=="close") {
					callbackOnClose();
				} else if (payload=="complete") {
					callbackOnComplete();
				} else {
					this.warning("Invalid payload received: "+payload);
				}
				return returnValue;
			}, forceAdTagUrl, forceAd ? "true":"false", forceAdCampaignOptions);
			
			if (isSimulationMode()) {
				if (useSimulationSuccessResponse) {
					this.instance.StartCoroutine(this.instance.delayedExecution(() => {
						this.call(apiCall, "complete");
					}, 0.5f));
					this.instance.StartCoroutine(this.instance.delayedExecution(() => {
						this.call(apiCall, "close");
					}, 1f));
					return this.call(apiCall, "open");
				} else {
					this.instance.StartCoroutine(this.instance.delayedExecution(() => {
						this.call(apiCall, "close");
					}, 0.5f));
					return this.call(apiCall, "open");
				}
			} else {
				return this.call(apiCall, "open");
			}
		}

		/**
		 * Show an interlevel video ad for the provided VAST tagUrl:
		 * Set empty tagUrl for a demo tag. Use "[random]" for a random number, "[time]" for currentTimeMillis. 
		 * Do not use demo tags in production!
		 */
		float interlevelTimeScale;
		public LeChuckAPIResponse showInterlevelAd(Action<InterlevelAd> callback, string tagUrl = "", bool autoPause = true, bool useSimulationSuccessResponse = true) {
			interlevelTimeScale = Time.timeScale;
			if (Time.timeScale==0f) {
				autoPause = false;
			}
			ApiCall apiCall = new ApiCall("showInterlevelAd", (payload) => {
				InterlevelAd interlevelAd = null;
				bool returnValue = false; // By default, return false to avoid this callback being disposed automatically when the intermediate calls are performed
				try {
					interlevelAd = JsonUtility.FromJson<InterlevelAd>(payload);
				} catch (Exception e) {
					this.warning("Invalid payload received ("+e.Message+"): "+payload);
				}
				if (autoPause) {
					this.log("Ad event received: " + interlevelAd.adEvent + ". Current timeScale: " + Time.timeScale);
					if (interlevelAd.adEvent=="start") {
						AudioListener.pause = true;
						interlevelTimeScale = Time.timeScale;
						Time.timeScale = 0f;
					}
					if (interlevelAd.adEvent=="endOk" || interlevelAd.adEvent=="endKo" ) {
						this.log("Resuming game. Current timeScale: " + Time.timeScale + " Resuming timeScale to " + interlevelTimeScale);
						Time.timeScale = interlevelTimeScale;
						returnValue = true; // Important: Remove callback after this event
						AudioListener.pause = false;
					}
				}
				callback(interlevelAd);
				return returnValue;
			}, tagUrl);
			if (useSimulationSuccessResponse) {
				if (isSimulationMode()) {
					this.instance.StartCoroutine(this.instance.delayedExecution(() => {
						this.call(apiCall, "{\"adEvent\":\"endOk\"}");
					}, 0.5f));
					return this.call(apiCall, "{\"adEvent\":\"start\"}");
				} else {
					return this.call(apiCall, "{\"adEvent\":\"endOk\"}");
				}
			} else {
				// Default simulation response = Failure
				return  this.call(apiCall, "{\"adEvent\":\"endKo\"}");
			}	
		}

		/**
		 * Get an item detail
		 */
		public LeChuckAPIResponse itemDetail(Action<ItemDetail> callback, string itemUid, bool useSimulationSuccessResponse = true) {
			ApiCall apiCall = new ApiCall("itemDetail", (payload) => {
				ItemDetail itemDetail = null;
				try {
					itemDetail = JsonUtility.FromJson<ItemDetail>(payload);
					if (itemDetail.item == null) {
						itemDetail.item = new Item();
					}
					itemDetail.item.uid = itemUid;
				} catch (Exception e) {
					this.warning("Invalid payload received ("+e.Message+"): "+payload);
				}
				callback(itemDetail);
				return true; // Important: Return true to remove callback afterwards
			}, itemUid);
			if (useSimulationSuccessResponse) {
				return this.call(apiCall, "{\"success\":true,\"errorType\":\"\",\"errorMessage\":\"\",\"item\":{\"id\":\"818\",\"uid\":\""+itemUid+"\",\"name\":\"test\",\"is_enabled\":\"1\",\"is_buyable\":\"0\",\"is_usable\":\"1\",\"price_minicoins\":\"1\",\"max_stock\":\"100\"}}");
			} else {
				// Default simulation response = Failure
				return this.call(apiCall, "{\"success\":false,\"errorType\":\"NOT_FOUND\",\"errorMessage\":\"Sorry, the page you requested was not found. Exception: Item '"+itemUid+"' not found or has been deleted\"}");
			}	
		}
		
		/**
		 * Get the list og items configured for the game
		 */
		public LeChuckAPIResponse itemList(Action<ItemList> callback, bool useSimulationSuccessResponse = true) {
			ApiCall apiCall = new ApiCall("itemList", (payload) => {
				ItemList itemList = null;
				try {
					itemList = JsonUtility.FromJson<ItemList>(payload);
					if (itemList.items == null) {
						itemList.items = new Item[0];
					}
				} catch (Exception e) {
					this.warning("Invalid payload received ("+e.Message+"): "+payload);
				}
				callback(itemList);
				return true; // Important: Return true to remove callback afterwards
			});
			if (useSimulationSuccessResponse) {
				return this.call(apiCall, "{\"success\":true,\"errorType\":\"\",\"errorMessage\":\"\",\"items\":[{\"id\":\"818\",\"uid\":\"test\",\"name\":\"test\",\"is_enabled\":\"1\",\"is_buyable\":\"0\",\"is_usable\":\"1\",\"price_minicoins\":\"1\",\"max_stock\":\"100\"},{\"id\":\"13\",\"uid\":\"test2\",\"name\":\"test2\",\"is_enabled\":\"1\",\"is_buyable\":\"1\",\"is_usable\":\"0\",\"price_minicoins\":\"100\",\"max_stock\":\"1\"}]}");
			} else {
				// Default simulation response = Failure
				return this.call(apiCall, "{\"success\":false,\"errorType\":\"NOT_FOUND\",\"errorMessage\":\"Sorry, the page you requested was not found.\"}");
			}	
		}

		/**
		 * Get an user item stock in his inventory
		 */
		public LeChuckAPIResponse itemStock(Action<ItemStock> callback, string itemUid, bool useSimulationSuccessResponse = true) {
			ApiCall apiCall = new ApiCall("itemStock", (payload) => {
				ItemStock itemStock = null;
				try {
					itemStock = JsonUtility.FromJson<ItemStock>(payload);
					if (itemStock.item == null) {
						itemStock.item = new Item();
					}
					itemStock.item.uid = itemUid;
				} catch (Exception e) {
					this.warning("Invalid payload received ("+e.Message+"): "+payload);
				}
				callback(itemStock);
				return true; // Important: Return true to remove callback afterwards
			}, itemUid);
			if (useSimulationSuccessResponse) {
				return this.call(apiCall, "{\"success\":true,\"errorType\":\"\",\"errorMessage\":\"\",\"item\":{\"id\":\"12\",\"uid\":\""+itemUid+"\",\"name\":\"test\",\"is_enabled\":\"1\",\"is_buyable\":\"1\",\"is_usable\":\"0\",\"price_minicoins\":\"100\",\"max_stock\":\"100\"},\"stock\":1,\"timestamp\":1422547620822,\"errorType\":\"\",\"errorMessage\":\"\"}");
			} else {
				// Default simulation response = Failure
				return this.call(apiCall, "{\"success\":false,\"item\":false,\"stock\":false,\"timestamp\":false,\"errorType\":\"NOT_FOUND\",\"errorMessage\":\"Sorry, the page you requested was not found. Exception: Item '"+itemUid+"' not found or has been deleted\"}");
			}	
		}
		
		/**
		 * Gets the full user inventory with item stocks
		 */
		public LeChuckAPIResponse itemInventory(Action<ItemInventory> callback, bool useSimulationSuccessResponse = true) {
			ApiCall apiCall = new ApiCall("itemInventory", (payload) => {
				ItemInventory itemInventory = null;
				try {
					itemInventory = JsonUtility.FromJson<ItemInventory>(payload);
					if (itemInventory.items == null) {
						itemInventory.items = new ItemWithStock[0];
					}
				} catch (Exception e) {
					this.warning("Invalid payload received ("+e.Message+"): "+payload);
				}
				callback(itemInventory);
				return true; // Important: Return true to remove callback afterwards
			});
			if (useSimulationSuccessResponse) {
				return this.call(apiCall, "{\"success\":true,\"errorType\":\"\",\"errorMessage\":\"\",\"items\":[{\"id\":12,\"stock\":1,\"timestamp\":1422547620822,\"item\":{\"id\":\"12\",\"uid\":\"test\",\"name\":\"test\",\"is_enabled\":\"1\",\"is_buyable\":\"1\",\"is_usable\":\"0\",\"price_minicoins\":\"20\",\"max_stock\":\"1\"}},{\"id\":13,\"stock\":1,\"timestamp\":1422547815663,\"item\":{\"id\":\"13\",\"uid\":\"test2\",\"name\":\"test2\",\"is_enabled\":\"1\",\"is_buyable\":\"1\",\"is_usable\":\"0\",\"price_minicoins\":\"100\",\"max_stock\":\"1\"}}]}");
			} else {
				// Default simulation response = Failure
				return this.call(apiCall, "{\"success\":false,\"errorType\":\"NOT_FOUND\",\"errorMessage\":\"Sorry, the page you requested was not found.\"}");
			}	
		}
		
		/**
		 * Initiates the purchase of an item.
		 */
		public LeChuckAPIResponse itemBuy(Action<ItemPurchaseList> callback, string itemUid, int qty, bool useSimulationSuccessResponse = true) {
			ApiCall apiCall = new ApiCall("itemBuy", (payload) => {
				ItemPurchaseList itemPurchase = null;
				try {
					itemPurchase = JsonUtility.FromJson<ItemPurchaseList>(payload);
					if (itemPurchase.items == null) {
						itemPurchase.items = new ItemPurchase[0];
					}
				} catch (Exception e) {
					this.warning("Invalid payload received ("+e.Message+"): "+payload);
				}
				callback(itemPurchase);
				return true; // Important: Return true to remove callback afterwards
			}, itemUid, qty.ToString());
			if (useSimulationSuccessResponse) {
				return this.call(apiCall, "{\"success\":true,\"errorType\":\"\",\"errorMessage\":\"\",\"items\":[{\"uid\":\""+itemUid+"\",\"qty\":"+qty+",\"stock\":"+qty+",\"total_minicoins\":0,\"is_dynamic\":false,\"dynamic_payload\":\"\"}]}");
			} else {
				// Default simulation response = Failure
				return this.call(apiCall, "{\"success\":false,\"errorType\":\"CANCELLED\",\"errorMessage\":\"User cancelled\",\"items\":null}");
			}	
		}
		
		/**
		 * Initiates the purchase of a dynamic item. Server 2 server validation & notification is mandatory.
		 */
		public LeChuckAPIResponse itemBuyDynamic(Action<ItemPurchaseList> callback, int priceInMinicoins, string title, string customIconUrl, string customData, bool useSimulationSuccessResponse = true) {
			ApiCall apiCall = new ApiCall("itemBuy", (payload) => {
				ItemPurchaseList itemPurchase = null;
				try {
					itemPurchase = JsonUtility.FromJson<ItemPurchaseList>(payload);
					if (itemPurchase.items == null) {
						itemPurchase.items = new ItemPurchase[0];
					}
				} catch (Exception e) {
					this.warning("Invalid payload received ("+e.Message+"): "+payload);
				}
				callback(itemPurchase);
				return true; // Important: Return true to remove callback afterwards
			}, "dynamic", priceInMinicoins+"|"+title+"|"+customIconUrl+"|"+customData);
			if (useSimulationSuccessResponse) {
				return this.call(apiCall, "{\"success\":true,\"errorType\":\"\",\"errorMessage\":\"\",\"items\":[{\"uid\":\"dynamic\",\"qty\":1,\"stock\":0,\"total_minicoins\":0,\"is_dynamic\":false,\"dynamic_payload\":\""+priceInMinicoins+"|"+title+"|"+customIconUrl+"|"+customData+"\"}]}");
			} else {
				// Default simulation response = Failure
				return this.call(apiCall, "{\"success\":false,\"errorType\":\"CANCELLED\",\"errorMessage\":\"User cancelled\",\"items\":null}");
			}	
		}

		/**
		 * Consumes (decrements) an arbitrary amount from the user item stock. Will fail if the user has not enough stock.
		 */
		public LeChuckAPIResponse itemConsume(Action<ItemConsume> callback, string itemUid, int qty, bool useSimulationSuccessResponse = true) {
			ApiCall apiCall = new ApiCall("itemConsume", (payload) => {
				ItemConsume itemConsume = null;
				try {
					itemConsume = JsonUtility.FromJson<ItemConsume>(payload);
					if (itemConsume.success) {
						itemConsume.qty = qty;
					}
				} catch (Exception e) {
					this.warning("Invalid payload received ("+e.Message+"): "+payload);
				}
				callback(itemConsume);
				return true; // Important: Return true to remove callback afterwards
			}, itemUid, qty.ToString());
			if (useSimulationSuccessResponse) {
				return this.call(apiCall, "{\"success\":true,\"errorType\":\"\",\"errorMessage\":\"\",\"uid\":\""+itemUid+"\",\"new_stock\":0}");
			} else {
				// Default simulation response = Failure
				return this.call(apiCall, "{\"success\":false,\"errorType\":\"LIMIT_EXCEEDED\",\"errorMessage\":\"Not enough stock available. User stock: 0 items. Substraction requested: -1 items\",\"uid\":\""+itemUid+"\",\"newStock\":null}");
			}	
		}		
	}
	
	public IEnumerator delayedExecution(Action action, float time) {
		yield return new WaitForSecondsRealtime(time);
		action();
	}


}