/* 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.
 *
 * $URL$
 * $Id$
 *
 */

#include "common/util.h"

#include "sci/sci.h"
#include "sci/engine/state.h"
#include "sci/gui/gui_screen.h"
#include "sci/gui/gui_gfx.h"
#include "sci/gui/gui_animate.h"
#include "sci/gui/gui_text.h"
#include "sci/gui/gui_windowmgr.h"

namespace Sci {

// window styles
enum {
	SCI_WINDOWMGR_STYLE_TRANSPARENT = (1 << 0),
	SCI_WINDOWMGR_STYLE_NOFRAME     = (1 << 1),
	SCI_WINDOWMGR_STYLE_TITLE       = (1 << 2),
	SCI_WINDOWMGR_STYLE_TOPMOST     = (1 << 3),
	SCI_WINDOWMGR_STYLE_USER        = (1 << 7)
};

SciGuiWindowMgr::SciGuiWindowMgr(SciGui *gui, SciGuiScreen *screen, SciGuiGfx *gfx, SciGuiText *text)
	: _gui(gui), _screen(screen), _gfx(gfx), _text(text) {
}

SciGuiWindowMgr::~SciGuiWindowMgr() {
	// TODO: Clear _windowList and delete all stuff in it?
}

void SciGuiWindowMgr::init(Common::String gameId) {
	int16 offTop = 10;

	_wmgrPort = new GuiPort(0);
	_windowsById.resize(1);
	_windowsById[0] = _wmgrPort;

	// Jones sierra sci was called with parameter -Nw 0 0 200 320
	//  this actually meant not skipping the first 10 pixellines in windowMgrPort
	if (gameId == "jones")
		offTop = 0;

	_gfx->OpenPort(_wmgrPort);
	_gfx->SetPort(_wmgrPort);
	_gfx->SetOrigin(0, offTop);
	_wmgrPort->rect.bottom = 200 - offTop;
	_wmgrPort->rect.right = 320;
	_wmgrPort->rect.moveTo(0, 0);
	_wmgrPort->curTop = 0;
	_wmgrPort->curLeft = 0;
	_windowList.push_front(_wmgrPort);

	_picWind = NewWindow(Common::Rect(0, offTop, _screen->_width, _screen->_height), 0, 0, SCI_WINDOWMGR_STYLE_TRANSPARENT | SCI_WINDOWMGR_STYLE_NOFRAME, 0, true);
}

int16 SciGuiWindowMgr::isFrontWindow(GuiWindow *pWnd) {
	return _windowList.back() == pWnd;
}

void SciGuiWindowMgr::BeginUpdate(GuiWindow *wnd) {
	GuiPort *oldPort = _gfx->SetPort(_wmgrPort);
	PortList::iterator it = _windowList.reverse_begin();
	const PortList::iterator end = Common::find(_windowList.begin(), _windowList.end(), wnd);
	while (it != end) {
		// FIXME: We also store GuiPort objects in the window list.
		// We should add a check that we really only pass windows here...
		UpdateWindow((GuiWindow *)*it);
		--it;
	}
	_gfx->SetPort(oldPort);
}

void SciGuiWindowMgr::EndUpdate(GuiWindow *wnd) {
	GuiPort *oldPort = _gfx->SetPort(_wmgrPort);
	const PortList::iterator end = _windowList.end();
	PortList::iterator it = Common::find(_windowList.begin(), end, wnd);

	// wnd has to be in _windowList
	assert(it != end);

	while (++it != end) {
		// FIXME: We also store GuiPort objects in the window list.
		// We should add a check that we really only pass windows here...
		UpdateWindow((GuiWindow *)*it);
	}

	_gfx->SetPort(oldPort);
}

GuiWindow *SciGuiWindowMgr::NewWindow(const Common::Rect &dims, const Common::Rect *restoreRect, const char *title, uint16 style, int16 priority, bool draw) {
	// Find an unused window/port id
	uint id = 1;
	while (id < _windowsById.size() && _windowsById[id]) {
		++id;
	}
	if (id == _windowsById.size())
		_windowsById.push_back(0);
	assert(0 < id && id < 0xFFFF);

	GuiWindow *pwnd = new GuiWindow(id);
	Common::Rect r;

	if (!pwnd) {
		warning("Can't open window!");
		return 0;
	}

	_windowsById[id] = pwnd;
	if (style & SCI_WINDOWMGR_STYLE_TOPMOST)
		_windowList.push_front(pwnd);
	else
		_windowList.push_back(pwnd);
	_gfx->OpenPort(pwnd);
	r = dims;
	pwnd->rect = dims;
	if (restoreRect)
		pwnd->restoreRect = *restoreRect;
	
	pwnd->wndStyle = style;
	pwnd->hSaved1 = pwnd->hSaved2 = NULL_REG;
	pwnd->bDrawn = false;
	if ((style & SCI_WINDOWMGR_STYLE_TRANSPARENT) == 0)
		pwnd->saveScreenMask = (priority == -1 ? SCI_SCREEN_MASK_VISUAL : SCI_SCREEN_MASK_VISUAL | SCI_SCREEN_MASK_PRIORITY);
	
	if (title && (style & SCI_WINDOWMGR_STYLE_TITLE)) {
		pwnd->title = title;
	}
	
	r = dims;
	if (style == SCI_WINDOWMGR_STYLE_USER || !(style & SCI_WINDOWMGR_STYLE_NOFRAME)) {
		r.grow(1);
		if (style & SCI_WINDOWMGR_STYLE_TITLE) {
			r.top -= 10;
			r.bottom++;
		}
	}

	// FIXME: it seems as if shadows may result in the window getting moved one upwards
	//         so that the shadow is visible (lsl5)
	
	pwnd->dims = r;
	const Common::Rect *wmprect = &_wmgrPort->rect;
	int16 oldtop = pwnd->dims.top;
	int16 oldleft = pwnd->dims.left;
	if (wmprect->top > pwnd->dims.top)
		pwnd->dims.moveTo(pwnd->dims.left, wmprect->top);
	
	if (wmprect->bottom < pwnd->dims.bottom)
		pwnd->dims.moveTo(pwnd->dims.left, wmprect->bottom - pwnd->dims.bottom + pwnd->dims.top);
	
	if (wmprect->right < pwnd->dims.right)
		pwnd->dims.moveTo(wmprect->right + pwnd->dims.left - pwnd->dims.right, pwnd->dims.top);
	
	if (wmprect->left > pwnd->dims.left)
		pwnd->dims.moveTo(wmprect->left, pwnd->dims.top);
	
	pwnd->rect.moveTo(pwnd->rect.left + pwnd->dims.left - oldleft, pwnd->rect.top + pwnd->dims.top - oldtop);
	if (restoreRect == 0)
		pwnd->restoreRect = pwnd->dims;

	if (!(pwnd->wndStyle & (SCI_WINDOWMGR_STYLE_USER | SCI_WINDOWMGR_STYLE_NOFRAME))) {
		// The shadow is drawn slightly outside the window.
		// Enlarge restoreRect to cover that.
		pwnd->restoreRect.bottom++;
		pwnd->restoreRect.right++;
	}
	
	if (draw)
		DrawWindow(pwnd);
	_gfx->SetPort((GuiPort *)pwnd);
	_gfx->SetOrigin(pwnd->rect.left, pwnd->rect.top + _wmgrPort->top);
	pwnd->rect.moveTo(0, 0);
	return pwnd;
}

void SciGuiWindowMgr::DrawWindow(GuiWindow *pWnd) {
	if (pWnd->bDrawn)
		return;
	Common::Rect r;
	int16 wndStyle = pWnd->wndStyle;

	pWnd->bDrawn = true;
	GuiPort *oldport = _gfx->SetPort(_wmgrPort);
	_gfx->PenColor(0);
	if ((wndStyle & SCI_WINDOWMGR_STYLE_TRANSPARENT) == 0) {
		pWnd->hSaved1 = _gfx->BitsSave(pWnd->restoreRect, SCI_SCREEN_MASK_VISUAL);
		if (pWnd->saveScreenMask & SCI_SCREEN_MASK_PRIORITY) {
			pWnd->hSaved2 = _gfx->BitsSave(pWnd->restoreRect, SCI_SCREEN_MASK_PRIORITY);
			if ((wndStyle & SCI_WINDOWMGR_STYLE_USER) == 0)
				_gfx->FillRect(pWnd->restoreRect, SCI_SCREEN_MASK_PRIORITY, 0, 15);
		}
	}
	
	// drawing frame,shadow and title
	if (!(wndStyle & SCI_WINDOWMGR_STYLE_USER)) {
		r = pWnd->dims;
		if (!(wndStyle & SCI_WINDOWMGR_STYLE_NOFRAME)) {
			r.translate(1, 1);
			_gfx->FrameRect(r);// shadow
			r.translate(-1, -1);
			_gfx->FrameRect(r);// window frame

			if (wndStyle & SCI_WINDOWMGR_STYLE_TITLE) {
				_gfx->FrameRect(r);
				r.grow(-1);
				_gfx->FillRect(r, SCI_SCREEN_MASK_VISUAL, 0);
				if (!pWnd->title.empty()) {
					int16 oldcolor = _gfx->GetPort()->penClr;
					_gfx->PenColor(255);
					_text->Box(pWnd->title.c_str(), 1, r, SCI_TEXT_ALIGNMENT_CENTER, 0);
					_gfx->PenColor(oldcolor);
				}
				
				r = pWnd->dims;
				r.top += 9;
			}
			
			r.grow(-1);
		}
		
		if (!(wndStyle & SCI_WINDOWMGR_STYLE_TRANSPARENT))
			_gfx->FillRect(r, SCI_SCREEN_MASK_VISUAL, pWnd->backClr);

		_gfx->BitsShow(pWnd->restoreRect);
	}
	_gfx->SetPort(oldport);
}

void SciGuiWindowMgr::DisposeWindow(GuiWindow *pWnd, bool reanimate) {
	_gfx->SetPort(_wmgrPort);
	_gfx->BitsRestore(pWnd->hSaved1);
	_gfx->BitsRestore(pWnd->hSaved2);
	if (!reanimate)
		_gfx->BitsShow(pWnd->restoreRect);
	else
		_gui->graphRedrawBox(pWnd->restoreRect);
	_windowList.remove(pWnd);
	_gfx->SetPort(_windowList.back());
	_windowsById[pWnd->id] = 0;
	delete pWnd;
}

void SciGuiWindowMgr::UpdateWindow(GuiWindow *wnd) {
	GuiMemoryHandle handle;

	if (wnd->saveScreenMask && wnd->bDrawn) {
		handle = _gfx->BitsSave(wnd->restoreRect, SCI_SCREEN_MASK_VISUAL);
		_gfx->BitsRestore(wnd->hSaved1);
		wnd->hSaved1 = handle;
		if (wnd->saveScreenMask & SCI_SCREEN_MASK_PRIORITY) {
			handle = _gfx->BitsSave(wnd->restoreRect, SCI_SCREEN_MASK_PRIORITY);
			_gfx->BitsRestore(wnd->hSaved2);
			wnd->hSaved2 = handle;
		}
	}
}

} // End of namespace Sci
