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

# Renaming (almost :)
var DCT = dual_control_tools;
var ADC = aircraft_dual_control;
# NOTE: By loading the aircraft specific dual control module
#       as <aircraft_dual_control> this file is generic.
#       The aircraft specific modul must set the variables
#       pilot_type and copilot_type to the name (with full path) of
#       main 3d model XML for the pilot and copilot aircraft.
#       This module should be loades under the name dual_control.

# Allow aircraft to override the copilot view name. Deprecated.
if (!contains(ADC, "copilot_view")) {
  ADC.copilot_view = "Copilot View";
}

# Properties for position and orientation of local aircraft.
var l_lat     = "/position/latitude-deg";
var l_lon     = "/position/longitude-deg";
var l_alt     = "/position/altitude-ft";
var l_heading = "/orientation/heading-deg";
var l_pitch   = "/orientation/pitch-deg";
var l_roll    = "/orientation/roll-deg";

# Replicate remote state.
var r_airspeed  = "velocities/true-airspeed-kt";
var l_airspeed  = "/velocities/airspeed-kt";
var vertspeed   = "velocities/vertical-speed-fps";

# Default external views to slave to the MP pilot.
var views = {};
views["Helicopter View"] = 2;
views["Chase View"]      = 3;
views["Tower View"]      = 0;
views["Fly-By View"]     = 1;
views["Chase View Without Yaw"] = 1;

######################################################################
# Connect to new pilot
var process_data = 0;

var connect = func (pilot) {
  # Set external view eye and target paths.
  foreach (var vn; keys(views)) {
    var view_cfg = "/sim/view[" ~ view.indexof(vn) ~ "]/config";
    setprop(view_cfg ~ "/at-model", 0);

    if (views[vn] > 0) {
      setprop(view_cfg ~ "/eye-lat-deg-path",
              pilot.getNode(DCT.lat_mpp).getPath());
      setprop(view_cfg ~ "/eye-lon-deg-path",
              pilot.getNode(DCT.lon_mpp).getPath());
      setprop(view_cfg ~ "/eye-alt-ft-path",
              pilot.getNode(DCT.alt_mpp).getPath());
    }
    if (views[vn] > 1) {
      setprop(view_cfg ~ "/eye-heading-deg-path",
              pilot.getNode(DCT.heading_mpp).getPath());
    }
    if (views[vn] > 2) {
      setprop(view_cfg ~ "/eye-pitch-deg-path",
              pilot.getNode(DCT.pitch_mpp).getPath());
      setprop(view_cfg ~ "/eye-roll-deg-path",
              pilot.getNode(DCT.roll_mpp).getPath());
    }

    setprop(view_cfg ~ "/target-lat-deg-path",
            pilot.getNode(DCT.lat_mpp).getPath());
    setprop(view_cfg ~ "/target-lon-deg-path",
            pilot.getNode(DCT.lon_mpp).getPath());
    setprop(view_cfg ~ "/target-alt-ft-path",
            pilot.getNode(DCT.alt_mpp).getPath());
    setprop(view_cfg ~ "/target-heading-deg-path",
            pilot.getNode(DCT.heading_mpp).getPath());
    setprop(view_cfg ~ "/target-pitch-deg-path",
            pilot.getNode(DCT.pitch_mpp).getPath());
    setprop(view_cfg ~ "/target-roll-deg-path",
            pilot.getNode(DCT.roll_mpp).getPath());
  }

  # Tweak MP/AI filters
  pilot.getNode("controls/allow-extrapolation").setBoolValue(1);
  pilot.getNode("controls/lag-adjust-system-speed").setValue(5.0);  

  # Set up property aliases

  # Set up property mappings.
  process_data = 
    [
      # Map /postition/*
      DCT.Translator.new
        (pilot.getNode(DCT.lat_mpp), props.globals.getNode(l_lat)),
      DCT.Translator.new
        (pilot.getNode(DCT.lon_mpp), props.globals.getNode(l_lon)),
      DCT.Translator.new
        (pilot.getNode(DCT.alt_mpp), props.globals.getNode(l_alt)),
      # Map /orientation/*
      DCT.Translator.new
        (pilot.getNode(DCT.heading_mpp),
         props.globals.getNode(l_heading)),
      DCT.Translator.new
        (pilot.getNode(DCT.pitch_mpp),
         props.globals.getNode(l_pitch)),
      DCT.Translator.new
        (pilot.getNode(DCT.roll_mpp),
         props.globals.getNode(l_roll)),
      # Map /velocities/*
      DCT.Translator.new
        (pilot.getNode(r_airspeed),
         props.globals.getNode(l_airspeed)),
      DCT.Translator.new
        (pilot.getNode(vertspeed),
         props.globals.getNode(vertspeed)),
    ] ~ ADC.copilot_connect_pilot(pilot);

  print("Dual control ... connected to pilot.");
  setprop("/sim/messages/copilot", "Welcome aboard. I'm " ~
          pilot.getNode("callsign").getValue() ~ ".");
}

var disconnect = func {
  # Reset external view eye and target paths.
  foreach (var vn; keys(views)) {
    var view_cfg = "/sim/view[" ~ view.indexof(vn) ~ "]/config";
    
    if (views[vn] > 0) {
      setprop(view_cfg ~ "/eye-lat-deg-path",
              "/position/latitude-deg");
      setprop(view_cfg ~ "/eye-lon-deg-path",
              "/position/longitude-deg");
      setprop(view_cfg ~ "/eye-alt-ft-path",
              "/position/altitude-ft");
    }
    if (views[vn] > 1) {
      setprop(view_cfg ~ "/eye-heading-deg-path",
              "/orientation/heading-deg");
    }
    if (views[vn] > 2) {
      setprop(view_cfg ~ "/eye-pitch-deg-path",
              "/orientation/pitch-deg");
      setprop(view_cfg ~ "/eye-roll-deg-path",
              "/orientation/roll-deg");
    }
    setprop(view_cfg ~ "/target-lat-deg-path",
            "/sim/viewer/target/latitude-deg");
    setprop(view_cfg ~ "/target-lon-deg-path",
            "/sim/viewer/target/longitude-deg");
    setprop(view_cfg ~ "/target-alt-ft-path",
            "/sim/viewer/target/altitude-ft");
    setprop(view_cfg ~ "/target-heading-deg-path",
            "/sim/viewer/target/heading-deg");
    setprop(view_cfg ~ "/target-pitch-deg-path",
            "/sim/viewer/target/pitch-deg");
    setprop(view_cfg ~ "/target-roll-deg-path",
            "/sim/viewer/target/roll-deg");
  }
}


######################################################################
# Main loop singleton class.
var main = {
  _initialized : 0,
  init : func {
    if (!me._initialized) {
      me.loopid = 0;
      setlistener("/ai/models/model-added", func {
        settimer(func { me.activate(); }, 2);
      });
      me._initialized = 1;
    }
    me.active = 0;
    print("Copilot dual control ... initialized");
    settimer(func { me.activate(); }, 5);
  },
  reset : func {
    if (me.active) {
      print("Dual control ... disconnected from pilot.");
      disconnect();
      ADC.copilot_disconnect_pilot();
    }
    me.active = 0;
    me.loopid += 1;
    me._loop_(me.loopid);
  },
  activate : func {
    if (!me.active) {
      me.reset();
    }
  },
  update : func {
    var mpplayers =
      props.globals.getNode("/ai/models").getChildren("multiplayer");
    var r_callsign = getprop("/sim/remote/pilot-callsign");

    foreach (var pilot; mpplayers) {
      if ((pilot.getChild("valid").getValue()) and
          (pilot.getChild("callsign") != nil) and
          (pilot.getChild("callsign").getValue() == r_callsign)) {

        if (me.active == 0) {
          # Note: sim/model/path contains the model XML file. 
          if ((pilot.getNode("sim/model/path") != nil) and
              (pilot.getNode("sim/model/path").getValue() ==
               ADC.pilot_type)) {
            me.active = 1;
            connect(pilot);
          } else {
            print("Dual control ... pilot rejected - wrong aircraft type.");
            me.loopid += 1;
            return;
          }
        }

        # Mess with the MP filters. Highly experimental.
        if (pilot.getNode("controls/lag-time-offset") != nil) {
          var v = pilot.getNode("controls/lag-time-offset").getValue();
          #pilot.getNode("controls/lag-time-offset").setValue(0.99 * v);
        }

        foreach (var w; process_data) {
          w.update();
        }
        return;
      }
    }
    # The pilot player is not around. Idle loop.
    if (me.active) {
      print("Dual control ... disconnected from pilot.");
      disconnect();
      ADC.copilot_disconnect_pilot();
    }
    me.active = 0;
    me.loopid += 1;
  },
  _loop_ : func(id) {
    id == me.loopid or return;
    me.update();
    settimer(func { me._loop_(id); }, 0);
  }
};

###############################################################################
# Initialization.

var last_view = 0;

setlistener("/sim/signals/fdm-initialized", func {
  main.init();
});