/*  ZZ Open GL graphics plugin
 *  Copyright (c)2009-2010 zeydlitz@gmail.com, arcum42@gmail.com
 *  Based on Zerofrog's ZeroGS KOSMOS (c)2005-2008
 *
 *  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
 */

// Zerogs:VB implementation.
// VB stands for Visual Buffer, as I think

//------------------- Includes
 #include "Util.h"
#include "targets.h"
#include "ZZoglVB.h"
#include "GS.h"
#include "Mem.h"
extern float fiTexWidth[2], fiTexHeight[2];	// current tex width and height

// ----------------- Defines
#define MINMAX_SHIFT	3

//------------------ Constants

// ----------------- Global Variables
int maxmin = 608;
// ----------------- Code

// Constructor. Set width and height to 1
VB::VB()
{
	memset(this, 0, sizeof(VB));
	tex0.tw = 1;
	tex0.th = 1;
}

// Destructor
VB::~VB()
{
	Destroy();
}

void VB::Destroy()
{
	_aligned_free(pBufferData);
	pBufferData = NULL;
	nNumVertices = 0;

	prndr = NULL;
	pdepth = NULL;
}

int ConstraintReason;

// Return number of 64-pixels block, that guaranted could be hold in memory
// from gsfb.fbp and tbp (textrure pase), zbuf.zbp (Z-buffer), frame.fbp
// (previous frame).
inline int VB::FindMinimalMemoryConstrain(int tbp, int maxpos)
{
	int MinConstraint = maxpos;

	// make sure texture is far away from tbp
	{
		int Constraint = tbp - gsfb.fbp;

		if ((0 < Constraint) && (Constraint < MinConstraint))
		{
			MinConstraint = Constraint;
			ConstraintReason = 1;
		}
	}

	// offroad uses 0x80 fbp which messes up targets
	// special case when double buffering (hamsterball)
	// Suikoden 3 require e00 have this issue too. P3 - 0x1000.

	if (prndr != NULL)
	{
		int Constraint = frame.fbp - gsfb.fbp;

		if ((0x0 < Constraint) && (Constraint < MinConstraint))
		{
			MinConstraint = Constraint;
			ConstraintReason = 2;
		}
	}

	// old caching method
	// zmsk necessary for KH movie
	if (!zbuf.zmsk)
	{
		int Constraint = zbuf.zbp - gsfb.fbp;

		if ((0 < Constraint) && (Constraint < MinConstraint))
		{
			MinConstraint = Constraint;
			ConstraintReason = 3;
		}
	}

	// In 16Bit mode in one Word frame stored 2 pixels
	if (PSMT_ISHALF(gsfb.psm)) MinConstraint *= 2;

	return MinConstraint ;
}

// Return number of 64 pizel words that could be placed in Z-Buffer
// If no Z-buffer present return old constraint
inline int VB::FindZbufferMemoryConstrain(int tbp, int maxpos)
{
	int MinConstraint = maxpos;

	// Check tbp / zbuffer constraint
	if (!zbuf.zmsk)
	{
		int Constraint = (tbp - zbuf.zbp) * (PSMT_ISHALF(zbuf.psm) ? 2 : 1);

		if ((0 < Constraint) && (Constraint < MinConstraint))
		{
			MinConstraint = Constraint;
			ConstraintReason = 4;
		}
	}

	return MinConstraint;
}

// Return heights limiter from scissor...
inline int GetScissorY(int y)
{
	int fbh = (y >> MINMAX_SHIFT) + 1;

	if (fbh > 2 && (fbh & 1)) fbh -= 1;

	return fbh;
}

//There is several reasons to limit a height of frame: maximum buffer size, calculated size
//from fbw and fbh and scissoring.
inline int VB::FindMinimalHeightConstrain(int maxpos)
{
	int MinConstraint = maxpos;

	if (maxmin < MinConstraint)
	{
		MinConstraint = maxmin;
		ConstraintReason = 5;
	}

	if (gsfb.fbh < MinConstraint)
	{
		MinConstraint = gsfb.fbh;
		ConstraintReason = 6;
	}

	int ScissorConstraint = GetScissorY(scissor.y1) ;

	if (ScissorConstraint < MinConstraint)
	{
		MinConstraint = ScissorConstraint;
		ConstraintReason = 7;
	}

	return MinConstraint;
}

// 32 bit frames have additional constraints to frame
// maxpos was maximum length of frame at normal constraints
inline void VB::CheckFrame32bitRes(int maxpos)
{
	int fbh = frame.fbh;

	if (frame.fbh >= 512)
	{
		// neopets hack
		maxmin = min(maxmin, frame.fbh);
		frame.fbh = maxmin;
		ConstraintReason = 8;
	}

	// ffxii hack to stop resolving
	if (frame.fbp >= 0x3000 && fbh >= 0x1a0)
	{
		int endfbp = frame.fbp + frame.fbw * fbh / (PSMT_ISHALF(gsfb.psm) ? 128 : 64);

		// see if there is a previous render target in the way, reduce

		for (CRenderTargetMngr::MAPTARGETS::iterator itnew = s_RTs.mapTargets.begin(); itnew != s_RTs.mapTargets.end(); ++itnew)
		{
			if (itnew->second->fbp > frame.fbp && endfbp > itnew->second->fbp)
			{
				endfbp = itnew->second->fbp;
			}
		}

		frame.fbh = (endfbp - frame.fbp) * (PSMT_ISHALF(gsfb.psm) ? 128 :  64) / frame.fbw;

		if (frame.fbh < fbh) ConstraintReason = 9;
	}

}

// This is the main code for frame resizing.
// It checks for several reasons to resize and resizes if it needs to.
// 4Mb memory in 64 bit (4 bytes) words.
// |------------------------|---------------------|----------|----------|---------------------|
// 0                     gsfb.fbp               zbuff.zpb   tbp    frame.fbp              2^20/64
inline int VB::CheckFrameAddConstraints(int tbp)
{
	if (gsfb.fbw <= 0)
	{
		ERROR_LOG_SPAM("render target null, no constraints. Ignoring\n");
		return -1;
	}

	// Memory region after fbp
	int maxmemorypos = 0x4000 - gsfb.fbp;

	ConstraintReason = 0;

	maxmemorypos = FindMinimalMemoryConstrain(tbp, maxmemorypos);
	maxmemorypos = FindZbufferMemoryConstrain(tbp, maxmemorypos);

	int maxpos = 64 * maxmemorypos ;

	maxpos /= gsfb.fbw;

	//? atelier iris crashes without it
	if (maxpos > 256) maxpos &= ~0x1f;

#ifdef DEVBUILD
	int noscissorpos = maxpos;
	int ConstrainR1 = ConstraintReason;
#endif

	maxpos = FindMinimalHeightConstrain(maxpos);

	frame = gsfb;
	frame.fbh = maxpos;

	if (!PSMT_ISHALF(frame.psm) || !(conf.settings().full_16_bit_res)) CheckFrame32bitRes(maxpos);

#ifdef DEVBUILD
	if (frame.fbh == 0xe2)
		ZZLog::Debug_Log("Const: %x %x %d| %x %d %x %x", frame.fbh, frame.fbw, ConstraintReason, noscissorpos, ConstrainR1, tbp, frame.fbp);
#endif

// 	Fixme: Reserved psm for framebuffers
//	gsfb.psm &= 0xf; // shadow tower

	return 0;
}

// Check if after resizing new depth target is needed to be used.
// it returns 2 if a new depth target is used. 
inline int VB::CheckFrameResolveDepth(int tbp)
{
	int result = 0;
	CDepthTarget* pprevdepth = pdepth;
	pdepth = NULL;

	// just z changed
	frameInfo f = CreateFrame(zbuf.zbp, prndr->fbw, prndr->fbh, zbuf.psm, (zbuf.psm == 0x31) ? 0xff000000 : 0);

	CDepthTarget* pnewdepth = (CDepthTarget*)s_DepthRTs.GetTarg(f, CRenderTargetMngr::TO_DepthBuffer | CRenderTargetMngr::TO_StrictHeight |
							  (zbuf.zmsk ? CRenderTargetMngr::TO_Virtual : 0), get_maxheight(zbuf.zbp, gsfb.fbw, 0));

	assert(pnewdepth != NULL && prndr != NULL);
	if (pnewdepth->fbh != prndr->fbh) ZZLog::Debug_Log("pnewdepth->fbh(0x%x) != prndr->fbh(0x%x)", pnewdepth->fbh, prndr->fbh);
	//assert(pnewdepth->fbh == prndr->fbh);

	if ((pprevdepth != pnewdepth) || (pprevdepth != NULL && (pprevdepth->status & CRenderTarget::TS_NeedUpdate)))
		result = 2;

	pdepth = pnewdepth;

	return result;
}

// Check if after resizing, a new render target is needed to be used. Also perform deptarget check.
// Returns 1 if only 1 render target is changed and 3 -- if both.
inline int VB::CheckFrameResolveRender(int tbp)
{
	int result = 0;

	CRenderTarget* pprevrndr = prndr;
	prndr = NULL;
	CDepthTarget* pprevdepth = pdepth;
	pdepth = NULL;
	// Set renderes to NULL to prevent Flushing.

	CRenderTarget* pnewtarg = s_RTs.GetTarg(frame, 0, maxmin);
	assert(pnewtarg != NULL);

	// pnewtarg->fbh >= 0x1c0 needed for ffx

	if ((pnewtarg->fbh >= 0x1c0) && pnewtarg->fbh > frame.fbh && zbuf.zbp < tbp && !zbuf.zmsk)
	{
		// check if zbuf is in the way of the texture (suikoden5)
		int maxallowedfbh = (tbp - zbuf.zbp) * (PSMT_ISHALF(zbuf.psm) ? 128 : 64) / gsfb.fbw;

		if (PSMT_ISHALF(gsfb.psm)) maxallowedfbh *= 2;

		if (pnewtarg->fbh > maxallowedfbh + 32)   // +32 needed for ffx2
		{
			// destroy and recreate
			s_RTs.DestroyAllTargs(0, 0x100, pnewtarg->fbw);
			pnewtarg = s_RTs.GetTarg(frame, 0, maxmin);
			assert(pnewtarg != NULL);
		}
	}

	ZZLog::Prim_Log("frame_%d: fbp=0x%x fbw=%d fbh=%d(%d) psm=0x%x fbm=0x%x\n", ictx, gsfb.fbp, gsfb.fbw, gsfb.fbh, pnewtarg->fbh, gsfb.psm, gsfb.fbm);

	if ((pprevrndr != pnewtarg) || (pprevrndr != NULL && (pprevrndr->status & CRenderTarget::TS_NeedUpdate)))
		result = 1;

	prndr = pnewtarg;

	pdepth = pprevdepth;

	result |= CheckFrameResolveDepth(tbp);

	return result;
}

// After frame resetting, it is possible that 16 to 32 or 32 to 16 (color bits) conversion should be made.
inline void VB::CheckFrame16vs32Conversion()
{
	if (prndr->status & CRenderTarget::TS_NeedConvert32)
	{
		if (pdepth->pdepth != 0) pdepth->SetDepthStencilSurface();

		prndr->fbh *= 2;
		prndr->ConvertTo32();
		prndr->status &= ~CRenderTarget::TS_NeedConvert32;
	}
	else if (prndr->status & CRenderTarget::TS_NeedConvert16)
	{
		if (pdepth->pdepth != 0) pdepth->SetDepthStencilSurface();

		prndr->fbh /= 2;
		prndr->ConvertTo16();
		prndr->status &= ~CRenderTarget::TS_NeedConvert16;
	}
}

void SetContextTarget(int context);

// A lot of times, the target is too big and overwrites the texture.
// If tbp != 0, use it to bound.
void VB::CheckFrame(int tbp)
{
	GL_REPORT_ERRORD();
	
	static int bChanged;

	if (bNeedZCheck)
	{
		ZZLog::Prim_Log("zbuf_%d: zbp=0x%x psm=0x%x, zmsk=%d\n", ictx, zbuf.zbp, zbuf.psm, zbuf.zmsk);
		//zbuf = *zb;
	}

	if (m_Blocks[gsfb.psm].bpp == 0)
	{
		ZZLog::Error_Log("CheckFrame invalid bpp %d.", gsfb.psm);
		return;
	}

	bChanged = 0;

	if (bNeedFrameCheck)
	{
		// important to set before calling GetTarg
		bNeedFrameCheck = 0;
		bNeedZCheck = 0;

		if (CheckFrameAddConstraints(tbp) == -1) return;

		if ((prndr != NULL) && (prndr->psm != gsfb.psm))
		{
			// behavior for dest alpha varies
//			ResetAlphaVariables();
		}

		bChanged = CheckFrameResolveRender(tbp);

		CheckFrame16vs32Conversion();
	}
	else if (bNeedZCheck)
	{
		bNeedZCheck = 0;

		if (prndr != NULL && gsfb.fbw > 0) CheckFrameResolveDepth(tbp);
	}

	if (prndr != NULL) SetContextTarget(ictx);
	GL_REPORT_ERRORD();
}

// This is the case, most easy to perform, when nothing was changed
inline void VB::FlushTexUnchangedClutDontUpdate()
{
	if (ZZOglGet_cld_TexBits(uNextTex0Data[1]))
	{
		texClutWrite(ictx);
		// invalidate to make sure target didn't change!
		bVarsTexSync = false;
	}
}

// The second of easy branch. We does not change storage model, so we don't need to
// update anything except texture itself
inline void VB::FlushTexClutDontUpdate()
{
	if (!ZZOglClutStorageUnchanged(uCurTex0Data, uNextTex0Data)) Flush(ictx);

	// clut memory isn't going to be loaded so can ignore, but at least update CSA and CPSM!
	uCurTex0Data[1] = (uCurTex0Data[1] & CPSM_CSA_NOTMASK) | (uNextTex0Data[1] & CPSM_CSA_BITMASK);

	tex0.csa  = ZZOglGet_csa_TexBits(uNextTex0Data[1]);
	tex0.cpsm = ZZOglGet_cpsm_TexBits(uNextTex0Data[1]);

	texClutWrite(ictx);

	bVarsTexSync = false;
}


// Set texture variables after big change
inline void VB::FlushTexSetNewVars(u32 psm)
{
	tex0.tbp0 = ZZOglGet_tbp0_TexBits(uNextTex0Data[0]);
	tex0.tbw  = ZZOglGet_tbw_TexBitsMult(uNextTex0Data[0]);
	tex0.psm  = psm;
	tex0.tw   = ZZOglGet_tw_TexBitsExp(uNextTex0Data[0]);
	tex0.th   = ZZOglGet_th_TexBitsExp(uNextTex0Data[0], uNextTex0Data[1]);

	tex0.tcc  = ZZOglGet_tcc_TexBits(uNextTex0Data[1]);
	tex0.tfx  = ZZOglGet_tfx_TexBits(uNextTex0Data[1]);

	fiTexWidth[ictx] = (1 / 16.0f) / tex0.tw;
	fiTexHeight[ictx] = (1 / 16.0f) / tex0.th;
}

// Flush == draw on screen
// This function made VB state consistant before real Flush.
void VB::FlushTexData()
{
	GL_REPORT_ERRORD();
	
	//assert(bNeedTexCheck);
	if (bNeedTexCheck)
	{
		bNeedTexCheck = 0;

		u32 psm = ZZOglGet_psm_TexBitsFix(uNextTex0Data[0]);

		// don't update unless necessary

		if (ZZOglAllExceptClutIsSame(uCurTex0Data, uNextTex0Data))
		{
			// Don't need to do anything if there is no clutting and VB tex data was not changed
			if (!PSMT_ISCLUT(psm)) return;

			// have to write the CLUT again if only CLD was changed
			if (ZZOglClutMinusCLDunchanged(uCurTex0Data, uNextTex0Data))
			{
				FlushTexUnchangedClutDontUpdate();
				return;
			}

			// Cld bit is 0 means that clut buffer stay unchanged
			if (ZZOglGet_cld_TexBits(uNextTex0Data[1]) == 0)
			{
				FlushTexClutDontUpdate();
				return;
			}
		}

		// Made the full update
		Flush(ictx);

		bVarsTexSync = false;
		bTexConstsSync = false;

		uCurTex0Data[0] = uNextTex0Data[0];
		uCurTex0Data[1] = uNextTex0Data[1];

		FlushTexSetNewVars(psm);

		if (PSMT_ISCLUT(psm)) CluttingForFlushedTex(&tex0, uNextTex0Data[1], ictx) ;
		GL_REPORT_ERRORD();
	}
}
