#include "audio/audio.h"
#include <windows.h>
#include <objbase.h>
#include <string.h>
#include <math.h>

#define AUDIO_MAX_DEVICES   32
#define AUDIO_MAX_CHANNELS  32
#define AUDIO_TEST_TONE_HZ  440.0
#define AUDIO_TEST_TONE_SEC 2.0
#define AUDIO_PI            3.14159265358979323846

////////////////////////////////
// ASIO type definitions (minimal set for a host)

typedef long ASIOError;
typedef long ASIOBool;
typedef long long int ASIOSamples;
typedef long long int ASIOTimeStamp;

enum {
    ASE_OK             =  0,
    ASE_SUCCESS        =  0x3f4847a0,
    ASE_NotPresent     = -1000,
    ASE_HWMalfunction  = -999,
    ASE_InvalidParameter = -998,
    ASE_InvalidMode    = -997,
    ASE_SPNotAdvancing = -996,
    ASE_NoClock        = -995,
    ASE_NoMemory       = -994,
};

enum ASIOSampleType {
    ASIOSTInt16MSB   = 0,
    ASIOSTInt24MSB   = 1,
    ASIOSTInt32MSB   = 2,
    ASIOSTFloat32MSB = 3,
    ASIOSTFloat64MSB = 4,

    ASIOSTInt32MSB16 = 8,
    ASIOSTInt32MSB18 = 9,
    ASIOSTInt32MSB20 = 10,
    ASIOSTInt32MSB24 = 11,

    ASIOSTInt16LSB   = 16,
    ASIOSTInt24LSB   = 17,
    ASIOSTInt32LSB   = 18,
    ASIOSTFloat32LSB = 19,
    ASIOSTFloat64LSB = 20,

    ASIOSTInt32LSB16 = 24,
    ASIOSTInt32LSB18 = 25,
    ASIOSTInt32LSB20 = 26,
    ASIOSTInt32LSB24 = 27,
};

struct ASIOClockSource {
    long index;
    long channel;
    long group;
    ASIOBool isCurrentSource;
    char name[32];
};

struct ASIOChannelInfo {
    long   channel;
    ASIOBool isInput;
    ASIOBool isActive;
    long   channelGroup;
    ASIOSampleType type;
    char   name[32];
};

struct ASIOBufferInfo {
    ASIOBool isInput;
    long     channelNum;
    void    *buffers[2]; // F64 buffer
};

struct ASIOTimeCode {
    F64          speed;
    ASIOSamples     timeCodeSamples;
    unsigned long   flags;
    char            future[64];
};

struct AsioTimeInfo {
    F64          speed;
    ASIOTimeStamp   systemTime;
    ASIOSamples     samplePosition;
    F64          sampleRate;
    unsigned long   flags;
    char            reserved[12];
};

struct ASIOTime {
    long            reserved[4];
    AsioTimeInfo    timeInfo;
    ASIOTimeCode    timeCode;
};

struct ASIOCallbacks {
    void (*bufferSwitch)(long doubleBufferIndex, ASIOBool directProcess);
    void (*sampleRateDidChange)(F64 sRate);
    long (*asioMessage)(long selector, long value, void *message, F64 *opt);
    ASIOTime *(*bufferSwitchTimeInfo)(ASIOTime *params, long doubleBufferIndex, ASIOBool directProcess);
};

// ASIO message selectors
enum {
    kAsioSelectorSupported = 1,
    kAsioEngineVersion,
    kAsioResetRequest,
    kAsioBufferSizeChange,
    kAsioResyncRequest,
    kAsioLatenciesChanged,
    kAsioSupportsTimeInfo,
    kAsioSupportsTimeCode,
    kAsioSupportsInputMonitor,
};

////////////////////////////////
// IASIO COM interface
// Standard ASIO vtable — inherits IUnknown

class IASIO : public IUnknown {
public:
    virtual ASIOBool   init(void *sysHandle) = 0;
    virtual void       getDriverName(char *name) = 0;
    virtual long       getDriverVersion() = 0;
    virtual void       getErrorMessage(char *string) = 0;
    virtual ASIOError  start() = 0;
    virtual ASIOError  stop() = 0;
    virtual ASIOError  getChannels(long *numInputChannels, long *numOutputChannels) = 0;
    virtual ASIOError  getLatencies(long *inputLatency, long *outputLatency) = 0;
    virtual ASIOError  getBufferSize(long *minSize, long *maxSize, long *preferredSize, long *granularity) = 0;
    virtual ASIOError  canSampleRate(F64 sampleRate) = 0;
    virtual ASIOError  getSampleRate(F64 *sampleRate) = 0;
    virtual ASIOError  setSampleRate(F64 sampleRate) = 0;
    virtual ASIOError  getClockSources(ASIOClockSource *clocks, long *numSources) = 0;
    virtual ASIOError  setClockSource(long reference) = 0;
    virtual ASIOError  getSamplePosition(ASIOSamples *sPos, ASIOTimeStamp *tStamp) = 0;
    virtual ASIOError  getChannelInfo(ASIOChannelInfo *info) = 0;
    virtual ASIOError  createBuffers(ASIOBufferInfo *bufferInfos, long numChannels, long bufferSize, ASIOCallbacks *callbacks) = 0;
    virtual ASIOError  disposeBuffers() = 0;
    virtual ASIOError  controlPanel() = 0;
    virtual ASIOError  future(long selector, void *opt) = 0;
    virtual ASIOError  outputReady() = 0;
};

////////////////////////////////
// Internal state

struct AsioDriverInfo {
    char name[128];
    CLSID clsid;
};

struct AudioEngine {
    void *hwnd; // HWND for ASIO init

    // Device enumeration
    AudioDeviceInfo devices[AUDIO_MAX_DEVICES];
    AsioDriverInfo  drivers[AUDIO_MAX_DEVICES];
    S32         device_count;

    // Active driver
    IASIO          *driver;
    S32         active_device_index; // -1 = none

    // Buffer state
    ASIOBufferInfo  buffer_infos[AUDIO_MAX_CHANNELS];
    long            num_output_channels;
    long            num_input_channels;
    long            buffer_size;
    F64          sample_rate;
    ASIOSampleType  output_sample_type;
    ASIOCallbacks   callbacks;

    // Test tone state (accessed from callback thread)
    volatile LONG   test_tone_active;
    volatile LONG   test_tone_samples_remaining;
    F64          test_tone_phase; // written only from callback thread
};

////////////////////////////////
// Global engine pointer for ASIO callbacks (standard pattern — callbacks have no user-data param)

static AudioEngine *g_audio_engine = nullptr;

////////////////////////////////
// Sample writing helper

static void write_sample(void *dest, ASIOSampleType type, F64 value) {
    // Clamp to [-1, 1]
    if (value > 1.0) value = 1.0;
    if (value < -1.0) value = -1.0;

    switch (type) {
    case ASIOSTInt16LSB: {
        S16 s = (S16)(value * 32767.0);
        memcpy(dest, &s, 2);
    } break;
    case ASIOSTInt24LSB: {
        S32 s = (S32)(value * 8388607.0);
        U8 *d = (U8 *)dest;
        d[0] = (U8)(s & 0xFF);
        d[1] = (U8)((s >> 8) & 0xFF);
        d[2] = (U8)((s >> 16) & 0xFF);
    } break;
    case ASIOSTInt32LSB: {
        S32 s = (S32)(value * 2147483647.0);
        memcpy(dest, &s, 4);
    } break;
    case ASIOSTFloat32LSB: {
        F32 f = (F32)value;
        memcpy(dest, &f, 4);
    } break;
    case ASIOSTFloat64LSB: {
        memcpy(dest, &value, 8);
    } break;
    default: {
        // Unsupported type — write silence (4 bytes of zero)
        U32 z = 0;
        memcpy(dest, &z, 4);
    } break;
    }
}

static int sample_type_size(ASIOSampleType type) {
    switch (type) {
    case ASIOSTInt16LSB:   return 2;
    case ASIOSTInt24LSB:   return 3;
    case ASIOSTInt32LSB:   return 4;
    case ASIOSTFloat32LSB: return 4;
    case ASIOSTFloat64LSB: return 8;
    default:               return 4;
    }
}

////////////////////////////////
// ASIO callbacks (static — called from driver thread)

static void asio_buffer_switch(long doubleBufferIndex, ASIOBool directProcess) {
    (void)directProcess;

    AudioEngine *engine = g_audio_engine;
    if (!engine) return;

    long buf_size = engine->buffer_size;
    ASIOSampleType type = engine->output_sample_type;
    S32 bytes_per_sample = sample_type_size(type);

    LONG tone_active = InterlockedCompareExchange(&engine->test_tone_active, 0, 0);

    for (long ch = 0; ch < engine->num_output_channels; ch++) {
        void *buf = engine->buffer_infos[ch].buffers[doubleBufferIndex];
        if (!buf) continue;

        if (tone_active) {
            // Only generate tone, don't branch per-sample for active check
        } else {
            // Silence
            memset(buf, 0, (size_t)(buf_size * bytes_per_sample));
        }
    }

    if (tone_active) {
        F64 phase = engine->test_tone_phase;
        F64 phase_inc = 2.0 * AUDIO_PI * AUDIO_TEST_TONE_HZ / engine->sample_rate;
        LONG remaining = InterlockedCompareExchange(&engine->test_tone_samples_remaining, 0, 0);
        long samples_to_gen = (remaining < buf_size) ? (long)remaining : buf_size;

        for (long s = 0; s < buf_size; s++) {
            F64 sample_val = 0.0;
            if (s < samples_to_gen) {
                sample_val = sin(phase) * 0.5; // -6dB to avoid clipping
                phase += phase_inc;
                if (phase >= 2.0 * AUDIO_PI) phase -= 2.0 * AUDIO_PI;
            }

            // Write to all output channels (stereo or more)
            for (long ch = 0; ch < engine->num_output_channels; ch++) {
                void *buf = engine->buffer_infos[ch].buffers[doubleBufferIndex];
                if (!buf) continue;
                U8 *dest = (U8 *)buf + (size_t)(s * bytes_per_sample);
                write_sample(dest, type, sample_val);
            }
        }

        engine->test_tone_phase = phase;

        LONG new_remaining = InterlockedExchangeAdd(&engine->test_tone_samples_remaining, -samples_to_gen);
        if (new_remaining <= samples_to_gen) {
            InterlockedExchange(&engine->test_tone_active, 0);
            InterlockedExchange(&engine->test_tone_samples_remaining, 0);
        }
    }
}

static void asio_sample_rate_changed(F64 sRate) {
    AudioEngine *engine = g_audio_engine;
    if (engine) {
        engine->sample_rate = sRate;
    }
}

static long asio_message(long selector, long value, void *message, F64 *opt) {
    (void)value;
    (void)message;
    (void)opt;

    switch (selector) {
    case kAsioSelectorSupported:
        // Report which selectors we support
        if (value == kAsioEngineVersion ||
            value == kAsioResetRequest ||
            value == kAsioSupportsTimeInfo)
            return 1;
        return 0;
    case kAsioEngineVersion:
        return 2; // ASIO 2.0
    case kAsioResetRequest:
        return 1;
    case kAsioLatenciesChanged:
        return 1;
    case kAsioSupportsTimeInfo:
        return 0; // We use bufferSwitch, not bufferSwitchTimeInfo
    default:
        return 0;
    }
}

static ASIOTime *asio_buffer_switch_time_info(ASIOTime *params, long doubleBufferIndex, ASIOBool directProcess) {
    asio_buffer_switch(doubleBufferIndex, directProcess);
    return params;
}

////////////////////////////////
// Registry enumeration

static void enumerate_asio_drivers(AudioEngine *engine) {
    engine->device_count = 0;

    HKEY asio_key;
    if (RegOpenKeyExA(HKEY_LOCAL_MACHINE, "SOFTWARE\\ASIO", 0, KEY_READ, &asio_key) != ERROR_SUCCESS)
        return;

    char subkey_name[256];
    DWORD subkey_name_len;

    for (DWORD i = 0; engine->device_count < AUDIO_MAX_DEVICES; i++) {
        subkey_name_len = sizeof(subkey_name);
        if (RegEnumKeyExA(asio_key, i, subkey_name, &subkey_name_len, nullptr, nullptr, nullptr, nullptr) != ERROR_SUCCESS)
            break;

        HKEY driver_key;
        if (RegOpenKeyExA(asio_key, subkey_name, 0, KEY_READ, &driver_key) != ERROR_SUCCESS)
            continue;

        // Read CLSID string
        char clsid_str[64] = {};
        DWORD clsid_len = sizeof(clsid_str);
        DWORD type = 0;
        if (RegQueryValueExA(driver_key, "CLSID", nullptr, &type, (LPBYTE)clsid_str, &clsid_len) != ERROR_SUCCESS ||
            type != REG_SZ) {
            RegCloseKey(driver_key);
            continue;
        }

        // Parse CLSID string to GUID
        CLSID clsid;
        wchar_t clsid_wide[64];
        MultiByteToWideChar(CP_ACP, 0, clsid_str, -1, clsid_wide, 64);
        if (CLSIDFromString(clsid_wide, &clsid) != S_OK) {
            RegCloseKey(driver_key);
            continue;
        }

        S32 idx = engine->device_count++;
        strncpy_s(engine->devices[idx].name, sizeof(engine->devices[idx].name), subkey_name, _TRUNCATE);
        engine->devices[idx].id = idx;
        engine->drivers[idx].clsid = clsid;
        strncpy_s(engine->drivers[idx].name, sizeof(engine->drivers[idx].name), subkey_name, _TRUNCATE);

        RegCloseKey(driver_key);
    }

    RegCloseKey(asio_key);
}

////////////////////////////////
// Public API

AudioEngine *audio_create(void *hwnd) {
    AudioEngine *engine = new AudioEngine();
    memset(engine, 0, sizeof(*engine));
    engine->hwnd = hwnd;
    engine->active_device_index = -1;
    g_audio_engine = engine;

    CoInitialize(nullptr);
    enumerate_asio_drivers(engine);
    return engine;
}

void audio_destroy(AudioEngine *engine) {
    audio_close_device(engine);
    if (g_audio_engine == engine) g_audio_engine = nullptr;
    delete engine;
}

void audio_refresh_devices(AudioEngine *engine) {
    audio_close_device(engine);
    enumerate_asio_drivers(engine);
}

S32 audio_get_device_count(AudioEngine *engine) {
    return engine->device_count;
}

AudioDeviceInfo *audio_get_device(AudioEngine *engine, S32 index) {
    if (index < 0 || index >= engine->device_count)
        return nullptr;
    return &engine->devices[index];
}

B32 audio_open_device(AudioEngine *engine, S32 index) {
    // Close any existing device first
    audio_close_device(engine);

    if (index < 0 || index >= engine->device_count)
        return false;

    // Create COM instance of the ASIO driver
    IASIO *driver = nullptr;
    HRESULT hr = CoCreateInstance(engine->drivers[index].clsid,
                                   nullptr, CLSCTX_INPROC_SERVER,
                                   engine->drivers[index].clsid,
                                   (void **)&driver);
    if (FAILED(hr) || !driver)
        return false;

    // Initialize the driver
    if (!driver->init(engine->hwnd)) {
        driver->Release();
        return false;
    }

    // Query channel counts
    long num_in = 0, num_out = 0;
    if (driver->getChannels(&num_in, &num_out) != ASE_OK || num_out <= 0) {
        driver->Release();
        return false;
    }

    // Query buffer size
    long min_size = 0, max_size = 0, preferred_size = 0, granularity = 0;
    if (driver->getBufferSize(&min_size, &max_size, &preferred_size, &granularity) != ASE_OK) {
        driver->Release();
        return false;
    }

    // Query sample rate
    F64 sample_rate = 0;
    if (driver->getSampleRate(&sample_rate) != ASE_OK || sample_rate <= 0) {
        // Try setting a common rate
        if (driver->setSampleRate(44100.0) == ASE_OK) {
            sample_rate = 44100.0;
        } else {
            driver->Release();
            return false;
        }
    }

    // Query output channel sample type
    ASIOChannelInfo chan_info = {};
    chan_info.channel = 0;
    chan_info.isInput = 0;
    if (driver->getChannelInfo(&chan_info) != ASE_OK) {
        driver->Release();
        return false;
    }

    // Limit output channels
    if (num_out > AUDIO_MAX_CHANNELS) num_out = AUDIO_MAX_CHANNELS;

    // Setup buffer infos for output channels only
    memset(engine->buffer_infos, 0, sizeof(engine->buffer_infos));
    for (long ch = 0; ch < num_out; ch++) {
        engine->buffer_infos[ch].isInput = 0;
        engine->buffer_infos[ch].channelNum = ch;
        engine->buffer_infos[ch].buffers[0] = nullptr;
        engine->buffer_infos[ch].buffers[1] = nullptr;
    }

    // Setup callbacks
    engine->callbacks.bufferSwitch = asio_buffer_switch;
    engine->callbacks.sampleRateDidChange = asio_sample_rate_changed;
    engine->callbacks.asioMessage = asio_message;
    engine->callbacks.bufferSwitchTimeInfo = asio_buffer_switch_time_info;

    // Create buffers
    if (driver->createBuffers(engine->buffer_infos, num_out, preferred_size, &engine->callbacks) != ASE_OK) {
        driver->Release();
        return false;
    }

    // Store state
    engine->driver = driver;
    engine->active_device_index = index;
    engine->num_output_channels = num_out;
    engine->num_input_channels = num_in;
    engine->buffer_size = preferred_size;
    engine->sample_rate = sample_rate;
    engine->output_sample_type = chan_info.type;
    engine->test_tone_active = 0;
    engine->test_tone_samples_remaining = 0;
    engine->test_tone_phase = 0.0;

    // Start the driver
    if (driver->start() != ASE_OK) {
        driver->disposeBuffers();
        driver->Release();
        engine->driver = nullptr;
        engine->active_device_index = -1;
        return false;
    }

    // Notify driver that output is ready (optional, some drivers benefit)
    driver->outputReady();

    return true;
}

void audio_close_device(AudioEngine *engine) {
    if (!engine->driver) return;

    InterlockedExchange(&engine->test_tone_active, 0);
    InterlockedExchange(&engine->test_tone_samples_remaining, 0);

    engine->driver->stop();
    engine->driver->disposeBuffers();
    engine->driver->Release();
    engine->driver = nullptr;
    engine->active_device_index = -1;
    engine->test_tone_phase = 0.0;
}

void audio_play_test_tone(AudioEngine *engine) {
    if (!engine->driver) return;

    engine->test_tone_phase = 0.0;
    LONG total_samples = (LONG)(engine->sample_rate * AUDIO_TEST_TONE_SEC);
    InterlockedExchange(&engine->test_tone_samples_remaining, total_samples);
    InterlockedExchange(&engine->test_tone_active, 1);
}

B32 audio_is_test_tone_playing(AudioEngine *engine) {
    return InterlockedCompareExchange(&engine->test_tone_active, 0, 0) != 0;
}

void audio_update(AudioEngine *engine, F32 dt) {
    (void)engine;
    (void)dt;
    // Currently no per-frame work needed on the main thread.
    // The ASIO callback handles all audio processing.
    // This hook exists for future use (e.g., level metering).
}
