#!/usr/bin/python

frame_marker = True
frame_marker = False

pal = True
pal = False

## ----------------------------------------------------------------------------
## ----------------------------------------------------------------------------
##
##           ______              _        _             _ _
##          |  ____|            | |      | |           | | |
##          | |__ _ __ __ _  ___| |_ __ _| |___    __ _| | |
##          |  __| '__/ _` |/ __| __/ _` | / __|  / _` | | |
##          | |  | | | (_| | (__| || (_| | \__ \ | (_| | | |
##          |_|  |_|  \__,_|\___|\__\__,_|_|___/  \__,_|_|_|
##  _   _                                      _
## | | | |                                    | |
## | |_| |__   ___  __      ____ _ _   _    __| | _____      ___ __
## | __| '_ \ / _ \ \ \ /\ / / _` | | | |  / _` |/ _ \ \ /\ / / '_ \
## | |_| | | |  __/  \ V  V / (_| | |_| | | (_| | (_) \ V  V /| | | |
##  \__|_| |_|\___|   \_/\_/ \__,_|\__, |  \__,_|\___/ \_/\_/ |_| |_|
##                                  __/ |
##                                 |___/
##
## ----------------------------------------------------------------------------
##
##   An FPGA demo by doz from crtc, music by mr_lou, font by tunk.
##
##       Presented at Sundown 2013, 2nd place in Wild Compo.
##
## ----------------------------------------------------------------------------
## ----------------------------------------------------------------------------
##
## The demo was running on my custom FPGA board, which I designed in order
## to create an Amstrad CPC emulator. It's not yet available to anyone else
## so there's not really any point releasing a binary version, so I'm making
## the source VHDL available and the script to create the data file.
## 
## The source is (c) 2013 Ranulf Doswell
## 
## If you have an questions, please e-mail doz@ranulf.net
##
## ----------------------------------------------------------------------------
## ----------------------------------------------------------------------------

import math
import sys
from struct import pack
from struct import unpack

class outputter:
	def __init__(self,txtfile,binfile):
		if txtfile <> None:
			self.txtfile = open(txtfile,"w")
		else:	self.txtfile = None
		if binfile <> None:
			self.binfile = open(binfile,"wb")
			self.binfile.write('\000'*16)
			self.binfile.write('\377'*16)
		else:	self.binfile = None
		self.musfile = None
		self.last = {}
		self.skipped = 0
		self.sent = 0
		self.probs=[0]*2048
		if pal:
			self.output(1024+15,1)
		else:
			self.output(1024+15,0)

	def close(self):
		self.emitymraw(0,0,0,0,0,0,0)
		if self.binfile <> None:
			b=4096+27*64
			for i in xrange(0,2048):
				self.binfile.write(pack("<HH",b,i))
			self.binfile.close()
		if self.txtfile <> None:
			self.txtfile.write("\n")
			self.txtfile.close()

	def dumpasm(self,asmfile):
		if asmfile == None: return
		f = open(asmfile,"w")
		f.write("; coefficients\n")
		sp = 0
		for a in xrange(0,1024):
			if self.last.has_key(a):
				if sp <> 0:
					f.write("\tdefs %d\n"%sp)
					sp = 0
				f.write("\tdefw #%04x\n"%(self.last[a]&0xffff))
			else:
				sp = sp + 2
		if sp <> 0:
			f.write("\tdefs %d\n"%sp)

		f.write("; probabilities\n")
		sp = 0
		last = 0
		for d in self.probs:
			if d <> last:
				if sp <> 0:
					f.write("\tdefs %d,%d\n"%(sp,last))
					sp = 0
				last = d
			sp = sp + 1
		if sp <> 0:
			f.write("\tdefs %d,%d\n"%(sp,last))

		f.write("; text\n")
		sp = 0
		for a in xrange(4096,4096+2048):
			if self.last.has_key(a):
				if sp <> 0:
					f.write("\tdefs %d\n"%sp)
					sp = 0
				f.write("\tdefb #%02x\n"%(self.last[a]&0xff))
			else:
				sp = sp + 1
		if sp <> 0:
			f.write("\tdefs %d\n"%sp)

		f.close()

	def separator(self):
		if self.txtfile <> None:
			self.txtfile.write("\n")

	def output(self,a,d):
		if self.last.has_key(a):
			if (a < 0x400 or a>=0x408) and self.last[a] == d:
				self.skipped = self.skipped +1
				return
		if self.txtfile <> None:
			self.txtfile.write("%d %d "%(a,d))
		if self.binfile <> None:
			self.binfile.write(pack("<HH",a&65535,d&65535))
		self.last[a] = d
		self.sent = self.sent + 1
		if (self.sent%5000)==0:
			sys.stderr.write(".")
			sys.stderr.flush()
			if self.txtfile <> None:
				self.txtfile.flush()
			if self.binfile <> None:
				self.binfile.flush()

	def music(self,musfile):
		self.musfile = open(musfile,"rb")

	def frame(self,i):
		self.separator()
		self.output(1027,i)
		if frame_marker:
			self.text(40,0,"%3d.%02d"%(i/60,i%60))
			self.text(40,1," %5d"%i)
		if i > 0:
			self.emitym()
		self.separator()

	def emitym(self):
		(ta,tb,tc,n,aa,ab,ac)=(0,0,0,0,0,0,0)
		try:
				mb = self.musfile.read(16)
				(ta,tb,tc,n,en,aa,ab,ac,ep,es,dummy)=unpack("<HHHBBBBBHBH",mb)
				if (en& 1) <> 0: ta=ta+2048
				if (en& 2) <> 0: tb=tb+2048
				if (en& 4) <> 0: tc=tc+2048
				if (en& 8) <> 0: n=n+128
				if (en&16) <> 0: n=n+64
				if (en&32) <> 0: n=n+32
		except:	pass
		self.emitymraw(ta,tb,tc,n,aa,ab,ac)

	def emitymraw(self,ta,tb,tc,n,aa,ab,ac):
		self.output(1024+ 8,ta)
		self.output(1024+ 9,tb)
		self.output(1024+10,tc)
		self.output(1024+12,aa)
		self.output(1024+13,ab)
		self.output(1024+14,ac)
		self.output(1024+11,n)

	def prob(self,st,ln,val):
		if ln>0:
			self.output(1024,st)
			self.output(1025,val)
			self.output(1026,ln)
			st = st-2048
			self.probs[st:st+ln]=[val]*ln

	def transform(self,base,a,b,c,d,e,f):
		#print "transforming to base %d"%base
		self.output(base*8+0,int_mul(a))
		self.output(base*8+1,int_mul(b))
		self.output(base*8+2,int_ofs(e))
		self.output(base*8+4,int_mul(c))
		self.output(base*8+5,int_mul(d))
		self.output(base*8+6,int_ofs(f))

	def text(self,x,y,str):
		for c in str:
			self.output(4096+128*y+x,ord(c))
			self.output(4096+128*y+x+64,ord(c)+128)
			x=x+1
			if x==46:
				x=0
				y=y+1

def int_mul(v):
	x = int(v*4096)
	if x<0: x = 65536+x
	return x

def int_ofs(v):
	x = int(v*16)
	if x<0: x = 65536+x
	return int(x)

def int_ofs(v):
	x = int(v*16)
	if x<0: x = 65536+x
	return int(x)

def int_prob(v):
	x = int(v*65536)
	return int(x)

class fractchoice:
	def __init__(self,parent,colour,prob):
		self.parent=parent
		self.colour=colour
		self.prob=prob
		self.slot=None
		self.transform_data=None

	def clear(self):
		if self.slot<>None:
			self.parent.helper.freeslot(self.slot)
			self.slot=None

	def transform_rot(self,r,s,th,ph,e,f):
		self.transform(	r*math.cos(th),-s*math.sin(ph),
				r*math.sin(th), s*math.cos(ph),e,f)
		return self.parent

	def transform(self,a,b,c,d,e,f):
		self.transform_raw=(a,b,c,d,e,f)
		self.calc_transform()
		return self.parent

	def calc_transform(self):
		# recyling coords doesn't seem to work
		#self.clear()
		if self.slot == None:
			o=self.slot
			self.slot=self.parent.helper.allocslot(self.colour)
			self.parent.helper.freeslot(o)
		(a,b,c,d,e,f)=self.transform_raw

		(a,b,c,d,e,f)=(a,b*self.parent.xsys,c*self.parent.ysxs,d,
			self.parent.xo + e*self.parent.xs - a*self.parent.xo - b*self.parent.yo*self.parent.xsys,
			self.parent.yo + f*self.parent.ys - d*self.parent.yo - c*self.parent.xo*self.parent.ysxs)

		k = 256.0 / 208.0
		if pal:
			b = b/k
			c = c*k
			f = f*k

		self.parent.helper.out.separator()
		self.parent.helper.out.transform(self.slot,a,b,c,d,e,f)

class fractal:
	def __init__(self,name,helper,prob_bases,xs,ys,xo,yo):
		self.name=name
		self.helper=helper
		self.prob_bases=prob_bases
		self.choices = []
		self.resize(xs,ys,xo,yo)
		self.update_callback=None

	def resize(self,xs,ys,xo,yo):
		xs = float(xs)
		ys = float(ys)

		self.xsys = xs/ys
		self.ysxs = ys/xs

		self.xs = xs
		self.ys = ys
		self.xo = xo
		self.yo = yo
		for c in self.choices:
			c.calc_transform()

	def choice(self,colour,prob=1.0):
		c=fractchoice(self,colour,prob)
		self.choices.append(c)
		return c

	def clear(self):
		for c in self.choices:
			c.clear()
		self.choices = []

	def update(self):
		if self.update_callback<>None:
			self.update_callback()
		psum = 0
		for c in self.choices:
			psum = psum + c.prob

		ptot = 0
		for c in self.choices:
			c.pnorm=int(256*c.prob/psum)
			ptot = ptot + c.pnorm
		pover = 256-ptot

		out=self.helper.out
		psum=0
		for c in self.choices:
			pscaled = c.pnorm
			if pover>0:
				pscaled = pscaled + 1
				pover = pover -1
			out.separator()
			for pb in self.prob_bases:
				out.prob(psum+pb,pscaled,c.slot)
			psum = psum + pscaled

		assert psum == 256

class helper:
	def __init__(self, out):
		self.out = out
		self.fractals = []
		self.prob_bases = [ i for i in xrange(0x800,0x1000,0x100)]
		self.slots = [ [x for x in xrange(i,256,8)] for i in xrange(0,8)]
		self.unused = self.allocslot(0)
		self.out.prob(0x800,0x800,self.unused)

	def allocslot(self,c):
		s = self.slots[c][0]
		self.slots[c].remove(s)
		#print "allocating slot %d for colour %d"%(s,c)
		#assert s<>117
		return s

	def freeslot(self,s):
		if s <> None: self.slots[s&7].append(s)

	def create(self,name,xs=1.0,ys=1.0,xo=0.0,yo=0.0,num=1):
		f = fractal(name,self,self.prob_bases[:num],xs,ys,xo,yo)
		self.prob_bases = self.prob_bases[num:]
		self.fractals.append(f)
		return f

	def remove(self,f):
		if f.prob_bases<>None:
			for pb in f.prob_bases:
				self.out.prob(pb,pb+0x100,self.unused)
				self.prob_bases.append(pb)
			f.prob_bases=None
			f.clear()
			self.fractals.remove(f)

	def update(self):
		for f in self.fractals:
			f.update()

	def drawadd(self,i):
		self.out.output(1028,i)
