#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 UInt32 AudioChannelLabel;
typedef UInt32 AudioChannelLayoutTag;

struct AudioChannelDescription {
	AudioChannelLabel mChannelLabel;
	UInt32 mChannelFlags;
	float mCoordinates[3];
};
typedef struct AudioChannelDescription AudioChannelDescription;

struct AudioChannelLayout {
	AudioChannelLayoutTag mChannelLayoutTag;
	UInt32 mChannelBitmap;
	UInt32 mNumberChannelDescriptions;
	AudioChannelDescription mChannelDescriptions[kVariableLengthArray];
};
typedef struct AudioChannelLayout AudioChannelLayout;

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)
{
	if (audio_toolbox) {
		FreeLibrary(audio_toolbox);
		audio_toolbox = NULL;
	}
}

static bool load_from_shell_path(REFKNOWNFOLDERID rfid, const wchar_t *subpath)
{
	wchar_t *sh_path;
	if (SHGetKnownFolderPath(rfid, 0, NULL, &sh_path) != S_OK) {
		CA_LOG(LOG_WARNING, "Could not retrieve shell path");
		return false;
	}

	wchar_t path[MAX_PATH];
	_snwprintf(path, MAX_PATH, L"%s\\%s", sh_path, subpath);
	CoTaskMemFree(sh_path);

	SetDllDirectory(path);
	audio_toolbox = LoadLibraryW(L"CoreAudioToolbox.dll");
	SetDllDirectory(nullptr);

	return !!audio_toolbox;
}

static bool load_lib(void)
{
	/* -------------------------------------------- */
	/* attempt to load from path                    */

	audio_toolbox = LoadLibraryW(L"CoreAudioToolbox.dll");
	if (!!audio_toolbox)
		return true;

	/* -------------------------------------------- */
	/* attempt to load from known install locations */

	struct path_list_t {
		REFKNOWNFOLDERID rfid;
		const wchar_t *subpath;
	};

	path_list_t path_list[] = {
		{FOLDERID_ProgramFilesCommon,
		 L"Apple\\Apple Application Support"},
		{FOLDERID_ProgramFiles, L"iTunes"},
	};

	for (auto &val : path_list) {
		if (load_from_shell_path(val.rfid, val.subpath)) {
			return true;
		}
	}

	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
