#pragma once
#include "textures.h"
#include "v_video.h"
#include "g_levellocals.h"
#include "d_main.h"


struct FSoftwareTextureSpan
{
	uint16_t TopOffset;
	uint16_t Length;	// A length of 0 terminates this column
};


// For now this is just a minimal wrapper around FTexture. Once the software renderer no longer accesses FTexture directly, it is time for cleaning up.
class FSoftwareTexture : public ISoftwareTexture
{
protected:
	FGameTexture *mTexture;
	FTexture *mSource;
	TArray<uint8_t> Pixels;
	TArray<uint32_t> PixelsBgra;
	struct
	{
		const void* Pixels = nullptr;
		int LastUpdate = -1;
	} Unlockeddata[3];
	FSoftwareTextureSpan **Spandata[3] = { };
	DVector2 Scale;
	uint8_t WidthBits = 0, HeightBits = 0;
	uint16_t WidthMask = 0;
	int mPhysicalWidth, mPhysicalHeight;
	int mPhysicalScale;
	int mBufferFlags;

	void FreeAllSpans();
	template<class T> FSoftwareTextureSpan **CreateSpans(const T *pixels);
	void FreeSpans(FSoftwareTextureSpan **spans);
	void CalcBitSize();

public:
	FSoftwareTexture(FGameTexture *tex);
	
	virtual ~FSoftwareTexture()
	{
		FreeAllSpans();
	}

	FGameTexture *GetTexture() const
	{
		return mTexture;
	}
	
	// The feature from hell... :(
	bool useWorldPanning(FLevelLocals *Level) const
	{
		return mTexture->useWorldPanning() || (Level->flags3 & LEVEL3_FORCEWORLDPANNING);
	}

	bool isMasked()
	{
		return mTexture->isMasked();
	}

	uint16_t GetRotations() const
	{
		return mTexture->GetRotations();
	}
	
	int GetSkyOffset() const { return mTexture->GetSkyOffset(); }
	
	int GetWidth () { return mTexture->GetTexelWidth(); }
	int GetHeight () { return mTexture->GetTexelHeight(); }
	int GetWidthBits() { return WidthBits; }
	int GetHeightBits() { return HeightBits; }

	double GetScaledWidth () { return mTexture->GetDisplayWidth(); }
	double GetScaledHeight () { return mTexture->GetDisplayHeight(); }

	// Now with improved offset adjustment.
	int GetLeftOffset(int adjusted) { return mTexture->GetTexelLeftOffset(adjusted); }
	int GetTopOffset(int adjusted) { return mTexture->GetTexelTopOffset(adjusted); }
	
	// Interfaces for the different renderers. Everything that needs to check renderer-dependent offsets
	// should use these, so that if changes are needed, this is the only place to edit.
	
	// For the original software renderer
	int GetLeftOffsetSW() { return GetLeftOffset(r_spriteadjustSW); }
	int GetTopOffsetSW() { return GetTopOffset(r_spriteadjustSW); }
	double GetScaledLeftOffsetSW() { return mTexture->GetDisplayLeftOffset(r_spriteadjustSW); }
	double GetScaledTopOffsetSW() { return mTexture->GetDisplayTopOffset(r_spriteadjustSW); }
	
	DVector2 GetScale() const { return Scale; }
	int GetPhysicalWidth() { return mPhysicalWidth; }
	int GetPhysicalHeight() { return mPhysicalHeight; }
	int GetPhysicalScale() const { return mPhysicalScale; }
	
	virtual void Unload()
	{
		Pixels.Reset();
		PixelsBgra.Reset();
		for (auto& d : Unlockeddata) d = {};
	}
	
	// Returns true if the next call to GetPixels() will return an image different from the
	// last call to GetPixels(). This should be considered valid only if a call to CheckModified()
	// is immediately followed by a call to GetPixels().
	virtual bool CheckModified (int which) { return false; }

	void GenerateBgraFromBitmap(const FBitmap &bitmap);
	void CreatePixelsBgraWithMipmaps();
	void GenerateBgraMipmaps();
	int MipmapLevels();
	
	// Returns true if GetPixelsBgra includes mipmaps
	virtual bool Mipmapped() { return true; }

	// Returns a single column of the texture
	const uint8_t* GetColumn(int style, unsigned int column, const FSoftwareTextureSpan** spans_out)
	{
		column = WrapColumn(column);
		const uint8_t* pixels = GetPixels(style);
		if (spans_out)
			*spans_out = Spandata[style][column];
		return pixels + column * GetPhysicalHeight();
	}

	// Returns a single column of the texture, in BGRA8 format
	const uint32_t* GetColumnBgra(unsigned int column, const FSoftwareTextureSpan** spans_out)
	{
		column = WrapColumn(column);
		const uint32_t* pixels = GetPixelsBgra();
		if (spans_out)
			*spans_out = Spandata[2][column];
		return pixels + column * GetPhysicalHeight();
	}

	unsigned int WrapColumn(unsigned int column)
	{
		if ((unsigned)column >= (unsigned)GetPhysicalWidth())
		{
			if (WidthMask + 1 == GetPhysicalWidth())
			{
				column &= WidthMask;
			}
			else
			{
				column %= GetPhysicalWidth();
			}
		}
		return column;
	}

	// Returns the whole texture, stored in column-major order, in BGRA8 format
	const uint32_t* GetPixelsBgra()
	{
		int style = 2;
		if (Unlockeddata[2].LastUpdate == CurrentUpdate)
		{
			return static_cast<const uint32_t*>(Unlockeddata[style].Pixels);
		}
		else
		{
			UpdatePixels(style);
			return static_cast<const uint32_t*>(Unlockeddata[style].Pixels);
		}
	}

	// Returns the whole texture, stored in column-major order
	const uint8_t* GetPixels(int style)
	{
		if (Unlockeddata[style].LastUpdate == CurrentUpdate)
		{
			return static_cast<const uint8_t*>(Unlockeddata[style].Pixels);
		}
		else
		{
			UpdatePixels(style);
			return static_cast<const uint8_t*>(Unlockeddata[style].Pixels);
		}
	}

	const uint8_t *GetPixels(FRenderStyle style)
	{
		bool alpha = !!(style.Flags & STYLEF_RedIsAlpha);
		return GetPixels(alpha);
	}

	// Checks if the pixel data is loaded.
	bool CheckPixels() const
	{
		return V_IsTrueColor() ? PixelsBgra.Size() > 0 : Pixels.Size() > 0;
	}

	const uint8_t *GetColumn(FRenderStyle style, unsigned int column, const FSoftwareTextureSpan **spans_out)
	{
		bool alpha = !!(style.Flags & STYLEF_RedIsAlpha);
		return GetColumn(alpha, column, spans_out);
	}

	static int CurrentUpdate;
	void UpdatePixels(int style);

	virtual const uint32_t* GetPixelsBgraLocked();
	virtual const uint8_t* GetPixelsLocked(int style);
};

// A texture that returns a wiggly version of another texture.
class FWarpTexture : public FSoftwareTexture
{
	TArray<uint8_t> WarpedPixels[2];
	TArray<uint32_t> WarpedPixelsRgba;

	int bWarped = 0;
	uint64_t GenTime[3] = { UINT64_MAX, UINT64_MAX, UINT64_MAX };
	int WidthOffsetMultiplier, HeightOffsetMultiplier;  // [mxd]

public:
	FWarpTexture (FGameTexture *source, int warptype);

	const uint32_t *GetPixelsBgraLocked() override;
	const uint8_t *GetPixelsLocked(int style) override;
	bool CheckModified (int which) override;
	void GenerateBgraMipmapsFast();

private:

	int NextPo2 (int v); // [mxd]
	void SetupMultipliers (int width, int height); // [mxd]
};

class FSWCanvasTexture : public FSoftwareTexture
{
	void MakeTexture();
	void MakeTextureBgra();

	DCanvas *Canvas = nullptr;
	DCanvas *CanvasBgra = nullptr;

public:

	FSWCanvasTexture(FGameTexture* source);
	~FSWCanvasTexture();

	// Returns the whole texture, stored in column-major order
	const uint32_t *GetPixelsBgraLocked() override;
	const uint8_t *GetPixelsLocked(int style) override;

	virtual void Unload() override;
	void UpdatePixels(bool truecolor);

	DCanvas *GetCanvas() { GetPixels(0); return Canvas; }
	DCanvas *GetCanvasBgra() { GetPixelsBgra(); return CanvasBgra; }
	bool Mipmapped() override { return false; }

};

FSoftwareTexture* GetSoftwareTexture(FGameTexture* tex);
FSoftwareTexture* GetPalettedSWTexture(FTextureID texid, bool animate, bool checkcompat = false, bool allownull = false);
