#pragma once
#include <exception>
#include <string>
#include <string.h>
#include <stddef.h>
#include <stdint.h>
#include <cmath>
#include <vector>
#include <stdarg.h>
#include <optional>
#include <mutex>
#include <cmath>
#include <atomic>
#include <stack>
#include <filesystem>
#include <queue>
#include <cassert>
#include <SDL.h>
#include "log.hpp"
#include <ipc/common.pb.h>
namespace fs = std::filesystem;
std::string PadZeros(std::string input, size_t required_length);
uint8_t TimeToComponentCount(double time_code);
std::string TimeToString(double time_code, uint8_t min_components = 1);
std::string get_prefs_path();
std::string hexdump(void *ptr, size_t len);
inline std::string hexdump(std::string data) {
    return hexdump(data.data(), data.length());
}
inline const char *vcformat(const char *format, va_list args) {
    va_list args_copy;
    va_copy(args_copy, args);
    char *buf;
    size_t buflen = 0;
    buflen = vsnprintf(NULL, 0, format, args_copy) + 1;
    va_end(args_copy);
    buf = (char*)malloc(buflen);
    if (buf == NULL) {
        return NULL;
    }
    memset(buf, 0, buflen);
    buflen = vsnprintf(buf, buflen, format, args) + 1;
    return buf;
}
inline const char *cformat(const char *format, ...) {
    va_list args;
    va_start(args, format);
    const char *output = vcformat(format, args);
    va_end(args);
    return output;
}
inline std::string to_string_with_decimals(double value, unsigned decimals) {
    std::string num_text;
    if (value == 0) {
        num_text = "0";
    } else {
        size_t buflen = snprintf(NULL, 0, "%f", value);
        buflen++;
        char *buf = (char*)malloc(buflen);
        memset(buf, 0, buflen);
        snprintf(buf, buflen - 1, "%f", value);
        num_text = std::string(buf);
        free(buf);
    }
    int found = num_text.find(".");
    if (found == -1) {
        found = num_text.size();
        num_text.append(".0");
    }
    if (num_text[0] == '.') {
        num_text = "0" + num_text;
        found++;
    }
    if (decimals == 0) {
        num_text = num_text.substr(0, found);
        return num_text;
    }
    int chars_after_decimal = num_text.size() - found - 1;
    if (chars_after_decimal > decimals) {
        num_text = num_text.substr(0, found + decimals + 1);
    } else {
        for (int i = chars_after_decimal; i < decimals; i++) {
            num_text.push_back('0');
        }
    }
    return num_text;
}
inline size_t combine_hashes(std::initializer_list<size_t> hashes) {
    std::string values = "^";
    values += hashes.size();
    values += "@";
    for (auto value : values) {
        values += value;
        values += ";";
    }
    return std::hash<std::string>()(values);
}
inline bool is_zeroes(void *ptr, size_t len) {
	uint8_t *ptr8 = (uint8_t*)ptr;
	for (size_t i = 0; i < len; i++) {
		if (ptr8[i] != 0) {
			return false;
		}
	}
	return true;
}
inline void replace_all(std::string &input, std::string replace, std::string value) {
    size_t pos = 0;
    while (true) {
        pos = input.find(replace, pos);
        if (pos >= input.length()) break;
        input.replace(pos, pos + replace.length(), value);
        pos += value.length() - replace.length();
    }
}
fs::path get_base_dir();
int launch(std::vector<std::string> args);
int launch_self(std::string process_type, std::vector<std::string> extra_args);
#ifdef MIN
#undef MIN
#endif
#define MIN(x, y) ((x < y) ? x : y)
#ifdef MAX
#undef MAX
#endif
#define MAX(x, y) ((x > y) ? x : y)
#ifdef ABS
#undef ABS
#endif
#define ABS(x) ((x < 0) ? -x : x)
template<class T>
class Lock;
template<class T>
class LockAccessor {
	friend class Lock<T>;
	Lock<T> *lock;
	bool locked = true;
	LockAccessor(Lock<T> *lock) {
		this->lock = lock;
	}
	public:
	/// @brief Gets the value of the lock.
	/// @returns The value of the lock
	T get() {
		if (has_value()) {
			return *lock->value;
		} else {
			throw std::exception();
		}
	}
	/// @brief Sets the value of the lock, deinitializing the previous value in the process.
	/// @param value The new value of the lock
	/// @param owned Set to true to have the lock "own" the value. If true, the lock will deinitialize the value.
	void set(T value, bool owned = false) {
		lock->clear_value();
		lock->value = new T(value);
		lock->owned = owned;
	}
	T &operator *() {
		if (has_value()) {
			return *lock->value;
		} else {
			throw std::exception();
		}
	}
	T *operator->() {
		if (has_value()) {
			return lock->value;
		} else {
			throw std::exception();
		}
	}
	void clear() {
		lock->clear_value();
	}
	bool has_value() {
		return lock->has_value();
	}
	void unlock() {
		if (locked) {
			lock->mutex.unlock();
			locked = false;
		}
	}
	~LockAccessor() {
		unlock();
	}
};
template<class T>
class LockAccessor<T*> {
	friend class Lock<T*>;
	Lock<T*> *lock;
	std::atomic_bool locked = true;
	LockAccessor(Lock<T*> *lock) {
		this->lock = lock;
	}
	public:
	T &operator *() {
		if (has_value()) {
			return **lock->value;
		} else {
			throw std::exception();
		}
	}
	/// @brief Gets the value of the lock.
	/// @returns The value of the lock
	T *get() {
		if (has_value()) {
			return *lock->value;
		} else {
			throw std::exception();
		}
	}
	/// @brief Sets the value of the lock, deinitializing the previous value in the process.
	/// @param value The new value of the lock
	/// @param owned Set to true to have the lock "own" the value. If true, the lock will deinitialize the value.
	void set(T *value, bool owned = false) {
		lock->clear_value();
		lock->value = new T*(value);
		lock->owned = owned;
	}
	T *operator->() {
		if (has_value()) {
			return *lock->value;
		} else{
			throw std::exception();
		}
	}
	void clear() {
		lock->clear_value();
	}
	void unlock() {
		if (locked.exchange(false)) {
			lock->mutex->unlock();
		}
	}
	bool has_value() {
		return lock->has_value();
	}
	~LockAccessor() {
		unlock();
	}
};
template<class T>
class Lock {
	friend class LockAccessor<T>;
	T *value;
	std::recursive_mutex *mutex;
	bool owned = false;
	void clear_value() {
		if (owned && this->value != nullptr) {
			if constexpr (std::is_pointer_v<T>) {
				delete *this->value;
			}
			delete this->value;
		}
		this->value = nullptr;
	}
	public:
	bool has_value() {
		return this->value != nullptr;
	}
	T &get_unsafe() {
		if (has_value()) {
			return *value;
		} else {
			throw std::exception();
		}
	}
	LockAccessor<T> lock() {
		mutex->lock();
		return LockAccessor(this);
	}
	std::optional<LockAccessor<T>> try_lock(){
		if (mutex->try_lock()) {
			return LockAccessor(this);
		} else {
			return {};
		}
	}
	/// @brief Constructs a lock on a value. Is owner is true, the value will be owned by the lock.
	/// @param value The value to use with the lock
	/// @param owner Set to true to have the lock delete the value on deinitialization
	Lock(T value, bool owned = false)
	 : owned(owned)
	{
		this->value = new T(value);
		mutex = new std::recursive_mutex();
	}
	Lock()
	 : value(nullptr)
	 , owned(false)
	{
		mutex = new std::recursive_mutex();
	}
	Lock<T> &operator=(Lock<T> lock) = delete;
	~Lock() {
		mutex->lock();
		clear_value();
		mutex->unlock();
		delete this->mutex;
	}
};

class DynPtr {
    public:
    void *ptr;
    private:
    size_t ptr_len;
    size_t adjust_size(size_t len) {
        static const size_t chunk = 1024;
        size_t value = len;
        value /= chunk;
        if (len % chunk != 0) {
            value += 1;
        }
        value *= chunk;
        return value;
    }
    public:
    DynPtr() {
        this->ptr = NULL;
        this->ptr_len = 0;
    }
    void resize(size_t new_len) {
        new_len = adjust_size(new_len);
        if (this->ptr == NULL) {
            this->ptr = malloc(new_len);
            this->ptr_len = new_len;
        } else if (this->ptr_len != new_len) {
            this->ptr = realloc(this->ptr, new_len);
            this->ptr_len = new_len;
        }
        assert(this->ptr != NULL);
    }
    /// @brief Creates a DynPtr object and resizes it.
    /// @param len The minimum length of the pointer.
    DynPtr(size_t len) : DynPtr() {
        resize(len);
    }
    DynPtr(std::string input) : DynPtr(input.length()) {
    	memcpy(this->ptr, input.data(), input.length());
    }
    /// @brief Gets the pointer, but does not reallocate it.
    /// @param T The type of pointer to return. May be void to return void*.
    /// @returns The pointer, owned by the DynPtr object.
    template<class T>
    T *get() {
        assert(ptr != NULL);
        if constexpr (std::is_same_v<T, void>) {
            return ptr;
        } else {
            return (T*)ptr;
        }
    }
    /// @brief Reallocates the pointer, and returns it.
    /// @param len The amount of bytes for the pointer
    /// @param T The type of pointer to return. May be void to return void*
    /// @returns The reallocated pointer, owned by the DynPtr object.
    template<class T>
    T *get_byte_sized(size_t len) {
        resize(len);
        return get<T>();
    }
    /// @brief Reallocates the pointer, and returns it.
    /// @param len The amount of items the pointer should make room for. For void pointers, this is actually the amount of bytes.
    /// @param T The type of pointer to return, and to use in the sizeof call when not void.
    /// @returns The reallocated pointer, owned by the DynPtr object.
    template<class T>
    T *get_item_sized(size_t len) {
        if constexpr (std::is_same_v<T, void>) {
            return get_byte_sized<T>(len);
        } else {
            return get_byte_sized<T>(len * sizeof(T));
        }
    }
};

template <typename T>
class Fifo {
private:
    std::vector<T> buffer;
    size_t head = 0;
    size_t tail = 0;
    size_t count = 0;

public:
    Fifo(size_t initial_capacity = 16) : buffer(initial_capacity) {}

    void resize(size_t new_capacity) {
        std::vector<T> new_buffer(new_capacity);
        size_t new_count = std::min(count, new_capacity);

        for (size_t i = 0; i < new_count; ++i) {
            new_buffer[i] = buffer[(head + i) % buffer.size()];
        }

        buffer = std::move(new_buffer);
        head = 0;
        tail = new_count;
        count = new_count;
    }

    size_t size() const {
        return count;
    }

    size_t capacity() const {
        return buffer.size();
    }
    void push(const T& value) {
        if (count == buffer.size()) {
            resize(buffer.size() * 2);
        }
        buffer[tail] = value;
        tail = (tail + 1) % buffer.size();
        ++count;
    }

    void push(const T* values, size_t length) {
        if (count + length > buffer.size()) {
            resize(std::max(buffer.size() * 2, count + length));
        }

        for (size_t i = 0; i < length; ++i) {
            buffer[tail] = values[i];
            tail = (tail + 1) % buffer.size();
            ++count;
        }
    }

    T pop() {
        if (count == 0) {
            throw std::out_of_range("FIFO is empty");
        }
        T value = buffer[head];
        head = (head + 1) % buffer.size();
        --count;
        return value;
    }

    size_t pop(T* output, size_t max_length) {
        size_t popped = std::min(count, max_length);
        for (size_t i = 0; i < popped; ++i) {
            output[i] = buffer[head];
            head = (head + 1) % buffer.size();
        }
        count -= popped;
        return popped;
    }

    T* reserve(size_t length) {
        if (count + length > buffer.size()) {
            resize(std::max(buffer.size() * 2, count + length));
        }

        T* start = &buffer[tail];
        tail = (tail + length) % buffer.size();
        count += length;

        return start;
    }

    bool empty() const {
        return count == 0;
    }
    void clear() {
        count = 0;
        head = 0;
        tail = 0;
    }
    void shrink() {
        size_t offs = size() - capacity();
        if (offs != 0) {
            size_t old_head = head;
            size_t old_tail = tail;
            size_t new_head = head - offs;
            size_t new_tail = tail - offs;
            size_t start = std::min(head, tail);
            size_t end = std::max(head, tail);
            size_t len = end - start;
            for (size_t i = 0; i < len; i++) {
                size_t j = i + start;
                buffer[i] = buffer[j];
            }
            head = new_head;
            tail = new_tail;
        }
        buffer.resize(size());
    }
};
void blocking_write(int fd, const void *buf, const size_t len);
void blocking_read(int fd, void *buf, const size_t len);
int NotSDL_ConvertAudioSamples(const SDL_AudioSpec *src_spec, const Uint8 *src_data, int src_len,
                            const SDL_AudioSpec *dst_spec, Uint8 **dst_data, int *dst_len);

template<class T>
inline T *resolve_any(google::protobuf::Any value) {
    T *output = new T();
    value.UnpackTo(output);
    return output;
}
template<class T>
inline T resolve_value(google::protobuf::Any value) {
	throw std::exception();
}
template<>
inline bool resolve_value<bool>(google::protobuf::Any value) {
	BooleanProperty *property = resolve_any<BooleanProperty>(value);
	bool output = property->value();
	delete property;
	return output;
}
template<>
inline std::string resolve_value<std::string>(google::protobuf::Any value) {
	StringProperty *property = resolve_any<StringProperty>(value);
	std::string output = property->value();
	delete property;
	return output;
}
template<>
inline int resolve_value<int>(google::protobuf::Any value) {
	IntProperty *property = resolve_any<IntProperty>(value);
	int output = property->value();
	delete property;
	return output;
}
template<>
inline DynPtr resolve_value<DynPtr>(google::protobuf::Any value) {
	BytesProperty *property = resolve_any<BytesProperty>(value);
	std::string output = property->value();
	delete property;
	return DynPtr(output);
}

template<>
inline double resolve_value<double>(google::protobuf::Any value) {
	DoubleProperty *property = resolve_any<DoubleProperty>(value);
	double output = property->value();
	delete property;
	return output;
}
template<class T>
inline google::protobuf::Any value_to_any(T value) {
	google::protobuf::Any output;
	output.PackFrom(value);
	return output;
}
template<>
inline google::protobuf::Any value_to_any<double>(double value) {
	DoubleProperty output;
	output.set_value(value);
	return value_to_any<DoubleProperty>(output);
}

template<>
inline google::protobuf::Any value_to_any<bool>(bool value) {
	BooleanProperty output;
	output.set_value(value);
	return value_to_any<BooleanProperty>(output);
}

template<>
inline google::protobuf::Any value_to_any<std::string>(std::string value) {
	StringProperty output;
	output.set_value(value);
	return value_to_any<StringProperty>(output);
}
PropertyHint make_hint(double min, double max);
Property make_property(PropertyType type, std::string name, PropertyId id, std::optional<PropertyHint> hint);
Property make_property(PropertyType type, std::string name, std::string path, std::optional<PropertyHint> hint = {});
Property make_property(PropertyType type, std::string name, PropertyId id = PropertyId::BackendSpecific, std::optional<std::string> path = {}, std::optional<PropertyHint> hint = {});

class LooperLogScaler {
    double la;
    double lb;
    public:
    double x0;
    double x1;
    void update_min_max(double min, double max);
    LooperLogScaler(double min, double max);
    double scale_log(double value);
    double unscale_log(double value);
};