/*
** viewport.cpp
**
** This file is part of mkxp.
**
** Copyright (C) 2013 Jonas Kulla <Nyocurio@gmail.com>
**
** mkxp 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.
**
** mkxp 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 mkxp.  If not, see <http://www.gnu.org/licenses/>.
*/

#include "viewport.h"

#include "globalstate.h"
#include "etc.h"
#include "util.h"
#include "quad.h"
#include "glstate.h"

#include "SDL2/SDL_rect.h"

#include "sigc++/connection.h"

#include <QDebug>

struct ViewportPrivate
{
	/* Needed for geometry changes */
	Viewport *self;

	Rect *rect;
	sigc::connection rectCon;

	Color *color;
	Tone *tone;

	IntRect screenRect;
	int isOnScreen;

	EtcTemps tmp;

	ViewportPrivate(int x, int y, int width, int height, Viewport *self)
	    : self(self),
	      rect(&tmp.rect),
	      color(&tmp.color),
	      tone(&tmp.tone),
	      isOnScreen(false)
	{
		rect->set(x, y, width, height);
		updateRectCon();
	}

	~ViewportPrivate()
	{
		rectCon.disconnect();
	}

	void onRectChange()
	{
		self->geometry.rect = rect->toIntRect();
		self->notifyGeometryChange();
		recomputeOnScreen();
	}

	void updateRectCon()
	{
		rectCon.disconnect();
		rectCon = rect->valueChanged.connect
		        (sigc::mem_fun(this, &ViewportPrivate::onRectChange));
	}

	void recomputeOnScreen()
	{
		SDL_Rect r1 = { screenRect.x, screenRect.y,
		                screenRect.w, screenRect.h };

		SDL_Rect r2 = { rect->x,     rect->y,
		                rect->width, rect->height };

		SDL_Rect result;
		isOnScreen = SDL_IntersectRect(&r1, &r2, &result);
	}

	bool needsEffectRender(bool flashing)
	{
		bool rectEffective = !rect->isEmpty();
		bool colorToneEffective = color->hasEffect() || tone->hasEffect() || flashing;

		return (rectEffective && colorToneEffective && isOnScreen);
	}
};

Viewport::Viewport(int x, int y, int width, int height)
    : SceneElement(*gState->screen()),
      sceneLink(this)
{
	initViewport(x, y, width, height);
}

Viewport::Viewport(Rect *rect)
    : SceneElement(*gState->screen()),
      sceneLink(this)
{
	initViewport(rect->x, rect->y, rect->width, rect->height);
}

void Viewport::initViewport(int x, int y, int width, int height)
{
	p = new ViewportPrivate(x, y, width, height, this);

	/* Set our own geometry */
	geometry.rect = IntRect(x, y, width, height);

	/* Handle parent geometry */
	onGeometryChange(scene->getGeometry());
}

Viewport::~Viewport()
{
	dispose();
}

#define DISP_CLASS_NAME "viewport"

DEF_ATTR_RD_SIMPLE(Viewport, OX,   int,   geometry.xOrigin)
DEF_ATTR_RD_SIMPLE(Viewport, OY,   int,   geometry.yOrigin)
DEF_ATTR_RD_SIMPLE(Viewport, Rect, Rect*, p->rect)

DEF_ATTR_SIMPLE(Viewport, Color, Color*, p->color)
DEF_ATTR_SIMPLE(Viewport, Tone, Tone*, p->tone)

void Viewport::setOX(int value)
{
	GUARD_DISPOSED

	if (geometry.xOrigin == value)
		return;

	geometry.xOrigin = value;
	notifyGeometryChange();
}

void Viewport::setOY(int value)
{
	GUARD_DISPOSED

	if (geometry.yOrigin == value)
		return;

	geometry.yOrigin = value;
	notifyGeometryChange();
}

void Viewport::setRect(Rect *value)
{
	GUARD_DISPOSED

	if (p->rect == value)
		return;

	p->rect = value;
	p->updateRectCon();
	p->onRectChange();
}

/* Scene */
void Viewport::composite()
{
	if (emptyFlashFlag)
		return;

	bool renderEffect = p->needsEffectRender(flashing);

	if (elements.getSize() == 0 && !renderEffect)
		return;

	// What to do here:
	// 1. Setup scissor box
	// 2. Geometry offsets for elements should be taken care off by Scene
	// 3. Call Scene::composite, or manually iterate and draw??

	/* Setup scissor */
	glState.scissorTest.pushSet(true);
	glState.scissorBox.pushSet(p->rect->toIntRect());

	Scene::composite();

	/* If any effects are visible, request parent Scene to
	 * render them. */
	if (renderEffect)
		scene->requestViewportRender
		        (p->color->norm, flashColor, p->tone->norm);

	glState.scissorBox.pop();
	glState.scissorTest.pop();
}

/* SceneElement */
void Viewport::draw()
{
	composite();
}

void Viewport::onGeometryChange(const Geometry &geo)
{
	p->screenRect = geo.rect;
	p->recomputeOnScreen();
}

/* Disposable */
void Viewport::releaseResources()
{
	unlink();

	delete p;
}


ViewportElement::ViewportElement(Viewport *viewport, int z)
    : SceneElement(viewport ? *viewport : *gState->screen(), z),
      m_viewport(viewport)
{}

ViewportElement::ViewportElement(Viewport *viewport, int z, unsigned int cStamp)
    : SceneElement(viewport ? *viewport : *gState->screen(), z, cStamp)
{}

Viewport *ViewportElement::getViewport() const
{
	return m_viewport;
}

#ifdef RGSS2

void ViewportElement::setViewport(Viewport *viewport)
{
	m_viewport = viewport;
	setScene(viewport ? *viewport : *gState->screen());
	onViewportChange();
	onGeometryChange(scene->getGeometry());
}

#endif