object $root;

var $root child = 0;
var $root child_index = 0;
var $root inited = 1;

public method .children(): nooverride  {
    return children();
};

public method .debug(): nooverride  {
    arg [what];

    dblog("DEBUG: " + what.join(" "));
};

public method .del_objname(): nooverride  {
    arg name;
    
    (> del_objname(name) <);
};

public method .descendants(): nooverride  {
    var kids, i, c;
    
    kids = children();
    while ((| (c = kids[(i = i + 1)]) |))
        kids = union(kids, c.children());
    return kids;
};

public method .destroy(): nooverride  {
    if (!child)
        throw(~perm, "Attempt to destroy a defining parent.");
    (| .uninitialize() |);
    destroy();
};

public method .find_method() {
    arg method;
    
    return (> find_method(method) <);
};

private method .initialize(): nooverride  {
    var ancestors, ancestor, pos, len, method;
    
    if (inited)
        throw(~perm, "Already initialized.");
    ancestors = ancestors();
    len = ancestors.length();
    for pos in [0 .. len - 1] {
        ancestor = ancestors[len - pos];
        method = tosym("init_" + tostr(ancestor.objname('symbol)));
        catch ~methodnf {
            if ((.find_method(method)) != ancestor)
                throw(~perm, ((("Initialization method for " + (ancestor.objname())) + " in wrong place(") + (find_method(method).objname())) + ")");
            .(method)();
        }
    }
    inited = 1;
    child = 1;
};

public method .log(): nooverride  {
    arg what;
    var line;
    
    if (type(what) == 'string) {
        dblog(what);
    } else if (type(what) == 'list) {
        for line in (what)
            .log(line);
    } else {
        throw(~invarg, "Log must be called with a string or a list of strings");
    }
};

public method .objname(): nooverride  {
    arg [args];
    var name;
    
    name = (| objname() |);
    if (args)
        return name || 0;
    if (name)
        return "$" + tostr(name);
    return toliteral(this());
};

public method .parents(): nooverride  {
    return parents();
};

public method .set_objname(): nooverride  {
    arg objname;
    
    if (type(objname) != 'symbol)
        throw(~perm, "Name is not a symbol.");
    
    // Make sure everything is lowercase.
    objname = tosym(lowercase(tostr(objname)));
    
    // Do nothing if objname isn't different.
    if (objname == (| objname() |))
        return;
    (> set_objname(objname) <);
};

public method .spawn(): nooverride  {
    arg [other_parents];
    var base, obj, name;
    
//    if (child)
//        throw(~perm, "Attempt to spawn a non-defining object.");
    name = .objname('symbol);
    base = tostr(name);
    while ((| lookup(name) |)) {
        child_index = child_index + 1;
        name = tosym((base + "_") + tostr(child_index));
    }
    obj = (> create([this()] + other_parents) <);
    catch any {
        (> obj.initialize() <);
    } with {
        .log($parse_lib.traceback(traceback()));
        if (!(| obj.destroy() |))
            throw(~ack, "Unable to destroy aborted attempt", traceback());
        rethrow(error());
    }
    // (> set_objname(name) <);
    return obj;
};

private method .uninitialize(): nooverride  {
    var ancestor, p;
    
    for ancestor in (ancestors()) {
        method = tosym("uninit_" + tostr(ancestor.objname('symbol)));
        catch ~methodnf {
            if ((.find_method(method)) != ancestor)
                throw(~perm, ((("UnInitialization method for " + (ancestor.objname())) + " in wrong place (") + ((.find_method(method)).objname())) + ")");
            .(method)();
        }
    }
    for p in (.variables())
        .del_var('p);
};