#pragma once

#include <memory>
#include <functional>
#include <vector>

#if defined(_WIN32)
#define DISCORD_EXPORT __declspec(dllexport)
#pragma warning(disable : 4251)
#else
#define DISCORD_EXPORT __attribute__((visibility("default")))
#endif

// --------------------------------------------------------------------------------------
// forward declarations for all internal discord services
// --------------------------------------------------------------------------------------
namespace DiscordInternal {
class EventLoop;
class SocketClient;
class VoiceUser;
class ProcessObserver;
}

// --------------------------------------------------------------------------------------
// So Discord. Much Wow!
//
// This is the public API for using the native discord app services.
//
// This APIs exposed by this class are safe to use across multiple threads.
//
// All async callbacks will be invoked on the discord worker thread, so your
// code must take care to only do thread-safe things in those callbacks.
// --------------------------------------------------------------------------------------
class Discord {
public:
    DISCORD_EXPORT Discord();
    DISCORD_EXPORT ~Discord();

    // ------------------------------------------------------------
    // Server Lifecycle
    // ------------------------------------------------------------
    typedef std::function<void(bool isConnected,
                               const std::string& chosenProtocol,
                               unsigned int chosenPort,
                               const std::string& error)> ConnectToServerCallback;
    DISCORD_EXPORT void ConnectToServer(uint32_t ssrc,
                                        uint64_t localUserId,
                                        const std::string& address,
                                        int port,
                                        ConnectToServerCallback callback);
    DISCORD_EXPORT void DisconnectFromServer();

    // ------------------------------------------------------------
    // User Lifecycle
    // ------------------------------------------------------------
    DISCORD_EXPORT void CreateVoiceUser(uint32_t ssrc,
                                        uint64_t userId,
                                        bool isMuted = false,
                                        float volume = 1.0f);
    DISCORD_EXPORT void DestroyVoiceUser(uint64_t userId);

    // ------------------------------------------------------------
    // User Specific Playback
    // ------------------------------------------------------------
    DISCORD_EXPORT void SetUserPlayoutVolume(uint64_t userId, float volume);
    DISCORD_EXPORT void MuteUser(uint64_t userId, bool willBeMuted = true);
    typedef std::function<void(uint64_t userId, bool isUserSpeakingNow)>
      UserSpeakingStatusChangedCallback;
    DISCORD_EXPORT void SetUserSpeakingStatusChangedCallback(
      UserSpeakingStatusChangedCallback const& callback);

    // ------------------------------------------------------------
    // Local User Only Mutation
    // ------------------------------------------------------------
    uint64_t GetLocalUserId();
    DISCORD_EXPORT void DeafenLocalUser(bool willBeDeafened);
    DISCORD_EXPORT void MuteLocalUser(bool willBeMuted);
    typedef std::function<void(float voiceDb)> LocalVoiceLevelChangedCallback;
    DISCORD_EXPORT void SetLocalVoiceLevelChangedCallback(
      LocalVoiceLevelChangedCallback const& callback);

    DISCORD_EXPORT void GetIdleMilliseconds(std::function<void(unsigned int)> callback);

    // ------------------------------------------------------------
    // Input event registry
    // ------------------------------------------------------------
    enum InputEventFlags : uint32_t {
        IEF_RELEASED = 1 << 0,          // want callback on button release
        IEF_PRESSED = 1 << 1,           // want callback on button press
        IEF_ONLY_IN_FOCUS = 1 << 2,     // only when window is focused
        IEF_ONLY_NOT_IN_FOCUS = 1 << 3, // only when window is not focused
        IEF_ALL = 0xffffffff
    };
    typedef std::pair<int /* deviceType */, int /* scanCode */> ScanCodeCombo;
    typedef std::vector<ScanCodeCombo> ScanCodeCombos;
    typedef std::function<void(const ScanCodeCombos& buttons, bool isPressed, void* userData)>
      InputEventCallback;
    typedef std::vector<std::pair<int, ScanCodeCombos>> InputEventList;
    typedef std::function<void(const InputEventList& registeredEvents)> InputEventListingCallback;
    typedef void (*OnSystemInputEvent)(int deviceType, int buttonCode, bool isButtonDown);
    DISCORD_EXPORT void RegisterInputEvent(int eventId,
                                           ScanCodeCombos buttons,
                                           uint32_t inputEventFlagsToWatch,
                                           InputEventCallback callback,
                                           void* userData);
    DISCORD_EXPORT void UnregisterInputEvent(int eventId);
    DISCORD_EXPORT void UnregisterAllInputEvents();
    DISCORD_EXPORT void GetRegisteredInputEvents(InputEventListingCallback callback);
    DISCORD_EXPORT void SetInputEventCallback(OnSystemInputEvent onInputEvent);
    DISCORD_EXPORT void SetHostApplicationFocused(bool focused);

    // ------------------------------------------------------------
    // Audio Settings
    // mode == -1 to disable
    // ------------------------------------------------------------
    DISCORD_EXPORT void EnableBuiltInAEC(bool willBeEnabled);
    DISCORD_EXPORT void SetEchoCancellation(bool, int /*webrtc::EcModes*/ mode);
    DISCORD_EXPORT void SetNoiseSuppression(bool, int /*webrtc::NsModes*/ mode);
    DISCORD_EXPORT void SetAutomaticGainControl(bool, int /*webrtc::AgcModes*/ mode);

    // ------------------------------------------------------------
    // Audio Playback
    // ------------------------------------------------------------
    DISCORD_EXPORT void PlayLocalSound(const std::string& fileName, float volume);

    // ------------------------------------------------------------
    // Audio Devices
    // ------------------------------------------------------------
    struct DISCORD_EXPORT DeviceDescription {
        char name[128];
        char guid[128];

        DeviceDescription()
        {
            name[0] = '\0';
            guid[0] = '\0';
        }

        bool operator==(DeviceDescription const& rhs) const
        {
            return (0 == strncmp(name, rhs.name, 128)) && (0 == strncmp(guid, rhs.guid, 128));
        }
        bool operator!=(DeviceDescription const& rhs) const { return !((*this) == rhs); }
    };

    typedef std::vector<DeviceDescription> AudioDeviceList;
    typedef std::function<void(AudioDeviceList const& inputDevices,
                               AudioDeviceList const& outputDevices)> DeviceChangeCallback;

    DISCORD_EXPORT void SetDeviceChangeCallback(DeviceChangeCallback const& callback);

    typedef std::function<void(const std::vector<DeviceDescription>& devices)>
      DevicesCollectedCallback;
    DISCORD_EXPORT void GetRecordingDevices(DevicesCollectedCallback callback);
    DISCORD_EXPORT void SetRecordingDevice(int inputDeviceId);

    DISCORD_EXPORT void GetPlayoutDevices(DevicesCollectedCallback callback);
    DISCORD_EXPORT void SetPlayoutDevice(int outputDeviceId);

    // ------------------------------------------------------------
    // Volumes
    // ------------------------------------------------------------
    DISCORD_EXPORT void SetMicVolume(float inputVolume);
    float GetSpeakerVolume();
    DISCORD_EXPORT void SetSpeakerVolume(float outputVolume);

    // ------------------------------------------------------------
    // Audio Mode
    // ------------------------------------------------------------
    enum AudioModeType {
        MODE_VAD,
        MODE_PTT,
    };

    DISCORD_EXPORT void SetAudioInputMode(AudioModeType mode);

    // ------------------------------------------------------------
    // VAD
    // ------------------------------------------------------------
    // this is the number of voice frames to queue up BEFORE the treshold is
    // triggered, so we capture a little bit
    // to help smooth out people being quiet when the first start talking.
    DISCORD_EXPORT void SetVADLeadingFramesToBuffer(int numFrames);

    // this is the number of voice frames to send after we determine the user
    // has stopped talking.
    DISCORD_EXPORT void SetVADTrailingFramesToSend(int numFrames);
    DISCORD_EXPORT void SetVADTriggerThreshold(float thresoldDb);

    // ------------------------------------------------------------
    // PTT
    // ------------------------------------------------------------
    DISCORD_EXPORT void SetPTTTriggerInputs(const ScanCodeCombos& scanCodeCombos);
    DISCORD_EXPORT void SetPTTActive(bool active);

    // ------------------------------------------------------------
    // Observe processes
    // ------------------------------------------------------------
    struct DISCORD_EXPORT ObservedProcess {
        uint32_t id;
        std::vector<std::string> executables;
    };
    DISCORD_EXPORT void SetObservedProcesses(
      std::vector<ObservedProcess> const& processes,
      std::function<void(std::vector<std::pair<uint32_t, bool>> const&)> changeCallback);
    DISCORD_EXPORT void ClearObservedProcesses();
    DISCORD_EXPORT void GetProcesses(std::function<void(std::vector<std::string> const&)> callback);

    // ------------------------------------------------------------
    // Audio Session Control
    // ------------------------------------------------------------
    DISCORD_EXPORT void SetAttenuationMode(bool enable, float factor);
    DISCORD_EXPORT void SetDuckingPreference(bool enableDucking);

    // ------------------------------------------------------------
    // Debug Output
    // ------------------------------------------------------------
    DISCORD_EXPORT void DebugDump(std::function<void(std::string const&)> onReady);

private:
    std::shared_ptr<DiscordInternal::EventLoop> mEventLoop;
    std::unique_ptr<DiscordInternal::SocketClient> mSocketClient;
    std::shared_ptr<DiscordInternal::VoiceUser> mLocalVoiceUser;
    std::unique_ptr<DiscordInternal::ProcessObserver> mProcessObserver;

    ConnectToServerCallback mConnectedToServerCallback;
};
