from inc_noesis import *
import os

#set to True in order to enable the pvr reordering context menu tools
ENABLE_PVR_REORDERING_TOOL = False

#registerNoesisTypes is called by Noesis to allow the script to register formats.
#Do not implement this function in script files unless you want them to be dedicated format modules!
def registerNoesisTypes():
	handle = noesis.register("PVRTC Image", ".pvr")
	noesis.setHandlerTypeCheck(handle, pvrCheckType)
	noesis.setHandlerLoadRGBA(handle, pvrLoadRGBA)
	if ENABLE_PVR_REORDERING_TOOL:
		toolHandle = noesis.registerTool("PVR to Morton", pvrToMortonToolMethod, "Converts PVR block order from linear to Morton.")
		noesis.setToolFlags(toolHandle, noesis.NTOOLFLAG_CONTEXTITEM)
		noesis.setToolVisibleCallback(toolHandle, pvrReorderContextVisible)
		toolHandle = noesis.registerTool("PVR to Linear", pvrToLinearToolMethod, "Converts PVR block order from Morton to linear.")
		noesis.setToolFlags(toolHandle, noesis.NTOOLFLAG_CONTEXTITEM)
		noesis.setToolVisibleCallback(toolHandle, pvrReorderContextVisible)
	
	return 1


class PVRTCImage:
	def __init__(self, reader):
		self.reader = reader

	def parseV2(self):
		self.reader.seek(4, NOESEEK_ABS)
		self.w = self.reader.readInt()
		self.h = self.reader.readInt()
		self.fmt = self.reader.readInt()
		self.flags = self.reader.readInt()
		self.isPVR2 = False
		#pvr 2bpp = 24, pvr 4bpp = 25
		#pvr2 bpp = 28, pvr2 4bpp = 29
		pixelFormat = self.flags & 255
		if pixelFormat == 28 or pixelFormat == 29:
			self.isPVR2 = True #pvr2
		elif pixelFormat != 24 and pixelFormat != 25:
			return 0 #not supported
		self.dataSize = self.reader.readInt()
		self.bpp = self.reader.readInt()
		self.reader.seek(44, NOESEEK_ABS)
		if self.reader.readInt() != 0x21525650: #"PVR!"
			return 0
		if self.flags & 0x10000:
			self.flip = 1
		self.dataOfs = 52
		return 1

	def parseV3(self):
		if self.reader.getSize() < 68:
			return 0
		self.reader.seek(4, NOESEEK_ABS)
		self.asz = self.reader.readInt()
		self.fmt = self.reader.readInt()
		
		self.isPVR2 = False
		if self.fmt == 0 or self.fmt == 1:
			self.bpp = 2
		elif self.fmt == 2 or self.fmt == 3:
			self.bpp = 4
		elif self.fmt == 4:
			self.bpp = 2
			self.isPVR2 = True
		elif self.fmt == 5:
			self.bpp = 4
			self.isPVR2 = True
		else:
			return 0
		self.fmtTag = self.reader.readInt()
		self.colorSpace = self.reader.readInt()
		self.chanType = self.reader.readInt()
		
		#self.reader.seek(24, NOESEEK_ABS)
		self.h = self.reader.readInt()
		self.w = self.reader.readInt()
		self.depth = self.reader.readInt()
		self.surfCount = self.reader.readInt()
		self.faceCount = self.reader.readInt()
		self.mipCount = self.reader.readInt()
		
		#self.reader.seek(48, NOESEEK_ABS)
		self.metaLen = self.reader.readInt()
		if self.metaLen > 4:
			metaDataEnd = self.reader.getOffset() + self.metaLen
			while self.reader.getOffset() < (metaDataEnd - 4):
				metaFourCC = self.reader.readInt()
				if metaFourCC != 0x3525650:
					break
				metaKey = self.reader.readInt()
				metaKeySize = self.reader.readInt()
				metaKeyData = self.reader.readBytes(metaKeySize)
				if metaKey == 3: #logical orientation - todo, support flipping other axes
					if metaKeyData[1] != 0:
						self.flip = 1
				
		self.dataOfs = 52+self.metaLen
		return 1

	def parseImageInfo(self):
		if self.reader.getSize() < 52:
			return 0

		self.flip = 0
		self.ver = 0
		self.rawVer = self.reader.readInt()
		if self.rawVer == 52:
			self.ver = 2
			return self.parseV2()
		elif self.rawVer == 0x3525650:
			self.ver = 3
			return self.parseV3()

		return 0

	def decode(self):
		self.reader.seek(self.dataOfs, NOESEEK_ABS)
		d = 4 if self.bpp == 2 else 2
		decodeFlags = noesis.PVRTC_DECODE_PVRTC2 if self.isPVR2 else 0
		
		#not sure if this is correct, but v3 pvrtc2's produced by PvrTexTool seem to not be morton ordered
		if self.ver >= 3 and self.isPVR2:
			decodeFlags |= noesis.PVRTC_DECODE_LINEARORDER
		
		#this seems to be PvrTexTool's decoding behavior, but it's wrong - verified on hardware with the SGX543
		#if self.bpp == 4 and self.isPVR2:
		#	decodeFlags |= noesis.PVRTC_DECODE_PVRTC2_NO_OR_WITH_0_ALPHA
		
		r = rapi.imageDecodePVRTC(self.reader.readBytes((self.w*self.h) // d), self.w, self.h, self.bpp, decodeFlags)
		if self.flip == 1:
			r = rapi.imageFlipRGBA32(r, self.w, self.h, 0, 1)
		return r


#noesis passes in data as a bytearray
def pvrCheckType(data):
	pvr = PVRTCImage(NoeBitStream(data))
	if pvr.parseImageInfo() != 1:
		return 0
	if pvr.bpp != 2 and pvr.bpp != 4:
		return 0 #not supported in this script
	return 1

#texList should be filled with NoeTexture objects, which will be read by Noesis
def pvrLoadRGBA(data, texList):
	pvr = PVRTCImage(NoeBitStream(data))
	if pvr.parseImageInfo() != 1:
		return 0

	texList.append(NoeTexture("pvrtex", pvr.w, pvr.h, pvr.decode(), noesis.NOESISTEX_RGBA32))
	return 1

#see if the pvr reorder context tool should be visible
def pvrReorderContextVisible(toolIndex, selectedFile):
	if (selectedFile is None or
		os.path.splitext(selectedFile)[1].lower() != ".pvr" or
		(noesis.getFormatExtensionFlags(os.path.splitext(selectedFile)[1]) & noesis.NFORMATFLAG_IMGREAD) == 0):
		return 0
	return 1

#perform the block order change
def pvrReorderBlocks(reorderMethod):
	srcPath = noesis.getSelectedFile()
	if srcPath is None or os.path.exists(srcPath) is not True:
		noesis.messagePrompt("Selected file isn't readable through the standard filesystem.")
		return 0

	noeMod = noesis.instantiateModule()
	noesis.setModuleRAPI(noeMod)

	with open(srcPath, "rb") as fSrc:
		pvrData = fSrc.read()
		pvr = PVRTCImage(NoeBitStream(pvrData))
		if pvr.parseImageInfo() == 1:
			pvrImageData = pvrData[pvr.dataOfs:]
			pvrImageDataSize = len(pvrImageData)
			blockW = 8 if pvr.bpp == 2 else 4
			blockH = 4
			blocksW = pvr.w // blockW
			blocksH = pvr.h // blockH
			#possible todo - support individual mips/faces
			pvrImageData = reorderMethod(pvrImageData, blocksW, blocksH, 8, 2)
			
			dstPathDefault = os.path.splitext(srcPath)[0] + "_reordered.pvr"
			dstPath = noesis.userPrompt(noesis.NOEUSERVAL_SAVEFILEPATH, "Save File", "Select destination for the reordered PVR file.", dstPathDefault, None)
			if dstPath is not None:	
				with open(dstPath, "wb") as fDst:
					fDst.write(pvrData[:pvr.dataOfs])
					fDst.write(pvrImageData)
	noesis.freeModule(noeMod)
	return 0

#to morton method
def pvrToMortonToolMethod(toolIndex):
	return pvrReorderBlocks(rapi.imageToMortonOrder)

#to linear method
def pvrToLinearToolMethod(toolIndex):
	return pvrReorderBlocks(rapi.imageFromMortonOrder)
