/*
    Rosegarden
    A sequencer and musical notation editor.

    This program is Copyright 2000-2008
        Guillaume Laurent   <glaurent@telegraph-road.org>,
        Chris Cannam        <cannam@all-day-breakfast.com>,
        Richard Bown        <bownie@bownie.com>

    The moral right of the authors to claim authorship of this work
    has been asserted.

    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 2 of the
    License, or (at your option) any later version.  See the file
    COPYING included with this distribution for more information.
*/

// Specialisation of SoundDriver to support ALSA (http://www.alsa-project.org)
//
//
#ifndef _ALSADRIVER_H_
#define _ALSADRIVER_H_

#include <vector>
#include <set>
#include <map>

#ifdef HAVE_ALSA

#include <alsa/asoundlib.h> // ALSA

#include "SoundDriver.h"
#include "Instrument.h"
#include "Device.h"
#include "AlsaPort.h"
#include "Scavenger.h"
#include "RunnablePluginInstance.h"

#ifdef HAVE_LIBJACK
#include "JackDriver.h"
#endif

namespace Rosegarden
{

class AlsaDriver : public SoundDriver
{
public:
    AlsaDriver(MappedStudio *studio);
    virtual ~AlsaDriver();

    // shutdown everything that's currently open
    void shutdown();

    virtual bool initialise();
    virtual void initialisePlayback(const RealTime &position);
    virtual void stopPlayback();
    virtual void punchOut();
    virtual void resetPlayback(const RealTime &oldPosition, const RealTime &position);
    virtual void allNotesOff();
    virtual void processNotesOff(const RealTime &time, bool now, bool everything = false);

    virtual RealTime getSequencerTime();

    virtual MappedComposition *getMappedComposition();
    
    virtual bool record(RecordStatus recordStatus,
                        const std::vector<InstrumentId> *armedInstruments = 0,
                        const std::vector<TQString> *audioFileNames = 0);

    virtual void startClocks();
    virtual void startClocksApproved(); // called by JACK driver in sync mode
    virtual void stopClocks();
    virtual bool areClocksRunning() const { return m_queueRunning; }

    virtual void processEventsOut(const MappedComposition &mC);
    virtual void processEventsOut(const MappedComposition &mC,
                                  const RealTime &sliceStart,
                                  const RealTime &sliceEnd);

    // Return the sample rate
    //
    virtual unsigned int getSampleRate() const {
#ifdef HAVE_LIBJACK
        if (m_jackDriver) return m_jackDriver->getSampleRate();
        else return 0;
#else
        return 0;
#endif
    }

    // Define here to catch this being reset
    //
    virtual void setMIDIClockInterval(RealTime interval);

    // initialise subsystems
    //
    bool initialiseMidi();
    void initialiseAudio();

    // Some stuff to help us debug this
    //
    void getSystemInfo();
    void showQueueStatus(int queue);

    // Process pending
    //
    virtual void processPending();

    // We can return audio control signals to the gui using MappedEvents.
    // Meter levels or audio file completions can go in here.
    //
    void insertMappedEventForReturn(MappedEvent *mE);

    
    virtual RealTime getAudioPlayLatency() {
#ifdef HAVE_LIBJACK
        if (m_jackDriver) return m_jackDriver->getAudioPlayLatency();
#endif
        return RealTime::zeroTime;
    }

    virtual RealTime getAudioRecordLatency() {
#ifdef HAVE_LIBJACK
        if (m_jackDriver) return m_jackDriver->getAudioRecordLatency();
#endif
        return RealTime::zeroTime;
    }

    virtual RealTime getInstrumentPlayLatency(InstrumentId id) {
#ifdef HAVE_LIBJACK
        if (m_jackDriver) return m_jackDriver->getInstrumentPlayLatency(id);
#endif
        return RealTime::zeroTime;
    }

    virtual RealTime getMaximumPlayLatency() {
#ifdef HAVE_LIBJACK
        if (m_jackDriver) return m_jackDriver->getMaximumPlayLatency();
#endif
        return RealTime::zeroTime;
    }
        

    // Plugin instance management
    //
    virtual void setPluginInstance(InstrumentId id,
                                   TQString identifier,
                                   int position) {
#ifdef HAVE_LIBJACK
        if (m_jackDriver) m_jackDriver->setPluginInstance(id, identifier, position);
#endif
    }

    virtual void removePluginInstance(InstrumentId id, int position) {
#ifdef HAVE_LIBJACK
        if (m_jackDriver) m_jackDriver->removePluginInstance(id, position);
#endif
    }

    // Remove all plugin instances
    //
    virtual void removePluginInstances() {
#ifdef HAVE_LIBJACK
        if (m_jackDriver) m_jackDriver->removePluginInstances();
#endif
    }

    virtual void setPluginInstancePortValue(InstrumentId id,
                                            int position,
                                            unsigned long portNumber,
                                            float value) {
#ifdef HAVE_LIBJACK
        if (m_jackDriver) m_jackDriver->setPluginInstancePortValue(id, position, portNumber, value);
#endif
    }

    virtual float getPluginInstancePortValue(InstrumentId id,
                                             int position,
                                             unsigned long portNumber) {
#ifdef HAVE_LIBJACK
        if (m_jackDriver) return m_jackDriver->getPluginInstancePortValue(id, position, portNumber);
#endif
        return 0;
    }

    virtual void setPluginInstanceBypass(InstrumentId id,
                                         int position,
                                         bool value) {
#ifdef HAVE_LIBJACK
        if (m_jackDriver) m_jackDriver->setPluginInstanceBypass(id, position, value);
#endif
    }

    virtual TQStringList getPluginInstancePrograms(InstrumentId id,
                                                  int position) {
#ifdef HAVE_LIBJACK
        if (m_jackDriver) return m_jackDriver->getPluginInstancePrograms(id, position);
#endif
        return TQStringList();
    }

    virtual TQString getPluginInstanceProgram(InstrumentId id,
                                             int position) {
#ifdef HAVE_LIBJACK
        if (m_jackDriver) return m_jackDriver->getPluginInstanceProgram(id, position);
#endif
        return TQString();
    }

    virtual TQString getPluginInstanceProgram(InstrumentId id,
                                             int position,
                                             int bank,
                                             int program) {
#ifdef HAVE_LIBJACK
        if (m_jackDriver) return m_jackDriver->getPluginInstanceProgram(id, position, bank, program);
#endif
        return TQString();
    }

    virtual unsigned long getPluginInstanceProgram(InstrumentId id,
                                                   int position,
                                                   TQString name) {
#ifdef HAVE_LIBJACK
        if (m_jackDriver) return m_jackDriver->getPluginInstanceProgram(id, position, name);
#endif
        return 0;
    }
    
    virtual void setPluginInstanceProgram(InstrumentId id,
                                          int position,
                                          TQString program) {
#ifdef HAVE_LIBJACK
        if (m_jackDriver) m_jackDriver->setPluginInstanceProgram(id, position, program);
#endif
    }

    virtual TQString configurePlugin(InstrumentId id,
                                    int position,
                                    TQString key,
                                    TQString value) {
#ifdef HAVE_LIBJACK
        if (m_jackDriver) return m_jackDriver->configurePlugin(id, position, key, value);
#endif
        return TQString();
    }

    virtual void setAudioBussLevels(int bussId,
                                    float dB,
                                    float pan) {
#ifdef HAVE_LIBJACK
        if (m_jackDriver) m_jackDriver->setAudioBussLevels(bussId, dB, pan);
#endif
    }

    virtual void setAudioInstrumentLevels(InstrumentId instrument,
                                          float dB,
                                          float pan) {
#ifdef HAVE_LIBJACK
        if (m_jackDriver) m_jackDriver->setAudioInstrumentLevels(instrument, dB, pan);
#endif
    }

    virtual void claimUnwantedPlugin(void *plugin);
    virtual void scavengePlugins();

    virtual bool checkForNewClients();

    virtual void setLoop(const RealTime &loopStart, const RealTime &loopEnd);

    virtual void sleep(const RealTime &);

    // ----------------------- End of Virtuals ----------------------

    // Create and send an MMC command
    //
    void sendMMC(MidiByte deviceId,
                 MidiByte instruction,
                 bool isCommand,
                 const std::string &data);

    // Check whether the given event is an MMC command we need to act on
    // (and if so act on it)
    //
    bool testForMMCSysex(const snd_seq_event_t *event);

    // Create and enqueue a batch of MTC quarter-frame events
    //
    void insertMTCTQFrames(RealTime sliceStart, RealTime sliceEnd);

    // Create and enqueue an MTC full-frame system exclusive event
    //
    void insertMTCFullFrame(RealTime time);

    // Parse and accept an incoming MTC quarter-frame event
    //
    void handleMTCTQFrame(unsigned int data_byte, RealTime the_time);

    // Check whether the given event is an MTC sysex we need to act on
    // (and if so act on it)
    //
    bool testForMTCSysex(const snd_seq_event_t *event);

    // Adjust the ALSA clock skew for MTC lock
    //
    void tweakSkewForMTC(int factor);

    // Recalibrate internal MTC factors
    //
    void calibrateMTC();

    // Send a System message straight away
    //
    void sendSystemDirect(MidiByte command, int *arg);

    // Scheduled system message with arguments
    //
    void sendSystemQueued(MidiByte command,
                          const std::string &args,
                          const RealTime &time);

    // Set the record device
    //
    void setRecordDevice(DeviceId id, bool connectAction);
    void unsetRecordDevices();

    virtual bool canReconnect(Device::DeviceType type);

    virtual DeviceId addDevice(Device::DeviceType type,
                               MidiDevice::DeviceDirection direction);
    virtual void removeDevice(DeviceId id);
    virtual void renameDevice(DeviceId id, TQString name);

    // Get available connections per device
    // 
    virtual unsigned int getConnections(Device::DeviceType type,
                                        MidiDevice::DeviceDirection direction);
    virtual TQString getConnection(Device::DeviceType type,
                                  MidiDevice::DeviceDirection direction,
                                  unsigned int connectionNo);
    virtual void setConnection(DeviceId deviceId, TQString connection);
    virtual void setPlausibleConnection(DeviceId deviceId, TQString connection);

    virtual unsigned int getTimers();
    virtual TQString getTimer(unsigned int);
    virtual TQString getCurrentTimer();
    virtual void setCurrentTimer(TQString);
 
    virtual void getAudioInstrumentNumbers(InstrumentId &audioInstrumentBase,
                                           int &audioInstrumentCount) {
        audioInstrumentBase = AudioInstrumentBase;
#ifdef HAVE_LIBJACK
        audioInstrumentCount = AudioInstrumentCount;
#else
        audioInstrumentCount = 0;
#endif
    }
 
    virtual void getSoftSynthInstrumentNumbers(InstrumentId &ssInstrumentBase,
                                               int &ssInstrumentCount) {
        ssInstrumentBase = SoftSynthInstrumentBase;
#ifdef HAVE_DSSI
        ssInstrumentCount = SoftSynthInstrumentCount;
#else
        ssInstrumentCount = 0;
#endif
    }

    virtual TQString getStatusLog();

    // To be called regularly from JACK driver when idle
    void checkTimerSync(size_t frames);

    virtual void runTasks();

    // Report a failure back to the GUI
    //
    virtual void reportFailure(MappedEvent::FailureCode code);

protected:
    typedef std::vector<AlsaPortDescription *> AlsaPortList;

    ClientPortPair getFirstDestination(bool duplex);
    ClientPortPair getPairForMappedInstrument(InstrumentId id);
    int getOutputPortForMappedInstrument(InstrumentId id);
    std::map<unsigned int, std::map<unsigned int, MappedEvent*> >  m_noteOnMap;

    /**
     * Bring m_alsaPorts up-to-date; if newPorts is non-null, also
     * return the new ports (not previously in m_alsaPorts) through it
     */
    virtual void generatePortList(AlsaPortList *newPorts = 0);
    virtual void generateInstruments();

    virtual void generateTimerList();
    virtual std::string getAutoTimer(bool &wantTimerChecks);

    void addInstrumentsForDevice(MappedDevice *device);
    MappedDevice *createMidiDevice(AlsaPortDescription *,
                                   MidiDevice::DeviceDirection);

    virtual void processMidiOut(const MappedComposition &mC,
                                const RealTime &sliceStart,
                                const RealTime &sliceEnd);

    virtual void processSoftSynthEventOut(InstrumentId id,
                                          const snd_seq_event_t *event,
                                          bool now);

    virtual bool isRecording(AlsaPortDescription *port);

    virtual void processAudioQueue(bool /* now */) { }

    virtual void setConnectionToDevice(MappedDevice &device, TQString connection);
    virtual void setConnectionToDevice(MappedDevice &device, TQString connection,
                                       const ClientPortPair &pair);

private:
    RealTime getAlsaTime();

    // Locally convenient to control our devices
    //
    void sendDeviceController(DeviceId device,
                              MidiByte byte1,
                              MidiByte byte2);
                              
    int checkAlsaError(int rc, const char *message);

    AlsaPortList m_alsaPorts;

    // ALSA MIDI/Sequencer stuff
    //
    snd_seq_t                   *m_midiHandle;
    int                          m_client;

    int                          m_inputPort;
    
    typedef std::map<DeviceId, int> DeviceIntMap;
    DeviceIntMap                 m_outputPorts;

    int                          m_syncOutputPort;
    int                          m_controllerPort;

    int                          m_queue;
    int                          m_maxClients;
    int                          m_maxPorts;
    int                          m_maxQueues;

    // Because this can fail even if the driver's up (if
    // another service is using the port say)
    //
    bool                         m_midiInputPortConnected;

    bool                         m_midiSyncAutoConnect;

    RealTime                     m_alsaPlayStartTime;
    RealTime                     m_alsaRecordStartTime;

    RealTime                     m_loopStartTime;
    RealTime                     m_loopEndTime;

    // MIDI Time Code handling:

    unsigned int                 m_eat_mtc;
    // Received/emitted MTC data breakdown:
    RealTime                     m_mtcReceiveTime;
    RealTime                     m_mtcEncodedTime;
    int                          m_mtcFrames;
    int                          m_mtcSeconds;
    int                          m_mtcMinutes;
    int                          m_mtcHours;
    int                          m_mtcSMPTEType;

    // Calculated MTC factors:
    int                          m_mtcFirstTime;
    RealTime                     m_mtcLastEncoded;
    RealTime                     m_mtcLastReceive;
    long long int                m_mtcSigmaE;
    long long int                m_mtcSigmaC;
    unsigned int                 m_mtcSkew;

    bool                         m_looping;

    bool                         m_haveShutdown;

#ifdef HAVE_LIBJACK
    JackDriver *m_jackDriver;
#endif

    Scavenger<RunnablePluginInstance> m_pluginScavenger;

    //!!! -- hoist to SoundDriver w/setter?
    typedef std::set<InstrumentId> InstrumentSet;
    InstrumentSet m_recordingInstruments;

    typedef std::map<DeviceId, ClientPortPair> DevicePortMap;
    DevicePortMap m_devicePortMap;

    typedef std::map<ClientPortPair, DeviceId> PortDeviceMap;
    PortDeviceMap m_suspendedPortMap;

    std::string getPortName(ClientPortPair port);
    ClientPortPair getPortByName(std::string name);

    DeviceId getSpareDeviceId();

    struct AlsaTimerInfo {
        int clas;
        int sclas;
        int card;
        int device;
        int subdevice;
        std::string name;
        long resolution;
    };
    std::vector<AlsaTimerInfo> m_timers;
    std::string m_currentTimer;

    // This auxiliary queue is here as a hack, to avoid stuck notes if
    // resetting playback while a note-off is currently in the ALSA
    // queue.  When playback is reset by ffwd or rewind etc, we drop
    // all the queued events (which is generally what is desired,
    // except for note offs) and reset the queue timer (so the note
    // offs would have the wrong time stamps even if we hadn't dropped
    // them).  Thus, we need to re-send any recent note offs before
    // continuing.  This queue records which note offs have been
    // added to the ALSA queue recently.
    //
    NoteOffQueue m_recentNoteOffs;
    void pushRecentNoteOffs(); // move from recent to normal queue after reset
    void cropRecentNoteOffs(const RealTime &t); // remove old note offs
    void weedRecentNoteOffs(unsigned int pitch, MidiByte channel,
			    InstrumentId instrument); // on subsequent note on

    bool m_queueRunning;
    
    bool m_portCheckNeeded;

    enum { NeedNoJackStart, NeedJackReposition, NeedJackStart } m_needJackStart;
    
    bool m_doTimerChecks;
    bool m_firstTimerCheck;
    double m_timerRatio;
    bool m_timerRatioCalculated;

    std::string getAlsaModuleVersionString();
    std::string getKernelVersionString();
    void extractVersion(std::string vstr, int &major, int &minor, int &subminor, std::string &suffix);
    bool versionIsAtLeast(std::string vstr, int major, int minor, int subminor);
};

}

#endif // HAVE_ALSA

#endif // _ALSADRIVER_H_