#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <sound/asound.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include <errno.h>

#define DESIRED_SAMPLE_RATE 44100
#define DESIRED_CHANNELS 2  // Prefer stereo, as mono often not supported on hardware
#define DESIRED_PERIOD_SIZE 1024
#define DESIRED_PERIODS 4
#define FORMAT SNDRV_PCM_FORMAT_S16_LE
#define DURATION 5  // seconds
#define BYTES_PER_SAMPLE 2

int main() {
    int card = -1, device = -1;
    FILE *proc = fopen("/proc/asound/pcm", "r");
    if (!proc) {
        perror("Failed to open /proc/asound/pcm");
        return 1;
    }

    printf("Opened /proc/asound/pcm\n");

    char line[256];
    while (fgets(line, sizeof(line), proc)) {
        if (strstr(line, "playback")) {
            sscanf(line, "%02d-%02d", &card, &device);
            break;
        }
    }
    fclose(proc);

    if (card < 0 || device < 0) {
        fprintf(stderr, "No playback devices found\n");
        return 1;
    }

    char devname[64];
    snprintf(devname, sizeof(devname), "/dev/snd/pcmC%dD%dp", card, device);

    int fd = open(devname, O_WRONLY);
    if (fd < 0) {
        perror("Failed to open PCM device");
        return 1;
    }

    printf("Opened device\n");

    struct snd_pcm_hw_params params;
    memset(&params, 0, sizeof(params));

    // Initialize to full possible ranges for refinement
    int i;
    for (i = SNDRV_PCM_HW_PARAM_FIRST_MASK; i <= SNDRV_PCM_HW_PARAM_LAST_MASK; i++) {
        struct snd_mask *mask = &params.masks[i - SNDRV_PCM_HW_PARAM_FIRST_MASK];
        mask->bits[0] = ~0U;
        mask->bits[1] = ~0U;
    }
    for (i = SNDRV_PCM_HW_PARAM_FIRST_INTERVAL; i <= SNDRV_PCM_HW_PARAM_LAST_INTERVAL; i++) {
        struct snd_interval *interval = &params.intervals[i - SNDRV_PCM_HW_PARAM_FIRST_INTERVAL];
        interval->min = 0;
        interval->max = ~0U;
        interval->openmin = interval->openmax = 0;
        interval->integer = 0;
    }
    params.rmask = ~0U;
    params.cmask = 0;
    params.info = ~0U;

    // Refine to hardware-supported ranges
    if (ioctl(fd, SNDRV_PCM_IOCTL_HW_REFINE, &params) < 0) {
        perror("Failed initial HW_REFINE");
        close(fd);
        return 1;
    }

    printf("Refined hw params\n");

    // Set access to RW_INTERLEAVED if supported
    struct snd_mask *access_mask = &params.masks[SNDRV_PCM_HW_PARAM_ACCESS - SNDRV_PCM_HW_PARAM_FIRST_MASK];
    unsigned int access_bit = SNDRV_PCM_ACCESS_RW_INTERLEAVED;
    if (!(access_mask->bits[access_bit / 32] & (1U << (access_bit % 32)))) {
        fprintf(stderr, "RW_INTERLEAVED access not supported\n");
        close(fd);
        return 1;
    }
    memset(access_mask, 0, sizeof(*access_mask));
    access_mask->bits[access_bit / 32] |= (1U << (access_bit % 32));
    params.rmask |= (1U << SNDRV_PCM_HW_PARAM_ACCESS);

    // Set format to S16_LE if supported
    struct snd_mask *format_mask = &params.masks[SNDRV_PCM_HW_PARAM_FORMAT - SNDRV_PCM_HW_PARAM_FIRST_MASK];
    unsigned int format_bit = FORMAT;
    if (!(format_mask->bits[format_bit / 32] & (1U << (format_bit % 32)))) {
        fprintf(stderr, "S16_LE format not supported\n");
        close(fd);
        return 1;
    }
    memset(format_mask, 0, sizeof(*format_mask));
    format_mask->bits[format_bit / 32] |= (1U << (format_bit % 32));
    params.rmask |= (1U << SNDRV_PCM_HW_PARAM_FORMAT);

    // Set channels to nearest supported (prefer DESIRED_CHANNELS)
    struct snd_interval *channels_int = &params.intervals[SNDRV_PCM_HW_PARAM_CHANNELS - SNDRV_PCM_HW_PARAM_FIRST_INTERVAL];
    unsigned int channels = DESIRED_CHANNELS;
    if (channels < channels_int->min) channels = channels_int->min;
    if (channels > channels_int->max) channels = channels_int->max;
    if (channels != DESIRED_CHANNELS) {
        fprintf(stderr, "Adjusted channels to %u (hardware range: %u-%u)\n", channels, channels_int->min, channels_int->max);
    }
    channels_int->min = channels_int->max = channels;
    channels_int->integer = 1;
    params.rmask |= (1U << SNDRV_PCM_HW_PARAM_CHANNELS);

    // Set sample rate to nearest supported
    struct snd_interval *rate_int = &params.intervals[SNDRV_PCM_HW_PARAM_RATE - SNDRV_PCM_HW_PARAM_FIRST_INTERVAL];
    unsigned int sample_rate = DESIRED_SAMPLE_RATE;
    if (sample_rate < rate_int->min) sample_rate = rate_int->min;
    if (sample_rate > rate_int->max) sample_rate = rate_int->max;
    if (sample_rate != DESIRED_SAMPLE_RATE) {
        fprintf(stderr, "Adjusted sample rate to %u Hz (hardware range: %u-%u)\n", sample_rate, rate_int->min, rate_int->max);
    }
    rate_int->min = rate_int->max = sample_rate;
    rate_int->integer = 1;
    params.rmask |= (1U << SNDRV_PCM_HW_PARAM_RATE);

    // Set period size to nearest supported
    struct snd_interval *period_size_int = &params.intervals[SNDRV_PCM_HW_PARAM_PERIOD_SIZE - SNDRV_PCM_HW_PARAM_FIRST_INTERVAL];
    unsigned int period_size = DESIRED_PERIOD_SIZE;
    if (period_size < period_size_int->min) period_size = period_size_int->min;
    if (period_size > period_size_int->max) period_size = period_size_int->max;
    if (period_size != DESIRED_PERIOD_SIZE) {
        fprintf(stderr, "Adjusted period size to %u frames (hardware range: %u-%u)\n", period_size, period_size_int->min, period_size_int->max);
    }
    period_size_int->min = period_size_int->max = period_size;
    period_size_int->integer = 1;
    params.rmask |= (1U << SNDRV_PCM_HW_PARAM_PERIOD_SIZE);

    // Set periods (less strict, clamp to range)
    struct snd_interval *periods_int = &params.intervals[SNDRV_PCM_HW_PARAM_PERIODS - SNDRV_PCM_HW_PARAM_FIRST_INTERVAL];
    unsigned int periods = DESIRED_PERIODS;
    if (periods < periods_int->min) periods = periods_int->min;
    if (periods > periods_int->max) periods = periods_int->max;
    if (periods != DESIRED_PERIODS) {
        fprintf(stderr, "Adjusted periods to %u (hardware range: %u-%u)\n", periods, periods_int->min, periods_int->max);
    }
    periods_int->min = periods_int->max = periods;
    periods_int->integer = 1;
    params.rmask |= (1U << SNDRV_PCM_HW_PARAM_PERIODS);

    // Refine again with desired values to confirm
    if (ioctl(fd, SNDRV_PCM_IOCTL_HW_REFINE, &params) < 0) {
        perror("Failed to refine desired HW params");
        close(fd);
        return 1;
    }

    printf("Refined hw params the last time\n");

    // Now apply the parameters
    if (ioctl(fd, SNDRV_PCM_IOCTL_HW_PARAMS, &params) < 0) {
        perror("Failed to set HW params");
        close(fd);
        return 1;
    }

    printf("HW params set\n");

    if (ioctl(fd, SNDRV_PCM_IOCTL_PREPARE, 0) < 0) {
        perror("Failed to prepare PCM");
        close(fd);
        return 1;
    }

    printf("Prepared\n");

    // Generate and play sine wave
    size_t frame_size = channels * BYTES_PER_SAMPLE;
    short *buffer = malloc(period_size * frame_size);
    if (!buffer) {
        perror("Failed to allocate buffer");
        close(fd);
        return 1;
    }

    unsigned long total_frames = (unsigned long)sample_rate * DURATION;
    unsigned long frames_written = 0;
    double phase = 0.0;
    const double freq = 440.0;  // Hz
    const double phase_inc = 2.0 * M_PI * freq / sample_rate;

    while (frames_written < total_frames) {
        size_t frames_to_write = period_size;
        if (frames_written + frames_to_write > total_frames) {
            frames_to_write = total_frames - frames_written;
        }

        for (size_t j = 0; j < frames_to_write; ++j) {
            short sample = (short)(32767.0 * sin(phase));
            for (unsigned int ch = 0; ch < channels; ++ch) {
                buffer[j * channels + ch] = sample;
            }
            phase += phase_inc;
            if (phase >= 2.0 * M_PI) phase -= 2.0 * M_PI;
        }

        ssize_t bytes_written = write(fd, buffer, frames_to_write * frame_size);
        printf("bytes written = %lu\n", bytes_written);
        if (bytes_written < 0) {
            if (errno == EPIPE) {
                // Underrun: recover
                ioctl(fd, SNDRV_PCM_IOCTL_PREPARE, 0);
                continue;
            }
            perror("Write failed");
            break;
        }

        frames_written += bytes_written / frame_size;
    }

    free(buffer);
    // Drain remaining data
    ioctl(fd, SNDRV_PCM_IOCTL_DRAIN, 0);
    close(fd);
    return 0;
}