# -*- coding: utf-8 -*-
"""
Boîte de dialogue pour sélection des chansons.

$Id$
$URL$
"""

import pygame
from pygame.locals import K_RETURN
from pgu.gui import FileDialog
import pgu.gui.basic as basic
import pgu.gui.input as input
import pgu.gui.button as button
import pgu.gui.pguglobals as pguglobals
import pgu.gui.table as table
import pgu.gui.area as area
from pgu.gui.const import *
from pgu.gui.dialog import Dialog
from pgu.gui.app import Desktop
import types
from datetime import timedelta

import os
import tempfile
from xml.etree import ElementTree
from minwii.musicxml import musicXml2Song

INDEX_TXT = 'index.txt'
PICTURE_ITEM_SIZE = 64

def appEventFactory(app, dlg) :
    # monkey patch de la méthode gestionnaire d'événements :
    # l'ensemble du Desktop écoute les événements de la roulette de la souris
    # et les redirige sur la liste déroulante.
    def _appEvent(self, e) :
        
        if dlg.list.vscrollbar:
            if not hasattr(dlg.list.vscrollbar,'value'): 
                return False

            if e.type == pygame.locals.MOUSEBUTTONDOWN:
                if e.button == 4: #wheel up
                    dlg.list.vscrollbar._click(-1)
                    return True
                elif e.button == 5: #wheel down
                    dlg.list.vscrollbar._click(1)
                    return True
        return Desktop.event(self, e)

    return types.MethodType(_appEvent, app)

class FileOpenDialog(FileDialog):
    
    
    
    def __init__(self, path):
        cls1 = 'filedialog'
        if not path: self.curdir = os.getcwd()
        else: self.curdir = path
        self.dir_img = basic.Image(
            pguglobals.app.theme.get(cls1+'.folder', '', 'image'))
        self.soundfile_img = basic.Image(
            pguglobals.app.theme.get(cls1+'.soundfile', '', 'image'))
        td_style = {'padding_left': 4,
                    'padding_right': 4,
                    'padding_top': 2,
                    'padding_bottom': 2}
        self.title = basic.Label("Ouvrir une chanson", cls="dialog.title.label")
        self.body = table.Table()
        self.list = area.List(width=880, height=375)
        self.input_dir = input.Input()
        self.input_file = input.Input()
        self._current_sort = 'alpha'
        self._list_dir_()
        self.button_ok = button.Button("Ouvrir")
        self.button_sort_alpha = button.Button("A-Z")
        self.button_sort_alpha.connect(CLICK, self._set_current_sort_, 'alpha')
        self.button_sort_num = button.Button("0-9")
        self.button_sort_num.connect(CLICK, self._set_current_sort_, 'num')
        self.body.tr()
        self.body.td(basic.Label("Dossier"), style=td_style, align=-1)
        self.body.td(self.input_dir, style=td_style)
        self.body.td(self.button_sort_alpha)
        self.body.td(self.button_sort_num)
        self.body.tr()
        self.body.td(self.list, colspan=4, style=td_style)
        self.list.connect(CHANGE, self._item_select_changed_, None)
        #self.list.connect(CLICK, self._check_dbl_click_, None)
        self._last_time_click = pygame.time.get_ticks()
        self.button_ok.connect(CLICK, self._button_okay_clicked_, None)
        self.body.tr()
        self.body.td(basic.Label("Fichier"), style=td_style, align=-1)
        self.body.td(self.input_file, style=td_style)
        self.body.td(self.button_ok, style=td_style, colspan=2)
        self.value = None
        Dialog.__init__(self, self.title, self.body)
        
        # monkey patch
        app = pguglobals.app
        self.__regularEventMethod = app.event
        app.event = appEventFactory(app, self)
    
    def close(self, w=None) :
        FileDialog.close(self, w)
        # retrait du monkey patch
        app = pguglobals.app
        app.event = self.__regularEventMethod
        
            
    def _list_dir_(self):
        self.input_dir.value = self.curdir
        self.input_dir.pos = len(self.curdir)
        self.input_dir.vpos = 0
        dirs = []
        files = []
        try:
            for i in os.listdir(self.curdir):
                if i.startswith('.') : continue
                if os.path.isdir(os.path.join(self.curdir, i)): dirs.append(i)
                else: files.append(i)
        except:
            self.input_file.value = "Dossier innacessible !"

        dirs.sort()
        dirs.insert(0, '..')

        files.sort()
        for i in dirs:
            self.list.add(i, image=self.dir_img, value=i)
        
        xmlFiles = []
        for i in files:
            if not i.endswith('.xml') :
                continue
            filepath = os.path.join(self.curdir, i)
            xmlFiles.append(filepath)
        
        if xmlFiles :
            printableLines = self.getPrintableLines(xmlFiles)
            for l in printableLines :
                imgpath = os.path.splitext(os.path.join(self.curdir, l[1]))[0] + '.jpg'
                if os.path.exists(imgpath) :
                    img = pygame.image.load(imgpath)
                    iw, ih = img.get_width(), img.get_height()
                    style = {}
                    if iw > ih :
                        style['width'] = PICTURE_ITEM_SIZE
                        style['height'] = int(round(PICTURE_ITEM_SIZE * float(ih) / iw))
                    else :
                        style['heigth'] = PICTURE_ITEM_SIZE
                        style['width'] = int(round(PICTURE_ITEM_SIZE * float(iw) / ih))
                        
                    img = basic.Image(img, style=style)
                else :
                    img = self.soundfile_img
                self.list.add(l[0], value = l[1], image = img)
 
        self.list.set_vertical_scroll(0)
    
    def getPrintableLines(self, xmlFiles) :
        index = self.getUpdatedIndex(xmlFiles)

        printableLines = []
        for l in index :
            l = l.strip()
            l = l.split('\t')
            printableLines.append(('%s - %s / %s' % (l[2], l[3], l[4]), l[0]))
        
        return printableLines
    
    
    @staticmethod
    def getSongTitle(file) :
        it = ElementTree.iterparse(file, ['start', 'end'])
        creditFound = False
        title = os.path.basename(file)
        
        for evt, el in it :
            if el.tag == 'credit' :
                creditFound = True
            if el.tag == 'credit-words' and creditFound:
                title = el.text
                break
            if el.tag == 'part-list' :
                # au delà de ce tag : aucune chance de trouver un titre
                break
        return title
    
    @staticmethod
    def getSongMetadata(file) :
        metadata = {}
        metadata['title'] = FileOpenDialog.getSongTitle(file).encode('iso-8859-1')
        metadata['mtime'] = str(os.stat(file).st_mtime)
        metadata['file'] = os.path.basename(file)
        song = musicXml2Song(file)
        metadata['distinctNotes'] = len(song.distinctNotes)
        
        duration = song.duration / 1000.
        duration = int(round(duration, 0))
        duration = timedelta(seconds=duration)
        try :
            duration = str(duration) # p.ex. 0:03:05
            duration = duration.split(':')
            h, m, s = [int(n) for n in duration]
            if h : raise ValueError(h)
            duration = ':'.join([str(n).zfill(2) for n in (m, s)])
        except :
            raise
            duration = srt(duration)

        metadata['duration'] = duration
        
        # histo = song.intervalsHistogram
        # coeffInter = reduce(lambda a, b : a + b,
        #                     [abs(k) * v for k, v in histo.items()])
        # 
        # totInter = reduce(lambda a, b: a+b, histo.values())
        # totInter = totInter - histo.get(0, 0)
        # difficulty = int(round(float(coeffInter) / totInter, 0))
        # metadata['difficulty'] = difficulty

        return metadata
    
    def getUpdatedIndex(self, xmlFiles) :
        indexTxtPath = os.path.join(self.curdir, INDEX_TXT)
        index = []
        
        if not os.path.exists(indexTxtPath) :
            musicXmlFound = False
            tmp = tempfile.TemporaryFile(mode='r+')
            for file in xmlFiles :
                try :
                    metadata = FileOpenDialog.getSongMetadata(file)
                    musicXmlFound = True
                except ValueError, e :
                    print e
                    if e.args and e.args[0] == 'not a musicxml file' :
                        continue
                
                line = '%(file)s\t%(mtime)s\t%(title)s\t%(distinctNotes)d\t%(duration)s\n' % metadata
                index.append(line)
                tmp.write(line)
            
            if musicXmlFound :
                tmp.seek(0)
                indexFile = open(indexTxtPath, 'w')
                indexFile.write(tmp.read())
                indexFile.close()
            tmp.close()
        else :
            indexedFiles = {}
            indexTxt = open(indexTxtPath, 'r')

            # check if index is up to date, and update entries if so.
            for l in filter(None, indexTxt.readlines()) :
                parts = l.split('\t')
                fileBaseName, modificationTime = parts[0], parts[1]
                filePath = os.path.join(self.curdir, fileBaseName)

                if not os.path.exists(filePath) :
                    continue

                indexedFiles[fileBaseName] = l
                currentMtime = str(os.stat(filePath).st_mtime)
                
                # check modification time missmatch
                if currentMtime != modificationTime :
                    try :
                        metadata = FileOpenDialog.getSongMetadata(filePath)
                        musicXmlFound = True
                    except ValueError, e :
                        print e
                        if e.args and e.args[0] == 'not a musicxml file' :
                            continue
                    
                    metadata = FileOpenDialog.getSongMetadata(filePath)
                    line = '%(file)s\t%(mtime)s\t%(title)s\t%(distinctNotes)d\t%(duration)s\n' % metadata
                    indexedFiles[fileBaseName] = line
            
            # check for new files.
            for file in xmlFiles :
                fileBaseName = os.path.basename(file)
                if not indexedFiles.has_key(fileBaseName) :
                    try :
                        metadata = FileOpenDialog.getSongMetadata(filePath)
                        musicXmlFound = True
                    except ValueError, e :
                        print e
                        if e.args and e.args[0] == 'not a musicxml file' :
                            continue
                
                    metadata = FileOpenDialog.getSongMetadata(file)
                    line = '%(file)s\t%(mtime)s\t%(title)s\t%(distinctNotes)d\t%(duration)s\n' % metadata
                    indexedFiles[fileBaseName] = line
            
            # ok, the index is up to date !
            
            index = indexedFiles.values()
            

        if self._current_sort == 'alpha' :
            def s(a, b) :
                da = desacc(a.split('\t')[2]).lower()
                db = desacc(b.split('\t')[2]).lower()
                return cmp(da, db)
                
        elif self._current_sort == 'num' :
            def s(a, b) :
                da = int(a.split('\t')[3])
                db = int(b.split('\t')[3])
                return cmp(da, db)
        else :
            s = cmp
        
        index.sort(s)
        return index
    
    def _set_current_sort_(self, arg) :
        self._current_sort = arg
        self.list.clear()
        self._list_dir_()
    
    def _check_dbl_click_(self, arg) :
        if pygame.time.get_ticks() - self._last_time_click < 300 :
            self._button_okay_clicked_(None)
        else :
            self._last_time_click = pygame.time.get_ticks()
    
    def event(self, e) :
        FileDialog.event(self, e)
        
        if e.type == CLICK and \
           e.button == 1 and \
           self.list.rect.collidepoint(e.pos) :
            self._check_dbl_click_(e)
        
        if e.type == KEYDOWN and e.key == K_RETURN :
            self._button_okay_clicked_(None)
            

# utils
from unicodedata import decomposition
from string import printable
_printable = dict([(c, True) for c in printable])
isPrintable = _printable.has_key

def _recurseDecomposition(uc):
    deco = decomposition(uc).split()
    fullDeco = []
    if deco :
        while (deco) :
            code = deco.pop()
            if code.startswith('<') :
                continue
            c = unichr(int(code, 16))
            subDeco = decomposition(c).split()
            if subDeco :
                deco.extend(subDeco)
            else :
                fullDeco.append(c)
        fullDeco.reverse()
    else :
        fullDeco.append(uc)
    
    fullDeco = u''.join(filter(lambda c : isPrintable(c), fullDeco))
    return fullDeco

def desacc(s, encoding='iso-8859-1') :
    us = s.decode(encoding, 'ignore')
    ret = []
    for uc in us :
        ret.append(_recurseDecomposition(uc))
    return u''.join(ret)