#pragma comment (lib, "libmpg123.lib")
#pragma comment (lib, "openal32.lib")

#include "se_openal.h"
#include "log.h"
#include <iostream>
#include <vector>
#include <string>
#ifndef WIN32
#include <unistd.h>
#else
#include <windows.h>
#endif
#include <stdio.h>
#include <memory.h>

using namespace std;

// первая реализация
// TODO: поддержка затухания музыки при смене трека

namespace se
{
	ZSoundOpenAL::ZSoundOpenAL( ALuint source )
	{
		_source = source;
	}

	ZSoundOpenAL::~ZSoundOpenAL()
	{
		alSourceStop( _source );
		alDeleteSources ( 1, &_source );
	}

	void ZSoundOpenAL::kill()
	{
		alSourceStop( _source );
	}

	void ZSoundOpenAL::set_volume( float volume )
	{
		alSourcef( _source, AL_GAIN, max(0.f, (volume-0.5f)*2.f) );
	}

	ZSoundEngineOpenAL::ZSoundEngineOpenAL()
	{
	    Message( "SE", "Start creating Sound Engine" );
		_device = 0;
		_context = 0;
		_m_f = 0;
		_cur_buf = 0;
		_mpg_state = MPG123_NEED_MORE;

		for (int a=0; a<MAX_SOUND; a++)
			_buffer[a] = 0;

		_device = alcOpenDevice( 0 );

		if (!_device)
			Error( "SE", "Cannot open device" );

		_context = alcCreateContext( _device, 0 );

		if (!_context)
			Error( "SE", "Cannot create context" );

		alcMakeContextCurrent ( _context );

		float pos[] = { 0, 0, 0 };
		float velocity[] = { 0, 0, 0 };
		float orient[] = { 1, 0, 0, 0, 0, 1 };
		alListenerfv( AL_POSITION, pos );
		alListenerfv( AL_VELOCITY, velocity );
		alListenerfv( AL_ORIENTATION, orient );

		alGenSources( 1, &_music_source );

		float sourcePos[] = { 0, 0, 2 };
		float sourceVel[] = { 0, 0, 0 };

		//alSourcei( _music_source, AL_BUFFER, _buffer[num] );
		alSourcef( _music_source, AL_PITCH, 1.f );
		alSourcef( _music_source, AL_GAIN, 1.f );
		alSourcefv( _music_source, AL_POSITION, sourcePos );
		alSourcefv( _music_source, AL_VELOCITY, sourceVel );
		alSourcei( _music_source, AL_LOOPING, AL_FALSE );
		_cur_volume = 1.f;

		alGenBuffers( 2, _music_buffer );

		if (alGetError() != AL_NO_ERROR)
			Error("SE", "Failed during initializarion of sound engine.");

		// mpg123 init
		mpg123_init();
		int re;
		_mpg_handle = mpg123_new(0, &re);
		//cerr << re << "\n";
		if (!_mpg_handle)
			Error( "SE", "Cannot create mpg123 handle." );
		mpg123_param(_mpg_handle, MPG123_VERBOSE, 2, 0);

		_cur_music = -1;

		Message( "SE", "Sound Engine is created!" );
	}

	ZSoundEngineOpenAL::~ZSoundEngineOpenAL()
	{
	    Message( "SE", "Killing Sound Engine..." );
		alcDestroyContext( _context );
		alcCloseDevice( _device );

		for (set< ZSoundOpenAL * >::iterator it = _sounds.begin(); it != _sounds.end(); it ++)
			delete (*it);

		for (int a=0; a<MAX_SOUND; a++)
			if (_buffer[a])
				alDeleteBuffers( 1, &_buffer[a] );

		if (_m_f) fclose( _m_f );

		alDeleteSources( 1, &_music_source );
		alDeleteBuffers( 2, _music_buffer );

		mpg123_delete(_mpg_handle);
		mpg123_exit();

		Message( "SE", "Sound Engine is destroyed!" );
	}

	bool ZSoundEngineOpenAL::load_music( int num, const char * path )
	{
		/*char buffer[1024];
		memset( buffer, 0, sizeof( buffer ) );
#ifdef WIN32
		GetCurrentDirectory( 1000, buffer );
#else
		getcwd( buffer, 1000 );
#endif
		int i = (int)strlen(buffer);
		buffer[i++] = '/';
		for (int a=0; path[a]; a++)
			buffer[i++] = path[a];*/

		_path[num] = string( path );
		return true;
	}

	#pragma pack (push, 1)
	struct ZWavFmt
	{
		unsigned short	_encoding;
		unsigned short	_channels;
		unsigned int	_frequency;
		unsigned int	_byterate;
		unsigned short	_block_align;
		unsigned short	_bits_per_sample;
	};
	#pragma pack (pop)

	bool ZSoundEngineOpenAL::load_sound( int num, const char * path )
	{
		unsigned int	RIFF = 0x46464952;
		unsigned int	WAVE = 0x45564157;
		unsigned int	FMT  = 0x20746D66;
		unsigned int	DATA = 0x61746164;

		FILE * f = fopen( path, "rb" );

		if (!f)
            Error( "SE", (string("Cannot open file ")+string(path)).c_str() );

		unsigned int	magic1, magic2;
		unsigned int	length;

		fread( &magic1, 4, 1, f );
		fread( &length, 4, 1, f );
		fread( &magic2, 4, 1, f );

		//cerr << magic1 << " " << length << " " << magic2 << "\n";

		if (magic1 != RIFF || magic2 != WAVE)
			Error( "SE", (string("The file ") + string(path) + string(" it not a wav file.")).c_str() );

		unsigned int tmp, len;
		bool data_loaded = false;
		unsigned char * buf;
		ZWavFmt fmt;
		while ( fread( &tmp, 4, 1, f ) )
		{
			fread( &len, 4, 1, f );
			if (tmp == FMT)
			{
				fread( &fmt, 1, sizeof( ZWavFmt ), f );
				if (fmt._encoding != 1)
					Error( "SE", (string("Cannot load file ") + string(path)).c_str() );
			}
			else if (tmp == DATA)
			{
				//cerr << len << "\n";
				buf = new unsigned char[len];
				fread(buf, 1, len, f);
				data_loaded = true;
				break;
			}
			else Error( "SE", (string("Cannot load file ") + string(path)).c_str() );
		}

		fclose( f );

		if (fmt._channels < 0 || fmt._frequency == 0 || !data_loaded)
			Error( "SE", (string("Cannot load file ") + string(path)).c_str() );

		unsigned int format;
		if ( fmt._channels == 2 )		// stereo
		{
			if ( fmt._bits_per_sample == 16 )
				format = AL_FORMAT_STEREO16;
			else
				format = AL_FORMAT_STEREO8;
		}
		else
		{
			if ( fmt._bits_per_sample == 16 )
				format = AL_FORMAT_MONO16;
			else
				format = AL_FORMAT_MONO8;
		}

		alGenBuffers( 1, &_buffer[num] );
		alBufferData( _buffer[num], format, buf, len, fmt._frequency );

		delete [] buf;

		//cerr << "load_sound " << alGetError() << "\n";
		if (alGetError() != AL_NO_ERROR)
			Error("SE", "Failed during loading wav file.");

        Message( "SE", (string("Sound ")+string(path)+" is loaded.").c_str() );

		return true;
	}

	int ZSoundEngineOpenAL::decode_part(int _buf)
	{
		if (_mpg_state == MPG123_ERR)
			Error("SE", ( string("Mpg123 error: %s") + string(mpg123_strerror(_mpg_handle))).c_str() );

		static int in_bytes = 0, out_bytes = 0;

		size_t pos = 0;
		if (_mpg_state != MPG123_NEED_MORE)
			_mpg_state = mpg123_decode(_mpg_handle, 0, 0, _out_buf, BUFFER_SIZE, &pos);

		if (pos < BUFFER_SIZE)
		{
			int sz = fread( _in_buf, 1, BUFFER_SIZE, _m_f );
			in_bytes += sz;
			//cerr << sz << " bytes readed\n";
			if (sz<=0) return 0; // end of file

			size_t szz;
			_mpg_state = mpg123_decode( _mpg_handle, _in_buf, BUFFER_SIZE, _out_buf+pos, BUFFER_SIZE-pos, &szz);
			pos += szz;
		}

		out_bytes += pos;

		//cerr << in_bytes << " bytes in; " << out_bytes << " byters out\n";

		if (_mpg_state == MPG123_ERR)
			Error("SE", ( string("Mpg123 error: %s") + string(mpg123_strerror(_mpg_handle))).c_str() );

		if (pos==0) return 0;

		long rate;
		int channels, enc, enc_size;
		mpg123_getformat( _mpg_handle, &rate, &channels, &enc );
		enc_size = 16; //mpg123_encsize(enc)*8;
		//fprintf(stderr, "New format: %li Hz, %i channels, encoding size %i\n", rate, channels, enc_size);

		unsigned int format;
		if ( channels == 2 )		// stereo
		{
			if ( enc_size == 16 )
				format = AL_FORMAT_STEREO16;
			else
				format = AL_FORMAT_STEREO8;
		}
		else
		{
			if ( enc_size == 16 )
				format = AL_FORMAT_MONO16;
			else
				format = AL_FORMAT_MONO8;
		}

		alBufferData( _music_buffer[ _buf ], format, _out_buf, pos, rate );

		if (alGetError() != AL_NO_ERROR)
			Error("SE", "Failed during decoding chunk of stream.");
		return pos;
	}

	void ZSoundEngineOpenAL::run_music( int num, float volume, bool sudo )
	{
		if (_m_f) fclose(_m_f);
		_m_f = fopen( _path[num].c_str(), "rb" );
		if (_m_f==0) Error( "SE", (string("Cannot open ") + string(_path[num])).c_str() );

		mpg123_close(_mpg_handle);
		mpg123_open_feed(_mpg_handle);
		if (!_mpg_handle)
			Error( "SE", "Cannot init mpg123 handle." );

		alSourceStop( _music_source );

		ALint queued;
		alGetSourcei( _music_source, AL_BUFFERS_QUEUED, &queued );

		for (int a=0; a<queued; a++)
		{
			alSourceUnqueueBuffers ( _music_source, 1, &_music_buffer[_cur_buf] );
			_cur_buf = 1 - _cur_buf;
		}

		_cur_buf = 0;

		while (1)
		{
			int res = decode_part( 0 );
			if (res) break;
		}

		alSourceQueueBuffers( _music_source, 1, &_music_buffer[0] );
		decode_part( 1 );
		alSourceQueueBuffers( _music_source, 1, &_music_buffer[1] );

		alSourcePlay( _music_source );
		set_volume( volume );

		_cur_music = num;

		if (alGetError() != AL_NO_ERROR)
			Error("SE", "Failed during starting new music.");

        Message( "SE", (string("Music ")+string(_path[num])+" is started to play").c_str() );
	}

	int ZSoundEngineOpenAL::get_cur_music()
	{
		return _cur_music;
	}

	void ZSoundEngineOpenAL::set_volume( float volume )
	{
		_cur_volume = volume;
		if (_music_source)
			alSourcef( _music_source, AL_GAIN, max(0.f, (volume-0.5f)*2.f) );
	}

	XSound * ZSoundEngineOpenAL::play_sound(int num, float volume, bool loop)
	{
		if (!loop)
		{
			if (_cur_frame_sounds.find( num ) != _cur_frame_sounds.end()) return 0;
			_cur_frame_sounds.insert( num );
		}

		ALuint source;
		alGenSources( 1, &source );

		float sourcePos[] = { 0, 0, 2 };
		float sourceVel[] = { 0, 0, 0 };

		alSourcei( source, AL_BUFFER, _buffer[num] );
		alSourcef( source, AL_PITCH, 1.f );
		alSourcef( source, AL_GAIN, max(0.f, (volume-0.5f)*2.f) );
		alSourcefv( source, AL_POSITION, sourcePos );
		alSourcefv( source, AL_VELOCITY, sourceVel );
		alSourcei( source, AL_LOOPING, loop ? AL_TRUE : AL_FALSE );

		alSourcePlay( source );

		//cerr << "play_sound " << alGetError() << "\n";

		ZSoundOpenAL * snd = new ZSoundOpenAL( source );
		_sounds.insert( snd );

		if (alGetError() != AL_NO_ERROR)
			Error("SE", "Failed during playing sound.");

		return snd;
	}

	void ZSoundEngineOpenAL::stop_all_sounds()
	{
		while (_sounds.size() > 0)
		{
			set< ZSoundOpenAL * >::iterator it = _sounds.begin();
			delete (*it);
			_sounds.erase(it);
		}
		_cur_frame_sounds.clear();
	}

	void ZSoundEngineOpenAL::check_time()
	{
		ALint proc;

		alGetSourcei( _music_source, AL_BUFFERS_PROCESSED, &proc );

		if ( proc == 1 )
		{
			alSourceUnqueueBuffers( _music_source, 1, &_music_buffer[_cur_buf] );

			int sz = decode_part( _cur_buf );
			if (sz>0)
			{
				alSourceQueueBuffers( _music_source, 1, &_music_buffer[_cur_buf] );
			}
			else
			{
				_cur_music = -1;
				cerr << "End of file?\n";
			}

			_cur_buf = 1 - _cur_buf;
		}
		else if ( proc == 2 )
		{
			alSourceUnqueueBuffers( _music_source, 2, _music_buffer );

			_cur_buf = 0;
			decode_part( 0 );
			alSourceQueueBuffers( _music_source, 1, &_music_buffer[0] );
			decode_part( 1 );
			alSourceQueueBuffers( _music_source, 1, &_music_buffer[1] );

			alSourcePlay( _music_source );
		}

		_cur_frame_sounds.clear();

        vector< ZSoundOpenAL * > vec;
		set< ZSoundOpenAL * >::iterator it = _sounds.begin();
		while (it != _sounds.end())
		{
			ALint state;
			alGetSourcei( (*it)->_source, AL_SOURCE_STATE, &state );
			if( state == AL_STOPPED )
			{
				ZSoundOpenAL * snd = (*it);
				delete snd;
				//it = _sounds.erase(it);
				vec.push_back( snd );
			}
			it++;
		}
		for (int a=0; a<(int)vec.size(); a++)
            _sounds.erase( vec[a] );
	}
};
