# Copyright 2018 Stuart Buchanan
# This file is part of FlightGear.
#
# Foobar is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 2 of the License, or
# (at your option) any later version.
#
# FlightGear is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with FlightGear.  If not, see <http://www.gnu.org/licenses/>.
#
# MFD Surround
#
# Header fields at the top of the page
#
# PageGroup navigation, displayed in the bottom right of the
# FMS, and controlled by the FMS knob

# Set of pages, references by SVG ID
var PAGE_GROUPS = [

  { label: "MapPageGroupLabel",
          group: "MapPageGroup",
          pages: [ "NavigationMap", "TrafficMap", "Stormscope", "WeatherDataLink", "TAWSB"],
  },
  { label: "WPTGroupLabel",
          group: "WPTPageGroup",
          pages: [ "AirportInfo", "IntersectionInfo", "NDBInfo", "VORInfo", "UserWPTInfo"],
  },

  { label: "AuxGroupLabel",
          group: "AuxPageGroup",
          pages: [ "TripPlanning", "Utility", "GPSStatus", "XMRadio", "SystemStatus"],
  },

  { label: "FPLGroupLabel",
          group: "FPLPageGroup",
          pages: [ "ActiveFlightPlanNarrow", "FlightPlanCatalog", "StoredFlightPlan"],
  },

  { label: "LstGroupLabel",
          group: "LstPageGroup",
          pages: [ "Checklist1", "Checklist2", "Checklist3", "Checklist4", "Checklist5"],
  },

  { label: "NrstGroupLabel",
          group: "NrstPageGroup",
          pages: [ "NearestAirports", "NearestIntersections", "NearestNDB", "NearestVOR", "NearestUserWaypoints", "NearestFrequencies", "NearestAirspaces"],
  }
];

# Mapping for header labels to specific FMS or ADC messages, and sprintf formatting
# to use
var HEADER_MAPPING = {
  "BRG" : { message : "FMSLegBearingMagDeg",      format : "%d°"},
  "XTK" : { message : "FMSLegCourseError",        format : "%.1fnm"},
  "DIS" : { message : "FMSDistance",              format : "%.1fnm"},
  "DTK" : { message : "FMSLegDesiredTrack",       format : "%d°"},
  "END" : { message : "EnduranceHrs",             format : "%.1fhrs"},
  "ESA" : { message : "EnRouteSafeAltitude",      format : "%dft"},    # TODO
  "ETA" : { message : "FMSEstimatedTimeArrival",  format : ""},        # TODO
  "ETE" : { message : "FMSEstimatedTimeEnroute",     format : ""},
  "FOD" : { message : "FMSFuelOverDestination",      format : "%dgal"},
  "FOB" : { message : "FuelOnBoard",                 format : "%dgal"},
  "GS"  : { message : "FMSGroundspeed",              format : "%dkts"},
  "MSA" : { message : "MinimumSafeAltitude",         format : "%dft"},    # TODO
  "TAS" : { message : "ADCTrueAirspeed",             format : "%dkts"},
  "TKE" : { message : "FMSLegTrackErrorAngle",       format : "%d°"},
  "TRK" : { message : "FMSLegTrack",                 format : "%d°"},
  "VSR" : { message : "FMSLegVerticalSpeedRequired", format : "%dfpm"},   # TODO
};

var Surround =
{
  new : func (mfd, myCanvas, device, svg, pfd=0)
  {
    var obj = { parents : [
      Surround,
      MFDPage.new(mfd, myCanvas, device, svg, "Surround", ""),
    ] };

    obj.pfd = pfd;

    var textElements = [
      "Comm1StandbyFreq", "Comm1SelectedFreq",
      "Comm2StandbyFreq", "Comm2SelectedFreq",
      "Nav1StandbyFreq", "Nav1SelectedFreq",
      "Nav2StandbyFreq", "Nav2SelectedFreq",
      "Nav1ID", "Nav2ID",
    ];

    obj.addTextElements(textElements);

    if (pfd) {
      obj.addTextElements(["HeaderFrom", "HeaderTo", "LegDistance", "LegBRG"]);
      obj._dto = PFD.HighlightElement.new(obj.pageName, svg, "HeaderDTO", "DTO");
      obj._leg = PFD.HighlightElement.new(obj.pageName, svg, "HeaderActiveLeg", "Leg");
    } else {
      obj.addTextElements(["Header1Label", "Header1Value",
                            "Header2Label", "Header2Value",
                            "Header3Label", "Header3Value",
                            "Header4Label", "Header4Value"]);
    }

    obj._comm1selected = PFD.HighlightElement.new(obj.pageName, svg, "Comm1Selected", "Comm1");
    obj._comm2selected = PFD.HighlightElement.new(obj.pageName, svg, "Comm2Selected", "Comm2");

    obj._nav1selected = PFD.HighlightElement.new(obj.pageName, svg, "Nav1Selected", "Nav1");
    obj._nav2selected = PFD.HighlightElement.new(obj.pageName, svg, "Nav2Selected", "Nav2");

    obj._comm1failed = PFD.HighlightElement.new(obj.pageName, svg, "Comm1Failed", "Comm1");
    obj._comm2failed = PFD.HighlightElement.new(obj.pageName, svg, "Comm2Failed", "Comm2");

    obj._nav1failed = PFD.HighlightElement.new(obj.pageName, svg, "Nav1Failed", "Nav1");
    obj._nav2failed = PFD.HighlightElement.new(obj.pageName, svg, "Nav2Failed", "Nav2");

    obj._canvas = myCanvas;
    obj._menuVisible = 0;
    obj._selectedPageGroup = 0;
    obj._selectedPage = 0;

    obj._elements = {};

    foreach (var pageGroup; PAGE_GROUPS) {
      var group = svg.getElementById(pageGroup.group);
      var label = svg.getElementById(pageGroup.label);
      assert(group != nil, "Unable to find element " ~ pageGroup.group);
      assert(label != nil, "Unable to find element " ~ pageGroup.label);
      obj._elements[pageGroup.group] = group;
      obj._elements[pageGroup.label] = label;

      foreach(var pg; pageGroup.pages) {
        var page = svg.getElementById(pg);
        assert(page != nil, "Unable to find element " ~ pg);
        obj._elements[pg] = page;
      }
    }

    # Timers to control when to hide the menu after inactivity, and when to load
    # a new page.
    obj._hideMenuTimer = maketimer(3, obj, obj.hideMenu);
    obj._hideMenuTimer.singleShot = 1;

    obj._loadPageTimer = maketimer(0.5, obj, obj.loadPage);
    obj._loadPageTimer.singleShot = 1;

    obj.hideMenu();

    obj.setController(fg1000.SurroundController.new(obj, svg));
    return obj;
  },

  handleNavComData : func(data) {
    foreach(var name; keys(data)) {
      var val = data[name];

      if (name == "Comm1SelectedFreq") me.setTextElement("Comm1SelectedFreq", sprintf("%0.03f", val));
      if (name == "Comm1StandbyFreq") me.setTextElement("Comm1StandbyFreq", sprintf("%0.03f", val));
      if (name == "Comm1Serviceable") {
        if (val == 1) {
          me._comm1failed.setVisible(0);
        } else {
          me._comm1failed.setVisible(1);
        }
      }

      if (name == "Comm2SelectedFreq") me.setTextElement("Comm2SelectedFreq", sprintf("%0.03f", val));
      if (name == "Comm2StandbyFreq") me.setTextElement("Comm2StandbyFreq", sprintf("%0.03f", val));
      if (name == "Comm2Serviceable") {
        if (val == 1) {
          me._comm2failed.setVisible(0);
        } else {
          me._comm2failed.setVisible(1);
        }
      }

      if (name == "CommSelected") {
        if (val == 1) {
          me._comm1selected.setVisible(1);
          me._comm2selected.setVisible(0);
        } else {
          me._comm1selected.setVisible(0);
          me._comm2selected.setVisible(1);
        }
      }

      if (name == "Nav1SelectedFreq") me.setTextElement("Nav1SelectedFreq", sprintf("%0.03f", val));
      if (name == "Nav1StandbyFreq") me.setTextElement("Nav1StandbyFreq", sprintf("%0.03f", val));
      if (name == "Nav1Serviceable") {
        if (val == 1) {
          me._nav1failed.setVisible(0);
        } else {
          me._nav1failed.setVisible(1);
        }
      }

      if (name == "Nav2SelectedFreq") me.setTextElement("Nav2SelectedFreq", sprintf("%0.03f", val));
      if (name == "Nav2StandbyFreq") me.setTextElement("Nav2StandbyFreq", sprintf("%0.03f", val));
      if (name == "Nav2Serviceable") {
        if (val == 1) {
          me._nav2failed.setVisible(0);
        } else {
          me._nav2failed.setVisible(1);
        }
      }

      if (name == "NavSelected") {
        if (val == 1) {
          me._nav1selected.setVisible(1);
          me._nav2selected.setVisible(0);
        } else {
          me._nav1selected.setVisible(0);
          me._nav2selected.setVisible(1);
        }
      }

      if (name == "Nav1ID") me.setTextElement("Nav1ID", val);
      if (name == "Nav2ID") me.setTextElement("Nav2ID", val);

      # TODO - COM Volume - display the current volume for 2 seconds in place of the
      # standby frequency.


    }
  },

  # Update Header data with FMS or ADC data.
  updateHeaderData : func(data) {

    if (me.pfd) {
      # From, To, leg distance and leg bearing headers
      if (data["FMSLegID"]) {
        if (data["FMSLegID"] == "") {
          # No Leg, so hide the headers
          me.setTextElement("HeaderTo", "");
          me.setTextElement("HeaderFrom", "");
          me._dto.setVisible(0);
          me._leg.setVisible(0);
        } else {
          me.setTextElement("HeaderTo", data["FMSLegID"]);
          me._leg.setVisible(1);

          if (data["FMSMode"] == "dto") {
            me.setTextElement("HeaderFrom", "");
            me._dto.setVisible(1);
          } else {
            me._dto.setVisible(0);
          }
        }
      }

      if (data["FMSLegDesiredTrack"]) me.setTextElement("LegBRG", sprintf("%i°", data["FMSLegDesiredTrack"]));
      if (data["FMSLegDistanceNM"]) me.setTextElement("LegDistance", sprintf("%.1fnm", data["FMSLegDistanceNM"]));
    } else {
      # MFD - 4 configurable Headers
      var headers = ["Header1", "Header2", "Header3", "Header4"];
      foreach (var header; headers) {

        # Get the currently configured heading and set the surround to display it.
        var label = me.mfd.ConfigStore.get("MFD" ~ header);
        assert(label != nil, "No header configured in ConfigStore for " ~ header);
        me.setTextElement(header ~ "Label", label);

        # Determine how it maps to Emesary data notifications
        var mapping = HEADER_MAPPING[label];
        assert(mapping != nil, "No header mapping for " ~ label);

        if (data[mapping.message] != nil) {
          # Format and display the value
          var value = sprintf(mapping.format, data[mapping.message]);

          if (mapping.message == "FMSEstimatedTimeEnroute") {
            # Special case to format time strings.
            var hrs = int(data[mapping.message]);
            var mins = int(60*(data[mapping.message] - hrs));
            var secs = int(3600*(data[mapping.message] - hrs - mins/60));

            if (hrs == 0) {
              value = sprintf("%d:%02d", mins, secs);
            } else {
              value = sprintf("%d:%02d", hrs, mins);
            }
          }
          me.setTextElement(header ~ "Value", value);
        }
      }
    }
  },

  getCurrentPage : func()
  {
    var currentpage = PAGE_GROUPS[me._selectedPageGroup].pages[me._selectedPage];
    return me.getMFD().getPage(currentpage);
  },

  # Function to change a page based on the selection
  loadPage : func()
  {
    var pageToLoad = PAGE_GROUPS[me._selectedPageGroup].pages[me._selectedPage];
    var page = me.getMFD().getPage(pageToLoad);

    assert(page != nil, "Unable to find page " ~ pageToLoad);
    me.device.selectPage(page);
  },
  incrPageGroup : func(val) {
    var incr_or_decr = (val > 0) ? 1 : -1;
    me._selectedPageGroup = math.mod(me._selectedPageGroup + incr_or_decr, size(PAGE_GROUPS));
    me._selectedPage = 0;
  },
  incrPage : func(val) {
    var incr_or_decr = (val > 0) ? 1 : -1;
    me._selectedPage = math.mod(me._selectedPage + incr_or_decr, size(PAGE_GROUPS[me._selectedPageGroup].pages));
  },
  showMenu : func()
  {
    foreach(var pageGroup; PAGE_GROUPS)
    {
      if (PAGE_GROUPS[me._selectedPageGroup].label == pageGroup.label)
      {
        # Display the page group and highlight the label
        me._elements[pageGroup.group].setVisible(1);
        me._elements[pageGroup.label].setVisible(1);
        me._elements[pageGroup.label].setColor(0.7,0.7,1.0);

        foreach (var page; pageGroup.pages)
        {
          # Highlight the current page.
          if (pageGroup.pages[me._selectedPage] == page) {
            me._elements[page].setColor(0.7,0.7,1.0);
          } else {
            me._elements[page].setColor(0.7,0.7,0.7);
          }
        }
      }
      else
      {
        # Hide the pagegroup and unhighlight the label on the bottom
        me._elements[pageGroup.group].setVisible(0);
        me._elements[pageGroup.label].setVisible(1);
        me._elements[pageGroup.label].setColor(0.7,0.7,0.7);
      }
    }
    me._menuVisible = 1;
    me._hideMenuTimer.stop();
    me._hideMenuTimer.restart(3);
    me._loadPageTimer.stop();
    me._loadPageTimer.restart(0.5);

  },
  hideMenu : func()
  {
    foreach(var pageGroup; PAGE_GROUPS)
    {
      me._elements[pageGroup.group].setVisible(0);
      me._elements[pageGroup.label].setVisible(0);
    }
    me._menuVisible = 0;
  },
  isMenuVisible : func()
  {
    return me._menuVisible;
  },


};