//-----------------------------------------------------------------------------
//
// Copyright 1993-1996 id Software
// Copyright 1999-2016 Randy Heit
// Copyright 2016 Magnus Norddahl
//
// 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/
//
//-----------------------------------------------------------------------------
//

#include <stdlib.h>
#include <stddef.h>
#include <cmath>

#include "doomdef.h"
#include "doomstat.h"
#include "doomdata.h"

#include "r_sky.h"
#include "v_video.h"

#include "m_swap.h"
#include "a_sharedglobal.h"
#include "d_net.h"
#include "g_level.h"
#include "r_walldraw.h"
#include "v_palette.h"
#include "r_data/colormaps.h"
#include "a_dynlight.h"
#include "swrenderer/drawers/r_draw.h"
#include "swrenderer/segments/r_drawsegment.h"
#include "swrenderer/scene/r_opaque_pass.h"
#include "swrenderer/scene/r_3dfloors.h"
#include "swrenderer/scene/r_portal.h"
#include "swrenderer/scene/r_scene.h"
#include "swrenderer/scene/r_light.h"
#include "swrenderer/viewport/r_viewport.h"
#include "swrenderer/line/r_walldraw.h"
#include "swrenderer/line/r_wallsetup.h"
#include "swrenderer/r_renderthread.h"
#include "r_memory.h"

EXTERN_CVAR(Bool, r_fullbrightignoresectorcolor)

namespace swrenderer
{
	RenderWallPart::RenderWallPart(RenderThread* thread)
	{
		Thread = thread;
	}

	void RenderWallPart::Render(const sector_t* lightsector, seg_t* curline, int tier, const FWallCoords& WallC, FSoftwareTexture* pic, int x1, int x2, const short* walltop, const short* wallbottom, const ProjectedWallTexcoords& texcoords, bool mask, bool additive, fixed_t alpha)
	{
		if (pic == nullptr)
			return;

		this->x1 = x1;
		this->x2 = x2;
		this->lightsector = lightsector;
		this->curline = curline;
		this->tier = tier;
		this->WallC = WallC;
		this->pic = pic;
		this->mask = mask;
		this->additive = additive;
		this->alpha = alpha;

		light_list = GetLightList();

		mLight.SetColormap(lightsector, curline, tier);
		mLight.SetLightLeft(Thread, WallC);

		CameraLight* cameraLight = CameraLight::Instance();
		if (cameraLight->FixedColormap() || cameraLight->FixedLightLevel() >= 0 || !(lightsector->e && lightsector->e->XFloor.lightlist.Size()))
		{
			ProcessNormalWall(walltop, wallbottom, texcoords);
		}
		else
		{
			ProcessStripedWall(walltop, wallbottom, texcoords);
		}
	}

	void RenderWallPart::ProcessStripedWall(const short* uwal, const short* dwal, const ProjectedWallTexcoords& texcoords)
	{
		RenderPortal* renderportal = Thread->Portal.get();

		ProjectedWallLine most1, most2, most3;
		const short* up = uwal;
		short* down = most1.ScreenY;

		for (unsigned int i = 0; i < lightsector->e->XFloor.lightlist.Size(); i++)
		{
			ProjectedWallCull j = most3.Project(Thread->Viewport.get(), lightsector->e->XFloor.lightlist[i].plane, &WallC, curline, renderportal->MirrorFlags & RF_XFLIP);
			if (j != ProjectedWallCull::OutsideAbove)
			{
				for (int j = x1; j < x2; ++j)
				{
					down[j] = max(min(most3.ScreenY[j], up[j]), dwal[j]); // this cannot use clamp due to failing range checks.
				}
				ProcessNormalWall(up, down, texcoords);
				up = down;
				down = (down == most1.ScreenY) ? most2.ScreenY : most1.ScreenY;
			}

			mLight.SetColormap(lightsector, curline, tier, &lightsector->e->XFloor.lightlist[i]);
		}

		ProcessNormalWall(up, dwal, texcoords);
	}

	void RenderWallPart::ProcessNormalWall(const short* uwal, const short* dwal, const ProjectedWallTexcoords& texcoords)
	{
		CameraLight* cameraLight = CameraLight::Instance();
		RenderViewport* viewport = Thread->Viewport.get();

		WallDrawerArgs drawerargs;

		// Textures that aren't masked can use the faster opaque drawer
		if (!pic->isMasked() && mask && alpha >= OPAQUE && !additive)
		{
			drawerargs.SetStyle(true, false, OPAQUE, light_list);
		}
		else
		{
			drawerargs.SetStyle(mask, additive, alpha, light_list);
		}

		if (cameraLight->FixedLightLevel() >= 0)
		{
			drawerargs.SetBaseColormap((r_fullbrightignoresectorcolor) ? &FullNormalLight : mLight.GetBaseColormap());
			drawerargs.SetLight(0.0f, cameraLight->FixedLightLevelShade());
			drawerargs.fixedlight = true;
		}
		else if (cameraLight->FixedColormap())
		{
			drawerargs.SetBaseColormap(cameraLight->FixedColormap());
			drawerargs.SetLight(0.0f, 0);
			drawerargs.fixedlight = true;
		}
		else
		{
			drawerargs.SetBaseColormap(mLight.GetBaseColormap());
			drawerargs.SetLight(0.0f, mLight.GetLightLevel(), mLight.GetFoggy(), viewport);
			drawerargs.fixedlight = false;
		}

		int count = x2 - x1;
		short* data = Thread->FrameMemory->AllocMemory<short>(count << 1);

		drawerargs.x1 = x1;
		drawerargs.x2 = x2;
		drawerargs.uwal = data - x1;
		drawerargs.dwal = data + count - x1; // to avoid calling AllocMemory twice
		memcpy(drawerargs.uwal + x1, uwal + x1, sizeof(short) * count);
		memcpy(drawerargs.dwal + x1, dwal + x1, sizeof(short) * count);
		drawerargs.WallC = WallC;
		drawerargs.texcoords = texcoords;

		drawerargs.lightpos = mLight.GetLightPos(x1);
		drawerargs.lightstep = mLight.GetLightStep();

		drawerargs.lightlist = light_list;

		drawerargs.texwidth = pic->GetPhysicalWidth();
		drawerargs.texheight = pic->GetPhysicalHeight();
		if (viewport->RenderTarget->IsBgra())
		{
			drawerargs.texpixels = pic->GetPixelsBgra();
			drawerargs.fracbits = 32;
			drawerargs.mipmapped = r_mipmap && pic->Mipmapped();
		}
		else
		{
			drawerargs.texpixels = pic->GetPixels(DefaultRenderStyle());
			int fracbits = 32 - pic->GetHeightBits();
			if (fracbits == 32) fracbits = 0; // One pixel tall textures
			drawerargs.fracbits = fracbits;
			drawerargs.mipmapped = false;
		}

		// This data really should be its own command as it rarely changes
		drawerargs.SetDest(viewport);
		drawerargs.CenterX = Thread->Viewport->CenterX;
		drawerargs.CenterY = Thread->Viewport->CenterY;
		drawerargs.FocalTangent = Thread->Viewport->viewwindow.FocalTangent;
		drawerargs.InvZtoScale = Thread->Viewport->InvZtoScale;
		drawerargs.ViewpointPos = { (float)Thread->Viewport->viewpoint.Pos.X, (float)Thread->Viewport->viewpoint.Pos.Y, (float)Thread->Viewport->viewpoint.Pos.Z };
		drawerargs.Sin = Thread->Viewport->viewpoint.Sin;
		drawerargs.Cos = Thread->Viewport->viewpoint.Cos;
		drawerargs.TanCos = Thread->Viewport->viewpoint.TanCos;
		drawerargs.TanSin = Thread->Viewport->viewpoint.TanSin;
		drawerargs.PortalMirrorFlags = Thread->Portal->MirrorFlags;

		drawerargs.DrawWall(Thread);
	}

	FLightNode* RenderWallPart::GetLightList()
	{
		CameraLight* cameraLight = CameraLight::Instance();
		if ((cameraLight->FixedLightLevel() >= 0) || cameraLight->FixedColormap())
			return nullptr; // [SP] Don't draw dynlights if invul/lightamp active
		else if (curline && curline->sidedef)
			return curline->sidedef->lighthead;
		else
			return nullptr;
	}
}
