/* ScummVM - Graphic Adventure Engine
 *
 * ScummVM is the legal property of its developers, whose names
 * are too numerous to list here. Please refer to the COPYRIGHT
 * file distributed with this source distribution.
 *
 * 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.
 *
 * Background handling code.
 */

#include "tinsel/background.h"
#include "tinsel/cliprect.h"	// object clip rect defs
#include "tinsel/graphics.h"
#include "tinsel/sched.h"	// process sheduler defs
#include "tinsel/object.h"
#include "tinsel/pid.h"	// process identifiers
#include "tinsel/tinsel.h"

namespace Tinsel {

// FIXME: Avoid non-const global vars

// current background
const BACKGND *g_pCurBgnd = NULL;

/**
 * Called to initialize a background.
 * @param pBgnd			Pointer to data struct for current background
 */

void InitBackground(const BACKGND *pBgnd) {
	int i;			// playfield counter
	PLAYFIELD *pPlayfield;	// pointer to current playfield

	// set current background
	g_pCurBgnd = pBgnd;

	// init background sky color
	SetBgndColor(pBgnd->rgbSkyColor);

	// start of playfield array
	pPlayfield = pBgnd->fieldArray;

	// for each background playfield
	for (i = 0; i < pBgnd->numPlayfields; i++, pPlayfield++) {
		// init playfield pos
		pPlayfield->fieldX = intToFrac(pBgnd->ptInitWorld.x);
		pPlayfield->fieldY = intToFrac(pBgnd->ptInitWorld.y);

		// no scrolling
		pPlayfield->fieldXvel = intToFrac(0);
		pPlayfield->fieldYvel = intToFrac(0);

		// clear playfield display list
		pPlayfield->pDispList = NULL;

		// clear playfield moved flag
		pPlayfield->bMoved = false;
	}
}

/**
 * Sets the xy position of the specified playfield in the current background.
 * @param which			Which playfield
 * @param newXpos		New x position
 * @param newYpos		New y position
 */

void PlayfieldSetPos(int which, int newXpos, int newYpos) {
	PLAYFIELD *pPlayfield;	// pointer to relavent playfield

	// make sure there is a background
	assert(g_pCurBgnd != NULL);

	// make sure the playfield number is in range
	assert(which >= 0 && which < g_pCurBgnd->numPlayfields);

	// get playfield pointer
	pPlayfield = g_pCurBgnd->fieldArray + which;

	// set new integer position
	pPlayfield->fieldX = intToFrac(newXpos);
	pPlayfield->fieldY = intToFrac(newYpos);

	// set moved flag
	pPlayfield->bMoved = true;
}

/**
 * Returns the xy position of the specified playfield in the current background.
 * @param which			Which playfield
 * @param pXpos			Returns current x position
 * @param pYpos			Returns current y position
 */

void PlayfieldGetPos(int which, int *pXpos, int *pYpos) {
	PLAYFIELD *pPlayfield;	// pointer to relavent playfield

	// make sure there is a background
	assert(g_pCurBgnd != NULL);

	// make sure the playfield number is in range
	assert(which >= 0 && which < g_pCurBgnd->numPlayfields);

	// get playfield pointer
	pPlayfield = g_pCurBgnd->fieldArray + which;

	// get current integer position
	*pXpos = fracToInt(pPlayfield->fieldX);
	*pYpos = fracToInt(pPlayfield->fieldY);
}

/**
 * Returns the x position of the center of the specified playfield
 * @param which			Which playfield
 */

int PlayfieldGetCenterX(int which) {
	PLAYFIELD *pPlayfield; // pointer to relavent playfield

	// make sure there is a background
	assert(g_pCurBgnd != NULL);

	// make sure the playfield number is in range
	assert(which >= 0 && which < g_pCurBgnd->numPlayfields);

	// get playfield pointer
	pPlayfield = g_pCurBgnd->fieldArray + which;

	// get current integer position
	return fracToInt(pPlayfield->fieldX) + SCREEN_WIDTH/2;
}

/**
 * Returns the display list for the specified playfield.
 * @param which			Which playfield
 */

OBJECT **GetPlayfieldList(int which) {
	PLAYFIELD *pPlayfield;	// pointer to relavent playfield

	// make sure there is a background
	assert(g_pCurBgnd != NULL);

	// make sure the playfield number is in range
	assert(which >= 0 && which < g_pCurBgnd->numPlayfields);

	// get playfield pointer
	pPlayfield = g_pCurBgnd->fieldArray + which;

	// return the display list pointer for this playfield
	return &pPlayfield->pDispList;
}

/**
 * Draws all the playfield object lists for the current background.
 * The playfield velocity is added to the playfield position in order
 * to scroll each playfield before it is drawn.
 */

void DrawBackgnd() {
	int i;			// playfield counter
	PLAYFIELD *pPlay;	// playfield pointer
	int prevX, prevY;	// save interger part of position
	Common::Point ptWin;	// window top left

	if (g_pCurBgnd == NULL)
		return;		// no current background

	// scroll each background playfield
	for (i = 0; i < g_pCurBgnd->numPlayfields; i++) {
		// get pointer to correct playfield
		pPlay = g_pCurBgnd->fieldArray + i;

		// save integer part of position
		prevX = fracToInt(pPlay->fieldX);
		prevY = fracToInt(pPlay->fieldY);

		// update scrolling
		pPlay->fieldX += pPlay->fieldXvel;
		pPlay->fieldY += pPlay->fieldYvel;

		// convert fixed point window pos to a int
		ptWin.x = fracToInt(pPlay->fieldX);
		ptWin.y = fracToInt(pPlay->fieldY);

		// set the moved flag if the playfield has moved
		if (prevX != ptWin.x || prevY != ptWin.y)
			pPlay->bMoved = true;

		// sort the display list for this background - just in case somebody has changed object Z positions
		SortObjectList(&pPlay->pDispList);

		// generate clipping rects for all objects that have moved etc.
		FindMovingObjects(&pPlay->pDispList, &ptWin,
			&pPlay->rcClip,	false, pPlay->bMoved);

		// clear playfield moved flag
		pPlay->bMoved = false;
	}

	// merge the clipping rectangles
	MergeClipRect();

	// redraw all playfields within the clipping rectangles
	const RectList &clipRects = GetClipRects();
	for (RectList::const_iterator r = clipRects.begin(); r != clipRects.end(); ++r) {
		// clear the clip rectangle on the virtual screen
		// for each background playfield
		for (i = 0; i < g_pCurBgnd->numPlayfields; i++) {
			Common::Rect rcPlayClip;	// clip rect for this playfield

			// get pointer to correct playfield
			pPlay = g_pCurBgnd->fieldArray + i;

			// convert fixed point window pos to a int
			ptWin.x = fracToInt(pPlay->fieldX);
			ptWin.y = fracToInt(pPlay->fieldY);

			if (IntersectRectangle(rcPlayClip, pPlay->rcClip, *r))
				// redraw all objects within this clipping rect
				UpdateClipRect(&pPlay->pDispList, &ptWin,	&rcPlayClip);
		}
	}

	// transfer any new palettes to the video DAC
	PalettesToVideoDAC();

	// update the screen within the clipping rectangles
	for (RectList::const_iterator r = clipRects.begin(); r != clipRects.end(); ++r) {
		UpdateScreenRect(*r);
	}

	g_system->updateScreen();

	// delete all the clipping rectangles
	ResetClipRect();
}

} // End of namespace Tinsel
