/// <summary>
        /// Logs a session and returns session information on success.
        /// </summary>
        public static void LogSession(MonoBehaviour mb, ClientArgs args, Guid userId, string detail, string revisionId, Action<Guid, string> onSuccess, Action<string> onFailure)
        {
            var data = new Dictionary<string, object>();
            data.Add("user_id", userId);
            data.Add("release_id", args.ReleaseId);
            // XXX (kasiu): Need to check if this is the right DateTime string to send. I THINK THIS IS WRONG BUT I DON'T GIVE A FOOBAR RIGHT NOW. FIXME WHEN THE SERVER SCREAMS.
            data.Add("client_time", DateTime.Now.ToString());
            data.Add("detail", detail);
            data.Add("library_revid", revisionId);

            // Processing to get session_id
            Action<string> callback = s => {
                var s2 = s.Replace("{", "").Replace("}", "").Replace("\"", "").Trim();
                var split = s2.Split(',');
                if (split.Length != 2) {
                    onFailure(string.Format("LogSession received ill-formatted JSON: {0}", s));
                }
                var sessionId = split[0].Split(':')[1].Trim();
                var sessionKey = split[1].Split(':')[1].Trim();

                onSuccess(new Guid(sessionId), sessionKey);
            };

            var newArgs = new ClientArgs(new Uri(args.BaseUri, "/api/session"), args);
            SendNonSessionRequest(mb, newArgs, data, callback, onFailure);
        }
        /// <summary>
        /// Queries for an experimental condition (integer), which is assigned on success.
        /// </summary>
        public static void QueryExperimentalCondition(MonoBehaviour mb, ClientArgs args, Guid userId, Guid experimentId, Action<int> onSuccess, Action<string> onFailure)
        {
            var data = new Dictionary<string, object>();
            data.Add("user_id", userId);
            data.Add("experiment_id", experimentId);
            Action<string> callback = s => {
                // HACK (kasiu): Again, more delightful condition retrieval without the pain of real JSON parsing.
                var split = s.Split(':');
                if (split.Length != 2) {
                    onFailure(string.Format("QueryExperimentalCondition received ill-formatted JSON: {0}", s));
                }

                var conditionStr = split[1].Replace("}", "").Trim();
                onSuccess(int.Parse(conditionStr));
            };

            var newArgs = new ClientArgs(new Uri(args.BaseUri, "/api/experiment"), args);
            SendNonSessionRequest(mb, newArgs, data, callback, onFailure);
        }
    /// <summary>
    /// Tests the telemetry server by writing some hilarious dummy values.
    /// Left public to invoke externally if you desire.
    /// </summary>
    public void TestTelemetryServer()
    {
        Debug.Log("Testing the telemetry server");
        // Set up some dummy values for testing.
        var releaseId = Guid.NewGuid();
        var releaseKey = Guid.NewGuid().ToString();
        var clientArgs = new ClientArgs(serverUri, releaseId, releaseKey);

        // TESTING QUERY USER ID ----> and some other stuff.
        var userId = Guid.Empty;
        var experimentalCondition = -1;

        Action<string> onSuccess = s => {
            Debug.Log("OnSuccess: " + s);
        };

        Action<string> onFailure = s => {
            Debug.LogError("OnFailure: " + s);
        };

        Action<int> setExperimentalConditionOnSuccess = i => {
            experimentalCondition = i;
            Debug.Log("Got successful user experiment condition: " + i);
        };

        Action<string> setUserDataOnSuccess = s => {
            // Probably could do something with data? Nothing gets returned, so we don't care.

            // TESTING QUERY DATA
            Debug.Log("Testing that QueryUserData works...");
            UnityBackend.QueryUserData(this, clientArgs, userId, onSuccess, onFailure);
        };

        Action<string> setOnLogEventOnSuccess = s => {
            Debug.Log("Succesfully logged event: " + s);
        };

        Action<Guid, string> setOnLogSessionOnSuccess = (g, s) => {
            Debug.Log("Got session id: " + g.ToString());
            Debug.Log("Got session key: " + s);

            // TESTING LOG EVENTS (just going to log some root events because f**k tasks for now)
            var eventDetail = new Dictionary<string, object>();
            eventDetail.Add("PANCAKES", "HAMSTERS");

            var eventDict = new Dictionary<string, object>();
            eventDict.Add("category_id", 42);
            eventDict.Add("type_id", 666);
            eventDict.Add("session_sequence_index", 1);
            eventDict.Add("client_time", DateTime.Now.ToString());
            eventDict.Add("detail", MicroJSON.Serialize(eventDetail));

            var events = new object[] { eventDict };
            UnityBackend.LogEvents(this, serverUri, events, g, s, setOnLogEventOnSuccess, onFailure);
        };

        Action<Guid> setUserIdOnSuccess = g => {
            userId = g;
            Debug.Log("Got successful user id: " + g.ToString());

            // TESTING QUERY EXPERIMENTAL CONDITION
            Debug.Log("Testing that QueryExperimentalCondition works...");
            UnityBackend.QueryExperimentalCondition(this, clientArgs, userId, new Guid("00000000-0000-0000-0000-000000000000"), setExperimentalConditionOnSuccess, onFailure);

            // TESTING SAVE/QUERY USER DATA
            Debug.Log("Testing that SaveUserData works...");
            var saveData = new Dictionary<string, object>();
            saveData.Add("I'm some", new object[] { "save", "data" });
            UnityBackend.SetUserData(this, clientArgs, userId, MicroJSON.Serialize(saveData), setUserDataOnSuccess, onFailure);

            // TESTING LOG SESSION
            Debug.Log("Testing that LogSession works...");
            var sessionData = new Dictionary<string, object>();
            sessionData.Add("i'm", "some_data");
            sessionData.Add("with", new object[] { 2, "arrays" });

            UnityBackend.LogSession(this, clientArgs, userId, MicroJSON.Serialize(sessionData), "UNHAPPY ID", setOnLogSessionOnSuccess, onFailure);
        };

        Debug.Log("Testing that QueryUserId works...");
        UnityBackend.QueryUserId(this, clientArgs, "dedennehblehs", setUserIdOnSuccess, onFailure);
    }
        /// <summary>
        /// Sends a non-session request.
        /// </summary>
        private static void SendNonSessionRequest(MonoBehaviour mb, ClientArgs args, Dictionary<string, object> data, Action<string> onSuccess, Action<string> onFailure)
        {
            var values = new Dictionary<string, object>();
            values.Add("version", PROTOCOL_VERSION);
            values.Add("data", MicroJSON.Serialize(data));
            values.Add("release", args.ReleaseId);
            values.Add("checksum", string.Empty);
            var jsonString = MicroJSON.Serialize(values);

            mb.StartCoroutine(SendPostRequest(args.BaseUri, jsonString, onSuccess, onFailure));
        }
        /// <summary>
        /// Queries the user id.
        /// </summary>
        public static void QueryUserId(MonoBehaviour mb, ClientArgs args, string username, Action<Guid> onSuccess, Action<string> onFailure)
        {
            var data = new Dictionary<string, object>();
            data.Add("username", username);
            Action<string> callback = s => {
                // HACK (kasiu): Get the Guid out without real JSON parsing.
                var split = s.Split(':');
                if (split.Length != 2) {
                    onFailure(string.Format("QueryUserId received ill-formatted JSON: {0}", s));
                }
                var s2 = split[1];
                var startIndex = s2.IndexOf('"') + 1; // +1 past the first quote
                var endIndex = s2.IndexOf('"', startIndex);
                var guid = s2.Substring(startIndex, endIndex - startIndex);
                onSuccess(new Guid(guid));
            };

            var newArgs = new ClientArgs(new Uri(args.BaseUri, "/api/user"), args);
            SendNonSessionRequest(mb, newArgs, data, callback, onFailure);
        }
        /// <summary>
        /// Sets user data.
        /// </summary>
        public static void SetUserData(MonoBehaviour mb, ClientArgs args, Guid userId, string savedata, Action<string> onSuccess, Action<string> onFailure)
        {
            var data = new Dictionary<string, object>();
            data.Add("id", userId);
            data.Add("savedata", savedata);

            // We don't need to implement a special callback here, since we don't get anything back.
            var newArgs = new ClientArgs(new Uri(args.BaseUri, "/api/user/set_data"), args);
            SendNonSessionRequest(mb, newArgs, data, onSuccess, onFailure);
        }
        /// <summary>
        /// Queries for any saved user data.
        /// </summary>
        public static void QueryUserData(MonoBehaviour mb, ClientArgs args, Guid userId, Action<string> onSuccess, Action<string> onFailure)
        {
            var data = new Dictionary<string, object>();
            data.Add("id", userId);

            // TODO (kasiu): Process the user data here?

            var newArgs = new ClientArgs(new Uri(args.BaseUri, "/api/user/get_data"), args.ReleaseId, args.ReleaseKey);
            SendNonSessionRequest(mb, newArgs, data, onSuccess, onFailure);
        }
    /// <summary>
    /// Unity Start()
    /// </summary>
    private void Start()
    {
        #if UNITY_EDITOR
        this.serverUri = new Uri(DevServer);
        #else
        this.serverUri = new Uri(ProdServer);
        #endif

        // Set up some dummy values for testing.
        var releaseId = Guid.NewGuid();
        var releaseKey = Guid.NewGuid().ToString();
        var clientArgs = new ClientArgs(serverUri, releaseId, releaseKey);

        // TESTING QUERY USER ID ----> and some other stuff.
        var userId = Guid.Empty;
        var experimentalCondition = -1;

        var pClient = this.gameObject.GetComponent<PapikaClient>();
        if (pClient == null) {
            throw new NullReferenceException("Please make sure the PapikaClient component is also attached to the game object with this component.");
        }

        pClient.Initialize(clientArgs);

        Action<string> onSuccess = s => {
            Debug.Log("OnSuccess: " + s);
        };

        Action<string> onFailure = s => {
            Debug.LogError("OnFailure: " + s);
        };

        Action<int> setExperimentalConditionOnSuccess = i => {
            experimentalCondition = i;
            Debug.Log("Got successful user experiment condition: " + i);
        };

        Action<string> setUserDataOnSuccess = s => {
            // Probably could do something with data? Nothing gets returned, so we don't care.

            // TESTING QUERY DATA
            Debug.Log("Testing that QueryUserData works...");
            pClient.QueryUserData(userId, onSuccess, onFailure);
        };

        Action<Guid> setUserIdOnSuccess = g => {
            userId = g;
            Debug.Log("Got successful user id: " + g.ToString());

            // TESTING QUERY EXPERIMENTAL CONDITION
            Debug.Log("Testing that QueryExperimentalCondition works...");
            pClient.QueryExperimentalCondition(userId, new Guid("00000000-0000-0000-0000-000000000000"), setExperimentalConditionOnSuccess, onFailure);

            // TESTING SAVE/QUERY USER DATA
            Debug.Log("Testing that SaveUserData works...");
            var saveData = new Dictionary<string, object>();
            saveData.Add("I'm some", new object[] { "save", "data (again)" });
            pClient.SetUserData(userId, MicroJSON.Serialize(saveData), setUserDataOnSuccess, onFailure);

            // TESTING LOG SESSION
            Debug.Log("Testing that LogSession works...");
            var sessionData = new Dictionary<string, object>();
            sessionData.Add("i'm", "some_data");
            sessionData.Add("with", new object[] { 2, "arrays (maybe)" });

            pClient.StartSession(userId, MicroJSON.Serialize(sessionData));

            // TESTING LOG EVENTS (just going to log some root events)
            // XXX (kasiu): Does not test task logging yet.
            Debug.Log("Testing that LogEvent works...");
            var eventDetail = new Dictionary<string, object>();
            eventDetail.Add("WAFFLES", "HAMSTERS");
            pClient.Root.LogEvent(42, 667, MicroJSON.Serialize(eventDetail));
        };

        pClient.QueryUserId("I love cheesecake", setUserIdOnSuccess, onFailure);
    }
 // PUBLIC CLIENT FUNCTIONS!
 /// <summary>
 /// Sets the client-side configuration arguments.
 /// This should be called in a Unity Start() call, probably.
 /// </summary>
 public void Initialize(ClientArgs args)
 {
     this.config = args;
 }
        /// <summary>
        /// Unity Awake()
        /// </summary>
        private void Awake()
        {
            // this may be called multiple times if scene gets reloaded, don't reinitialize!

            // Process editor parameters or assume user will call initialize with them later.
            if (this.config == null && !(string.IsNullOrEmpty(BaseUri) || string.IsNullOrEmpty(ReleaseId) || string.IsNullOrEmpty(ReleaseKey))) {
                this.config = new ClientArgs(new Uri(BaseUri), new Guid(ReleaseId), ReleaseKey);
            }

            if (this.eventsToLog == null) {
                this.eventsToLog = new List<object>();
                this.sessionSequenceCounter = 1;
                this.sessionId = null;
                this.sessionKey = null;
                this.isFlushEventsLocked = false;
                this.taskIdCounter = 1;
                this.wasStartSessionCalled = false;
                this.Root = new TaskLogger(null, logEvent, getTaskId);
            }
        }
 /// <summary>
 /// Constructor.
 /// This one is used internally to copy over non-Uri args and really
 /// shouldn't be used outside of backend code.
 /// </summary>
 public ClientArgs(Uri newUri, ClientArgs oldArgs)
 {
     BaseUri = newUri;
     ReleaseId = oldArgs.ReleaseId;
     ReleaseKey = oldArgs.ReleaseKey;
 }