#ifndef PGSQL_VALUE_H
#define PGSQL_VALUE_H

#include "Pgsql_declare.h"
#include "Pgsql_oids.h"
#include "ArrayParser.h"
#include <sstream>
#include <vector>
#include <QString>
#include <QDateTime>
#include <optional>

namespace Pgsql {


	template <typename E>
	E StringToArrayElem(std::string_view sv);

	/** \brief Class that is returned as value of a cell to facilitate auto conversion.
	 */
	class Value {
	public:
		const char *empty_str = "";
		Value(const char *val, Oid typ);
		QString asQString() const;

		bool null() const { return m_val == nullptr; }
		const char* c_str() const { return m_val == nullptr ? empty_str : m_val; }

		operator QString() const;
		operator QDateTime() const;
		operator QDate() const;
		operator QTime() const;
		operator std::string() const;
		operator int16_t() const;
		operator int32_t() const;
		operator Oid() const;
		operator int64_t() const;
		operator bool() const;
		operator float() const;
		operator double() const;
		operator char() const;

		bool isString() const;




		/// Retrieves an array type value and passes them to an insert iterator.
		///
		/// When the array it self is NULL this function behaves the same as for an empty array. No
		/// elements are given to the insert iterator and the function returns. Use the null() function
		/// to check for NULL values.
		///
		/// \param insert_iter An insert_iterator which is used to add the elements of the array to a container.
		/// \param nullhandling This only affects how NULL elements within the array are handled.
		template <typename E, typename I>
		void getAsArray(I insert_iter, NullHandling nullhandling = NullHandling::Throw) const
		{
			if (m_val == nullptr) {
				return;
			}
			else {
				using value_type = E;
				ArrayParser parser(m_val, -1);
				for (;;) {
					auto res = parser.GetNextElem();
					if (res.ok) {
						if (res.value) {
							insert_iter = StringToArrayElem<E>(*res.value);
						}
						else {
							if (nullhandling == NullHandling::Throw)
								throw std::runtime_error("Unexpected NULL value in array");
						}
					}
					else
						break;
				}
			}
		}

		/// Retrieves an array typed value, and passes the element to the insert iterator
		///
		/// \param value_for_nulls Each time it encounters a NULL element in the array it
		///    will instead pass this value to the insert iterator.
		template <typename E, typename I>
		void getAsArray(I insert_iter, const E &value_for_nulls) const
		{
			if (m_val == nullptr) return;

			using value_type = E;
			ArrayParser parser(m_val, -1);
			for (;;) {
				auto res = parser.GetNextElem();
				if (res.ok) {
					if (res.value) {
						insert_iter = StringToArrayElem<E>(*res.value);
					}
					else {
						insert_iter = value_for_nulls;
					}
				}
				else
					break;
			}
		}

		/// Retrieves an array typed value, and passes the elements to the insert iterator
		template <typename E, typename I>
		void getAsArrayOfOptional(I insert_iter) const
		{
			if (m_val == nullptr) return;

			using value_type = E;
			ArrayParser parser(m_val, -1);
			for (;;) {
				auto res = parser.GetNextElem();
				if (res.ok) {
					if (res.value) {
						insert_iter = StringToArrayElem<E>(*res.value);
					}
					else {
						insert_iter = std::nullopt;
					}
				}
				else
					break;
			}
		}

		/// De catalog uses vector types which are similar to array
		/// but uses spaces as seperators. AFAIK there are only integral
		/// vector types so this implementation is only tested for those
		template <typename E, typename I>
		void getAsVector(I insert_iter) const
		{
			if (m_val == nullptr || *m_val == '\0') return;

			const char * pos = m_val;
			const char * start = pos;
			for (;;) {
				while (*pos != 0 && *pos != ' ') ++pos;
				// The cast (to prevent warning) should be save as start should always be <= pos
				insert_iter = StringToArrayElem<E>(std::string_view(start, static_cast<size_t>(pos-start)));
				if (*pos == 0)
					break;
				start = ++pos; // skip space and set new start to match
			}
		}

		inline Oid getOid() const { return m_typ; }
	private:
		const char *m_val;
		Oid m_typ;
	};

	template <typename T>
	void operator<<(std::optional<T> &s, const Value &v)
	{
		if (v.null())
			s = std::optional<T>();
		else
			s = v.operator T();
	}

	template <typename T>
	void operator<<(std::vector<T> &s, const Value &v)
	{
		v.getAsArray(std::back_inserter(s));
	}

	template <typename T>
	void operator<<(T &s, const Value &v)
	{
		s = v.operator T();
	}
	
	template <typename E>
	E StringToArrayElem(std::string_view sv)
	{
		std::string str(sv.data(), sv.length());
		Value val(str.c_str(), OidFor<E>::elem());
		E v;
		v << val;
		return v;
	}

} // end namespace Pgsql

#endif // PGSQL_VALUE_H
