# Copyright (C) 2022 Tibor 'Igor2' Palinkas
# License: GNU LGPL 2.1 or later
# (Output generated using this script is not affected by the license of the script)

BEGIN {
	q="\""

	DEFAULT["pin_flags"] = "__auto"

	s_default=1
	s_weak=2
	s_explicit=3
	
	offs_x = 0
	offs_y = 0
	proto_next_id = 0
	pin_grid = 4000

# index names
	i_objs = 1
	i_objid = 2
	i_indent = 3
	i_attrs = 4
	i_id = 5
	i_x = 6
	i_y = 7
	i_rot = 9
	i_mirx = 10
	i_miry = 11


	pi=3.141592654

	NL = "\n"

# redirect: output file
	ofn = "/dev/stdout"

# minuid
	for(n = 32; n < 127; n++)
		ORD[sprintf("%c", n)] = n
	BASE64 = "ABCDEFGHIJKLMNOPQRSTUVXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
}

# call this with the first 19 characters of minuid --gen
function minuid_init(seed   ,n)
{
	minuid_prefix = seed;
	if (length(minuid_prefix) != 19) {
		print "Error: invalid seed in minuid_init(): needs to be 19 chars long" > "/dev/stderr"
		exit(1)
	}
	for(n = 0; n < 5; n++)
		MINUID_SUFF[n] = 0
}


function minuid_gen(    n,s)
{
	if (minuid_prefix == "") {
		print "Error: called minuid_get() without minuid_init()" > "/dev/stderr"
		exit(1)
	}

	for(n = 4; n >= 0; n--) {
		MINUID_SUFF[n]++
		if (MINUID_SUFF[n] < 63)
			break;
		MINUID_SUFF[n] = 0
	}

	s = minuid_prefix
	for(n = 0; n < 5; n++)
		s = s substr(BASE64, (MINUID_SUFF[n] % 64 + 1), 1)
	return s
}

function maybe_print_minuid(ind)
{
	if (minuid_prefix != "")
		print ind "uuid=" minuid_gen() ";" > ofn
}

function maybe_ret_minuid(ind)
{
	if (minuid_prefix != "")
		return ind "uuid=" minuid_gen() ";" NL
	return ""
}

# Throw an error and exit
function error(msg)
{
	print "Error: " msg > "/dev/stderr"
	exit 1
}

# return a if it is not empty, else return b
function either(a, b)
{
	return a != "" ? a : b
}

# strip leading/trailing whitespaces
function strip(s)
{
	sub("^[ \t\r\n]*", "", s)
	sub("[ \t\r\n]*$", "", s)
	return s
}

function lht_str(s)
{
	if (s ~ "[^A-Za-z0-9 _%.-]") {
		gsub("}", "\\}", s)
		return "{" s "}"
	}
	return s
}

function sym_text_(GRP, x, y, str, rot, mirx, miry, dynfloat, pen      ,s)
{
	if (pen == "") pen = "sym-decor";

	s = s GRP[i_indent] "ha:text." (++GRP[i_objid]) " {"
	s = s " x1=" x "; y1=" y "; rot=" either(rot, 0)
	if (mirx) s = s "; mirx=1"
	if (miry) s = s "; miry=1"
	s = s "; stroke=" pen ";"
	if (dynfloat > 0) {
		if ((pen == "term-primary") || (pen == "term-secondary") || (dynfloat ~ "nofloat"))
			s = s " dyntext=1;"
		else
			s = s " dyntext=1; floater=1;"
	}

	s = s " text=" lht_str(str) "; }"

	GRP[i_objs] = GRP[i_objs] s NL
}

function sym_text(GRP, x, y, str, rot, mirx, miry, dynfloat, pen)
{
	sym_text(GRP, x, y, str, rot, dynfloat, pen)
}

function sym_text_attr_(GRP, x, y, attrname, rot, mirx, miry, pen,     prefix      ,s)
{
	if (!(attrname ~ "^[aA]."))
		attrname = "A." attrname
	return sym_text_(GRP, x, y, prefix "%../" attrname "%", rot, mirx, miry, 1, pen)
}

function sym_text_attr(GRP, x, y, attrname, rot, pen,     prefix)
{
	sym_text_attr_(GRP, x, y, attrname, rot, 0, 0, pen, prefix)
}

function sym_line_(id, x1, y1, x2, y2, pen   ,s)
{
	if (pen == "") pen = "sym-decor";

	if (id != "")
		id = "." id

	s = "ha:line" id " {"
	s = s " x1=" x1 "; y1=" y1 "; x2=" x2 "; y2=" y2 "; stroke=" pen "; }"
	return s
}

function sym_line(GRP, x1, y1, x2, y2, pen   ,s)
{
	s = GRP[i_indent] sym_line_(++GRP[i_objid], x1, y1, x2, y2, pen)
	GRP[i_objs] = GRP[i_objs] s NL
}

function sym_arc(GRP, cx, cy, r, a_start, a_delta, pen      ,s)
{
	if (pen == "") pen = "sym-decor";

	s = s GRP[i_indent] "ha:arc." (++GRP[i_objid]) " {"
	s = s " cx=" cx "; cy=" cy "; r=" r "; sang=" a_start "; dang=" a_delta "; stroke=" pen "; }"

	GRP[i_objs] = GRP[i_objs] s NL
}

# POLY[] is an array indexed between 0 to 2*N-1 for a polygon of N
# vertices, packed as x0;y0;x1;y1;x2;y2 ... xN;yN. The usual pcb-rnd
# polygon rules apply: at least 3 vertices, no self-intersection. This
# call does not make any attempt on cheking polygon validity.
function sym_poly(GRP, POLY, pen, fill     ,s,n)
{
	if (pen == "") pen = "sym-decor";

	s = s GRP[i_indent] "ha:polygon." (++GRP[i_objid]) " {" NL
	s = s GRP[i_indent] "	li:outline {" NL

	for(n = 2; (n in POLY); n += 2)
		s = s GRP[i_indent] "		" sym_line_(id, POLY[n-2], POLY[n-1], POLY[n], POLY[n+1], pen) NL
	s = s GRP[i_indent] "		" sym_line_(id, POLY[n-2], POLY[n-1], POLY[0], POLY[1], pen) NL

	s = s GRP[i_indent] "	}" NL
	s = s GRP[i_indent] "	stroke=" pen NL
	if (fill != "")
		s = s GRP[i_indent] "	fill=" fill NL
	s = s GRP[i_indent] "}"

	GRP[i_objs] = GRP[i_objs] s NL
}

# reset a polygon so it has no vertices
function poly_reset(POLY     ,n)
{
	for(n = 0; (n in POLY); n += 2)
		delete POLY[n]
	POLY["len"] = 0
}

# append x;y to the end of a polygon's vertex list
function poly_append(POLY, x, y)
{
	POLY[POLY["len"]++] = x;
	POLY[POLY["len"]++] = y;
}

function sym_rect(GRP, x1, y1, x2, y2, pen, fill      ,POLY)
{
	poly_reset(POLY)
	poly_append(POLY, x1, y1)
	poly_append(POLY, x1, y2)
	poly_append(POLY, x2, y2)
	poly_append(POLY, x2, y1)

	sym_poly(GRP, POLY, pen, fill)
}


# start generating a subcircuit
function sym_begin(name, ax, ay)
{
	print "ha:cschem-group-v1 {" > ofn

	SYM[i_indent]="			";
	SYM[i_objid] = 0;
	SYM[i_id] = 1;

	grp_attr_append(SYM, "role", "symbol")
	if (name != "") {
		grp_attr_append(SYM, "name", name)
		sym_text_attr(SYM, int(ax), int(ay), "name", 0, "sym-primary")
	}
}

# generate subcircuit footers
function sym_end(     layer,n,v,L,lt,UID)
{
	print grp_render(SYM, "	") > ofn
	print "}" > ofn
}

function grp_attr_append(GRP, key, val     ,s)
{
	s = s GRP[i_indent] key "=" lht_str(val) ";"
	GRP[i_attrs] = GRP[i_attrs] s NL
}

function grp_attrarr_append(GRP, key, AA,    n,v)
{
	s = s GRP[i_indent] "li:" key "= {" NL
	v = AA["len"]
	for(n = 0; n < v; n++)
		s = s GRP[i_indent] "\t" lht_str(AA[n]) NL
	s = s GRP[i_indent] "}" NL
	GRP[i_attrs] = GRP[i_attrs] s
}

function attrarr_reset(AA)
{
	AA["len"] = 0
}

function attrarr_append(AA, val)
{
	AA[AA["len"]++] = val
}


function grp_render(GRP, ind    ,s)
{
	s = s ind "ha:group." GRP[i_id] " {" NL
	s = s maybe_ret_minuid(ind "	")
	if ((GRP[i_x] != 0) || (GRP[i_y] != 0))
		s = s ind  "	x=" GRP[i_x] "; y=" GRP[i_y] ";" NL
	if ((GRP[i_rot] != 0) || (GRP[i_mirx] != 0) || (GRP[i_miry] != 0))
		s = s ind  "	rot=" GRP[i_rot] "; mirx=" GRP[i_mirx] "; miry=" GRP[i_miry] ";" NL
	s = s ind  "	li:objects {" NL
	s = s GRP[i_objs]
	s = s ind  "	}" NL
	s = s ind  "	ha:attrib {" NL
	s = s GRP[i_attrs]
	s = s ind  "	}" NL
	s = s ind  "}"
	return s
}

function slen(str)
{
	return (length(str)-1)/4
}

# generate a term group; orientaiton is one of: left, right, top, bottom.
# Orientation "left" means "left facing terminal": horizontal line with
# 0;0 of the terminal is on the right, anticipated connection point on the left.
# In other words, if the symbol is a box, ori means on which side the term is placed.
# If pindecor is non-empty, it can be "circle" or "intri" or "outtri" or "bidirtri" (in pin space).
# If intdecor is non-empty, it can be "intri" or "outtri" or "bidirtri" (in name2 space).
# If label2 is non-empty, print that instead of terminal name for name2
# Normally name is the terminal name (which is also the "pin number", name2=="");
# alternatively name is the "pin number" and name2 is the terminal name.
# Then if name2 is not "" and label2 is not "", the printout will differ from term name
function sym_term(GRP, x, y, ori, name, name2, pindecor, intdecor, label2,      s,TERM,rot,mirx,miry,dx,tx,TRI,dynt)
{
	TERM[i_id] = ++GRP[i_objid];
	TERM[i_indent] = GRP[i_indent] "\t\t";
	TERM[i_x] = x
	TERM[i_y] = y
	rot = 0
	mirx = 0
	miry = 0

	if (ori == "left") {
		mirx = 1
	}
	else if (ori == "right") {
#		nothing to do: default
	}
	else if (ori == "top") {
		rot = 90
	}
	else if (ori == "bottom") {
		rot = -90
		mirx = 1
	}
	TERM[i_rot] = rot
	TERM[i_mirx] = mirx
	TERM[i_miry] = miry

	grp_attr_append(TERM, "role", "terminal")

	if (pindecor == "circle") {
		dx = 1000
		sym_arc(TERM, dx/2, 0, dx/2, 0, 360, "term-decor")
	}
	else if (pindecor == "outtri") {
		dx = 1000
		poly_reset(TRI)
		poly_append(TRI, dx, 0)
		poly_append(TRI, 0, +dx)
		poly_append(TRI, 0, -dx)
		sym_poly(TERM, TRI, "term-decor", "term-decor")
	}
	else if (pindecor == "intri") {
		dx = 1000
		poly_reset(TRI)
		poly_append(TRI, 0, 0)
		poly_append(TRI, dx, +dx)
		poly_append(TRI, dx, -dx)
		sym_poly(TERM, TRI, "term-decor", "term-decor")
	}
	else if (pindecor == "bidirtri") {
		dx = 2000
		poly_reset(TRI)
		poly_append(TRI, 0, 0)
		poly_append(TRI, dx/2, +dx/2)
		poly_append(TRI, dx, 0)
		poly_append(TRI, dx/2, -dx/2)
		sym_poly(TERM, TRI, "term-decor", "term-decor")
	}
	else
		dx = 0;

	if (intdecor == "intri") {
		tx = 1000
		poly_reset(TRI)
		poly_append(TRI, -tx, 0)
		poly_append(TRI, 0, +tx)
		poly_append(TRI, 0, -tx)
		sym_poly(TERM, TRI, "sym-decor", "")
	}
	else if (intdecor == "outtri") {
		tx = 1000
		poly_reset(TRI)
		poly_append(TRI, 0, 0)
		poly_append(TRI, -tx, +tx)
		poly_append(TRI, -tx, -tx)
		sym_poly(TERM, TRI, "sym-decor", "")
	}
	else if (intdecor == "bidirtri") {
		tx = 2000
		poly_reset(TRI)
		poly_append(TRI, 0, 0)
		poly_append(TRI, -tx/2, +tx/2)
		poly_append(TRI, -tx, 0)
		poly_append(TRI, -tx/2, -tx/2)
		sym_poly(TERM, TRI, "sym-decor", "")
	}
	else
		tx = 0;

	sym_line(TERM, dx, 0, pin_grid, 0, "term-decor")

	if (name2 != "") {
		grp_attr_append(TERM, "pinnum", name)
		grp_attr_append(TERM, "name", name2)
		sym_text_attr(TERM, (1/4) * pin_grid, 0, "a.display/name", 0, "term-primary")
		if (label2 != "") {
			dynt = (label2 ~ "^[%]")
			sym_text_(TERM, -tx-500, -0.5 * pin_grid, label2, 0, 1, 0, dynt, "term-secondary")
		}
		else
			sym_text_attr_(TERM, -tx-500, -0.5 * pin_grid, "name", 0, 1, 0, "term-secondary")
	}
	else {
		grp_attr_append(TERM, "name", name)
		sym_text_attr(TERM, (1/4) * pin_grid, 0, "name", 0, "term-primary")
	}

	GRP[i_objs] = GRP[i_objs] grp_render(TERM, GRP[i_indent]) NL
}


function set_arg_(OUT, key, value, strength)
{
	if (OUT[key, "strength"] > strength)
		return

	OUT[key] = value
	OUT[key, "strength"] = strength
}

# set parameter key=value with optioal strength (s_* consts) in array OUT[]
# set only if current strength is stronger than the original value
# if key starts with a "?", use s_weak
# if key is in DEFAULT[], use DEFAULT[] instead of OUT[]
function set_arg(OUT, key, value     ,strength)
{
	if (strength == "")
		strength = s_explicit
	if (key ~ "^[?]") {
		sub("^[?]", "", key)
		strength = s_weak
	}

	if (key in DEFAULT) {
		if (DEFAULT[key, "dim"])
			value = parse_dim_(value, 0)
		set_arg_(DEFAULT, key, value, strength)
	}
	else
		set_arg_(OUT, key, value, strength)
}

# Process a generator argument list from arg_names. Save the result in
# array OUT. If mandatory is specified, check whether all mandatory
# parameters are specified
# Both arg_names and mandatory are comma separated list of argument names
function proc_args(OUT, arg_names,   mandatory,  N,A,M,v,n,key,val,pos)
{
	gsub(" ", "", arg_names)
	gsub(" ", "", mandatory)
	split(arg_names, N, ",")
	v = split(args, A, ",")

# fill in all named and positional arguments
	pos = 1
	for(n = 1; n <= v; n++) {
		A[n] = strip(A[n])
		if (A[n] == "")
			continue
		if (A[n] ~ "=") {
#			named
			key=A[n]
			val=A[n]
			sub("=.*", "", key)
			sub("^[^=]*=", "", val)
			set_arg(OUT, key, val, s_explicit)
		}
		else {
#			positional
			if (N[pos] == "") {
				error("too many positional arguments at " A[n])
			}
			while((N[pos] in OUT) && (N[pos, "strength"] == s_explicit)) pos++
			set_arg(OUT, N[pos], A[n], s_explicit)
			pos++
		}
	}

# check whether all mandatory arguments are specified
	v = split(mandatory, M, ",")
	for(n = 1; n <= v; n++) {
		if (!(M[n] in OUT)) {
			error("missing argument " M[n] " (or positional " n ")")
			exit 1
		}
	}
}

# decide whether x is true or false
# returns 1 if true
# returns 0 if false
function tobool(x)
{
	if (x == int(x))
		return (int(x) != 0)

	x = tolower(x)
	return (x == "true") || (x == "yes") || (x == "on")
}

function help_extract(SEEN, fn, dirn, OVER, IGN,     WANT,tmp,key,val,i,skip)
{
	if (fn in SEEN)
		return
	SEEN[fn]++
	print "#@@info-gen-extract " fn > ofn
	close(fn)
	while((getline line < fn) > 0) {
		if (line ~ "^#@@include") {
			sub("^#@@include[ \t]*", "", line)
			tmp = dirn "/" line
			WANT[tmp]++
		}
		else if (line ~ "^#@@over@ignore") {
			key = line
			sub("^#@@over@ignore:", "", key)
			sub(" .*", "", key)
			IGN[key] = 1
		}
		else if (line ~ "^#@@over@") {
			key = line
			sub("^#@@over@", "", key)
			val = "#@@" key
			sub(" .*", "", key)
			OVER[key] = val
		}
		else if (line ~ "^#@@") {
			key = line
			sub("^#@@", "", key)
			sub(" .*", "", key)
			skip = 0
			for(i in IGN) {
				if (key ~ i)
					skip = 1
			}
			if (skip)
				continue
			if (key in OVER) {
				print OVER[key] > ofn
				OVER[key "::PRINTED"] = 1
			}
			else
				print line > ofn
		}
	}
	close(fn)
	for(tmp in WANT)
		help_extract(SEEN, tmp, dirn, OVER, IGN)
}

function help_print(   SEEN, OVER, dirn, k)
{
	print "#@@info-generator pcb-rnd common_subc.awk" > ofn
	dirn = genfull
	sub("/[^/]*$", "", dirn)
	help_extract(SEEN, genfull, dirn, OVER)
	for(k in OVER) {
		if (!(k ~ "::PRINTED$") && !((k "::PRINTED") in OVER))
			print OVER[k] > ofn
	}
}

function help_auto()
{
	if ((args ~ "^--help") || (args ~ ",[ \t]*--help")) {
		help_print()
		exit(0)
	}
}
