//
// Copyright (C) 1993-1996 by id Software, Inc.
//
// This source is available for distribution and/or modification
// only under the terms of the DOOM Source Code License as
// published by id Software. All rights reserved.
//
// The source is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// FITNESS FOR A PARTICULAR PURPOSE. See the DOOM Source Code License
// for more details.
//

#include <stdio.h>
#include <stdlib.h>
#include <algorithm>
#include "p_lnspec.h"
#include "templates.h"
#include "doomdef.h"
#include "m_swap.h"
#include "i_system.h"
#include "w_wad.h"
#include "g_levellocals.h"
#include "p_maputl.h"
#include "swrenderer/things/r_visiblesprite.h"
#include "swrenderer/things/r_visiblespritelist.h"
#include "swrenderer/things/r_voxel.h"
#include "swrenderer/things/r_particle.h"
#include "swrenderer/things/r_sprite.h"
#include "swrenderer/things/r_wallsprite.h"
#include "swrenderer/things/r_playersprite.h"
#include "swrenderer/segments/r_drawsegment.h"
#include "swrenderer/scene/r_portal.h"
#include "swrenderer/scene/r_translucent_pass.h"
#include "swrenderer/scene/r_viewport.h"
#include "swrenderer/scene/r_light.h"
#include "swrenderer/plane/r_visibleplane.h"
#include "swrenderer/plane/r_visibleplanelist.h"
#include "swrenderer/r_memory.h"

EXTERN_CVAR(Int, r_drawfuzz)
EXTERN_CVAR(Bool, r_drawvoxels)
EXTERN_CVAR(Bool, r_blendmethod)

CVAR(Bool, r_fullbrightignoresectorcolor, true, CVAR_ARCHIVE | CVAR_GLOBALCONFIG);

namespace swrenderer
{
	bool RenderTranslucentPass::DrewAVoxel;
	TArray<drawseg_t *> RenderTranslucentPass::portaldrawsegs;

	void RenderTranslucentPass::Deinit()
	{
		RenderVoxel::Deinit();
	}

	void RenderTranslucentPass::Clear()
	{
		VisibleSpriteList::Instance()->Clear();
		DrewAVoxel = false;
	}

	void RenderTranslucentPass::CollectPortals()
	{
		// This function collects all drawsegs that may be of interest to R_ClipSpriteColumnWithPortals 
		// Having that function over the entire list of drawsegs can break down performance quite drastically.
		// This is doing the costly stuff only once so that R_ClipSpriteColumnWithPortals can 
		// a) exit early if no relevant info is found and
		// b) skip most of the collected drawsegs which have no portal attached.
		portaldrawsegs.Clear();
		for (drawseg_t* seg = ds_p; seg-- > firstdrawseg; ) // copied code from killough below
		{
			// I don't know what makes this happen (some old top-down portal code or possibly skybox code? something adds null lines...)
			// crashes at the first frame of the first map of Action2.wad
			if (!seg->curline) continue;

			line_t* line = seg->curline->linedef;
			// ignore minisegs from GL nodes.
			if (!line) continue;

			// check if this line will clip sprites to itself
			if (!line->isVisualPortal() && line->special != Line_Mirror)
				continue;

			// don't clip sprites with portal's back side (it's transparent)
			if (seg->curline->sidedef != line->sidedef[0])
				continue;

			portaldrawsegs.Push(seg);
		}
	}

	bool RenderTranslucentPass::ClipSpriteColumnWithPortals(int x, VisibleSprite *spr)
	{
		RenderPortal *renderportal = RenderPortal::Instance();

		// [ZZ] 10.01.2016: don't clip sprites from the root of a skybox.
		if (renderportal->CurrentPortalInSkybox)
			return false;

		for (drawseg_t *seg : portaldrawsegs)
		{
			// ignore segs from other portals
			if (seg->CurrentPortalUniq != renderportal->CurrentPortalUniq)
				continue;

			// (all checks that are already done in R_CollectPortals have been removed for performance reasons.)

			// don't clip if the sprite is in front of the portal
			if (!P_PointOnLineSidePrecise(spr->WorldPos().X, spr->WorldPos().Y, seg->curline->linedef))
				continue;

			// now if current column is covered by this drawseg, we clip it away
			if ((x >= seg->x1) && (x < seg->x2))
				return true;
		}

		return false;
	}

	void RenderTranslucentPass::DrawMaskedSingle(bool renew)
	{
		RenderPortal *renderportal = RenderPortal::Instance();

		auto &sortedSprites = VisibleSpriteList::Instance()->SortedSprites;
		for (int i = sortedSprites.Size(); i > 0; i--)
		{
			if (sortedSprites[i - 1]->IsCurrentPortalUniq(renderportal->CurrentPortalUniq))
			{
				sortedSprites[i - 1]->Render();
			}
		}

		// render any remaining masked mid textures

		// Modified by Lee Killough:
		// (pointer check was originally nonportable
		// and buggy, by going past LEFT end of array):

		//		for (ds=ds_p-1 ; ds >= drawsegs ; ds--)    old buggy code

		if (renew)
		{
			Clip3DFloors::Instance()->fake3D |= FAKE3D_REFRESHCLIP;
		}
		for (drawseg_t *ds = ds_p; ds-- > firstdrawseg; )	// new -- killough
		{
			// [ZZ] the same as above
			if (ds->CurrentPortalUniq != renderportal->CurrentPortalUniq)
				continue;
			// kg3D - no fake segs
			if (ds->fake) continue;
			if (ds->maskedtexturecol != nullptr || ds->bFogBoundary)
			{
				R_RenderMaskedSegRange(ds, ds->x1, ds->x2);
			}
		}
	}

	void RenderTranslucentPass::Render()
	{
		CollectPortals();
		VisibleSpriteList::Instance()->Sort(DrewAVoxel);

		Clip3DFloors *clip3d = Clip3DFloors::Instance();
		if (clip3d->height_top == nullptr)
		{ // kg3D - no visible 3D floors, normal rendering
			DrawMaskedSingle(false);
		}
		else
		{ // kg3D - correct sorting
			// ceilings
			for (HeightLevel *hl = clip3d->height_cur; hl != nullptr && hl->height >= ViewPos.Z; hl = hl->prev)
			{
				if (hl->next)
				{
					clip3d->fake3D = FAKE3D_CLIPBOTTOM | FAKE3D_CLIPTOP;
					clip3d->sclipTop = hl->next->height;
				}
				else
				{
					clip3d->fake3D = FAKE3D_CLIPBOTTOM;
				}
				clip3d->sclipBottom = hl->height;
				DrawMaskedSingle(true);
				VisiblePlaneList::Instance()->RenderHeight(hl->height);
			}

			// floors
			clip3d->fake3D = FAKE3D_DOWN2UP | FAKE3D_CLIPTOP;
			clip3d->sclipTop = clip3d->height_top->height;
			DrawMaskedSingle(true);
			for (HeightLevel *hl = clip3d->height_top; hl != nullptr && hl->height < ViewPos.Z; hl = hl->next)
			{
				VisiblePlaneList::Instance()->RenderHeight(hl->height);
				if (hl->next)
				{
					clip3d->fake3D = FAKE3D_DOWN2UP | FAKE3D_CLIPTOP | FAKE3D_CLIPBOTTOM;
					clip3d->sclipTop = hl->next->height;
				}
				else
				{
					clip3d->fake3D = FAKE3D_DOWN2UP | FAKE3D_CLIPBOTTOM;
				}
				clip3d->sclipBottom = hl->height;
				DrawMaskedSingle(true);
			}
			clip3d->DeleteHeights();
			clip3d->fake3D = 0;
		}
		RenderPlayerSprite::RenderPlayerSprites();
	}
}
