/*
	Based up Neutrino-GUI - Tuxbox-Project
	Copyright (C) 2001 by Steffen Hehn 'McClean'

	(C) 2008,2013,2014 by Thilo Graf
	(C) 2009,2010,2013 Stefan Seyfried

	License: GPL

	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 St, Fifth Floor,
	Boston, MA  02110-1301, USA.
 */

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#include <math.h>
#include <global.h>
#include <neutrino.h>

#include "cc_item_progressbar.h"
#define ITEMW 4
#define POINT 2

#define RED    0xFF0000
#define GREEN  0x00FF00
#define YELLOW 0xFFFF00

CProgressBar::CProgressBar(	const int x_pos, const int y_pos, const int w, const int h,
				fb_pixel_t color_frame, fb_pixel_t color_body, fb_pixel_t color_shadow,
				const fb_pixel_t active_col, const fb_pixel_t passive_col,
				const int r, const int g, const int b,
				CComponentsForm *parent)
{
	//CComponentsItem
	cc_item_type 	= CC_ITEMTYPE_PROGRESSBAR;

	//CComponents
	x 		= x_pos;
	y 		= y_pos;
	width		= w;
	height		= h;

	col_frame 	= color_frame;
	col_body	= color_body;
	col_shadow	= color_shadow;

	pb_red 		= r;
	pb_green 	= g;
	pb_yellow 	= b;
	pb_active_col	= active_col;
	pb_passive_col 	= passive_col;

	pb_design		= &g_settings.progressbar_design;
	pb_gradient		= &g_settings.progressbar_gradient;
	pb_type			= PB_REDLEFT;

	pb_last_width 		= -1;
	pb_value		= 0;
	pb_max_value		= 0;

	// init start positions x/y active bar
	pb_x 			= x + fr_thickness;
	pb_y 			= y + fr_thickness;
	pb_active_width 	= 0;
	pb_max_width		= width - 2*fr_thickness;
	pb_height		= 0;
	pb_start_x_passive 	= 0;
	pb_passive_width 	= width;
	initParent(parent);
}

//calculate bar dimensions
void CProgressBar::initDimensions()
{
	//prevent stupid callers, can give invalid values like "-1"...
	if (pb_value < 0)
		pb_value = 0;
	if (pb_value > pb_max_value)
		pb_value = pb_max_value;

	//assign start positions x/y active bar
	//NOTE: real values are only reqiured, if we paint active/passive bar with own render methodes or not embedded cc-items
	pb_x = (cc_parent ? cc_xr : x) + fr_thickness;
	pb_y = (cc_parent ? cc_yr : y) + fr_thickness;

	// width for active bar with current value
	pb_active_width = max(0, pb_last_width);
	if (pb_max_value)
		pb_active_width = (width - 2*fr_thickness) * pb_value / pb_max_value;

	// max width active/passive bar
	pb_max_width = width - 2*fr_thickness;

	// max height of active/passive bar
	pb_height = height - 2*fr_thickness;

	pb_start_x_passive = pb_x + pb_active_width;

	pb_passive_width = pb_max_width - pb_active_width;

	// background = frame
	if ( col_frame == 0 )
		col_frame = pb_active_col;
}

class CProgressBarCache;
static std::vector<CProgressBarCache *> pbCache;

class CProgressBarCache
{
	private:
		// keys to lookup:
		int pb_height, pb_width;
		int pb_active_col, pb_passive_col;
		int design;
		bool pb_invert, gradient;
		int pb_red, pb_yellow, pb_green;

		int yoff;

		fb_pixel_t *active, *passive;

		static inline unsigned int make16color(__u32 rgb){return 0xFF000000 | rgb;};
		void paintBoxRel(fb_pixel_t *b, int x, int y, int dx, int dy, fb_pixel_t col);
		void applyGradient(fb_pixel_t *buf);
		void createBitmaps();

		CProgressBarCache(int _height, int _width, int _pb_active_col, int _pb_passive_col, int _design, bool _invert, bool _gradient, int _red, int _yellow, int _green)
			: pb_height(_height), pb_width(_width), pb_active_col(_pb_active_col), pb_passive_col(_pb_passive_col), design(_design), pb_invert(_invert), gradient(_gradient),
			  pb_red(_red), pb_yellow(_yellow), pb_green(_green), yoff(0)
		{
			if (pbCache.size() > 10)
				clear();
			createBitmaps();
		}
		void clear();
	public:
		void paint(int x, int y, int pb_active_width, int pb_passive_width);
		static CProgressBarCache *lookup(int _height, int _width, int _pb_active_col, int _pb_passive_col, int _design, bool _invert, bool _gradient, int _red, int _yellow, int _green);
};

void CProgressBarCache::clear()
{
	for (std::vector<CProgressBarCache *>::iterator it = pbCache.begin(); it != pbCache.end(); ++it) {
		if ((*it)->active)
			free((*it)->active);
		if ((*it)->passive)
			free((*it)->passive);
	}
	pbCache.clear();
}

CProgressBarCache *CProgressBarCache::lookup(int _height, int _width, int _pb_active_col, int _pb_passive_col, int _design, bool _invert, bool _gradient, int _red, int _yellow, int _green)
{
	// sanitize
	if (_design == CProgressBar::PB_MONO)
		_red = _yellow = _green = 0;

	// lookup
	std::vector<CProgressBarCache *>::iterator it = pbCache.begin();
	for (; it != pbCache.end() && ((*it)->pb_height != _height || (*it)->pb_width != _width ||
				       (*it)->pb_active_col != _pb_active_col || (*it)->pb_passive_col != _pb_passive_col ||
				       (*it)->design != _design || (*it)->pb_invert != _invert || (*it)->gradient != _gradient ||
				       (*it)->pb_red != _red || (*it)->pb_yellow != _yellow || (*it)->pb_green != _green); ++it);
	if (it != pbCache.end())
		return *it;

	CProgressBarCache *pbc = new CProgressBarCache(_height, _width, _pb_active_col, _pb_passive_col, _design, _invert, _gradient, _red, _yellow, _green);
	pbCache.push_back(pbc);
	return pbc;
}

void CProgressBarCache::paint(int x, int y, int pb_active_width, int pb_passive_width)
{
	y += yoff;
	static CFrameBuffer *frameBuffer = CFrameBuffer::getInstance();
	unsigned int stride = frameBuffer->getStride() / sizeof(fb_pixel_t);
	fb_pixel_t *p = frameBuffer->getFrameBufferPointer() + y * stride + x;
	int off = stride - pb_width;
	if (pb_active_width > pb_width)
		pb_active_width = pb_width;
	if (pb_active_width + pb_passive_width != pb_width)
		pb_passive_width = pb_width - pb_active_width;
	fb_pixel_t *ap = active;
	fb_pixel_t *pp = passive;
	for (int h = 0; h < pb_height; h++) {
		int w = 0;
		for (; w < pb_active_width; w++, p++, ap++)
			if (*ap)
				*p = *ap;
		pp += pb_active_width;
		for (; w < pb_width; w++, p++, pp++)
			if (*pp)
				*p = *pp;
		ap += pb_passive_width;
		p += off;
	}
}

void CProgressBarCache::paintBoxRel(fb_pixel_t *b, int x, int y, int dx, int dy, fb_pixel_t col)
{
	if (x < 0) {
		dx -= x;
		x = 0;
	}
	if (y < 0) {
		dy -= y;
		y = 0;
	}
	if (x + dx > pb_width)
		dx = pb_width - x;
	if (y + dy > pb_height)
		dy = pb_height - y;
	if (dx < 1 || dy < 1)
		return;
	b += pb_width * y + x;
	fb_pixel_t *e = b + pb_width * (dy - 1) + dx;
	int off = pb_width - dx;
	while (b < e) {
		fb_pixel_t *ex = b + dx;
		while (b < ex)
			*b++ = col;
		b += off;
	}
}

void CProgressBarCache::createBitmaps()
{
	active = (fb_pixel_t *) calloc(1, pb_width * pb_height * sizeof(fb_pixel_t));
	if (!active)
		return;
	passive = (fb_pixel_t *) calloc(1, pb_width * pb_height * sizeof(fb_pixel_t));
	if (!passive) {
		free(active);
		return;
	}

	int itemw = ITEMW, itemh = ITEMW, pointx = POINT, pointy = POINT;
	switch (design){
		default:
		case CProgressBar::PB_MONO:	// monochrome
			paintBoxRel(active,  0, 0, pb_width, pb_height, pb_active_col );
			paintBoxRel(passive, 0, 0, pb_width, pb_height, pb_passive_col);
			if (gradient) {
				applyGradient(active);
				applyGradient(passive);
			}
			return;
		case CProgressBar::PB_MATRIX: // ::::: matrix
			break;
		case CProgressBar::PB_LINES_V: // ||||| vert. lines
			itemh = pb_height;
			pointy = pb_height;
			break;
		case CProgressBar::PB_LINES_H: // ===== horiz. lines
			itemw = POINT;
			break;
		case CProgressBar::PB_COLOR: // filled color
			itemw = 1;
			itemh = pb_height;
			pointy = pb_height;
			break;
	}

	const int spc = itemh - pointy;			/* space between horizontal lines / points */
	int hcnt = (pb_height + spc) / itemh;		/* how many POINTs is the bar high */
	yoff = (pb_height + spc - itemh * hcnt) / 2;

	int i = 0;

	int sh_x = 0;
	/* red, yellow, green are given in percent */
	int rd = pb_red    * pb_width / (100 * itemw);	/* how many POINTs red */
	int yw = pb_yellow * pb_width / (100 * itemw);	/* how many POINTs yellow */
	int gn = pb_green  * pb_width / (100 * itemw);	/* how many POINTs green */

	// fixup rounding errors
	while ((rd + yw + gn) * itemw < pb_width) {
		if (gn)
			gn++;
		if (yw && ((rd + yw + gn) * itemw < pb_width))
			yw++;
		if (rd && ((rd + yw + gn) * itemw < pb_width))
			rd++;
	}

	yw += rd;
	gn += yw;

	uint32_t rgb, diff = 0;
	int step, b = 0;

	for (i = 0; i < rd; i++, sh_x += itemw) {
		diff = i * 255 / rd;
		rgb = RED + (diff << 8); // adding green
		fb_pixel_t color = make16color(rgb);
		int sh_y = 0;
		for (int j = 0; j < hcnt; j++, sh_y += itemh)
			paintBoxRel(active, sh_x, sh_y, pointx, pointy, color);
	}
	step = yw - rd - 1;
	if (step < 1)
		step = 1;
	for (; i < yw; i++, sh_x += itemw) {
		diff = b++ * 255 / step / 2;
		rgb = YELLOW - (diff << 16); // removing red
		fb_pixel_t color = make16color(rgb);
		int sh_y = 0;
		for (int j = 0; j < hcnt; j++, sh_y += itemh)
			paintBoxRel(active, sh_x, sh_y, pointx, pointy, color);
	}
	int off = diff;
	b = 0;
	step = gn - yw - 1;
	if (step < 1)
		step = 1;
	for (; i < gn; i++, sh_x += itemw) {
		diff = b++ * 255 / step / 2 + off;
		rgb = YELLOW - (diff << 16); // removing red
		fb_pixel_t color = make16color(rgb);
		int sh_y = 0;
		for (int j = 0; j < hcnt; j++, sh_y += itemh)
			paintBoxRel(active, sh_x, sh_y, pointx, pointy, color);
	}
	if (pb_invert) {
		for (int l = 0; l < pb_height; l++) {
			fb_pixel_t *as = active + l * pb_width;
			fb_pixel_t *ae = as + pb_width - 1;
			for (; as < ae; as++, ae--) {
				fb_pixel_t t = *as;
				*as = *ae;
				*ae = t;
			}
		}
	}

	if (gradient)
		applyGradient(active);

	fb_pixel_t *a = active, *p = passive;
	fb_pixel_t *end = a + pb_width * pb_height;
	for (; a < end; a++, p++) {
		fb_pixel_t q = *a;
		unsigned int gray = ((q & 0xff) + ((q >> 8) & 0xff) + ((q >> 16) & 0xff)) / 3;
		q >>= 24;
		q <<= 8;
		q |= gray;
		q <<= 8;
		q |= gray;
		q <<= 8;
		q |= gray;
		*p = q;
	}
}

void CProgressBarCache::applyGradient(fb_pixel_t *b)
{
	for (int y = 0; y < pb_height; y++) {
		int _o = y * pb_width;
		fb_pixel_t last_old = 0;
		fb_pixel_t last_new = 0;
		for (int _x = pb_width - 1; _x > -1; _x--) {
			fb_pixel_t &v = *(b + _o + _x);
			if (v != last_old) {
				last_old = v;
				double s = sin((y + .5) * M_PI / pb_height) * .8 + .2;
				float fr = float(((last_old >> 16) & 0xff) * s + 0.5);
				float fg = float(((last_old >>  8) & 0xff) * s + 0.5);
				float fb = float(((last_old      ) & 0xff) * s + 0.5);
				last_new = (last_old & 0xFF000000)
					| ((unsigned int)fr << 16)
					| ((unsigned int)fg <<  8)
					| ((unsigned int)fb      );
			}
			v = last_new;
		}
	}
}

void CProgressBar::paintProgress(bool do_save_bg)
{
	if (*pb_design == PB_OFF) {
		paintInit(false);
		return;
	}
	if (pb_type == PB_TIMESCALE)
		setRgb(g_settings.progressbar_timescale_red, g_settings.progressbar_timescale_green, g_settings.progressbar_timescale_yellow);

	if (!pb_red && !pb_yellow && !pb_green)
		pb_green = 1;
	int sum = pb_red + pb_yellow + pb_green;
	pb_red *= 100;
	pb_yellow *= 100;
	pb_green *= 100;
	pb_red /= sum;
	pb_yellow /= sum;
	pb_green /= sum;

	if (*pb_gradient)
		setFrameThickness(0);

	initDimensions();

	//body
	if (pb_last_width == -1 && col_body != 0) /* first paint */
		paintInit(do_save_bg); 

	//progress
	bool pb_invert = (pb_type == PB_REDRIGHT) || ((pb_type == PB_TIMESCALE) && g_settings.progressbar_timescale_invert);

	if (cc_allow_paint){
		if (pb_active_width != pb_last_width) {
			CProgressBarCache *pbc = CProgressBarCache::lookup(pb_height, pb_max_width, pb_active_col, pb_passive_col, *pb_design, pb_invert, *pb_gradient, pb_red, pb_yellow, pb_green);
			if (pbc)
				pbc->paint(pb_x, pb_y, pb_active_width, pb_passive_width);
			is_painted = true;
		}
	}

	if (is_painted)
		pb_last_width = pb_active_width;
}


void CProgressBar::paint(bool do_save_bg)
{
  	paintProgress(do_save_bg);
}

void CProgressBar::setType(pb_type_t type)
{
	pb_type = type;
}
