# DO228 ND Canvas
# Bea Wolf
# based on work by Joshua Davidson and artix

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))
		printlog("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 = "";
				#printlog("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;
	#printlog(_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");
};

