/**
 * Scalar (non-SIMD) convolution kernels.
 * Serves as baseline and fallback for all architectures.
 */

#include "../dispatch.h"

/**
 * Convolution for standard (time, channels) layout.
 *
 * Memory layout:
 *   input[s * channels + ch]  = sample s, channel ch
 *   output[s * channels + ch] = sample s, channel ch
 *
 * Uses 4x loop unrolling for improved instruction-level parallelism.
 */
void convolve_scalar(
    float * __restrict__ output,
    const float * __restrict__ kernel,
    const float * __restrict__ input,
    unsigned int kernel_len,
    unsigned int channels,
    unsigned int n_out)
{
    for (unsigned int s = 0; s < n_out; s++) {
        float *out_row = output + s * channels;

        /* Process 4 channels at a time for ILP */
        unsigned int ch = 0;
        for (; ch + 4 <= channels; ch += 4) {
            float sum0 = 0.0f, sum1 = 0.0f, sum2 = 0.0f, sum3 = 0.0f;

            for (unsigned int k = 0; k < kernel_len; k++) {
                float kval = kernel[k];
                const float *in_row = input + (s + k) * channels + ch;
                sum0 += kval * in_row[0];
                sum1 += kval * in_row[1];
                sum2 += kval * in_row[2];
                sum3 += kval * in_row[3];
            }

            out_row[ch + 0] = sum0;
            out_row[ch + 1] = sum1;
            out_row[ch + 2] = sum2;
            out_row[ch + 3] = sum3;
        }

        /* Handle remaining channels */
        for (; ch < channels; ch++) {
            float sum = 0.0f;
            for (unsigned int k = 0; k < kernel_len; k++) {
                sum += kernel[k] * input[(s + k) * channels + ch];
            }
            out_row[ch] = sum;
        }
    }
}

/**
 * Convolution for transposed (channels, time) layout.
 *
 * Memory layout:
 *   input[s]  = sample s (single channel)
 *   output[s] = sample s (single channel)
 *
 * Processes one channel at a time. Caller iterates over channels.
 * Uses 8x loop unrolling for improved instruction-level parallelism.
 */
void convolve_transposed_scalar(
    float * __restrict__ output,
    const float * __restrict__ kernel,
    const float * __restrict__ input,
    unsigned int kernel_len,
    unsigned int n_out)
{
    /* Process 8 output samples at a time for better ILP */
    unsigned int s = 0;
    for (; s + 8 <= n_out; s += 8) {
        float sum0 = 0.0f, sum1 = 0.0f, sum2 = 0.0f, sum3 = 0.0f;
        float sum4 = 0.0f, sum5 = 0.0f, sum6 = 0.0f, sum7 = 0.0f;

        for (unsigned int k = 0; k < kernel_len; k++) {
            float kval = kernel[k];
            const float *in_ptr = input + s + k;
            sum0 += kval * in_ptr[0];
            sum1 += kval * in_ptr[1];
            sum2 += kval * in_ptr[2];
            sum3 += kval * in_ptr[3];
            sum4 += kval * in_ptr[4];
            sum5 += kval * in_ptr[5];
            sum6 += kval * in_ptr[6];
            sum7 += kval * in_ptr[7];
        }

        output[s + 0] = sum0;
        output[s + 1] = sum1;
        output[s + 2] = sum2;
        output[s + 3] = sum3;
        output[s + 4] = sum4;
        output[s + 5] = sum5;
        output[s + 6] = sum6;
        output[s + 7] = sum7;
    }

    /* Process 4 output samples at a time */
    for (; s + 4 <= n_out; s += 4) {
        float sum0 = 0.0f, sum1 = 0.0f, sum2 = 0.0f, sum3 = 0.0f;

        for (unsigned int k = 0; k < kernel_len; k++) {
            float kval = kernel[k];
            sum0 += kval * input[s + k];
            sum1 += kval * input[s + k + 1];
            sum2 += kval * input[s + k + 2];
            sum3 += kval * input[s + k + 3];
        }

        output[s + 0] = sum0;
        output[s + 1] = sum1;
        output[s + 2] = sum2;
        output[s + 3] = sum3;
    }

    /* Handle remaining samples */
    for (; s < n_out; s++) {
        float sum = 0.0f;
        for (unsigned int k = 0; k < kernel_len; k++) {
            sum += kernel[k] * input[s + k];
        }
        output[s] = sum;
    }
}

/**
 * Polyphase upsampling for transposed layout (scalar).
 *
 * Processes all phases simultaneously per output position.
 * kernel_T layout: [kernel_len][factor] (row-major)
 */
void upsample_polyphase_scalar(
    float * __restrict__ output,
    const float * __restrict__ kernel_T,
    const float * __restrict__ input,
    unsigned int kernel_len,
    unsigned int factor,
    unsigned int n_valid)
{
    if (factor == 2) {
        /* factor=2: process 4 positions at once, accumulate per phase, interleave */
        unsigned int s = 0;
        for (; s + 4 <= n_valid; s += 4) {
            float a0_p0 = 0.0f, a0_p1 = 0.0f;
            float a1_p0 = 0.0f, a1_p1 = 0.0f;
            float a2_p0 = 0.0f, a2_p1 = 0.0f;
            float a3_p0 = 0.0f, a3_p1 = 0.0f;
            for (unsigned int k = 0; k < kernel_len; k++) {
                float k0 = kernel_T[k * 2 + 0];
                float k1 = kernel_T[k * 2 + 1];
                a0_p0 += input[s + 0 + k] * k0;
                a0_p1 += input[s + 0 + k] * k1;
                a1_p0 += input[s + 1 + k] * k0;
                a1_p1 += input[s + 1 + k] * k1;
                a2_p0 += input[s + 2 + k] * k0;
                a2_p1 += input[s + 2 + k] * k1;
                a3_p0 += input[s + 3 + k] * k0;
                a3_p1 += input[s + 3 + k] * k1;
            }
            output[s * 2 + 0] = a0_p0; output[s * 2 + 1] = a0_p1;
            output[s * 2 + 2] = a1_p0; output[s * 2 + 3] = a1_p1;
            output[s * 2 + 4] = a2_p0; output[s * 2 + 5] = a2_p1;
            output[s * 2 + 6] = a3_p0; output[s * 2 + 7] = a3_p1;
        }
        for (; s < n_valid; s++) {
            float acc0 = 0.0f, acc1 = 0.0f;
            for (unsigned int k = 0; k < kernel_len; k++) {
                float in_val = input[s + k];
                acc0 += in_val * kernel_T[k * 2 + 0];
                acc1 += in_val * kernel_T[k * 2 + 1];
            }
            output[s * 2 + 0] = acc0;
            output[s * 2 + 1] = acc1;
        }
    } else if (factor == 8) {
        /* factor=8: process 4 positions at once with 4x8 accumulators */
        unsigned int s = 0;
        for (; s + 4 <= n_valid; s += 4) {
            float a0[8] = {0}, a1[8] = {0}, a2[8] = {0}, a3[8] = {0};
            for (unsigned int k = 0; k < kernel_len; k++) {
                const float *kr = kernel_T + k * 8;
                float i0 = input[s + 0 + k], i1 = input[s + 1 + k];
                float i2 = input[s + 2 + k], i3 = input[s + 3 + k];
                for (unsigned int p = 0; p < 8; p++) {
                    a0[p] += i0 * kr[p];
                    a1[p] += i1 * kr[p];
                    a2[p] += i2 * kr[p];
                    a3[p] += i3 * kr[p];
                }
            }
            for (unsigned int p = 0; p < 8; p++) {
                output[s * 8 +  0 + p] = a0[p];
                output[s * 8 +  8 + p] = a1[p];
                output[s * 8 + 16 + p] = a2[p];
                output[s * 8 + 24 + p] = a3[p];
            }
        }
        for (; s < n_valid; s++) {
            float acc[8] = {0};
            for (unsigned int k = 0; k < kernel_len; k++) {
                float in_val = input[s + k];
                const float *kr = kernel_T + k * 8;
                for (unsigned int p = 0; p < 8; p++)
                    acc[p] += in_val * kr[p];
            }
            for (unsigned int p = 0; p < 8; p++)
                output[s * 8 + p] = acc[p];
        }
    } else {
        /* General fallback */
        for (unsigned int s = 0; s < n_valid; s++) {
            float *out_ptr = output + s * factor;
            for (unsigned int p = 0; p < factor; p++)
                out_ptr[p] = 0.0f;
            for (unsigned int k = 0; k < kernel_len; k++) {
                float in_val = input[s + k];
                const float *kern_row = kernel_T + k * factor;
                for (unsigned int p = 0; p < factor; p++)
                    out_ptr[p] += in_val * kern_row[p];
            }
        }
    }
}

/**
 * Multi-filter convolution for groups=1 case (scalar).
 *
 * Applies multiple filters to a single input channel.
 * Strategy: Process each filter fully before moving to the next.
 * This keeps filter data in cache and gives contiguous output writes.
 */
void convolve_multi_filter_scalar(
    float * __restrict__ output,
    const float * __restrict__ filters,
    const float * __restrict__ input,
    unsigned int n_filters,
    unsigned int filter_len,
    unsigned int n_out)
{
    /* Process each filter independently for better cache behavior on output */
    for (unsigned int f = 0; f < n_filters; f++) {
        const float *filter = &filters[f * filter_len];
        float *out_row = &output[f * n_out];

        /* Process 8 output samples at a time for better ILP */
        unsigned int s = 0;
        for (; s + 8 <= n_out; s += 8) {
            float sum0 = 0.0f, sum1 = 0.0f, sum2 = 0.0f, sum3 = 0.0f;
            float sum4 = 0.0f, sum5 = 0.0f, sum6 = 0.0f, sum7 = 0.0f;

            for (unsigned int k = 0; k < filter_len; k++) {
                float kval = filter[k];
                const float *in_ptr = &input[s + k];
                sum0 += kval * in_ptr[0];
                sum1 += kval * in_ptr[1];
                sum2 += kval * in_ptr[2];
                sum3 += kval * in_ptr[3];
                sum4 += kval * in_ptr[4];
                sum5 += kval * in_ptr[5];
                sum6 += kval * in_ptr[6];
                sum7 += kval * in_ptr[7];
            }

            out_row[s + 0] = sum0;
            out_row[s + 1] = sum1;
            out_row[s + 2] = sum2;
            out_row[s + 3] = sum3;
            out_row[s + 4] = sum4;
            out_row[s + 5] = sum5;
            out_row[s + 6] = sum6;
            out_row[s + 7] = sum7;
        }

        /* Handle remaining samples */
        for (; s < n_out; s++) {
            float sum = 0.0f;
            for (unsigned int k = 0; k < filter_len; k++) {
                sum += filter[k] * input[s + k];
            }
            out_row[s] = sum;
        }
    }
}
