/*
    PIP - Platform Independent Primitives
    Basic PIScreen tile
    Copyright (C) 2015  Ivan Pelipenko peri4ko@gmail.com

    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 3 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, see <http://www.gnu.org/licenses/>.
*/

#include "piscreentile.h"
#include "piscreendrawer.h"


/** \class PIScreenTile
 * \brief Console output class
 * \details
 * \section PIScreen_sec0 Synopsis
 * This class provides output to console with automatic alignment and update.
 * It supports tabs, keyboard listening, formats and colors.
 * 
 * \section PIScreen_sec1 Layout
 * %PIScreen works with variable pointers. You should add your variables with
 * functions \a addVariable() which receives label name, pointer to variable
 * and optional column and format. Columns count is dynamically increased if
 * new column used. E.g. if you add variable to empty tab to column 3, columns
 * count will be increased to 3, but two firsts columns will be empty. Each column
 * filled from top to bottom, but you can add just string with function
 * \a addString() or add empty line with function \a addEmptyLine(). Layout scheme:
 * \image html piconsole_layout.png
 * 
 * \section PIScreen_sec2 Keyboard usage
 * %PIScreen should to be single in application. %PIScreen aggregate PIKbdListener
 * which grab keyboard and automatic switch tabs by theirs bind keys. If there is no
 * tab binded to pressed key external function "slot" will be called
 * 
 **/

using namespace PIScreenTypes;


PIScreenTile::PIScreenTile(const PIString & n, Direction d, SizePolicy p): PIObject(n) {
	direction = d;
	size_policy = p;
	focus_flags = 0;
	screen = 0;
	minimumWidth = minimumHeight = x = y = width = height = pw = ph = 0;
	maximumWidth = maximumHeight = 65535;
	marginLeft = marginRight = marginTop = marginBottom = spacing = 0;
	parent = 0;
	back_symbol = ' ';
	visible = true;
	has_focus = false;
}


PIScreenTile::~PIScreenTile() {
	//piCout << this << "~";
	if (screen)
		screen->tileRemovedInternal(this);
	setScreen(0);
	deleteChildren();
	if (!parent) return;
	parent->tiles.removeOne(this);
}


void PIScreenTile::addTile(PIScreenTile * t) {
	if (t == 0) return;
	if (tiles.contains(t)) return;
	tiles << t;
	t->parent = this;
	t->setScreen(screen);
}


void PIScreenTile::takeTile(PIScreenTile * t) {
	if (!tiles.contains(t)) return;
	tiles.removeOne(t);
	t->parent = 0;
	t->setScreen(0);
}


void PIScreenTile::removeTile(PIScreenTile * t) {
	if (!tiles.contains(t)) return;
	tiles.removeOne(t);
	t->parent = 0;
	t->setScreen(0);
	delete t;
}


PIVector<PIScreenTile * > PIScreenTile::children(bool only_visible) {
	PIVector<PIScreenTile * > ret;
	piForeach (PIScreenTile * t, tiles)
		if (t->visible || !only_visible)
			ret << t << t->children(only_visible);
	return ret;
}


void PIScreenTile::raiseEvent(TileEvent e) {
	if (!screen) return;
	screen->tileEventInternal(this, e);
}


void PIScreenTile::setScreen(PIScreenBase * s) {
	screen = s;
	piForeach (PIScreenTile * t, tiles)
		t->setScreen(s);
}


void PIScreenTile::deleteChildren() {
	//piCout << this << "deleteChildren";
	piForeach (PIScreenTile * t, tiles) {
		//piCout << this << "  child" << t;
		//t->deleteChildren();
		t->parent = 0;
		delete t;
	}
	tiles.clear();
}


void PIScreenTile::setFocus() {
	if (!screen || !focus_flags[CanHasFocus]) return;
	screen->tileSetFocusInternal(this);
}


void PIScreenTile::drawEventInternal(PIScreenDrawer * d) {
	if (!visible) {
		//d->clearRect(x, y, x + width, y + height);
		return;
	}
	d->fillRect(x, y, x + width, y + height, back_symbol, (Color)back_format.color_char, (Color)back_format.color_back, back_format.flags);
	drawEvent(d);
	piForeach (PIScreenTile * t, tiles)
		t->drawEventInternal(d);
}


void PIScreenTile::sizeHint(int & w, int & h) const {
	w = 0;
	h = 0;
	if (tiles.isEmpty()) return;
	int sl = spacing * (tiles.size_s() - 1);
	if (direction == Horizontal) w += sl;
	else h += sl;
	piForeachC (PIScreenTile * t, tiles) {
		if (!t->visible) continue;
		int cw(0), ch(0);
		t->sizeHint(cw, ch);
		cw = piClampi(cw, t->minimumWidth, t->maximumWidth);
		ch = piClampi(ch, t->minimumHeight, t->maximumHeight);
		if (direction == Horizontal) {
			w += cw; h = piMaxi(h, ch);
		} else {
			h += ch; w = piMaxi(w, cw);
		}
	}
	w += marginLeft + marginRight;
	h += marginTop + marginBottom;
}


void PIScreenTile::layout() {
	if (tiles.isEmpty() || !visible) return;
	int as(0), ts(0), ts2(0), ecnt(0), pcnt(0);
	ts = (direction == Horizontal) ? (width - marginLeft - marginRight) : (height - marginTop - marginBottom);
	ts2 = (direction != Horizontal) ? (width - marginLeft - marginRight) : (height - marginTop - marginBottom);
	ts -= spacing * (tiles.size_s() - 1);
	PIVector<int> hints(tiles.size_s());
	PIVector<float> asizes(tiles.size_s());
	for (int i = 0; i < tiles.size_s(); ++i) {
		PIScreenTile * t(tiles[i]);
		int cw(0), ch(0), cs(0);
		if (t->visible) {
			t->sizeHint(cw, ch);
			cw = piClampi(cw, t->minimumWidth, t->maximumWidth);
			ch = piClampi(ch, t->minimumHeight, t->maximumHeight);
			if (t->size_policy == Expanding) ++ecnt;
			if (t->size_policy == Preferred) ++pcnt;
			cs = (direction == Horizontal) ? cw : ch;
			as += cs;
		}
		hints[i] = cs;
		asizes[i] = 0.f;
	}
	if (as <= ts) {
		int acnt(0);
		SizePolicy pol;
		if (ecnt > 0) {
			acnt = ecnt;
			pol = Expanding;
		} else if (pcnt > 0) {
			acnt = pcnt;
			pol = Preferred;
		}
		if (acnt > 0) {
			float add_a = float(ts - as), add_s = add_a / acnt, add_da(0.);
			asizes.fill(add_s);
			PISet<int> max_tl;
			for (int i = 0; i < tiles.size_s(); ++i) {
				if (tiles[i]->size_policy == pol && tiles[i]->visible) {
					float maxs = (direction == Horizontal) ? tiles[i]->maximumWidth : tiles[i]->maximumHeight;
					if (hints[i] + asizes[i] > maxs) {
						max_tl << i;
						float pas = asizes[i];
						asizes[i] = maxs - hints[i];
						acnt--;
						if (acnt > 0) {
							pas = (pas - asizes[i]) / acnt;
							for (int j = 0; j < tiles.size_s(); ++j) {
								if (i == j) continue;
								if (max_tl[j]) continue;
								if (tiles[j]->size_policy == pol && tiles[j]->visible)
									asizes[j] += pas;
							}
						}
					}
				}
			}
			for (int i = 0; i < tiles.size_s(); ++i) {
				if (tiles[i]->size_policy == pol && tiles[i]->visible) {
					int a = piRound(asizes[i] + add_da);
					add_da += asizes[i] - a;
					hints[i] += a;
				}
			}
		}
	}
	int cx = x + marginLeft, cy = y + marginTop;
	for (int i = 0; i < tiles.size_s(); ++i) {
		PIScreenTile * t(tiles[i]);
		if (!t->visible) continue;
		t->x = cx;
		t->y = cy;
		if (direction == Horizontal) {
			t->width = hints[i];
			t->height = ts2;
			cx += hints[i] + spacing;
		} else {
			t->width = ts2;
			t->height = hints[i];
			cy += hints[i] + spacing;
		}
		if (t->pw != t->width || t->ph != t->height)
			t->resizeEvent(t->width, t->height);
		t->pw = t->width;
		t->ph = t->height;
		t->layout();
	}
}
