#!/usr/bin/python
#
# Copyright (C) 2005-2007 by Pieter Palmers
#               2007-2008 by Arnold Krille
#
# This file is part of FFADO
# FFADO = Free Firewire (pro-)audio drivers for linux
#
# FFADO is based upon FreeBoB.
#
# This program 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 3 of the License, or
# (at your option) any later version.
#
# This program 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 this program.  If not, see <http://www.gnu.org/licenses/>.
#

import sys

# Add the path of the installed ffado-mixer-modules
sys.path.append( "$PYTHONDIR" )

from ffadomixer_config import FFADO_VERSION

import os
import time
import dbus

from qt import *

from ffado_registration import *

from mixer_phase88 import *
from mixer_phase24 import *
from mixer_saffire import *
from mixer_saffirele import *
from mixer_saffirepro import *
from mixer_audiofire import *
from mixer_bcoaudio5 import *
from mixer_edirolfa66 import *
from mixer_edirolfa101 import *
from mixer_mackie_generic import *
from mixer_quatafire import *
from mixer_motu import *
from mixer_dummy import *
from mixer_global import *

use_generic = False
try:
    from mixer_generic import *
    print "The generic mixer is found, seems to be a developer using ffadomixer..."
except ImportError:
    pass
else:
    use_generic = True

SupportedDevices=[
    [(0x000aac, 0x00000003),'Phase88Control'],
    [(0x000aac, 0x00000004),'Phase24Control'],
    [(0x000aac, 0x00000007),'Phase24Control'],
    [(0x00130e, 0x00000003),'SaffireProMixer'],
    [(0x00130e, 0x00000006),'SaffireProMixer'],
    [(0x00130e, 0x00000000),'SaffireMixer'],
    [(0x001486, 0x00000af2),'AudioFireMixer'],
    [(0x001486, 0x00000af4),'AudioFireMixer'],
    [(0x001486, 0x00000af8),'AudioFireMixer'],
    [(0x001486, 0x0000af12),'AudioFireMixer'],
    [(0x0007f5, 0x00010049),'BCoAudio5Control'],
    [(0x0040AB, 0x00010049),'EdirolFa66Control'],
    [(0x0040AB, 0x00010048),'EdirolFa101Control'],
    [(0x00000f, 0x00010067),'MackieGenericControl'],
    [(0x000f1b, 0x00010064),'QuataFireMixer'],
    [(0x0001f2, 0x00000000),'MotuMixer'],
    ]

POLL_SLEEP_TIME_MSEC = 100 # 100ms

class ControlInterface:
    def __init__(self, servername, basepath):
        self.basepath=basepath
        self.servername=servername
        self.bus=dbus.SessionBus()

    def setContignuous(self, subpath, v, idx=None):
        try:
            path = self.basepath + subpath
            dev = self.bus.get_object(self.servername, path)
            dev_cont = dbus.Interface(dev, dbus_interface='org.ffado.Control.Element.Continuous')
            if idx == None:
                dev_cont.setValue(v)
            else:
                dev_cont.setValueIdx(idx,v)
        except:
            print "Failed to set Continuous %s on server %s" % (path, self.servername)

    def getContignuous(self, subpath, idx=None):
        try:
            path = self.basepath + subpath
            dev = self.bus.get_object(self.servername, path)
            dev_cont = dbus.Interface(dev, dbus_interface='org.ffado.Control.Element.Continuous')
            if idx == None:
                return dev_cont.getValue()
            else:
                return dev_cont.getValueIdx(idx)
        except:
            print "Failed to get Continuous %s on server %s" % (path, self.servername)
            return 0

    def setDiscrete(self, subpath, v):
        try:
            path = self.basepath + subpath
            dev = self.bus.get_object(self.servername, path)
            dev_cont = dbus.Interface(dev, dbus_interface='org.ffado.Control.Element.Discrete')
            dev_cont.setValue(v)
        except:
            print "Failed to set Discrete %s on server %s" % (path, self.servername)

    def getDiscrete(self, subpath):
        try:
            path = self.basepath + subpath
            dev = self.bus.get_object(self.servername, path)
            dev_cont = dbus.Interface(dev, dbus_interface='org.ffado.Control.Element.Discrete')
            return dev_cont.getValue()
        except:
            print "Failed to get Discrete %s on server %s" % (path, self.servername)
            return 0

    def setText(self, subpath, v):
        try:
            path = self.basepath + subpath
            dev = self.bus.get_object(self.servername, path)
            dev_cont = dbus.Interface(dev, dbus_interface='org.ffado.Control.Element.Text')
            dev_cont.setValue(v)
        except:
            print "Failed to set Text %s on server %s" % (path, self.servername)

    def getText(self, subpath):
        try:
            path = self.basepath + subpath
            dev = self.bus.get_object(self.servername, path)
            dev_cont = dbus.Interface(dev, dbus_interface='org.ffado.Control.Element.Text')
            return dev_cont.getValue()
        except:
            print "Failed to get Text %s on server %s" % (path, self.servername)
            return 0

    def setMatrixMixerValue(self, subpath, row, col, v):
        try:
            path = self.basepath + subpath
            dev = self.bus.get_object(self.servername, path)
            dev_cont = dbus.Interface(dev, dbus_interface='org.ffado.Control.Element.MatrixMixer')
            dev_cont.setValue(row, col, v)
        except:
            print "Failed to set MatrixMixer %s on server %s" % (path, self.servername)

    def getMatrixMixerValue(self, subpath, row, col):
        try:
            path = self.basepath + subpath
            dev = self.bus.get_object(self.servername, path)
            dev_cont = dbus.Interface(dev, dbus_interface='org.ffado.Control.Element.MatrixMixer')
            return dev_cont.getValue(row, col)
        except:
            print "Failed to get MatrixMixer %s on server %s" % (path, self.servername)
            return 0

    def enumSelect(self, subpath, v):
        try:
            path = self.basepath + subpath
            dev = self.bus.get_object(self.servername, path)
            dev_cont = dbus.Interface(dev, dbus_interface='org.ffado.Control.Element.Enum')
            dev_cont.select(v)
        except:
            print "Failed to select %s on server %s" % (path, self.servername)

    def enumSelected(self, subpath):
        try:
            path = self.basepath + subpath
            dev = self.bus.get_object(self.servername, path)
            dev_cont = dbus.Interface(dev, dbus_interface='org.ffado.Control.Element.Enum')
            return dev_cont.selected()
        except:
            print "Failed to get selected enum %s on server %s" % (path, self.servername)
            return 0

    def enumGetLabel(self, subpath, v):
        try:
            path = self.basepath + subpath
            dev = self.bus.get_object(self.servername, path)
            dev_cont = dbus.Interface(dev, dbus_interface='org.ffado.Control.Element.Enum')
            return dev_cont.getEnumLabel(v)
        except:
            print "Failed to get enum label %s on server %s" % (path, self.servername)
            return 0

    def enumCount(self, subpath):
        try:
            path = self.basepath + subpath
            dev = self.bus.get_object(self.servername, path)
            dev_cont = dbus.Interface(dev, dbus_interface='org.ffado.Control.Element.Enum')
            return dev_cont.count()
        except:
            print "Failed to get enum count %s on server %s" % (path, self.servername)
            return 0

class DeviceManagerInterface:
    def __init__(self, servername, basepath):
        self.basepath=basepath + '/DeviceManager'
        self.servername=servername
        self.bus=dbus.SessionBus()
        self.dev = self.bus.get_object(self.servername, self.basepath)
        self.iface = dbus.Interface(self.dev, dbus_interface='org.ffado.Control.Element.Container')
        # signal reception does not work yet since we need a mainloop for that
        # and qt3 doesn't provide one for python/dbus
        #try:
            #self.dev.connect_to_signal("Updated", self.updateSignal, \
                                       #dbus_interface="org.ffado.Control.Element.Container", arg0=self)
        #except dbus.DBusException:
            #traceback.print_exc()

    #def updateSignal(self):
        #print ("Received update signal")

    def getNbDevices(self):
        return self.iface.getNbElements()
    def getDeviceName(self, idx):
        return self.iface.getElementName(idx)


class ConfigRomInterface:
    def __init__(self, servername, devicepath):
        self.basepath=devicepath + '/ConfigRom'
        self.servername=servername
        self.bus=dbus.SessionBus()
        self.dev = self.bus.get_object(self.servername, self.basepath)
        self.iface = dbus.Interface(self.dev, dbus_interface='org.ffado.Control.Element.ConfigRomX')
    def getGUID(self):
        return self.iface.getGUID()
    def getVendorName(self):
        return self.iface.getVendorName()
    def getModelName(self):
        return self.iface.getModelName()
    def getVendorId(self):
        return self.iface.getVendorId()
    def getModelId(self):
        return self.iface.getModelId()
    def getUnitVersion(self):
        return self.iface.getUnitVersion()

class ClockSelectInterface:
    def __init__(self, servername, devicepath):
        self.basepath=devicepath + '/Generic/ClockSelect'
        self.servername=servername
        self.bus=dbus.SessionBus()
        self.dev = self.bus.get_object(self.servername, self.basepath)
        self.iface = dbus.Interface(self.dev, dbus_interface='org.ffado.Control.Element.AttributeEnum')
    def count(self):
        return self.iface.count()
    def select(self, idx):
        return self.iface.select(idx)
    def selected(self):
        return self.iface.selected()
    def getEnumLabel(self, idx):
        return self.iface.getEnumLabel(idx)
    def attributeCount(self):
        return self.iface.attributeCount()
    def getAttributeValue(self, idx):
        return self.iface.getAttributeValue(idx)
    def getAttributeName(self, idx):
        return self.iface.getAttributeName(idx)

class SamplerateSelectInterface:
    def __init__(self, servername, devicepath):
        self.basepath=devicepath + '/Generic/SamplerateSelect'
        self.servername=servername
        self.bus=dbus.SessionBus()
        self.dev = self.bus.get_object(self.servername, self.basepath)
        self.iface = dbus.Interface(self.dev, dbus_interface='org.ffado.Control.Element.Enum')
    def count(self):
        return self.iface.count()
    def select(self, idx):
        return self.iface.select(idx)
    def selected(self):
        return self.iface.selected()
    def getEnumLabel(self, idx):
        return self.iface.getEnumLabel(idx)

class TextInterface:
    def __init__(self, servername, basepath):
        self.basepath=basepath
        self.servername=servername
        self.bus=dbus.SessionBus()
        self.dev = self.bus.get_object( self.servername, self.basepath )
        self.iface = dbus.Interface( self.dev, dbus_interface="org.ffado.Control.Element.Text" )

    def text(self):
        return self.iface.getValue()

    def setText(self,text):
        self.iface.setValue(text)

class HLine( QFrame ):
    def __init__( self, parent ):
        QFrame.__init__( self, parent )
        self.setFrameShape( QFrame.HLine )
        self.setLineWidth( 2 )
        self.setMinimumHeight( 10 )

class PollUpdateWidgets(QObject):
    def __init__(self, widgets):
        QObject.__init__(self)
        self.widgets = widgets

    def updateWidgets(self):
        for w in self.widgets:
            if 'polledUpdate' in dir(w):
                try:
                    w.polledUpdate()
                except:
                    print "error in polled update"

if __name__ == "__main__":

    server='org.ffado.Control'
    basepath='/org/ffado/Control'

    app = QApplication(sys.argv)

    msg = QMessageBox()

    repeat = 1
    while repeat > 0:
        try:
            devmgr=DeviceManagerInterface(server, basepath)
            nbDevices=devmgr.getNbDevices()
            repeat -= 1
        except dbus.DBusException, ex:
            print "\n"
            print "==========================================================="
            print "ERROR: Could not communicate with the FFADO DBus service..."
            print "==========================================================="
            print "\n"
            tmp = msg.question( msg, "FFADO-DBus not found", "<qt><b>The connection to FFADOs DBus service could not be established.</b><p>Probably you didn't start the ffado-dbus-server. Should I try this now?</qt>", QMessageBox.Yes, QMessageBox.No )
            if tmp == 4:
                sys.exit(-1)
            else:
                os.spawnlp( os.P_NOWAIT, "ffado-dbus-server" )
                nb_checks = 20
                while nb_checks > 0:
                    nb_checks = nb_checks - 1
                    try:
                        devmgr=DeviceManagerInterface(server, basepath)
                        nbDevices=devmgr.getNbDevices()
                        nb_checks = 0
                        repeat = 0
                    except dbus.DBusException, ex:
                        time.sleep( 1 )

    if nbDevices == 0:
        print "No supported device found..."
        msg.information( msg, "No mixer found", "No devices with mixer support discovered." )
        sys.exit( -1 )

    mw = QTabWidget()

    mixerwidgets = []

    for idx in range(nbDevices):
        path=devmgr.getDeviceName(idx)
        print "Found device %d: %s" % (idx, path)

        cfgrom = ConfigRomInterface(server, basepath+'/DeviceManager/'+path)
        vendorId = cfgrom.getVendorId()
        modelId = cfgrom.getModelId()
        unitVersion = cfgrom.getUnitVersion()
        GUID = cfgrom.getGUID()
        vendorName = cfgrom.getVendorName()
        modelName = cfgrom.getModelName()
        print " Found (%s, %X, %X) %s %s" % (str(GUID), vendorId, modelId, vendorName, modelName)

        # check whether this has already been registered at ffado.org
        reg = ffado_registration(FFADO_VERSION, int(GUID, 16),
                                 vendorId, modelId,
                                 vendorName, modelName)
        reg.check_for_registration()

        thisdev=(vendorId, modelId);
        # The MOTU devices use unitVersion to differentiate models.  For the
        # moment thought we don't need to know precisely which model we're
        # using.
        if vendorId == 0x1f2:
            thisdev=(vendorId, 0x00000000)

        dev = None
        for d in SupportedDevices:
            if d[0] == thisdev:
                dev = d

        w = QWidget( mw )
        l = QVBoxLayout( w )

        # create a control object
        hw = ControlInterface(server, basepath+'/DeviceManager/'+path)
        clockselect = ClockSelectInterface( server, basepath+"/DeviceManager/"+path )
        samplerateselect = SamplerateSelectInterface( server, basepath+"/DeviceManager/"+path )
        nickname = TextInterface( server, basepath+"/DeviceManager/"+path+"/Generic/Nickname" )

        #
        # Generic elements for all mixers follow here:
        #
        tmp = GlobalMixer( w )
        tmp.configrom = cfgrom
        tmp.clockselect = clockselect
        tmp.samplerateselect = samplerateselect
        tmp.nickname = nickname
        tmp.hw = hw
        tmp.initValues()
        l.addWidget( tmp, 1 )

        #
        # Line to separate
        #
        l.addWidget( HLine( w ) )

        #
        # Specific (or dummy) mixer widgets get loaded in the following
        #
        if dev != None:
            mixerapp = dev[1]
            if vendorId == 0x00130e:
                is_saffirele = False
                is_saffirepro = False

                # hack for the focusrite devices
                # Saffire:        0x130e010001????
                # SaffireLE:    0x130e010004????
                if modelId == 0x00000000:
                    if int(GUID, 16) >= 0x130e0100040000:
                        is_saffirele = True
                        print "Found SaffireLE GUID"
                    else:
                        is_saffirele = False
                        print "Found Saffire GUID"

                # different panel for different clock frequency
                selected = samplerateselect.selected()
                samplerate = int(samplerateselect.getEnumLabel( selected ))

                # adat on PRO26 makes a difference
                have_adat = False
                if modelId == 0x00000003: # PRO26
                    is_saffirepro = True
                    state = hw.getDiscrete('/Control/ADATDisable')
                    if state:
                        have_adat = False
                        print "detected PRO26, ADAT disabled"
                    else:
                        have_adat = True
                        print "detected PRO26, ADAT enabled"
                elif modelId == 0x00000006: # PRO10
                    is_saffirepro = True

                suffix = ''
                if samplerate <= 48000:
                    suffix = "Large"
                # on the saffire pro 26, the large panel can be used
                # at 96k when adat is disabled. pro10 = pro26 w/o ADAT
                elif samplerate <= 96000 and is_saffirepro and have_adat:
                    suffix = "Small"
                elif samplerate <= 96000 and is_saffirepro:
                    suffix = "Large"
                # higher samplerates need the small panel
                else:
                    suffix = "Small"

                if is_saffirepro:
                    mixerapp = "SaffireProMixer" + suffix
                elif is_saffirele:
                    mixerapp = "SaffireLEMixer" + suffix
                else:
                    mixerapp = "SaffireMixer"

            exec( "mixerwidget = "+mixerapp+"( w )" )

        else:
            mixerwidget = DummyMixer( w )
            mixerapp = modelName+" (Dummy)"

        #
        # The same for all mixers
        #
        l.addWidget( mixerwidget, 10 )
        mixerwidget.configrom = cfgrom
        mixerwidget.clockselect = clockselect
        mixerwidget.samplerateselect = samplerateselect
        mixerwidget.nickname = nickname
        mixerwidget.hw = hw
        if 'buildMixer' in dir(mixerwidget):
            mixerwidget.buildMixer()
        if 'initValues' in dir(mixerwidget):
            mixerwidget.initValues()
        mixerwidgets.append(mixerwidget)
        mw.addTab( w, mixerapp )

    #
    # Show the generic (development) mixer if it is available
    #
    if nbDevices > 0 and use_generic:
        mw.addTab( GenericMixer( devmgr.bus, server, mw ), "Generic Mixer" )

    #
    # Only really show the mainwindow and start the mixer when at least on mixer is shown
    #
    if mw.count() > 0:
        puw = PollUpdateWidgets(mixerwidgets)
        timer = QTimer(puw)
        QObject.connect( timer, SIGNAL('timeout()'), puw.updateWidgets );
        timer.start( POLL_SLEEP_TIME_MSEC );

        # Adjust size of mixer window to the requirements of the selected device mixer(s) 
        mw.adjustSize()

        mw.show()

        QObject.connect(app,SIGNAL("lastWindowClosed()"),app,SLOT("quit()"))

        app.exec_loop()
