// Send a message representing new page info to the controller.
    // Typically will be called when the user presses previous or next.
    // Sends until success, in a new thread.
    public void SendStorybookPageInfoAction(StorybookPageInfo pageInfo)
    {
        Thread thread = new Thread(() => {
            Dictionary <string, object> publish = new Dictionary <string, object>();
            publish.Add("topic", Constants.STORYBOOK_PAGE_INFO_TOPIC);
            publish.Add("op", "publish");

            Dictionary <string, object> data = new Dictionary <string, object>();
            data.Add("header", RosbridgeUtilities.GetROSHeader());
            data.Add("story_name", pageInfo.storyName);
            data.Add("page_number", pageInfo.pageNumber);
            data.Add("sentences", pageInfo.sentences);

            List <Dictionary <string, object> > tinkerTexts =
                new List <Dictionary <string, object> > ();
            foreach (StorybookTinkerText t in pageInfo.tinkerTexts)
            {
                Dictionary <string, object> tinkerText = new Dictionary <string, object>();
                tinkerText.Add("has_scene_object", t.hasSceneObject);
                tinkerText.Add("scene_object_id", t.sceneObjectId);
                tinkerText.Add("word", t.word);
                tinkerTexts.Add(tinkerText);
            }
            data.Add("tinkertexts", tinkerTexts);

            List <Dictionary <string, object> > sceneObjects =
                new List <Dictionary <string, object> >();
            foreach (StorybookSceneObject o in pageInfo.sceneObjects)
            {
                Dictionary <string, object> sceneObject = new Dictionary <string, object>();
                sceneObject.Add("id", o.id);
                sceneObject.Add("label", o.label);
                sceneObject.Add("in_text", o.inText);
                sceneObjects.Add(sceneObject);
            }
            data.Add("scene_objects", sceneObjects);

            List <Dictionary <string, object> > prompts = new List <Dictionary <string, object> >();
            foreach (JiboPrompt p in pageInfo.prompts)
            {
                Dictionary <string, object> prompt = new Dictionary <string, object>();
                prompt.Add("question", p.question);
                prompt.Add("response", p.response);
                prompt.Add("hint", p.hint);
                prompts.Add(prompt);
            }
            data.Add("prompts", prompts);

            publish.Add("msg", data);
            Logger.Log("Sending page info ROS message: " + Json.Serialize(publish));
            bool sent = false;
            while (!sent)
            {
                sent = this.rosClient.SendMessage(Json.Serialize(publish));
            }
            Logger.Log("Successfully sent page info ROS message.");
        });

        thread.Start();
    }
    // Helper function to wrap together two actions:
    // (1) loading a page and (2) sending the StorybookPageInfo message over ROS.
    private void loadPageAndSendRosMessage(SceneDescription sceneDescription)
    {
        // Load the page.
        this.storyManager.LoadPage(sceneDescription);

        // Send the ROS message to update the controller about what page we're on now.
        StorybookPageInfo updatedInfo = new StorybookPageInfo();

        updatedInfo.storyName  = this.currentStory.GetName();
        updatedInfo.pageNumber = this.currentPageNumber;
        updatedInfo.sentences  = this.storyManager.stanzaManager.GetAllSentenceTexts();

        // Update state (will get automatically sent to the controller.
        StorybookStateManager.SetStorySelected(this.currentStory.GetName(),
                                               this.currentStory.GetNumPages());

        // Gather information about scene objects.
        StorybookSceneObject[] sceneObjects =
            new StorybookSceneObject[sceneDescription.sceneObjects.Length];
        for (int i = 0; i < sceneDescription.sceneObjects.Length; i++)
        {
            SceneObject          so  = sceneDescription.sceneObjects[i];
            StorybookSceneObject sso = new StorybookSceneObject();
            sso.id          = so.id;
            sso.label       = so.label;
            sso.inText      = so.inText;
            sceneObjects[i] = sso;
        }
        updatedInfo.sceneObjects = sceneObjects;

        // Gather information about tinker texts.
        StorybookTinkerText[] tinkerTexts =
            new StorybookTinkerText[this.storyManager.tinkerTexts.Count];
        for (int i = 0; i < this.storyManager.tinkerTexts.Count; i++)
        {
            TinkerText          tt  = this.storyManager.tinkerTexts[i].GetComponent <TinkerText>();
            StorybookTinkerText stt = new StorybookTinkerText();
            stt.word           = tt.word;
            stt.hasSceneObject = false;
            stt.sceneObjectId  = -1;
            tinkerTexts[i]     = stt;
        }
        foreach (Trigger trigger in sceneDescription.triggers)
        {
            if (trigger.type == TriggerType.CLICK_TINKERTEXT_SCENE_OBJECT)
            {
                tinkerTexts[trigger.args.textId].hasSceneObject = true;
                tinkerTexts[trigger.args.textId].sceneObjectId  = trigger.args.sceneObjectId;
            }
        }
        updatedInfo.tinkerTexts = tinkerTexts;

        // Send the message.
        if (Constants.USE_ROS)
        {
            this.rosManager.SendStorybookPageInfoAction(updatedInfo);
        }
    }