#include "portaudiocpp/System.hxx"

#include <cstddef>
#include <cassert>

#include "portaudiocpp/HostApi.hxx"
#include "portaudiocpp/Device.hxx"
#include "portaudiocpp/Stream.hxx"
#include "portaudiocpp/Exception.hxx"
#include "portaudiocpp/SystemHostApiIterator.hxx"
#include "portaudiocpp/SystemDeviceIterator.hxx"

namespace portaudio
{
	// -----------------------------------------------------------------------------------

	// Static members:
	System *System::instance_ = NULL;
	int System::initCount_ = 0;
	HostApi **System::hostApis_ = NULL;
	Device **System::devices_ = NULL;
	Device *System::nullDevice_ = NULL;

	// -----------------------------------------------------------------------------------

	int System::version()
	{
		return Pa_GetVersion();
	}

	const char *System::versionText()
	{
		return Pa_GetVersionText();
	}

	void System::initialize()
	{
		++initCount_;

		if (initCount_ == 1)
		{
			// Create singleton:
			assert(instance_ == NULL);
			instance_ = new System();

			// Initialize the PortAudio system:
			{
				PaError err = Pa_Initialize();

				if (err != paNoError)
					throw PaException(err);
			}

			// Create and populate device array:
			{
				int numDevices = instance().deviceCount();

				devices_ = new Device*[numDevices];

				for (int i = 0; i < numDevices; ++i)
					devices_[i] = new Device(i);
			}

			// Create and populate host api array:
			{
				int numHostApis = instance().hostApiCount();

				hostApis_ = new HostApi*[numHostApis];

				for (int i = 0; i < numHostApis; ++i)
					hostApis_[i] = new HostApi(i);
			}
			
			// Create null device:
			nullDevice_ = new Device(paNoDevice);
		}
	}

	void System::terminate()
	{
		PaError err = paNoError;

		if (initCount_ == 1)
		{
			// Destroy null device:
			delete nullDevice_;

			// Destroy host api array:
			{
				if (hostApis_ != NULL)
				{
					int numHostApis = instance().hostApiCount();

					for (int i = 0; i < numHostApis; ++i)
						delete hostApis_[i];

					delete[] hostApis_;
					hostApis_ = NULL;
				}
			}

			// Destroy device array:
			{
				if (devices_ != NULL)
				{
					int numDevices = instance().deviceCount();

					for (int i = 0; i < numDevices; ++i)
						delete devices_[i];

					delete[] devices_;
					devices_ = NULL;
				}
			}

			// Terminate the PortAudio system:
			assert(instance_ != NULL);
			err = Pa_Terminate();

			// Destroy singleton:
			delete instance_;
			instance_ = NULL;
		}

		if (initCount_ > 0)
			--initCount_;

		if (err != paNoError)
			throw PaException(err);
	}


	System &System::instance()
	{
		assert(exists());

		return *instance_;
	}

	bool System::exists()
	{
		return (instance_ != NULL);
	}

	// -----------------------------------------------------------------------------------

	System::HostApiIterator System::hostApisBegin()
	{
		System::HostApiIterator tmp;
		tmp.ptr_ = &hostApis_[0]; // begin (first element)
		return tmp;
	}

	System::HostApiIterator System::hostApisEnd()
	{
		int count = hostApiCount();

		System::HostApiIterator tmp;
		tmp.ptr_ = &hostApis_[count]; // end (one past last element)
		return tmp;
	}

	HostApi &System::defaultHostApi()
	{
		PaHostApiIndex defaultHostApi = Pa_GetDefaultHostApi();

		if (defaultHostApi < 0)
			throw PaException(defaultHostApi);

		return *hostApis_[defaultHostApi];
	}

	HostApi &System::hostApiByTypeId(PaHostApiTypeId type)
	{
		PaHostApiIndex index = Pa_HostApiTypeIdToHostApiIndex(type);

		if (index < 0)
			throw PaException(index);

		return *hostApis_[index];
	}

	HostApi &System::hostApiByIndex(PaHostApiIndex index)
	{
		if (index < 0 || index >= hostApiCount())
			throw PaException(paInternalError);

		return *hostApis_[index];
	}

	int System::hostApiCount()
	{
		PaHostApiIndex count = Pa_GetHostApiCount();

		if (count < 0)
			throw PaException(count);

		return count;
	}

	// -----------------------------------------------------------------------------------

	System::DeviceIterator System::devicesBegin()
	{
		DeviceIterator tmp;
		tmp.ptr_ = &devices_[0];

		return tmp;
	}

	System::DeviceIterator System::devicesEnd()
	{
		int count = deviceCount();

		DeviceIterator tmp;
		tmp.ptr_ = &devices_[count];

		return tmp;
	}

	//////
	/// Returns the System's default input Device, or the null Device if none 
	/// was available.
	//////
	Device &System::defaultInputDevice()
	{
		PaDeviceIndex index = Pa_GetDefaultInputDevice();
		return deviceByIndex(index);
	}

	//////
	/// Returns the System's default output Device, or the null Device if none 
	/// was available.
	//////
	Device &System::defaultOutputDevice()
	{
		PaDeviceIndex index = Pa_GetDefaultOutputDevice();
		return deviceByIndex(index);
	}

	//////
	/// Returns the Device for the given index.
	/// Will throw a paInternalError equivalent PaException if the given index 
	/// is out of range.
	//////
	Device &System::deviceByIndex(PaDeviceIndex index)
	{
		if (index < -1 || index >= deviceCount())
		{
			throw PaException(paInternalError);
		}

		if (index == -1)
			return System::instance().nullDevice();

		return *devices_[index];
	}

	int System::deviceCount()
	{
		PaDeviceIndex count = Pa_GetDeviceCount();

		if (count < 0)
			throw PaException(count);

		return count;
	}

	Device &System::nullDevice()
	{
		return *nullDevice_;
	}

	// -----------------------------------------------------------------------------------

	void System::sleep(long msec)
	{
		Pa_Sleep(msec);
	}

	int System::sizeOfSample(PaSampleFormat format)
	{
		PaError err = Pa_GetSampleSize(format);
		if (err < 0)
		{
			throw PaException(err);
			return 0;
		}

		return err;
	}

	// -----------------------------------------------------------------------------------

	System::System()
	{
		// (left blank intentionally)
	}

	System::~System()
	{
		// (left blank intentionally)
	}

	// -----------------------------------------------------------------------------------

} // namespace portaudio

