/* 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 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/>.
 *
 * Background handling code.
 */

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

namespace Tinsel {

Background::Background(Font* font) : _font(font), _pCurBgnd(nullptr), _hBgPal(0), _BGspeed(0), _hBackground(0), _bDoFadeIn(false), _bgReels(0) {
	for (int i = 0; i < MAX_BG; i++) {
		_pBG[i] = nullptr;
		_thisAnim[i].pObject = nullptr;
	}
}

/**
 * Called to initialize a background.
 */
void Background::InitBackground() {
	// set current background
	_pCurBgnd = new BACKGND();
	_pCurBgnd->rgbSkyColor = BLACK;
	_pCurBgnd->ptInitWorld = Common::Point(0, 0);
	_pCurBgnd->rcScrollLimits = Common::Rect(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT);
	_pCurBgnd->refreshRate = 0;	// no background update process
	_pCurBgnd->pXscrollTable = nullptr;
	_pCurBgnd->pYscrollTable = nullptr;
	_pCurBgnd->bAutoErase = false;

	int numPlayFields = 2;
	if (TinselVersion == 3) {
		numPlayFields = 9;
	}
	for (int i = 0; i < numPlayFields; ++i) {
		PLAYFIELD playfield = {
		    NULL,                                            // display list
		    0,                                               // init field x
		    0,                                               // init field y
		    0,                                               // x vel
		    0,                                               // y vel
		    Common::Rect(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT), // clip rect
		    false                                            // moved flag
		};
		_pCurBgnd->fieldArray.push_back(playfield);
	}

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

/**
 * 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 Background::PlayfieldSetPos(unsigned int which, int newXpos, int newYpos) {
	PLAYFIELD *pPlayfield;	// pointer to relavent playfield

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

	// make sure the playfield number is in range
	assert(which < _pCurBgnd->fieldArray.size());

	// get playfield pointer
	pPlayfield = &_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 Background::PlayfieldGetPos(unsigned int which, int *pXpos, int *pYpos) {
	PLAYFIELD *pPlayfield;	// pointer to relavent playfield

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

	// make sure the playfield number is in range
	assert(which < _pCurBgnd->fieldArray.size());

	// get playfield pointer
	pPlayfield = &_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 Background::PlayfieldGetCenterX(unsigned int which) {
	PLAYFIELD *pPlayfield; // pointer to relavent playfield

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

	// make sure the playfield number is in range
	assert(which < _pCurBgnd->fieldArray.size());

	// get playfield pointer
	pPlayfield = &_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 **Background::GetPlayfieldList(unsigned int which) {
	PLAYFIELD *pPlayfield;	// pointer to relavent playfield

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

	// make sure the playfield number is in range
	assert(which < _pCurBgnd->fieldArray.size());

	// get playfield pointer
	pPlayfield = &_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 Background::DrawBackgnd() {
	PLAYFIELD *pPlay;	// playfield pointer
	int prevX, prevY;	// save interger part of position
	Common::Point ptWin;	// window top left

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

	// scroll each background playfield
	for (unsigned int i = 0; i < _pCurBgnd->fieldArray.size(); i++) {
		// get pointer to correct playfield
		pPlay = &_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 (unsigned int i = 0; i < _pCurBgnd->fieldArray.size(); i++) {
			Common::Rect rcPlayClip;	// clip rect for this playfield

			// get pointer to correct playfield
			pPlay = &_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);
		}
	}

	if (TinselVersion != 3) {
		// 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();
}

int Background::BgWidth() {
	assert(_pBG[0]);
	return MultiRightmost(_pBG[0]) + 1;
}

int Background::BgHeight() {
	assert(_pBG[0]);
	return MultiLowest(_pBG[0]) + 1;
}

void Background::SetBackPal(SCNHANDLE hPal) {
	_hBgPal = hPal;

	_font->FettleFontPal(_hBgPal);
	CreateTranslucentPalette(_hBgPal);
}

void Background::DropBackground() {
	_pBG[0] = nullptr;	// No background

	if (TinselVersion <= 1)
		_hBgPal = 0;	// No background palette
}

void Background::ChangePalette(SCNHANDLE hPal) {
	SwapPalette(FindPalette(_hBgPal), hPal);
	SetBackPal(hPal);
}

void Background::WaitForBG(CORO_PARAM) {
	CORO_BEGIN_CONTEXT;
	CORO_END_CONTEXT(_ctx);

	CORO_BEGIN_CODE(_ctx);

	while (_pBG[0] == nullptr) {
		CORO_SLEEP(1);
	}

	CORO_END_CODE;
}

} // End of namespace Tinsel
