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

$Id$
$URL$
"""

from widgets.playingscreen import PlayingScreenBase
from eventutils import EventDispatcher
from events import eventCodes
from synth import Synth
from musicxml import musicXml2Song
import pygame

SUPPORTED_FILE_HEADER = 'ENV winwii log format version : 1.0'

class LogFileReader(object) :
    """
    classe utilitaire pour l'accès aux données d'un fichier de log MinWii.
    """
    
    def __init__(self, logfile) :
        """ logfile : chemin d'accès au fichier de log MinWii.
            le format supporté est actuellement la version 1.0 uniquement.
        """
        if isinstance(logfile, str) :
            self.logfile = open(logfile, 'r')
        else :
            self.logfile = logfile
        
        firstline = self.next()
        assert firstline == SUPPORTED_FILE_HEADER
        
    
    def getSongFile(self) :
        "retourne le chemin d'accès au fichier musicxml de la chanson"
        f = self.logfile
        pos = f.tell()

        f.seek(0)
        for l in self :
            if l.startswith('APP chanson :') :
                break
        songfile = l.split(':', 1)[1].strip()
        f.seek(pos)
        return songfile
    
    def getSoundFontFile(self) :
        "retourne le chemin d'accès au fichier de la soundfont (*.sf2)"
        f = self.logfile
        pos = f.tell()
        f.seek(0)
        for l in self :
            if l.startswith('ENV soundfont :') :
                break
        soundFontFile = l.split(':', 1)[1].strip()
        f.seek(pos)
        return soundFontFile

    def getBank(self) :
        "retourne le paramètre bank du synthétiseur (entier)"
        f = self.logfile
        pos = f.tell()
        f.seek(0)
        for l in self :
            if l.startswith('APP bank :') :
                break
        f.seek(pos)
        bank = l.split(':', 1)[1].strip()
        return int(bank)
    
    def getPreset(self) :
        "retourne le paramètre preset du synthétiseur (entier)"
        f = self.logfile
        pos = f.tell()
        f.seek(0)
        for l in self :
            if l.startswith('APP preset :') :
                break
        f.seek(pos)
        preset = l.split(':', 1)[1].strip()
        return int(preset)

    def getScreenResolution(self) :
        "retourne la résolution écran (tuple de deux entiers)"
        f = self.logfile
        pos = f.tell()
        f.seek(0)
        for l in self :
            if l.startswith('ENV résolution écran :') :
                break
        screenResolution = eval(l.split(':', 1)[1].strip())
        f.seek(pos)
        return screenResolution
    
    def getFirstEventTicks(self) :
        "retourne le timecode du premier événement (entier)"
        f = self.logfile
        pos = f.tell()
        f.seek(0)
        for l in self :
            if l.startswith('EVT ') :
                break
        firstTicks = int(l.split(None, 2)[1])
        f.seek(pos)
        return firstTicks
    
    def __del__(self) :
        self.logfile.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, ''
                

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()
        
    