/******************************************************************************\
 * Copyright (c) 2004-2020
 *
 * Author(s):
 *  Volker Fischer
 *
 ******************************************************************************
 *
 * 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.
 *
 * 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, write to the Free Software Foundation, Inc.,
 * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
 *
\******************************************************************************/

#pragma once

#include <QMutex>
#include <QMessageBox>
#include "../src/util.h"
#include "../src/global.h"
#include "../src/soundbase.h"

// copy the ASIO SDK in the llcon/windows directory: "llcon/windows/ASIOSDK2" to
// get it work
#include "asiosys.h"
#include "asio.h"
#include "asiodrivers.h"


/* Definitions ****************************************************************/
// stereo for input and output
#define NUM_IN_OUT_CHANNELS         2


/* Classes ********************************************************************/
class CSound : public CSoundBase
{
public:
    CSound ( void       (*fpNewCallback) ( CVector<int16_t>& psData, void* arg ),
             void*      arg,
             const int  iCtrlMIDIChannel,
             const bool bNoAutoJackConnect );
    virtual ~CSound() { UnloadCurrentDriver(); }

    virtual int  Init ( const int iNewPrefMonoBufferSize );
    virtual void Start();
    virtual void Stop();

    virtual void OpenDriverSetup() { ASIOControlPanel(); }

    // channel selection
    virtual int     GetNumInputChannels() { return static_cast<int> ( lNumInChanPlusAddChan ); }
    virtual QString GetInputChannelName ( const int iDiD ) { return channelInputName[iDiD]; }
    virtual void    SetLeftInputChannel  ( const int iNewChan );
    virtual void    SetRightInputChannel ( const int iNewChan );
    virtual int     GetLeftInputChannel()  { return vSelectedInputChannels[0]; }
    virtual int     GetRightInputChannel() { return vSelectedInputChannels[1]; }

    virtual int     GetNumOutputChannels() { return static_cast<int> ( lNumOutChan ); }
    virtual QString GetOutputChannelName ( const int iDiD ) { return channelInfosOutput[iDiD].name; }
    virtual void    SetLeftOutputChannel  ( const int iNewChan );
    virtual void    SetRightOutputChannel ( const int iNewChan );
    virtual int     GetLeftOutputChannel()  { return vSelectedOutputChannels[0]; }
    virtual int     GetRightOutputChannel() { return vSelectedOutputChannels[1]; }

    virtual double  GetInOutLatencyMs() { return dInOutLatencyMs; }

protected:
    virtual QString  LoadAndInitializeDriver ( int  iIdx,
                                               bool bOpenDriverSetup );
    virtual void     UnloadCurrentDriver();
    int              GetActualBufferSize ( const int iDesiredBufferSizeMono );
    QString          CheckDeviceCapabilities();
    bool             CheckSampleTypeSupported ( const ASIOSampleType SamType );
    bool             CheckSampleTypeSupportedForCHMixing ( const ASIOSampleType SamType );
    void             ResetChannelMapping();

    static void GetSelCHAndAddCH ( const int iSelCH,    const int iNumInChan,
                                   int&      iSelCHOut, int&      iSelAddCHOut )
    {
        // we have a mixed channel setup
        // definitions:
        // - mixed channel setup only for 4 physical inputs:
        //   SelCH == 4: Ch 0 + Ch 2
        //   SelCh == 5: Ch 0 + Ch 3
        //   SelCh == 6: Ch 1 + Ch 2
        //   SelCh == 7: Ch 1 + Ch 3
        if ( iSelCH >= iNumInChan )
        {
            iSelAddCHOut = ( ( iSelCH - iNumInChan ) % 2 ) + 2;
            iSelCHOut    = ( iSelCH - iNumInChan ) / 2;
        }
        else
        {
            iSelAddCHOut = -1;
            iSelCHOut    = iSelCH;
        }
    }

    int              iASIOBufferSizeMono;
    int              iASIOBufferSizeStereo;

    long             lNumInChan;
    long             lNumInChanPlusAddChan; // includes additional "added" channels
    long             lNumOutChan;
    double           dInOutLatencyMs;
    CVector<int>     vSelectedInputChannels;
    CVector<int>     vSelectedOutputChannels;

    CVector<int16_t> vecsMultChanAudioSndCrd;

    QMutex           ASIOMutex;

    // utility functions
    static int16_t   Flip16Bits ( const int16_t iIn );
    static int32_t   Flip32Bits ( const int32_t iIn );
    static int64_t   Flip64Bits ( const int64_t iIn );

    // audio hardware buffer info
    struct sHWBufferInfo
    {
        long lMinSize;
        long lMaxSize;
        long lPreferredSize;
        long lGranularity;
    } HWBufferInfo;

    // ASIO stuff
    ASIODriverInfo   driverInfo;
    ASIOBufferInfo   bufferInfos[2 * MAX_NUM_IN_OUT_CHANNELS]; // for input and output buffers -> "2 *"
    ASIOChannelInfo  channelInfosInput[MAX_NUM_IN_OUT_CHANNELS];
    QString          channelInputName[MAX_NUM_IN_OUT_CHANNELS];
    ASIOChannelInfo  channelInfosOutput[MAX_NUM_IN_OUT_CHANNELS];
    bool             bASIOPostOutput;
    ASIOCallbacks    asioCallbacks;

    // callbacks
    static void      bufferSwitch ( long index, ASIOBool processNow );
    static ASIOTime* bufferSwitchTimeInfo ( ASIOTime* timeInfo, long index, ASIOBool processNow );
    static void      sampleRateChanged ( ASIOSampleRate ) {}
    static long      asioMessages ( long selector, long value, void* message, double* opt );

    char* cDriverNames[MAX_NUMBER_SOUND_CARDS];
};