#pragma once

#include "ehs/Types.h"
#include "ehs/DataType.h"
#include "ehs/Str.h"
#include "ehs/Serializer.h"
#include "ehs/Vector.h"
#include "ehs/Array.h"
#include "ehs/io/Resource.h"
#include "AudioCodec.h"

namespace ehs
{
    class EHS_LIB_IO Audio : public Resource
    {
	private:
		static Array<AudioCodec> codecs;
		UInt_64 sampleRate;
		DataType dataType;
		UInt_8 byteDepth;
		UInt_8 channels;
		UInt_64 frames;
		float length;
		Byte* data;
		Byte* peak;

	public:
		static bool HasCodec(UInt_64 hashExt);

		static bool HasCodec(const Str_8& ext);

		static bool AddCodec(AudioCodec codec);

		static const AudioCodec* GetCodec(UInt_64 hashExt);

		static const AudioCodec* GetCodec(const Str_8& ext);

		~Audio() override;

		Audio();

		Audio(const Str_8& filePath);

		Audio(const Str_8& filePath, DataType type);

		Audio(Str_8 id, UInt_64 sampleRate, DataType dataType, UInt_8 channels, UInt_64 frames, const Byte* data);

		Audio(Str_8 id, UInt_64 sampleRate, DataType dataType, UInt_8 channels, const Serializer<UInt_64>& data);

		Audio(Str_8 id, UInt_64 sampleRate, DataType dataType, UInt_8 channels, const Vector<Byte>& data);

		Audio(Str_8 id, UInt_64 sampleRate, DataType dataType, UInt_8 channels, const Array<Byte>& data);

		Audio(Str_8 id, UInt_64 sampleRate, DataType dataType, UInt_8 channels, UInt_64 frames);

		Audio(Audio&& audio) noexcept;

		Audio(const Audio& audio);

		Audio& operator=(Audio&& audio) noexcept;

		Audio& operator=(const Audio& audio);

		operator const Byte*() const;

		operator Byte*();

		void Release() override;

		UInt_64 GetSampleRate() const;

		DataType GetDataType() const;

		UInt_8 GetByteDepth() const;

		UInt_8 GetBitDepth() const;

		UInt_8 GetChannels() const;

		UInt_64 GetFrameCount() const;

		UInt_64 GetSampleCount() const;

		UInt_64 GetSize() const;

		float GetLength() const;

		Array<Byte> FrameAsMono(UInt_64 frameIndex) const;

		Array<Byte> FrameAsStereo(UInt_64 frameIndex) const;

		Array<Byte> FrameAsFive_One(UInt_64 frameIndex) const;

		Array<Byte> FrameAsSeven_One(UInt_64 frameIndex) const;

		SInt_8 SampleAsSInt_8(UInt_64 sampleIndex) const;

		SInt_16 SampleAsSInt_16(UInt_64 sampleIndex) const;

		float SampleAsFloat(UInt_64 sampleIndex) const;

		SInt_32 SampleAsSInt_32(UInt_64 sampleIndex) const;

		SInt_64 SampleAsSInt_64(UInt_64 sampleIndex) const;

		SInt_8 PeakAsSInt_8() const;

		SInt_16 PeakAsSInt_16() const;

		float PeakAsFloat() const;

		SInt_32 PeakAsSInt_32() const;

		SInt_64 PeakAsSInt_64() const;

		void SetPeak(UInt_64 size, const Byte* newPeak);

		const Byte* GetPeak() const;

		void ToDataType(DataType newDataType);

		Audio GetAsDataType(DataType newDataType) const;

		void ToChannels(UInt_8 newChannels);

		Audio GetAsChannels(UInt_8 newChannels) const;

		bool Export(const Str_8& filePath) const;

	private:
		void ToMono(UInt_64 newFrameCount, Byte* newData, UInt_64 frameOffset) const;

		void Mono_to_Stereo(UInt_64 newFrameCount, Byte* newData, UInt_64 frameOffset) const;

		void Five_One_to_Stereo(UInt_64 newFrameCount, Byte* newData, UInt_64 frameOffset) const;

		void Seven_One_to_Stereo(UInt_64 newFrameCount, Byte* newData, UInt_64 frameOffset) const;

		void Mono_to_Five_One(UInt_64 newFrameCount, Byte* newData, UInt_64 frameOffset) const;

		void Stereo_to_Five_One(UInt_64 newFrameCount, Byte* newData, UInt_64 frameOffset) const;

		void Seven_One_to_Five_One(UInt_64 newFrameCount, Byte* newData, UInt_64 frameOffset) const;

		void Mono_to_Seven_One(UInt_64 newFrameCount, Byte* newData, UInt_64 frameOffset) const;

		void Stereo_to_Seven_One(UInt_64 newFrameCount, Byte* newData, UInt_64 frameOffset) const;

		void Five_One_to_Seven_One(UInt_64 newFrameCount, Byte* newData, UInt_64 frameOffset) const;

		// To SInt_8
		void SInt_16_to_SInt_8(Byte* newData, Byte* newPeak) const;

		void Float_to_SInt_8(Byte* newData, Byte* newPeak) const;

		void SInt_32_to_SInt_8(Byte* newData, Byte* newPeak) const;

		void SInt_64_to_SInt_8(Byte* newData, Byte* newPeak) const;

		// To SInt_16
		void SInt_8_to_SInt_16(Byte* newData, Byte* newPeak) const;

		void Float_to_SInt_16(Byte* newData, Byte* newPeak) const;

		void SInt_32_to_SInt_16(Byte* newData, Byte* newPeak) const;

		void SInt_64_to_SInt_16(Byte* newData, Byte* newPeak) const;

		// To Float
		void SInt_8_to_Float(Byte* newData, Byte* newPeak) const;

		void SInt_16_to_Float(Byte* newData, Byte* newPeak) const;

		void SInt_32_to_Float(Byte* newData, Byte* newPeak) const;

		void SInt_64_to_Float(Byte* newData, Byte* newPeak) const;

		// To SInt_32
		void SInt_8_to_SInt_32(Byte* newData, Byte* newPeak) const;

		void SInt_16_to_SInt_32(Byte* newData, Byte* newPeak) const;

		void Float_to_SInt_32(Byte* newData, Byte* newPeak) const;

		void SInt_64_to_SInt_32(Byte* newData, Byte* newPeak) const;

		// To SInt_64
		void SInt_8_to_SInt_64(Byte* newData, Byte* newPeak) const;

		void SInt_16_to_SInt_64(Byte* newData, Byte* newPeak) const;

		void Float_to_SInt_64(Byte* newData, Byte* newPeak) const;

		void SInt_32_to_SInt_64(Byte* newData, Byte* newPeak) const;
    };

	bool EncodeEHA(const ehs::AudioCodec* codec, ehs::Serializer<ehs::UInt_64>& out, const ehs::Audio* in);

	bool DecodeEHA(const ehs::AudioCodec* codec, ehs::Serializer<ehs::UInt_64>& in, ehs::Audio* out);

	bool DecodeWAV(const ehs::AudioCodec* codec, ehs::Serializer<ehs::UInt_64>& in, ehs::Audio* out);
}