/fileselect.py
import pygame
import cPickle
import os, os.path, re, math

from keyfile import *
from fx import *
import event

fileroot = "songs"
songcache = 'songcache.pickle'
possave = 0

def mksongcache():
	songs = FileNode()
	songs.crawl(fileroot)
	f = open(songcache,'w')
	cPickle.dump(songs,f)
	f.close()
	return songs

filetile = pygame.image.load("gfx/filetile.png")

class SelectWidget(FX):
	color = (240,240,255)
	basecolor = (200,0,0)
	fadetime = 700
	size = (400,30)

	OFF = 0
	ON = 1
	FADING = 2

	def __init__(self,text,location=(0,0)):
		FX.__init__(self)
		f = pygame.font.Font('font/Nano.ttf',16)
		self.text = f.render(text,True,self.color)
		self.surface = pygame.Surface(self.size).convert_alpha()
		self.surface.fill( (0,0,0,0) )
		self.surface.blit(filetile,(0,0))
		self.surface.blit(self.text, (9,7))
		self.pointlist = [(10,0),(self.size[0]-2,0),(self.size[0]-2,self.size[1]-2),(0,self.size[1]-2),(0,10)]
		self.state = self.OFF
		self.location = location

	def off(self):
		if self.state == self.ON:
			self.state = self.FADING
			self.fade_t = pygame.time.get_ticks()

	def on(self):
		self.state = self.ON

	def draw(self,t):
		self.screen.blit(self.surface,self.location)
		color = self.basecolor
		if self.state == self.ON:
			color = self.color
		if self.state == self.FADING:
			dt = pygame.time.get_ticks() - self.fade_t
			if dt < self.fadetime:
				alpha = math.exp(-0.003*dt)
				color = [(1.0-alpha) * self.basecolor[n] + alpha * self.color[n] for n in range(0,3)]
			else:
				self.state = self.OFF
		pointlist = [(x[0] + self.location[0],x[1] + self.location[1]) for x in self.pointlist]
		pygame.draw.lines(self.screen,color,True,pointlist,2)

# This would look a lot less ugly if only there were something like
# perl's cmp and <=> operators.
# FUCK YOU PYTHON WHY DOES THIS HAVE TO BE SO COMPLICATED!?!?!?!
def WTFsort(a,b):
	if a.type == a.DIRECTORY and b.type == b.DIRECTORY:
		if a.name > b.name:
			return 1
		elif a.name < b.name:
			return -1
		else:
			return 0
	elif a.type == a.DIRECTORY and b.type == b.FILE:
		return 1
	elif a.type == a.FILE and b.type == b.DIRECTORY:
		return -1
	else:
		if a.info['player'] > b.info['player']:
			return 1
		elif a.info['player'] < b.info['player']:
			return -1
		else:
			if not 'playlevel' in a.info:
				return -1
			if not 'playlevel' in b.info:
				return 1
			if a.info['playlevel'] > b.info['playlevel']:
				return 1
			elif a.info['playlevel'] < b.info['playlevel']:
				return -1
			else:
				if not 'total' in a.info:
					return -1
				if not 'total' in b.info:
					return 1
				if a.info['total'] > b.info['total']:
					return 1
				elif a.info['total'] < b.info['total']:
					return -1
				else:
					return 0

filematch = re.compile('\.(bms|bme|sm)$',re.I)

class FileNode:
	DIRECTORY = 0
	FILE = 1

	def __init__(self):
		self.children = []
		self.open = False
		self.info = None
		self.widget = None

	def crawl(self,path):
		print "Crawling " + path
		self.path = path
		if os.path.isdir(path):
			self.type = self.DIRECTORY
			self.name = os.path.basename(path)
			files = filter(lambda x: os.path.isdir(os.path.join(path,x)) or filematch.search(x), os.listdir(path))
			for f in files:
				c = FileNode()
				if c.crawl(os.path.join(path,f)):
					self.children.append(c)
			self.children.sort(WTFsort)
		elif os.path.isfile(path):
			self.type = self.FILE
			self.info = kf_info(path)
			self.name = self.info['title']
		else:
			print path + " isn't a file or a directory. Ignoring."
			return False
		return True

	def update(self):
		for c in self.children:
			if not os.path.exists(c.path):
				self.children.remove(c)
			else:
				c.update()

	def add(self,child):
		self.children.append(child)

	def tolist(self,depth=0):
		self.depth = depth
		l = [self]
		if self.open:
			for c in self.children:
				r = c.tolist(depth+1)
				for i in r:
					l.append(i)
		return l

	def getwidget(self):
		if not self.widget:
			self.widget = SelectWidget(self.name)
		return self.widget

	def nukewidgets(self):
		self.widget = None
		for c in self.children:
			c.nukewidgets()

class FileSelect:

	def __init__(self):
		self.screen = pygame.display.get_surface()
		try:
			f = open(songcache,'r')
			self.songs = cPickle.load(f)
			f.close()
		except IOError:
			self.songs = mksongcache()
		except cPickle.UnpicklingError:
			self.songs = mksongcache()

		self.period = 428
		self.songlist = self.songs.tolist()
		try:
			self.position = self.songlist.index(self.songs.current)
		except AttributeError:
			self.position = 0
		except ValueError:
			self.position = 0

		self.fslabel = Text('file select','font/Nano.ttf',30,(330,30),(255,0,0))
		self.fadebottom = pygame.Surface((640,200)).convert_alpha()
		for n in range(0,200):
			alpha = int((1.0 - math.exp(-n/30.0)) * 255.0)
			color = (0,0,0,alpha)
			pygame.draw.line(self.fadebottom,color,(0,n),(640,n))
		self.labels = {}
		self.olabels = {}
		self.olabels['title'] = Text('title','font/Nano.ttf',12, (20,320),(255,130,130))
		self.labels['title'] = Text('','font/Nano.ttf',18, (30,330),(230,230,255))
		self.olabels['artist'] = Text('artist','font/Nano.ttf',12, (20,360),(255,130,130))
		self.labels['artist'] = Text('','font/Nano.ttf',16, (30,370),(230,230,255))
		self.olabels['genre'] = Text('genre','font/Nano.ttf',12, (20,390),(255,130,130))
		self.labels['genre'] = Text('','font/Nano.ttf',16, (30,400),(230,230,255))
		self.olabels['playlevel'] = Text('difficulty','font/Nano.ttf',12, (20,420),(255,130,130))
		self.labels['playlevel'] = Text('','font/Nano.ttf',16, (30,430),(230,230,255))
		self.olabels['bpm'] = Text('BPM','font/Nano.ttf',12, (20,450),(255,130,130))
		self.labels['bpm'] = Text('','font/Nano.ttf',16, (30,460),(230,230,255))
		self.calcsongwidgets()

	def draw(self,t):
		self.screen.fill( (30,0,0) )
		l = len(self.songlist)
		for nu in range(self.position-4,self.position+8):
			n = nu % l
			self.songlist[n].getwidget().draw(t)

		self.screen.blit(self.fadebottom,(0,280))
		for k in self.olabels.keys():
			self.olabels[k].draw(t)
		for k in self.labels.keys():
			self.labels[k].draw(t)
		self.fslabel.draw(t)

	def calcsongwidgets(self):
		l = len(self.songlist)
		for nu in range(self.position-4,self.position+8):
			n = nu % l
			w = self.songlist[n].getwidget()
			w.location = (10 + self.songlist[n].depth * 20, 150 - (self.position - nu)*30)
			if n == self.position:
				w.on()
				if self.songlist[n].type == FileNode.DIRECTORY:
					for tag in ('artist','genre','playlevel','bpm'):
						self.labels[tag].settext('')
					self.labels['title'].settext(self.songlist[n].name)
				else:
					for tag in ('title','artist','genre','bpm'):
						self.labels[tag].settext(self.songlist[n].info[tag])
					try:
						playlevel = int(self.songlist[n].info['playlevel'])
						self.labels['playlevel'].settext(("*" * playlevel) + "(" + str(playlevel) + ")")
					except ValueError:
						self.labels['playlevel'].settext(str(self.songlist[n].info['playlevel']))
			else:
				w.off()

	def run(self):
		global possave
		decided = 0
		pygame.mixer.music.load('snd/0x01.ogg')
		pygame.mixer.music.set_volume(0.75)
		pygame.mixer.music.play(-1)
		stab = pygame.mixer.Sound('snd/stab.ogg')
		stab.set_volume(0.25)
		start_t = pygame.time.get_ticks()

		while decided == 0:
			t = pygame.time.get_ticks() - start_t
			act = event.getaction()
			while act:
				if act == event.CANCEL:
					return ['mainmenu']
				elif act == event.DOWN:
					self.position += 1
					self.calcsongwidgets()
				elif act == event.UP:
					self.position -= 1
					self.calcsongwidgets()
				elif act == event.OK:
					if self.songlist[self.position].type == FileNode.DIRECTORY:
						self.songlist[self.position].open = not self.songlist[self.position].open
						self.songlist = self.songs.tolist()
						self.calcsongwidgets()
					else:
						decided = self.position
				act = event.getaction()

			self.position = self.position % len(self.songlist)
			self.draw(t)
			pygame.display.flip()

		pygame.mixer.music.fadeout(1500)
		stab.play()

		self.songs.nukewidgets()
		self.songs.current = self.songlist[self.position]
		f = open(songcache,'w')
		cPickle.dump(self.songs,f)
		f.close()

		#possave = self.position
		return ['play',self.songlist[decided].path]