/******************************************************************************
Copyright (C) 2006 Matteo Lucarelli

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

#include "Fl_Synoptic.h"

Fl_Synoptic::Fl_Synoptic(int x,int y,int w,int h,flsMouse_cb *mouseCb,const void* pClient):Fl_Box(x,y,w,h,0)
{
	m_Img=NULL;
	m_MouseCb=mouseCb;
	m_pClient=pClient;
	fl_register_images();
	m_currGroup=NULL;
}

Fl_Synoptic::~Fl_Synoptic()
{
	if (m_Img) delete m_Img;
	
	struct item* currItem;
	while (itemList.moveLast()){
		currItem=((item*)(itemList.getCurrentPayload()));
		if (currItem->image) delete (currItem->image);
		if (currItem->text) delete (currItem->text);
		delete currItem;
	}
	struct group* currGroup;
	while (groupList.moveLast()){
		currGroup=((group*)(groupList.getCurrentPayload()));
		delete currGroup;
	}
}

//////////////////////// PROTECTED ///////////////////////////////////////////

Fl_Shared_Image *Fl_Synoptic::rotate_image(Fl_Shared_Image *img,int mode)
{
	int w_rot,h_rot,d;
	double dxx,dyy,dxy,dyx,sx,sy;
	
	int rh=(int)(0.707*img->h()+0.5);
	int rw=(int)(0.707*img->w()+0.5);
	
	switch (mode){	
		case 1:
			dxx=0.707; dxy=-0.707; dyx=0.707 ; dyy=0.707 ; 
			sx=rh; sy=0;
			w_rot=rw+rh;
			h_rot=w_rot;
			break;
		case 3:
			dxx=-0.707; dxy=-0.707; dyx=0.707 ; dyy=-0.707 ; 
			sx=rh+rw-1; sy=rh;		
			w_rot=rw+rh;
			h_rot=w_rot;
			break;
		case 5:
			dxx=-0.707; dxy=0.707; dyx=-0.707 ; dyy=-0.707 ; 
			sx=rw; sy=rw+rh-1;					
			w_rot=rw+rh;
			h_rot=w_rot;
			break;
		case 7:
			dxx=0.707; dxy=0.707; dyx=-0.707 ; dyy=+0.707 ; 
			sx=0; sy=rw-1;
			w_rot=rw+rh;
			h_rot=w_rot;
			break;
		case 0:
			w_rot=img->w();
			h_rot=img->h();
			break;
		case 4:
			dxx=-1 ; dxy=0 ; dyx=0 ; dyy=-1 ; 
			w_rot=img->w();
			h_rot=img->h();
			sx=w_rot-1; sy=h_rot-1;								
			break;
		case 2:
			dxx=0 ; dxy=-1 ; dyx=1 ; dyy=0 ; 			
			w_rot=img->h();
			h_rot=img->w();
			sx=w_rot-1;
			sy=0;
			break;
		case 6:
			dxx=0 ; dxy=1 ; dyx=-1 ; dyy=0 ; 				
			w_rot=img->h();
			h_rot=img->w();
			sx=0;
			sy=h_rot-1;
			break;
	}
	
	
	Fl_Shared_Image *rot = (Fl_Shared_Image*)img->copy(w_rot,h_rot);
	if (mode==0) return rot;
	
	d=img->d();

	int cx,cy,o,rx,ry;
	const uchar* imgp=(const uchar*)img->data()[0];
	uchar* rotp=(uchar *)rot->data()[0];
	
	memset(rotp,0,(w_rot*h_rot*d));

	for (cx=0;cx<img->w();cx++){
		for (cy=0;cy<img->h();cy++){			
			rx=(int)(sx+cx*dxx+cy*dxy);
			ry=(int)(sy+cx*dyx+cy*dyy);			
			for (o=0;o<d;o++)
				*(rotp+(rx+ry*w_rot)*d+o)=*(imgp+(cx+cy*img->w())*d+o);
		}
	}
	
	if ((mode==2)||(mode==4)||(mode==6)) return rot;
	
	// a very rough smoothing
	uchar* pp;
	bool unt;
	for (cx=1;cx<w_rot-1;cx++){
		for (cy=1;cy<h_rot-1;cy++){
			unt=true;
			pp=rotp+(cx+cy*w_rot)*d;
			for (o=0;o<d;o++) if (*(pp+o)!=0) unt=false;
			if (unt) {
				for (o=0;o<d;o++){
					if ((*(pp+o+d)!=0)&&(*(pp+o-d)!=0))
						*(pp+o)=(uchar)(((double)*(pp+o-d)+(double)*(pp+o+d))/2);
				}
			}
		}	
	}	
	return rot;
}

void Fl_Synoptic::controlGroup(int ID)
{
	if (m_currGroup){
		m_currGroup->last=ID;
		if (m_currGroup->first==0) 
			m_currGroup->first=ID;
	}
}

int Fl_Synoptic::handle(int event) 
{
	if ((event==FL_PUSH)&&(m_MouseCb)){
		int it=getItem(Fl::event_x()-x(),Fl::event_y()-y());
		if (it!=0) m_MouseCb(it,m_pClient);
	}
}

void Fl_Synoptic::draw()
{
	fl_draw_box(FL_BORDER_BOX,x(),y(),w(),h(),m_bgColor);
	if (m_Img) m_Img->draw(x()+1,y()+1,w()-2,h()-2,m_xOffset,m_yOffset);
	fl_push_clip(x()+1,y()+1,w()-2,h()-2);
	
	itemList.moveFirst();
	struct item* currItem;
	if ( itemList.getCount()!=0 ) do{
	
		currItem=(item*)itemList.getCurrentPayload();
		if ((!currItem->visible)||((currItem->fcolor==-1)&&(currItem->bcolor==-1))) continue;
		
		switch (currItem->type) 
		{
		    case FLS_POINT:
		    	if (currItem->fcolor!=-1) fl_color(currItem->fcolor);
		    	if (currItem->bcolor!=-1) fl_color(currItem->bcolor);
			fl_point(currItem->x1+x(),currItem->y1+y());
			break;
		    case FLS_LINE:
		    	if (currItem->fcolor!=-1) fl_color(currItem->fcolor);
		    	if (currItem->bcolor!=-1) fl_color(currItem->bcolor);
		    	fl_line_style(currItem->lineStyle,currItem->lineWidth);
			fl_line(currItem->x1+x(),currItem->y1+y(),currItem->x2+x(),currItem->y2+y());
			break;
		    case FLS_RECT:
			if (currItem->fcolor!=(Fl_Color)-1){
				fl_color(currItem->fcolor); 				
				fl_rectf(currItem->x1+x()-currItem->x2/2,currItem->y1+y()-currItem->y2/2,
				         currItem->x2,currItem->y2);
			}
			if (currItem->bcolor!=(Fl_Color)-1){
				fl_color(currItem->bcolor);
				fl_line_style(currItem->lineStyle,currItem->lineWidth);
				fl_rect(currItem->x1+x()-currItem->x2/2,currItem->y1+y()-currItem->y2/2,
				        currItem->x2,currItem->y2);
			}
			break;
		    case FLS_TRIANGLE:
			if (currItem->fcolor!=-1){
				fl_color(currItem->fcolor);
				fl_polygon(currItem->x1+x(),currItem->y1+y(),
				           currItem->x2+x(),currItem->y2+y(),
				           currItem->x3+x(),currItem->y3+y());
			}
			if (currItem->bcolor!=-1){
				fl_color(currItem->bcolor);
				fl_line_style(currItem->lineStyle,currItem->lineWidth);
				fl_loop(currItem->x1+x(),currItem->y1+y(),
				        currItem->x2+x(),currItem->y2+y(),
				        currItem->x3+x(),currItem->y3+y());
			}
			break;
		    case FLS_QUAD:
		    	fl_push_matrix();		    
		    	fl_rotate(90);

			if (currItem->fcolor!=-1){
				fl_color(currItem->fcolor); 
				fl_polygon(currItem->x1+x(),currItem->y1+y(),
				           currItem->x2+x(),currItem->y2+y(),
				           currItem->x3+x(),currItem->y3+y(),
				           currItem->x4+x(),currItem->y4+y()); 
			}
			if (currItem->bcolor!=-1){
				fl_color(currItem->bcolor);
				fl_line_style(currItem->lineStyle,currItem->lineWidth);
				fl_loop(currItem->x1+x(),currItem->y1+y(),
				        currItem->x2+x(),currItem->y2+y(),
				        currItem->x3+x(),currItem->y3+y(),
				        currItem->x4+x(),currItem->y4+y());	
			}
			fl_pop_matrix();
			break;
		    case FLS_ELLIPSE:
			if (currItem->fcolor!=-1){
				fl_color(currItem->fcolor); 
				fl_pie(currItem->x1+x()-currItem->x2/2,currItem->y1+y()-currItem->y2/2,
				       currItem->x2,currItem->y2,0,360);
			}
			if (currItem->bcolor!=-1){
				fl_color(currItem->bcolor);
				fl_line_style(currItem->lineStyle,currItem->lineWidth);
				fl_arc(currItem->x1+x()-currItem->x2/2,currItem->y1+y()-currItem->y2/2,
				       currItem->x2,currItem->y2,0,300);
			}
			break;
		    case FLS_IMAGE:
			currItem->image->draw(currItem->x1+x(),currItem->y1+y());
			break;
		    case FLS_TEXT:
		    	if (currItem->bcolor!=-1){
		    		fl_color(currItem->bcolor);
		    		fl_rectf(currItem->x1+x()-currItem->x2/2,currItem->y1+y()-currItem->y2/2,
				         currItem->x2,currItem->y2);

		    	}
		    	fl_color(currItem->fcolor);
		    	fl_font(currItem->x3,currItem->y3);
			fl_draw(currItem->text,currItem->x1+x()-currItem->x2/2,currItem->y1+y()-currItem->y2/2,
			        currItem->x2,currItem->y2,FL_ALIGN_CENTER);
			
			
			break;
		}
	}while (itemList.moveNext());
	fl_pop_clip();
	fl_line_style(0);
}

//////////////// BACKGROUND //////////////////////////////////////////////////

bool Fl_Synoptic::loadBackground(const char* file,int xOffset,int yOffset)
{
	setBgOffset(xOffset,yOffset);
	Fl_Shared_Image* si=Fl_Shared_Image::get(file);
	
	if (si){
		if (m_Img) delete m_Img;
		m_Img=si->copy();
		si->release();
		redraw();
		return true;
	}
	return false;
}

void Fl_Synoptic::setBgColor(Fl_Color col)
{
	m_bgColor=col;
}

void Fl_Synoptic::setBgOffset(int xOffset,int yOffset)
{
	m_xOffset=xOffset;
	m_yOffset=yOffset;
}	

//////////// ADD AND DELETE //////////////////////////////////////////////////

int Fl_Synoptic::addItem(int type,Fl_Color fcolor,Fl_Color bcolor,int lineWidth,int lineStyle,
                        bool visible,int x1,int y1,int x2,int y2,int x3,int y3,int x4,int y4)
{
	struct item *newItem=new item();	
	newItem->visible=visible;
	newItem->type=type;
	newItem->fcolor=fcolor;
	newItem->bcolor=bcolor;
	newItem->lineWidth=lineWidth;
	newItem->lineStyle=lineStyle;
	newItem->image=NULL;
	newItem->text=NULL;
	newItem->x1=x1;
	newItem->y1=y1;
	newItem->x2=x2;
	newItem->y2=y2;
	newItem->x3=x3;
	newItem->y3=y3;
	newItem->x4=x4;
	newItem->y4=y4;
	itemList.addNewLast(newItem);
	controlGroup(itemList.getCount());
	if (visible) redraw();
	return itemList.getCount();
}

int Fl_Synoptic::addImage(const char* file,int x,int y,bool visible,int rotate,int width,int height)
{	
	Fl_Shared_Image* si1=Fl_Shared_Image::get(file);
	if (!si1) return 0;

	if ((rotate<0)||(rotate>7)) rotate=0;
	Fl_Shared_Image *si2=rotate_image(si1,rotate);
	
	struct item *newItem=new item();
	newItem->visible=visible;
	newItem->type=FLS_IMAGE;
	newItem->fcolor=(Fl_Color)0;
	newItem->bcolor=(Fl_Color)0;
	if ((width!=0)||(height!=0)) newItem->image=si2->copy(width,height);
	else newItem->image=si2->copy();
	newItem->x1=x;
	newItem->y1=y;
	itemList.addNewLast(newItem);
	si1->release();
	si2->release();
	controlGroup(itemList.getCount());
	if (visible) redraw();
	return itemList.getCount();	
}

int Fl_Synoptic::addImageCopy(int ID,int x,int y,bool visible)
{
	if (!itemList.moveTo(ID)) return 0;
	struct item *it=(item*)(itemList.getCurrentPayload());
	if (it->type!=FLS_IMAGE) return 0;
	
	struct item *newItem=new item();
	newItem->visible=visible;
	newItem->type=FLS_IMAGE;
	newItem->fcolor=(Fl_Color)0;
	newItem->bcolor=(Fl_Color)0;
	newItem->image=it->image;
	newItem->x1=x;
	newItem->y1=y;
	itemList.addNewLast(newItem);
	controlGroup(itemList.getCount());
	if (visible) redraw();
	return itemList.getCount();	
}

int Fl_Synoptic::addText(const char* text,int x,int y,int face,int size,Fl_Color color,Fl_Color lcolor,int lw,int lh,bool visible)
{
	struct item *newItem=new item();
	newItem->text = new char[strlen(text)];
	strcpy(newItem->text,text);
	newItem->visible=visible;
	newItem->type=FLS_TEXT;
	newItem->fcolor=color;
	newItem->bcolor=lcolor;
	newItem->x1=x;
	newItem->y1=y;
	
	if ((lw==-1)||(lh==-1)){
		int tw,th;
		fl_font(face,size);
		fl_measure(text,tw,th);
		if (lw==-1) lw=tw;	
		if (lh==-1) lh=th;
	}
	newItem->x2=lw;
	newItem->y2=lh;
	newItem->x3=face;
	newItem->y3=size;
	itemList.addNewLast(newItem);
	controlGroup(itemList.getCount());
	if (visible) redraw();
	return itemList.getCount();	
}
	
bool Fl_Synoptic::delItem(int ID)
{
	if (!itemList.moveTo(ID)) return false;
	struct item *it=(item*)(itemList.getCurrentPayload());
	bool visible=it->visible;
	if (it->image) delete (it->image);
	if (it->text) delete (it->text);
	delete (it);
	itemList.delCurrent();
	if (visible) redraw();
	return true;
}

///////////// GET - SET FUNCTIONS ////////////////////////////////////////////

bool Fl_Synoptic::moveItem(int ID,int x1,int y1,int x2,int y2,int x3,int y3,int x4,int y4)
{
	if (!itemList.moveTo(ID)) return false;
	struct item *it=(item*)(itemList.getCurrentPayload());
	
	if (x1!=-1) it->x1=x1;
	if (y1!=-1) it->y1=y1;	
	if (x2!=-1) it->x2=x2;
	if (y2!=-1) it->y2=y2;	
	if (x3!=-1) it->x3=x3;
	if (y3!=-1) it->y3=y3;	
	if (x4!=-1) it->x4=x4;
	if (y4!=-1) it->y4=y4;	
	
	if (it->visible) redraw();
	return true;
}

bool Fl_Synoptic::getItemVisible(int ID)
{
	if (!itemList.moveTo(ID)) return false;
	return ((item*)(itemList.getCurrentPayload()))->visible;
}


bool Fl_Synoptic::setItemVisible(int ID,bool visible)
{
	if (!itemList.moveTo(ID)) return false;
	struct item *it=(item*)(itemList.getCurrentPayload());
	if (it->visible!=visible){
		it->visible=visible;	
		redraw();
	}
	return true;
}
	
bool Fl_Synoptic::setItemFillColor(int ID,Fl_Color fcolor)
{
	if (!itemList.moveTo(ID)) return false;
	struct item *it=(item*)(itemList.getCurrentPayload());
	it->fcolor=fcolor;	
	if (it->visible) redraw();
	return true;
}

bool Fl_Synoptic::setItemBorderColor(int ID,Fl_Color bcolor)
{
	if (!itemList.moveTo(ID)) return false;
	struct item *it=(item*)(itemList.getCurrentPayload());
	it->bcolor=bcolor;	
	if (it->visible) redraw();
	return true;
}

bool Fl_Synoptic::setItemLineWidth(int ID,int lineWidth)
{
	if (!itemList.moveTo(ID)) return false;
	struct item *it=(item*)(itemList.getCurrentPayload());
	it->lineWidth=lineWidth;	
	if (it->visible) redraw();
	return true;
}

bool Fl_Synoptic::setItemLineStyle(int ID,int lineStyle)
{
	if (!itemList.moveTo(ID)) return false;
	struct item *it=(item*)(itemList.getCurrentPayload());
	it->lineStyle=lineStyle;	
	if (it->visible) redraw();
	return true;
}

int Fl_Synoptic::getItem(int Px,int Py)
{
	if (itemList.getCount()==0) return 0;
	struct item *it;
	int Xmin,Xmax,Ymin,Ymax;
	itemList.moveLast();
	do{
		it=(item*)(itemList.getCurrentPayload());
		if ((!it->visible)||((it->fcolor==-1)&&(it->bcolor==-1))) continue;
		
		switch (it->type) {
		    case FLS_POINT:
		    	if ((it->x1==Px)&&(it->y1==Py))
		    		return itemList.getCurrentIndex();
		    	else continue;
			break;
		    case FLS_LINE:
		    	// should be better!
		    	Xmin=(it->x1<it->x2?it->x1:it->x2);
		    	Xmax=(it->x1>it->x2?it->x1:it->x2);
		    	Ymin=(it->y1<it->y2?it->y1:it->y2);
		    	Ymax=(it->y1>it->y2?it->y1:it->y2);    	
			break;
		    case FLS_RECT:
		    case FLS_ELLIPSE:
		    case FLS_TEXT:
			Xmin=it->x1-it->x2/2;
			Xmax=it->x1+it->x2/2;
			Ymin=it->y1-it->y2/2;
			Ymax=it->y1+it->y2/2;
			break;
		    case FLS_TRIANGLE:
		    	Xmin=(it->x1<it->x2?it->x1:it->x2);
		    	Xmin=(it->x3<Xmin?it->x3:Xmin);
		    	Xmax=(it->x1>it->x2?it->x1:it->x2);
		    	Xmax=(it->x3>Xmax?it->x3:Xmax);
		    	Ymin=(it->y1<it->y2?it->y1:it->y2);
		    	Ymin=(it->y3<Ymin?it->y3:Ymin);
		    	Ymax=(it->y1>it->y2?it->y1:it->y2);
		    	Ymax=(it->y3>Ymax?it->y3:Ymax);
			break;
		    case FLS_QUAD:
		    	Xmin=(it->x1<it->x2?it->x1:it->x2);
		    	Xmin=(it->x3<Xmin?it->x3:Xmin);
		    	Xmin=(it->x4<Xmin?it->x4:Xmin);
		    	Xmax=(it->x1>it->x2?it->x1:it->x2);
		    	Xmax=(it->x3>Xmax?it->x3:Xmax);
		    	Xmax=(it->x4>Xmax?it->x4:Xmax);
		    	Ymin=(it->y1<it->y2?it->y1:it->y2);
		    	Ymin=(it->y3<Ymin?it->y3:Ymin);
		    	Ymin=(it->y4<Ymin?it->y4:Ymin);
		    	Ymax=(it->y1>it->y2?it->y1:it->y2);
		    	Ymax=(it->y3>Ymax?it->y3:Ymax);
		    	Ymax=(it->y4>Ymax?it->y4:Ymax);
			break;
		    case FLS_IMAGE:
			Xmin=it->x1;
			Xmax=it->x1+it->image->w();
			Ymin=it->y1;
			Ymax=it->y1+it->image->h();
			break;
		}
		if ((Px>=Xmin)&&(Px<=Xmax)&&(Py>=Ymin)&&(Py<=Ymax))
			return itemList.getCurrentIndex();
	}while (itemList.movePrev());
	return 0;
}

/////////////// GROUPS ///////////////////////////////////////////////////////

int Fl_Synoptic::beginGroup()
{
	m_currGroup=new group();
	m_currGroup->first=0;
	m_currGroup->last=0;
	groupList.addNewLast(m_currGroup);
	return groupList.getCount();
}

int Fl_Synoptic::endGroup()
{
	m_currGroup=NULL;
	return groupList.getCount();
}

bool Fl_Synoptic::setGroupVisible(int ID,bool visible)
{
	if (!groupList.moveTo(ID)) return false;
	struct group *gp=(group*)(groupList.getCurrentPayload());
	for(int i=gp->first;i<=gp->last;i++){
		if (!itemList.moveTo(i)) continue;
		((item*)(itemList.getCurrentPayload()))->visible=visible;
	}
	redraw();
	return true;
}

bool Fl_Synoptic::setGroupFillColor(int ID,Fl_Color fcolor)
{
	if (!groupList.moveTo(ID)) return false;
	struct group *gp=(group*)(groupList.getCurrentPayload());
	for(int i=gp->first;i<=gp->last;i++){
		if (!itemList.moveTo(i)) continue;
		((item*)(itemList.getCurrentPayload()))->fcolor=fcolor;
	}
	redraw();
	return true;
}

bool Fl_Synoptic::setGroupBorderColor(int ID,Fl_Color bcolor)
{
	if (!groupList.moveTo(ID)) return false;
	struct group *gp=(group*)(groupList.getCurrentPayload());
	for(int i=gp->first;i<=gp->last;i++){
		if (!itemList.moveTo(i)) continue;
		((item*)(itemList.getCurrentPayload()))->bcolor=bcolor;
	}
	redraw();
	return true;
}

bool Fl_Synoptic::setGroupLineWidth(int ID,int lineWidth)
{
	if (!groupList.moveTo(ID)) return false;
	struct group *gp=(group*)(groupList.getCurrentPayload());
	for(int i=gp->first;i<=gp->last;i++){
		if (!itemList.moveTo(i)) continue;
		((item*)(itemList.getCurrentPayload()))->lineWidth=lineWidth;
	}
	redraw();
	return true;
}

bool Fl_Synoptic::setGroupLineStyle(int ID,int lineStyle)
{
	if (!groupList.moveTo(ID)) return false;
	struct group *gp=(group*)(groupList.getCurrentPayload());
	for(int i=gp->first;i<=gp->last;i++){
		if (!itemList.moveTo(i)) continue;
		((item*)(itemList.getCurrentPayload()))->lineStyle=lineStyle;
	}
	redraw();
	return true;
}

/////////////// LAYERS ///////////////////////////////////////////////////////

bool Fl_Synoptic::moveFront(int ID)
{
	if (!itemList.moveTo(ID)) return false;
	if (ID==itemList.getCount()) return false;
	
	struct item *it=(item*)(itemList.getCurrentPayload());
	itemList.delCurrent();
	itemList.moveTo(ID);
	itemList.addNewAfterCurrent(it);
	redraw();
	return true;
}

bool Fl_Synoptic::moveBack(int ID)
{
	if (!itemList.moveTo(ID)) return false;
	if (ID==1) return false;
	
	struct item *it=(item*)(itemList.getCurrentPayload());
	itemList.delCurrent();
	itemList.moveTo(ID-1);
	itemList.addNewBeforeCurrent(it);
	redraw();
	return true;
}