//
// Created by imper on 10/10/21.
//

#ifndef XOR_CRYPT
#define XOR_CRYPT

#include <string>
#include <unistd.h>
#include <stdexcept>
#include "log-console"
#include <cstring>
#include <utility>
#include <sys/ioctl.h>
#include <sys/stat.h>

#define LOG_COLOR color::faint << color::yellow
#define ERR_COLOR color::red
#define PRINT_PREFIX _detail_::_log_ << log_console::l_lock << log_console::l_localtime << l_location

#define LOG (PRINT_PREFIX << LOG_COLOR)
#define ERR (PRINT_PREFIX << ERR_COLOR)

#define ENDENTLN color::reset << log_console::l_endent

namespace xc
{
	namespace pipe
	{
		enum : int
		{
			read = 0,
			write,
			size
		};
	}
	
	namespace _detail_ __attribute__((visibility("hidden")))
	{
		extern log_console _log_;
		extern log_console _err_;
	}
	
	typedef unsigned char byte;
	typedef unsigned long size_t;
	
	template <typename string_t>
	concept string_type = std::is_same_v<string_t, std::string> || std::is_same_v<string_t, std::wstring>;
	
	
	static inline void turn_logging(bool status = false)
	{
		xc::_detail_::_log_ << (status ? log_console::on : log_console::off);
	}
	
	static inline constexpr byte* copy_data_to(const void* data, byte* to, size_t size)
	{
		auto* tmp = static_cast<const byte*>(data);
		if (data == to) return to;
		if (!to || !data) return nullptr;
		
		for (size_t i = 0; i < size; ++i)
			to[i] = tmp[i];
		
		return to;
	}
	
	static inline constexpr byte* copy_data(const void* data, size_t size)
	{
		auto* res = size && data ? new byte[size] : nullptr;
		return copy_data_to(data, res, size);
	}
	
	
	class buffer
	{
	public:
		inline buffer() noexcept: m_mem(nullptr), m_capacity(0)
		{ }
		
		inline buffer(size_t size) noexcept: m_mem(new byte[size]), m_capacity(size)
		{ }
		
		inline buffer(void* buffer, size_t size) noexcept: m_mem(static_cast<byte*>(buffer)), m_capacity(size)
		{ }
		
		inline buffer(const buffer& another) noexcept: m_mem(copy_data(another.m_mem, another.m_capacity)), m_capacity(another.m_capacity)
		{ }
		
		inline buffer(buffer&& another) noexcept: m_mem(another.m_mem), m_capacity(another.m_capacity)
		{
			another.m_capacity = 0;
			another.m_mem = nullptr;
			LOG << "Pointer " << static_cast<void*>(m_mem) << color::reset << LOG_COLOR << " from buffer " << static_cast<void*>(&another)
				<< color::reset << LOG_COLOR << " moved to buffer " << static_cast<void*>(this) << color::reset << LOG_COLOR << "\nCapacity = "
				<< m_capacity << ENDENTLN;
		}
		
		[[nodiscard]] inline size_t capacity() const
		{ return m_capacity; }
		
		inline void realloc(size_t size)
		{
			LOG << "Deallocating buffer " << m_mem << color::reset << LOG_COLOR << " of size " << m_capacity << color::reset << LOG_COLOR
				<< "..." << ENDENTLN;
			delete[] m_mem;
			m_capacity = size;
			m_mem = new byte[size];
			LOG << "Allocated new buffer " << m_mem << color::reset << LOG_COLOR << " of size " << m_capacity << ENDENTLN;
		}
		
		inline void dealloc()
		{
			LOG << "Deallocating buffer " << m_mem << color::reset << LOG_COLOR << " of size " << m_capacity << color::reset << LOG_COLOR
				<< "..." << ENDENTLN;
			delete[] m_mem;
			LOG << "Deallocated buffer " << m_mem << ENDENTLN;
			m_capacity = 0;
			m_mem = nullptr;
		}
		
		[[nodiscard]] inline void* clone_mem() const
		{ return copy_data(m_mem, m_capacity); }
		
		inline size_t clone_mem(void* res, size_t size) const
		{
			size_t ret_size = std::min(size, m_capacity);
			copy_data_to(m_mem, static_cast<byte*>(res), ret_size);
			return ret_size;
		}
		
		[[nodiscard]] inline const void* get() const
		{ return m_mem; }
		
		[[nodiscard]] inline void* get()
		{ return m_mem; }
		
		inline void* abort_mem()
		{
			void* res = m_mem;
			LOG << "Released buffer " << m_mem << color::reset << LOG_COLOR << " of size " << m_capacity << ENDENTLN;
			m_capacity = 0;
			m_mem = nullptr;
			return res;
		}
		
		inline void from(const void* data, size_t size)
		{
			delete[] m_mem;
			m_capacity = size;
			m_mem = copy_data(data, size);
			LOG << "Created buffer " << m_mem << color::reset << LOG_COLOR << " of size " << m_capacity << color::reset << LOG_COLOR
				<< " from given " << data << ENDENTLN;
		}
		
		inline void take(void* data, size_t size)
		{
			delete[] m_mem;
			m_capacity = size;
			m_mem = static_cast<byte*>(data);
			LOG << "Captured buffer " << m_mem << color::reset << LOG_COLOR << " of size " << m_capacity << ENDENTLN;
		}
	
	private:
		byte* m_mem;
		size_t m_capacity;
	};
	
	
	class source
	{
	public:
		source(const source&) = delete;
		
		inline source(source&&) = default;
		
		inline operator bool() const
		{ return last_read > 0; }
		
		[[nodiscard]] inline virtual size_t available() = 0;
		
		inline virtual ssize_t read(void* data, size_t size)
		{
			ssize_t read = ::read(fd, data, size);
			last_read = read;
			if (read < 0)
			{
				char error[128];
				::strerror_r(errno, error, 128);
				ERR << "error " << errno << " in read() " << color::italic << error << ENDENTLN;
			}
			return read;
		}
		
		[[nodiscard]] inline ssize_t get_last_read() const
		{ return last_read; }
		
		inline void close()
		{
			if (fd > 0)
			{
				if (::close(fd) < 0)
				{
					char error[128];
					::strerror_r(errno, error, 128);
					ERR << "error " << errno << " in close() " << color::italic << error << ENDENTLN;
				}
				fd = -1;
			}
		}
		
		inline ~source() = default;
	
	protected:
		friend class xor_decrypt;
		
		ssize_t last_read = -1;
		int fd;
		
		explicit source(int fd) : fd(fd)
		{ }
	};
	
	
	class empty_source : public source
	{
	public:
		inline empty_source() : source(-1)
		{ }
		
		[[nodiscard]] inline size_t available() override
		{
			return 0;
		}
		
		inline ssize_t read(void*, size_t) override
		{
			last_read = -1;
			return -1;
		}
	};
	
	
	class file_source : public source
	{
	public:
		inline explicit file_source(FILE* file_ptr) : source(file_ptr->_fileno)
		{ }
		
		[[nodiscard]] inline size_t available() override
		{
			struct stat st{ };
			::fstat(fd, &st);
			return st.st_size;
		}
		
		inline ssize_t read(void* data, size_t size) override
		{ return source::read(data, size); }
	};
	
	
	class stream_source : public source
	{
	public:
		inline explicit stream_source(int stream_fd) : source(stream_fd)
		{ }
		
		inline explicit stream_source(FILE* stream_ptr) : stream_source(stream_ptr->_fileno)
		{ }
		
		[[nodiscard]] inline size_t available() override
		{
			long available;
			::ioctl(fd, FIONREAD, &available);
			if (available <= 0)
				::ioctl(fd, FIOQSIZE, &available);
			return available;
		}
		
		inline ssize_t read(void* data, size_t size) override
		{ return source::read(data, size); }
	};
	
	
	class buffer_source : public source
	{
	public:
		inline explicit buffer_source(buffer* buffer) : source(-1), buf(buffer)
		{ }
		
		[[nodiscard]] inline size_t available() override
		{ return buf->capacity(); }
		
		inline ssize_t read(void* data, size_t size) override
		{ return buf->clone_mem(data, size); }
	
	private:
		buffer* buf;
	};
	
	
	class destination
	{
	public:
		
		destination(const destination&) = delete;
		
		inline destination(destination&&) = default;
		
		inline operator bool() const
		{ return last_write > 0; }
		
		inline virtual ssize_t write(const void* data, size_t size)
		{
			ssize_t wrote = ::write(fd, data, size);
			last_write = wrote;
			if (wrote < 0)
			{
				char error[128];
				::strerror_r(errno, error, 128);
				ERR << "error " << errno << " in write() " << color::italic << error << ENDENTLN;
			}
			return wrote;
		}
		
		[[nodiscard]] inline ssize_t get_last_write() const
		{ return last_write; }
		
		inline void close()
		{
			if (fd > 0)
			{
				if (::close(fd) < 0)
				{
					char error[128];
					::strerror_r(errno, error, 128);
					ERR << "error " << errno << " in close() " << color::italic << error << ENDENTLN;
				}
				fd = -1;
			}
		}
		
		inline ~destination() = default;
	
	protected:
		friend class xor_encrypt;
		
		ssize_t last_write = -1;
		int fd;
		
		explicit destination(int fd) : fd(fd)
		{ }
	};
	
	
	class empty_destination : public destination
	{
	public:
		empty_destination() : destination(-1)
		{ }
		
		ssize_t write(const void*, size_t) override
		{
			last_write = -1;
			return -1;
		}
	};
	
	
	class file_destination : public destination
	{
	public:
		explicit file_destination(FILE* file_ptr) : destination(file_ptr->_fileno)
		{ }
		
		ssize_t write(const void* data, size_t size) override
		{ return destination::write(data, size); }
	};
	
	
	class stream_destination : public destination
	{
	public:
		explicit stream_destination(int stream_fd) : destination(stream_fd)
		{ }
		
		explicit stream_destination(FILE* stream_ptr) : stream_destination(stream_ptr->_fileno)
		{ }
		
		ssize_t write(const void* data, size_t size) override
		{ return destination::write(data, size); }
	};
	
	
	class buffer_destination : public destination
	{
	public:
		explicit buffer_destination(buffer* buffer) : destination(-1), buf(buffer)
		{ }
		
		ssize_t write(const void* data, size_t size) override
		{
			buf->take(const_cast<void*>(data), size);
			last_write = size;
			return size;
		}
	
	private:
		buffer* buf;
	};
	
	
	class xor_crypt
	{
	public:
		inline void set_password(const void* password, size_t size)
		{
			passwd = copy_data(password, size);
			passwd_size = size;
			LOG << "xor_crypt object " << static_cast<void*>(this) << color::reset << LOG_COLOR
				<< "'s password changed (check password manually with debugger)!" << ENDENTLN;
		}
		
		inline void reset_iter()
		{
			passwd_iter = 0;
			LOG << "xor_crypt object " << static_cast<void*>(this) << color::reset << LOG_COLOR << "'s password iterator reset!" << ENDENTLN;
		}
		
		inline void crypt(byte* data, size_t size)
		{
			LOG << "Crypting buffer " << static_cast<void*>(data) << color::reset << LOG_COLOR << " of size" << size << color::reset << LOG_COLOR
				<< ENDENTLN;
			for (byte* i = data; i < data + size; ++i)
			{
				*i ^= passwd[passwd_iter++];
				passwd_iter %= passwd_size;
			}
			LOG << "Crypted buffer " << static_cast<void*>(data) << color::reset << LOG_COLOR << ". Password iterator = " << passwd_iter
				<< ENDENTLN;
		}
	
	protected:
		inline xor_crypt(const void* password, size_t size) : passwd(copy_data(password, size)), passwd_size(size)
		{ }
		
		inline xor_crypt(const xor_crypt& xc) : passwd(copy_data(xc.passwd, xc.passwd_size)), passwd_size(xc.passwd_size),
												passwd_iter(xc.passwd_iter)
		{ }
		
		inline xor_crypt(xor_crypt&& xc) noexcept: passwd(xc.passwd), passwd_size(xc.passwd_size), passwd_iter(xc.passwd_iter)
		{
			xc.passwd = nullptr;
			xc.passwd_size = 0;
			xc.passwd_iter = 0;
		}
		
		byte* passwd;
		size_t passwd_size;
		size_t passwd_iter{ };
	};
	
	
	class xor_decrypt : public xor_crypt
	{
	public:
		inline xor_decrypt(const void* password, size_t size, source* source = nullptr)
				: xor_crypt(password, size), src(source)
		{ }
		
		inline explicit xor_decrypt(const std::string& password, source* source = nullptr)
				: xor_decrypt(password.c_str(), password.size(), source)
		{ }
		
		inline xor_decrypt(const xor_decrypt& xd) = delete;
		
		inline xor_decrypt(xor_decrypt&& xd) noexcept: xor_crypt(std::move(xd)), src(std::move(xd.src))
		{ }
		
		inline ssize_t read(void* data, size_t size)
		{
			ssize_t res = src->read(data, size);
			crypt(static_cast<byte*>(data), res);
			return res;
		}
		
		inline void set_source(source* source)
		{ src = std::unique_ptr<class source>(source); }
		
		[[nodiscard]] inline size_t available() const
		{ return src->available(); }
		
		void close_fd()
		{ src->close(); }
	
	private:
		std::unique_ptr<source> src;
	};
	
	class xor_encrypt : public xor_crypt
	{
	public:
		inline xor_encrypt(const void* password, size_t size, destination* destination = nullptr)
				: xor_crypt(password, size), dest(destination)
		{ }
		
		inline explicit xor_encrypt(const std::string& password, destination* destination = nullptr)
				: xor_encrypt(password.c_str(), password.size(), destination)
		{ }
		
		inline xor_encrypt(const xor_encrypt& xe) = delete;
		
		inline xor_encrypt(xor_encrypt&& xe) noexcept: xor_crypt(std::move(xe)), dest(std::move(xe.dest))
		{ }
		
		inline ssize_t write(const void* data, size_t size)
		{
			auto* tmp = copy_data(data, size);
			crypt(tmp, size);
			return dest->write(tmp, size);
		}
		
		inline void set_destination(destination* _dest)
		{ dest = std::unique_ptr<destination>(_dest); }
		
		inline void close_fd()
		{ dest->close(); }
	
	private:
		std::unique_ptr<destination> dest;
	};
	
	/// xor-decrypt declarations
	
	inline xor_decrypt& operator>>(buffer* source_buf, xor_decrypt& xd)
	{
		xd.set_source(new buffer_source(source_buf));
		return xd;
	}
	
	inline xor_decrypt& operator>>(FILE* source_file, xor_decrypt& xd)
	{
		xd.set_source(new file_source(source_file));
		return xd;
	}
	
	inline xor_decrypt& operator>>(int source_fd, xor_decrypt& xd)
	{
		xd.set_source(new stream_source(source_fd));
		return xd;
	}
	
	inline xor_decrypt& operator>>(xor_decrypt& xd, std::string& res)
	{
		size_t size = xd.available();
		res.resize(size);
		char* data = res.data();
		xd.read(data, size);
		return xd;
	}
	
	inline xor_decrypt& operator>>(xor_decrypt& xd, buffer* res)
	{
		size_t size = xd.available();
		res->realloc(size);
		xd.read(res->get(), size);
		return xd;
	}
	
	/// xor-encrypt declarations
	
	inline xor_encrypt& operator<<(buffer* dest_buf, xor_encrypt& xe)
	{
		xe.set_destination(new buffer_destination(dest_buf));
		return xe;
	}
	
	inline xor_encrypt& operator<<(FILE* dest_file, xor_encrypt& xe)
	{
		xe.set_destination(new file_destination(dest_file));
		return xe;
	}
	
	inline xor_encrypt& operator<<(int dest_fd, xor_encrypt& xe)
	{
		xe.set_destination(new stream_destination(dest_fd));
		return xe;
	}
	
	template <std::integral char_t>
	inline xor_encrypt& operator<<(xor_encrypt& xe, const std::basic_string<char_t>& data)
	{
		xe.write(data.c_str(), data.size() * sizeof(char_t));
		return xe;
	}
	
	template <std::integral char_t>
	inline xor_encrypt& operator<<(xor_encrypt& xe, const char_t* data)
	{
		xe.write(data, static_strlen(data) * sizeof(char_t));
		return xe;
	}
	
	inline xor_encrypt& operator<<(xor_encrypt& xe, const buffer* data)
	{
		xe.write(data->get(), data->capacity());
		return xe;
	}
}

#endif //XOR_CRYPT