//Converts a data value into a degree value between 225 deg (-10%) and -45 deg (110%)
    double ValueToDegrees(NumericalData data)
    {
        double degrees = (100 * (data.value - data.minValue) / (data.maxValue - data.minValue)) * -2.25 + 202.5;

        if (degrees > 225)
        {
            return(225);
        }
        else if (degrees < -45)
        {
            return(-45);
        }
        else
        {
            return(degrees);
        }
    }
    //Converts json data into TelemetryData objects, sorts by severity and name, and updates notification array
    void UpdateNotificationsArray(JSONData jsonData)
    {
        //Numerical data
        NumericalData Heart_bpm   = new NumericalData("Heart rate", jsonData.heart_bpm, "bpm", 0, 300);
        NumericalData P_suit      = new NumericalData("Suit pressure", jsonData.p_suit, "psia", 2, 4);
        NumericalData P_sub       = new NumericalData("External pressure", jsonData.p_sub, "psia", 2, 4);
        NumericalData T_sub       = new NumericalData("External temperature", jsonData.t_sub, "F", -150, 250);
        NumericalData V_fan       = new NumericalData("Fan speed", jsonData.v_fan, "RPM", 10000, 40000);
        NumericalData P_o2        = new NumericalData("O2 pressure", jsonData.p_o2, "psia", 750, 950);
        NumericalData Rate_o2     = new NumericalData("O2 flow rate", jsonData.rate_o2, "psi/min", 0.5, 1);
        NumericalData Cap_battery = new NumericalData("Battery capacity", jsonData.cap_battery, "amp-hr", 0, 30);
        NumericalData P_h2o_g     = new NumericalData("H2O gas pressure", jsonData.p_h2o_g, "psia", 14, 16);
        NumericalData P_h2o_l     = new NumericalData("H2O liquid pressure", jsonData.p_h2o_l, "psia", 14, 16);
        NumericalData P_sop       = new NumericalData("SOP pressure", jsonData.p_sop, "psia", 750, 950);
        NumericalData Rate_sop    = new NumericalData("SOP flow rate", jsonData.rate_sop, "psia/min", 0.5, 1);

        //Switch data
        SwitchData Sop_on        = new SwitchData("SOP active", jsonData.sop_on, false);
        SwitchData Sspe          = new SwitchData("Pressure emergency", jsonData.sspe, true);
        SwitchData Fan_error     = new SwitchData("Fan error", jsonData.fan_error, true);
        SwitchData Vent_error    = new SwitchData("Vent error", jsonData.vent_error, true);
        SwitchData Vehicle_power = new SwitchData("Receiving power", jsonData.vehicle_power, false);
        SwitchData H2o_off       = new SwitchData("H2O offline", jsonData.h2o_off, true);
        SwitchData O2_off        = new SwitchData("O2 offline", jsonData.o2_off, true);

        //Timer data
        TimerData T_battery = new TimerData("Battery life", jsonData.t_battery, "01:00:00", "00:30:00");
        TimerData T_oxygen  = new TimerData("Oxygen remaining", jsonData.t_oxygen, "01:00:00", "00:30:00");
        TimerData T_water   = new TimerData("Water remaining", jsonData.t_water, "01:00:00", "00:30:00");

        //These next two lists are already in alphabetical order to save us a sort
        //*****BE VERY CAREFUL WHEN EDITING THESE NEXT TWO LISTS*****
        //*****There are constants at the top of the script which depend on the objects' positions*****
        numericalTextList[0]  = Cap_battery;
        numericalTextList[1]  = P_sub;
        numericalTextList[2]  = T_sub;
        numericalTextList[3]  = V_fan;
        numericalTextList[4]  = P_h2o_g;
        numericalTextList[5]  = P_h2o_l;
        numericalTextList[6]  = Heart_bpm;
        numericalTextList[7]  = Rate_o2;
        numericalTextList[8]  = P_o2;
        numericalTextList[9]  = Rate_sop;
        numericalTextList[10] = P_sop;
        numericalTextList[11] = P_suit;

        switchTextList[0] = Fan_error;
        switchTextList[1] = H2o_off;
        switchTextList[2] = O2_off;
        switchTextList[3] = Sspe;
        switchTextList[4] = Vehicle_power;
        switchTextList[5] = Sop_on;
        switchTextList[6] = Vent_error;

        timerTextList[0] = T_battery;
        timerTextList[1] = T_oxygen;
        timerTextList[2] = T_water;

        //We'll need to sort this list
        //Feel free to edit this list
        notificationsList[0]  = P_sub;
        notificationsList[1]  = T_sub;
        notificationsList[2]  = V_fan;
        notificationsList[3]  = P_o2;
        notificationsList[4]  = Rate_o2;
        notificationsList[5]  = Cap_battery;
        notificationsList[6]  = P_h2o_g;
        notificationsList[7]  = P_h2o_l;
        notificationsList[8]  = P_sop;
        notificationsList[9]  = Rate_sop;
        notificationsList[10] = Sop_on;
        notificationsList[11] = Sspe;
        notificationsList[12] = Fan_error;
        notificationsList[13] = Vent_error;
        notificationsList[14] = Vehicle_power;
        notificationsList[15] = H2o_off;
        notificationsList[16] = O2_off;
        notificationsList[17] = P_suit;
        notificationsList[18] = Heart_bpm;
        notificationsList[19] = T_battery;
        notificationsList[20] = T_oxygen;
        notificationsList[21] = T_water;

        //Sort by severity and then alphabetically
        notificationsList.Sort((x, y) =>
        {
            int retval = x.severity.CompareTo(y.severity);
            if (retval == 0)
            {
                retval = x.name.CompareTo(y.name);
            }
            return(retval);
        });
    }
    //Repeatedly read telemetry data from server using an HTTP GET request and update notification data
    IEnumerator GetTelemetryData()
    {
        while (true)
        {
            string numericalStr = "blank:blank", switchStr = "blank:blank", jsonStr = "blank:blank";

            //Get numerical data

            using (UnityWebRequest www1 = UnityWebRequest.Get(numericalDataURL))
            {
                yield return(www1.Send());

                if (www1.isError)
                {
                    numericalServerConnErr = true;
                    Debug.Log(www1.error);
                }
                else
                {
                    numericalServerConnErr = false;
                    numericalStr           = www1.downloadHandler.text;
                    numericalStr           = numericalStr.Trim();
                }
            }

            //Get switch data
            using (UnityWebRequest www2 = UnityWebRequest.Get(switchDataURL))
            {
                yield return(www2.Send());

                if (www2.isError)
                {
                    switchServerConnErr = true;
                    Debug.Log(www2.error);
                }
                else
                {
                    switchServerConnErr = false;
                    switchStr           = www2.downloadHandler.text;
                    switchStr           = switchStr.Trim();
                }
            }

            //Parse and update notifications if valid connections to servers exist
            //Note that BOTH connections must be working for any updates to occur
            //TODO: if one server goes down, figure out how to only update notifications from the other server
            if (!switchServerConnErr && !numericalServerConnErr)
            {
                jsonStr = numericalStr.Substring(1, numericalStr.Length - 3) + "," + switchStr.Substring(2, switchStr.Length - 3);
                JSONData jsonData = JSONData.CreateFromJSON(jsonStr);
                UpdateNotificationsArray(jsonData);
            }

            //Clear notifications
            foreach (Transform child in notificationsPanel.transform)
            {
                Destroy(child.gameObject);
            }

            //Clear right panel text
            foreach (Transform child in textPanel.transform)
            {
                Destroy(child.gameObject);
            }

            //Create new notifications
            //Special notifications for server communication failure appear first (if any)
            SwitchData switchConnNotification, numConnNotification;
            int        index = 0;
            if (switchServerConnErr)
            {
                switchConnNotification = new SwitchData("Switch server connection", "false", false);
                CreateTelemetryNotification(switchConnNotification, index);
                ++index;
            }
            if (numericalServerConnErr)
            {
                numConnNotification = new SwitchData("Telemetry server connection", "false", false);
                CreateTelemetryNotification(numConnNotification, index);
                ++index;
            }
            for (; index < MAX_NOTIFICATIONS; ++index)
            {
                if (notificationsList[index] == null)
                {
                    break;                                   //hit end of list early
                }
                if (notificationsList[index].severity == Severity.NOMINAL)
                {
                    break;                                                        //only show errors and warnings
                }
                CreateTelemetryNotification(notificationsList[index], index);
            }

            //Update notification icon
            Severity notifySeverity = notificationsList[0].severity; //there should always be something in index 0, even if server is down
            if (switchServerConnErr || numericalServerConnErr)
            {
                notifySeverity = Severity.CRITICAL;
            }
            String notifyIconPath = String.Format("Icons/notify-{0}", notifySeverity.ToString());
            Sprite notifyIcon     = Resources.Load <Sprite>(notifyIconPath);
            notifyImage.GetComponent <Image>().sprite = notifyIcon;

            //Update notification count
            int numNotifications = (NUM_NUMERICAL + NUM_SWITCH + NUM_TIMER) - notificationsList.FindAll(x => x.severity == Severity.NOMINAL).Count;
            if (numNotifications == 0)
            {
                notificationCountText.SetActive(false);
            }
            else
            {
                notificationCountText.SetActive(true);
                notificationCountText.GetComponentInChildren <Text>().text = "" + numNotifications;
            }

            /*
             * if (notifySeverity == Severity.CRITICAL)
             * {
             *  notifyImage.GetComponent<AudioSource>().Play();
             * }
             * else
             * {
             *  notifyImage.GetComponent<AudioSource>().Stop();
             * }
             */
            //Create telemetry text for right panel
            CreateTelemetryTextHeaders();
            for (int j = 0; j < numericalTextList.Count; ++j)
            {
                CreateTelemetryText(numericalTextList[j], j, TelemetryType.NUMERICAL);
            }
            for (int k = 0; k < switchTextList.Count; ++k)
            {
                CreateTelemetryText(switchTextList[k], k, TelemetryType.SWITCH);
            }
            for (int m = 0; m < timerTextList.Count; ++m)
            {
                CreateTelemetryText(timerTextList[m], m, TelemetryType.TIMER);
            }

            //Get pressure, oxygen, temperature, and battery data
            NumericalData suit_pressure   = numericalTextList[SUIT_PRESSURE_INDEX];
            NumericalData oxygen_pressure = numericalTextList[OXYGEN_INDEX];
            NumericalData temperature     = numericalTextList[TEMPERATURE_INDEX];
            TimerData     battery         = timerTextList[BATTERY_INDEX];

            //Update the pressure, oxygen, temperature, and battery icons and text
            if (suit_pressure != null)
            {
                String pressureIconPath = String.Format("Icons/suit-{0}", suit_pressure.severity.ToString());
                Sprite pressureIcon     = Resources.Load <Sprite>(pressureIconPath);
                pressureImage.sprite = pressureIcon;
                pressureText.GetComponentInChildren <Text>().text = suit_pressure.value + " " + suit_pressure.units;
            }
            if (oxygen_pressure != null)
            {
                String oxygenIconPath = String.Format("Icons/oxygen-{0}", oxygen_pressure.severity.ToString());
                Sprite oxygenIcon     = Resources.Load <Sprite>(oxygenIconPath);
                oxygenImage.sprite = oxygenIcon;
                oxygenText.GetComponentInChildren <Text>().text = oxygen_pressure.value + " " + oxygen_pressure.units;
            }
            if (temperature != null)
            {
                String temperatureIconPath = String.Format("Icons/temperature-{0}", temperature.severity.ToString());
                Sprite temperatureIcon     = Resources.Load <Sprite>(temperatureIconPath);
                temperatureImage.sprite = temperatureIcon;
                temperatureText.GetComponentInChildren <Text>().text = temperature.value + " " + temperature.units;
            }
            if (battery != null)
            {
                String batteryIconPath = String.Format("Icons/battery-{0}", battery.severity.ToString());
                Sprite batteryIcon     = Resources.Load <Sprite>(batteryIconPath);
                batteryImage.sprite = batteryIcon;
            }

            //Update pressure and oxygen arrows if they have values
            //If null, the arrows won't change their rotation
            if (suit_pressure != null)
            {
                double pressureAngle = ValueToDegrees(suit_pressure);
                pressureArrow.GetComponent <RectTransform>().rotation = Quaternion.Euler(0, 0, (float)pressureAngle);
            }
            if (oxygen_pressure != null)
            {
                double oxygenAngle = ValueToDegrees(oxygen_pressure);
                oxygenArrow.GetComponent <RectTransform>().rotation = Quaternion.Euler(0, 0, (float)oxygenAngle);
            }

            //Wait before pulling data again
            yield return(new WaitForSecondsRealtime((float)REFRESH_RATE));
        }
    }