/*  PCSX2 - PS2 Emulator for PCs
 *  Copyright (C) 2002-2010  PCSX2 Dev Team
 *
 *  PCSX2 is free software: you can redistribute it and/or modify it under the terms
 *  of the GNU Lesser General Public License as published by the Free Software Found-
 *  ation, either version 3 of the License, or (at your option) any later version.
 *
 *  PCSX2 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 PCSX2.
 *  If not, see <http://www.gnu.org/licenses/>.
 */

#pragma once
#include <deque>
#include "System/SysThreads.h"
#include "Gif.h"
#include "Vif.h"
#include "GS.h"

// FIXME common path ?
#include "Utilities/boost_spsc_queue.hpp"

struct GS_Packet;
extern void Gif_MTGS_Wait(bool isMTVU);
extern void Gif_FinishIRQ();
extern bool Gif_HandlerAD(u8* pMem);
extern bool Gif_HandlerAD_Debug(u8* pMem);
extern void Gif_AddBlankGSPacket(u32 size, GIF_PATH path);
extern void Gif_AddGSPacketMTVU     (GS_Packet& gsPack, GIF_PATH path);
extern void Gif_AddCompletedGSPacket(GS_Packet& gsPack, GIF_PATH path);
extern void Gif_ParsePacket(u8* data, u32 size, GIF_PATH path);
extern void Gif_ParsePacket(GS_Packet& gsPack, GIF_PATH path);

struct Gif_Tag {
	struct HW_Gif_Tag {
		u16 NLOOP	: 15;
		u16 EOP		: 1;
		u16 _dummy0	: 16;
		u32 _dummy1	: 14;
		u32 PRE		: 1;
		u32 PRIM	: 11;
		u32 FLG		: 2;
		u32 NREG	: 4;
		u32 REGS[2];
	} tag;

	u32  nLoop;		// NLOOP left to process
	u32  nRegs;		// NREG (1~16)
	u32  nRegIdx;	// Current nReg Index (packed mode processing)
	u32  len;		// Packet Length in Bytes (not including tag)
	u32  cycles;    // Time needed to process packet data in ee-cycles
	u8   regs[16];	// Regs
	bool hasAD;		// Has an A+D Write
	bool isValid;	// Tag is valid
	
	Gif_Tag() { Reset(); }
	Gif_Tag(u8* pMem, bool analyze = false) {
		setTag (pMem, analyze);
	}

	void Reset()  { memzero(*this); }
	u8   curReg() { return regs[nRegIdx&0xf]; }

	void packedStep() {
		if (nLoop > 0) {
			nRegIdx++;
			if (nRegIdx >= nRegs) {
				nRegIdx = 0;
				nLoop--;
			}
		}
	}

	void setTag(u8* pMem, bool analyze = false) {
		tag   = *(HW_Gif_Tag*)pMem;
		nLoop = tag.NLOOP;
		hasAD = false;
		nRegIdx = 0;
		isValid = 1;
		len = 0; // avoid uninitialized compiler warning
		switch(tag.FLG) {
			case GIF_FLG_PACKED:
				nRegs  = ((tag.NREG-1)&0xf) + 1;
				len    = (nRegs * tag.NLOOP)* 16;
				cycles = len << 1; // Packed Mode takes 2 ee-cycles
				if (analyze) analyzeTag();
				break;
			case GIF_FLG_REGLIST:
				nRegs  = ((tag.NREG-1)&0xf) + 1;
				len    =((nRegs * tag.NLOOP + 1) >> 1) * 16;
				cycles = len << 2; // Reg-list Mode takes 4 ee-cycles
				break;
			case GIF_FLG_IMAGE:
			case GIF_FLG_IMAGE2:
				nRegs  = 0;
				len    = tag.NLOOP * 16;
				cycles = len << 2; // Image Mode takes 4 ee-cycles
				tag.FLG = GIF_FLG_IMAGE;
				break;
			jNO_DEFAULT;
		}
	}

	void analyzeTag() {
		hasAD = false;
		u32 t = tag.REGS[0];
		u32 i = 0;
		u32 j = std::min<u32>(nRegs, 8);
		for(; i < j; i++) {
			regs[i] = t & 0xf;
			hasAD |= (regs[i] == GIF_REG_A_D);
			t >>= 4;
		}
		t = tag.REGS[1];
		j = nRegs;
		for(; i < j; i++) {
			regs[i] = t & 0xf;
			hasAD |= (regs[i] == GIF_REG_A_D);
			t >>= 4;
		}
	}
};

struct GS_Packet {
	// PERF note: this struct is copied various time in hot path. Don't add
	// new field

	u32  offset;     // Path buffer offset for start of packet
	u32  size;	     // Full size of GS-Packet
	s32  cycles;     // EE Cycles taken to process this GS packet
	s32  readAmount; // Dummy read-amount data needed for proper buffer calculations
	GS_Packet()  { Reset(); }
	void Reset() { memzero(*this); }
};

struct GS_SIGNAL {
	u32  data[2];
	bool queued;
	void Reset() { memzero(*this); }
};

struct GS_FINISH {
	bool gsFINISHFired;

	void Reset() { memzero(*this); }
};

static __fi void incTag(u32& offset, u32& size, u32 incAmount) {
	size   += incAmount;
	offset += incAmount;
}

struct Gif_Path_MTVU {
	u32   fakePackets; // Fake packets pending to be sent to MTGS
	GS_Packet fakePacket;
	// Set a size based on MTGS but keep a factor 2 to avoid too waste to much
	// memory overhead. Note the struct is instantied 3 times (for each gif
	// path)
	ringbuffer_base<GS_Packet, RingBufferSize / 2> gsPackQueue;
	Gif_Path_MTVU() { Reset(); }
	void Reset()    { fakePackets = 0;
		gsPackQueue.reset();
		fakePacket.Reset();
		fakePacket.size =~0u; // Used to indicate that its a fake packet
	}
};

struct Gif_Path {
	std::atomic<int> readAmount; // Amount of data MTGS still needs to read
	u8* buffer;		  // Path packet buffer
	u32 buffSize;	  // Full size of buffer
	u32 buffLimit;	  // Cut off limit to wrap around
	u32 curSize;	  // Used buffer in bytes
	u32 curOffset;	  // Offset of current gifTag
	u32 dmaRewind;    // Used by path3 when only part of a DMA chain is used
	Gif_Tag   gifTag; // Current GS Primitive tag
	GS_Packet gsPack; // Current GS Packet info
	GIF_PATH  idx;	  // Gif Path Index
	GIF_PATH_STATE state; // Path State
	Gif_Path_MTVU  mtvu;  // Must be last for saved states

	Gif_Path()  { Reset(); }
	~Gif_Path() { _aligned_free(buffer); }

	void Init(GIF_PATH _idx, u32 _buffSize, u32 _buffSafeZone) {
		idx       = _idx;
		buffSize  = _buffSize;
		buffLimit = _buffSize - _buffSafeZone;
		buffer    = (u8*)_aligned_malloc(buffSize, 16);
		Reset();
	}

	void Reset(bool softReset = false) {
		state = GIF_PATH_IDLE;
		if (softReset) {
			GUNIT_WARN("Gif Path %d - Soft Reset", idx+1);
			//gifTag.Reset();
			//gsPack.Reset();
			//curOffset = curSize;
			return;
		}
		mtvu.Reset();
		curSize     = 0;
		curOffset   = 0;
		readAmount  = 0;
		gifTag.Reset();
		gsPack.Reset();
	}

	bool isMTVU() const           { return !idx && THREAD_VU1; }
	s32 getReadAmount()           { return readAmount.load(std::memory_order_acquire) + gsPack.readAmount; }
	bool hasDataRemaining() const { return curOffset < curSize; }
	bool isDone() const           { return isMTVU() ? !mtvu.fakePackets : (!hasDataRemaining() && (state == GIF_PATH_IDLE || state == GIF_PATH_WAIT)); }

	// Waits on the MTGS to process gs packets
	void mtgsReadWait() {
		if (IsDevBuild) {
			DevCon.WriteLn(Color_Red,   "Gif Path[%d] - MTGS Wait! [r=0x%x]", idx+1, getReadAmount());
			Gif_MTGS_Wait(isMTVU());
			DevCon.WriteLn(Color_Green, "Gif Path[%d] - MTGS Wait! [r=0x%x]", idx+1, getReadAmount());
			return;
		}
		Gif_MTGS_Wait(isMTVU());
	}

	// Moves packet data to start of buffer
	void RealignPacket() {
		GUNIT_LOG("Path Buffer: Realigning packet!");
		s32 offset    = curOffset - gsPack.size;
		s32 sizeToAdd = curSize   - offset;
		s32 intersect = sizeToAdd - offset;
		if (intersect < 0) intersect = 0;
		for(;;) {
			s32 frontFree  = offset - getReadAmount();
			if (frontFree >= sizeToAdd - intersect) break;
			mtgsReadWait();
		}
		if (offset < (s32)buffLimit) { // Needed for correct readAmount values
			if (isMTVU()) gsPack.readAmount += buffLimit - offset;
			else Gif_AddBlankGSPacket(buffLimit - offset, idx);
		}
		//DevCon.WriteLn("Realign Packet [%d]", curSize - offset);
		if (intersect) memmove(buffer, &buffer[offset], curSize - offset);
		else       memcpy(buffer, &buffer[offset], curSize - offset);
		curSize      -= offset;
		curOffset     = gsPack.size;
		gsPack.offset = 0;
	}

	void CopyGSPacketData(u8* pMem, u32 size, bool aligned = false) {	
		if (curSize + size > buffSize) { // Move gsPack to front of buffer
			GUNIT_LOG("CopyGSPacketData: Realigning packet!");
			RealignPacket();
		}
		for(;;) {
			s32 offset  = curOffset - gsPack.size;
			s32 readPos = offset    - getReadAmount();
			if (readPos >= 0) break; // MTGS is reading in back of curOffset
			if ((s32)buffLimit + readPos > (s32)curSize + (s32)size) break; // Enough free front space
			mtgsReadWait(); // Let MTGS run to free up buffer space
		}
		pxAssertDev(curSize+size<=buffSize, "Gif Path Buffer Overflow!");
		memcpy (&buffer[curSize], pMem, size);
		curSize     += size;
	}

	// If completed a GS packet (with EOP) then set done to true
	// MTVU: This function only should be called called on EE thread
	GS_Packet ExecuteGSPacket(bool &done) {
		if (mtvu.fakePackets) { // For MTVU mode...
			mtvu.fakePackets--;
			done = true;
			return mtvu.fakePacket;
		}
		pxAssert(!isMTVU());
		for(;;) {
			if (!gifTag.isValid) { // Need new Gif Tag
				// We don't have enough data for a Gif Tag
				if (curOffset + 16 > curSize) {
					//GUNIT_LOG("Path Buffer: Not enough data for gif tag! [%d]", curSize-curOffset);
					return gsPack;
				}

				// Move packet to start of buffer
				if (curOffset > buffLimit) {
					RealignPacket();
				}

				gifTag.setTag(&buffer[curOffset], 1);

				state = (GIF_PATH_STATE)(gifTag.tag.FLG + 1);

				// We don't have enough data for a complete GS packet
				if(!gifTag.hasAD && curOffset + 16 + gifTag.len > curSize) {
					gifTag.isValid = false; // So next time we test again
					return gsPack;
				}

				incTag(curOffset, gsPack.size, 16); // Tag Size
				gsPack.cycles += 2 + gifTag.cycles; // Tag + Len ee-cycles
			}

			if (gifTag.hasAD) { // Only can be true if GIF_FLG_PACKED
				bool dblSIGNAL = false;
				while(gifTag.nLoop && !dblSIGNAL) {
					if (curOffset + 16 > curSize) return gsPack; // Exit Early
					if (gifTag.curReg() == GIF_REG_A_D) {
						if (!isMTVU()) dblSIGNAL = Gif_HandlerAD(&buffer[curOffset]);
					}
					incTag(curOffset, gsPack.size, 16); // 1 QWC
					gifTag.packedStep();
				}
				if (dblSIGNAL && !(gifTag.tag.EOP && !gifTag.nLoop)) return gsPack; // Exit Early
			}
			else incTag(curOffset, gsPack.size, gifTag.len); // Data length

			// Reload gif tag next loop
			gifTag.isValid = false;

			if (gifTag.tag.EOP) {
				GS_Packet t = gsPack;
				done = true;

				dmaRewind = 0;
				
				gsPack.Reset();
				gsPack.offset = curOffset;
				
				//Path 3 Masking is timing sensitive, we need to simulate its length! (NFSU2/Outrun 2006)
				
				if((gifRegs.stat.APATH-1) == GIF_PATH_3)
				{
					state = GIF_PATH_WAIT;

					if(curSize - curOffset > 0 && (gifRegs.stat.M3R || gifRegs.stat.M3P))
					{
						//Including breaking packets early (Rewind DMA to pick up where left off)
						//but only do this when the path is masked, else we're pointlessly slowing things down.
						dmaRewind = curSize - curOffset;
						curSize = curOffset;
					}	
				}
				else 
					state  = GIF_PATH_IDLE;

				return t; // Complete GS packet
			}
		}
	}

	// MTVU: Gets called on VU XGkicks on MTVU thread
	void ExecuteGSPacketMTVU() {
		// Move packet to start of buffer
		if (curOffset > buffLimit) {
			RealignPacket();
		}
		if (IsDevBuild) { // We check the packet to see if it actually
			for(;;) {     // needed to be processed by pcsx2...
				if (curOffset + 16 > curSize) break;
				gifTag.setTag(&buffer[curOffset], 1);
				
				if(!gifTag.hasAD && curOffset + 16 + gifTag.len > curSize) break;
				incTag(curOffset, gsPack.size, 16); // Tag Size
				
				if (gifTag.hasAD) { // Only can be true if GIF_FLG_PACKED
					while(gifTag.nLoop) {
						if (curOffset + 16 > curSize) break; // Exit Early
						if (gifTag.curReg() == GIF_REG_A_D) {
							pxAssert(!Gif_HandlerAD_Debug(&buffer[curOffset]));
						}
						incTag(curOffset, gsPack.size, 16); // 1 QWC
						gifTag.packedStep();
					}
				}
				else incTag(curOffset, gsPack.size, gifTag.len); // Data length
				if (curOffset >= curSize) break;
				if (gifTag.tag.EOP)       break;
			}
			pxAssert(curOffset == curSize);
			gifTag.isValid = false;
		}
		else {
			// We assume every packet is a full GS Packet
			// And we don't process anything on pcsx2 side
			gsPack.size += curSize - curOffset;
			curOffset    = curSize;
		}
	}

	// MTVU: Gets called after VU1 execution on MTVU thread
	void FinishGSPacketMTVU() {
		// Performance note: fetch_add atomic operation might create some stall for atomic
		// operation in gsPack.push
		readAmount.fetch_add(gsPack.size + gsPack.readAmount, std::memory_order_acq_rel);
		while (!mtvu.gsPackQueue.push(gsPack))
			;

		gsPack.Reset();
		gsPack.offset = curOffset;
	}

	// MTVU: Gets called by MTGS thread
	GS_Packet GetGSPacketMTVU() {
		// FIXME is the error path useful ?
		if (!mtvu.gsPackQueue.empty()) {
			return mtvu.gsPackQueue.front();
		}

		Console.Error("MTVU: Expected gsPackQueue to have elements!");
		pxAssert(0);
		return GS_Packet(); // gsPack.size will be 0
	}

	// MTVU: Gets called by MTGS thread
	void PopGSPacketMTVU() {
		mtvu.gsPackQueue.pop();
	}

	// MTVU: Returns the amount of pending
	// GS Packets that MTGS hasn't yet processed
	u32 GetPendingGSPackets() {
		return mtvu.gsPackQueue.size();
	}
};

struct Gif_Unit {
	Gif_Path   gifPath[3];
	GS_SIGNAL  gsSIGNAL; // Stalling Signal
	GS_FINISH  gsFINISH; // Finish Signal
	tGIF_STAT& stat;
	GIF_TRANSFER_TYPE lastTranType; // Last Transfer Type

	Gif_Unit() : gsSIGNAL(), gsFINISH(), stat(gifRegs.stat), lastTranType(GIF_TRANS_INVALID) {
		gifPath[0].Init(GIF_PATH_1, _1mb*9, _1mb  + _1kb);
		gifPath[1].Init(GIF_PATH_2, _1mb*9, _1mb  + _1kb);
		gifPath[2].Init(GIF_PATH_3, _1mb*9, _1mb  + _1kb);
	}

	// Enable softReset when resetting during game emulation
	void Reset(bool softReset = false) {
		GUNIT_WARN(Color_Red, "Gif Unit Reset!!! [soft=%d]", softReset);
		ResetRegs();
		gsSIGNAL.Reset();
		gsFINISH.Reset();
		gifPath[0].Reset(softReset);
		gifPath[1].Reset(softReset);
		gifPath[2].Reset(softReset);
		if(!softReset) {
			lastTranType = GIF_TRANS_INVALID;
		}
		//If the VIF has paused waiting for PATH3, recheck it after the reset has occurred (Eragon)
		if (vif1Regs.stat.VGW) {
			if (!(cpuRegs.interrupt & (1 << DMAC_VIF1)))
				CPU_INT(DMAC_VIF1, 1);
		}
	}

	// Resets Gif HW Regs
	void ResetRegs() {
		gifRegs.stat.reset();
		gifRegs.ctrl.reset();
		gifRegs.mode.reset();
	}

	// Adds a finished GS Packet to the MTGS ring buffer
	__fi void AddCompletedGSPacket(GS_Packet& gsPack, GIF_PATH path) {
		if (gsPack.size==~0u) Gif_AddGSPacketMTVU     (gsPack, path);
		else                  Gif_AddCompletedGSPacket(gsPack, path);
		if (PRINT_GIF_PACKET) Gif_ParsePacket(gsPack, path);
	}

	// Returns GS Packet Size in bytes
	u32 GetGSPacketSize(GIF_PATH pathIdx, u8* pMem, u32 offset = 0, u32 size = ~0u) {
		u32 memMask = pathIdx ? ~0u : 0x3fffu;
		u32 curSize = 0;
		for(;;) {
			Gif_Tag gifTag(&pMem[offset & memMask]);
			incTag(offset, curSize, 16 + gifTag.len); // Tag + Data length
			if (pathIdx == GIF_PATH_1 && curSize >= 0x4000) {
				DevCon.Warning("Gif Unit - GS packet size exceeded VU memory size!");
				return 0; // Bios does this... (Fixed if you delay vu1's xgkick by 103 vu cycles)
			}
			if (curSize >= size) return size;
			if (gifTag.tag.EOP)  return curSize;
		}
	}

	// Specify the transfer type you are initiating
	// The return value is the amount of data (in bytes) that was processed
	// If transfer cannot take place at this moment the return value is 0
	u32 TransferGSPacketData(GIF_TRANSFER_TYPE tranType, u8* pMem, u32 size, bool aligned=false) {

		if (THREAD_VU1) {
			Gif_Path& path1 = gifPath[GIF_PATH_1];
			if (tranType == GIF_TRANS_XGKICK) { // This is on the MTVU thread
				path1.CopyGSPacketData(pMem, size, aligned);
				path1.ExecuteGSPacketMTVU();
				return size;
			}
			if (tranType == GIF_TRANS_MTVU) {   // This is on the EE thread
				path1.mtvu.fakePackets++;
				if (CanDoGif()) Execute(false, true);
				return 0;
			}
		}

		GUNIT_LOG("%s - [path=%d][size=%d]", Gif_TransferStr[(tranType>>8)&0xf], (tranType&3)+1, size);
		if (size == 0)  { GUNIT_WARN("Gif Unit - Size == 0"); return 0; }
		if(!CanDoGif()) { GUNIT_WARN("Gif Unit - Signal or PSE Set or Dir = GS to EE"); }
		//pxAssertDev((stat.APATH==0) || checkPaths(1,1,1), "Gif Unit - APATH wasn't cleared?");
		lastTranType = tranType;

		if (tranType == GIF_TRANS_FIFO) {
			if(!CanDoPath3()) DevCon.Warning("Gif Unit - Path 3 FIFO transfer while !CanDoPath3()");
		}
		if (tranType == GIF_TRANS_DMA) {
			if(!CanDoPath3())   { if (!Path3Masked()) stat.P3Q = 1; return 0; } // DMA Stall
			//if (stat.P2Q) DevCon.WriteLn("P2Q while path 3");
		}
		if (tranType == GIF_TRANS_XGKICK) {
			if(!CanDoPath1())   { stat.P1Q = 1; } // We always buffer path1 packets
		}
		if (tranType == GIF_TRANS_DIRECT) {
			if(!CanDoPath2())   { stat.P2Q = 1; return 0; } // Direct Stall 
		}
		if (tranType == GIF_TRANS_DIRECTHL) {
			if(!CanDoPath2HL()) { stat.P2Q = 1; return 0; } // DirectHL Stall
		}

		gifPath[tranType&3].CopyGSPacketData(pMem, size, aligned);
		size -= Execute(tranType == GIF_TRANS_DMA, false);
		return size;
	}

	// Checks path activity for the given paths
	// Returns an int with a bit enabled if the corresponding
	// path is not finished (needs more data/processing for an EOP)
	__fi int checkPaths(bool p1, bool p2, bool p3, bool checkQ=false) {
		int ret = 0;
		ret |= (p1 && !gifPath[GIF_PATH_1].isDone()) << 0;
		ret |= (p2 && !gifPath[GIF_PATH_2].isDone()) << 1;
		ret |= (p3 && !gifPath[GIF_PATH_3].isDone()) << 2;
		return ret | (checkQ ? checkQueued(p1,p2,p3) : 0);
	}

	__fi int checkQueued(bool p1, bool p2, bool p3) {
		int ret = 0;
		ret |= (p1 && stat.P1Q) << 0;
		ret |= (p2 && stat.P2Q) << 1;
		ret |= (p3 && stat.P3Q) << 2;
		return ret;
	}

	// Send processed GS Primitive(s) to the MTGS thread
	// Note: Only does so if current path fully completed all
	// of its given gs primitives (but didn't upload them yet)
	void FlushToMTGS() {
		if (!stat.APATH) return;
		Gif_Path& path = gifPath[stat.APATH-1];
		if (path.gsPack.size && !path.gifTag.isValid) {
			AddCompletedGSPacket(path.gsPack, (GIF_PATH)(stat.APATH-1));
			path.gsPack.offset = path.curOffset;
			path.gsPack.size   = 0;
		}
	}

	// Processes gif packets and performs path arbitration
	// on EOPs or on Path 3 Images when IMT is set.
	int Execute(bool isPath3, bool isResume) {
		if (!CanDoGif()) { DevCon.Error("Gif Unit - Signal or PSE Set or Dir = GS to EE"); return 0; }
		bool didPath3 = false;
		int curPath = stat.APATH > 0 ? stat.APATH-1 : 0; //Init to zero if no path is already set.
		gifPath[2].dmaRewind = 0;
		stat.OPH = 1;
		
		for(;;) {
			if (stat.APATH) { // Some Transfer is happening
				Gif_Path& path   = gifPath[stat.APATH-1];
				bool done = false;
				GS_Packet gsPack = path.ExecuteGSPacket(done);
				if(!done) {
					if (stat.APATH == 3 && CanDoP3Slice() && !gsSIGNAL.queued) {
						if(!didPath3 && /*!Path3Masked() &&*/ checkPaths(1,1,0)) { // Path3 slicing
							didPath3 = true;
							stat.APATH = 0;
							stat.IP3   = 1;
							GUNIT_LOG(Color_Magenta, "Gif Unit - Path 3 slicing arbitration");
							if (gsPack.size > 16) { // Packet had other tags which we already processed
								u32 subOffset = path.gifTag.isValid ? 16 : 0; // if isValid, image-primitive not finished
								gsPack.size  -= subOffset; // Remove the image-tag (should be last thing read)
								AddCompletedGSPacket(gsPack, GIF_PATH_3); // Consider current packet complete
								path.gsPack.Reset(); // Reset gs packet info
								path.curOffset     -= subOffset;      // Start the next GS packet at the image-tag
								path.gsPack.offset  = path.curOffset; // Set to image-tag
								path.gifTag.isValid = false; // Reload tag next ExecuteGSPacket()
								pxAssert((s32)path.curOffset >= 0);
								pxAssert(path.state == GIF_PATH_IMAGE);
								GUNIT_LOG(Color_Magenta, "Gif Unit - Sending path 3 sliced gs packet!");
							}
							continue;
						}
					}
					//FlushToMTGS();
					//DevCon.WriteLn("Incomplete GS Packet for path %d, size=%d", stat.APATH, gsPack.size);
					break; // Not finished with GS packet
				}
				//DevCon.WriteLn("Adding GS Packet for path %d", stat.APATH);
				if (gifPath[curPath].state == GIF_PATH_WAIT || gifPath[curPath].state == GIF_PATH_IDLE) {
					AddCompletedGSPacket(gsPack, (GIF_PATH)(stat.APATH - 1));
				}
				
			}
			if (!gsSIGNAL.queued && !gifPath[0].isDone()) {
				stat.APATH = 1;
				stat.P1Q = 0;
				curPath = 0;
			}
			else if (!gsSIGNAL.queued && !gifPath[1].isDone()) {
				stat.APATH = 2;
				stat.P2Q = 0;
				curPath = 1;
			}
			else if (!gsSIGNAL.queued && !gifPath[2].isDone() && !Path3Masked()) {
				stat.APATH = 3;
				stat.P3Q = 0;
				stat.IP3 = 0;
				curPath = 2;
			}
			else {
				if(isResume || curPath == 0) {
					stat.APATH = 0;
					stat.OPH = 0;
				}

				break;
			}
		}
		//Some loaders/Refresh Rate selectors and things dont issue "End of Packet" commands
		//So we look and see if the end of the last tag is all there, if so, stick it in the buffer for the GS :)
		//(Invisible Screens on Terminator 3 and Growlanser 2/3)
		if(gifPath[curPath].curOffset == gifPath[curPath].curSize)
		{
			FlushToMTGS();
		}

		Gif_FinishIRQ();

		//Path3 can rewind the DMA, so we send back the amount we go back!
		if(isPath3)
			return gifPath[2].dmaRewind;
		else
			return 0;
	}

	// XGkick
	bool CanDoPath1() const {
		return (stat.APATH == 0 || stat.APATH == 1 || (stat.APATH == 3 && CanDoP3Slice())) && CanDoGif();
	}
	// Direct
	bool CanDoPath2() const {
		return (stat.APATH == 0 || stat.APATH == 2 || (stat.APATH == 3 && CanDoP3Slice())) && CanDoGif();
	}
	// DirectHL
	bool CanDoPath2HL() const {
		return (stat.APATH == 0 || stat.APATH == 2) && CanDoGif();
	}
	// Gif DMA
	bool CanDoPath3() const {
		return((stat.APATH == 0 && !Path3Masked()) || stat.APATH == 3) && CanDoGif();
	}

	bool CanDoP3Slice()const { return stat.IMT == 1 && gifPath[GIF_PATH_3].state == GIF_PATH_IMAGE; }
	bool CanDoGif() const    { return stat.PSE == 0 && stat.DIR == 0 && gsSIGNAL.queued == 0; }
	//Mask stops the next packet which hasnt started from transferring
	bool Path3Masked() const { return ((stat.M3R || stat.M3P) && (gifPath[GIF_PATH_3].state == GIF_PATH_IDLE || gifPath[GIF_PATH_3].state == GIF_PATH_WAIT)); }

	void PrintInfo(bool printP1=1, bool printP2=1, bool printP3=1) {
		u32 a = checkPaths(1,1,1), b = checkQueued(1,1,1);
		(void)a; // Don't warn about unused variable
		(void)b;
		GUNIT_LOG("Gif Unit - LastTransfer = %s, Paths = [%d,%d,%d], Queued = [%d,%d,%d]",
				   Gif_TransferStr[(lastTranType>>8)&0xf],
				   !!(a&1),!!(a&2),!!(a&4),!!(b&1),!!(b&2),!!(b&4));
		GUNIT_LOG("Gif Unit - [APATH = %d][Signal = %d][PSE = %d][DIR = %d]",
			       stat.APATH, gsSIGNAL.queued, stat.PSE, stat.DIR);
		GUNIT_LOG("Gif Unit - [CanDoGif = %d][CanDoPath3 = %d][CanDoP3Slice = %d]",
				   CanDoGif(), CanDoPath3(), CanDoP3Slice());
		if (printP1) PrintPathInfo(GIF_PATH_1);
		if (printP2) PrintPathInfo(GIF_PATH_2);
		if (printP3) PrintPathInfo(GIF_PATH_3);
	}

	void PrintPathInfo(GIF_PATH path) {
		GUNIT_LOG("Gif Path %d - [hasData = %d][state = %d]", path,
			       gifPath[path].hasDataRemaining(), gifPath[path].state);
	}
};

extern Gif_Unit gifUnit;
