#ifndef ASIO_H
#define ASIO_H

#include "typedefs.h"

#ifdef SHR3D_AUDIO_ASIO

#include <Unknwn.h>

#define DRVERR			-5000
#define DRVERR_INVALID_PARAM		DRVERR-1
#define DRVERR_DEVICE_ALREADY_OPEN	DRVERR-2
#define DRVERR_DEVICE_NOT_FOUND		DRVERR-3

#define MAXPATHLEN			512
#define MAXDRVNAMELEN		128

namespace ASIO
{
  using Samples = i64;
  using TimeStamp = TimeNS;
  using SampleRate = f64;

  // Boolean values are expressed as i32
  enum Bool : i32 {
    False = 0,
    True = 1
  };

  // Sample Types are expressed as i32
  enum struct SampleType : i32 {
    Int16MSB = 0,
    Int24MSB = 1,		// used for 20 bits as well
    Int32MSB = 2,
    Float32MSB = 3,		// IEEE 754 32 bit float
    Float64MSB = 4,		// IEEE 754 64 bit double float

    // these are used for 32 bit data buffer, with different alignment of the data inside
    // 32 bit PCI bus systems can be more easily used with these
    Int32MSB16 = 8,		// 32 bit data with 16 bit alignment
    Int32MSB18 = 9,		// 32 bit data with 18 bit alignment
    Int32MSB20 = 10,		// 32 bit data with 20 bit alignment
    Int32MSB24 = 11,		// 32 bit data with 24 bit alignment

    Int16LSB = 16,
    Int24LSB = 17,		// used for 20 bits as well
    Int32LSB = 18,
    Float32LSB = 19,		// IEEE 754 32 bit float, as found on Intel x86 architecture
    Float64LSB = 20, 		// IEEE 754 64 bit double float, as found on Intel x86 architecture

    // these are used for 32 bit data buffer, with different alignment of the data inside
    // 32 bit PCI bus systems can more easily used with these
    Int32LSB16 = 24,		// 32 bit data with 18 bit alignment
    Int32LSB18 = 25,		// 32 bit data with 18 bit alignment
    Int32LSB20 = 26,		// 32 bit data with 20 bit alignment
    Int32LSB24 = 27,		// 32 bit data with 24 bit alignment

    //	ASIO DSD format.
    DSDInt8LSB1 = 32,		// DSD 1 bit data, 8 samples per byte. First sample in Least significant bit.
    DSDInt8MSB1 = 33,		// DSD 1 bit data, 8 samples per byte. First sample in Most significant bit.
    DSDInt8NER8 = 40,		// DSD 8 bit data, 1 sample per byte. No Endianness required.

    LastEntry
  };

  //- - - - - - - - - - - - - - - - - - - - - - - - -
  // Error codes
  //- - - - - - - - - - - - - - - - - - - - - - - - -

  enum struct Error : i32 {
    OK = 0,             // This value will be returned whenever the call succeeded
    SUCCESS = 0x3f4847a0,	// unique success return value for ASIOFuture calls
    NotPresent = -1000, // hardware input or output is not present or available
    HWMalfunction,      // hardware is malfunctioning (can be returned by any ASIO function)
    InvalidParameter,   // input parameter invalid
    InvalidMode,        // hardware is in a bad mode or used in a bad mode
    SPNotAdvancing,     // hardware is not running when sample position is inquired
    NoClock,            // sample clock or rate cannot be determined or is not present
    NoMemory            // not enough memory for completing the request
  };

  enum TimeCodeFlags
  {
    kTcValid = 1,
    kTcRunning = 1 << 1,
    kTcReverse = 1 << 2,
    kTcOnspeed = 1 << 3,
    kTcStill = 1 << 4,

    kTcSpeedValid = 1 << 8
  };

  enum TimeInfoFlags
  {
    kSystemTimeValid = 1,
    kSamplePositionValid = 1 << 1,
    kSampleRateValid = 1 << 2,
    kSpeedValid = 1 << 3,

    kSampleRateChanged = 1 << 4,
    kClockSourceChanged = 1 << 5
  };

  // asioMessage selectors
  enum
  {
    kAsioSelectorSupported = 1,
    kAsioEngineVersion,
    kAsioResetRequest,
    kAsioBufferSizeChange,
    kAsioResyncRequest,
    kAsioLatenciesChanged,
    kAsioSupportsTimeInfo,
    kAsioSupportsTimeCode,
    kAsioMMCCommand,
    kAsioSupportsInputMonitor,
    kAsioSupportsInputGain,
    kAsioSupportsInputMeter,
    kAsioSupportsOutputGain,
    kAsioSupportsOutputMeter,
    kAsioOverload,

    kAsioNumMessageSelectors
  };

  enum
  {
    kAsioEnableTimeCodeRead = 1,
    kAsioDisableTimeCodeRead,
    kAsioSetInputMonitor,
    kAsioTransport,
    kAsioSetInputGain,
    kAsioGetInputMeter,
    kAsioSetOutputGain,
    kAsioGetOutputMeter,
    kAsioCanInputMonitor,
    kAsioCanTimeInfo,
    kAsioCanTimeCode,
    kAsioCanTransport,
    kAsioCanInputGain,
    kAsioCanInputMeter,
    kAsioCanOutputGain,
    kAsioCanOutputMeter,
    kAsioOptionalOne,

    kAsioSetIoFormat = 0x23111961,
    kAsioGetIoFormat = 0x23111983,
    kAsioCanDoIoFormat = 0x23112004,

    kAsioCanReportOverload = 0x24042012,

    kAsioGetInternalBufferSamples = 0x25042012
  };

  enum
  {
    kTransStart = 1,
    kTransStop,
    kTransLocate,
    kTransPunchIn,
    kTransPunchOut,
    kTransArmOn,
    kTransArmOff,
    kTransMonitorOn,
    kTransMonitorOff,
    kTransArm,
    kTransMonitor
  };

#pragma pack(push,4)
  enum struct IoFormatType : i32
  {
    kASIOFormatInvalid = -1,
    kASIOPCMFormat = 0,
    kASIODSDFormat = 1,
  };

  struct TimeInfo
  {
    f64 speed;
    TimeStamp systemTime;
    Samples samplePosition;
    SampleRate sampleRate;
    u32 flags;
    char reserved[12];
  };

  struct TimeCode
  {
    f64 speed; // speed relation (fraction of nominal speed)
    // optional; set to 0. or 1. if not supported
    Samples timeCodeSamples;        // time in samples
    u32 flags;                  // some information flags (see below)
    char future[64];
  };

  struct Time
  {
    i32 reserved[4];
    TimeInfo timeInfo;
    TimeCode timeCode;
  };

  struct Callbacks
  {
    void (*bufferSwitch) (i32 doubleBufferIndex, ASIO::Bool directProcess);
    void (*sampleRateDidChange) (ASIO::SampleRate sRate);
    i32(*asioMessage) (i32 selector, i32 value, void* message, f64* opt);
    ASIO::Time* (*bufferSwitchTimeInfo) (ASIO::Time* params, i32 doubleBufferIndex, ASIO::Bool directProcess);
  };

  struct ClockSource
  {
    i32 index;
    i32 associatedChannel;
    i32 associatedGroup;
    Bool isCurrentSource;
    char name[32];
  };

  struct ChannelInfo
  {
    i32 channel;
    Bool isInput;
    Bool isActive;
    i32 channelGroup;
    SampleType type;
    char name[32];
  };

  struct BufferInfo
  {
    Bool isInput;
    i32 channelNum;
    void* buffers[2];
  };

  struct InputMonitor
  {
    i32 input;
    i32 output;
    i32 gain;
    Bool state;
    i32 pan;
  };

  struct ChannelControls
  {
    i32 channel;
    Bool isInput;
    i32 gain;
    i32 meter;
    char future[32];
  };

  struct TransportParameters
  {
    i32 command;
    Samples samplePosition;
    i32 track;
    i32 trackSwitches[16];
    char future[64];
  };

  struct IoFormat_s
  {
    IoFormatType	FormatType;
    char				future[512 - sizeof(IoFormatType)];
  };

  struct ASIOInternalBufferInfo
  {
    i32 inputSamples;
    i32 outputSamples;
  };
#pragma pack(pop)

  struct IASIO : IUnknown
  {
    virtual ASIO::Bool init(void* sysHandle) = 0;
    virtual void getDriverName(char* name) = 0;
    virtual i32 getDriverVersion() = 0;
    virtual void getErrorMessage(char* string) = 0;
    virtual ASIO::Error start() = 0;
    virtual ASIO::Error stop() = 0;
    virtual ASIO::Error getChannels(i32* numInputChannels, i32* numOutputChannels) = 0;
    virtual ASIO::Error getLatencies(i32* inputLatency, i32* outputLatency) = 0;
    virtual ASIO::Error getBufferSize(i32* minSize, i32* maxSize, i32* preferredSize, i32* granularity) = 0;
    virtual ASIO::Error canSampleRate(ASIO::SampleRate sampleRate) = 0;
    virtual ASIO::Error getSampleRate(ASIO::SampleRate* sampleRate) = 0;
    virtual ASIO::Error setSampleRate(ASIO::SampleRate sampleRate) = 0;
    virtual ASIO::Error getClockSources(ASIO::ClockSource* clocks, i32* numSources) = 0;
    virtual ASIO::Error setClockSource(i32 reference) = 0;
    virtual ASIO::Error getSamplePosition(ASIO::Samples* sPos, ASIO::TimeStamp* tStamp) = 0;
    virtual ASIO::Error getChannelInfo(ASIO::ChannelInfo* info) = 0;
    virtual ASIO::Error createBuffers(ASIO::BufferInfo* bufferInfos, i32 numChannels, i32 bufferSize, ASIO::Callbacks* callbacks) = 0;
    virtual ASIO::Error disposeBuffers() = 0;
    virtual ASIO::Error controlPanel() = 0;
    virtual ASIO::Error future(i32 selector, void* opt) = 0;
    virtual ASIO::Error outputReady() = 0;
  };

  struct drvstruct
  {
    i32 drvID;
    CLSID clsid;
    char drvname[MAXDRVNAMELEN];
    LPVOID asiodrv;
    struct drvstruct* next;
  };

  struct Manager
  {
    Manager();

    i32 OpenDriver(i32 drvID, void** asiodrv);
    i32 CloseDriver(i32 drvID);

    i32 GetDriverName(i32 drvID, char* drvname, i32 drvnamesize);

    // or use directly access
    drvstruct* lpdrvlist = nullptr;
    i32 numDrivers = 0;

    ASIO::IASIO* loadDriver(const char* name);
    i32 curIndex = -1;
  };
}

#endif // SHR3D_AUDIO_ASIO

#endif // ASIO_H
