# Create a text object for the label; addr is the textual oidpath of a group,
# lcx and lcy are the center of the longest line within the group if the group
# is a wire-net.
function autolabel_grp(oidpp, lcx, lcy,       addr,role,txt_ptr)
{
	addr = "object:" @ oidpp;
	role = propget(addr, "a/role");

	# place text with pen and location depending on parent type
	if (role == "wire-net")
		txt_ptr = drawtext("retptr", addr, lcx+0, lcy+0, "%../A.name%", "wire");
	else if (role == "symbol")
		txt_ptr = drawtext("retptr", addr, 0, 0, "%../A.name%", "sym-primary");

	# make new text object a dyntext floater
	if (txt_ptr != "") {
		propset("objbin", txt_ptr, "p/text/dyntext", 1);
		propset("objbin", txt_ptr, "p/floater", 1);
	}
}

# User callable action; only the first argument should be passed
function autolabel(scope    ,list,oid,par,GRPS,LINE,addr,role,dyntext,textstr,oidpp,oaddr,x1,y1,x2,y2)
{
	# fawk: make sure GRPS and LINE are arrays, just in case no group is found and
	# the first loop doesn't write them.
	GRPS[];
	LINE[];

	# create a list for finding target objects
	list = idplist("alloc");

	# build the list depending on scope; deal with only text objects, and only
	# those that are not locked in any way (e.g. symbol's non-floaters are locked)
	if ((scope == "") || (scope == "selected"))
		query("append", list, "!(@.anylocked) && (@.p.selected)");
	else if (scope == "all")
		query("append", list, "!(@.anylocked)");
	else {
		idplist("free", list);
		message("ERROR", "autolabel: first argument must be all or selected");
		return;
	}

	# batch all operations together into a single undo serial so they are
	# undone atomically: do not increase undo serial while doing rotations
	undo("FreezeSerial");

	# iterate over the list of text objects found, always moving the first
	# into oidp
	for(oidp = idplist("pop", list); oidp != ""; oidp = idplist("pop", list)) {
		oaddr = addr = "object:" @ idp("print", oidp);
		dyntext = propget(addr, "p/text/dyntext");
		textstr = propget(addr, "p/text/text");
		type = getobjtype(oidp);
		role = propget(addr, "a/role");

		# if role is unset, we may have hit a child object of a group
		# switch to the parent
		if (role == "") {
			par = getparentgrp(oidp);
			idp("free", oidp);
			oidp = par;
			if (oidp != "")
				role = propget("object:" @ idp("print", oidp), "a/role");
		}

		# Store object address in textual form in an array for each group
		# that is suitable for our action. The textual form of the address is
		# safe to remember (until the objects are deleted), we can free oidp.
		# Indexing an array with the group address guarantees we remember every
		# candidate group only once. Keep only symbols and wire-nets. First
		if ((role == "symbol") || (role == "wire-net")) {
			addr = idp("print", oidp);
	
			# The value for each group is set to 0 once the group is mapped, or
			# 1 if a dyntext printing the name is found
			if (dyntext && (textstr == "%../A.name%")) {
				GRPS[addr] = 1;
			}
			else if (!(addr in GRPS))
				GRPS[addr] = 0;

			# also remember longest line per group; for wirenets this is where
			# we place the label
			if (type == "line") {
				x1 = propget(oaddr, "p/line/x1");
				y1 = propget(oaddr, "p/line/y1");
				x2 = propget(oaddr, "p/line/x2");
				y2 = propget(oaddr, "p/line/y2");
				llen = (x2-x1) * (x2-x1) + (y2-y1) * (y2-y1);

				# remember longest line only; +0 is for the case the array lookup
				# returns NIL
				if (llen > LINE[addr, "len"]+0) {
					LINE[addr, "len"] = llen;
					LINE[addr, "cx"] = (x2+x1)/2;
					LINE[addr, "cy"] = (y2+y1)/2;
				}
			}
		}


		# need to free oid path to avoid memory leak
		if (oidp != "")
			idp("free", oidp);
	}

	# need to free empty list to avoid memory leak
	idplist("free", list);

	# visit each candidate group only once, call autolabel_grp() if the group
	# doesn't already have a label
	for(oidpp in GRPS)
		if (GRPS[oidpp] == 0)
			autolabel_grp(oidpp, LINE[oidpp, "cx"], LINE[oidpp, "cy"]);

	# batch all operations together into a single undo serial so they are
	# undone atomically: allow increasing serials again, increase once so
	# the whole batch has its own unique serial number
	undo("UnFreezeSerial");
	undo("IncSerial");
}

# Runs once when the script is ran
function main(ARGV)
{
	# register action called directly by the user
	fgw_func_reg(autolabel);

	# create a menu with the hotkey {p s a}
	cookie = scriptcookie();
	createmenu("/main_menu/Plugins/script/auto create labels on selected nets or symbols", "autolabel(selected)", "Create labels (floater dyntext objects) to selected nets/symbols", cookie, "<key>p;<key>s;<key>a");
}
