##
# Returns true if the first object is an instance of the second
# (class) object.  Example: isa(someObject, props.Node)
#
isa = func {
    obj = arg[0]; class = arg[1];
    if(obj == nil or !contains(obj, "parents")) { return 0; }
    foreach(c; obj.parents) {
        if(c == class)     { return 1; }
        elsif(isa(obj, c)) { return 1; }
    }
    return 0;
}

##
# Invokes a FlightGear command specified by the first argument.  The
# second argument specifies the property tree to be passed to the
# command as its argument.  It may be either a props.Node object or a
# string, in which case it specifies a path in the global property
# tree.
#
fgcommand = func(cmd, node=nil) {
    if(isa(node, props.Node)) node = node._g;
    _fgcommand(cmd, node);
}

##
# Returns the SGPropertyNode argument to the currently executing
# function. Wrapper for the internal _cmdarg function that retrieves
# the ghost handle to the argument and wraps it in a
# props.Node object.
#
cmdarg = func { props.wrapNode(_cmdarg()) }

##
# Utility.  Does what it you think it does.
#
abs = func { if(arg[0] < 0) { -arg[0] } else { arg[0] } }

##
# Convenience wrapper for the _interpolate function.  Takes a
# single string or props.Node object in arg[0] indicating a target
# property, and a variable-length list of time/value pairs.  Example:
#
#  interpolate("/animations/radar/angle",
#              180, 1, 360, 1, 0, 0,
#              180, 1, 360, 1, 0, 0,
#              180, 1, 360, 1, 0, 0,
#              180, 1, 360, 1, 0, 0,
#              180, 1, 360, 1, 0, 0,
#              180, 1, 360, 1, 0, 0,
#              180, 1, 360, 1, 0, 0,
#              180, 1, 360, 1, 0, 0);
#
# This will swing the "radar dish" smoothly through 8 revolutions over
# 16 seconds.  Note the use of zero-time interpolation between 360 and
# 0 to wrap the interpolated value properly.
#
interpolate = func {
    if(isa(arg[0], props.Node)) { arg[0] = arg[0]._g; }
    elsif(typeof(arg[0]) != "scalar") { return; }
    _interpolate(arg[0], size(arg) == 1 ? [] : subvec(arg, 1));
}


##
# Convenience wrapper for the _setlistener function. Takes a
# single string or props.Node object in arg[0] indicating the
# listened to property, a function in arg[1], and an optional
# bool in arg[2], which triggers the function initially if true.
#
setlistener = func {
    if(isa(arg[0], props.Node)) { arg[0] = arg[0]._g; }
    var id = _setlistener(arg[0], arg[1], size(arg) > 2 ? arg[2] : 0);
    if(__.log_level <= 2) {
        var c = caller(1);
        print(sprintf("setting listener #%d in %s, line %s", id, c[2], c[3]))
    }
    return id;
}


##
# Returns true if the symbol name is defined in the caller, or the
# caller's lexical namespace.  (i.e. defined("varname") tells you if
# you can use varname in an expression without a undefined symbol
# error.
#
defined = func(sym) {
    var fn = 1;
    while((var frame = caller(fn)) != nil) {
        if(contains(frame[0], sym)) { return 1; }
        fn += 1;
    }
    return 0;
}


##
# Print log messages in appropriate --log-level.
# Usage: printlog("warn", "...");
# The underscore hash prevents helper functions/variables from
# needlessly polluting the global namespace.
#
__ = {};
__.dbg_types = { none:0, bulk:1, debug:2, info:3, warn:4, alert:5 };
__.log_level = __.dbg_types[getprop("/sim/logging/priority")];
printlog = func(level, args...) {
    if(__.dbg_types[level] >= __.log_level) { call(print, args); }
}


##
# Load and execute ~/.fgfs/Nasal/*.nas files in alphabetic order
# after all $FG_ROOT/Nasal/*.nas files were loaded.
#
_setlistener("/sim/signals/nasal-dir-initialized", func {
    var path = getprop("/sim/fg-home") ~ "/Nasal";
    if ((var dir = directory(path)) == nil) return;
    foreach (var file; sort(dir, cmp)) {
        if (substr(file, -4) != ".nas") continue;

        var module = substr(file, 0, size(file) - 4);
        file = path ~ "/" ~ file;
        printlog("info", ">>> executing local Nasal file ", file);

        if (!contains(globals, module)) var locals = globals[module] = {};
        elsif (typeof(globals[module]) == "hash") var locals = globals[module];
        else var locals = {};

        var err = [];
        var code = call(func { compile(io.readfile(file), file) }, nil, err);
        if (size(err)) {
            print(file ~ ": " ~ err[0]);
            continue;
        }
        call(bind(code, globals), nil, nil, locals, err);
        debug.printerror(err);
    }
});