# Convert a resistor value to numeric (ohm). Formats accepted:
#  number
#  number suffix  (e.g. 5.1k)
#  suffix in number (e.g. 4k7)
function getrval(str,     n,v,c,tmp,mult)
{
	tmp = "";
	mult = 1;
	v = length(str);
	for(n = 1; n <= v; n++) {
		c = substr(str, n, 1);
		if ((c == "k") || (c == "k")) {
			c = ".";
			mult=1000;
		}
		else if (c == "M") {
			c = ".";
			mult=1000000;
		}
		else if (c == "m") {
			c = ".";
			mult=1/1000;
		}
		else if (c == "u") {
			c = ".";
			mult=1/1000000;
		}
		tmp = tmp @ c;
	}

	return tmp * mult;
}


# Truncate trailing zeros after the decimal dot
function setrval(str,     n,v,c,dot,good)
{
	str = str @ "";
	v = length(str);
	for(n = 1; n <= v; n++) {
		c = substr(str, n, 1);
		if (c == ".") {
			dot = 1;
			good = n;
		}
		else if (dot && (c != "0"))
			good = n;
		else if (!dot)
			good = n;
	}
	return substr(str, 1, good);
}


# Calculate output voltage from input voltages and resistances
function calc_outv()
{
	outv = voltage[2] + (voltage[1] - voltage[2]) * value[2] / (value[1] + value[2]);
}

# Calculate output voltage and refresh the dialog
function divider_recalc_outv()
{
	# first read current values from the dialog
	value[1] = dad("divdlg", "get", w_val[1]);
	value[2] = dad("divdlg", "get", w_val[2]);
	voltage[1] = dad("divdlg", "get", w_vol[1]);
	voltage[2] = dad("divdlg", "get", w_vol[2]);

	calc_outv();

	dad("divdlg", "set", w_outv, outv+0);
}

# Exclusive locking mechanism: make sure exactly one lock checkboxe is active
# Caclulate dst's lock value based on src's lock value (both dst and src are
# in range of 1..2)
function divider_recalc_lock(dst, src    ,val)
{
	val = dad("divdlg", "get", w_lock[src+0]);
	dad("divdlg", "set", w_lock[dst+0], (!val) + 0);
}

# Recalculate resistor a resistor value based on output voltage and all
# other inputs. Locks determine which resistor is recalculated.
function divider_recalc_resistor(    ,idx,tmp)
{
	outv = dad("divdlg", "get", w_outv);

	# cache common voltage component
	tmp = (outv - voltage[2]) / (voltage[1] - voltage[2]);

	# figure which one to recalc
	if (dad("divdlg", "get", w_lock[1]) == 0) {
		value[1] = value[2] * (1-tmp) / tmp;
		dad("divdlg", "set", w_val[1], value[1]);
	}
	else {
		value[2] = value[1] *tmp / (1-tmp);
		dad("divdlg", "set", w_val[2], value[2]);
	}
}

# User callable action
function divider(  list,obj,R1,R2,refdes,res)
{
	list = idplist("alloc");

	# search resistors: they are GRP objects with role==symbol and name attribute
	# starting with R (as a convention). Accept only selected objects.
	query("append", list, "(@.type == GRP) && (@.a.role == \"symbol\") && (@.a.name ~ \"^R\") && (@.p.selected)");

	if (idplist("length", list) != 2) {
		message("ERROR", "divider() requires exactly 2 resistors selected");
		return;
	}

	# save the idpath of the two resistors in textual format
	R1 = idplist("pop", list);
	R2 = idplist("pop", list);
	R1i = idp("print", R1);
	R2i = idp("print", R2);

	# free binary idpaths and the idpath list
	idp("free", R1);
	idp("free", R2);
	idplist("free", list);


	# retrieve refdes (name) and value attributes
	refdes[1] = propget("object:" @ R1i, "a/name");
	refdes[2] = propget("object:" @ R2i, "a/name");
	orig[1] = value[1] = getrval(propget("object:" @ R1i, "a/value"));
	orig[2] = value[2] = getrval(propget("object:" @ R2i, "a/value"));
	calc_outv();

	# create the dialog box
	dad("divdlg", "new");
	dad("divdlg", "begin_vbox");
		dad("divdlg", "label", "Calculate resistor divider");
			dad("divdlg", "begin_table", 4);
				dad("divdlg", "label", "resistor");
				dad("divdlg", "label", "locked");
				dad("divdlg", "label", "resistance");
				dad("divdlg", "label", "voltage");

				dad("divdlg", "label", refdes[1]);
				w_lock[1] = dad("divdlg", "bool");
					dad("divdlg", "default", 1);
					dad("divdlg", "onchange", "divider_recalc_lock(2, 1)");
				w_val[1] = dad("divdlg", "real", 0, 10000000);
					dad("divdlg", "default", value[1]+0);
					dad("divdlg", "onchange", "divider_recalc_outv()");
				w_vol[1] = dad("divdlg", "real", 0, 10000000); ;
					dad("divdlg", "default", voltage[1]);
					dad("divdlg", "onchange", "divider_recalc_outv()");

				dad("divdlg", "label", refdes[2]);
				w_lock[2] = dad("divdlg", "bool");
					dad("divdlg", "default", 0);
					dad("divdlg", "onchange", "divider_recalc_lock(1, 2)");
				w_val[2] = dad("divdlg", "real", 0, 10000000);
					dad("divdlg", "default", value[2]+0);
					dad("divdlg", "onchange", "divider_recalc_outv()");
				w_vol[2] = dad("divdlg", "real", 0, 10000000);
					dad("divdlg", "default", voltage[2]);
					dad("divdlg", "onchange", "divider_recalc_outv()");
			dad("divdlg", "end");
		dad("divdlg", "begin_hbox");
			dad("divdlg", "label", "output voltage:");
			w_outv = dad("divdlg", "real", 0, 10000000);
				dad("divdlg", "default", outv);
				dad("divdlg", "onchange", "divider_recalc_resistor()");
		dad("divdlg", "end");
		dad("divdlg", "button_closes", "cancel", -1, "set & close", 0);
	dad("divdlg", "end");

	res = dad("divdlg", "run_modal", "Resistor divider", "");

	# res == 0 means clicked "set & close", as specified in button_closes above
	if (res == 0) {
		# write back only those values that differ from the originally read-out ones
		# this reduces noise in the undo buffer and accidental conversion of
		# numeric formats, e.g. 4k7 to 4700
		if (value[1] != orig[1])
			propset("object:" @ R1i, "a/value", setrval(value[1]));
		if (value[2] != orig[2])
			propset("object:" @ R2i, "a/value", setrval(value[2]));
	}
}

# Runs once when the script is ran
function main(ARGV)
{
	# default voltages values; this is something we can not read out from
	# the drawing
	voltage[1] = 3.3;
	voltage[2] = 0;

	# register user callable action
	fgw_func_reg(divider);

	# register internal actions the dialog box will call back
	fgw_func_reg(divider_recalc_outv);
	fgw_func_reg(divider_recalc_lock);
	fgw_func_reg(divider_recalc_resistor);

	# create a menu with the hotkey {p s d}
	cookie = scriptcookie();
	createmenu("/main_menu/Plugins/script/resistor divider", "divider()", "Invokes resistor divider calculator", cookie, "<key>p;<key>s;<key>d");
}
