//
//  lcd_gui_display.c	  Part of GUI support library, Display handler
//  Copyright(C)2014-2017 Toyohiko Togashi tog001@nifty.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/>
//
//
//    Update history
//    ---------- ----- -------------------------------------------
//    2014.02.15 v0.0  First cording
//    2014.03.15 v0.2  Commit
//    2017.05.20 v0.2a Bug fix
//

#include <stddef.h>				// NULL
#include <stdlib.h>				// rand...
#include "lcd_gui_figure.h"		// Figure data
#include "lcd_gui_display.h"	// Public information and configuration
#include "lcd_draw.h"			// HW driver

// Inline function
#define MAX(a,b)	(((a) > (b)) ? (a) : (b))
#define MIN(a,b)	(((a) < (b)) ? (a) : (b))
#define ABS(a)		(((a) >  0 ) ? (a) : -(a))

#define FILL_TEXT
#define V01B
//
//	Rect segment
//
int lcd_guiDisplayRect(struct lcd_gui *seg, struct lcd_guiCoordinate *cardinalPoint){
	struct lcd_guiRect *p;
	int	f;

	f = 0;
	if (seg->flags.dirty) {
		p = (struct lcd_guiRect *)seg;
		lcd_drawRect(
				p->area.x + cardinalPoint->x,
				p->area.y + cardinalPoint->y,
				p->area.width,
				p->area.height,
				p->color);
		seg->flags.dirty = 0;
		f = 1;
	}
	return(f);
};

//
//	Single line segment
//
int lcd_guiDisplayLine(struct lcd_gui *seg, struct lcd_guiCoordinate *cardinalPoint){
	struct lcd_guiLine	*p;
	int	f;

	f = 0;
	if (seg->flags.dirty) {
		p = (struct lcd_guiLine *)seg;
		lcd_drawLine(
			p->startPoint.x + cardinalPoint->x,
			p->startPoint.y + cardinalPoint->y,
			p->endPoint.x   + cardinalPoint->x,
			p->endPoint.y   + cardinalPoint->y,
			p->lineWidth,
			p->color);
		seg->flags.dirty = 0;
		f = 1;
	}
	return(f);
};

//
//	Poly line segment
//
int lcd_guiDisplayPoly(struct lcd_gui *seg, struct lcd_guiCoordinate *cardinalPoint){
	int	f;

	f = 0;
	if (seg->flags.dirty) {
		struct lcd_guiPoly			*p;
		struct lcd_guiCoordinate	*c;
		int		n;
		int		m;

		p = (struct lcd_guiPoly *)seg;
		c = p->point;
		for(m = p->m; m > 0; --m) {
			for(n = p->n - 1; n > 0; --n) {
				lcd_drawLine(
					c->x     + cardinalPoint->x,
					c->y     + cardinalPoint->y,
					(c+1)->x + cardinalPoint->x,
					(c+1)->y + cardinalPoint->y,
					p->lineWidth,
					p->color);
				c++;
			}
			c++;
		}
		seg->flags.dirty = 0;
		f = 1;
	}
	return(f);
};

//
//	Arc line segment
//
int lcd_guiDisplayArc(struct lcd_gui *seg, struct lcd_guiCoordinate *cardinalPoint){
	int	f;

	f = 0;
	if (seg->flags.dirty) {
		struct lcd_guiArc			*p;

		p = (struct lcd_guiArc *)seg;
		lcd_drawArc(
			p->center.x + cardinalPoint->x,
			p->center.y + cardinalPoint->y,
			p->r,
			p->startRad,
			p->endRad,
			p->lineWidth,
			p->color);
		seg->flags.dirty = 0;
		f = 1;
	}
	return(f);
};

//
//	Character string segment
//
int lcd_guiDisplayStr(struct lcd_gui *seg, struct lcd_guiCoordinate *cardinalPoint){
	struct lcd_guiStr	*p;
	int	f;

	f = 0;
	if (seg->flags.dirty) {
		p = (struct lcd_guiStr *)seg;
		lcd_drawStr(
				p->point.x + cardinalPoint->x,
				p->point.y + cardinalPoint->y,
				p->color.face,
				p->color.back,
				p->str);
		seg->flags.dirty = 0;
		f = 1;
	}
	return(f);
};

//
//	Bit image segment
//
int lcd_guiDisplayImage(struct lcd_gui *seg, struct lcd_guiCoordinate *cardinalPoint){
	struct lcd_guiImage *p;
	int	f;

	f = 0;
	if (seg->flags.dirty) {
		p = (struct lcd_guiImage *)seg;
		lcd_drawImage(
				p->area.x + cardinalPoint->x,
				p->area.y + cardinalPoint->y,
				p->area.width,
				p->area.height,
				p->image);
		seg->flags.dirty = 0;
		f = 1;
	}
	return(f);
};

//
//	Text string segment
//
#ifdef DELTE							// DEL START 2017.05.20
int lcd_guiDisplayText(struct lcd_gui *seg, struct lcd_guiCoordinate *cardinalPoint){
	int	f;

	f = 0;
	if (seg->flags.dirty) {
		struct lcd_guiText	*p;
		struct lcd_guiArea	a;
		char					*str;
		int						maxLine;
		int						maxColumn;
		int						i;
		int						j;
		int						k;

		p         = (struct lcd_guiText *)seg;
		maxLine   = p->area.height / p->font->height;
		maxColumn = MIN(p->area.width / p->font->width, LCD_GUI_STRING_COLUMN_MAX);
		k   = 0;
		str = p->str;
		for(j = 0; j < maxLine; j++) {
			for(i = 0; i < maxColumn; i++) {
				if ((*str == '\0') || (*str == '\n')) {
					break;
				}
				str++;
			}
			k = MAX(k, i);
			if (*str == '\0') {
				break;
			}
		}
		maxLine   = MIN(maxLine, j + 1);
		maxColumn = k;

		a.x       = p->area.x + cardinalPoint->x;
		a.y       = p->area.y + cardinalPoint->y;
		a.width   = maxColumn * p->font->width;
		a.height  = maxLine * p->font->height;
		switch(p->attr.horizontalAlign){
		case LCD_GUI_TEXT_ALIGN_CENTER:
			a.x += ((p->area.width - a.width) / 2);
			break;
		case LCD_GUI_TEXT_ALIGN_RIGHT:
			a.x += (p->area.width - a.width);
			break;
		default:
			break;
		}
		switch(p->attr.varticalAlign){
		case LCD_GUI_TEXT_ALIGN_CENTER:
			a.y += ((p->area.height - a.height) / 2);
			break;
		case LCD_GUI_TEXT_ALIGN_DOWN:
			a.y += (p->area.height - a.height);
			break;
		default:
			break;
		}

#ifdef FILL_TEXT
		lcd_drawRect(
				p->area.x + cardinalPoint->x,
				p->area.y + cardinalPoint->y,
				p->area.width,
				p->area.height,
				p->color.back);
#endif
		str = p->str;
		for(; maxLine > 0; --maxLine) {
			char	buf[LCD_GUI_STRING_COLUMN_MAX+1];
			char	*c;
			int		i;

			c = buf;
			for(i = 0; i < maxColumn; i++) {
				if ((*str == '\0') || (*str == '\n')) {
					break;
				}
				*c++ = *str++;
			}
			*c = '\0';
			lcd_drawStr2(
					a.x,
					a.y,
					p->color.face,
					p->color.back,
					buf,
					p->font);
			if (*str == '\0') {
				break;
			}
			a.y += p->font->height;
		}
		seg->flags.dirty = 0;
		f = 1;
	}
	return(f);
};
#else									// DEL END - ADD START 2017.05.20
int lcd_guiDisplayText(struct lcd_gui *seg, struct lcd_guiCoordinate *cardinalPoint){
	int	f;

	f = 0;
	if (seg->flags.dirty) {
		struct lcd_guiText	*p;
		struct lcd_guiArea	a;
		char					*str;
		int						maxLine;
		int						maxColumn;
		int						i;
		int						j;
		int						k;

		p         = (struct lcd_guiText *)seg;
		maxLine   = p->area.height / p->font->height;
		maxColumn = MIN(p->area.width / p->font->width, LCD_GUI_STRING_COLUMN_MAX);
		k   = 0;
		str = p->str;
		for(j = 0; j < maxLine; j++) {
			for(i = 0; i < maxColumn; i++) {
				if ((*str == '\0') || (*str == '\n')) {
					break;
				}
				str++;
			}
			k = MAX(k, i);
			if (*str == '\0') {
				break;
			}
			if (*str == '\n') {
				str++;
			}
		}
		maxLine   = MIN(maxLine, j + 1);
		maxColumn = k;

		a.x       = p->area.x + cardinalPoint->x;
		a.y       = p->area.y + cardinalPoint->y;
		a.width   = maxColumn * p->font->width;
		a.height  = maxLine   * p->font->height;
		switch(p->attr.horizontalAlign){
		case LCD_GUI_TEXT_ALIGN_CENTER:
			a.x += ((p->area.width - a.width) / 2);
			break;
		case LCD_GUI_TEXT_ALIGN_RIGHT:
			a.x += (p->area.width - a.width);
			break;
		default:
			break;
		}
		switch(p->attr.varticalAlign){
		case LCD_GUI_TEXT_ALIGN_CENTER:
			a.y += ((p->area.height - a.height) / 2);
			break;
		case LCD_GUI_TEXT_ALIGN_DOWN:
			a.y += (p->area.height - a.height);
			break;
		default:
			break;
		}

#ifdef FILL_TEXT
		lcd_drawRect(
				p->area.x + cardinalPoint->x,
				p->area.y + cardinalPoint->y,
				p->area.width,
				p->area.height,
				p->color.back);
#endif
		str = p->str;
		for(; maxLine > 0; --maxLine) {
			char	buf[LCD_GUI_STRING_COLUMN_MAX+1];
			char	*c;
			int		i;

			c = buf;
			for(i = 0; i < maxColumn; i++) {
				if ((*str == '\0') || (*str == '\n')) {
					break;
				}
				*c++ = *str++;
			}
			*c = '\0';
			lcd_drawStr2(
					p->attr.horizontalAlign == LCD_GUI_TEXT_ALIGN_CENTER ? a.x + ((maxColumn - i) * p->font->width / 2) : a.x,
					a.y,
					p->color.face,
					p->color.back,
					buf,
					p->font);
			if (*str == '\0') {
				break;
			}
			if (*str == '\n') {
				str++;
			}
			a.y += p->font->height;
		}
		seg->flags.dirty = 0;
		f = 1;
	}
	return(f);
};
#endif									// ADD END 2017.05.20

//
//	Gage segment
//
int lcd_guiDisplayGage(struct lcd_gui *seg, struct lcd_guiCoordinate *cardinalPoint){
	int	f;

	f = 0;
	if (seg->flags.dirty) {
		struct lcd_guiGage *p;
		int						pos;
		int						dlt;
		float					value;

		p = (struct lcd_guiGage *)seg;
		value = MIN(p->value, 1.0);
		value = MAX(value, 0.0);
#ifdef V01B
		if (p->seg.flags.init || !(rand() & 0x1f)) {
			lcd_drawRect(
					p->area.x + cardinalPoint->x,
					p->area.y + cardinalPoint->y,
					p->area.width,
					p->area.height,
					p->color.back);
			p->seg.flags.init     = 0;
			p->internal.beforePos = 0;
		}
		if (p->attr.vartical) {
			pos = value * p->area.height + 0.5;
			dlt = pos - p->internal.beforePos;
			if (dlt > 0) {			// INC
				lcd_drawRect(
						p->area.x + cardinalPoint->x,
						p->area.y + cardinalPoint->y + p->area.height - pos,
						p->area.width,
						dlt,
						p->color.face);
			} else if (dlt < 0) {	// DEC
				dlt = -dlt;
				lcd_drawRect(
						p->area.x + cardinalPoint->x,
						p->area.y + cardinalPoint->y + p->area.height - p->internal.beforePos,
						p->area.width,
						dlt,
						p->color.back);
			}
		} else {
			pos = value * p->area.width + 0.5;
			dlt = pos - p->internal.beforePos;
			if (dlt > 0) {
				lcd_drawRect(
						p->area.x + cardinalPoint->x + p->internal.beforePos,
						p->area.y + cardinalPoint->y,
						dlt,
						p->area.height,
						p->color.face);
			} else if (dlt < 0) {
				dlt = -dlt;
				lcd_drawRect(
						p->area.x + cardinalPoint->x + pos,
						p->area.y + cardinalPoint->y,
						dlt,
						p->area.height,
						p->color.back);
			}
		}
		p->internal.beforePos = pos;
#else
		if (p->attr.vartical) {		// Vertical
			pos = value * p->area.height + 0.5;
			lcd_drawRect(
					p->area.x + cardinalPoint->x,
					p->area.y + cardinalPoint->y,
					p->area.width,
					p->area.height - pos,
					p->color.back);
			lcd_drawRect(
					p->area.x + cardinalPoint->x,
					p->area.y + cardinalPoint->y + p->area.height - pos,
					p->area.width,
					pos,
					p->color.face);
		} else {					// Horizontal
			pos = value * p->area.width + 0.5;
			lcd_drawRect(
					p->area.x + cardinalPoint->x,
					p->area.y + cardinalPoint->y,
					pos,
					p->area.height,
					p->color.face);
			lcd_drawRect(
					p->area.x + cardinalPoint->x + pos,
					p->area.y + cardinalPoint->y,
					p->area.width - pos,
					p->area.height,
					p->color.back);
		}
#endif
		seg->flags.dirty = 0;
		f = 1;
	}
	return(f);
};

//
//	Bar chart
//
int lcd_guiDisplayBarChart(struct lcd_gui *seg, struct lcd_guiCoordinate *cardinalPoint){
	int	f;

	f = 0;
	if (seg->flags.dirty) {
		struct lcd_guiBarChart *p;
		int				i;
		short			x;
		short			y;
		short			h;
		short			w;
		float			*v;
		unsigned short	*c;
		short			s;

		p = (struct lcd_guiBarChart *)seg;
		x = p->area.x + cardinalPoint->x;
		y = p->area.y + cardinalPoint->y;
		if (p->attr.vartical) {		// Vertical
//			v = p->value + (p->number-1);	// DEL 2017.05.20
//			c = p->color + (p->number-1);	// DEL 2017.05.20
			v = p->value;					// ADD 2017.05.20
			c = p->color;					// ADD 2017.05.20
			h = p->area.height;
			for(i = p->number; i > 0; --i) {
				s = *v * p->area.height + 0.5;
				h -= s;
				lcd_drawRect(x, y+h, p->area.width, s, *c);
//				--v;						// DEL 2017.05.20
//				--c;						// DEL 2017.05.20
				v++;						// ADD 2017.05.20
				c++;						// ADD 2017.05.20
			}
			lcd_drawRect(x, y, p->area.width, h, p->backColor);
		} else {					// Horizontal
			v = p->value;
			c = p->color;
			w = p->area.width;
			for(i = p->number; i > 0; --i) {
				s = *v * p->area.width + 0.5;
				lcd_drawRect(x, y, s, p->area.height, *c);
				x += s;
				w -= s;
				v++;
				c++;
			}
			lcd_drawRect(x, y, w, p->area.height, p->backColor);
		}
		seg->flags.dirty = 0;
		f = 1;
	}
	return(f);
};

//
// 7SEG LED segment
//
void disp7seg(unsigned char image, short x, short y, short w, short h, int width, unsigned fcolor, unsigned bcolor){
	const struct {
		short	sx;
		short	sy;
		short	ex;
		short	ey;
	} sengemtLayout[8] = {
			{15, 0, 34, 0},	// a
			{32,26, 36, 2},	// b
			{26,58, 30,32},	// c
			{ 4,62, 23,62},	// d
			{ 6,32,  0,58},	// e
			{12, 2,  7,26},	// f
			{12,30, 30,30},	// g
			{36,60, 36,62},	// .		40x64
	};
	int				j;
	unsigned 		color;

	for(j = 0; j < 8; j++) {
		if (image & 0x80) {
			color = fcolor;
		} else {
			color = bcolor;
		}
		lcd_drawLine(
				sengemtLayout[j].sx * w / 40 + x,
				sengemtLayout[j].sy * h / 64 + y,
				sengemtLayout[j].ex * w / 40 + x,
				sengemtLayout[j].ey * h / 64 + y,
				width, color);
		image <<= 1;
	}
	return;
}

int lcd_guiDisplay7seg(struct lcd_gui *seg, struct lcd_guiCoordinate *base){
	int	f;

	f = 0;
	if (seg->flags.dirty) {
		struct lcd_gui7seg *p;
		short			cw;
		short			ch;
		short			x;
		short			y;
		int				n;
		unsigned char	*im;

		p  = (struct lcd_gui7seg *)seg;
		cw = p->area.width / p->column;
		ch = p->area.height;
		x  = p->area.x + base->x;
		y  = p->area.y + base->y;
		for(n = p->column, im = p->pattern; n > 0; --n, im++) {
			disp7seg(*im, x, y, cw, ch, p->lineWidth, p->color.face, p->color.back);
			x += cw;
		}
		seg->flags.dirty = 0;
		f = 1;
	}
	return(f);
};

//
//	Display refresh handler
//
int lcd_guiDisplay(struct lcd_gui *seg, struct lcd_guiCoordinate *base){
	int	f;

	f = 0;
	for(; seg != NULL; seg = seg->next){
		if (seg->dispProg) {
			if (seg->dispProg(seg, base)) {
				f = 1;
			}
		}
	}
	return(f);
}

//
//	Display
//
int lcd_guiDisplayGroup(struct lcd_gui *seg, struct lcd_guiCoordinate *cardinalPoint){
	int	f;

	f = 0;
	if (!(seg->flags.hidden)) {
		struct lcd_guiGroup			*group;
		struct lcd_guiCoordinate	point;

		group   = (struct lcd_guiGroup *)seg;
		point.x = cardinalPoint->x + group->area.x;
		point.y = cardinalPoint->y + group->area.y;
		if (seg->flags.dirty) {
			struct lcd_gui	*p;

			seg->flags.dirty = 0;
			if (group->backColor) {
				lcd_drawRect(point.x, point.y, group->area.width, group->area.height, group->backColor);
			}
			for(p = group->top; p != NULL; p = p->next) {
				p->flags.dirty = 1;
			}
		}
//		if (lcd_guiDisplay(group->top, &point)) {
//			f = 1;
//		}
		for(seg = group->top; seg != NULL; seg = seg->next){
			if ((seg->dispProg != NULL) && !(seg->flags.hidden)) {
				if (seg->dispProg(seg, &point)) {
					f = 1;
				}
			}
		}

	}
	return(f);
}




