###############################################################################
##
## Nasal module for dual control over the multiplayer network.
##
##  Copyright (C) 2007 - 2010  Anders Gidenstam  (anders(at)gidenstam.org)
##  This file is licensed under the GPL license version 2 or later.
##
###############################################################################

## MP properties
var lat_mpp     = "position/latitude-deg";
var lon_mpp     = "position/longitude-deg";
var alt_mpp     = "position/altitude-ft";
var heading_mpp = "orientation/true-heading-deg";
var pitch_mpp   = "orientation/pitch-deg";
var roll_mpp    = "orientation/roll-deg";

# Import components from the mp_broadcast module.
var Binary         = mp_broadcast.Binary;
var MessageChannel = mp_broadcast.MessageChannel;

###############################################################################
# Utility classes

############################################################
# Translate a property into another.
#  Factor and offsets are only used for numeric values.
#   src    - source      : property node
#   dest   - destination : property node
#   factor -             : double
#   offset -             : double
var Translator = {};
Translator.new = func (src = nil, dest = nil, factor = 1, offset = 0) {
  var obj = { parents   : [Translator],
              src       : src,
              dest      : dest,
              factor    : factor,
              offset    : offset };
  if (obj.src == nil or obj.dest == nil) {
    print("Translator[");
    print("  ", debug.string(obj.src));
    print("  ", debug.string(obj.dest));
    print("]");
    fail();
  }

  return obj;
}
Translator.update = func () {
  var v = me.src.getValue();
  if (is_num(v)) {
    me.dest.setValue(me.factor * v + me.offset);
  } else {
    if (typeof(v) == "scalar")
      me.dest.setValue(v);
  }
}

############################################################
# Detects flanks on two insignals encoded in a property.
# - positive signal up/down flank
# - negative signal up/down flank
#   n                 - source : property node
#   on_positive_flank - action : func (v)
#   on_negative_flank - action : func (v)
var EdgeTrigger = {};
EdgeTrigger.new = func (n, on_positive_flank, on_negative_flank) {
  var obj = { parents   : [EdgeTrigger],
              old       : 0,
              node      : n, 
              pos_flank : on_positive_flank,
              neg_flank : on_negative_flank };
  if (obj.node == nil) {
    print("EdgeTrigger[");
    print("  ", debug.string(obj.node));
    print("]");
    fail();
  }
  return obj;
}
EdgeTrigger.update = func {
  # NOTE: float MP properties get interpolated.
  #       This detector relies on that steady state is reached between
  #       flanks.
  var val = me.node.getValue();
  if (!is_num(val)) return;
  if (me.old == 1) {
    if (val < me.old) {
      me.pos_flank(0);
    }
  } elsif (me.old == 0) {
    if (val > me.old) {
      me.pos_flank(1);
    } elsif (val < me.old) {
      me.neg_flank(1);
    }
  } elsif (me.old == -1) {
    if (val > me.old) {
      me.neg_flank(0);
    }
  }
  me.old = val;
}

############################################################
# StableTrigger: Triggers an action when a MPP property
#                becomes stable (i.e. doesn't change for
#                MIN_STABLE seconds).
#   src     - MP prop : property node
#   action  - action to take when the value becomes stable : [func(v)]
# An action is triggered when value has stabilized.
var StableTrigger = {};
StableTrigger.new = func (src, action) {
  var obj = { parents      : [StableTrigger],
              src          : src,
              action       : action,
              old          : 0,
              stable_since : 0,
              wait         : 0,
              MIN_STABLE   : 0.01 };
  # Error checking.
  var bad = (obj.src == nil) or (action = nil);

  if (bad) {
    print("StableTrigger[");
    print("  ", debug.string(obj.src));
    print("  ", debug.string(obj.action));
    print("]");
    fail();
  }

  return obj;
}
StableTrigger.update = func () {
  var v   = me.src.getValue();
  if (!is_num(v)) return;
  var t = getprop("/sim/time/elapsed-sec"); # NOTE: simulated time.

  if ((me.old == v) and
      ((t - me.stable_since) > me.MIN_STABLE) and (me.wait == 1)) {
    # Trigger action.
    me.action(v);

    me.wait = 0;
  } elsif (me.old == v) {
    # Wait. This is either before the signal is stable or after the action. 
  } else {
    me.stable_since = t;
    me.wait         = 1;
    me.old          = me.src.getValue();
  }
}

############################################################
# Selects the most recent value of two properties.
#   src1      -  : property node
#   src2      -  : property node
#   dest      -  : property node
#   threshold -  : double
var MostRecentSelector = {};
MostRecentSelector.new = func (src1, src2, dest, threshold) {
  var obj = { parents   : [MostRecentSelector],
              old1      : 0,
              old2      : 0,
              src1      : src1,
              src2      : src2,
              dest      : dest,
              thres     : threshold };
  if (obj.src1 == nil or obj.src2 == nil or obj.dest == nil) {
    print("MostRecentSelector[");
    print("  ", debug.string(obj.src1));
    print("  ", debug.string(obj.src2));
    print("  ", debug.string(obj.dest));
    print("]");
  }

  return obj;
}
MostRecentSelector.update = func {
  var v1 = me.src1.getValue();
  var v2 = me.src2.getValue();
  if (!is_num(v1) and !is_num(v2)) return;
  elsif (!is_num(v1)) me.dest.setValue(v2);
  elsif (!is_num(v2)) me.dest.setValue(v1);
  else {
      if (abs (v2 - me.old2) > me.thres) {
          me.old2 = v2;
          me.dest.setValue(me.old2);
      }
      if (abs (v1 - me.old1) > me.thres) {
          me.old1 = v1;
          me.dest.setValue(me.old1);
      }
  }
}

############################################################
# Adds two input properties.
#   src1      -  : property node
#   src2      -  : property node
#   dest      -  : property node
var Adder = {};
Adder.new = func (src1, src2, dest) {
  var obj = { parents : [DeltaAccumulator],
              src1    : src1,
              src2    : src2,
              dest    : dest };
  if (obj.src1 == nil or obj.src2 == nil or obj.dest == nil) {
    print("Adder[");
    print("  ", debug.string(obj.src1));
    print("  ", debug.string(obj.src2));
    print("  ", debug.string(obj.dest));
    print("]");
    fail();
  }

  return obj;
}
Adder.update = func () {
  var v1 = me.src1.getValue();
  var v2 = me.src2.getValue();
  if (!is_num(v1) or !is_num(v2)) return;
  me.dest.setValue(v1 + v2);
}

############################################################
# Adds the delta of src to dest.
#   src       -  : property node
#   dest      -  : property node
var DeltaAdder = {};
DeltaAdder.new = func (src, dest) {
  var obj = { parents : [DeltaAdder],
              old     : 0,
              src     : src,
              dest    : dest };
  if (obj.src == nil or obj.dest == nil) {
    print("DeltaAdder[", debug.string(obj.src), ", ",
          debug.string(obj.dest), "]");
    fail();
  }

  return obj;
}
DeltaAdder.update = func () {
  var v = me.src.getValue();
  if (!is_num(v)) return;
  me.dest.setValue((v - me.old) + me.dest.getValue());
  me.old = v;
}

############################################################
# Switch encoder: Encodes upto 32 boolean properties in one
# int property.
#   inputs    - list of property nodes
#   dest      - where the bitmask is stored : property node
var SwitchEncoder = {};
SwitchEncoder.new = func (inputs, dest) {
  var obj = { parents : [SwitchEncoder],
              inputs  : inputs,
              dest    : dest };
  # Error checking.
  var bad = (obj.dest == nil);
  foreach (var i; inputs) {
    if (i == nil) { bad = 1; }
  }

  if (bad) {
    print("SwitchEncoder[");
    foreach (var i; inputs) {
      print("  ", debug.string(i));
    }
    print("  ", debug.string(obj.dest));
    print("]");
    fail();
  }

  return obj;
}
SwitchEncoder.update = func () {
  var v = 0;
  var b = 1;
  forindex (var i; me.inputs) {
    if (me.inputs[i].getBoolValue()) {
      v = v + b;
    }
    b *= 2;
  }
  me.dest.setIntValue(v);
}

############################################################
# Switch decoder: Decodes a bitmask in an int property.
#   src     -                 : property node
#   actions - list of actions : [func(b)]
# Actions are triggered when their input bit change.
# Due to interpolation the decoder needs to wait for a
# stable input value.
var SwitchDecoder = {};
SwitchDecoder.new = func (src, actions) {
  var obj = { parents : [SwitchDecoder],
              wait         : 0,
              old          : 0,
              old_stable   : 0,
              stable_since : 0,
              reset        : 1,
              src          : src,
              actions      : actions,
              MIN_STABLE   : 0.1 };
  # Error checking.
  var bad = (obj.src == nil);
  foreach (var a; obj.actions) {
    if (a == nil) { bad = 1; }
  }
  
  if (bad) {
    print("SwitchDecoder[");
    print("  ", debug.string(obj.src));
    foreach (var a; obj.actions) {
      print("  ", debug.string(a));
    }
    print("]");
    fail();
  }

  return obj;
}
SwitchDecoder.update = func () {
  var t = getprop("/sim/time/elapsed-sec"); # NOTE: simulated time.
  var v = me.src.getValue();
  if (!is_num(v)) return;

  if ((me.old == v) and ((t - me.stable_since) > me.MIN_STABLE) and
      (me.wait == 1)) {
    var ov = me.old_stable;
# Use this to improve.
#<cptf> here's the boring version:  var bittest = func(u, b) { while (b) { u = int(u / 2); b -= 1; } u != int(u / 2) * 2; }
    forindex (var i; me.actions) {
      var m  = math.mod(v, 2);
      var om = math.mod(ov, 2);
      if ((m != om or me.reset)) { me.actions[i](m?1:0); }
      v  = (v - m)/2;
      ov = (ov - om)/2;
    }
    me.old_stable = me.src.getValue();
    me.wait  = 0;
    me.reset = 0;
  } elsif (me.old == v) {
    # Wait. This is either before the bitmask is stable or after
    # it has been processed. 
  } else {
    me.stable_since = t;
    me.wait         = 1;
    me.old          = me.src.getValue();
  }
}

############################################################
# Time division multiplexing encoder: Transmits a list of
# properties over a MP enabled string property.
#   inputs  - input properties : [property node]
#   dest    - MP string prop   : property node
# Note: TDM can have high latency so it is best used for
# non-time critical properties.
var TDMEncoder = {};
TDMEncoder.new = func (inputs, dest) {
  var obj = { parents   : [TDMEncoder],
              inputs    : inputs,
              channel   : MessageChannel.new(dest,
                                             func (msg) {
                                               print("This should not happen!");
                                             }),
              MIN_INT   : 0.25,
              last_time : 0,
              next_item : 0,
              old       : [] };
  # Error checking.
  var bad = (dest == nil) or (obj.channel == nil);
  foreach (var i; inputs) {
    if (i == nil) { bad = 1; }
  }

  if (bad) {
    print("TDMEncoder[");
    foreach (var i; inputs) {
      print("  ", debug.string(i));
    }
    print("  ", debug.string(dest));
    print("]");
  }

  setsize(obj.old, size(obj.inputs));

  return obj;
}
TDMEncoder.update = func () {
  var t = getprop("/sim/time/elapsed-sec"); # NOTE: simulated time.
  if (t > me.last_time + me.MIN_INT) {
    var n = size(me.inputs);
    while (1) {
      var v = me.inputs[me.next_item].getValue();

      if ((n <= 0) or (me.old[me.next_item] != v)) {
        # Set the MP properties to send the next item.
        me.channel.send(Binary.encodeByte(me.next_item) ~
                        Binary.encodeDouble(v));

        me.old[me.next_item] = v;

        me.last_time = t;
        me.next_item += 1;
        if (me.next_item >= size(me.inputs)) { me.next_item = 0; }
        return;
      } else {
        # Search for changed property.
        n -= 1;
        me.next_item += 1;
        if (me.next_item >= size(me.inputs)) { me.next_item = 0; }
      }         
    }
  }
}

############################################################
# Time division multiplexing decoder: Receives a list of
# properties over a MP enabled string property.
#   src     - MP string prop  : property node
#   actions - list of actions : [func(v)]
# An action is triggered when its value is received.
# Note: TDM can have high latency so it is best used for
# non-time critical properties.
var TDMDecoder = {};
TDMDecoder.new = func (src, actions) {
  var obj = { parents      : [TDMDecoder],
              actions      : actions };
  obj.channel = MessageChannel.new(src,
                                   func (msg) {
                                     obj.process(msg);
                                   });

  # Error checking.
  var bad = (src == nil) or (obj.channel == nil);
  foreach (var a; actions) {
    if (a == nil) { bad = 1; }
  }

  if (bad) {
    print("TDMDecoder[");
    print("  ", debug.string(src));
    foreach (var a; actions) {
      print("  ", debug.string(a));
    }
    print("]");
    fail();
  }

  return obj;
}
TDMDecoder.process = func (msg) {
  var v1 = Binary.decodeByte(msg);
  var v2 = Binary.decodeDouble(substr(msg, 1));
  # Trigger action.
  me.actions[v1](v2);
}
TDMDecoder.update = func {
  me.channel.update();
}

###############################################################################
# Internal utility functions

var is_num = func (v) {
    return num(v) != nil;
}

# fail causes a Nasal runtime error so we get a backtrace.
var fail = func {
    error_detected_in_calling_context();
}

###############################################################################

###############################################################################
# Copilot selection dialog.
#
# Usage: dual_control_tools.copilot_dialog.show(<copilot type string>);
#
var COPILOT_DLG = 0;
var copilot_dialog = {};
############################################################
copilot_dialog.init = func (copilot_type, x = nil, y = nil) {
    me.x = x;
    me.y = y;
    me.bg = [0, 0, 0, 0.3];    # background color
    me.fg = [[1.0, 1.0, 1.0, 1.0]]; 
    #
    # "private"
    if (contains(aircraft_dual_control, "copilot_view")) {
        me.title = "Pilot selection";
    } else {
        me.title = "Copilot selection";
    }
    me.basenode = props.globals.getNode("/sim/remote", 1);
    me.dialog = nil;
    me.namenode = props.Node.new({"dialog-name" : me.title });
    me.listeners = [];
    me.copilot_type = copilot_type;
}
############################################################
copilot_dialog.create = func {
    if (me.dialog != nil)
        me.close();

    me.dialog = gui.Widget.new();
    me.dialog.set("name", me.title);
    if (me.x != nil)
        me.dialog.set("x", me.x);
    if (me.y != nil)
        me.dialog.set("y", me.y);

    me.dialog.set("layout", "vbox");
    me.dialog.set("default-padding", 0);
    var titlebar = me.dialog.addChild("group");
    titlebar.set("layout", "hbox");
    titlebar.addChild("empty").set("stretch", 1);
    if (contains(aircraft_dual_control, "copilot_view")) {
        titlebar.addChild("text").set("label", "Pilots online");
    } else {
        titlebar.addChild("text").set("label", "Copilots online");
    }
    var w = titlebar.addChild("button");
    w.set("pref-width", 16);
    w.set("pref-height", 16);
    w.set("legend", "");
    w.set("default", 0);
    w.set("key", "esc");
    w.setBinding("nasal", "dual_control_tools.copilot_dialog.destroy(); ");
    w.setBinding("dialog-close");
    me.dialog.addChild("hrule");

    var content = me.dialog.addChild("group");
    content.set("layout", "vbox");
    content.set("halign", "center");
    content.set("default-padding", 5);

    # Generate the dialog contents.
    me.players = me.find_copilot_players();
    var i = 0;
    var tmpbase  = me.basenode.getNode("dialog", 1);
    var selected = me.basenode.getNode("pilot-callsign").getValue();
    foreach (var p; me.players) {
        var tmp = tmpbase.getNode("b[" ~ i ~ "]", 1);
        tmp.setBoolValue(streq(selected, p));
        var w = content.addChild("checkbox");
        w.node.setValues({"label"    : p,
                          "halign"   : "left",
                          "property" : tmp.getPath()});
        w.setBinding
            ("nasal",
             "dual_control_tools.copilot_dialog.select_action(" ~ i ~ ");");
        i = i + 1;
    }
    me.dialog.addChild("hrule");

    # Display the dialog.
    fgcommand("dialog-new", me.dialog.prop());
    fgcommand("dialog-show", me.namenode);
}
############################################################
copilot_dialog.close = func {
    fgcommand("dialog-close", me.namenode);
}
############################################################
copilot_dialog.destroy = func {
    COPILOT_DLG = 0;
    me.close();
    foreach(var l; me.listeners)
        removelistener(l);
    delete(gui.dialog, "\"" ~ me.title ~ "\"");
}
############################################################
copilot_dialog.show = func (copilot_type) {
#    print("Showing MPCopilots dialog!");
    if (!COPILOT_DLG) {
        COPILOT_DLG = int(getprop("/sim/time/elapsed-sec"));
        me.init(copilot_type);
        me.create();
        me._update_(COPILOT_DLG);
    }
}
############################################################
copilot_dialog._redraw_ = func {
    if (me.dialog != nil) {
        me.close();
        me.create();
    }
}
############################################################
copilot_dialog._update_ = func (id) {
    if (COPILOT_DLG != id) return;
    me._redraw_();
    settimer(func { me._update_(id); }, 4.1);
}
############################################################
copilot_dialog.select_action = func (n) {
    var selected = me.basenode.getNode("pilot-callsign").getValue();
    var bs = me.basenode.getNode("dialog").getChildren();
    # Assumption: There are two true b:s or none. The one not matching selected
    #             is the new selection.
    var i = 0;
    me.basenode.getNode("pilot-callsign").setValue("");
    foreach (var b; bs) {
        if (!b.getValue() and (i == n)) {
            b.setValue(1);
            me.basenode.getNode("pilot-callsign").setValue(me.players[i]);
        } else {
            b.setValue(0);
        }
        i = i + 1;
    }
    dual_control.main.reset();
    me._redraw_();
}
############################################################
# Return a list containing all nearby copilot players of the right type.
copilot_dialog.find_copilot_players = func {
    var mpplayers =
        props.globals.getNode("/ai/models").getChildren("multiplayer");

    var res = [];
    foreach (var pilot; mpplayers) {
        if ((pilot.getNode("valid") != nil) and
            (pilot.getNode("valid").getValue()) and
            (pilot.getNode("sim/model/path") != nil)) {
            var type = pilot.getNode("sim/model/path").getValue();

            if (type == me.copilot_type) {
                append(res, pilot.getNode("callsign").getValue());
            }
        }
    }
#    debug.dump(res);
    return res; 
}
###############################################################################
