#   Copyright (C) 2002-2003 Yannick Gingras <ygingras@ygingras.net>
#   Copyright (C) 2002-2003 Vincent Barbin <vbarbin@openbeatbox.org>

#   This file is part of Open Beat Box.

#   Open Beat Box is free software; you can redistribute it and/or modify
#   it under the terms of the GNU General Public License as published by
#   the Free Software Foundation; either version 2 of the License, or
#   (at your option) any later version.

#   Open Beat Box is distributed in the hope that it will be useful,
#   but WITHOUT ANY WARRANTY; without even the implied warranty of
#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#   GNU General Public License for more details.

#   You should have received a copy of the GNU General Public License
#   along with Open Beat Box; if not, write to the Free Software
#   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
from qt import *
from SimpleApp import SimpleApp
import time
from threading import Thread
from OBBGui.Floater import Floater
from OBBGui.OBBWidget import OBBWidget
from OBBGui.PushButton import PushButton
from OBBGui.ToggleButton import ToggleButton
from OBBGui.HitButton import HitButton
from OBBGui.OBBLight import OBBLight
from OBBGui.OBBFrame import OBBFrame
from OBBGui.OBBText import OBBText
from OBBGui.StretchFrame import StretchFrame
from OBBGui.OBBLabel import OBBLabel
from OBBGui.OBBSlider import OBBSlider
from OBBGui.OBBSpinBox import OBBSpinBox
from OBBGui.PixmapSet import *
from OBBGui.ImageLoader import ImageLoader
from OBBUtils.OBBTimer import OBBTimer
from OBBFuncts import *
from OBBDebugger import *
from os.path import splitext
import sys

# sound stuff
from OBBSound.OBBSongCreator import OBBSongCreator
from OBBSound.SoundDevice import SoundDevice

MAX_LED = 16
MAX_INSTR = 8
SPLASH_DELAY = 5
DEF_BPM = 240

DEFAULT_PATERN_FILE = os.path.join(getPatDir(), "Untitled.opt")

class PatEditorDemo(SimpleApp):
    def __init__( self, demoDelay = DEF_BPM, Verbose = 0):
        SimpleApp.__init__(self)
        self.demoDelay = demoDelay
        self.hitTimer = OBBTimer(DEF_BPM)
        self.lambdaFuncts = [] # to keep a ref on anonymous functs
        self.floaters = []
        self.imgLoader = ImageLoader()
        self.scrollTimer = QTimer(self)
        self.connect(self.scrollTimer, SIGNAL("timeout()"), self.scroll)
        self.debugger = OBBDebugger(Verbose)

    def exec_loop(self):
        self.app = QApplication([])

        print "BPM : %d" % self.hitTimer.getInterval()
        self.quitTimer = QTimer(self)

        if self.demoDelay: # auto-kick gui (probably in a unittest)
            self.splash = None
            self.quitTimer.singleShot( self.demoDelay * 1000,
                                       self.quit )
        else: # show the splash ! : )
            self.initSplash()
            self.quitTimer.singleShot( SPLASH_DELAY * 1000,
                                       self.kickSplash )

        self.initSound()
        self.initWidgets()
        self.creator.setFileName(DEFAULT_PATERN_FILE)
        self.scrollTimer.start(100)
        self.app.exec_loop()
        

    def initSampleMap(self):
        self.sampleMap = { 0:os.path.join( getSndDir(), "hat03.wav"   ),
                           1:os.path.join( getSndDir(), "hat07.wav"   ),
                           2:os.path.join( getSndDir(), "kick01.wav"  ),
                           3:os.path.join( getSndDir(), "snr02.wav"   ),
                           4:os.path.join( getSndDir(), "bongo02.wav" ),
                           5:os.path.join( getSndDir(), "tom01.wav"   ),
                           6:os.path.join( getSndDir(), "snr04.wav"   ),
                           7:os.path.join( getSndDir(), "tabla01.wav" ),}

    def initSound(self):
        self.initSampleMap()
        self.creator = OBBSongCreator()
        self.creator.newSongDocument(DEF_BPM)
        self.sndDev = SoundDevice()
        self.creator.setHitPerCycle(MAX_LED)
        self.adjustTempo(self.creator.getTempo())
        self.adjustRefreshRate(2)
        for i in range(MAX_INSTR):
            self.creator.setInstrument( i, self.sampleMap[i] )

        self.connect( self.hitTimer, PYSIGNAL("hit(int)"), self.sndDev.handleHit )
        self.connect( self.hitTimer, PYSIGNAL("hit(int)"), self.forwardHit )
        self.connect( self,
                      PYSIGNAL("hitStateChanged(int, int)"),
                      self.forwardHitState )
        self.connect( self, PYSIGNAL("play()"), self.playLoop )
        self.connect( self, PYSIGNAL("stop()"), self.sndDev.stop )
        self.connect( self.creator,
                      PYSIGNAL("createSample()"),
                      self.sndDev.handleLoadSample )

        self.connect(self.creator,
                     PYSIGNAL("fileNameChanged(str)"),
                     self.updateFileNameLabel)


    def forwardHit(self, hitId):
        self.creator.handleHit(hitId)

    def forwardHitState(self, holderId, hitId):
        self.creator.handleSongChange(holderId, hitId)

    def playLoop(self):
        self.hitTimer.changeBpm(self.creator.getTempo())
        self.sndDev.handleHit(MAX_LED-1)
        self.forwardHit(MAX_LED-1)

    def kickSplash(self):
        self.splash.hide()
        del(self.splash)
        del(self.splashPix)

    def initSplash(self):
        self.splash = Floater()

        self.splashPix = self.loadSimplePixmaps("splash")

        splashFrame = OBBFrame(self.splashPix, self.splash)

        self.splash.addSubWidget(splashFrame)
        self.splash.reCenter()
        self.splash.show()
        qApp.processEvents()

    def changeHitState(self, holderId, buttonId):
        debug("stateChanged : (%d, %d)" % ( holderId, buttonId ))
        self.emit(PYSIGNAL("hitStateChanged(int, int)"), ( holderId, buttonId, ))

    def play(self):
        debug("play")
        self.emit(PYSIGNAL("stop()"), ())
        self.hitTimer.start()
        self.emit(PYSIGNAL("play()"), ())

    def rec(self):
        debug("rec")
        self.emit(PYSIGNAL("rec()"), ())


    def stop(self):
        debug("stop")
        self.hitTimer.stop()
        self.emit(PYSIGNAL("stop()"), ())

    def newFile(self):
        self.creator.newSongDocument(DEF_BPM, DEFAULT_PATERN_FILE, self.sampleMap)
    
    def save(self):
        if self.creator.getFileName() == DEFAULT_PATERN_FILE:
            self.saveAs()
        
    def saveAs(self):
        filename = QFileDialog.getSaveFileName( self.creator.doc.getFileName(),
                                                "OBB Pattern (*.opt)")
        # QStrings are not false when empty (and we like pyStrings...)
        filename = unicode(filename).strip()
               
        if filename:
            if filename == splitext(filename)[0]:
                filename = filename + ".opt"
            self.creator.setFileName(filename)
            self.creator.save()

    def load(self):
        filename = QFileDialog.getOpenFileName( self.creator.doc.getFileName(),
                                                "OBB Pattern (*.opt)")
        # QStrings are not false when empty (and we like pyStrings...)
        filename = unicode(filename).strip()
        
        if filename:
            self.creator.loadFile(filename)

    def quit(self):
        self.stop()
        self.app.closeAllWindows()
        self.creator.cleanUpTemporaryFiles()
        print "quit"

    def updateLedState(self, led, hitId, curHit):
        if hitId == curHit:
            led.turnOn()
        elif (hitId+1) % MAX_LED == curHit: # previous led
            led.turnOff()
        #else:
        #    pass # nothing to do
            
    def updateHitButtonState(self, position, instr, buttonId, holderId, hitButton):
        if buttonId == position and holderId == instr:
            hitButton.toggleState()
    
    def updateIntrumentLabel(self, instrument, instrumentId, label, text):
        if instrument == instrumentId:
            label.setText(text)
            
            
    def updateInstrumentPanning(self, instrumentId, instrument, slider, value):
        if instrument == instrumentId:
            slider.setValue(value)
            
    def scroll(self):
        self.emit(PYSIGNAL("scroll()"), ())

    def loadButtonPixmaps(self, type):
        pixmaps = PixmapSet()
        for state in (DISABLED, ACTIVATED, DESACTIVATED):
            pixmap = self.imgLoader.loadPixmap( os.path.join( getImgDir(),
                                            "%s_%s.png" % (type, state)))
            pixmaps.addState(pixmap, state)
        return pixmaps

    def loadSimplePixmaps(self, type):
        pixmaps = PixmapSet()
        pixmap = self.imgLoader.loadPixmap( os.path.join( getImgDir(),
                                        ("%s.png" % type)))
        pixmaps.addState(pixmap, DISABLED)

        return pixmaps


    def addPushButton(self, pixmapSet, parent, x, y, command, enabled=1):
        button = PushButton(pixmapSet, parent, x, y)
        self.connect(button, PYSIGNAL("clicked()"), command)
        parent.addSubWidget(button)
        button.enable(enabled)

    def addHitButton( self,
                      butPixSet,
                      ledPixSet,
                      parent,
                      x,
                      y,
                      ledX,
                      ledY,
                      holderId,
                      butId ):

        button = HitButton(butPixSet, ledPixSet, parent, x, y, ledX, ledY)
        func = lambda hId=holderId, bId=butId : self.changeHitState(hId, bId)
        self.lambdaFuncts.append(func)
        self.connect( button,
                      PYSIGNAL("stateChanged()"),
                      func )
        
        parent.addSubWidget(button)

        func = lambda curHit, wid=button, hId=butId: self.updateLedState(
            wid,
            hId,
            curHit )
        self.lambdaFuncts.append(func)
        self.connect(self.hitTimer, PYSIGNAL("hit(int)"), func)
        
        func = lambda bId, hId, wbId=butId, whId=holderId, abId=button :\
            self.updateHitButtonState(bId, hId, wbId, whId, abId)
        self.lambdaFuncts.append(func)
        self.connect(self.creator, PYSIGNAL("hitStateLoaded(int, int)"), func)
        
        self.connect(self, PYSIGNAL("stop()"), button.turnOff)
        
    def adjustTempo(self, bpm, tempoSpinBox=None ):
        self.creator.setTempo(bpm)
        self.hitTimer.changeBpm(bpm)
        if tempoSpinBox != None:
            tempoSpinBox.setValue(bpm)
        
    def adjustRefreshRate(self, Rate):
        self.creator.hitPerRecorderCall = Rate
        self.sndDev.hitsPerLoad = Rate
        self.hitTimer.refreshRate = Rate

    def loadImages(self):
        self.buttonPixmaps = self.loadButtonPixmaps("hit")
        self.ledPixmaps = self.loadButtonPixmaps("led")
        self.recPixmaps = self.loadButtonPixmaps("rec")
        self.playPixmaps = self.loadButtonPixmaps("play")
        self.stopPixmaps = self.loadButtonPixmaps("stop")
        self.powPixmaps = self.loadButtonPixmaps("pow")
        self.prevPixmaps = self.loadButtonPixmaps("prev")
        self.newPixmaps = self.loadButtonPixmaps("new")
        self.openPixmaps = self.loadButtonPixmaps("open")
        self.upPixmaps = self.loadButtonPixmaps("up")
        self.downPixmaps = self.loadButtonPixmaps("down")
        self.savePixmaps = self.loadButtonPixmaps("save")
        self.saveAsPixmaps = self.loadButtonPixmaps("save_as")
        self.settingsPixmaps = self.loadButtonPixmaps("settings")

        self.sliderFramePixmaps = self.loadSimplePixmaps("slider_frame")
        self.sliderHandlePixmaps = self.loadSimplePixmaps("slider_handle")
        self.vholderPixmaps = self.loadSimplePixmaps("vholder")
        self.sframePixmaps = self.loadSimplePixmaps("lback")
        self.numPixmaps = self.loadSimplePixmaps("lback_big")
        self.holderPixmaps = self.loadSimplePixmaps("holder")
        self.hitHolderPixmaps = self.loadSimplePixmaps("hit_holder")

    def createControlBar(self):
        floater = Floater()
        holder = StretchFrame( self.holderPixmaps,
                               floater,
                               0,
                               0,
                               "x",
                               100,
                               120,
                               800 )

        self.addPushButton(self.powPixmaps,      holder,  27, 51, self.quit)
        self.addPushButton(self.playPixmaps,     holder,  60, 17, self.play)
        self.addPushButton(self.recPixmaps,      holder,  92, 51, self.rec, 0)
        self.addPushButton(self.stopPixmaps,     holder, 125, 17, self.stop)
        self.addPushButton(self.savePixmaps,     holder, 157, 51,
                                                             self.save)
        self.addPushButton(self.openPixmaps,     holder, 189, 17, self.load)
        self.addPushButton(self.saveAsPixmaps,   holder, 221, 51, self.saveAs)
        self.addPushButton(self.newPixmaps,      holder, 253, 17, self.newFile)

        self.filenameLabel = OBBLabel( self.sframePixmaps,
                                       holder,
                                       315,
                                       15,
                                       20,
                                       40,
                                       380 )
        self.connect(self, PYSIGNAL("scroll()"), self.filenameLabel.scroll)
        holder.addSubWidget(self.filenameLabel)

        sBox = OBBSpinBox( self.numPixmaps,
                           self.upPixmaps,
                           self.downPixmaps,
                           holder,
                           315,
                           55,
                           15,
                           20,
                           100,
                           12,
                           15,
                           24 )
        holder.addSubWidget(sBox)
        sBox.setValue(self.creator.getTempo())
        self.connect( sBox,
                      PYSIGNAL("valueChanged(int)"),
                      self.adjustTempo )
        self.adjustTempo(self.creator.getTempo())
        funct = lambda bpm, sBoxId=sBox : self.adjustTempo(bpm, sBoxId)
        self.connect( self.creator,
                      PYSIGNAL("tempoValueLoaded(int)"),
                      funct)
        self.lambdaFuncts.append(funct)
        volumeSlider = OBBSlider( self.sliderFramePixmaps,
                                  self.sliderHandlePixmaps,
                                  holder,
                                  450,
                                  60,
                                  "x",
                                  30,
                                  25,
                                  200,
                                  defaultValue=100)

        holder.addSubWidget(volumeSlider)

        self.connect(volumeSlider,
                     PYSIGNAL("valueChanged(int)"),
                     self.sndDev.setVolume)
        self.connect(volumeSlider,
                     PYSIGNAL("valueChanging(int)"),
                     self.sndDev.setVolume )

        floater.addSubWidget(holder)
        self.floaters.append(floater)


    def loadSample(self, hitLabel, hitId):
        filename = QFileDialog.getOpenFileName( getSndDir(),
                                                "Sounds (*.wav)")

        # QStrings are not false when empty (and we like pyStrings...)
        filename = str(filename).strip()
        
        if filename:
            filename = os.path.join( getSndDir(),
                                     os.path.basename(filename))
            
            self.creator.setInstrument( hitId, filename )
            hitLabel.setText(filename)

    def previewSample(self, patternId):
        self.sndDev.play(self.creator.getInstrument(patternId))

    def addHitButtonGroup( self,
                           holderId,
                           holder,
                           fist,
                           last,
                           startX,
                           startY,
                           row,
                           nbRow):
        for j in range(fist, last+1):
            self.addHitButton( self.buttonPixmaps,
                               self.ledPixmaps,
                               holder,
                               (j-MAX_LED*(row-1)/nbRow)*58+startX,
                               startY,
                               35,
                                8,
                               holderId,
                               j)


    def addHitButtons(self, holderId, holder):
        self.addHitButtonGroup( holderId, holder, 0, 3, 205, 9, 1, 2)
        self.addHitButtonGroup( holderId, holder, 4, 7, 220, 9, 1, 2)

        self.addHitButtonGroup( holderId, holder,  8, 11, 175, 36, 2, 2)
        self.addHitButtonGroup( holderId, holder, 12, 15, 190, 36, 2, 2)


    def initWidgets(self):
        # tweak the double click
        qApp.setDoubleClickInterval(0)
        
        self.loadImages()

        floater = Floater()
        vholder = StretchFrame(self.vholderPixmaps,
                               floater,
                               0,
                               0,
                               "y",
                               200,
                               10,
                               620)
        floater.addSubWidget(vholder)
        self.floaters.append(floater)

        # holders
        for i in range(MAX_INSTR):
            holder = StretchFrame( self.hitHolderPixmaps,
                                   floater,
                                   25,
                                   i*73+15,
                                   "x",
                                   100,
                                    20,
                                   720 )


            funct = lambda id=i : self.previewSample(id)
            self.addPushButton(self.prevPixmaps, holder, 20, 9, funct)
            self.lambdaFuncts.append(funct)

           
            settingsFloater = self.addSettingsFloater(300, i*73+15, i)

            led = OBBLight(self.ledPixmaps, holder, 116, 44)
            holder.addSubWidget(led)

            funct = lambda fl=settingsFloater, l=led : self.toggleVis(fl, l)
            self.addPushButton(self.settingsPixmaps, holder, 80, 9, funct)
            self.lambdaFuncts.append(funct)

            self.addHitButtons(i, holder)

            floater.addSubWidget(holder)

        self.createControlBar()
        floater.move( (qApp.desktop().width() - floater.width())/2,
                      (qApp.desktop().height() - floater.height())/2 + 43 )
        floater.show()

        if self.splash:
            self.splash.raiseW()

        floater = self.floaters[-1] # last one is control bar
        floater.move( (qApp.desktop().width() - floater.width())/2, 8 )
        floater.show()


        self.app.setMainWidget(floater)


    def addSettingsFloater(self, x, y, instrId):
        floater = Floater()
        holder = StretchFrame( self.holderPixmaps,
                               floater,
                               0,
                               0,
                               "x",
                               100,
                               120,
                               800 )
        floater.addSubWidget(holder)
        self.floaters.append(floater)
        floater.move(x, y)

        label = OBBLabel( self.sframePixmaps,
                              holder,
                              125,
                              20,
                              20,
                              10,
                              70 )
        label.setText("instr.#%d" % (instrId + 1))
        holder.addSubWidget(label)

        label = OBBLabel( self.sframePixmaps,
                              holder,
                              125,
                              50,
                              20,
                              10,
                              70 )
            
        label.setText("balance :")
        holder.addSubWidget(label)

        slider = OBBSlider( self.sliderFramePixmaps,
                            self.sliderHandlePixmaps,
                            holder,
                            200,
                            50,
                            "x",
                            30,
                            25,
                            150,
                            minRange=-100,
                            maxRange=100,
                            defaultValue=0)
            

        holder.addSubWidget(slider)
        funct = lambda val, id=instrId : self.creator.setPanning(id, val)
        self.connect(slider, PYSIGNAL("valueChanged(int)"), funct)
        self.lambdaFuncts.append(funct)
    
        funct = lambda instrument, value, instrumentId=instrId, sliderId=slider :\
            self.updateInstrumentPanning(instrumentId, instrument, sliderId, value)
        self.lambdaFuncts.append(funct)
        self.connect(self.creator, PYSIGNAL("instrumentPanValueLoaded(int, int)"), funct)
        
        label = OBBLabel( self.sframePixmaps,
                          holder,
                          80,
                          84,
                          20,
                          10,
                          570 )

        label.setText( self.sampleMap[instrId] )
        holder.addSubWidget(label)

        funct = lambda lab=label, id=instrId : self.loadSample(lab, id)
        self.addPushButton(self.openPixmaps, holder,  27, 49, funct)
        self.lambdaFuncts.append(funct)
        
        funct = lambda instrument, text, instrumentId=instrId, lab=label :\
            self.updateIntrumentLabel(instrumentId, instrument, lab, text)
        self.lambdaFuncts.append(funct)
        self.connect(self.creator, PYSIGNAL("instrumentFileNameLoaded(int, string)"), funct)
        return floater

    def toggleVis(self, floater, led):
        if floater.isVisible():
            floater.hide()
            led.turnOff()
        else:
            floater.show()
            led.turnOn()
            
    def updateFileNameLabel(self, filename):
        msg = " "*75
        msg += "working on " + filename
        
        self.filenameLabel.setText(msg)
    
        
