#include "RichText.h"

namespace Upp {

RichTable::Format::Format()
{
	grid = 4;
	gridcolor = Black;
	frame = 10;
	framecolor = Black;
	before = after = lm = rm = 0;
	header = 0;
	keep = newpage = newhdrftr = false;
}

#include "RichText.h"

void RichTable::ExpandFrr(VectorMap<int, Rect>& frr, int pi, int l, int r, int t, int b)
{
	Rect& fr = frr.GetAdd(pi, Rect(INT_MAX, INT_MAX, INT_MIN, INT_MIN));
	fr.left = min(fr.left, l);
	fr.right = max(fr.right, r);
	fr.top = min(fr.top, t);
	fr.bottom = max(fr.bottom, b);
}

bool RichTable::RowPaint(PageDraw& pw, RichContext rc, const Layout& tab, bool header,
                          int i, int ny, Rect pg, Rect npg,
                          VectorMap<int, Rect>& frr,
                          PaintInfo& pi, int pd, bool sel) const
{
	const Array<RichCell>& row = cell[i];
	const PaintRow& pr = tab[i];
	if(pw.tracer)
		pw.tracer->TableRow(GetPageRect(pr.py), rc.py, i, *this);
	int gridln = LineZoom(pi.zoom, format.grid);
	int ff2ln = gridln - gridln / 2;
	Color gc = pi.ResolveInk(format.gridcolor);
	if(gridln == 0 && !IsNull(pi.showcodes)) {
		gridln = 1;
		gc = pi.showcodes;
	}
	PageY py = pr.py;
	py.page += pd;
	if(py.page != clayout.py.page) // We are on the next page already
		pg = npg;
	for(int j = 0; j < format.column.GetCount();) {
		const RichCell& cell = row[j];
		const PaintCell& pc = pr[j];
		if(pc.top) {
			PageY pyy = tab[min(ny - 1, i + cell.vspan)].pyy;
			pyy.page += pd; // move header line
			bool paint = pyy > pi.top;
			Rect xpg = pg; // on first page
			Rect nxpg = npg; // on next page
			int y;
			int ny = pi.zoom * pyy.y;
			y = pi.zoom * pr.gpy.y;
			nxpg.left = xpg.left = pi.zoom * pc.left;
			xpg.right = pi.zoom * pc.right;
			if(j) { // need to draw vertical grid line on the left
				nxpg.left = xpg.left = xpg.left - ff2ln; // move half of vertical grid width left
				if(py.page == pyy.page)
					pw.Page(py.page).DrawRect(xpg.left, y, gridln, ny - y, gc);
				else {
					pw.Page(py.page).DrawRect(xpg.left, y, gridln, pg.bottom - y, gc);
					for(int i = py.page + 1; i < pyy.page; i++)
						pw.Page(i).DrawRect(nxpg.left, npg.top, gridln, npg.bottom - npg.top, gc);
					pw.Page(pyy.page).DrawRect(nxpg.left, npg.top, gridln, ny - npg.top, gc);
				}
				nxpg.left = xpg.left = xpg.left + gridln;
			}
			if(j + cell.hspan < format.column.GetCount() - 1)
				xpg.right = pi.zoom * pc.right - ff2ln; // make place for the next grid line
			nxpg.right = xpg.right;
			if(!pr.first) { // Draw horizontal grid line
				if(paint)
					pw.Page(py.page).DrawRect(xpg.left, y, xpg.Width(), gridln, gc);
				y += gridln;
			}
			if(paint) {
				if(pw.tracer)
					pw.tracer->TableCell(rc.page, rc.py, i, j, *this, pyy);
				row[j].Paint(pw, pc.MakeRichContext(MakeRichContext(rc, py, header)), pyy, xpg, nxpg, y, ny, pi,
				             sel && j >= pi.cells.left && i >= pi.cells.top &&
				             j + cell.hspan <= pi.cells.right && i + cell.vspan <= pi.cells.bottom);
				if(pw.tracer)
					pw.tracer->EndTableCell(pyy);
			}
			if(pyy.page == py.page)
				ExpandFrr(frr, py.page, xpg.left, xpg.right, y, ny);
			else {
				ExpandFrr(frr, py.page, xpg.left, xpg.right, y, xpg.bottom);
				for(int i = py.page + 1; i < pyy.page; i++)
					ExpandFrr(frr, i, nxpg.left, nxpg.right, nxpg.top, nxpg.bottom);
				ExpandFrr(frr, pyy.page, nxpg.left, nxpg.right, nxpg.top, ny);
			}
			int l = cell.text.GetLength() + 1;
			pi.sell -= l;
			pi.selh -= l;
			pi.tablesel -= cell.text.GetTableCount();
		}
		j += cell.hspan + 1;
	}
	if(pw.tracer)
		pw.tracer->EndTableRow(rc.py);
	return rc.py >= pi.bottom;
}

void RichTable::Paint(PageDraw& pw, RichContext rc, const PaintInfo& _pi, bool baselevel) const
{
	if(pw.tracer)
		pw.tracer->Table(rc.page, rc.py, *this);
	const TabLayout& tab = Realize(rc);
	PaintInfo pi = _pi;
	int frameln = LineZoom(pi.zoom, format.frame);
	int gridln = LineZoom(pi.zoom, format.grid);
	Rect pg[2];
	Rect hpg(0, 0, 0, 0);
	RichContext hrc = rc;
	int hy = min(format.header, cell.GetCount());
	for(int pass = 0;; pass++) {
		Rect& pgr = pg[pass];
		pgr = pass ? tab.next_page : tab.first_page;
		pgr.left += format.lm;
		pgr.right -= format.rm;
		pgr.left = pi.zoom * pgr.left;
		pgr.right = pi.zoom * pgr.right;
		pgr.top = pi.zoom * pgr.top;
		pgr.bottom = pi.zoom * pgr.bottom;
		pgr.Deflate(LineZoom(pi.zoom, format.frame));
		if(pass)
			break;
		hpg = pgr;
		if(tab.hasheader) {
			hpg.bottom = pi.zoom * tab.header[hy - 1].pyy.y;
			pgr.top = hpg.bottom + LineZoom(pi.zoom, format.grid);
		}
		hrc.Page();
	}
	
	bool allsel = false;
	if(pi.sell < 0 && pi.selh >= 0) {
		pi.sell = pi.selh = 0;
		allsel = true;
	}
	bool sel = pi.tablesel == 0;
	int ny = cell.GetCount();

	if(format.newhdrftr && ny && baselevel) {
		Image img = RichTextImg::HdrFtr();
		Size isz = img.GetSize();
		pw.Page(tab[0].py.page).DrawImage(-7 - isz.cx, pi.zoom * tab[0].py.y, img, pi.showcodes);
	}

	VectorMap<int, Rect> frr; // page -> table rectangle
	for(int i = 0; i < ny; i++)
		if(RowPaint(pw, rc, tab, false, i, ny, pg[0], pg[1], frr, pi, 0, sel))
			break;

	Color gc = pi.ResolveInk(format.gridcolor);
	Color fc = pi.ResolveInk(format.framecolor);
	int fl = frameln;
	if(!IsNull(pi.showcodes)) {
		if(fl == 0 && !IsNull(pi.showcodes)) {
			fl = 1;
			fc = pi.showcodes;
		}
		if(gridln == 0) {
			gridln = 1;
			gc = pi.showcodes;
		}
	}
	for(int i = 0; i < frr.GetCount(); i++) { // add headers on subsequent pages and outer border
		pi.tablesel = 0;
		pi.sell = pi.selh = -1;
		int pgi = frr.GetKey(i);
		Rect r = frr[i];
		if(pgi > tab.py.page && tab.hasheader) // need to paint header here...
			for(int i = 0; i < hy; i++) {
				RowPaint(pw, rc, tab.header, true, i, hy, hpg, hpg, frr, pi, pgi, false);
				pw.Page(pgi).DrawRect(r.left, hpg.bottom, r.Width(), gridln, gc);
			}
		r = frr[i].Inflated(fl);
		Draw& w = pw.Page(pgi);
		if(!r.IsEmpty()) {
			DrawFatFrame(w, r, fc, fl);
			if(allsel)
				pi.DrawSelection(w, r.left, r.top, r.Width(), r.Height());
		}
	}
	if(pw.tracer)
		pw.tracer->EndTable(rc.py);
}

int RichTable::GetWidth(const RichStyles& st) const
{
	Size sz = GetSize();
	Buffer<int> col(sz.cx, 0);
	
	for(int x = 0; x < sz.cx; x++)
		for(int y = 0; y < sz.cy; y++) {
			const RichCell::Format& fmt = GetFormat(y, x);
			col[x] = max(col[x], Get(y, x).GetWidth(st)
			         + fmt.border.left + fmt.border.right + fmt.margin.left + fmt.margin.right);
		}
	
	int sum = 0;
	for(int i = 0; i < sz.cx; i++)
		sum += format.column[i]; // sum of column ratios

	int maxcx = 0;
	for(int i = 0; i < sz.cx; i++)
		maxcx = max(maxcx, (col[i] * sum + format.column[i] - 1) / format.column[i]);
	                                     //^^^^ round it up
	return maxcx + (sz.cx + 1) * format.grid + format.lm + format.rm;
}

PageY RichTable::GetHeight(RichContext rc) const
{
	PageY pyy = Realize(rc).pyy;
	pyy.y += format.frame;
	pyy.y += format.after;
	if(pyy.y > GetPageRect(pyy).bottom)
		NextPage(pyy);
	return pyy;
}

PageY RichTable::GetTop(RichContext rc) const
{
	if(cell.GetCount() == 0)
		return rc.py;
	PageY py = Realize(rc)[0].py;
	py.y -= format.frame;
	return py;
}

RichCaret RichTable::GetCaret(int pos, RichContext rc) const
{
	ASSERT(pos >= 0);
	int nx = format.column.GetCount();
	int ny = cell.GetCount();
	const TabLayout& tab = Realize(rc);
	for(int i = 0; i < ny; i++) {
		const PaintRow& pr = tab[i];
		PageY pyy;
		for(int j = 0; j < nx; j++) {
			if(ci[i][j].valid) {
				const RichCell& cl = cell[i][j];
				pyy = tab[min(ny - 1, i + cl.vspan)].pyy;
				int l = cl.text.GetLength() + 1;
				if(pos < l)
					return cl.GetCaret(pos, pr[j].MakeRichContext(MakeRichContext(rc, pr.py)), pyy);
				pos -= l;
			}
		}
	}
	NEVER();
	return RichCaret();
}

int  RichTable::GetPos(int x, PageY y, RichContext rc) const
{
	int nx = format.column.GetCount();
	int ny = cell.GetCount();
	const TabLayout& tab = Realize(rc);
	int pos = 0;
	for(int i = 0; i < ny; i++) {
		const PaintRow& pr = tab[i];
		rc.py = pr.py;
		for(int j = 0; j < nx; j++)
			if(ci[i][j].valid) {
				const RichCell& cl = cell[i][j];
				const PaintCell& pc = pr[j];
				PageY pyy = tab[min(ny - 1, i + cl.vspan)].pyy;
				if(y < pyy
				   && (j == 0 || x >= pc.page_left - format.grid)
				   && (j == nx - 1 || x < pc.page_right))
					return cl.GetPos(x, y, pr[j].MakeRichContext(MakeRichContext(rc, pr.py)), pyy) + pos;
				pos += cl.text.GetLength() + 1;
			}
	}
	return pos ? pos - 1 : 0;
}

RichHotPos  RichTable::GetHotPos(int x, PageY y, int tolerance, RichContext rc) const
{
	RichHotPos hp;
	hp.textleft = rc.page.left;
	hp.textcx = rc.page.Width();
	int nx = format.column.GetCount();
	int ny = cell.GetCount();
	const TabLayout& tab = Realize(rc);
	int left = tab.first_page.left;
	int right = tab.first_page.right;
	if(abs(x - left) <= tolerance) {
		hp.table = 0;
		hp.column = RICHHOT_LM;
		hp.delta = x - left;
		return hp;
	}
	if(abs(x - right) <= tolerance) {
		hp.table = 0;
		hp.column = RICHHOT_RM;
		hp.delta = x - right;
		return hp;
	}
	int ti = 0;
	for(int i = 0; i < ny; i++) {
		const PaintRow& pr = tab[i];
		for(int j = 0; j < nx; j++) {
			if(ci[i][j].valid) {
				const RichCell& cl = cell[i][j];
				const PaintCell& pc = pr[j];
				PageY pyy = tab[min(ny - 1, i + cl.vspan)].pyy;
				if(y < pyy) {
					if(j < nx - 1 && abs(x - pc.right) <= tolerance) {
						hp.table = 0;
						hp.column = j + cl.hspan;
						hp.delta = x - pc.right;
						hp.left = left;
						hp.cx = right - left;
						return hp;
					}
					if((j == 0 || x >= pc.left - format.grid) && (j == nx - 1 || x < pc.right)) {
						hp = cl.GetHotPos(x, y, tolerance, pr[j].MakeRichContext(rc), pyy);
						hp.table += ti;
						return hp;
					}
				}
				ti += cl.text.GetTableCount();
			}
		}
	}
	return RichHotPos();
}

int RichTable::GetVertMove(int pos, int gx, RichContext rc, int dir) const
{
	if(cell.GetCount() == 0)
		return -1;
	int nx = format.column.GetCount();
	int ny = cell.GetCount();
	const TabLayout& tab = Realize(rc);
	Point cp;
	if(pos >= 0)
		cp = FindCell(pos);
	else {
		cp.y = dir > 0 ? 0 : cell.GetCount() - 1;
		cp.x = 0;
		const PaintRow& pr = tab[cp.y];
		for(int j = 0; j < nx; j++)
			if(ci[cp.y][cp.x].valid && gx < pr[j].page_right) {
				cp.x = j;
				break;
			}
		pos = -1;
	}
	for(;;) {
		RichContext rc1 = tab[cp.y][cp.x].MakeRichContext(rc);
		const RichCell& c = cell[cp.y][cp.x];
		if(c.Reduce(rc1)) {
			int q = cell[cp.y][cp.x].text.GetVertMove(pos, gx, rc1, dir);
			if(q >= 0)
				return q + GetCellPos(cp);
		}
		pos = -1;
		cp.y += dir;
		if(cp.y < 0 || cp.y >= ny)
			return -1;
	}
}

void RichTable::GatherValPos(Vector<RichValPos>& f, RichContext rc, int pos, int type) const
{
	ASSERT(pos >= 0);
	int nx = format.column.GetCount();
	int ny = cell.GetCount();
	const TabLayout& tab = Realize(rc);
	for(int i = 0; i < ny; i++) {
		const PaintRow& pr = tab[i];
		rc.py = pr.py;
		for(int j = 0; j < nx; j++)
			if(ci[i][j].valid) {
				const RichCell& cl = cell[i][j];
				cl.GatherValPos(f, pr[j].MakeRichContext(rc), tab[min(ny - 1, i + cl.vspan)].pyy, pos, type);
				pos += cl.text.GetLength() + 1;
			}
	}
}

void RichTable::ApplyZoom(Zoom z, const RichStyles& ostyle, const RichStyles& zstyle)
{
	Invalidate();
	r_row = -1;
	int nx = format.column.GetCount();
	int ny = cell.GetCount();
	format.frame = LineZoom(z, format.frame);
	format.grid = LineZoom(z, format.grid);
	format.before *= z;
	format.lm *= z;
	format.rm *= z;
	format.after *= z;
	for(int i = 0; i < ny; i++)
		for(int j = 0; j < nx; j++)
			if(ci[i][j].valid) {
				RichCell& c = cell[i][j];
				c.format.border *= z;
				c.format.margin *= z;
				c.format.minheight *= z;
				c.text.ApplyZoom(z, ostyle, zstyle);
			}
}

}
