//
// libtgvoip is free and unencumbered public domain software.
// For more information, see http://unlicense.org or the UNLICENSE file
// you should have received with this source code distribution.
//

#ifndef LIBTGVOIP_BUFFERINPUTSTREAM_H
#define LIBTGVOIP_BUFFERINPUTSTREAM_H

#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <stdexcept>
#include <array>
#include <limits>
#include <stddef.h>
#include "threading.h"
#include "utils.h"

namespace tgvoip{
	class Buffer;

	class BufferInputStream{

	public:
		BufferInputStream(const unsigned char* data, size_t length);
		BufferInputStream(const Buffer& buffer);
		~BufferInputStream();
		void Seek(size_t offset);
		size_t GetLength();
		size_t GetOffset();
		size_t Remaining();
		unsigned char ReadByte();
		int64_t ReadInt64();
		int32_t ReadInt32();
		int16_t ReadInt16();
		int32_t ReadTlLength();
		void ReadBytes(unsigned char* to, size_t count);
		void ReadBytes(Buffer& to);
		BufferInputStream GetPartBuffer(size_t length, bool advance);

	private:
		void EnsureEnoughRemaining(size_t need);
		const unsigned char* buffer;
		size_t length;
		size_t offset;
	};

	class BufferOutputStream{
	friend class Buffer;
	public:
		TGVOIP_DISALLOW_COPY_AND_ASSIGN(BufferOutputStream);
		BufferOutputStream(size_t size);
		BufferOutputStream(unsigned char* buffer, size_t size);
		~BufferOutputStream();
		void WriteByte(unsigned char byte);
		void WriteInt64(int64_t i);
		void WriteInt32(int32_t i);
		void WriteInt16(int16_t i);
		void WriteBytes(const unsigned char* bytes, size_t count);
		void WriteBytes(const Buffer& buffer);
		void WriteBytes(const Buffer& buffer, size_t offset, size_t count);
		unsigned char* GetBuffer();
		size_t GetLength();
		void Reset();
		void Rewind(size_t numBytes);
		
		BufferOutputStream& operator=(BufferOutputStream&& other){
			if(this!=&other){
				if(!bufferProvided && buffer)
					free(buffer);
				buffer=other.buffer;
				offset=other.offset;
				size=other.size;
				bufferProvided=other.bufferProvided;
				other.buffer=NULL;
			}
			return *this;
		}

	private:
		void ExpandBufferIfNeeded(size_t need);
		unsigned char* buffer=NULL;
		size_t size;
		size_t offset;
		bool bufferProvided;
	};

	class BufferPool{
	public:
		TGVOIP_DISALLOW_COPY_AND_ASSIGN(BufferPool);
		BufferPool(unsigned int size, unsigned int count);
		~BufferPool();
		unsigned char* Get();
		void Reuse(unsigned char* buffer);
		size_t GetSingleBufferSize();
		size_t GetBufferCount();

	private:
		uint64_t usedBuffers;
		int bufferCount;
		size_t size;
		unsigned char* buffers[64];
		Mutex mutex;
	};

	class Buffer{
	public:
		Buffer(size_t capacity){
			if(capacity>0)
				data=(unsigned char *) malloc(capacity);
			else
				data=NULL;
			length=capacity;
		};
		TGVOIP_DISALLOW_COPY_AND_ASSIGN(Buffer); // use Buffer::CopyOf to copy contents explicitly
		Buffer(Buffer&& other) noexcept {
			data=other.data;
			length=other.length;
			other.data=NULL;
		};
		Buffer(BufferOutputStream&& stream){
			data=stream.buffer;
			length=stream.offset;
			stream.buffer=NULL;
		}
		Buffer(){
			data=NULL;
			length=0;
		}
		~Buffer(){
			if(data)
				free(data);
			data=NULL;
		};
		Buffer& operator=(Buffer&& other){
			if(this!=&other){
				if(data)
					free(data);
				data=other.data;
				length=other.length;
				other.data=NULL;
			}
			return *this;
		}
		unsigned char& operator[](size_t i){
			if(i>=length)
				throw std::out_of_range("");
			return data[i];
		}
		const unsigned char& operator[](size_t i) const{
			if(i>=length)
				throw std::out_of_range("");
			return data[i];
		}
		unsigned char* operator*(){
			return data;
		}
		const unsigned char* operator*() const{
			return data;
		}
		void CopyFrom(const Buffer& other, size_t count, size_t srcOffset=0, size_t dstOffset=0){
			if(!other.data)
				throw std::invalid_argument("CopyFrom can't copy from NULL");
			if(other.length<srcOffset+count || length<dstOffset+count)
				throw std::out_of_range("Out of offset+count bounds of either buffer");
			memcpy(data+dstOffset, other.data+srcOffset, count);
		}
		void CopyFrom(const void* ptr, size_t dstOffset, size_t count){
			if(length<dstOffset+count)
				throw std::out_of_range("Offset+count is out of bounds");
			memcpy(data+dstOffset, ptr, count);
		}
		void Resize(size_t newSize){
			data=(unsigned char *) realloc(data, newSize);
			length=newSize;
		}
		size_t Length() const{
			return length;
		}
		bool IsEmpty() const{
			return length==0;
		}
		static Buffer CopyOf(const Buffer& other){
			Buffer buf(other.length);
			buf.CopyFrom(other, other.length);
			return buf;
		}
	private:
		unsigned char* data;
		size_t length;
	};

	template <typename T, size_t size, typename AVG_T=T> class HistoricBuffer{
	public:
		HistoricBuffer(){
			std::fill(data.begin(), data.end(), (T)0);
		}

		AVG_T Average(){
			AVG_T avg=(AVG_T)0;
			for(T& i:data){
				avg+=i;
			}
			return avg/(AVG_T)size;
		}

		AVG_T Average(size_t firstN){
			AVG_T avg=(AVG_T)0;
			for(size_t i=0;i<firstN;i++){
				avg+=(*this)[i];
			}
			return avg/(AVG_T)firstN;
		}

		AVG_T NonZeroAverage(){
			AVG_T avg=(AVG_T)0;
			int nonZeroCount=0;
			for(T& i:data){
				if(i!=0){
					nonZeroCount++;
					avg+=i;
				}
			}
			if(nonZeroCount==0)
				return (AVG_T)0;
			return avg/(AVG_T)nonZeroCount;
		}

		void Add(T el){
			data[offset]=el;
			offset=(offset+1)%size;
		}

		T Min(){
			T min=std::numeric_limits<T>::max();
			for(T& i:data){
				if(i<min)
					min=i;
			}
			return min;
		}

		T Max(){
			T max=std::numeric_limits<T>::min();
			for(T& i:data){
				if(i>max)
					max=i;
			}
			return max;
		}

		void Reset(){
			std::fill(data.begin(), data.end(), (T)0);
			offset=0;
		}

		T& operator[](size_t i){
			assert(i<size);
			// [0] should return the most recent entry, [1] the one before it, and so on
			ptrdiff_t _i=offset-i-1;
			if(_i<0)
				_i=size+_i;
			return data[_i];
		}
	private:
		std::array<T, size> data;
		ptrdiff_t offset=0;
	};
}

#endif //LIBTGVOIP_BUFFERINPUTSTREAM_H
