// This file is part of ZetaGlest <https://github.com/ZetaGlest>
//
// Copyright (C) 2018  The ZetaGlest team
//
// ZetaGlest is a fork of MegaGlest <https://megaglest.org>
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.

// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>

#include "sound_file_loader.h"

#include <vorbis/codec.h>
#include <vorbis/vorbisfile.h>

#include "sound.h"
#include "util.h"
#include "platform_util.h"
#include "byte_order.h"
#include "leak_dumper.h"

using namespace Shared::Platform;
using namespace std;
using namespace Shared::Util;

namespace Shared {
	namespace Sound {

		// =====================================================
		//	class WavSoundFileLoader
		// =====================================================

		void WavSoundFileLoader::open(const string &path, SoundInfo *soundInfo) {
			char chunkId[] = { '-', '-', '-', '-', '\0' };
			uint32 size32 = 0;
			uint16 size16 = 0;
			int count;
			fileName = path;

			f.open(path.c_str(), ios_base::in | ios_base::binary);

			if (!f.is_open()) {
				throw game_runtime_error("Error opening wav file: " + string(path), true);
			}

			//RIFF chunk - Id
			f.read(chunkId, 4);
			static bool bigEndianSystem = Shared::PlatformByteOrder::isBigEndian();
			if (bigEndianSystem == true) {
				for (unsigned int i = 0; i < 4; ++i) {
					chunkId[i] = Shared::PlatformByteOrder::fromCommonEndian(chunkId[i]);
				}
			}

			if (strcmp(chunkId, "RIFF") != 0) {
				throw game_runtime_error("Not a valid wav file (first four bytes are not RIFF):" + path, true);
			}

			//RIFF chunk - Size 
			f.read((char*) &size32, 4);
			if (bigEndianSystem == true) {
				size32 = Shared::PlatformByteOrder::fromCommonEndian(size32);
			}

			//RIFF chunk - Data (WAVE string)
			f.read(chunkId, 4);
			if (bigEndianSystem == true) {
				for (unsigned int i = 0; i < 4; ++i) {
					chunkId[i] = Shared::PlatformByteOrder::fromCommonEndian(chunkId[i]);
				}
			}

			if (strcmp(chunkId, "WAVE") != 0) {
				throw game_runtime_error("Not a valid wav file (wave data don't start by WAVE): " + path, true);
			}

			// === HEADER ===

			//first sub-chunk (header) - Id
			f.read(chunkId, 4);
			if (bigEndianSystem == true) {
				for (unsigned int i = 0; i < 4; ++i) {
					chunkId[i] = Shared::PlatformByteOrder::fromCommonEndian(chunkId[i]);
				}
			}

			if (strcmp(chunkId, "fmt ") != 0) {
				throw game_runtime_error("Not a valid wav file (first sub-chunk Id is not fmt): " + path, true);
			}

			//first sub-chunk (header) - Size 
			f.read((char*) &size32, 4);
			if (bigEndianSystem == true) {
				size32 = Shared::PlatformByteOrder::fromCommonEndian(size32);
			}

			//first sub-chunk (header) - Data (encoding type) - Ignore
			f.read((char*) &size16, 2);
			if (bigEndianSystem == true) {
				size16 = Shared::PlatformByteOrder::fromCommonEndian(size16);
			}

			//first sub-chunk (header) - Data (nChannels)
			f.read((char*) &size16, 2);
			if (bigEndianSystem == true) {
				size16 = Shared::PlatformByteOrder::fromCommonEndian(size16);
			}

			soundInfo->setChannels(size16);

			//first sub-chunk (header) - Data (nsamplesPerSecond)
			f.read((char*) &size32, 4);
			if (bigEndianSystem == true) {
				size32 = Shared::PlatformByteOrder::fromCommonEndian(size32);
			}

			soundInfo->setsamplesPerSecond(size32);

			//first sub-chunk (header) - Data (nAvgBytesPerSec)  - Ignore
			f.read((char*) &size32, 4);
			if (bigEndianSystem == true) {
				size32 = Shared::PlatformByteOrder::fromCommonEndian(size32);
			}

			//first sub-chunk (header) - Data (blockAlign) - Ignore
			f.read((char*) &size16, 2);
			if (bigEndianSystem == true) {
				size16 = Shared::PlatformByteOrder::fromCommonEndian(size16);
			}

			//first sub-chunk (header) - Data (nsamplesPerSecond)
			f.read((char*) &size16, 2);
			if (bigEndianSystem == true) {
				size16 = Shared::PlatformByteOrder::fromCommonEndian(size16);
			}

			soundInfo->setBitsPerSample(size16);

			soundInfo->setBitRate(soundInfo->getSamplesPerSecond() * soundInfo->getChannels() * soundInfo->getBitsPerSample() / 8);

			if (soundInfo->getBitsPerSample() != 8 && soundInfo->getBitsPerSample() != 16) {
				throw game_runtime_error("Bits per sample must be 8 or 16: " + path, true);
			}
			bytesPerSecond = soundInfo->getBitsPerSample() * 8 * soundInfo->getSamplesPerSecond()*soundInfo->getChannels();

			count = 0;
			do {
				count++;

				// === DATA ===
				//second sub-chunk (samples) - Id
				f.read(chunkId, 4);
				if (bigEndianSystem == true) {
					for (unsigned int i = 0; i < 4; ++i) {
						chunkId[i] = Shared::PlatformByteOrder::fromCommonEndian(chunkId[i]);
					}
				}

				if (strncmp(chunkId, "data", 4) != 0) {
					continue;
				}

				//second sub-chunk (samples) - Size
				f.read((char*) &size32, 4);
				if (bigEndianSystem == true) {
					size32 = Shared::PlatformByteOrder::fromCommonEndian(size32);
				}

				dataSize = size32;
				soundInfo->setSize(dataSize);
			} while (strncmp(chunkId, "data", 4) != 0 && count < maxDataRetryCount);

			if (f.bad() || count == maxDataRetryCount) {
				throw game_runtime_error("Error reading samples: " + path, true);
			}

			dataOffset = (uint32) f.tellg();

		}

		uint32 WavSoundFileLoader::read(int8 *samples, uint32 size) {
			f.read(reinterpret_cast<char*> (samples), size);
			static bool bigEndianSystem = Shared::PlatformByteOrder::isBigEndian();
			if (bigEndianSystem == true) {
				Shared::PlatformByteOrder::toEndianTypeArray<int8>(samples, size);
			}

			return (uint32) f.gcount();
		}

		void WavSoundFileLoader::close() {
			f.close();
		}

		void WavSoundFileLoader::restart() {
			f.seekg(dataOffset, ios_base::beg);
		}

		// =======================================
		//        Ogg Sound File Loader
		// =======================================

		OggSoundFileLoader::OggSoundFileLoader() {
			vf = NULL;
			f = NULL;
		}

		void OggSoundFileLoader::open(const string &path, SoundInfo *soundInfo) {
			fileName = path;

#ifdef WIN32
			f = _wfopen(utf8_decode(path).c_str(), L"rb");
#else
			f = fopen(path.c_str(), "rb");
#endif
			if (f == NULL) {
				throw game_runtime_error("Can't open ogg file: " + path, true);
			}

			vf = new OggVorbis_File();
			if (vf == NULL) {
				throw game_runtime_error("Can't create ogg object for file: " + path, true);
			}

			ov_open(f, vf, NULL, 0);

			vorbis_info *vi = ov_info(vf, -1);
			if (vi == NULL) {
				throw game_runtime_error("Can't read ogg header info for file: " + path, true);
			}

			uint32 samples = static_cast<uint32>(ov_pcm_total(vf, -1));

			if (SystemFlags::getSystemSettingType(SystemFlags::debugSound).enabled) SystemFlags::OutputDebug(SystemFlags::debugSound, "In [%s::%s Line: %d] path = [%s] vi->version = %d, vi->channels = %d, vi->rate = %ld, vi->bitrate_upper = %ld, vi->bitrate_nominal = %ld, vi->bitrate_lower = %ld, vi->bitrate_window = %ld, samples = " SIZE_T_SPECIFIER "\n", __FILE__, __FUNCTION__, __LINE__, path.c_str(), vi->version, vi->channels, vi->rate, vi->bitrate_upper, vi->bitrate_nominal, vi->bitrate_lower, vi->bitrate_window, samples);

			soundInfo->setChannels(vi->channels);
			soundInfo->setsamplesPerSecond(vi->rate);
			soundInfo->setBitsPerSample(16);
			soundInfo->setSize(samples * 2 * vi->channels);
			soundInfo->setBitRate(vi->bitrate_nominal);
		}

		uint32 OggSoundFileLoader::read(int8 *samples, uint32 size) {
			int section;
			int totalBytesRead = 0;

			while (size > 0) {
				int bytesRead = ov_read(vf, reinterpret_cast<char*> (samples), size,
					0, 2, 1, &section);
				if (bytesRead == 0) {
					break;
				}
				size -= bytesRead;
				samples += bytesRead;
				totalBytesRead += bytesRead;
			}
			return totalBytesRead;
		}

		void OggSoundFileLoader::close() {
			if (vf != NULL) {
				ov_clear(vf);
				delete vf;
				vf = 0;
			}
			//fclose(f);
		}

		void OggSoundFileLoader::restart() {
			ov_raw_seek(vf, 0);
		}

		// =====================================================
		//	class SoundFileLoaderFactory
		// =====================================================

		SoundFileLoaderFactory::SoundFileLoaderFactory() {
			registerClass<WavSoundFileLoader>("wav");
			registerClass<OggSoundFileLoader>("ogg");
		}

		SoundFileLoaderFactory *SoundFileLoaderFactory::getInstance() {
			static SoundFileLoaderFactory soundFileLoaderFactory;
			return &soundFileLoaderFactory;
		}

	}
} //end namespace
