
#import <stdexcept>
#import "MediaPlayer.h"


@implementation CallBackCommonParams
@synthesize frame;
@synthesize bounds;
@synthesize state;
@synthesize status;
@synthesize stream_info;

- (void) reset
{
	frame = 0;
	bounds = 0;
	state  = 0;
	status = 0;
    stream_info = 0;
}

@end

#define kOutputBus 0
#define kInputBus 1

namespace videonext { namespace media { namespace iphone {
	
std::string state_to_str(VN_PLAYER_STREAM_STATE state)
{
	std::string state_str;
	switch (state)
	{
		case IDLE:
			state_str="IDLE";
			break;
		case PLAY_FROM_SERVER:
			state_str="PLAY_FROM_SERVER";
			break;
		case PLAY_FROM_BUFFER:
			state_str="PLAY_FROM_BUFFER";
			break;
		case STOPPED:
			state_str="STOPPED";
			break;
		case OPENING:
			state_str="OPENING";
			break;
        case OPENED:
            state_str="OPENED";
            break;
        case BUFFERING:
            state_str="BUFFERING";
            break;
		default:
			state_str="UNKNOWN";
	}
		
	return state_str;
}
	
	
MediaPlayer::MediaPlayer(const std::string &url, VnPlayer *outputView)
	: url_(url), view_(outputView),
	  audioUnit_(0), audioInputBuffer_(0)	
{
	params_ = [[CallBackCommonParams alloc] init];
	[params_ reset];
	
    memset(&conf_, 0, sizeof(conf_));
	conf_.cache_size = 2;
    conf_.decoder_type = ::DECODER_SW;
	conf_.pixel_format = ::PIX_BGR32;
	conf_.rtp_transport = RTP_TRANSPORT_TCP;
	conf_.client_data = this;
    conf_.buffer_len  = 1000;
	
    struct vn_player_callbacks_t callbacks = {};
	callbacks.buffer_changed = buffer_changed;
	callbacks.new_frame      = new_frame;
	callbacks.new_stream     = new_stream;
	callbacks.state_changed  = state_changed;
	callbacks.stream_removed = stream_removed;
    callbacks.msg_log        = msg_log;
	
    ctx_ = vn_player_create(&conf_);
	vn_player_set_callbacks(ctx_, &callbacks);
}

void MediaPlayer::open()
{
	vn_player_open(ctx_, url_.c_str());
}

void MediaPlayer::play()
{
    vn_player_play(ctx_);
}

void MediaPlayer::resume(int direction)
{
	vn_player_resume(ctx_, direction);
}
	

void MediaPlayer::pause()
{
	vn_player_pause(ctx_);
}
	
void MediaPlayer::moveToTimestamp(time_t ts)
{
	vn_player_move_to_ts(ctx_, ts);
}
	
void MediaPlayer::teardown()
{
	vn_player_destroy(ctx_);
}
	
/*virtual*/ MediaPlayer::~MediaPlayer()
{
	if (audioUnit_)
	{
		MutexGuard g(audioInputBufferMutex_);		
		
		try 
		{
			OSStatus status = AudioOutputUnitStop (audioUnit_);
			VN_CHECK_OSSTATUS_THROW_EXCEPTION(status, std::runtime_error, "AudioOutputUnitStop()");
			
			status = AudioUnitUninitialize(audioUnit_);
			VN_CHECK_OSSTATUS_THROW_EXCEPTION(status, std::runtime_error, " AudioUnitUninitialize()");
			
			status = AudioSessionSetActive(false); 
			VN_CHECK_OSSTATUS(status, "AudioSessionSetActive(false)");
		}
		catch (std::runtime_error &e) {
			fprintf(stderr, "Failed to destroy audio unit: %s\n", e.what());
		}
		
        usleep(100000);
		av_fifo_free(audioInputBuffer_);
        audioInputBuffer_ = 0;
		
	}
}
	
/*static*/ void MediaPlayer::new_frame(const vn_player_frame_t *frame, 
									   const vn_player_cache_boundaries_t *bounds, void *client_data)
{	
	MediaPlayer *player = (MediaPlayer*)client_data;

	if (frame->stream_info->type == ::AUDIO)
	{
		MutexGuard g(player->audioInputBufferMutex_);
        
        //UInt32 s = av_fifo_size(player->audioInputBuffer_);
#if !TARGET_IPHONE_SIMULATOR
        // FIXME: real devices failed to initialize stereo Audio Units. So workaround is to play only one channel
        vn_player_stream_info_t *streamInfo = frame->stream_info;
        for(unsigned i=0; i<frame->data_size[0]; i+=(streamInfo->bits_per_sample/8*streamInfo->num_channels))
            av_fifo_generic_write(player->audioInputBuffer_, frame->data[0]+i, streamInfo->bits_per_sample/8, 0);
#else
		av_fifo_generic_write(player->audioInputBuffer_, frame->data[0], frame->data_size[0], 0);
#endif
        //fprintf(stderr, "*** %d ==> %d\n", s, av_fifo_size(player->audioInputBuffer_));
        
		player->audioInputBufferMutex_.signal();
	}
	else if (frame->stream_info->type == ::VIDEO)
	{
		[player->params_ reset];	
		player->params_.frame  = (vn_player_frame_t *)frame;
		player->params_.bounds = (vn_player_cache_boundaries_t *)bounds;
	
		[player->view_ performSelectorOnMainThread:@selector(displayFrame:) withObject:player->params_ waitUntilDone:YES];
	}
}

/*static*/ void MediaPlayer::buffer_changed(const vn_player_cache_boundaries_t *bounds, void *client_data)
{
}

/*static*/ void MediaPlayer::state_changed(VN_PLAYER_STREAM_STATE state, const vn_player_result_status_t *status, 
										   void *client_data)
{
	fprintf(stderr,"State Changed---\n");   
	fprintf(stderr,"State: %d (%s)\n", state, state_to_str(state).c_str());   
	fprintf(stderr,"Result status: %d (%s)\n", status->error_code, status->error_str?status->error_str:"");
	fprintf(stderr, "---\n");   
	
	
	MediaPlayer *player = (MediaPlayer*)client_data;
	
	[player->params_ reset];
	
	player->params_.state  = &state;
	player->params_.status = (vn_player_result_status_t *)status;

	[player->view_ performSelectorOnMainThread:@selector(stateChanged:) withObject:player->params_ waitUntilDone:YES];

    if(state == ::OPENED)
        player->play();
    else if(state == ::STOPPED) {
        player->teardown();
		delete player;
	}
}

/*static*/ void MediaPlayer::new_stream(const vn_player_stream_info_t *streamInfo, void *client_data)
{
	MediaPlayer *player = (MediaPlayer*)client_data;
	if (streamInfo->type == ::AUDIO)
	{
		try {
			player->initAudioOutput(streamInfo);
		}
		catch (std::runtime_error &e) {
			fprintf(stderr, "Audio output init failed: %s\n", e.what());
			exit(1);
		}
	}
    
    [player->params_ reset];
    player->params_.stream_info = (vn_player_stream_info_t *)streamInfo;
    [player->view_ performSelectorOnMainThread:@selector(newStream:) withObject:player->params_ waitUntilDone:YES];
}

/*static*/ void MediaPlayer::stream_removed(const vn_player_stream_info_t *streamInfo, void *client_data)
{
}

/*static*/void MediaPlayer::msg_log(const char *msg, void *client_data)
{
	//fprintf(stderr, "%s", msg);
}
	
void MediaPlayer::initAudioOutput(const vn_player_stream_info_t *streamInfo) throw (std::runtime_error)
{
    OSStatus status;
    
    fprintf(stderr, "Trying to init audio device: rate: %d, channels: %d, bits: %d\n",
			streamInfo->sampling_freq, streamInfo->num_channels, streamInfo->bits_per_sample);
    
	memset(&audioFormat_, 0, sizeof(audioFormat_));
	audioFormat_.mSampleRate = (float)streamInfo->sampling_freq;
#if !TARGET_IPHONE_SIMULATOR
    // FIXME: real devices failed to initialize stereo Audio Units. So workaround is to play only one channel
    audioFormat_.mChannelsPerFrame = 1; //streamInfo->num_channels;
#else
    audioFormat_.mChannelsPerFrame = streamInfo->num_channels;
#endif
	audioFormat_.mFormatID = kAudioFormatLinearPCM;
    audioFormat_.mFormatFlags = kLinearPCMFormatFlagIsSignedInteger | kLinearPCMFormatFlagIsPacked;
	audioFormat_.mBitsPerChannel = streamInfo->bits_per_sample;
	audioFormat_.mBytesPerPacket = audioFormat_.mBytesPerFrame
			= (audioFormat_.mBitsPerChannel / 8) * audioFormat_.mChannelsPerFrame;
	audioFormat_.mFramesPerPacket = 1;
	
	audioInputBuffer_ = av_fifo_alloc(8192 * 16);
    int inputBufferSize = av_fifo_size(audioInputBuffer_);
	
	UInt32 category = kAudioSessionCategory_PlayAndRecord;	
	status = AudioSessionSetProperty(kAudioSessionProperty_AudioCategory, 
									 sizeof(category), 
									 &category);
	VN_CHECK_OSSTATUS_THROW_EXCEPTION(status, std::runtime_error, "AudioSessionSetProperty(kAudioSessionProperty_AudioCategory)");
	/*
    UInt32 allowMixing = true;
    status = AudioSessionSetProperty (
                                      kAudioSessionProperty_OverrideCategoryMixWithOthers,  // 1
                                      sizeof (allowMixing),                                 // 2
                                      &allowMixing                                          // 3
                                      );
    
    VN_CHECK_OSSTATUS_THROW_EXCEPTION(status, std::runtime_error, "AudioSessionSetProperty(kAudioSessionProperty_OverrideCategoryMixWithOthers)");
    */
	UInt32 route = kAudioSessionOverrideAudioRoute_Speaker;
	status = AudioSessionSetProperty(kAudioSessionProperty_OverrideAudioRoute,
									 sizeof(route),
									 &route);
	VN_CHECK_OSSTATUS_THROW_EXCEPTION(status, std::runtime_error, "AudioSessionSetProperty(kAudioSessionProperty_OverrideAudioRoute)");
	

	status = AudioSessionSetProperty(kAudioSessionProperty_PreferredHardwareSampleRate, 
									 sizeof(audioFormat_.mSampleRate), 
									 &audioFormat_.mSampleRate);
	VN_CHECK_OSSTATUS_THROW_EXCEPTION(status, std::runtime_error, "AudioSessionSetProperty(kAudioSessionProperty_PreferredHardwareSampleRate)");
	
	Float32 ioBufferDuration = 0.0f;
	status = AudioSessionSetProperty(kAudioSessionProperty_PreferredHardwareIOBufferDuration, 
									 sizeof(ioBufferDuration), 
									 &ioBufferDuration);
	VN_CHECK_OSSTATUS_THROW_EXCEPTION(status, std::runtime_error, "AudioSessionSetProperty(kAudioSessionProperty_PreferredHardwareIOBufferDuration)");
	
	status = AudioSessionSetActive(true);
	VN_CHECK_OSSTATUS(status, "AudioSessionSetActive(true)");
	
	
	// Getting actual values: 
	UInt32 size = sizeof(audioFormat_.mSampleRate);
	AudioSessionGetProperty(kAudioSessionProperty_CurrentHardwareSampleRate,
							&size,	
							&audioFormat_.mSampleRate);
	
	size = sizeof(audioFormat_.mChannelsPerFrame);
	AudioSessionGetProperty(kAudioSessionProperty_CurrentHardwareInputNumberChannels, 
							&size, 
							&audioFormat_.mChannelsPerFrame);
	
	size = sizeof(ioBufferDuration);
	AudioSessionGetProperty(kAudioSessionProperty_PreferredHardwareIOBufferDuration, 
							&size,
							&ioBufferDuration);
	
	fprintf(stderr, "audioFormat.mSampleRate: %f\n", audioFormat_.mSampleRate);
	fprintf(stderr, "audioFormat.mChannelsPerFrame: %d\n", (unsigned)audioFormat_.mChannelsPerFrame);
	fprintf(stderr, "ioBufferDuration: %f\n", ioBufferDuration);
	
	// Describe audio component
	AudioComponentDescription desc;
	desc.componentType = kAudioUnitType_Output;
	desc.componentSubType = kAudioUnitSubType_RemoteIO;
	desc.componentFlags = 0;
	desc.componentFlagsMask = 0;
	desc.componentManufacturer = kAudioUnitManufacturer_Apple;
	
	// Get component
	AudioComponent inputComponent = AudioComponentFindNext(NULL, &desc);
	
	// Get audio units
	status = AudioComponentInstanceNew(inputComponent, &audioUnit_);
	VN_CHECK_OSSTATUS_THROW_EXCEPTION(status, std::runtime_error, "AudioComponentInstanceNew()");
    
	// Enable Output on the AUHAL
	UInt32 flag = 1;
	status = AudioUnitSetProperty(audioUnit_, 
								  kAudioOutputUnitProperty_EnableIO, 
								  kAudioUnitScope_Output, 
								  kOutputBus, 
								  &flag, 
								  sizeof(flag));
	VN_CHECK_OSSTATUS_THROW_EXCEPTION(status, std::runtime_error, "AudioUnitSetProperty(kAudioOutputUnitProperty_EnableIO)");

	// Set audio format 
	status = AudioUnitSetProperty(audioUnit_, 
								  kAudioUnitProperty_StreamFormat, 
								  kAudioUnitScope_Output, 
								  kInputBus, 
								  &audioFormat_, 
								  sizeof(audioFormat_));
	VN_CHECK_OSSTATUS_THROW_EXCEPTION(status, std::runtime_error, "AudioUnitSetProperty(kAudioUnitProperty_StreamFormat)");
	
    status = AudioUnitSetProperty(audioUnit_,
                                  kAudioUnitProperty_StreamFormat,
                                  kAudioUnitScope_Input,
                                  kOutputBus,
                                  &audioFormat_,
                                  sizeof(audioFormat_));
    VN_CHECK_OSSTATUS_THROW_EXCEPTION(status, std::runtime_error, "AudioUnitSetProperty(kAudioUnitProperty_StreamFormat)");
	
	// Set output callback
	AURenderCallbackStruct callbackStruct;
	callbackStruct.inputProc = playbackCallback;
	callbackStruct.inputProcRefCon = this;
	status = AudioUnitSetProperty(audioUnit_, 
								  kAudioUnitProperty_SetRenderCallback, 
								  kAudioUnitScope_Global, 
								  kOutputBus,
								  &callbackStruct, 
								  sizeof(callbackStruct));
	
	// Initialize Audio Unit
	status = AudioUnitInitialize(audioUnit_);
    NSError *error = [NSError errorWithDomain:NSOSStatusErrorDomain
                                         code:status
                                     userInfo:nil];
    NSLog(@"Error: %@", [error description]);
	VN_CHECK_OSSTATUS_THROW_EXCEPTION(status, std::runtime_error, "AudioUnitInitialize()");
	
	status = AudioSessionSetActive(true); 
	VN_CHECK_OSSTATUS(status, "AudioSessionSetActive(true)");
	
	status = AudioOutputUnitStart(audioUnit_);
	VN_CHECK_OSSTATUS_THROW_EXCEPTION(status, std::runtime_error, "AudioOutputUnitStart()");
}

	
/*static*/ OSStatus MediaPlayer::playbackCallback(void *opaque, 
												  AudioUnitRenderActionFlags *ioActionFlags, 
												  const AudioTimeStamp *inTimeStamp, 
												  UInt32 inBusNumber, 
												  UInt32 inNumberFrames, 
												  AudioBufferList *ioData) 
{
	// Notes: ioData contains buffers (may be more than one!)
    // Fill them up as much as you can. Remember to set the size value in each buffer to match how
    // much data is in the buffer.
	//fprintf(stderr, "MediaPlayer::playbackCallback()\n");
	
	MediaPlayer *player = (MediaPlayer*)opaque;
	MutexGuard g(player->audioInputBufferMutex_);
	
    if (player->audioInputBuffer_ == 0)
        return noErr;
    
    for (int i=0; i < ioData->mNumberBuffers; i++) { // in practice we will only ever have 1 buffer, since audio format is mono
        
        // uncomment to hear random noise
        /*AudioBuffer buffer = ioData->mBuffers[i];
        UInt16 *frameBuffer = (UInt16*)buffer.mData;
        for (int j = 0; j < inNumberFrames; j++)
        frameBuffer[j] = rand();
        continue;*/
        
        int inputBufferSize = av_fifo_size(player->audioInputBuffer_);
        
        if (inputBufferSize == 0)
        {
            //fprintf(stderr, "inputBufferSize==0\n");
            bzero(ioData->mBuffers[i].mData, ioData->mBuffers[i].mDataByteSize);
            return noErr;
        }

        if (ioData->mBuffers[i].mDataByteSize > inputBufferSize)
            ioData->mBuffers[i].mDataByteSize = inputBufferSize;

        if (1/*player->view_.audioOutputEnabled*/)
        {
            av_fifo_generic_read(player->audioInputBuffer_, ioData->mBuffers[i].mData, ioData->mBuffers[i].mDataByteSize, 0);
        }
        else  
        {
            av_fifo_drain(player->audioInputBuffer_, ioData->mBuffers[i].mDataByteSize);
            bzero(ioData->mBuffers[i].mData, ioData->mBuffers[i].mDataByteSize);
        }
        
        /*fprintf(stderr, "[%d] Buffer %d has %d channels and wants %d bytes of data. Queue size: %d\n", i,
                 ioData->mNumberBuffers, ioData->mBuffers[i].mNumberChannels, ioData->mBuffers[i].mDataByteSize,
                 inputBufferSize);*/
    }
	
	return noErr;
}		
	
	
}}}
