// Copyright 2014-2025 Jesper Larsson
//
// This file is part of Klipspringer, <https://klipspringer.avadeaux.net/>
//
// Klipspringer is free software: you can redistribute it and/or modify it under the terms of the
// GNU General Public License as published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// Klipspringer is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
// even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// General Public License for more details.
//
// You should have received a copy of the GNU General Public License along with Klipspringer. If
// not, see <https://www.gnu.org/licenses/>.

#include <stdlib.h>
#include <stdint.h>
#include "Library.h"

// Generated by javac -h
#include "net_avadeaux_klipspringer_codec_PcmBuffer_PcmBuffer1.h"
#include "net_avadeaux_klipspringer_codec_PcmBuffer_PcmBuffer2.h"
#include "net_avadeaux_klipspringer_codec_PcmBuffer_PcmBuffer3.h"
#include "net_avadeaux_klipspringer_codec_PcmBuffer_PcmBuffer4.h"

// A pointer to this struct is cast to an integer and used for interaction with the Java side.
typedef struct {
    void *restrict buf;         // underlying storage of Java-side direct buffer
    bool buf_signed;            // buf is signed
    bool buf_bigend;            // buf is in big endian
    uint32_t sign;              // 1 in sign bit position only
    uint32_t xsign;             // sign if LSBX, zero otherwise
    uint8_t shift;              // shifted positions (zero unless MSB)
    unsigned channels;          // number of channels in format
} BufferRecord;

// -------------------------------------------------------------------------------------------------
// Base class methods.

#define METHOD(name) JNICALL Java_net_avadeaux_klipspringer_codec_PcmBuffer_ ## name

JNIEXPORT jlong
METHOD(create) (JNIEnv *env,
                jobject jthis,
                jobject jbuf,
                jint jchannels,
                jboolean jsigned,
                jboolean jbigend,
                jint jsign,
                jint jxsign,
                jint jshift)
{
    void *buf = (*env)->GetDirectBufferAddress(env, jbuf);
    if (buf == NULL) { raiseError(env, "Cannot get byte buffer address"); return 0; }
    BufferRecord *brec = malloc(sizeof *brec);
    if (brec == NULL) { raiseError(env, "Failed to allocate decoder record"); return 0; }

    brec->buf = buf;
    brec->buf_signed = jsigned;
    brec->buf_bigend = jbigend;
    brec->sign = jsign;
    brec->xsign = jxsign;
    brec->shift = jshift;
    brec->channels = jchannels;

    return (intptr_t) brec;
}

JNIEXPORT void
METHOD(free) (JNIEnv *env,
              jobject jthis,
              long jbrec)
{
    free((BufferRecord *) (intptr_t) jbrec);
}

// -------------------------------------------------------------------------------------------------
// Macros common to put and monomix.

// Sets the offset variables C0 and C2 for 3-byte access.
#define ENDIAN_OFF(ISBIG, C)                                            \
    uint8_t C ## 0, C ## 2;                                             \
    if (ISBIG) {                                                        \
        C ## 0 = 16;                                                    \
        C ## 2 = 0;                                                     \
    } else {                                                            \
        C ## 0 = 0;                                                     \
        C ## 2 = 16;                                                    \
    }

// -------------------------------------------------------------------------------------------------
// Subclass put methods.

// Method head
#define PUT(S) JNICALL Java_net_avadeaux_klipspringer_codec_            \
    ## PcmBuffer_00024PcmBuffer ## S ## _put                            \
    (JNIEnv *env,                                                       \
     jobject jthis,                                                     \
     jlong jbrec,                                                       \
     jobject jdata,                                                     \
     jint jdataPos,                                                     \
     jboolean jdataSigned,                                              \
     jint jdataSs,                                                      \
     jboolean jdataBigend,                                              \
     jint jbufPos,                                                      \
     jint jsamples)                                                     \

// Common declaractions
#define PUT_DECL(T)                                                     \
    BufferRecord *brec = (BufferRecord *) (intptr_t) jbrec;             \
    T flip = jdataSigned == brec->buf_signed ? 0 : brec->sign;          \
    T xsign = brec->xsign;                                              \
    uint8_t shift = brec->shift;                                        \
    uint8_t *data8 = (*env)->GetDirectBufferAddress(env, jdata);        \
    if (data8 == NULL) {                                                \
        raiseError(env, "Cannot get data byte buffer address");         \
        return;                                                         \
    }                                                                   \
    data8 += jdataPos;

// The main loop of put. In the loop body, the normal case is that data[i] is the value to read, and
// buf[i] the destination. The exception is for ss=3: If both data and buf have ss=3, data[i to i+2]
// are the bytes of the sample to read and buf[i to i+2] the bytes to write. If only one of the
// arrays has ss=3, we use an extra loop variable j for the byte positions (read from data8[j to
// j+2] or write to buf[j to j+2], while i has its normal meaning.
//
// - T     the type of a sample value
// - DATA  expression that reads a value from data, position i or j
// - WRITE expression that writes v to buf
// - II    increment per sample of buf, 3 if both buf and data have ss=3, 1 otherwise
// - JDEF  definition of j, or empty
// - JINCR expression that increments j, or empty
#define PUT_LOOP(T, DATA, WRITE, II, JDEF, JINCR)                       \
    for (int i = 0 JDEF; i < II * jsamples; i += II JINCR) {            \
        T v = DATA ^ flip;                                              \
        v = (v | -(v & xsign)) << shift;                                \
        WRITE;                                                          \
    }

// Read expression for 16-bit or 32-bit data[i].
#define DATA1(BITS)     (jdataBigend ? be ## BITS ## toh(data[i]) : le ## BITS ## toh(data[i]))

// Read expression for 24-bit data8[i to i+2] or [j to j+2].
#define DATA3(I)        (data8[I] << d0 | data8[I+1] << 8 | data8[I+2] << d2)

// Write expression for 16-bit or 32-bit buf[i].
#define WRITE1(BITS)    buf[i] = buf_bigend ? htobe ## BITS(v) : htole ## BITS(v)

// Write expression for 24-bit buf[i to i+2] or [j to j+2].
#define WRITE3(I)       buf[I] = v >> b0; buf[I+1] = v >> 8; buf[I+2] = v >> b2

// Comma.
#define CM              ,

JNIEXPORT void PUT(1)
{
    PUT_DECL(uint8_t);
    uint8_t *restrict buf = brec->buf;
    buf += jbufPos;

    if (jdataSs == 2) {
        PUT_LOOP(uint8_t, data8[j], buf[i] = v, 1, CM j = jdataBigend ? 1 : 0, CM j += 2);
    } else {
        PUT_LOOP(uint8_t, data8[i], buf[i] = v, 1,,);
    }
}

JNIEXPORT void PUT(2)
{
    PUT_DECL(uint16_t);
    uint16_t *restrict buf = brec->buf;
    buf += jbufPos/2;
    bool buf_bigend = brec->buf_bigend;

    if (jdataSs == 2) {
        uint16_t *data = (uint16_t *) data8;
        PUT_LOOP(uint16_t, DATA1(16), WRITE1(16), 1,,);
    } else {
        PUT_LOOP(uint16_t, data8[i], WRITE1(16), 1,,);
    }
}

JNIEXPORT void PUT(3)
{
    PUT_DECL(uint32_t);
    uint8_t *restrict buf = brec->buf;
    buf += jbufPos;
    ENDIAN_OFF(brec->buf_bigend, b);

    if (jdataSs == 4) {
        uint32_t *data = (uint32_t *) data8;
        PUT_LOOP(uint32_t, DATA1(32), WRITE3(j), 1, CM j = 0, CM j += 3);
    } else {
        ENDIAN_OFF(jdataBigend, d);
        PUT_LOOP(uint32_t, DATA3(i), WRITE3(i), 3,,);
    }
}

JNIEXPORT void PUT(4)
{
    PUT_DECL(uint32_t);
    uint32_t *restrict buf = brec->buf;
    buf += jbufPos/4;
    bool buf_bigend = brec->buf_bigend;

    if (jdataSs == 4) {
        uint32_t *data = (uint32_t *) data8;
        PUT_LOOP(uint32_t, DATA1(32), WRITE1(32), 1,,);
    } else {
        ENDIAN_OFF(jdataBigend, d);
        PUT_LOOP(uint32_t, DATA3(j), WRITE1(32), 1, CM j = 0, CM j += 3);
    }
}

// -------------------------------------------------------------------------------------------------
// Subclass monomix methods

// Method head.
#define MONOMIX(ss) JNICALL Java_net_avadeaux_klipspringer_codec_       \
    ## PcmBuffer_00024PcmBuffer ## ss ## _monomix                       \
    (JNIEnv *env,                                                       \
     jobject jthis,                                                     \
     jlong jbrec,                                                       \
     jint jpos,                                                         \
     jint jframes)

// Common declaractions, where T is the individual sample type.
#define MIX_DECL(T)                                                     \
    BufferRecord *brec = (BufferRecord *) (intptr_t) jbrec;             \
    T xsign = (T) brec->xsign << brec->shift;                           \
    T sflip = brec->buf_signed ? (T) brec->sign << brec->shift : 0;     \
    T mask = ~-((T) brec->sign << brec->shift+1);                       \
    unsigned channels = brec->channels;

// The main loop of put. In the loop body, the normal case is that buf[i+c] is the value read and
// write. The exception is for ss=3, in which case it is buf[i+c to i+c+2].
//
// - T     the type of a sample value calculation, wide enough for the sum of frame samples
// - II    increment per sample of buf, 3 if ss=3, 1 otherwise
// - RMIX  expression that reads a value from buf, position given by i and c
// - WMIX  expression that writes the corresponding positions
#define MIX_LOOP(T, II, RMIX, WMIX)                                     \
    for (int i = 0; i < II * jframes * channels; i += II * channels) {  \
        T v = 0;                                                        \
        for (int c = 0; c < II * channels; c += II) {                   \
            v += (RMIX ^ sflip) & mask;                                 \
        }                                                               \
        v = v/channels ^ sflip;                                         \
        v |= -(v & xsign);                                              \
        for (int c = 0; c < II * channels; c += II) {                   \
            WMIX;                                                       \
        }                                                               \
    }

// Read expression for 16-bit or 32-bit buf[i].
#define RMIX1(BITS)     (buf_bigend ? be ## BITS ## toh(buf[i+c]) : le ## BITS ## toh(buf[i+c]))

// Read expression for 24-bit buf[i].
#define RMIX3           (buf[i+c] << b0 | buf[i+c+1] << 8 | buf[i+c+2] << b2)

// Write expression for 16-bit or 32-bit buf[i].
#define WMIX1(BITS)     buf[i+c] = buf_bigend ? htobe ## BITS(v) : htole ## BITS(v)

// Read expression for 24-bit buf[i].
#define WMIX3           buf[i+c] = v >> b0; buf[i+c+1] = v >> 8; buf[i+c+2] = v >> b2

// Separates out the common case of two channels (stereo) for efficiency.
#define MIX_CASES(T, II, BUF, WRITE)                                    \
    if (channels == 2) {                                                \
        MIX_LOOP(T, II, BUF, WRITE);                                    \
    } else {                                                            \
        MIX_LOOP(T, II, BUF, WRITE);                                    \
    }

JNIEXPORT void MONOMIX(1)
{
    MIX_DECL(uint8_t);
    uint8_t *buf = brec->buf;
    buf += jpos;
    MIX_CASES(unsigned, 1, buf[i], buf[i] = v);
}

JNIEXPORT void MONOMIX(2)
{
    MIX_DECL(uint16_t);
    uint16_t *buf = brec->buf;
    buf += jpos/2;
    bool buf_bigend = brec->buf_bigend;
    MIX_CASES(uint32_t, 1, RMIX1(16), WMIX1(16));
}

JNIEXPORT void MONOMIX(3)
{
    MIX_DECL(uint32_t);
    uint8_t *buf = brec->buf;
    buf += jpos;
    ENDIAN_OFF(brec->buf_bigend, b);
    MIX_CASES(uint32_t, 3, RMIX3, WMIX3);
}

JNIEXPORT void MONOMIX(4)
{
    MIX_DECL(uint32_t);
    uint32_t *buf = brec->buf;
    buf += jpos/4;
    bool buf_bigend = brec->buf_bigend;
    MIX_CASES(uint64_t, 1, RMIX1(32), WMIX1(32));
}
