#                            COPYRIGHT
#
#  cschem - modular/flexible schematics editor - box symbol generator utility
#  Copyright (C) 2022,2023 Tibor 'Igor2' Palinkas
#
#  (Supported by NLnet NGI0 PET Fund in 2022)
#
#  This program is free software; you can redistribute it and/or modify
#  it under the terms of the GNU General Public License as published by
#  the Free Software Foundation; either version 2 of the License, or
#  (at your option) any later version.
#
#  This program is distributed in the hope that it will be useful,
#  but WITHOUT ANY WARRANTY; without even the implied warranty of
#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#  GNU General Public License for more details.
#
#  You should have received a copy of the GNU General Public License
#  along with this program; if not, write to the Free Software
#  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
#
#  Contact:
#    Project page: http://repo.hu/projects/sch-rnd
#    contact lead developer: http://www.repo.hu/projects/sch-rnd/contact.html
#    mailing list: http://www.repo.hu/projects/sch-rnd/contact.html

BEGIN {
	refdes="U?"
	cmd = MINUID " --gen"
	cmd | getline uid0
	close(cmd)
	uid0 = substr(uid0, 1, 19)
	minuid_init(uid0)
	text_size_mult=0.45
}

function count_pin_txtbrd(loc, pin, TXTBRD,        pname,len)
{
	if (PIN[pin, "label"] == "") {
		pname = pin
		sub(".*::", "", pname)
	}
	else
		pname = PIN[pin, "label"]

	if ((!(pname in TEXTW))) {
		len = length(pname)
		if (PIN[pin, "dir"] != "")
			len++;
		len = int(len * text_size_mult + 0.5)
	}
	else
		len = int(TEXTW[pname] / 1000 + 0.5)

# dbg("name=" pname " " len)
	if (len > TXTBRD[loc])
		TXTBRD[loc] = len
}

function count_pins(slot, loc, expand_slot, TXTBRD,    n,cnt, pin, cinc, TMP, c1, first)
{
	cnt = 0

	if (slot == "") { # all slots
		first = 1
		for(n = 0; n < nslots; n++) {
			# figure the number of pins in this slot; returns 0 if slot is not for this loc
			c1 = count_pins(SLOTS[n], loc, expand_slot, TXTBRD)
			if (c1 != 0) {
				# account for the space inserted between slots
				if (expand_slot && !first)
					cnt+=2
				cnt += c1
				first = 0
			}
		}
	}

	for(n = 0; n < SLOT[slot, "npins"]; n++) {
		pin = PINS[slot, n]
		if (PIN[pin, "loc"] ==  loc) {
			if (expand_slot)
				cinc = split(PIN[pin, "num"], TMP, "[:]")
			else
				cinc = 1
			cnt += cinc
			count_pin_txtbrd(loc, pin, TXTBRD)
		}
	}
	return cnt
}

function draw_pin(pin, ST, slotidx, SLT   ,x2,y2,pname,pnum,pindecor,intdecor,label2)
{
	x2 = ST["x"] * pin_grid
	y2 = ST["y"] * pin_grid

	pname = pin
	sub(/^.*::/,"", pname)

	if (slotidx == "") {
		pnum = PIN[pin, "num"]
		if (pnum ~ ":") {
# if slotted, use first and then rely on portmap
			sub(":.*$", "", pnum)
		}
	}
	else {
		pnum = SLT[slotidx]
# if slotted and we are generating monolith, prefix with slot number
		if (PIN[pin, "num"] ~ ":")
			pname = slotidx pname
	}


	if (PIN[pin, "invcirc"])
		pindecor = "circle"

	if (PIN[pin, "dir"] == "in")
		intdecor = "intri"
	else if (PIN[pin, "dir"] == "out")
		intdecor = "outtri"
	else if (PIN[pin, "dir"] == "bidir")
		intdecor = "bidirtri"

	if ((pin SUBSEP "funcmap") in PIN)
		label2 = "%../a.funcmap/name%"
	else
		label2=PIN[pin, "label"];

#	dbg("draw_pin(): pname=" pname " dir=" ST["pdir"] " at " x2 ";" y2)
	sym_term(SYM,   x2, y2, ST["pdir"], pnum, pname, pindecor, intdecor, label2)
}

# draw pins of a given slot:loc, with first pin touching the box at x0;y0
# px and py are x and y dir for the pin line, stx and sty are stepping to
# the next coord. State is stored in ST
function draw_pins_(slot, ALIGN, loc, stx, sty, ST, currslot   ,n,v,i,siden,A,pin)
{
	ST["pdir"] = loc
	siden = 0
	for(n = 0; n < SLOT[slot, "npins"]; n++) {
		pin = PINS[slot, n]
		if (PIN[pin, "loc"] ==  loc) {
			if (ALIGN["cfg", "expand_slot"]) {
				v = split(PIN[pin, "num"], A, "[:]")
				i = currslot
				if (i > v) continue
				draw_pin(pin, ST, i, A)
				ST["x"] += stx
				ST["y"] += sty
				if ((ALIGN["midgap", loc] != "") && (siden == ALIGN["midgap", loc])) {
					ST["x"] += stx
					ST["y"] += sty
				}
				siden++
			}
			else {
				draw_pin(pin, ST, "", A)
				ST["x"] += stx
				ST["y"] += sty
				if ((ALIGN["midgap", loc] != "") && (siden == ALIGN["midgap", loc])) {
					ST["x"] += stx
					ST["y"] += sty
				}
				siden++
			}
		}
	}
	return siden
}

function draw_pins(slot, ALIGN, loc, x0, y0, stx, sty,    ST,save_last,    n,currslot,orig_STx,orig_STy)
{
	if (slot == "") { # all slots
		LAST_CRD[loc, "x"] = ""
		LAST_CRD[loc, "y"] = ""
		for(n = 0; n < nslots; n++)
			draw_pins(SLOTS[n], ALIGN, loc, x0, y0, stx, sty, ST, 1)
	}

	if ((LAST_CRD[loc, "x"] != "") && save_last)
		ST["x"] = LAST_CRD[loc, "x"]
	else
		ST["x"] = x0 + ALIGN[loc, "x0"]

	if ((LAST_CRD[loc, "y"] != "") && save_last)
		ST["y"] = LAST_CRD[loc, "y"]
	else
		ST["y"] = y0 + ALIGN[loc, "y0"]

	orig_STx = ST["x"]
	orig_STy = ST["y"]


	if (ALIGN["cfg", "expand_slot"]) {
# generate pins in a per slot fashion until slots run out (or at most 256 of them to avoid infinite loop)
		for(currslot = 1; currslot <= 256; currslot++) {
			if (draw_pins_(slot, ALIGN, loc, stx, sty, ST, currslot) == 0)
				break
		}
	}
	else
		draw_pins_(slot, ALIGN, loc, stx, sty, ST, "")

	if (save_last) {
		# monolith: remember last X and Y st so the next slot can continue from there
		# if not the first slot on the given side, also add step (stx/sty) to separate
		if (orig_STx != ST["x"]) {
			if (LAST_CRD[loc, "x"] != "")
				ST["x"] += stx
			LAST_CRD[loc, "x"] = ST["x"]
		}
		if (orig_STy != ST["y"]) {
			if (LAST_CRD[loc, "y"] != "")
				ST["y"] += sty
			LAST_CRD[loc, "y"] = ST["y"] + sty
		}
	}
}


function draw_all_pin(sn, ALIGN,     bw, bh)
{
	bw = ALIGN["box","width"]
	bh = ALIGN["box","height"]

	ALIGN["left", "y0"] = -ALIGN["left", "y0"]
	ALIGN["right", "y0"] = -ALIGN["right", "y0"]

	#                              x0         y0         stx sty
	draw_pins(sn, ALIGN, "left",   -border,   bh,         +0, -1)
	draw_pins(sn, ALIGN, "right",  bw+border, bh,         +0, -1)
	draw_pins(sn, ALIGN, "top",    border,    bh+border,  +1, +0)
	draw_pins(sn, ALIGN, "bottom", border,    -border,    +1, +0)
}

function align_calc(ALIGN, loc, boxloc, target_field, how    ,diff)
{
	diff = ALIGN["box",boxloc] - ALIGN[loc,"count"]
	if (how == "center")
		ALIGN[loc, target_field] = diff/2-0.5
	else if ((how == "begin") || (how == ""))
		ALIGN[loc, target_field] = 0
	else if (how == "end")
		ALIGN[loc, target_field] = diff
	else
		print "Invalid alignment: " how > stderr

	ALIGN[loc, target_field] = int(ALIGN[loc, target_field])+1

	dbg("align_calc: loc=" loc " boxloc=" boxloc " how=" how " res=" ALIGN[loc, target_field] " boxloc=" ALIGN["box",boxloc] " count=" ALIGN[loc,"count"]  " target_field=" target_field)
}

function align_adjust_center(size, pins1, how1, pins2, how2    ,adj1,adj2)
{
	adj1 = 0
	adj2 = 0

	if ((how1 == "center") && (pins1 == 1))
		adj1 = (size % 2)
	if ((how2 == "center") && (pins2 == 1))
		adj2 = (size % 2)

	return adj1 || adj2
}

# Calculate where to place a gap (after which pin) so that an even number of
# pins on an odd sized side distribute symmetrical
function align_get_midgap(size, numpins, how)
{
	if (how != "center")
		return ""

	if ((numpins < 2) || (numpins % 2) || ((size % 2) != 0))
		return ""

# we have even number of pins for odd size
	return numpins/2-1
}

function align_pins(sn, ALIGN,     TXTBRD)
{
	ALIGN["top","count"] = count_pins(sn, "top", ALIGN["cfg", "expand_slot"], TXTBRD)
	ALIGN["bottom","count"] =count_pins(sn, "bottom", ALIGN["cfg", "expand_slot"], TXTBRD)
	ALIGN["left","count"] = count_pins(sn, "left", ALIGN["cfg", "expand_slot"], TXTBRD)
	ALIGN["right","count"] = count_pins(sn, "right", ALIGN["cfg", "expand_slot"], TXTBRD)

	ALIGN["box","width"] = max(max(ALIGN["top","count"]+1, ALIGN["bottom","count"]+1), 1)
	ALIGN["box","height"] = max(max(ALIGN["left","count"]+1, ALIGN["right","count"]+1), 1)

#	dbg("TXTBRD: " ALIGN["box","width"] " " TXTBRD["left"] " " TXTBRD["right"])
	delta = TXTBRD["left"] + TXTBRD["right"]-1
	if (delta > 0)
		ALIGN["box","width"] += delta

#	dbg("TXTBRD: " ALIGN["box","height"] " " TXTBRD["top"] " " TXTBRD["bottom"])
	delta = TXTBRD["top"] + TXTBRD["bottom"]
	if (ALIGN["box","height"] < delta)
		ALIGN["box","height"] = delta

	ALIGN["box","height"] += align_adjust_center(ALIGN["box","height"], ALIGN["left", "count"], SLOT[sn, "pinalign", "left"], ALIGN["right", "count"], SLOT[sn, "pinalign", "right"])
	ALIGN["box","width"] += align_adjust_center(ALIGN["box","width"], ALIGN["bottom", "count"], SLOT[sn, "pinalign", "bottom"], ALIGN["top", "count"], SLOT[sn, "pinalign", "top"])

	ALIGN["midgap", "left"] = align_get_midgap(ALIGN["box","height"], ALIGN["left", "count"], SLOT[sn, "pinalign", "left"])
	ALIGN["midgap", "right"] = align_get_midgap(ALIGN["box","height"], ALIGN["right", "count"], SLOT[sn, "pinalign", "right"])
	ALIGN["midgap", "top"] = align_get_midgap(ALIGN["box","width"], ALIGN["top", "count"], SLOT[sn, "pinalign", "top"])
	ALIGN["midgap", "bottom"] = align_get_midgap(ALIGN["box","width"], ALIGN["bottom", "count"], SLOT[sn, "pinalign", "bottom"])

	align_calc(ALIGN, "left", "height", "y0", SLOT[sn, "pinalign", "left"])
	align_calc(ALIGN, "right", "height", "y0", SLOT[sn, "pinalign", "right"])
	align_calc(ALIGN, "top", "width", "x0", SLOT[sn, "pinalign", "top"])
	align_calc(ALIGN, "bottom", "width", "x0", SLOT[sn, "pinalign", "bottom"])
}

function draw_all_labels(sn    ,key,pat,x,y,ak,prefix,vis)
{
	grp_attr_append(SYM, "-symbol-generator", "boxsym-rnd")
	pat = "^" sn SUBSEP "attr" SUBSEP
	y = -2*pin_grid
	x = -2*pin_grid
	for(key in SLOT) {
		if (key ~ pat) {
			ak = key
			sub(pat, "", ak)
			vis = SLOT[sn, "attr_vis", ak]
			if (vis == "both")
				prefix = ak "="
			else
				prefix = ""
			grp_attr_append(SYM, ak, SLOT[key])
			if (vis != "none") {
				sym_text_attr(SYM, x, y, ak, 0, "sym-secondary", prefix)
				y -= pin_grid
			}
		}
	}
}

function gen_portmap(slot     ,n,i,v,pin,pname,A,AA,cnt)
{
	dbg("generate portmap for " slot ":")
	attrarr_reset(AA)
	cnt = 0
	for(n = 0; n < SLOT[slot, "npins"]; n++) {
		pin = PINS[slot, n]
		v = split(PIN[pin, "num"], A, "[:]")
		if (v > 1) {
			pname = pin
			sub(".*::", "", pname)
			for(i = 1; i <= v; i++) {
				attrarr_append(AA, i "/" pname " -> pcb/pinnum=" A[i])
				cnt++
				dbg(" " i "/" pname " -> pcb/pinnum=" A[i])
			}
		}
	}
	if (cnt > 0)
		grp_attrarr_append(SYM, "portmap", AA)
}

function gen_monolith(   ALIGN)
{
	ofn=basename ".ry"
	if (ofn == inputname) {
		print "Error: can not generate monolith symbol because of file name collision. Rename your input file so it doesn't end in .ry!" > "/dev/stderr"
		return
	}
	ALIGN["cfg", "expand_slot"] = 1
	align_pins("", ALIGN)
	sym_begin(refdes, -2*pin_grid, -pin_grid)
	draw_box(SYM, ALIGN["box","width"], ALIGN["box","height"], border, SLOT["", "shape"], SLOT["", "shape_fill"])
	draw_all_pin("", ALIGN, 1)
	draw_all_labels("")
	sym_end()
	delete SYM
	ofn=""
}

function gen_slot(sn    ,ALIGN)
{
	ofn=basename "_" sn ".ry"
	ALIGN["cfg", "expand_slot"] = 0
	align_pins(sn, ALIGN)
	sym_begin(refdes, -2*pin_grid, -pin_grid)
	draw_box(SYM, ALIGN["box","width"], ALIGN["box","height"], border, SLOT[sn, "shape"], SLOT[sn, "shape_fill"])
	draw_all_pin(sn, ALIGN, 0)
	draw_all_labels(sn)
	gen_portmap(sn)
	sym_end()
	delete SYM
	ofn=""
}

function gen_slots(   n)
{
	for(n = 0; n < nslots; n++) {
		dbg("gen_slots(): n=" n)
		gen_slot(SLOTS[n])
	}
}

function get_text_size1(cmd, txt)
{
	print "fawk_print(" q "T: " txt q ", fontinfo(\"TextWidth\", " q txt q ", \"term-secondary\"), fontinfo(\"TextHeight\", " q txt q ", \"term-secondary\"));" | cmd
}

function get_text_sizes(      n,pname,tmpfn,cmd)
{
	tmpfn = "boxsym-rnd.est.tmp"
	close(tmpfn)

	cmd = "sch-rnd --gui batch > " tmpfn
	close(cmd);

	print "fawk" | cmd

	for(n in PIN) {
		if (n ~ (SUBSEP "label$")) {
		get_text_size1(cmd, PIN[n]);
		}
		if (n ~ (SUBSEP "num$")) {
			pname = n
			sub(SUBSEP "num$", "", pname)
			sub(".*::", "", pname)
			get_text_size1(cmd, pname);
		}
	}
	close(cmd)
	while((getline < tmpfn) == 1) {
		if ($1 == "T:") {
			TEXTW[$2] = $3
			TEXTH[$2] = $4
		}
	}
	close(tmpfn)
}


END {
	get_text_sizes()
	gen_monolith()
	dbg("---------")
	gen_slots()
}
