# A3XX ND Canvas
# Joshua Davidson (Octal450)
# Based on work by artix

# Copyright (c) 2020 Josh Davidson (Octal450)

var assert_m = canvas.assert_m;

# --------------------------------
# From FGDATA/Nasal/canvas/api.nas
# --------------------------------

# Recursively get all children of class specified by first param
canvas.Group.getChildrenOfType = func(type, array = nil){
    var children = array;
    if(children == nil)
        children = [];
    var my_children = me.getChildren();
    if(typeof(type) != "vector")
        type = [type];
    foreach(var c; my_children){
        foreach(var t; type){
            if(isa(c, t)){
                append(children, c);
            }
        }
        if(isa(c, canvas.Group)){
            c.getChildrenOfType(type, children);
        }
    }
    return children;
};

    # Set color to children of type Path and Text. It is possible to optionally
    # specify which types of children should be affected by passing a vector as
    # the last agrument, ie. my_group.setColor(1,1,1,[Path]);
canvas.Group.setColor = func(){
    var color = arg;
    var types = [Path, Text];
    var arg_c = size(color);
    if(arg_c > 1 and typeof(color[-1]) == "vector"){
        types = color[-1];
        color = subvec(color, 0, arg_c - 1);
    }
    var children = me.getChildrenOfType(types);
    if(typeof(color) == "vector"){
        var first = color[0];
        if(typeof(first) == "vector")
            color = first;
    }
    foreach(var c; children)
    c.setColor(color);
};

canvas.Map.addLayer =  func(factory, type_arg=nil, priority=nil, style=nil, opts=nil, visible=1)
{
    if(contains(me.layers, type_arg))
        logprint("warn", "addLayer() warning: overwriting existing layer:", type_arg);
    var options = opts;
    # Argument handling

    if (type_arg != nil) {
        var layer = factory.new(type:type_arg, group:me, map:me, style:style, options:options, visible:visible);
        var type = factory.get(type_arg);
        var key = type_arg;
    } else {
        var layer = factory.new(group:me, map:me, style:style, options:options, visible:visible);
        var type = factory;
        var key = factory.type;
    }
    me.layers[type_arg] = layer;

    if (priority == nil)
        priority = type.df_priority;
    if (priority != nil)
        layer.group.setInt("z-index", priority);

    return layer; # return new layer to caller() so that we can directly work with it, i.e. to register event handlers (panning/zooming)
};

# -----------------------------------------
# From FGDATA/Nasal/canvas/MapStructure.nas
# -----------------------------------------

var opt_member = func(h,k) {
    if (contains(h, k)) return h[k];
    if (contains(h, "parents")) {
        var _=h.parents;
        for (var i=0;i<size(_);i+=1){
            var v = opt_member(_[i], k);
            if (v != nil) return v;
        }
    }
    return nil;
};

# Symbol

canvas.Symbol.formattedString = func(frmt, model_props){
    if(me.model == nil) return frmt;
    var args = [];
    foreach(var prop; model_props){
        if(contains(me.model, prop)){
            var val = me.model[prop];
            var tp = typeof(val);
            if(tp != "scalar"){
                val = "";
                #logprint("warn", "formattedString: invalid type for "~prop~" (" ~ tp ~ ")");
            } else {
                append(args, val);
            }
        }
    }
    return call(sprintf, [frmt] ~ args);
};

canvas.Symbol.getOption = func(name,  default = nil){
    var opt = me.options;
    if(opt == nil)
        opt = me.layer.options;
    if(opt == nil) return default;
    var val = opt_member(opt, name);
    if(val == nil) return default;
    return val;
};

canvas.Symbol.getStyle = func(name, default = nil){
    var st = me.style;
    if(st == nil)
        st = me.layer.style;
    if(st == nil) return default;
    var val = opt_member(st, name);
    if(typeof(val) == "func"){
        val = (call(val,[],me));
    }
    if(val == nil) return default;
    return val;
};

canvas.Symbol.getLabelFromModel = func(default_val = nil){
    if(me.model == nil) return default_val;
    if(default_val == nil and contains(me.model, "id"))
        default_val = me.model.id;
    var label_content = me.getOption("label_content");
    if(label_content == nil) return default_val;
    if(typeof(label_content) == "scalar")
        label_content = [label_content];
    var format_s = me.getOption("label_format");
    var label = "";
    if(format_s == nil){
        format_s = "%s";
    }
    return me.formattedString(format_s, label_content);
};

canvas.Symbol.callback = func(name, args...){
    name = name ~"_callback";
    var f = me.getOption(name);
    if(typeof(f) == "func"){
        return call(f, args, me);
    }
};

# DotSym

canvas.DotSym.update = func() {
    if (me.controller != nil) {
        if (!me.controller.update(me, me.model)) return;
        elsif (!me.controller.isVisible(me.model)) {
            me.element.hide();
            return;
        }
    } else
        me.element.show();
    me.draw();
    if(me.getOption("disable_position", 0)) return; # << CHECK FOR OPTION "disable_position"
    var pos = me.controller.getpos(me.model);
    if (size(pos) == 2)
        pos~=[nil]; # fall through
    if (size(pos) == 3)
        var (lat,lon,rotation) = pos;
    else __die("DotSym.update(): bad position: "~debug.dump(pos));
    # print(me.model.id, ": Position lat/lon: ", lat, "/", lon);
    me.element.setGeoPosition(lat,lon);
    if (rotation != nil)
        me.element.setRotation(rotation);
};

# SVGSymbol

canvas.SVGSymbol.init = func() {
    me.callback("init_before");
    var opt_path = me.getStyle("svg_path");
    if(opt_path != nil)
        me.svg_path = opt_path;
    if (!me.cacheable) {
        if(me.svg_path != nil and me.svg_path != "")
            canvas.parsesvg(me.element, me.svg_path);
        # hack:
        if (var scale = me.layer.style["scale_factor"])
            me.element.setScale(scale);
        if ((var transl = me.layer.style["translate"]) != nil)
            me.element.setTranslation(transl);
    } else {
        __die("cacheable not implemented yet!");
    }
    me.callback("init_after");
    me.draw();
};

canvas.SVGSymbol.draw = func{
    me.callback("draw");
};

# SymbolLayer

canvas.SymbolLayer._new = func(m, style, controller, options) {
    # print("SymbolLayer setup options:", m.options!=nil);
    m.style = default_hash(style, m.df_style);
    m.options = default_hash(options, m.df_options);

    if (controller == nil)
        controller = m.df_controller;
    assert_m(controller, "parents");
    if (controller.parents[0] == SymbolLayer.Controller)
        controller = controller.new(m);
    assert_m(controller, "parents");
    assert_m(controller.parents[0], "parents");
    if (controller.parents[0].parents[0] != SymbolLayer.Controller)
        __die("MultiSymbolLayer: OOP error");
    if(options != nil){ # << CHECK FOR CONFIGURABLE LISTENERS
        var listeners = opt_member(controller, "listeners");
        var listen = opt_member(options, "listen");
        if (listen != nil and listeners != nil){
            var listen_tp = typeof(listen);
            if(listen_tp != "vector" and listen_tp != "scalar")
                __die("Options 'listen' cannot be a "~ listen_tp);
            if(typeof(listen) == "scalar")
                listen = [listen];
            foreach(var node_name; listen){
                var node = opt_member(options, node_name);
                if(node == nil)
                    node = node_name;
                append(controller.listeners,
                       setlistener(node, func call(m.update,[],m),0,0));
            }
        }
    }
    m.controller = controller;
};

# LineSymbol

canvas.LineSymbol.new = func(group, layer, model, controller=nil) {
	if (me == nil) __die("Need me reference for LineSymbol.new()");
	if (typeof(model) != "vector") {
		if(typeof(model) == "hash"){
			if(!contains(model, "path"))
			canvas.__die("LineSymbol.new(): model hash requires path");
		}
		else canvas.__die("LineSymbol.new(): need a vector of points or a hash");
	}
	var m = {
		parents: [me],
		group: group,
		layer: layer,
		model: model,
		controller: controller == nil ? me.df_controller : controller,
		element: group.createChild(
			"path", me.element_id
		),
	};
	append(m.parents, m.element);
	canvas.Symbol._new(m);

	m.init();
	return m;
};
	# Non-static:
canvas.LineSymbol.draw = func() {
	me.callback("draw_before");
	if (!me.needs_update) return;
	#logprint(_MP_dbg_lvl, "redrawing a LineSymbol "~me.layer.type);
	me.element.reset();
	var cmds = [];
	var coords = [];
	var cmd = canvas.Path.VG_MOVE_TO;
	var path = me.model;
	if(typeof(path) == "hash"){
		path = me.model.path;
		if(path == nil) 
			canvas.__die("LineSymbol model requires a 'path' member (vector)");
	}
	foreach (var m; path) {
		if(size(keys(m)) >= 2){
			var (lat,lon) = me.controller.getpos(m);
			append(coords,"N"~lat);
			append(coords,"E"~lon);
			append(cmds,cmd); 
			cmd = canvas.Path.VG_LINE_TO;
		} else {
			cmd = canvas.Path.VG_MOVE_TO;
		}
	}
	me.element.setDataGeo(cmds, coords);
	me.element.update(); # this doesn"t help with flickering, it seems
	me.callback("draw_after");
};