# GUI callback when nx or ny value is changed; "which" is x or y to indicate
# which value is being changed, "other" is the opposite index
function zillion_gui_chg(which, other)
{
	# remember the new value
	GUI[which] = dad("zdlg", "get", W[which]);

	# make sure we are not going to do an 1x1 copy
	if ((GUI[which] == 1) && (dad("zdlg", "get", W[other]) == 1))
		dad("zdlg", "set", W[other], 2);
}

# dialog box for setting nx and ny when they are not given in arguments
# save widget IDs in script-global W["x"] and W["y"], save results in
# script-global GUI["x"] and GUI["y"]
function zillion_gui()
{
	dad("zdlg", "new");
	dad("zdlg", "begin_vbox");
		dad("zdlg", "begin_hbox");
			dad("zdlg", "label", "nx");
			W["x"] = dad("zdlg", "integer", 1, 1000);
				dad("zdlg", "default", GUI["x"]);
				dad("zdlg", "onchange", "zillion_gui_chg(x, y)");
		dad("zdlg", "end");
		dad("zdlg", "begin_hbox");
			dad("zdlg", "label", "ny");
			W["y"] = dad("zdlg", "integer", 1, 1000);
				dad("zdlg", "default", GUI["y"]);
				dad("zdlg", "onchange", "zillion_gui_chg(y, x)");
		dad("zdlg", "end");
		dad("zdlg", "button_closes", "cancel", -1, "Do it!", 0);
	dad("zdlg", "end");

	return dad("zdlg", "run_modal", "Zillion copies", "");
}

# User callable action
function zillion(nx, ny,     x,y,dx,dy,list,oidp,addr,x1,y1,x2,y2,bbx1,bby1,bbx2,bby2,ox,oy)
{

	# if number of copies is not specified, present a dialog for setting them
	if (nx == "") {
		GUI["x"] = GUI["y"] = 3;
		if (zillion_gui() != 0)
			return;
		nx = GUI["x"];
		ny = GUI["y"];
	}
	else {
		# when ny is not specified, assume 1 for a single row of copies
		if (ny == "")
			ny = 1;

		# input validation: we need more than an 1x1 array
		nx = int(nx);
		ny = int(ny);
		if ((nx < 1) || (ny < 1) || (nx+ny == 2)) {
			message("ERROR", "zillion(): arguments must be integers >= 1 and there must be more than 1 copies");
			return 1;
		}
	}

	# create a list for all selected objects for the bbox caclulation
	list = idplist("alloc");

	# calculate bbox of selection of first-level objects (objects directly on
	# the sheet, not within groups like symbols or wirenets)
	query("append", list, "(@.p.selected) && (@.level == 1)");

	# iterate over the list of text objects found, always moving the first
	# into oidp
	bbx1 = bby1 = +100000000;
	bbx2 = bby2 = -100000000;
	for(oidp = idplist("pop", list); oidp != ""; oidp = idplist("pop", list)) {
		addr = "object:" @ idp("print", oidp);
		x1 = propget(addr, "p/bbox/x1");
		y1 = propget(addr, "p/bbox/y1");
		x2 = propget(addr, "p/bbox/x2");
		y2 = propget(addr, "p/bbox/y2");
		if (x1 < bbx1) bbx1 = x1;
		if (y1 < bby1) bby1 = y1;
		if (x2 > bbx2) bbx2 = x2;
		if (y2 > bby2) bby2 = y2;
	}

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


	# calculate displacement: width and height rounded to 4000 with some margin
	dx = bbx2-bbx1;
	dy = bby2-bby1;
	dx = int(dx/4000+1)*4000;
	dy = int(dy/4000+1)*4000;

	# calculate buffer origin, again 4000 rounded to make sure everything lands
	# aligned at the end
	ox = int(bbx1/4000+1)*4000;
	oy = int(bby1/4000+1)*4000;

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

	# make a copy of the selection, grab at ox;oy
	buffercopy(ox, oy);

	# create an array of copies, grown to right and up; skip the copy at
	# 0;0 so that we do not paste over the original input
	for(y = 0; y < ny; y++) {
		for(x = 0; x < nx; x++) {
			if (x+y > 0) {
				bufferpaste(ox + dx*x, oy + dy*y);
			}
		}
	}


	# 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(zillion);

	# register action called back yb gui widgets
	fgw_func_reg(zillion_gui_chg);

	# create a menu with the hotkey {p s z}
	cookie = scriptcookie();
	createmenu("/main_menu/Plugins/script/make a lot of copies", "zillion([x, y])", "Make a lot of copies of selected objects", cookie, "<key>p;<key>s;<key>z");
}
