#import "Finch-Sound.h"
#import <AudioToolbox/AudioToolbox.h> 
#import "Finch-Decoder.h"
#import "Finch-Sample.h"
#import "Finch-Reporter.h"
#define CLEAR_ERROR_FLAG alGetError()
#define DETACH_SOURCE 0

// Clears the error flag.
bool checkSuccessOrLog(NSString* msg)
{
    ALenum errcode;
    if ((errcode = alGetError()) != AL_NO_ERROR)
    {
        NSLog(@"%@, error code %x.", msg, errcode);
        return NO;
    }
    return YES;
}

#pragma mark Designated Initializer

bool FinchSound::initWithData(const ALvoid* data, ALsizei size, ALenum format, ALsizei frequency, float seconds)
{
    ALCcontext *const currentContext = alcGetCurrentContext();
    if (currentContext == NULL)
    {
        NSLog(@"OpenAL context not set, did you initialize Finch?");
        return false;
    }
    
    // Allocate buffer.
    CLEAR_ERROR_FLAG;
    alGenBuffers(1, &buffer);
    if (!checkSuccessOrLog(@"Failed to allocate OpenAL buffer"))
        return false;

    // Pass sound data to OpenAL.
    CLEAR_ERROR_FLAG;
    alBufferData(buffer, format, data, size, frequency);
    if (!checkSuccessOrLog(@"Failed to fill OpenAL buffers"))
        return false;
    
    // Initialize the source.
    CLEAR_ERROR_FLAG;
    alGenSources(1, &source);
    alSourcei(source, AL_BUFFER, buffer);
    if (!checkSuccessOrLog(@"Failed to create OpenAL source"))
        return false;

    gain = 1;
    duration = seconds;
    return true;
}

FinchSound::~FinchSound()
{
    stop();
    CLEAR_ERROR_FLAG;
    alSourcei(source, AL_BUFFER, DETACH_SOURCE);
    alDeleteBuffers(1, &buffer), buffer = 0;
    alDeleteSources(1, &source), source = 0;
    checkSuccessOrLog(@"Failed to clean up after sound");
    //[super dealloc]; ARC dumb
}

bool FinchSound::initWithFile(NSString* name, NSError** error)
{
	
    FinchSample *sample = [FinchDecoder decodeFile:name error:error];
    if (!sample)
        return nil;
    
    FinchReporter *reporter = [[FinchReporter alloc] initWithDomain:@"Sound Initialization" error:error];

    // Check the number of channels
    if (sample.channels != 1 && sample.channels != 2)
	{
        *error = [reporter errorWithCode: kSEInvalidNumberOfChannels];
        return nil;
    }
    
    // Check sample resolution
    if (sample.bitsPerChannel != 8 && sample.bitsPerChannel != 16)
	{
        *error = [reporter errorWithCode:kSEInvalidSampleResolution];
        return nil;
    }
    
    // Check data endianity
    if (sample.endianity != kLittleEndian)
	{
        *error = [reporter errorWithCode: kSEInvalidEndianity];
        return nil;
    }
    
    const ALenum format = sample.channels == 1 ?
        (sample.bitsPerChannel == 16 ? AL_FORMAT_MONO16 : AL_FORMAT_MONO8) :
        (sample.bitsPerChannel == 16 ? AL_FORMAT_STEREO16 : AL_FORMAT_STEREO8);
    return initWithData(sample.data.bytes, sample.data.length, format, sample.sampleRate, sample.duration);
}

bool FinchSound::initWithFile(NSString* name)
{
    NSError *error = nil;
    bool result = initWithFile(name, &error);
    if (error)
        NSLog(@"There was an error loading a sound: %@", [error localizedDescription]);
    return result;
}

#pragma mark Playback Controls

float FinchSound::getDuration()
{
	return duration;
}

void FinchSound::setGain(float val)
{
    gain = val;
    alSourcef(source, AL_GAIN, gain);
}
float FinchSound::getGain()
{
	return gain;
}

void FinchSound::setPitch(float val)
{
    pitch = val;
    alSourcef(source, AL_PITCH, pitch);
}
float FinchSound::getPitch()
{
	return pitch;
}

bool FinchSound::isPlaying()
{
    ALint state;
    alGetSourcei(source, AL_SOURCE_STATE, &state);
    return (state == AL_PLAYING);
}

void FinchSound::setLooping(bool yn)
{
    loop = yn;
    alSourcei(source, AL_LOOPING, yn);
}

void FinchSound::play()
{
    if (playing)
        stop();
    CLEAR_ERROR_FLAG;
    alSourcePlay(source);
    checkSuccessOrLog(@"Failed to start sound");
}

void FinchSound::pause()
{
    if (!playing)
        return;
    CLEAR_ERROR_FLAG;
    alSourcePause(source);
    checkSuccessOrLog(@"Failed to pause sound");
}

void FinchSound::stop()
{
    if (!playing)
        return;
    CLEAR_ERROR_FLAG;
    alSourceStop(source);
    checkSuccessOrLog(@"Failed to stop sound");
}
