#define NO_MIN_MAX 1
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <ShlObj.h>

#include <util/dstr.h>

typedef unsigned long UInt32;
typedef signed long SInt32;
typedef signed long long SInt64;
typedef double Float64;

typedef SInt32 OSStatus;
typedef unsigned char Boolean;

typedef UInt32 AudioFormatPropertyID;

enum { kVariableLengthArray = 1,
};

struct OpaqueAudioConverter;
typedef struct OpaqueAudioConverter *AudioConverterRef;
typedef UInt32 AudioConverterPropertyID;

struct AudioValueRange {
	Float64 mMinimum;
	Float64 mMaximum;
};
typedef struct AudioValueRange AudioValueRange;

struct AudioBuffer {
	UInt32 mNumberChannels;
	UInt32 mDataByteSize;
	void *mData;
};
typedef struct AudioBuffer AudioBuffer;

struct AudioBufferList {
	UInt32 mNumberBuffers;
	AudioBuffer mBuffers[kVariableLengthArray];
};
typedef struct AudioBufferList AudioBufferList;

struct AudioStreamBasicDescription {
	Float64 mSampleRate;
	UInt32 mFormatID;
	UInt32 mFormatFlags;
	UInt32 mBytesPerPacket;
	UInt32 mFramesPerPacket;
	UInt32 mBytesPerFrame;
	UInt32 mChannelsPerFrame;
	UInt32 mBitsPerChannel;
	UInt32 mReserved;
};
typedef struct AudioStreamBasicDescription AudioStreamBasicDescription;

struct AudioStreamPacketDescription {
	SInt64 mStartOffset;
	UInt32 mVariableFramesInPacket;
	UInt32 mDataByteSize;
};
typedef struct AudioStreamPacketDescription AudioStreamPacketDescription;

typedef OSStatus (*AudioConverterComplexInputDataProc)(
	AudioConverterRef inAudioConverter, UInt32 *ioNumberDataPackets,
	AudioBufferList *ioData,
	AudioStreamPacketDescription **outDataPacketDescription,
	void *inUserData);

enum { kAudioCodecPropertyNameCFString = 'lnam',
       kAudioCodecPropertyManufacturerCFString = 'lmak',
       kAudioCodecPropertyFormatCFString = 'lfor',
       //kAudioCodecPropertyHasVariablePacketByteSizes          = 'vpk?',
       kAudioCodecPropertySupportedInputFormats = 'ifm#',
       kAudioCodecPropertySupportedOutputFormats = 'ofm#',
       kAudioCodecPropertyAvailableInputSampleRates = 'aisr',
       kAudioCodecPropertyAvailableOutputSampleRates = 'aosr',
       kAudioCodecPropertyAvailableBitRateRange = 'abrt',
       kAudioCodecPropertyMinimumNumberInputPackets = 'mnip',
       kAudioCodecPropertyMinimumNumberOutputPackets = 'mnop',
       kAudioCodecPropertyAvailableNumberChannels = 'cmnc',
       kAudioCodecPropertyDoesSampleRateConversion = 'lmrc',
       kAudioCodecPropertyAvailableInputChannelLayoutTags = 'aicl',
       kAudioCodecPropertyAvailableOutputChannelLayoutTags = 'aocl',
       kAudioCodecPropertyInputFormatsForOutputFormat = 'if4o',
       kAudioCodecPropertyOutputFormatsForInputFormat = 'of4i',
       kAudioCodecPropertyFormatInfo = 'acfi',
};

enum { kAudioCodecPropertyInputBufferSize = 'tbuf',
       kAudioCodecPropertyPacketFrameSize = 'pakf',
       kAudioCodecPropertyMaximumPacketByteSize = 'pakb',
       kAudioCodecPropertyCurrentInputFormat = 'ifmt',
       kAudioCodecPropertyCurrentOutputFormat = 'ofmt',
       kAudioCodecPropertyMagicCookie = 'kuki',
       kAudioCodecPropertyUsedInputBufferSize = 'ubuf',
       kAudioCodecPropertyIsInitialized = 'init',
       kAudioCodecPropertyCurrentTargetBitRate = 'brat',
       kAudioCodecPropertyCurrentInputSampleRate = 'cisr',
       kAudioCodecPropertyCurrentOutputSampleRate = 'cosr',
       kAudioCodecPropertyQualitySetting = 'srcq',
       kAudioCodecPropertyApplicableBitRateRange = 'brta',
       kAudioCodecPropertyApplicableInputSampleRates = 'isra',
       kAudioCodecPropertyApplicableOutputSampleRates = 'osra',
       kAudioCodecPropertyPaddedZeros = 'pad0',
       kAudioCodecPropertyPrimeMethod = 'prmm',
       kAudioCodecPropertyPrimeInfo = 'prim',
       kAudioCodecPropertyCurrentInputChannelLayout = 'icl ',
       kAudioCodecPropertyCurrentOutputChannelLayout = 'ocl ',
       kAudioCodecPropertySettings = 'acs ',
       kAudioCodecPropertyFormatList = 'acfl',
       kAudioCodecPropertyBitRateControlMode = 'acbf',
       kAudioCodecPropertySoundQualityForVBR = 'vbrq',
       kAudioCodecPropertyMinimumDelayMode = 'mdel' };

enum { kAudioCodecBitRateControlMode_Constant = 0,
       kAudioCodecBitRateControlMode_LongTermAverage = 1,
       kAudioCodecBitRateControlMode_VariableConstrained = 2,
       kAudioCodecBitRateControlMode_Variable = 3,
};

enum { kAudioFormatLinearPCM = 'lpcm',
       kAudioFormatAC3 = 'ac-3',
       kAudioFormat60958AC3 = 'cac3',
       kAudioFormatAppleIMA4 = 'ima4',
       kAudioFormatMPEG4AAC = 'aac ',
       kAudioFormatMPEG4CELP = 'celp',
       kAudioFormatMPEG4HVXC = 'hvxc',
       kAudioFormatMPEG4TwinVQ = 'twvq',
       kAudioFormatMACE3 = 'MAC3',
       kAudioFormatMACE6 = 'MAC6',
       kAudioFormatULaw = 'ulaw',
       kAudioFormatALaw = 'alaw',
       kAudioFormatQDesign = 'QDMC',
       kAudioFormatQDesign2 = 'QDM2',
       kAudioFormatQUALCOMM = 'Qclp',
       kAudioFormatMPEGLayer1 = '.mp1',
       kAudioFormatMPEGLayer2 = '.mp2',
       kAudioFormatMPEGLayer3 = '.mp3',
       kAudioFormatTimeCode = 'time',
       kAudioFormatMIDIStream = 'midi',
       kAudioFormatParameterValueStream = 'apvs',
       kAudioFormatAppleLossless = 'alac',
       kAudioFormatMPEG4AAC_HE = 'aach',
       kAudioFormatMPEG4AAC_LD = 'aacl',
       kAudioFormatMPEG4AAC_ELD = 'aace',
       kAudioFormatMPEG4AAC_ELD_SBR = 'aacf',
       kAudioFormatMPEG4AAC_ELD_V2 = 'aacg',
       kAudioFormatMPEG4AAC_HE_V2 = 'aacp',
       kAudioFormatMPEG4AAC_Spatial = 'aacs',
       kAudioFormatAMR = 'samr',
       kAudioFormatAudible = 'AUDB',
       kAudioFormatiLBC = 'ilbc',
       kAudioFormatDVIIntelIMA = 0x6D730011,
       kAudioFormatMicrosoftGSM = 0x6D730031,
       kAudioFormatAES3 = 'aes3',
};

enum { kAudioFormatFlagIsFloat = (1L << 0),
       kAudioFormatFlagIsBigEndian = (1L << 1),
       kAudioFormatFlagIsSignedInteger = (1L << 2),
       kAudioFormatFlagIsPacked = (1L << 3),
       kAudioFormatFlagIsAlignedHigh = (1L << 4),
       kAudioFormatFlagIsNonInterleaved = (1L << 5),
       kAudioFormatFlagIsNonMixable = (1L << 6),
       kAudioFormatFlagsAreAllClear = (1L << 31),

       kLinearPCMFormatFlagIsFloat = kAudioFormatFlagIsFloat,
       kLinearPCMFormatFlagIsBigEndian = kAudioFormatFlagIsBigEndian,
       kLinearPCMFormatFlagIsSignedInteger = kAudioFormatFlagIsSignedInteger,
       kLinearPCMFormatFlagIsPacked = kAudioFormatFlagIsPacked,
       kLinearPCMFormatFlagIsAlignedHigh = kAudioFormatFlagIsAlignedHigh,
       kLinearPCMFormatFlagIsNonInterleaved = kAudioFormatFlagIsNonInterleaved,
       kLinearPCMFormatFlagIsNonMixable = kAudioFormatFlagIsNonMixable,
       kLinearPCMFormatFlagsAreAllClear = kAudioFormatFlagsAreAllClear,

       kAppleLosslessFormatFlag_16BitSourceData = 1,
       kAppleLosslessFormatFlag_20BitSourceData = 2,
       kAppleLosslessFormatFlag_24BitSourceData = 3,
       kAppleLosslessFormatFlag_32BitSourceData = 4,
};

enum { kAudioFormatFlagsNativeEndian = 0 };

enum {
	// AudioStreamBasicDescription structure properties
	kAudioFormatProperty_FormatInfo = 'fmti',
	kAudioFormatProperty_FormatName = 'fnam',
	kAudioFormatProperty_EncodeFormatIDs = 'acof',
	kAudioFormatProperty_DecodeFormatIDs = 'acif',
	kAudioFormatProperty_FormatList = 'flst',
	kAudioFormatProperty_ASBDFromESDS = 'essd',
	kAudioFormatProperty_ChannelLayoutFromESDS = 'escl',
	kAudioFormatProperty_OutputFormatList = 'ofls',
	kAudioFormatProperty_Encoders = 'aven',
	kAudioFormatProperty_Decoders = 'avde',
	kAudioFormatProperty_FormatIsVBR = 'fvbr',
	kAudioFormatProperty_FormatIsExternallyFramed = 'fexf',
	kAudioFormatProperty_AvailableEncodeBitRates = 'aebr',
	kAudioFormatProperty_AvailableEncodeSampleRates = 'aesr',
	kAudioFormatProperty_AvailableEncodeChannelLayoutTags = 'aecl',
	kAudioFormatProperty_AvailableEncodeNumberChannels = 'avnc',
	kAudioFormatProperty_ASBDFromMPEGPacket = 'admp',
	//
	// AudioChannelLayout structure properties
	kAudioFormatProperty_BitmapForLayoutTag = 'bmtg',
	kAudioFormatProperty_MatrixMixMap = 'mmap',
	kAudioFormatProperty_ChannelMap = 'chmp',
	kAudioFormatProperty_NumberOfChannelsForLayout = 'nchm',
	kAudioFormatProperty_ValidateChannelLayout = 'vacl',
	kAudioFormatProperty_ChannelLayoutForTag = 'cmpl',
	kAudioFormatProperty_TagForChannelLayout = 'cmpt',
	kAudioFormatProperty_ChannelLayoutName = 'lonm',
	kAudioFormatProperty_ChannelLayoutSimpleName = 'lsnm',
	kAudioFormatProperty_ChannelLayoutForBitmap = 'cmpb',
	kAudioFormatProperty_ChannelName = 'cnam',
	kAudioFormatProperty_ChannelShortName = 'csnm',
	kAudioFormatProperty_TagsForNumberOfChannels = 'tagc',
	kAudioFormatProperty_PanningMatrix = 'panm',
	kAudioFormatProperty_BalanceFade = 'balf',
	//
	// ID3 tag (MP3 metadata) properties
	kAudioFormatProperty_ID3TagSize = 'id3s',
	kAudioFormatProperty_ID3TagToDictionary = 'id3d',
};

enum { kAudioConverterPropertyMinimumInputBufferSize = 'mibs',
       kAudioConverterPropertyMinimumOutputBufferSize = 'mobs',
       kAudioConverterPropertyMaximumInputBufferSize = 'xibs',
       kAudioConverterPropertyMaximumInputPacketSize = 'xips',
       kAudioConverterPropertyMaximumOutputPacketSize = 'xops',
       kAudioConverterPropertyCalculateInputBufferSize = 'cibs',
       kAudioConverterPropertyCalculateOutputBufferSize = 'cobs',
       kAudioConverterPropertyInputCodecParameters = 'icdp',
       kAudioConverterPropertyOutputCodecParameters = 'ocdp',
       kAudioConverterSampleRateConverterAlgorithm = 'srci',
       kAudioConverterSampleRateConverterComplexity = 'srca',
       kAudioConverterSampleRateConverterQuality = 'srcq',
       kAudioConverterSampleRateConverterInitialPhase = 'srcp',
       kAudioConverterCodecQuality = 'cdqu',
       kAudioConverterPrimeMethod = 'prmm',
       kAudioConverterPrimeInfo = 'prim',
       kAudioConverterChannelMap = 'chmp',
       kAudioConverterDecompressionMagicCookie = 'dmgc',
       kAudioConverterCompressionMagicCookie = 'cmgc',
       kAudioConverterEncodeBitRate = 'brat',
       kAudioConverterEncodeAdjustableSampleRate = 'ajsr',
       kAudioConverterInputChannelLayout = 'icl ',
       kAudioConverterOutputChannelLayout = 'ocl ',
       kAudioConverterApplicableEncodeBitRates = 'aebr',
       kAudioConverterAvailableEncodeBitRates = 'vebr',
       kAudioConverterApplicableEncodeSampleRates = 'aesr',
       kAudioConverterAvailableEncodeSampleRates = 'vesr',
       kAudioConverterAvailableEncodeChannelLayoutTags = 'aecl',
       kAudioConverterCurrentOutputStreamDescription = 'acod',
       kAudioConverterCurrentInputStreamDescription = 'acid',
       kAudioConverterPropertySettings = 'acps',
       kAudioConverterPropertyBitDepthHint = 'acbd',
       kAudioConverterPropertyFormatList = 'flst',
};

enum { kAudioConverterQuality_Max = 0x7F,
       kAudioConverterQuality_High = 0x60,
       kAudioConverterQuality_Medium = 0x40,
       kAudioConverterQuality_Low = 0x20,
       kAudioConverterQuality_Min = 0,
};

enum { kAudio_UnimplementedError = -4,
       kAudio_FileNotFoundError = -43,
       kAudio_FilePermissionError = -54,
       kAudio_TooManyFilesOpenError = -42,
       kAudio_BadFilePathError = '!pth', // 0x21707468, 561017960
       kAudio_ParamError = -50,
       kAudio_MemFullError = -108,

       kAudioConverterErr_FormatNotSupported = 'fmt?',
       kAudioConverterErr_OperationNotSupported = 0x6F703F3F,
       // 'op??', integer used because of trigraph
       kAudioConverterErr_PropertyNotSupported = 'prop',
       kAudioConverterErr_InvalidInputSize = 'insz',
       kAudioConverterErr_InvalidOutputSize = 'otsz',
       // e.g. byte size is not a multiple of the frame size
       kAudioConverterErr_UnspecifiedError = 'what',
       kAudioConverterErr_BadPropertySizeError = '!siz',
       kAudioConverterErr_RequiresPacketDescriptionsError = '!pkd',
       kAudioConverterErr_InputSampleRateOutOfRange = '!isr',
       kAudioConverterErr_OutputSampleRateOutOfRange = '!osr',
};

typedef OSStatus (*AudioConverterNew_t)(
	const AudioStreamBasicDescription *inSourceFormat,
	const AudioStreamBasicDescription *inDestinationFormat,
	AudioConverterRef *outAudioConverter);

typedef OSStatus (*AudioConverterDispose_t)(AudioConverterRef inAudioConverter);

typedef OSStatus (*AudioConverterReset_t)(AudioConverterRef inAudioConverter);

typedef OSStatus (*AudioConverterGetProperty_t)(
	AudioConverterRef inAudioConverter,
	AudioConverterPropertyID inPropertyID, UInt32 *ioPropertyDataSize,
	void *outPropertyData);

typedef OSStatus (*AudioConverterGetPropertyInfo_t)(
	AudioConverterRef inAudioConverter,
	AudioConverterPropertyID inPropertyID, UInt32 *outSize,
	Boolean *outWritable);

typedef OSStatus (*AudioConverterSetProperty_t)(
	AudioConverterRef inAudioConverter,
	AudioConverterPropertyID inPropertyID, UInt32 inPropertyDataSize,
	const void *inPropertyData);

typedef OSStatus (*AudioConverterFillComplexBuffer_t)(
	AudioConverterRef inAudioConverter,
	AudioConverterComplexInputDataProc inInputDataProc,
	void *inInputDataProcUserData, UInt32 *ioOutputDataPacketSize,
	AudioBufferList *outOutputData,
	AudioStreamPacketDescription *outPacketDescription);

typedef OSStatus (*AudioFormatGetProperty_t)(AudioFormatPropertyID inPropertyID,
					     UInt32 inSpecifierSize,
					     const void *inSpecifier,
					     UInt32 *ioPropertyDataSize,
					     void *outPropertyData);

typedef OSStatus (*AudioFormatGetPropertyInfo_t)(
	AudioFormatPropertyID inPropertyID, UInt32 inSpecifierSize,
	const void *inSpecifier, UInt32 *outPropertyDataSize);

static AudioConverterNew_t AudioConverterNew = NULL;
static AudioConverterDispose_t AudioConverterDispose = NULL;
static AudioConverterReset_t AudioConverterReset = NULL;
static AudioConverterGetProperty_t AudioConverterGetProperty = NULL;
static AudioConverterGetPropertyInfo_t AudioConverterGetPropertyInfo = NULL;
static AudioConverterSetProperty_t AudioConverterSetProperty = NULL;
static AudioConverterFillComplexBuffer_t AudioConverterFillComplexBuffer = NULL;
static AudioFormatGetProperty_t AudioFormatGetProperty = NULL;
static AudioFormatGetPropertyInfo_t AudioFormatGetPropertyInfo = NULL;

static HMODULE audio_toolbox = NULL;

static void release_lib(void)
{
#define RELEASE_LIB(x)          \
	if (x) {                \
		FreeLibrary(x); \
		x = NULL;       \
	}

	RELEASE_LIB(audio_toolbox);
#undef RELEASE_LIB
}

static bool load_lib(void)
{
	PWSTR common_path;
	if (SHGetKnownFolderPath(FOLDERID_ProgramFilesCommon, 0, NULL,
				 &common_path) != S_OK) {
		CA_LOG(LOG_WARNING, "Could not retrieve common files path");
		return false;
	}

	struct dstr path = {0};
	dstr_printf(&path, "%S\\Apple\\Apple Application Support", common_path);
	CoTaskMemFree(common_path);

	wchar_t *w_path = dstr_to_wcs(&path);
	dstr_free(&path);

	SetDllDirectory(w_path);
	bfree(w_path);

#define LOAD_LIB(x, n)            \
	x = LoadLibrary(TEXT(n)); \
	if (!x)                   \
		CA_LOG(LOG_DEBUG, "Failed loading library '" n "'");

	LOAD_LIB(audio_toolbox, "CoreAudioToolbox.dll");
#undef LOAD_LIB

	SetDllDirectory(NULL);

	if (audio_toolbox)
		return true;

	release_lib();
	return false;
}

static void unload_core_audio(void)
{
	AudioConverterNew = NULL;
	AudioConverterDispose = NULL;
	AudioConverterReset = NULL;
	AudioConverterGetProperty = NULL;
	AudioConverterGetPropertyInfo = NULL;
	AudioConverterSetProperty = NULL;
	AudioConverterFillComplexBuffer = NULL;
	AudioFormatGetProperty = NULL;
	AudioFormatGetPropertyInfo = NULL;

	release_lib();
}

#ifdef _MSC_VER
#pragma warning(push)
#pragma warning(disable : 4706)
#endif
static bool load_core_audio(void)
{
	if (!load_lib())
		return false;

#define LOAD_SYM_FROM_LIB(sym, lib, dll)                                   \
	if (!(sym = (sym##_t)GetProcAddress(lib, #sym))) {                 \
		DWORD err = GetLastError();                                \
		CA_LOG(LOG_ERROR,                                          \
		       "Couldn't load " #sym " from " dll ": %lu (0x%lx)", \
		       err, err);                                          \
		goto unload_everything;                                    \
	}

#define LOAD_SYM(sym) \
	LOAD_SYM_FROM_LIB(sym, audio_toolbox, "CoreAudioToolbox.dll")
	LOAD_SYM(AudioConverterNew);
	LOAD_SYM(AudioConverterDispose);
	LOAD_SYM(AudioConverterReset);
	LOAD_SYM(AudioConverterGetProperty);
	LOAD_SYM(AudioConverterGetPropertyInfo);
	LOAD_SYM(AudioConverterSetProperty);
	LOAD_SYM(AudioConverterFillComplexBuffer);
	LOAD_SYM(AudioFormatGetProperty);
	LOAD_SYM(AudioFormatGetPropertyInfo);
#undef LOAD_SYM

	return true;

unload_everything:
	unload_core_audio();

	return false;
}
#ifdef _MSC_VER
#pragma warning(pop)
#endif
