/// <summary>
        /// Initializes a new instance of the <see cref="SensusUI.ScriptRunnerPage"/> class.
        /// </summary>
        /// <param name="scriptRunner">Script runner to display.</param>
        public ScriptRunnerPage(ScriptRunner scriptRunner)
        {
            Title = "Script";

            StackLayout contentLayout = new StackLayout
                {
                    Orientation = StackOrientation.Vertical,
                    VerticalOptions = LayoutOptions.FillAndExpand
                };

            foreach (StackLayout stack in UiProperty.GetPropertyStacks(scriptRunner))
                contentLayout.Children.Add(stack);

            Button editPromptsButton = new Button
            {
                Text = "Edit Prompts",
                FontSize = 20,
                HorizontalOptions = LayoutOptions.FillAndExpand
            };

            editPromptsButton.Clicked += async (o, e) =>
            {
                await Navigation.PushAsync(new PromptsPage(scriptRunner.Script.Prompts));
            };

            contentLayout.Children.Add(editPromptsButton);

            Button editTriggersButton = new Button
            {
                Text = "Edit Triggers",
                FontSize = 20,
                HorizontalOptions = LayoutOptions.FillAndExpand
            };

            editTriggersButton.Clicked += async (o, e) =>
            {
                await Navigation.PushAsync(new ScriptTriggersPage(scriptRunner));
            };

            contentLayout.Children.Add(editTriggersButton);

            Content = new ScrollView
            {
                Content = contentLayout
            };
        }
        /// <summary>
        /// Initializes a new instance of the <see cref="SensusUI.ScriptTriggersPage"/> class.
        /// </summary>
        /// <param name="scriptRunner">Script runner to display.</param>
        public ScriptTriggersPage(ScriptRunner scriptRunner)
        {
            Title = "Script Triggers";

            ListView triggerList = new ListView();
            triggerList.ItemTemplate = new DataTemplate(typeof(TextCell));
            triggerList.ItemTemplate.SetBinding(TextCell.TextProperty, new Binding(".", stringFormat: "{0}"));
            triggerList.ItemsSource = scriptRunner.Triggers;
            triggerList.ItemTapped += async (o, e) =>
            {
                if (triggerList.SelectedItem == null)
                    return;

                SensusService.Probes.User.Trigger selectedTrigger = triggerList.SelectedItem as SensusService.Probes.User.Trigger;

                string selectedAction = await DisplayActionSheet(selectedTrigger.ToString(), "Cancel", null, "Delete");

                if (selectedAction == "Delete")
                {
                    if (await DisplayAlert("Confirm Delete", "Are you sure you want to delete the selected trigger?", "Yes", "Cancel"))
                    {
                        scriptRunner.Triggers.Remove(selectedTrigger);
                        triggerList.SelectedItem = null;  // must reset this, since it isn't reset automatically
                    }
                }
            };
            
            Content = triggerList;

            ToolbarItems.Add(new ToolbarItem(null, "plus.png", async () =>
                    {
                        if (scriptRunner.Probe.Protocol.Probes.Where(p => p != scriptRunner.Probe && p.Enabled).Count() > 0)
                            await Navigation.PushAsync(new AddScriptTriggerPage(scriptRunner));
                        else
                            SensusServiceHelper.Get().FlashNotificationAsync("You must enable other probes before adding triggers.");
                    }));
        }
        /// <summary>
        /// Initializes a new instance of the <see cref="SensusUI.AddScriptTriggerPage"/> class.
        /// </summary>
        /// <param name="scriptRunner">Script runner to add trigger to.</param>
        public AddScriptTriggerPage(ScriptRunner scriptRunner)
        {
            _scriptRunner = scriptRunner;

            Title = "Add Trigger";

            List<Probe> enabledProbes = _scriptRunner.Probe.Protocol.Probes.Where(p => p != _scriptRunner.Probe && p.Enabled).ToList();
            if (enabledProbes.Count == 0)
            {
                Content = new Label
                {
                    Text = "No enabled probes. Please enable them before creating triggers.",
                    FontSize = 20
                };

                return;
            }

            StackLayout contentLayout = new StackLayout
            {
                Orientation = StackOrientation.Vertical,
                VerticalOptions = LayoutOptions.FillAndExpand
            };

            Label probeLabel = new Label
            {
                Text = "Probe:",
                FontSize = 20
            };

            Picker probePicker = new Picker { Title = "Select Probe", HorizontalOptions = LayoutOptions.FillAndExpand };
            foreach (Probe enabledProbe in enabledProbes)
                probePicker.Items.Add(enabledProbe.DisplayName);

            contentLayout.Children.Add(new StackLayout
            {
                Orientation = StackOrientation.Horizontal,
                HorizontalOptions = LayoutOptions.FillAndExpand,
                Children = { probeLabel, probePicker }
            });

            StackLayout triggerDefinitionLayout = new StackLayout
            {
                Orientation = StackOrientation.Vertical,
                VerticalOptions = LayoutOptions.FillAndExpand
            };

            contentLayout.Children.Add(triggerDefinitionLayout);

            Switch changeSwitch = new Switch();
            Switch regexSwitch = new Switch();
            Switch fireRepeatedlySwitch = new Switch();
            Switch ignoreFirstDatumSwitch = new Switch();
            TimePicker startTimePicker = new TimePicker { HorizontalOptions = LayoutOptions.FillAndExpand };
            TimePicker endTimePicker = new TimePicker { HorizontalOptions = LayoutOptions.FillAndExpand };

            probePicker.SelectedIndexChanged += (o, e) =>
                {
                    _selectedProbe = null;
                    _selectedDatumProperty = null;
                    _conditionValue = null;

                    triggerDefinitionLayout.Children.Clear();

                    if (probePicker.SelectedIndex < 0)
                        return;

                    _selectedProbe = enabledProbes[probePicker.SelectedIndex];

                    PropertyInfo[] datumProperties = _selectedProbe.DatumType.GetProperties(BindingFlags.Public | BindingFlags.Instance).Where(p => p.GetCustomAttributes<ProbeTriggerProperty>().Count() > 0).ToArray();
                    if(datumProperties.Length == 0)
                        return;

                    #region datum property picker
                    Label datumPropertyLabel = new Label
                    {
                        Text = "Property:",
                        FontSize = 20
                    };

                    Picker datumPropertyPicker = new Picker { Title = "Select Datum Property", HorizontalOptions = LayoutOptions.FillAndExpand };
                    foreach (PropertyInfo datumProperty in datumProperties)
                    {
                        ProbeTriggerProperty triggerProperty = datumProperty.GetCustomAttributes<ProbeTriggerProperty>().First();
                        datumPropertyPicker.Items.Add(triggerProperty.Name ?? datumProperty.Name);
                    }

                    triggerDefinitionLayout.Children.Add(new StackLayout
                    {
                        Orientation = StackOrientation.Horizontal,
                        HorizontalOptions = LayoutOptions.FillAndExpand,
                        Children = { datumPropertyLabel, datumPropertyPicker }
                    });
                    #endregion

                    #region condition picker (same for all datum types)
                    Label conditionLabel = new Label
                    {
                        Text = "Condition:",
                        FontSize = 20
                    };

                    Picker conditionPicker = new Picker { Title = "Select Condition", HorizontalOptions = LayoutOptions.FillAndExpand };
                    TriggerValueCondition[] conditions = Enum.GetValues(typeof(TriggerValueCondition)) as TriggerValueCondition[];
                    foreach (TriggerValueCondition condition in conditions)
                        conditionPicker.Items.Add(condition.ToString());

                    conditionPicker.SelectedIndexChanged += (oo, ee) =>
                        {
                            if (conditionPicker.SelectedIndex < 0)
                                return;

                            _selectedCondition = conditions[conditionPicker.SelectedIndex];
                        };

                    triggerDefinitionLayout.Children.Add(new StackLayout
                    {
                        Orientation = StackOrientation.Horizontal,
                        HorizontalOptions = LayoutOptions.FillAndExpand,
                        Children = { conditionLabel, conditionPicker }
                    });
                    #endregion

                    #region condition value for comparison, based on selected datum property -- includes change calculation (for double datum) and regex (for string datum)
                    StackLayout conditionValueStack = new StackLayout
                    {
                        Orientation = StackOrientation.Vertical,
                        HorizontalOptions = LayoutOptions.FillAndExpand
                    };

                    triggerDefinitionLayout.Children.Add(conditionValueStack);

                    datumPropertyPicker.SelectedIndexChanged += (oo, ee) =>
                        {
                            _selectedDatumProperty = null;
                            _conditionValue = null;

                            conditionValueStack.Children.Clear();

                            if (datumPropertyPicker.SelectedIndex < 0)
                                return;

                            _selectedDatumProperty = datumProperties[datumPropertyPicker.SelectedIndex];

                            ProbeTriggerProperty datumTriggerAttribute = _selectedDatumProperty.GetCustomAttribute<ProbeTriggerProperty>();

                            View conditionValueStackView = null;
                            bool allowChangeCalculation = false;
                            bool allowRegularExpression = false;

                            if (datumTriggerAttribute is ListProbeTriggerProperty)
                            {
                                Picker conditionValuePicker = new Picker { Title = "Select Condition Value", HorizontalOptions = LayoutOptions.FillAndExpand };
                                object[] items = (datumTriggerAttribute as ListProbeTriggerProperty).Items;
                                foreach (object item in items)
                                    conditionValuePicker.Items.Add(item.ToString());

                                conditionValuePicker.SelectedIndexChanged += (ooo, eee) =>
                                    {
                                        if (conditionValuePicker.SelectedIndex < 0)
                                            return;

                                        _conditionValue = items[conditionValuePicker.SelectedIndex];
                                    };

                                conditionValueStackView = conditionValuePicker;
                            }
                            else if (datumTriggerAttribute is NumberProbeTriggerProperty)
                            {
                                Entry entry = new Entry
                                {
                                    Keyboard = Keyboard.Numeric,
                                    HorizontalOptions = LayoutOptions.FillAndExpand
                                };

                                entry.TextChanged += (ooo, eee) =>
                                    {
                                        double value;
                                        if (double.TryParse(eee.NewTextValue, out  value))
                                            _conditionValue = value;
                                    };

                                conditionValueStackView = entry;
                                allowChangeCalculation = true;
                            }
                            else if (datumTriggerAttribute is TextProbeTriggerProperty)
                            {
                                Entry entry = new Entry
                                {
                                    Keyboard = Keyboard.Default,
                                    HorizontalOptions = LayoutOptions.FillAndExpand
                                };

                                entry.TextChanged += (ooo, eee) => _conditionValue = eee.NewTextValue;

                                conditionValueStackView = entry;
                                allowRegularExpression = true;
                            }
                            else if (datumTriggerAttribute is BooleanProbeTriggerProperty)
                            {
                                Switch booleanSwitch = new Switch();

                                booleanSwitch.Toggled += (ooo, eee) => _conditionValue = eee.Value;

                                conditionValueStackView = booleanSwitch;
                            }

                            Label conditionValueStackLabel = new Label
                            {
                                Text = "Value:",
                                FontSize = 20
                            };

                            conditionValueStack.Children.Add(new StackLayout
                            {
                                Orientation = StackOrientation.Horizontal,
                                HorizontalOptions = LayoutOptions.FillAndExpand,
                                Children = { conditionValueStackLabel, conditionValueStackView }
                            });

                            #region change calculation
                            if (allowChangeCalculation)
                            {
                                Label changeLabel = new Label
                                {
                                    Text = "Change:",
                                    FontSize = 20
                                };

                                changeSwitch.IsToggled = false;

                                conditionValueStack.Children.Add(new StackLayout
                                {
                                    Orientation = StackOrientation.Horizontal,
                                    HorizontalOptions = LayoutOptions.FillAndExpand,
                                    Children = { changeLabel, changeSwitch }
                                });
                            }
                            #endregion

                            #region regular expression
                            if (allowRegularExpression)
                            {
                                Label regexLabel = new Label
                                {
                                    Text = "Regular Expression:",
                                    FontSize = 20
                                };

                                regexSwitch.IsToggled = false;

                                conditionValueStack.Children.Add(new StackLayout
                                {
                                    Orientation = StackOrientation.Horizontal,
                                    HorizontalOptions = LayoutOptions.FillAndExpand,
                                    Children = { regexLabel, regexSwitch }
                                });
                            }
                            #endregion
                        };

                    datumPropertyPicker.SelectedIndex = 0;
                    #endregion

                    #region fire repeatedly
                    Label fireRepeatedlyLabel = new Label
                        {
                            Text = "Fire Repeatedly:",
                            FontSize = 20
                        };

                    fireRepeatedlySwitch.IsToggled = false;

                    triggerDefinitionLayout.Children.Add(new StackLayout
                        {
                            Orientation = StackOrientation.Horizontal,
                            HorizontalOptions = LayoutOptions.FillAndExpand,
                            Children = { fireRepeatedlyLabel, fireRepeatedlySwitch }
                        });
                    #endregion

                    #region ignore first datum
                    Label ignoreFirstDatumLabel = new Label
                        {
                            Text = "Ignore First Datum:",
                            FontSize = 20
                        };

                    ignoreFirstDatumSwitch.IsToggled = false;

                    triggerDefinitionLayout.Children.Add(new StackLayout
                        {
                            Orientation = StackOrientation.Horizontal,
                            HorizontalOptions = LayoutOptions.FillAndExpand,
                            Children = { ignoreFirstDatumLabel, ignoreFirstDatumSwitch }
                        });
                    #endregion

                    #region start/end times
                    Label startTimeLabel = new Label
                        {
                            Text = "Start Time:",
                            FontSize = 20
                        };

                    startTimePicker.Time = new TimeSpan(8, 0, 0);

                    triggerDefinitionLayout.Children.Add(new StackLayout
                        {
                            Orientation = StackOrientation.Horizontal,
                            HorizontalOptions = LayoutOptions.FillAndExpand,
                            Children = { startTimeLabel, startTimePicker }
                        });

                    Label endTimeLabel = new Label
                        {
                            Text = "End Time:",
                            FontSize = 20
                        };

                    endTimePicker.Time = new TimeSpan(21, 0, 0);

                    triggerDefinitionLayout.Children.Add(new StackLayout
                        {
                            Orientation = StackOrientation.Horizontal,
                            HorizontalOptions = LayoutOptions.FillAndExpand,
                            Children = { endTimeLabel, endTimePicker }
                        });
                    #endregion
                };

            probePicker.SelectedIndex = 0;

            Button okButton = new Button
            {
                Text = "OK",
                FontSize = 20
            };

            okButton.Clicked += async (o, e) =>
                {
                    try
                    {
                        _scriptRunner.Triggers.Add(new SensusService.Probes.User.Trigger(_selectedProbe, _selectedDatumProperty, _selectedCondition, _conditionValue, changeSwitch.IsToggled, fireRepeatedlySwitch.IsToggled, regexSwitch.IsToggled, ignoreFirstDatumSwitch.IsToggled, startTimePicker.Time, endTimePicker.Time));
                        await Navigation.PopAsync();
                    }
                    catch (Exception ex)
                    {
                        string message = "Failed to add trigger:  " + ex.Message;
                        UiBoundSensusServiceHelper.Get(true).FlashNotificationAsync(message);
                        UiBoundSensusServiceHelper.Get(true).Logger.Log(message, LoggingLevel.Normal, GetType());
                    }
                };

            contentLayout.Children.Add(okButton);

            Content = new ScrollView
            {
                Content = contentLayout
            };
        }