#!/usr/bin/python3

import sys
import math
import PIL
from PIL import Image
from numpy import asarray

def labelFromCharXY(prefix,charFirst, charX, numCharX, charY):
	charIndex = charY*numCharX + charX
	currChar = chr(charIndex+charFirst)
	return "{:s}char{:d}".format(prefix,ord(currChar))


def stackAdvance(bytes):
	if (bytes==0):
		return
		
	print ("\ttsc")
	if (bytes==1):
		print ("\tdec")
	elif (bytes==2):
		print ("\tdec")		# For two bytes, a double-DEC is still faster than SEC/SBC
		print ("\tdec")
	else:
		print ("\tsec")
		print ("\tsbc #%d" % bytes)
	
	print ("\ttcs")
	
	
def main(argv):
	CHAR_WIDTH = int(argv[0])
	CHAR_HEIGHT = int(argv[1])
	CHAR_FIRST = int(argv[2])
	CHROMA = int(argv[3])
	prefix = argv[4]
	image = Image.open(argv[5])
	
	pixels = asarray(image)
	numCharX = (int)(image.size[0]/CHAR_WIDTH)
	numCharY = (int)(image.size[1]/CHAR_HEIGHT)
		
	# Generate wrapper code
	addWrapperCode(prefix,CHAR_WIDTH,CHAR_FIRST)
	
	# Generate jump table for glyphs
	print ("%scharacterJumpTable:" % prefix)
	for charY in range(0,numCharY):
		for charX in range(0,numCharX):
			print ("\t.addr %s" % labelFromCharXY(prefix,CHAR_FIRST,charX,numCharX,charY))
	print ("\n; Chroma Key is $%x\n" % CHROMA)
	
	# Generate code for each glyph
	for charY in range(0,numCharY):
		for charX in range(0,numCharX):
			
			print ("%s:\n" % labelFromCharXY(prefix,CHAR_FIRST,charX,numCharX,charY), end="")
			
			# Header for each rendering operation
			print ("\ttya")		# Transfer character VRAM position from Y to stack pointer
			print ("\ttcs")
			
			# Iterate through all the pixels
			charOriginX = charX*CHAR_WIDTH
			charOriginY = charY*CHAR_HEIGHT
			pendingStackMove = 0
				
			for charRow in reversed(range(0,CHAR_HEIGHT)):
				
				# Print a comment to make generated source easier to understand
				print ("\t; Line %d, Pixel values: " % charRow, end="")
				for grouping in range(0,CHAR_WIDTH,4):
					print("%x%x%x%x " %
						(pixels[charOriginY+charRow][charOriginX+0+grouping],
						pixels[charOriginY+charRow][charOriginX+1+grouping],
						pixels[charOriginY+charRow][charOriginX+2+grouping],
						pixels[charOriginY+charRow][charOriginX+3+grouping]), end="")
				print ("")
					
				nextRowDelta = 160
				localStackList = []
				rowPushTotal = 0	# Row-right relative
				
				for charCol in reversed(range(0,CHAR_WIDTH,4)):
					nibbles = [pixels[charOriginY+charRow][charOriginX+charCol],
								pixels[charOriginY+charRow][charOriginX+charCol+1],
								pixels[charOriginY+charRow][charOriginX+charCol+2],
								pixels[charOriginY+charRow][charOriginX+charCol+3]]
					
					word = nibbles[2]<<12 | nibbles[3]<<8 | nibbles[0]<<4 | nibbles[1]
					requiredStackIndex = charCol/2
					
					if (nibbles[0]==CHROMA and nibbles[1]==CHROMA and nibbles[2]==CHROMA and nibbles[3]==CHROMA):
						# Case 1 : All chroma, so let stack advance with no work
						pass
						
					elif (nibbles[0]!=CHROMA and nibbles[1]!=CHROMA and nibbles[2]!=CHROMA and nibbles[3]!=CHROMA):
						# Case 2 : No chroma, so fast push of all four pixels
						offsetNeeded = (CHAR_WIDTH/2-requiredStackIndex) - rowPushTotal - 2
						
						if (offsetNeeded>0):
							stackAdvance(offsetNeeded+pendingStackMove)	# Advance stack to position needed for our two byte push
							pendingStackMove = 0
							nextRowDelta -= offsetNeeded
						else:
							stackAdvance(pendingStackMove)		# First thing we did wasn't a stack move, so apply previous row pending first
							pendingStackMove = 0
							offsetNeeded=0
						print ("\tpea $%04x" % word)
						nextRowDelta -= 2
						rowPushTotal += (2+offsetNeeded)
					else:
						# Case 3 : Mixed chroma, so we need to and-in mask and or-in sprite.
						#  These pixels are saved until the end of the row, then backfilled with stack-relative addressing
						mask = 0xFFFF
						if (nibbles[0]!=CHROMA):
							mask = mask & 0xFF0F
						if (nibbles[1]!=CHROMA):
							mask = mask & 0xFFF0
						if (nibbles[2]!=CHROMA):
							mask = mask & 0x0FFF
						if (nibbles[3]!=CHROMA):
							mask = mask & 0xF0FF
						
						sprite = word
						if (nibbles[0]==CHROMA):
							sprite = sprite & 0xFF0F
						if (nibbles[1]==CHROMA):
							sprite = sprite & 0xFFF0
						if (nibbles[2]==CHROMA):
							sprite = sprite & 0x0FFF
						if (nibbles[3]==CHROMA):
							sprite = sprite & 0xF0FF
						
						localStackEntry = [requiredStackIndex,mask,sprite]
						localStackList.append(localStackEntry)
						
				# Process any local stack-relative work we accumulated
				if len(localStackList) > 0:
					if (rowPushTotal < CHAR_WIDTH/2):			# Get stack pointer to end of row if needed
						cleanupPush = CHAR_WIDTH/2-rowPushTotal
						stackAdvance(cleanupPush + pendingStackMove)
						pendingStackMove=0
						rowPushTotal = CHAR_WIDTH/2
						nextRowDelta -= cleanupPush
						
					extraReach = rowPushTotal - CHAR_WIDTH/2 + 1		# Amount to "reach back" from one byte past end of row so LDA/STA can fill in skipped pixels
					for stackEntry in localStackList:
						print ("\tlda %d,S" % (stackEntry[0] + extraReach))				# Blend mask, sprite, and background
						print ("\tand #$%04x" % stackEntry[1])
						print ("\tora #$%04x" % stackEntry[2])
						print ("\tsta %d,S" % (stackEntry[0] + extraReach))
						
				# Advance stack pointer to next row
				pendingStackMove += nextRowDelta		# Save this stack move for next row, because we can often combine them
				
			# Footer for each rendering operation
			print ("\tjmp renderCharJumpReturn_%s\n" % prefix)
	return

def scaleForFontWidth(charWidth):
	output = ""
	shiftCount = math.log(charWidth/2,2)
	for i in range(0,(int)(shiftCount)):
		output = output + "\tasl\n"
	return output
	
def addWrapperCode(prefix, charWidth, firstChar):
	code = """

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; renderString_{:s}
;
; Draws a Pascal string for font "{:s}"
;
; PARAML0 = Pointer to string
; Y = VRAM position of lower left corner of string at which to draw
;
; Trashes SCRATCHL,X,Y,A
;
renderString_{:s}:
	sty SCRATCHL	; Cache VRAM position

	plb				; Temporarily revert to caller's DBR to access their pointer
	BITS8
	lda (PARAML0)
	tax
	BITS16
	phb

	; Advance VRAM pointer to end of string
{:s}
	clc
	adc SCRATCHL
	dec
	sta SCRATCHL
	
renderStringLoop_{:s}:

	; Fetch and render next character in string
	txy
	lda #0
	plb				; Temporarily revert to caller's DBR to access their pointer
	BITS8A
	lda (PARAML0),y
	BITS16
	phb
	ldy SCRATCHL
	jsr renderChar_{:s}

	dex
	beq renderStringDone_{:s}

	; Calculate VRAM pointer for position of next character
	lda SCRATCHL
	sec
	sbc #{:d}/2					; Width of one char in bytes
	sta SCRATCHL
	bra renderStringLoop_{:s}

renderStringDone_{:s}:
	jmp renderStringReturn

.export renderString_{:s}

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; renderChar_{:s}
;
; Draws a single character
;
; A = ASCII code to draw
; Y = VRAM position of lower right corner at which to draw
;
renderChar_{:s}:
	SAVE_AXY

	sec
	sbc #{:d}		; ASCII code of first char in font sheet
	asl
	tax
	FASTGRAPHICS

	jmp ({:s}characterJumpTable,x)

renderCharJumpReturn_{:s}:	; Compiled glyphs jump back here. Can't rts because stack is turboborked
	SLOWGRAPHICS

	RESTORE_AXY
	rts

""".format(prefix,prefix,prefix,scaleForFontWidth(charWidth),prefix,prefix,prefix,charWidth,prefix,prefix,prefix,prefix,prefix,firstChar,prefix,prefix)
	print (code)
	return


if __name__ == "__main__":
	main(sys.argv[1:])


