#pragma once
#include <any>
#include <google/protobuf/message.h>
#include <google/protobuf/any.h>
#include <mutex>
#include <atomic>
#include <stdint.h>
#include <future>
#include <thread>
#include <string>
#include <vector>
#include <memory>
#include "thirdparty/CRC.hpp"
#include "playback_backend.hpp"
#include "rpc.hpp"
#include <ipc/common.pb.h>
#include <ipc/internal.pb.h>
#include <log.hpp>
#include "options.hpp"
#include "util.hpp"
class PlaybackProcess;
class PlaybackProcessServiceImpl {
	Lock<PlaybackBackend*> cur_backend_lock;
    Lock<DynPtr*> render_ptr;
	public:
	PlaybackProcess *process;
	RenderResponseOrError Render(const RenderCommand *cmd);
	PropertyDataOrError Get(const GetProperty *request);
	MaybeError Set(const SetProperty *request);
	ResetResponse Reset(const ResetProperty *request);
	MaybeError Quit(const QuitCmd *request);
	MaybeError Init(const InitCommand *cmd);
	PropertyList GetPropertyList(const GetPropertyListCommand *cmd);
};
//#define DEBUG_PRINT_IPC
void print_ipc_message(const google::protobuf::Message &msg, size_t level = 0);
class PlaybackProcess  {
    friend class HostProcessImpl;
    friend class PlaybackProcessServiceImpl;
    void threadfunc();
    int send_fd;
    int recv_fd;
    bool multi_process = false;
    PlaybackProcess *other_process = nullptr;
    PlaybackProcessServiceImpl impl;
    int pid;
    bool init_failed = false;
    std::mutex start_mutex;
    std::mutex ipc_mutex;
    std::condition_variable started;
    bool is_playback_process = false;
    std::atomic_bool done;
    RPCResponse handle_command(RPCCall &msg);
    RPCResponse SendCommand(RPCCall *msg);
    std::string get_version_code();
    PropertyData get_property(PropertyId id, std::optional<uint64_t> idx = {});
    inline google::protobuf::Any get_property_value_any(PropertyId id, std::optional<uint64_t> idx = {}) {
        PropertyData data = get_property(id, idx);
        return data.value();
    }
    template<class T>
    inline T *get_property_value(PropertyId id, std::optional<uint64_t> idx = {}) {
        if constexpr (std::is_same_v<T, std::string>) {
            auto *property = get_property_value<StringProperty>(id, idx);
            std::string *tmp = new std::string(property->value());
            delete property;
            return tmp;
        } else {
            return resolve_any<T>(get_property_value_any(id, idx));
        }
    }
    inline double get_property_double(PropertyId id, std::optional<uint64_t> idx = {}) {
    	auto *property = get_property_value<DoubleProperty>(id, idx);
	    double output = double(property->value());
	    delete property;
	    return output;
    }
    inline std::string get_property_string(PropertyId id, std::optional<uint64_t> idx = {}) {
        auto *property = get_property_value<StringProperty>(id, idx);
        std::string output = std::string(property->value());
        delete property;
        return output;
    }
    void set_property(PropertyId id, PropertyData data, std::optional<uint64_t> idx = {});
    template<class T>
    inline void set_property_value(PropertyId id, T *value, std::optional<uint64_t> idx = {}) {
        PropertyData data;
        data.mutable_value()->PackFrom(*value);
        set_property(id, data, idx);
    }
    inline void set_property_double(PropertyId id, double value, std::optional<uint64_t> idx = {}) {
    	PropertyData data;
     	DoubleProperty property;
      	property.set_value(value);
       	data.mutable_value()->PackFrom(property);
        set_property(id, data, idx);
    }
    friend int looper_run_playback_process(std::vector<std::string> args);
    PlaybackProcess(PlaybackProcess *parent);
    PlaybackProcess(std::vector<std::string> args);
    void run_playback_process();
    public:
    bool process_running();
    double get_position();
    double get_length();
    void set_position(double pos);
    size_t get_stream_idx();
    void set_stream_idx(size_t idx);
    std::string get_title();
    std::string get_file_path();
    std::string get_file_name();
    std::string get_backend_id();
    double get_cached_rate();
    void set_rate(double value);
    std::string get_backend_name();
    PlaybackStream get_playback_stream(size_t idx);
    std::vector<PlaybackStream> get_playback_streams();
    AudioSpec *get_audio_spec();
    size_t render(void *buf, size_t maxlen);

    std::optional<google::protobuf::Any> get_property(std::string path);
    bool set_property(std::string path, google::protobuf::Any value);
    std::vector<Property> get_property_list();
    PlaybackProcess(std::string filename, int idx = 0);
    ~PlaybackProcess();
};