#   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 OBBTimer import OBBTimer
from OBBFuncts import *
from OBBDebugger import *
import os
import sys

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

MAX_LED = 16
SPLASH_DELAY = 5
DEF_BPM = 240

class PatEditorDemo(SimpleApp):
    def __init__( self, demoDelay = DEF_BPM):
        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()

    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 ugly splash ! : )
            self.initSplash()
            self.quitTimer.singleShot( SPLASH_DELAY * 1000,
                                       self.kickSplash )

        self.initSound()
        self.initWidgets()
        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" )}


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

    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 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 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)
        self.connect(self, PYSIGNAL("stop()"), button.turnOff)

    def adjustTempo(self, bpm):
        self.creator.setTempo(bpm)
        self.hitTimer.changeBpm(bpm)

    def adjustRefreshRate(self, Rate):
        self.creator.hitPerRecorderCall = Rate
        self.sndDev.hitsPerLoad = 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.openPixmaps = self.loadButtonPixmaps("open")
        self.upPixmaps = self.loadButtonPixmaps("up")
        self.downPixmaps = self.loadButtonPixmaps("down")

        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.fontPixmaps = self.loadSimplePixmaps("font")
        self.holderPixmaps = self.loadSimplePixmaps("holder")

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

        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)

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

        sBox = OBBSpinBox( self.numPixmaps,
                           self.upPixmaps,
                           self.downPixmaps,
                           holder,
                           185,
                           55,
                           15,
                           20,
                           100,
                           12,
                           15,
                           24 )
        holder.addSubWidget(sBox)
        sBox.setValue(self.creator.getTempo())
        self.connect( sBox,
                      PYSIGNAL("valueChanged(int)"),
                      self.adjustTempo )

        volumeSlider = OBBSlider( self.sliderFramePixmaps,
                                  self.sliderHandlePixmaps,
                                  holder,
                                  320,
                                  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
        filename = str(filename)

        if filename:
            self.sampleMap[hitId] = 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)*70+startX,
                               startY,
                               47,
                               12,
                               holderId,
                               j)


    def addHitButtons(self, holderId, holder):
        self.addHitButtonGroup( holderId, holder, 0, 3, 365, 15, 1, 2)
        self.addHitButtonGroup( holderId, holder, 4, 7, 380, 15, 1, 2)

        self.addHitButtonGroup( holderId, holder, 8, 11, 335, 50, 2, 2)
        self.addHitButtonGroup( holderId, holder, 12, 15, 350, 50, 2, 2)


    def initWidgets(self):
        self.loadImages()

        floater = Floater()
        vholder = OBBFrame(self.vholderPixmaps, floater)
        floater.addSubWidget(vholder)
        self.floaters.append(floater)

        # holders
        for i in range(4):
            holder = StretchFrame( self.holderPixmaps,
                                   floater,
                                   25,
                                   i*130+15,
                                   "x",
                                   100,
                                   120,
                                   1000 )

            # only one label (they are too slow)
            label = OBBLabel( self.sframePixmaps,
                              holder,
                              360,
                              84,
                              20,
                              50,
                              500 )

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

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

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

            holder.addSubWidget(slider)

            funct = lambda val, id=i : self.creator.setPanning(id, val)
            self.connect(slider, PYSIGNAL("valueChanged(int)"), funct)
            self.lambdaFuncts.append(funct)

            funct = lambda lab=label, id=i : self.loadSample(lab, id)
            self.addPushButton(self.openPixmaps, holder,  60, 17, funct)
            self.lambdaFuncts.append(funct)

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

            self.addHitButtons(i, holder)

            floater.addSubWidget(holder)

        self.createControlBar()

        for floater in self.floaters:
            floater.reCenter()
            floater.show()
            if self.splash:
                self.splash.raiseW()

        floater.move( (qApp.desktop().width() - floater.width())/2, 10 )

        floater = self.floaters[0]

        floater.move( (qApp.desktop().width() - floater.width())/2,
                      (qApp.desktop().height() - floater.height())/2 + 30 )

        self.app.setMainWidget(floater)

