/*(LGPL)
---------------------------------------------------------------------------
	window.cpp - Generic Rendering Window
---------------------------------------------------------------------------
 * Copyright (C) 2001-2003, 2006, 2007 David Olofson
 *
 * This program is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation; either version 2.1 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
 * Lesser General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program; if not, write to the Free Software Foundation,
 * Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */

#include "logger.h"
#include "window.h"
#include "gfxengine.h"
#include "sofont.h"

#define	SELECT	if(selected != this) _select();

window_t *window_t::selected = NULL;

window_t::window_t()
{
	engine = NULL;
	surface = NULL;
	next = NULL;
	prev = NULL;
	phys_rect.x = phys_rect.y = 0;
	phys_rect.w = 320;
	phys_rect.h = 240;
	fgcolor = bgcolor = 0;
	selected = 0;
	xs = ys = 256;
	bg_bank = -1;
	bg_frame = -1;
	_visible = 1;
	_offscreen = 0;
}


window_t::~window_t()
{
	if(_offscreen && surface)
		SDL_FreeSurface(surface);
	if(selected == this)
		selected = NULL;
	unlink();
}


void window_t::init(gfxengine_t *e)
{
	link(e);
	xs = engine->xs;
	ys = engine->ys;
	if(_offscreen && surface)
	{
		SDL_FreeSurface(surface);
		surface = NULL;
	}
	_offscreen = 0;
	surface = engine->surface();
}


void window_t::visible(int vis)
{
	if(_offscreen)
		return;		// Cannot be visible!
	if(_visible == vis)
		return;
	_visible = vis;
	invalidate();
}


void window_t::link(gfxengine_t *e)
{
	unlink();
	engine = e;
	next = NULL;
	prev = engine->windows;
	if(prev)
	{
		while(prev->next)
			prev = prev->next;
		prev->next = this;
	}
	else
		engine->windows = this;
}


void window_t::unlink(void)
{
	if(engine)
	{
		if(engine->windows == this)
			engine->windows = next;
		if(engine->surface() == surface)
			surface = NULL;
	}
	if(next)
		next->prev = prev;
	if(prev)
		prev->next = next;
}


void window_t::place(int left, int top, int sizex, int sizey)
{
	int x2 = ((left + sizex) * xs + 128) >> 8;
	int y2 = ((top + sizey) * ys + 128) >> 8;
	phys_rect.x = (left * xs + 128) >> 8;
	phys_rect.y = (top * ys + 128) >> 8;
	phys_rect.w = x2 - phys_rect.x;
	phys_rect.h = y2 - phys_rect.y;
}


int window_t::offscreen()
{
	if(!engine)
		return -1;
	if(!engine->surface())
		return -1;
	if(_offscreen)
		return 0;	// Already offscreen!
	visible(0);
	_offscreen = 1;
	SDL_Surface *s = SDL_CreateRGBSurface(SDL_SWSURFACE,
			phys_rect.w, phys_rect.h,
			32, 0xff000000, 0x00ff0000,
			0x0000ff00, 0x000000ff);
	if(!s)
		return -1;
	surface = SDL_DisplayFormat(s);
	SDL_FreeSurface(s);
	if(!surface)
		return -1;
	return 0;
}


void window_t::select()
{
	if(engine)
		_select();
}


void window_t::_select()
{
	SDL_Rect r = phys_rect;
	selected = this;
	if(surface)
		SDL_SetClipRect(surface, &r);
}


void window_t::invalidate(SDL_Rect *r)
{
	if(!engine)
		return;
	if(!engine->surface())
		return;

	SELECT

	if(_offscreen)
	{
		if(!r)
		{
			SDL_Rect rr;
			rr.x = 0;
			rr.y = 0;
			rr.w = phys_rect.w;
			rr.h = phys_rect.h;
			phys_refresh(&rr);
			glSDL_Invalidate(surface, &rr);
		}
		else
		{
			phys_refresh(r);
			glSDL_Invalidate(surface, r);
		}
		return;
	}

	if(!r)
		engine->invalidate(&phys_rect, this);
	else
	{
		/* Translate to screen coordinates */
		SDL_Rect dr = *r;
		dr.x = (dr.x * xs + 128) >> 8;
		dr.y = (dr.y * ys + 128) >> 8;
		dr.w = (((dr.w + dr.x) * xs + 128) >> 8) - dr.x;
		dr.h = (((dr.h + dr.y) * ys + 128) >> 8) - dr.y;
		dr.x += phys_rect.x;
		dr.y += phys_rect.y;

		/* Clip to window (stolen from SDL_surface.c) */
		int Amin, Amax, Bmin, Bmax;

		/* Horizontal intersection */
		Amin = dr.x;
		Amax = Amin + dr.w;
		Bmin = phys_rect.x;
		Bmax = Bmin + phys_rect.w;
		if(Bmin > Amin)
			Amin = Bmin;
		dr.x = Amin;
		if(Bmax < Amax)
			Amax = Bmax;
		dr.w = Amax - Amin > 0 ? Amax - Amin : 0;

		/* Vertical intersection */
		Amin = dr.y;
		Amax = Amin + dr.h;
		Bmin = phys_rect.y;
		Bmax = Bmin + phys_rect.h;
		if(Bmin > Amin)
			Amin = Bmin;
		dr.y = Amin;
		if(Bmax < Amax)
			Amax = Bmax;
		dr.h = Amax - Amin > 0 ? Amax - Amin : 0;

		if(dr.w && dr.h)
			engine->invalidate(&dr, this);
	}
}


/*---------------------------------------------------------------
	Rendering API
---------------------------------------------------------------*/

Uint32 window_t::map_rgb(Uint8 r, Uint8 g, Uint8 b)
{
	if(!engine)
		return 0;

	if(surface)
		return SDL_MapRGB(surface->format, r, g, b);
	else
		return 0xffffff;
}

Uint32 window_t::map_rgb(Uint32 rgb)
{
	if(!engine)
		return 0;

	Uint8 r = (rgb >> 16) & 0xff;
	Uint8 g = (rgb >> 8) & 0xff;
	Uint8 b = rgb & 0xff;

	if(surface)
		return SDL_MapRGB(surface->format, r, g, b);
	else
		return 0xffffff;
}

void window_t::bgimage(int bank, int frame)
{
	bg_bank = bank;
	bg_frame = frame;
}


void window_t::colorkey(Uint32 color)
{
	if(!engine)
		return;
	if(!engine->surface())
		return;
	if(!_offscreen)
		return;
	SDL_SetColorKey(surface, SDL_SRCCOLORKEY, color);
}

void window_t::colorkey()
{
	if(!engine)
		return;
	if(!engine->surface())
		return;
	if(!_offscreen)
		return;
	SDL_SetColorKey(surface, 0, 0);
}

void window_t::alpha(float a)
{
	if(!engine)
		return;
	if(!engine->surface())
		return;
	if(!_offscreen)
		return;
	SDL_SetAlpha(surface, SDL_SRCALPHA, (int)(a * 255.0));
}


void window_t::font(int fnt)
{
	_font = fnt;
}


void window_t::string(int _x, int _y, const char *txt)
{
	string_fxp(PIXEL2CS(_x), PIXEL2CS(_y), txt);
}


void window_t::center(int _y, const char *txt)
{
	center_fxp(PIXEL2CS(_y), txt);
}


void window_t::center_token(int _x, int _y, const char *txt, char token)
{
	center_token_fxp(PIXEL2CS(_x), PIXEL2CS(_y), txt, token);
}


void window_t::string_fxp(int _x, int _y, const char *txt)
{
	if(!engine)
		return;

	_x = CS2PIXEL((_x * xs + 128) >> 8);
	_y = CS2PIXEL((_y * ys + 128) >> 8);
	SoFont *f = engine->get_font(_font);
	if(!f)
		return;
	SELECT
	_x += phys_rect.x;
	_y += phys_rect.y;
	if(surface)
		f->PutString(surface, _x, _y, txt);
}


void window_t::center_fxp(int _y, const char *txt)
{
	_y = CS2PIXEL((_y * ys + 128) >> 8);

	if(!engine)
		return;

	SoFont *f = engine->get_font(_font);
	if(!f)
		return;
	SELECT
	int _x = (phys_rect.w - f->TextWidth(txt) + 1) / 2;
	_x += phys_rect.x;
	_y += phys_rect.y;
	if(surface)
		f->PutString(surface, _x, _y, txt);
}


void window_t::center_token_fxp(int _x, int _y, const char *txt, char token)
{
	_x = CS2PIXEL((_x * xs + 128) >> 8);
	_y = CS2PIXEL((_y * ys + 128) >> 8);

	if(!engine)
		return;

	SoFont *f = engine->get_font(_font);
	if(!f)
		return;
	SELECT
	int _cx;
	if(-1 == token)
		_cx = _x - f->TextWidth(txt)/2;
	else
	{
		int tokpos;
		/*
		 * My docs won't say if strchr(???, 0) is legal
		 * or even defined, so I'm not taking any chances...
		 */
		if(token)
		{
			char *tok = strchr(txt, token);
			if(tok)
				tokpos = tok-txt;
			else
				tokpos = 255;
		}
		else
			tokpos = 255;
		_cx = _x - f->TextWidth(txt, 0, tokpos);
	}
	_cx += phys_rect.x;
	_y += phys_rect.y;
	if(surface)
		f->PutString(surface, _cx, _y, txt);
}


int window_t::textwidth_fxp(const char *txt, int min, int max)
{
	if(!engine)
		return strlen(txt);

	SoFont *f = engine->get_font(_font);
	if(!f)
		return strlen(txt);

	return (f->TextWidth(txt, min, max) << 16) / xs;
}


int window_t::textwidth(const char *txt, int min, int max)
{
	if(!engine)
		return strlen(txt);

	SoFont *f = engine->get_font(_font);
	if(!f)
		return strlen(txt);

	return (f->TextWidth(txt, min, max) << 8) / xs;
}


int window_t::fontheight()
{
	if(!engine)
		return 1;

	SoFont *f = engine->get_font(_font);
	if(!f)
		return 1;

	return (f->FontHeight() * 256) / ys;
}


void window_t::clear(SDL_Rect *r)
{
	SDL_Rect sr, dr;
	if(!engine)
		return;
	if(!surface)
		return;
	SELECT
	if(!r)
	{
		sr = phys_rect;
		sr.x = 0;
		sr.y = 0;
		dr = phys_rect;
	}
	else
	{
		sr.x = ((int)r->x * xs + 128) >> 8;
		sr.y = ((int)r->y * ys + 128) >> 8;
		sr.w = (((int)(r->x + r->w) * xs + 128) >> 8) - sr.x;
		sr.h = (((int)(r->y + r->h) * ys + 128) >> 8) - sr.y;
		dr = sr;
		dr.x += phys_rect.x;
		dr.y += phys_rect.y;
	}
	if((-1 == bg_bank) && (-1 == bg_frame))
		SDL_FillRect(surface, &dr, bgcolor);
	else
	{
		s_sprite_t *s = engine->get_sprite(bg_bank, bg_frame);
		if(!s || !s->surface)
		{
			SDL_FillRect(surface, &dr, bgcolor);
			return;
		}
		SDL_BlitSurface(s->surface, &sr, surface, &dr);
	}
}


void window_t::point(int _x, int _y)
{
	int x2 = ((_x + 1) * xs + 128) >> 8;
	int y2 = ((_y + 1) * ys + 128) >> 8;
	_x = (_x * xs + 128) >> 8;
	_y = (_y * ys + 128) >> 8;

	if(!engine)
		return;
	SELECT
	/* Quick hack; slow */
	SDL_Rect r;
	r.x = phys_rect.x + _x;
	r.y = phys_rect.y + _y;
	r.w = x2 - _x;
	r.h = y2 - _y;
	if(surface)
		SDL_FillRect(surface, &r, fgcolor);
}


void window_t::fillrect(int _x, int _y, int w, int h)
{
	int x2 = ((_x + w) * xs + 128) >> 8;
	int y2 = ((_y + h) * ys + 128) >> 8;
	_x = (_x * xs + 128) >> 8;
	_y = (_y * ys + 128) >> 8;

	if(!engine)
		return;
	SELECT
	SDL_Rect r;
	r.x = phys_rect.x + _x;
	r.y = phys_rect.y + _y;
	r.w = x2 - _x;
	r.h = y2 - _y;
	if(surface)
		SDL_FillRect(surface, &r, fgcolor);
}


void window_t::rectangle(int _x, int _y, int w, int h)
{
	fillrect(_x, _y, w, 1);
	fillrect(_x, _y + h - 1, w, 1);
	fillrect(_x, _y + 1, 1, h - 2);
	fillrect(_x + w - 1, _y + 1, 1, h - 2);
}


void window_t::fillrect_fxp(int _x, int _y, int w, int h)
{
	int xx = CS2PIXEL((_x * xs + 128) >> 8);
	int yy = CS2PIXEL((_y * ys + 128) >> 8);
	w = CS2PIXEL(((w + _x) * xs + 128) >> 8) - xx;
	h = CS2PIXEL(((h + _y) * ys + 128) >> 8) - yy;

	if(!engine)
		return;
	SELECT
	SDL_Rect r;
	r.x = phys_rect.x + xx;
	r.y = phys_rect.y + yy;
	r.w = w;
	r.h = h;
	if(surface)
		SDL_FillRect(surface, &r, fgcolor);
}


void window_t::sprite(int _x, int _y, int bank, int frame, int inval)
{
	sprite_fxp(PIXEL2CS(_x), PIXEL2CS(_y), bank, frame, inval);
}


void window_t::sprite_fxp(int _x, int _y, int bank, int frame, int inval)
{
	if(!engine)
		return;
	s_sprite_t *s = engine->get_sprite(bank, frame);
	if(!s || !s->surface)
		return;
	_x = CS2PIXEL(((_x - (s->x << 8)) * xs + 128) >> 8);
	_y = CS2PIXEL(((_y - (s->y << 8)) * ys + 128) >> 8);
	SDL_Rect dest_rect;

	SELECT
	dest_rect.x = phys_rect.x + _x;
	dest_rect.y = phys_rect.y + _y;
	if(surface)
		SDL_BlitSurface(s->surface, NULL, surface, &dest_rect);

	if(inval && !engine->autoinvalidate())
	{
		dest_rect.w = s->surface->w;
		dest_rect.h = s->surface->h;
		engine->invalidate(&dest_rect, this);
	}
}


void window_t::blit(int dx, int dy,
		int sx, int sy, int sw, int sh, window_t *src)
{
	if(!engine)
		return;
	if(!src)
		return;
	if(!surface)
		return;
	if(!src->surface)
		return;
	SELECT

	SDL_Rect src_rect;
	int sx2 = ((sx + sw) * xs + 128) >> 8;
	int sy2 = ((sy + sh) * ys + 128) >> 8;
	src_rect.x = (sx * xs + 128) >> 8;
	src_rect.y = (sy * ys + 128) >> 8;
	src_rect.w = sx2 - src_rect.x;
	src_rect.h = sy2 - src_rect.y;

	SDL_Rect dest_rect;
	dest_rect.x = phys_rect.x + ((dx * xs + 128) >> 8);
	dest_rect.y = phys_rect.y + ((dy * ys + 128) >> 8);

	SDL_BlitSurface(src->surface, &src_rect, surface, &dest_rect);
}


void window_t::blit(int dx, int dy, window_t *src)
{
	if(!engine)
		return;
	if(!src)
		return;
	if(!surface)
		return;
	if(!src->surface)
		return;

	SELECT
	dx = (dx * xs + 128) >> 8;
	dy = (dy * ys + 128) >> 8;

	SDL_Rect src_rect;
	src_rect.x = 0;
	src_rect.y = 0;
	src_rect.w = src->surface->w;
	src_rect.h = src->surface->h;

	SDL_Rect dest_rect;
	dest_rect.x = phys_rect.x + dx;
	dest_rect.y = phys_rect.y + dy;

	SDL_BlitSurface(src->surface, &src_rect, surface, &dest_rect);
}
