# -*- coding: utf-8 -*-
"""
Module de lecture des fichiers de log minwii

$Id$
$URL$
"""

from types import StringTypes
from widgets.playingscreen import PlayingScreenBase
from eventutils import EventDispatcher
from events import eventCodes
from synth import Synth
from musicxml import musicXml2Song
import pygame
from backwardsfilereader import BackwardsReader

SUPPORTED_FILE_HEADER = 'ENV winwii log format version : 1.0'

def inplaceread(m) :
    def readinplace(self, *args, **kw) :
        pos = self.logfile.tell()
        self.logfile.seek(0)
        ret = m(self, *args, **kw)
        self.logfile.seek(pos)
        return ret
    return readinplace

class LogFileReader(object) :
    """
    classe utilitaire pour l'accès aux données d'un fichier de log MinWii.
    """
    
    def __init__(self, logfile, mode='r') :
        """ logfile : chemin d'accès au fichier de log MinWii.
            le format supporté est actuellement la version 1.0 uniquement.
        """
        if isinstance(logfile, StringTypes) :
            self.logfile = open(logfile, mode)
        else :
            self.logfile = logfile
        
        firstline = self.next()
        assert firstline == SUPPORTED_FILE_HEADER
    
        
    @inplaceread
    def getSongFile(self) :
        "retourne le chemin d'accès au fichier musicxml de la chanson"
        for l in self :
            if l.startswith('APP chanson :') :
                break
        songfile = l.split(':', 1)[1].strip()
        return songfile
    
    @inplaceread
    def getSoundFontFile(self) :
        "retourne le chemin d'accès au fichier de la soundfont (*.sf2)"
        for l in self :
            if l.startswith('ENV soundfont :') :
                break
        soundFontFile = l.split(':', 1)[1].strip()
        return soundFontFile
    
    @inplaceread
    def getBank(self) :
        "retourne le paramètre bank du synthétiseur (entier)"
        for l in self :
            if l.startswith('APP bank :') :
                break
        bank = l.split(':', 1)[1].strip()
        return int(bank)
    
    @inplaceread
    def getPreset(self) :
        "retourne le paramètre preset du synthétiseur (entier)"
        for l in self :
            if l.startswith('APP preset :') :
                break
        preset = l.split(':', 1)[1].strip()
        return int(preset)

    @inplaceread
    def getScreenResolution(self) :
        "retourne la résolution écran (tuple de deux entiers)"
        for l in self :
            if l.startswith('ENV résolution écran :') :
                break
        screenResolution = eval(l.split(':', 1)[1].strip())
        return screenResolution
    
    @inplaceread
    def getMode(self) :
        "retourne le niveau de difficulté"
        for l in self :
            if l.startswith('APP mode :') :
                break
            
        mode = l.split(':', 1)[1].strip()
        return mode
    
    @inplaceread
    def getHID(self) :
        "retourne l'interface homme-machine utilisée"
        for l in self :
            if l.startswith('APP HID :') :
                break
            
        mode = l.split(':', 1)[1].strip()
        return mode
    
    @inplaceread
    def getFirstEventTicks(self) :
        "retourne le timecode du premier événement (entier)"
        for l in self :
            if l.startswith('EVT ') :
                break
        firstTicks = int(l.split(None, 2)[1])
        return firstTicks
    
    @inplaceread
    def getLastEventTicks(self) :
        "retourne le timecode du dernier événement (entier)"
        for l in self.getBackwardLineIterator() :
            if l.startswith('EVT ') :
                break
        else :
            return None
        
        lastTicks = int(l.split(None, 2)[1])
        return lastTicks
    
    def __del__(self) :
        self.close()
    
    def __iter__(self) :
        return self
    
    def next(self) :
        line = self.logfile.next().strip()
        return line
    
    def getEventsIterator(self) :
        """ Retourne un itérateur sur les événements.
            Chaque itération retourne un tuple de 3 éléments :
            (timecode, nom_événement, données) avec le typage :
            (entier,   chaîne,        chaîne)
        """
        self.logfile.seek(0)
        while True :
            try :
                l = self.next()
            except StopIteration :
                break
            
            if not l.startswith('EVT ') :
                continue
            try :
                ticks, eventName, message = l.split(None, 3)[1:]
                ticks = int(ticks)
                yield ticks, eventName, message
            except ValueError :
                ticks, eventName = l.split(None, 3)[1:]
                ticks = int(ticks)
                yield ticks, eventName, ''
    
    def getBackwardLineIterator(self) :
        br = BackwardsReader(self.logfile, BLKSIZE=128)
        line = br.readline()
        while line :
            yield line.strip()
            line = br.readline()
    
    @inplaceread
    def getMetadata(self) :
        metadata = {}
        self.next() # skip identification line.
        line = self.next()
        while line.startswith('METADATA ') :
            line = line.split(None, 1)[1]
            name, value = [v.strip() for v in line.split(':', 1)]
            metadata[name] = value
            line = self.next()
        return metadata
    
    def setMetadata(self, metadata) :
        f = self.logfile
        f.seek(0)
        before = f.readline()
        line = f.readline()
        while line.startswith('METADATA ') :
            line = f.readline()
        after = line + f.read()
        
        lines = []
        for name, value in metadata :
            lines.append('METADATA %s : %s' % (name, value.encode('utf-8')))
        metadata = '\n'.join(lines)
        f.seek(0)
        f.write(before)
        f.write(metadata)
        f.write(after)
    
    def close(self) :
        self.logfile.close()
        
                

class LogFilePlayer(PlayingScreenBase) :
    """
    ré-exécution d'une chanson sur la base de son fichier de log.
    """

    def __init__(self, logfile) :
        lfr = self.lfr = LogFileReader(logfile)
        songFile = lfr.getSongFile()
        soundFontFile = lfr.getSoundFontFile()
        sfPath = lfr.getSoundFontFile()
        bank = lfr.getBank()
        preset = lfr.getPreset()
        synth = Synth(sfPath=sfPath)
        synth.program_select(0, bank, preset)
        self.song = musicXml2Song(songFile)
        screenResolution = lfr.getScreenResolution()
        
        pygame.display.set_mode(screenResolution)
        
        super(LogFilePlayer, self).__init__(synth, self.song.distinctNotes)
    
    def run(self):
        self._running = True
        clock = pygame.time.Clock()
        pygame.display.flip()
        pygame.mouse.set_visible(False)
        
        previousTicks = self.lfr.getFirstEventTicks()
        eIter = self.lfr.getEventsIterator()

        for ticks, eventName, message in eIter :
            t0 = pygame.time.get_ticks()
            if eventName == 'COLSTATECHANGE' :
                parts = message.split(None, 4)
                if len(parts) == 4 :
                    parts.append('')
                index, state, midi, name, syllabus = parts
                index = int(index)
                midi = int(midi)
                state = state == 'True'
                col = self.columns[midi]
                col.update(state, syllabus=syllabus.decode('utf-8'))

            elif eventName == 'NOTEON':
                chan, key, vel = [int(v) for v in message.split(None, 2)]
                self.synth.noteon(chan, key, vel)

            elif eventName == 'NOTEOFF':
                chan, key = [int(v) for v in message.split(None, 1)]
                self.synth.noteoff(chan, key)
            
            elif eventName.startswith('COL') :
                pos = [int(n) for n in message.split(None, 4)[-1].strip('()').split(',')]
                self.cursor.setPosition(pos)
            
                
            pygame.event.clear()

            dirty = self.draw(pygame.display.get_surface())
            pygame.display.update(dirty)
            execTime = pygame.time.get_ticks() - t0
            
            delay = ticks - previousTicks - execTime
            if delay > 0 :
                pygame.time.wait(delay)
            
            previousTicks = ticks
        
        self.stop()
    