#pragma once

#include <climits>
#include <cstring>
#include <functional>
#include <memory>
#include <string>
#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 final {
public:
    static uint32_t const LEGACY_AUDIO_DEVICE = (1U << 0);

    DISCORD_EXPORT Discord(uint32_t flags = 0x0);
    DISCORD_EXPORT ~Discord();

    // ------------------------------------------------------------
    // Server Lifecycle
    // ------------------------------------------------------------
    struct DISCORD_EXPORT ConnectionInfo final {
        bool isConnected;
        std::string protocol;
        std::string localAddress;
        uint16_t localPort;
    };

    typedef std::function<void(const ConnectionInfo& info, 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();
    DISCORD_EXPORT void SimulatePacketLoss(double packetLossRate);

    // ------------------------------------------------------------
    // Encryption
    // ------------------------------------------------------------
    struct DISCORD_EXPORT EncryptionSettings final {
        std::string mode;
        std::vector<uint8_t> secretKey;
    };
    DISCORD_EXPORT void SetEncryptionSettings(const EncryptionSettings& settings);

    typedef std::function<void(const std::vector<std::string>& encryptionModes)>
      EncryptionModesCallback;
    DISCORD_EXPORT void GetEncryptionModes(EncryptionModesCallback callback);
    DISCORD_EXPORT void SetEncodingBitRate(int bitsPerSecond);

    // ------------------------------------------------------------
    // 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);
    DISCORD_EXPORT void SetIdleJitterBufferFlush(bool flushEnabled);

    // ------------------------------------------------------------
    // 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(int /*webrtc::EcModes*/ mode);
    DISCORD_EXPORT void SetNoiseSuppression(int /*webrtc::NsModes*/ mode);
    DISCORD_EXPORT void SetAutomaticGainControl(int /*webrtc::AgcModes*/ mode);
    DISCORD_EXPORT void SetMinimumPlayoutDelay(int delayMs);
    DISCORD_EXPORT void EnableForwardErrorCorrection(bool enabled);
    DISCORD_EXPORT void EnableDiscontinuousTransmission(bool enabled);
    DISCORD_EXPORT void SetExpectedPacketLossRate(double expectedPacketLossRate);

    // ------------------------------------------------------------
    // 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
    // ------------------------------------------------------------
    typedef std::function<void(float inputVolume, float outputVolume)> VolumeCallback;
    DISCORD_EXPORT void SetMicVolume(float inputVolume);
    DISCORD_EXPORT void SetSpeakerVolume(float outputVolume);
    DISCORD_EXPORT void SetVolumeChangeCallback(VolumeCallback callback);
    typedef std::function<void(bool inputDetected)> NoAudioInputCallback;
    DISCORD_EXPORT void SetNoAudioInputThreshold(float dBFSThreshold);
    DISCORD_EXPORT void SetNoAudioInputCallback(NoAudioInputCallback callback);

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

    DISCORD_EXPORT void SetAudioInputMode(AudioModeType mode);

    // ------------------------------------------------------------
    // VAD
    // ------------------------------------------------------------
    // this is the number of voice frames to queue up BEFORE the threshold 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);
    DISCORD_EXPORT void SetVADAutoThreshold(int autoThreshold);

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

    // ------------------------------------------------------------
    // Observe processes
    // ------------------------------------------------------------
    static const uint32_t PROCESS_DISCORD_ID_UNKNOWN = 0xffffffff;
    static const uint32_t PROCESS_DISCORD_ID_NOT_FOUND = 0xfffffffe;
    static const uint32_t PROCESS_DISCORD_ID_GUESSED = 0xfffffffd;
    static const uint32_t PROCESS_DISCORD_ID_OVERRIDE = 0xfffffffc;
    struct DISCORD_EXPORT RunningProcess {
        uint32_t mPID;
        uint32_t mLastSeenFocused;
        bool mIsElevated;
        bool mRequireElevation;
        std::string mExePath;
        std::string mExeName;
        std::string mCmdLine;
        std::string mName;
        // id if matched from table
        uint32_t mDiscordId;
        // name guess if not matched from table
        std::string mGameName;
    };
    struct DISCORD_EXPORT ObservedProcess {
        uint32_t id;
        std::string name;
        std::vector<std::string> executables;
        std::string cmdLine;
    };
    struct GameOverrideFlags {
        enum Type : uint32_t {
            BLOCK = 1 << 0,
            ADD = 1 << 1,
        };
    };
    struct DISCORD_EXPORT ProcessOverride {
        std::string gameName;
        std::string executablePath;
        std::string cmdLine;
        uint32_t flags; // GameOverrideFlags
    };
    DISCORD_EXPORT void SetObservedGamesCallback(
      std::vector<ObservedProcess> const& processes,
      std::function<void(std::vector<RunningProcess> const&)> changeCallback);
    DISCORD_EXPORT void ClearObservedProcesses();
    DISCORD_EXPORT void SetCandidateGamesCallback(
      std::function<void(std::vector<RunningProcess> const&)> callback);

    DISCORD_EXPORT void SetGameCandidateOverrides(std::vector<ProcessOverride> const& candidates);

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

    // ------------------------------------------------------------
    // Diagnostic Information
    // ------------------------------------------------------------
    struct QualityMetrics {
        uint64_t speakingUserId{0};
        int tier{-1};
        float quality{0.f};
        int previousTier{-1};
        int previousTierMs{0};
        int previousPrevTier{-1};
        int pingMin{INT_MAX};
        int pingMax{0};
        int pingAverage{0};
        int oscillationCounter{0};
        bool detectedOscillation{false};
    };
    DISCORD_EXPORT void CollectDiagnostics(std::function<void(std::string const&)> callback);
    DISCORD_EXPORT void GetPingHistory(std::function<void(std::vector<int> const&)> pingHistory);
    DISCORD_EXPORT void SetPingCallback(std::function<void(int)> pingCallback);
    DISCORD_EXPORT void SetQualityThresholds(std::vector<float> const& thresholds,
                                             uint64_t stableDurationMs,
                                             int maxOscillations,
                                             int maxReportsPerMinute);
    DISCORD_EXPORT void SetQualityCallback(std::function<void(QualityMetrics const&)> callback);

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

private:
    void OpenKeepAlive();
    void CloseKeepAlive();

    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;

    bool mKeepAliveEnabled;
    int mKeepAliveChannel;
    int mLocalUserEncodeBitRate;
};
