#! /usr/bin/awk -E
#
# Copyright (c) 2022 Martin Cernohorsky
# Redistribution and use in source and binary forms, with or without modification,
# are permitted provided that the following conditions are met:
# 
#     1. Redistributions of source code must retain the above copyright notice,
#        this list of conditions and the following disclaimer.
#     2. Redistributions in binary form must reproduce the above copyright notice,
#        this list of conditions and the following disclaimer in the documentation
#        and/or other materials provided with the distribution.
# 
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
# TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
# 

#
# this is Table v. 0.2 and should be considered to be a work in progress.
#

############### HTML PART STARTS HERE ###################################
function WWWhead( 			a, b, c)
{
	HTMLprefix=""
	if (tblRoffMode > 0)
		HTMLprefix=".HTML "

	__HTMLallbox="border: 1px solid black;"
	# __HTMLbox="border: 1px solid black; border-collapse: collapse;"
	__HTMLbox="border: 2px solid black;"
	__HTMLcenter="margin-left:auto; margin-right:auto; "
	HTMLhead=""

	if (tblOpt["center"] || tblOpt["box"] || tblOpt["allbox"])
	{
		HTMLhead=" style=\""
		if (tblOpt["center"])
			HTMLhead=HTMLhead __HTMLcenter
		if (tblOpt["box"] || tblOpt["allbox"])
			HTMLhead=HTMLhead __HTMLbox
		HTMLhead=HTMLhead "\""
	}
	print HTMLprefix "<table" HTMLhead ">"
	return 0
}

function HTMLcolor(f,		a, b, c, d, rv)
{
	if (substr(f,1,1) != "@") return "default";
	a = substr(f,2,1)
	b = substr(f,3,1)
	c = HTMLcolorList[a]
	d = HTMLcolorList[b]

	if (a=="") return "default"
	rv = c
	if (rv == "") return "default"
	if (b=="" || d=="") return rv

	rv = rv "; background-color: " d
	return rv
}

function HTMLfill(n,		i, rv)
{
	rv = ""
	for (i=0; i<n; i++) rv = rv "&numsp;"
	return rv
}

function HTMLspc(s,		c, i, rv)
{
	rv=""
	for (i=1; i<=length(s); i++)
	{
		c=substr(s,i,1)
		if (c==" ") c="&numsp;"
		if (c=="-") c="&minus;"
		if (c=="+") c="&plus;"
		rv = rv c
	}
	return rv
}

function WWWrow(i, 		a, b, c, d, f, j, k, l, p, q, r, w, rv)
{
	if (tblEntries[i,1] == "_")
	{
		print HTMLprefix "<tr>"
    		print HTMLprefix "<td colspan=\"100%\"><hr></td>"
		print HTMLprefix "</tr>"
		return 0
	}
	print HTMLprefix "<tr>"

	k = i - tblHlinesEmitted
	if (k > tblNfmt) k = tblNfmt

	for (j=1; j<=tblCols; j++)
	{
		rv = ""
		_WWWcolor="default"
		_WWWalign="left"
		if (k >= i) _WWWfmt="th"
		else _WWWfmt="td"
		_WWWfmt="td"
	
		w = tblSize[j]
		l = tblWidths[i,j]
		a = substr(tblFormats[k,j], 1, 1)
		f = substr(tblFormats[k,j], 2)
		e = tblEntries[i,j]

		# color handling
		b = index(f,"@")
		c = substr(f,b+2,1)
		if (b) _WWWcolor=HTMLcolor(substr(f,b))
		if (b==1) {if (c>="0" && c<="9") f=substr(f,4); else f=substr(f,3)}

		if (a == "l")
		{
			_WWWalign="'left'"
		}
		if (a == "r")
		{
			_WWWalign="'right'"
		}
		if (a == "c")
		{
			_WWWalign="'center'"
		}
		if (a == "n")
		{
			p = tblSize[j,1] - tblWidths[i,j,1]
			q = tblSize[j,2] - tblWidths[i,j,2]
			r = w - tblWidths[i,j] - p
			r = 0
			if (r > p) p = r
			# _WWWdata=tableRender(HTMLfill(p) HTMLspc(e) HTMLfill(q), f)
			_WWWdata=HTMLfill(p) HTMLspc(tableRender(e, f)) HTMLfill(q)
			_WWWalign="'right'"
		}
		else
		{
			_WWWdata=tableRender(e, f)
		}
		# if (substr(f,1,1)=="@") _WWWcolor=HTMLcolor(f)
		rv = "<" _WWWfmt " style=\"color: " _WWWcolor "\" "
		rv = rv "align=" _WWWalign ">" _WWWdata "</" _WWWfmt ">"
		print HTMLprefix rv
	}

	print HTMLprefix "</tr>"
	return -1
}

function WWWtail( 			a, b, c)
{
	print HTMLprefix "</table>"
	return 0
}


############### HTML PART ENDS HERE #####################################

function abort(s)
{
	print s > "/dev/stderr"
	exit -1
}

function tableHelp()
{
	print "Usage:"
	print "table -h" 
	print "table -R [-acdpW] [-l length] [-w width] [FILE ...]"
	print "table [-acdpW] [-l length] [-w width] [-t sep] [-i indent] [-H numhdrs] [-O format] [-I insert] [-A append] [FILE ...]"
	exit 0
}

function tableStart(H)
{
	tblRows = 0
	tblCols = 0
	tblHdrs = 0
	tblNfmt = 0
	delete tblFormats
	delete tblSize
	delete tblWidths
	delete tblEntries
	tblH = -1
	tblTH = -1
	if (H==1)
	{
		tblH=1
		tblTH = 0
	}
}

function awkSetRecord(r, s,			i, j, k, l, x)
{
	l = length(s)
	x = FS
	for (i=1; i<=l; i++)
	{
		k = substr(s, i, 1)
		if (index(s, k) > 0) FS = k
	}
	$0 = r
	FS = x
}

function tableAddFormat(		i)
{
	if (tblCols != 0 && tblCols != NF) abort("Table format mismatch!")

	tblCols = NF
	tblNfmt += 1
	for (i=1; i<=tblCols; i++)
	{
		tblFormats[tblNfmt, i] = $i
	}
}

function tableAddRecord(n,		a, i, j, k, l, s)
{
	# guesstimate (-g) goes here!
	tblRows += 1
	n = tblRows
	if (tblH==1) tblTH += 1
	for (i=1; i<=tblCols; i++)
	{
		if ($1 == "_")
		{
			tblEntries[n, i] = "_"
			continue
		}
		tblEntries[n, i] = $i
		l = length($i) + 0
		tblWidths[n, i] = l
		k = n
		if (k > tblNfmt) k = tblNfmt
		if (substr(tblFormats[k, i], 1, 1) == "n")
		{
			s = split($i, a, "[,.]")
			if (s > 2) abort("Strange number " $i "encountered")
			if (s < 2) s = split($i ".0", a, "[,.]")
			tblWidths[n, i, 1] = length(a[1]) + 0
			tblWidths[n, i, 2] = length(a[2]) + 0
			tblWidths[n, i] = l + 0
			if (tblWidths[n, i, 1] == 0 || tblWidths[n, i, 2] == 0) tblWidths[n, i] += 1
		}
	}
}

function tableSetTab(c)
{
	tblSavedTab = FS
	FS = c
}

function tableRestoreTab()
{
	FS = tblSavedTab
}

function tableSetIndent(n)
{
	tblIndent = substr("                                                                                  ", 1, n)
	tblOffset = n
}

function tableResetIndent()
{
	tblIndent = ""
	tblOffset = 0
}
	

function tableTH()
{
	tblH=0
}

function tableCalcWidth(n,			i, j, m1, m2, m)
{
	n = tblRows
	for (j=1; j<=tblCols; j++)
	{
		tblSize[j] = tblWidths[1,j] + 0
		if ((1, j, 1) in tblWidths)
		{
			tblSize[j,1] = tblWidths[1,j,1] + 0
			tblSize[j,2] = tblWidths[1,j,2] + 0
		}
	}
	for (i=2; i<=n; i++)
	{
		k = i
		if (k > tblNfmt) k = tblNfmt
		if (k < 1) abort("No format specified " tblNfmt)
		for (j=1; j<=tblCols; j++)
		{
			if (tblSize[j] < tblWidths[i,j]) tblSize[j] = tblWidths[i,j]
			if (substr(tblFormats[k,j], 1, 1) == "n")
			{
				if (tblSize[j,1] < tblWidths[i,j,1]+0) tblSize[j,1] = tblWidths[i,j,1] + 0
				if (tblSize[j,2] < tblWidths[i,j,2]+0) tblSize[j,2] = tblWidths[i,j,2] + 0
			}
		}
	}
	tblSpan = 0
	for (j=1; j<=tblCols; j++)
	{
		o=0; m=0
		if ((j, 1) in tblSize)
		{
			m = tblSize[j,1] + tblSize[j,2] + 1
			o = tblSize[j]
			if (m > o) tblSize[j] = m
			if (m < o) tblSize[j,1] = o - tblSize[j,2] - 1
		}
		tblSpan += tblSize[j]
	}
	tblSpan += 2 * (tblCols-1)
	if (tblBoxed) tblSpan += (tblCols-1) + 4
}

function tableFill(n)
{
	return substr("                                                                                  ", 1, n)
}

function tableHline(n, s, rv)
{
	s = "----------------------------------------------------------------------------------------------------------------------------------------------------------------"
	return substr(s, 1, tblSpan)
}

function tableOutput(i,				a, e, f, rv, j, k, l, m, n, o, p, q, r, w)
{
	rv = ""			# Indent?!?
	if (tblEntries[i,1] == "_")
	{
		tblHlinesEmitted += 1
		rv = rv tableHline()
		return rv
	}

	k = i - tblHlinesEmitted
	if (k > tblNfmt) k = tblNfmt
	for (j=1; j<=tblCols; j++)
	{
		if (j>1) rv = rv "  "
		w = tblSize[j]
		l = tblWidths[i,j]
		a = substr(tblFormats[k,j], 1, 1)
		f = substr(tblFormats[k,j], 2)
		e = tblEntries[i,j]
		if (a == "l")
		{
			rv = rv tableRender(e tableFill(w - l), f)
		}
		if (a == "r")
		{
			rv = rv tableRender(tableFill(w - l) e, f)
		}
		if (a == "c")
		{
			p = int((w - l) / 2)
			q = w - l - p
			rv = rv tableRender(tableFill(p) e tableFill(q), f)
		}
		if (a == "n")
		{
			p = tblSize[j,1] - tblWidths[i,j,1]
			q = tblSize[j,2] - tblWidths[i,j,2]
			r = w - tblWidths[i,j] - p
			r = 0
			if (r > p) p = r
			rv = rv tableRender(tableFill(p) e tableFill(q), f)
		}
	}
	return rv
}

function tableEnd(	s, i)
{
	# -A record	(if any)
	if (tblNapp)
	{
		for (i=1; i<=tblNapp; i++)
		{
			$0 = tblAppend[i]
			tableAddRecord()
		}
	}

	# calculate table dimensions and offset
	tableCalcWidth(tblRows)
	tblHlinesEmitted = 0
	tblHeaders = ""
	if (tblCentered)
	{
		i = int((ttyCols - tblSpan) / 2)
		if (i > 0) tableSetIndent(i)
	}

	# HTML STUFF GOES HERE (START)
	if (tblWWWMode) WWWhead();

	# output table rows
	for (i=1; i<=tblRows; i++)
	{
		# HTML STUFF GOES HERE! (DATA)
		if (tblWWWMode)
		{
			WWWrow(i)
			continue;
		}
		s = tblIndent tableOutput(i)
		if (i < tblTH)  tblHeaders = tblHeaders s "\n"
		if (i == tblTH) tblHeaders = tblHeaders s
		print s
		line += 1
		if (tblPaginate > 0)
		{
			if (line >= ttyLines - 1)
			{
				print "" # bp()
				print tblHeaders
				line = tblTH
			}
		}
	}

	# HTML STUFF GOES HERE (END)
	if (tblWWWMode) WWWtail();

	if (tblPaginate > 0 && tblWWWMode < 1)
	{
		# fill the rest of the page with empty lines
		for (; line < ttyLines; line++) print "" # bp() instead
	}
}

function tableRender(s, f,		rv, ES, m, seq)
{
	Esc = "\033["
	m = "m"

	seq = ""
	f = toupper(f)
	if (tblModeANSI)
	{
		if (f == "B") seq = Esc "1" m
		if (f == "I") seq = Esc "3" m
		if (f == "U") seq = Esc "4" m
		if (f == "O") seq = Esc "9" m
		if (substr(f, 1, 1) == "@")
		{
			seq = Esc "3" substr(f, 2, 1)
			if (substr(f, 3, 1) >= "0" && substr(f, 3, 1) <= "9")
			{
				seq = seq ";4" substr(f, 3, 1)
			}
			seq = seq m
		}
		return seq s Esc "0m"
	}
	if (tblModeDumb)
	{
		if (f == "B") 
		{
			rv = ""
			for (i=1; i<=length(s); i++) rv = rv substr(s,i,1) "" substr(s,i,1)
			return rv
		}
		if (f == "I" || f == "U")
		{
			rv = ""
			for (i=1; i<=length(s); i++) rv = rv "_" substr(s,i,1)
			return rv
		}
	}
	if (tblWWWMode) # [sic!]
	{
		seq = ""
		if (f == "B") seq = "b"
		if (f == "I") seq = "i"
		if (f == "U") seq = "u"
		if (f == "O") seq = "s"
		# if (substr(f, 1, 1) == "@") # handled elsewhere
		if (seq == "") return s
		else return "<" seq ">" s "</" seq ">"
	}
	return s
}

########################

BEGIN {
	helpString = "[+abcdeghpRsvW] [+l length] [+w width] [+t fieldsep] [+i indent] [+F format] [+I line] [+A line] [+H headers]"
	tblRoffMode = 0
	HTMLcolorList[0] = "black"
	HTMLcolorList[1] = "red"
	HTMLcolorList[2] = "green"
	HTMLcolorList[3] = "yellow"
	HTMLcolorList[4] = "blue"
	HTMLcolorList[5] = "magenta"
	HTMLcolorList[6] = "cyan"
	HTMLcolorList[7] = "white"
	for (i in ARGV) if (substr(ARGV[i],1,1) == "-" || substr(ARGV[i],1,1) == "+") if (index("R",substr(ARGV[i],2,1)) > 0) tblRoffMode=1
	for (i in ARGV) if (substr(ARGV[i],1,1) == "-" || substr(ARGV[i],1,1) == "+") if (index("W",substr(ARGV[i],2,1)) > 0) tblWWWMode=1
	
	# tty sizes
	ttyLines = 24
	ttyCols = 80
	if ("LINES" in ENVIRON) ttyLines = ENVIRON["LINES"] + 0
	if ("COLUMNS" in ENVIRON) ttyCols = ENVIRON["COLUMNS"] + 0
	for (i in ARGV) if (ARGV[i] == "-l" || ARGV[i] == "+l") {ttyLines = ARGV[i+1] + 0; delete ARGV[i]; delete ARGV[i+1]}
	for (i in ARGV) if (ARGV[i] == "-w" || ARGV[i] == "+w") {ttyCols = ARGV[i+1] + 0; delete ARGV[i+1]; delete ARGV[i]}

	# for (i in ARGV) if (ARGV[i] == "-o" || ARGV[i] == "+o") {tblOutfile = ARGV[i+1] + 0; delete ARGV[i]; delete ARGV[i+1]}
	# for (i in ARGV) if (ARGV[i] == "-P" || ARGV[i] == "+P") {tblPrinter = ARGV[i+1] + 0; delete ARGV[i]; delete ARGV[i+1]}

	tblTabChar = "\t"
	for (i in ARGV) if (ARGV[i] == "-t" || ARGV[i] == "+t")
		{tblTabChar = ARGV[i+1]; delete ARGV[i]; delete ARGV[i+1]}
	for (i in ARGV) if (ARGV[i] == "-F" || ARGV[i] == "+F" || ARGV[i] == "-O" || ARGV[i] == "+O")
		{if (tblRoffMode == 0) awkSetRecord(ARGV[i+1], "," tblTabChar); tableAddFormat(); delete ARGV[i]; delete ARGV[i+1]}
	for (i in ARGV) if (ARGV[i] == "-I" || ARGV[i] == "+I")
		{if (tblRoffMode == 0) awkSetRecord(ARGV[i+1], tblTabChar); tableAddRecord(); delete ARGV[i]; delete ARGV[i+1]}
	for (i in ARGV) if (ARGV[i] == "-A" || ARGV[i] == "+A")
		{if (tblRoffMode == 0) tblAppend[++tblNapp] = ARGV[i+1]; delete ARGV[i]; delete ARGV[i+1]}
	for (i in ARGV) if (ARGV[i] == "-H" || ARGV[i] == "+H")
		{if (tblRoffMode == 0) tblTH = ARGV[i+1]; delete ARGV[i]; delete ARGV[i+1]}
	for (i in ARGV) if (ARGV[i] == "-i" || ARGV[i] == "+i")
		{if (tblRoffMode == 0) tableSetIndent(ARGV[i+1]); delete ARGV[i]; delete ARGV[i+1]}

	for (i in ARGV)
	{
		c = substr(ARGV[i], 1, 1)
		l = length(ARGV[i])
		if (c != "+" && c != "-")  continue;
		delme = 0
		for (j=2; j<=l; j++)
		{
			c = substr(ARGV[i], j, 1)
			if (c == "R") {tblRoffMode = 1; delme++}
			if (c == "W") {tblWWWMode = 1; delme++}
			if (c == "v") {tblViewMode = 1; delme++}
			if (c == "p") {tblPaginate = 1; delme++}
			if (c == "a") {tblModeANSI = 1; delme++}
			if (c == "d") {tblModeDumb = 1; delme++}
			# if (c == "s") {tblModeMore = 1; delme++}

			if (c == "g") {tblGuessFmt = 1; delme++}
			if (c == "c") {tblCentered = 1; delme++}
			if (c == "e") {tblExpanded = 1; delme++}
			if (c == "b") {tblEnclosed = 1; delme++}
			if (c == "h" || c == "?") {tblLongHelp = 1; delme++}
		}
		if (delme) delete ARGV[i];
	}
	if (tblLongHelp >= 1) tableHelp()

	if (tblRoffMode == 1)
	{
		tblGuessFmt = 0
		tblCentered = 0
		tblExpanded = 0
		tblEnclosed = 0

		tblTabChar = "\t"
	}

	# 
	if (tblRoffMode)
	{
		tblInTable = 0
	}
	else
	{
		tblInTable = 1
		tableSetTab(tblTabChar)
	}

	if (tblWWWMode)
	{
		tblModeANSI=0
		tblModeDumb=0
		tblModeWWW=1
	}
}
	
/^\.TS/ {
	delete tblOpt
	tblOpt["allbox"]	= 0
	tblOpt["nokeep"]	= 0
	tblOpt["nowarn"]	= 0

	tblOpt["box"]		= 1
	tblOpt["doublebox"]	= 1
	tblOpt["center"]	= 2
	tblOpt["expand"]	= 3

	tblOpt["linesize"]	= 0
	tblOpt["delim"]		= 0
	tblOpt["tab"]		= 0

	tblHasHeader = 0
	if ($2 == "H") tblHasHeader = 1

	tblBoxed = 0
	tblCentered = 0
	tblExpanded = 0
	tblTabChar = "\t"

	tableStart(tblHasHeader) ### XXX FIXME
	delete tblIntro
	tblOptEnd = 0
	tblIntCnt = 0

	for (;;)
	{
		getline
		tblIntCnt += 1
		tblIntro[tblIntCnt] = $0
		if (substr($0, length($0)) == ";") tblOptEnd = tblIntCnt
		if (substr($0, length($0)) == ".") break
		if ($1 == ".TE") abort("Invalid table at" FILENAME ":" NR)
	}
	if (tblOptEnd != 0)
	{
		for (i=1; i<=tblOptEnd; i++)
		{
			if (i<tblOptEnd) $0 = tblIntro[i]
			else $0 = substr(tblIntro[i], 1, length(tblIntro[i]) - 1)
			for (j=1; j<=NF; j++)
			{
				if ($j in tblOpt)
				{
					k = tblOpt[$j]
					if (k == 1) tblBoxed = 1
					if (k == 2) tblCentered = 1
					if (k == 3) tblExpanded = 1
					continue
				}
				if (substr($j, 1, 8) == "linesize") continue
				if (substr($j, 1, 5) == "delim") continue
				if (substr($j, 1, 3) == "tab") 
				{
					tblTabChar = substr($j, 5, 1)
					continue
				}
			}
		}
	}
	for (i=tblOptEnd+1; i<=tblIntCnt; i++)
	{
		if (i<tblIntCnt) $0 = tblIntro[i]
		else $0 = substr(tblIntro[i], 1, length(tblIntro[i]) - 1)
		tableAddFormat()
	}
	if (tblNfmt < 1) abort("No format specified for table at line " NR)

	tableSetTab(tblTabChar)
	# the Table Data follows
	tblInTable = 1
	next
}

/^\.TH/ {
	tableTH()
	next
}

/^\.TE/ {
	tableEnd()
	tblInTable = 0
	tableRestoreTab()
	tableResetIndent()
	tblPaginate = 0

	# reset everything
	tblNfmt=0
	tblH=0
	tblTH=0
	tblRows=0
	tblCols=0
	tblHlinesEmitted=0
	delete tblFormats
	delete tblEntries
	delete tblWidths
	delete tblSize
	next
}

{
	if (tblInTable == 0)
	{
		print
		next
	}
	tableAddRecord()
	next
}

END {
	if (tblRoffMode == 0) tableEnd()
}
