# Properties under /consumables/fuel/tank[n]:
# + level-gal_us      - Current fuel load.  Can be set by user code.
# + level-lbs         - OUTPUT ONLY property, do not try to set
# + selected          - boolean indicating tank selection.
# + density-ppg       - Fuel density, in lbs/gallon.
# + capacity-gal_us   - Tank capacity
#
# Properties under /engines/engine[n]:
# + fuel-consumed-lbs - Output from the FDM, zeroed by this script
# + out-of-fuel       - boolean, set by this code.

var UPDATE_PERIOD     = 0.3;
var enabled           = nil;
var serviceable       = nil;
var fuel_freeze       = nil;
var ai_enabled        = nil;
var engines           = nil;
var tanks             = [];
var refuelingN        = nil;
var reportContactNode = nil;
var reportContact     = 0;
var contactN          = nil;
var aimodelsN         = nil;
var types             = {};
var update_model_list = 0;
var tankerNode        = nil;
var tankerContactNode = nil;

setlistener("/ai/models/model-added", func(v){
  update_model_list = 1;
});

setlistener("/ai/models/model-removed", func(v){
  update_model_list = 1;
});

var tankers = [];
var update_loop = func {
  # check for contact with tanker aircraft
  if (ai_enabled) {
    if (update_model_list) {
      update_model_list=0;
      tankers = [];
      var ac = aimodelsN.getChildren("tanker");
      var mp = aimodelsN.getChildren("multiplayer");

      foreach (var a; ac ~ mp) {
        if (!a.getNode("valid", 1).getValue()) {
          continue ;
        }
        if (!a.getNode("tanker", 1).getValue()) {
          continue ;
        }
        foreach (var t; a.getNode("refuel", 1).getChildren("type")) {
          var type = t.getValue();
          if (contains(types, type) and types[type]) {
            append(tankers, a);
          }
        }
      }
    }
  }
  var foundTankerNode = nil;
    if (serviceable) {
      foreach (var t; tankers) {
        if (t.getNode("refuel/contact", 1).getValue()) {
          foundTankerNode = t;
        }
      }
    }

    if (foundTankerNode != tankerNode) {
      tankerNode = foundTankerNode;
      if (tankerNode != nil) {
        tankerContactNode = tankerNode.getNode("refuel/contact", 1);
      } else {
        tankerContactNode = nil;
      }
    }
    reportContact = reportContactNode.getValue();
    if (tankerNode != nil and tankerContactNode != nil) {
      var tankerContact = tankerContactNode.getValue();
      if (tankerContact != contactN.getValue()) {
        if (reportContactNode.getValue()) {
          if (tankerContact) {
            setprop("/sim/messages/copilot", "Engage");
          } else {
            setprop("/sim/messages/copilot", "Disengage");
          }
        }
        contactN.setBoolValue(tankerContact);
      }
    } else {
      if (reportContact and contactN.getValue())
        setprop("/sim/messages/copilot", "Disengage");
      contactN.setBoolValue(0);
    }

  if (fuel_freeze) {
    return;
  }

  # sum up consumed fuel
  var consumed = 0;
  foreach (var e; engines) {
    var fuel = e.getNode("fuel-consumed-lbs");
    consumed += fuel.getValue();
    fuel.setDoubleValue(0);
  }

  # calculate fuel received
  if (tankerNode != nil) {
    # Flow rate is the minimum of the tanker maxium rate
    # and the aircraft maximum rate.  Both are expressed
    # in lbs/min
    var fuel_rate = math.min(tankerNode.getNode("refuel/max-fuel-transfer-lbs-min", 1).getValue() or 6000, refuelingN.getNode("max-fuel-transfer-lbs-min", 1).getValue() or 6000);
    var received =  UPDATE_PERIOD * fuel_rate / 60;
    consumed -= received;
  }

  # make list of selected tanks
  var selected_tanks = [];
  foreach (var t; tanks) {
    var cap = t.getNode("capacity-gal_us", 1).getValue();
    if (cap != nil and cap > 0.01 and t.getNode("selected", 1).getBoolValue()) {
      append(selected_tanks, t);
    }
  }

  var out_of_fuel = 0;
  if (size(selected_tanks) == 0) {
    out_of_fuel = 1;
  } elsif (consumed >= 0) {
    var fuel_per_tank = consumed / size(selected_tanks);
    foreach (var t; selected_tanks) {
      var ppg = t.getNode("density-ppg").getValue();
      var lbs = t.getNode("level-gal_us").getValue() * ppg;
      lbs -= fuel_per_tank;

      if (lbs < 0) {
        lbs = 0;
        # Kill the engines if we're told to, otherwise simply
        # deselect the tank.
        if (t.getNode("kill-when-empty", 1).getBoolValue()) {
          out_of_fuel = 1;
        } else {
          t.getNode("selected", 1).setBoolValue(0);
        }
      }

      var gals = lbs / ppg;
      t.getNode("level-gal_us").setDoubleValue(gals);
      t.getNode("level-lbs").setDoubleValue(lbs);
    }

  } else {
    #find the number of tanks which can accept fuel
    var available = 0;

    foreach (var t; selected_tanks) {
      var ppg = t.getNode("density-ppg").getValue();
      var capacity = t.getNode("capacity-gal_us").getValue() * ppg;
      var lbs = t.getNode("level-gal_us").getValue() * ppg;

      if (lbs < capacity) {
        available += 1;
      }
    }

    if (available > 0) {
      var fuel_per_tank = -consumed / available;

      # add fuel to each available tank
      foreach (var t; selected_tanks) {
        var ppg = t.getNode("density-ppg").getValue();
        var capacity = t.getNode("capacity-gal_us").getValue() * ppg;
        var lbs = t.getNode("level-gal_us").getValue() * ppg;

        lbs += fuel_per_tank;
        if (lbs > capacity) {
          lbs = capacity;
        }

        t.getNode("level-gal_us").setDoubleValue(lbs / ppg);
        t.getNode("level-lbs").setDoubleValue(lbs);
      }

      # print ("available ", available , " fuel_per_tank " , fuel_per_tank);
    }
  }

  var gals = 0;
  var lbs  = 0;
  var cap  = 0;
  foreach (var t; tanks) {
    gals += t.getNode("level-gal_us", 1).getValue();
    lbs += t.getNode("level-lbs", 1).getValue();
    cap += t.getNode("capacity-gal_us", 1).getValue();
  }

  setprop("/consumables/fuel/total-fuel-gals", gals);
  setprop("/consumables/fuel/total-fuel-lbs", lbs);
  if (cap == 0) {
    setprop("/consumables/fuel/total-fuel-norm", 0);
  } else {
    setprop("/consumables/fuel/total-fuel-norm", gals / cap);
  }

  foreach (var e; engines) {
    e.getNode("out-of-fuel", 1).setBoolValue(out_of_fuel);
  }
}

var aarTimer           = maketimer(UPDATE_PERIOD, update_loop);
aarTimer.simulatedTime = 1;

setlistener("/sim/signals/fdm-initialized", func {
  if (contains(globals, "fuel") and typeof(fuel) == "hash") {
    fuel.loop = func nil;       # kill $FG_ROOT/Nasal/fuel.nas' loop
  }

  contactN          = props.globals.initNode("/systems/refuel/contact", 0, "BOOL");
  refuelingN        = props.globals.getNode("/systems/refuel", 1);
  aimodelsN         = props.globals.getNode("ai/models", 1);
  engines           = props.globals.getNode("engines", 1).getChildren("engine");
  reportContactNode = refuelingN.getNode("report-contact", 1);

  foreach (var e; engines) {
    e.getNode("fuel-consumed-lbs", 1).setDoubleValue(0);
    e.getNode("out-of-fuel", 1).setBoolValue(0);
  }

  foreach (var t; props.globals.getNode("consumables/fuel", 1).getChildren("tank")) {
    if (!t.getAttribute("children")) {
      continue;           # skip native_fdm.cxx generated zombie tanks
    }

    append(tanks, t);
    t.initNode("level-gal_us", 0.0);
    t.initNode("level-lbs", 0.0);
    t.initNode("capacity-gal_us", 0.01); # not zero (div/zero issue)
    t.initNode("density-ppg", 6.0);      # gasoline
    t.initNode("selected", 1, "BOOL");
  }

  foreach (var t; props.globals.getNode("systems/refuel", 1).getChildren("type")) {
    types[t.getValue()] = 1;
  }
  if (size(types) == 0) {
    gui.popupTip('No items in systems/refuel/type[], air-to-air refueling will not work', 10);
  }
  setlistener("sim/freeze/fuel", func(n) fuel_freeze = n.getBoolValue(), 1);
  setlistener("sim/ai/enabled", func(n) ai_enabled = n.getBoolValue(), 1);
  setlistener("systems/refuel/serviceable", func(n) serviceable = n.getBoolValue(), 1);
  aarTimer.restart(UPDATE_PERIOD);
});
