<?xml version="1.0"?>

<PropertyList>
  <name>chat-menu</name>
  <x>10</x>
  <y>-30</y>
  <layout>vbox</layout>
  <draggable>false</draggable>
  <default-padding>1</default-padding>
  <font>
    <name>HELVETICA_12</name>
  </font>
  <color>
    <red>0</red>
    <green>0</green>
    <blue>0</blue>
    <alpha>0</alpha>
  </color>
  <text>
    <label>0. Back</label>
    <halign>left</halign>
    <color>
      <red>0.9</red>
      <green>0.2</green>
      <blue>0.2</blue>
      <alpha>1</alpha>
    </color>
  </text>
  <text>
    <label>1. Edit</label>
    <halign>left</halign>
    <color>
      <red>0.9</red>
      <green>0.2</green>
      <blue>0.2</blue>
      <alpha>1</alpha>
    </color>
  </text>
  <text>
    <halign>left</halign>
    <property>/sim/multiplay/chat-menu/entry[0]</property>
    <live>true</live>
    <color>
      <red>0.9</red>
      <green>0.2</green>
      <blue>0.2</blue>
      <alpha>1</alpha>
    </color>
  </text>
  <text>
    <halign>left</halign>
    <property>/sim/multiplay/chat-menu/entry[1]</property>
    <live>true</live>
    <color>
      <red>0.9</red>
      <green>0.2</green>
      <blue>0.2</blue>
      <alpha>1</alpha>
    </color>
  </text>
  <text>
    <halign>left</halign>
    <property>/sim/multiplay/chat-menu/entry[2]</property>
    <live>true</live>
    <color>
      <red>0.9</red>
      <green>0.2</green>
      <blue>0.2</blue>
      <alpha>1</alpha>
    </color>
  </text>
  <text>
    <halign>left</halign>
    <property>/sim/multiplay/chat-menu/entry[3]</property>
    <live>true</live>
    <color>
      <red>0.9</red>
      <green>0.2</green>
      <blue>0.2</blue>
      <alpha>1</alpha>
    </color>
  </text>
  <text>
    <halign>left</halign>
    <property>/sim/multiplay/chat-menu/entry[4]</property>
    <live>true</live>
    <color>
      <red>0.9</red>
      <green>0.2</green>
      <blue>0.2</blue>
      <alpha>1</alpha>
    </color>
  </text>
  <text>
    <halign>left</halign>
    <property>/sim/multiplay/chat-menu/entry[5]</property>
    <live>true</live>
    <color>
      <red>0.9</red>
      <green>0.2</green>
      <blue>0.2</blue>
      <alpha>1</alpha>
    </color>
  </text>
  <text>
    <halign>left</halign>
    <property>/sim/multiplay/chat-menu/entry[6]</property>
    <live>true</live>
    <color>
      <red>0.9</red>
      <green>0.2</green>
      <blue>0.2</blue>
      <alpha>1</alpha>
    </color>
  </text>
  <text>
    <halign>left</halign>
    <property>/sim/multiplay/chat-menu/entry[7]</property>
    <live>true</live>
    <color>
      <red>0.9</red>
      <green>0.2</green>
      <blue>0.2</blue>
      <alpha>1</alpha>
    </color>
  </text>

  <nasal>
    <open>
      <![CDATA[
      var text = "";
      var topNode = "/sim/multiplay/chat-menu/config";

      setprop("/sim/multiplay/chat-menu/entry", "1");
      setprop("/sim/multiplay/chat-menu/entry[1]", "2");
      setprop("/sim/multiplay/chat-menu/entry[2]", "3");
      setprop("/sim/multiplay/chat-menu/entry[3]", "4");
      setprop("/sim/multiplay/chat-menu/entry[4]", "5");
      setprop("/sim/multiplay/chat-menu/entry[5]", "6");
      setprop("/sim/multiplay/chat-menu/entry[6]", "7");
      setprop("/sim/multiplay/chat-menu/entry[7]", "8");
      setprop("/sim/multiplay/chat-menu/entry[8]", "9");
      setprop("/sim/multiplay/chat-menu/entry[9]", "0");

      # Get the conversation tree.
      var entryNodes = props.globals.getNode("/sim/multiplay/chat-menu").getChildren("entry");
      var pos = props.globals.getNode(topNode);

      # Get various substitution values.
      var callsign = getprop("/sim/multiplay/callsign");

      # Simple rounding of altitude to 100 ft.
      var altitude = int(getprop("/position/altitude-ft")/100) * 100;

      var type = getprop("/sim/description");
      if (type != "")
      {
        # Get the first word of the description, which will (hopefully) be
        # something appropriate, such as "Cessna", "Boeing".
        type = split(" ", type)[0];
      }

      # Get the nearest airport.
      var airport = airportinfo();

      # Determine the active runway. We have two ways to do this:
      # - If the aircraft is on the ground (or very close to it), we'll try to determine
      # the runway it is closest to, and facing the correct direction for.
      # - If the aircraft is in the air, we'll work out the active runway based on the wind.

      var active_runway = "";
      var on_ground = (getprop("/position/altitude-agl-ft") < 100);

      if (on_ground)
      {
        # To find out the closest runway to the aircrafts position, we'll look at the heading
        # required to go from the aircraft's current position to the center of each runway.
        # The closer this is to the runways real heading, the more likely this is the runway
        # we're on. Note that we can't rely on /sim/atc/runway, as this is only set on
        # initialization.
        var max = 360;
        var loc = geo.aircraft_position();

        foreach (var r; keys(airport.runways)) {
          var curr = airport.runways[r];
          var p = geo.Coord.new();
          p.set_latlon(curr.lat, curr.lon, airport.elevation);

          var course = loc.course_to(p) - curr.heading;
          while (course >= 180) course -= 360;
          while (course < -180) course += 360;

          var deviation = math.abs(course);

          if (deviation < max)
          {
            active_runway = r;
            max = deviation;
          }
        }

        # It may be the case that we're taxiing down the runway, past the mid-point.
        # If the aircraft is facing the "wrong" way for this runway, then take the
        # reciprocal runway.
        var heading = getprop("/orientation/heading-magnetic-deg");
        var deviation = heading - airport.runways[active_runway].heading;

        while (deviation >= 180) deviation -= 360;
        while (deviation < -180) deviation += 360;
        deviation = math.abs(deviation);

        if (deviation > 90)
        {
          var number = math.mod(num(substr(active_runway, 0, 2)) + 18, 36);
          var side = substr(active_runway, 2, 1);
          active_runway = sprintf("%02d%s", number, side == "R" ? "L" : side == "L" ? "R" : side);
        }
      }
      else
      {
        var wind_speed = getprop("/environment/wind-speed-kt");
        var wind_from = wind_speed ? getprop("/environment/wind-from-heading-deg") : 270;
        var max = -1;

        foreach (var r; keys(airport.runways)) {
          var curr = airport.runways[r];

          var wind = wind_from - curr.heading;
          while (wind >= 180) wind -= 360;
          while (wind < -180) wind += 360;

          var deviation = math.abs(wind) + 1e-20;
          var v = (0.01 * curr.length + 0.01 * curr.width) / deviation;

          if (v > max) {
            max = v;
            active_runway = r;
          }
        }
      }

      # Find our distance and cardinal direction to the airport.
      var directions = split(",", "North,North East,East,South East,South,South West,West,North West");
      var loc = geo.aircraft_position();
      var airportloc = geo.Coord.new();
      airportloc.set_latlon(airport.lat, airport.lon, airport.elevation);

      # We want the course _from_ the airport to the aircraft - for reporting ".. approaching from the SW"
      var airport_course = airportloc.course_to(loc);
      var airport_distance = int((airportloc.distance_to(loc) / 1609) + 0.5);

      # Get an index into our array of directions.
      var dir = int(math.mod((airport_course + 22.5), 360) / 45);
      var airport_direction = directions[dir];

      # Now launch the keyboard listener.
      var kbdevent = setlistener("/devices/status/keyboard/event", func (event) {

        # Only check the key when pressed.
        if (!event.getNode("pressed").getValue())
          return;

        var key = event.getNode("key");

        if (handle_key(key.getValue()))
          key.setValue(-1);           # drop key event
      });

      var handle_key = func (key) {
        # We only handle keys 0-9 and Esc

        if (key == 27)
        {
          # escape -> cancel
          removelistener(kbdevent);
          gui.popdown();
          fgcommand("dialog-close", props.Node.new({"dialog-name": "chat-menu"}));
          return 1;
        }

        if ((key < `0`) or (key > `9`))
        {
          # pass the event back.
          return 0;
        }

        if (key == `0`)
        {
          # Go back one level.
          text = "";

          if (pos.getName() != "config")
          {
            # Build up the chat string again.
            pos = pos.getParent();
            var p = pos;

            while ((p.getName() != "config") and
                   (p.getChild("name").getValue() != nil))
            {
              var t = string.trim(p.getChild("name").getValue());

              # Entries that begin with "[" are silent.
              if (t[0] != `[`)
              {
                text =  t ~ " " ~ text;
              }

              p = p.getParent();
            }
          }

          updateDialog();
        }

        if (key == `1`)
        {
          # Go to edit mode using the inline editor.
          removelistener(kbdevent);
          gui.popdown();
          multiplayer.compose_message(text);
          fgcommand("dialog-close", props.Node.new({"dialog-name": "chat-menu"}));
        }

        if ((key > `1`) and (key <= `9`))
        {
          # Select the appropriate new node and update.
          # The index starts from position 2.
          var i = key - `2`;

          if (i > (size(pos.getChildren("menu")) -1))
          {
            # Drop out if the user has entered a too large value.
            return 0;
          }

          var t = entryNodes[i].getValue();

          t = string.trim(substr(t, 3, size(t) -3));

          # Entries that begin with "[" are silent.
          if (t[0] != `[`)
          {
            text = text ~ " " ~ t;
          }

          pos = pos.getChildren("menu")[i];

          if (size(pos.getChildren("menu")) == 0)
          {
            # We've come to the end of the tree - send the message and close
            setprop("/sim/multiplay/chat", text);
            removelistener(kbdevent);
            gui.popdown();
            fgcommand("dialog-close", props.Node.new({"dialog-name": "chat-menu"}));
          }
          else
          {
            # We've got more tree to traverse.
            updateDialog();
          }
        }

        # If we got here, we consumed the event
        return 1;
      }

      # Substitute simple values into the string.
      # The values we handle are
      #
      # % - Type (first word of /sim/description)
      # # - callsign
      # $ - altitude
      # * - airport ID.
      # & - course from airport to aircraft - "North West"
      # ! - distance from aircraft to airport in miles.
      #
      var subvals = func (str) {
        var t = "";

        for (var p = 0; p < size(str); p += 1)
        {
          if (str[p] == `!`)    t ~= airport_direction;
          elsif (str[p] == `^`) t ~= airport_distance;
          elsif (str[p] == `(`) t ~= active_runway;
          elsif (str[p] == `*`) t ~= airport.name;
          elsif (str[p] == `%`) t ~= type;
          elsif (str[p] == `#`) t ~= callsign;
          elsif (str[p] == `$`) t ~= altitude;
          else t ~= chr(str[p]);
        }

        return t;
      }

      var updateDialog = func {

        var children = pos.getChildren("menu");
        var i = 0;
        foreach(var c; children)
        {
          var p = i + 2;
          var txt = p ~ ': ' ~ subvals(c.getChild("name").getValue());

          if (i < 9)
          {
            entryNodes[i].setValue(txt);
          }
          i = i + 1;
        }

        # Set the rest of the dialog to blank.
        while (i <= 9)
        {
          entryNodes[i].setValue("");
          i = i + 1;
        }

        # Write the popup.
        gui.popupTip(text, 1000000);
      }

      # Start by updating the dialog.
      updateDialog();
     ]]>
    </open>
  </nasal>
</PropertyList>