//
//  WavPackFile.m
//  Cog
//
//  Created by Vincent Spader on 6/6/05.
//  Copyright 2005 Vincent Spader All rights reserved.
//

#import "WavPackDecoder.h"

#import "Logging.h"

@implementation WavPackReader
- (id)initWithSource:(id<CogSource>)s {
	self = [super init];
	if(self) {
		source = s;
	}
	return self;
}

- (void)setSource:(id<CogSource>)s {
	source = s;
}

- (id<CogSource>)source {
	return source;
}
@end

@implementation WavPackDecoder

int32_t ReadBytesProc(void *ds, void *data, int32_t bcount) {
	WavPackReader *wv = (__bridge WavPackReader *)ds;

	return (int32_t)[[wv source] read:data amount:bcount];
}

uint32_t GetPosProc(void *ds) {
	WavPackReader *wv = (__bridge WavPackReader *)ds;

	return (uint32_t)[[wv source] tell];
}

int SetPosAbsProc(void *ds, uint32_t pos) {
	WavPackReader *wv = (__bridge WavPackReader *)ds;

	return ([[wv source] seek:pos whence:SEEK_SET] ? 0 : -1);
}

int SetPosRelProc(void *ds, int32_t delta, int mode) {
	WavPackReader *wv = (__bridge WavPackReader *)ds;

	return ([[wv source] seek:delta whence:mode] ? 0 : -1);
}

int PushBackByteProc(void *ds, int c) {
	WavPackReader *wv = (__bridge WavPackReader *)ds;

	if([[wv source] seekable]) {
		[[wv source] seek:-1 whence:SEEK_CUR];

		return c;
	} else {
		return -1;
	}
}

uint32_t GetLengthProc(void *ds) {
	WavPackReader *wv = (__bridge WavPackReader *)ds;

	if([[wv source] seekable]) {
		long currentPos = [[wv source] tell];

		[[wv source] seek:0 whence:SEEK_END];
		long size = [[wv source] tell];

		[[wv source] seek:currentPos whence:SEEK_SET];

		return (uint32_t)size;
	} else {
		return 0;
	}
}

int CanSeekProc(void *ds) {
	WavPackReader *wv = (__bridge WavPackReader *)ds;

	return [[wv source] seekable];
}

int32_t WriteBytesProc(void *ds, void *data, int32_t bcount) {
	return -1;
}

- (id)init {
	self = [super init];
	if(self) {
		wpc = NULL;
		inputBuffer = NULL;
		inputBufferSize = 0;
	}
	return self;
}

- (BOOL)open:(id<CogSource>)s {
	int open_flags = 0;
	char error[80];

	wv = [[WavPackReader alloc] initWithSource:s];

	if([s seekable]) {
		id audioSourceClass = NSClassFromString(@"AudioSource");
		NSURL *wvcurl = [[s url] URLByDeletingPathExtension];
		wvcurl = [wvcurl URLByAppendingPathExtension:@"wvc"];
		id<CogSource> wvcsrc = [audioSourceClass audioSourceForURL:wvcurl];
		if([wvcsrc open:wvcurl]) {
			wvc = [[WavPackReader alloc] initWithSource:wvcsrc];
		} else {
			wvc = nil;
		}
	} else {
		wvc = nil;
	}

	reader.read_bytes = ReadBytesProc;
	reader.get_pos = GetPosProc;
	reader.set_pos_abs = SetPosAbsProc;
	reader.set_pos_rel = SetPosRelProc;
	reader.push_back_byte = PushBackByteProc;
	reader.get_length = GetLengthProc;
	reader.can_seek = CanSeekProc;
	reader.write_bytes = WriteBytesProc;

	open_flags |= OPEN_DSD_NATIVE | OPEN_ALT_TYPES;

	if(![s seekable])
		open_flags |= OPEN_STREAMING;

	wpc = WavpackOpenFileInputEx(&reader, (__bridge void *)(wv), (__bridge void *)(wvc), error, open_flags, 0);
	if(!wpc) {
		DLog(@"Unable to open file..");
		return NO;
	}

	channels = WavpackGetNumChannels(wpc);
	channelConfig = WavpackGetChannelMask(wpc);
	bitsPerSample = WavpackGetBitsPerSample(wpc);

	frequency = WavpackGetSampleRate(wpc);

	totalFrames = WavpackGetNumSamples(wpc);

	isDSD = NO;

	float nativeFrequency = WavpackGetNativeSampleRate(wpc);

	if(nativeFrequency != frequency && bitsPerSample == 8) {
		isDSD = YES;
		frequency = nativeFrequency;
		bitsPerSample = 1;
		totalFrames *= 8;
	}

	bitrate = (int)(WavpackGetAverageBitrate(wpc, TRUE) / 1000.0);

	floatingPoint = MODE_FLOAT & WavpackGetMode(wpc) && 127 == WavpackGetFloatNormExp(wpc);

	isLossy = !(WavpackGetMode(wpc) & MODE_LOSSLESS);

	[self willChangeValueForKey:@"properties"];
	[self didChangeValueForKey:@"properties"];

	return YES;
}
/*
- (int)fillBuffer:(void *)buf ofSize:(UInt32)size
{
    int numsamples;
    int n;
    void *sampleBuf = malloc(size*2);

    numsamples = size/(bitsPerSample/8)/channels;
    n = WavpackUnpackSamples(wpc, sampleBuf, numsamples);

    int i;
    for (i = 0; i < n*channels; i++)
    {
        ((UInt16 *)buf)[i] = ((UInt32 *)sampleBuf)[i];
    }

    n *= (bitsPerSample/8)*channels;

    free(sampleBuf);

    return n;
}
*/
- (int)readAudio:(void *)buf frames:(UInt32)frames {
	uint32_t sample;
	int32_t audioSample;
	uint32_t samplesRead;
	int8_t *alias8;
	int16_t *alias16;
	int32_t *alias32;

	size_t newSize = frames * sizeof(int32_t) * channels;
	if(!inputBuffer || newSize > inputBufferSize) {
		inputBuffer = realloc(inputBuffer, inputBufferSize = newSize);
	}

	samplesRead = WavpackUnpackSamples(wpc, inputBuffer, frames);

	switch(bitsPerSample) {
		case 1:
		case 8:
			// No need for byte swapping
			alias8 = buf;
			for(sample = 0; sample < samplesRead * channels; ++sample) {
				*alias8++ = (int8_t)inputBuffer[sample];
			}
			break;
		case 16:
			// Convert to little endian byte order
			alias16 = buf;
			for(sample = 0; sample < samplesRead * channels; ++sample) {
				*alias16++ = OSSwapHostToLittleInt16((int16_t)inputBuffer[sample]);
			}
			break;
		case 24:
			// Convert to little endian byte order
			alias8 = buf;
			for(sample = 0; sample < samplesRead * channels; ++sample) {
				audioSample = inputBuffer[sample];
				*alias8++ = (int8_t)audioSample;
				*alias8++ = (int8_t)(audioSample >> 8);
				*alias8++ = (int8_t)(audioSample >> 16);
			}
			break;
		case 32:
			// Convert to little endian byte order
			alias32 = buf;
			for(sample = 0; sample < samplesRead * channels; ++sample) {
				*alias32++ = OSSwapHostToLittleInt32(inputBuffer[sample]);
			}
			break;
		default:
			ALog(@"Unsupported sample size: %d", bitsPerSample);
	}

	return samplesRead;
}

- (long)seek:(long)frame {
	uint32_t trueFrame = (uint32_t)frame;
	if(isDSD) {
		trueFrame /= 8;
		frame = trueFrame * 8;
	}
	WavpackSeekSample(wpc, trueFrame);

	return frame;
}

- (void)close {
	if(wpc) {
		WavpackCloseFile(wpc);
		wpc = NULL;
	}
	if(inputBuffer) {
		free(inputBuffer);
		inputBuffer = NULL;
		inputBufferSize = 0;
	}
	wvc = nil;
	wv = nil;
}

- (void)dealloc {
	[self close];
}

- (NSDictionary *)properties {
	return @{ @"channels": @(channels),
		      @"channelConfig": @(channelConfig),
		      @"bitsPerSample": @(bitsPerSample),
		      @"bitrate": @(bitrate),
		      @"sampleRate": @(frequency),
		      @"floatingPoint": @(floatingPoint),
		      @"totalFrames": @(totalFrames),
		      @"seekable": @([[wv source] seekable]),
		      @"codec": @"WavPack",
		      @"endian": @"little",
		      @"encoding": isLossy ? @"lossy" : @"lossless" };
}

- (NSDictionary *)metadata {
	return @{};
}

+ (NSArray *)fileTypes {
	return @[@"wv", @"wvp"];
}

+ (NSArray *)mimeTypes {
	return @[@"audio/x-wavpack"];
}

+ (float)priority {
	return 1.0;
}

+ (NSArray *)fileTypeAssociations {
	return @[
		@[@"Wavpack File", @"wv.icns", @"wv", @"wvp"]
	];
}

@end
