#ifdef _WIN32
#include "./node_modules/node-addon-api/napi.h"
#else
#include <napi.h>
#endif
#include <cstdint>
#include <vector>

using namespace Napi;

////////// Intel ADPCM step variation table ////////////
static int indexTable[16] = {
    -1, -1, -1, -1, 2, 4, 6, 8,
    -1, -1, -1, -1, 2, 4, 6, 8
};

static int stepsizeTable[89] = {
    7,     8,     9,    10,    11,    12,    13,    14,    16,   17,
    19,    21,    23,    25,    28,    31,    34,    37,    41,    45,
    50,    55,    60,    66,    73,    80,    88,    97,   107,   118,
    130,   143,   157,   173,   190,   209,   230,   253,   279,   307,
    337,   371,   408,   449,   494,   544,   598,   658,   724,   796,
    876,   963,  1060,  1166,  1282,  1411,  1552,  1707,  1878,  2066,
    2272,  2499,  2749,  3024,  3327,  3660,  4026,  4428,  4871,  5358,
    5894,  6484,  7132,  7845,  8630,  9493, 10442, 11487, 12635, 13899,
    15289, 16818, 18500, 20350, 22385, 24623, 27086, 29794, 32767
};

struct adpcm_state {
    int valprev;
    int index;
};

void adpcm_coder(short indata[], char outdata[], int len, adpcm_state *state) {
	short *inp;			// Input buffer pointer
	signed char *outp;	// output buffer pointer
	int val;			// Current input sample value
	int sign;			// Current adpcm sign bit
	int delta;			// Current adpcm output value
	int diff;			// Difference between val and valprev
	int step;			// Stepsize
	int valpred;		// Predicted output value
	int vpdiff;			// Current change to valpred
	int index;			// Current step change index
	int outputbuffer;	// place to keep previous 4-bit value
	int bufferstep;		// toggle between outputbuffer/output

	outp = (signed char *)outdata;  // 输出指针
	inp = indata;           		// 输入指针

	valpred = state->valprev;
	index = state->index;
	step = stepsizeTable[index];

	bufferstep = 1;

	for( ; len > 0 ; len-- )
	{
		val = *inp++;

		diff = val - valpred;
		sign = (diff < 0) ? 8 : 0;
		if ( sign ) diff = (-diff);

		/* Step 2 - Divide and clamp */
		/* Note:
		** This code *approximately* computes:
		**    delta = diff*4/step;
		**    vpdiff = (delta+0.5)*step/4;
		** but in shift step bits are dropped. The net result of this is
		** that even if you have fast mul/div hardware you cannot put it to
		** good use since the fixup would be too expensive.
		*/
		delta = 0;
		vpdiff = (step >> 3);

		if( diff >= step )
		{
			delta = 4;
			diff -= step;
			vpdiff += step;
		}
		step >>= 1;
		if( diff >= step  )
		{
			delta |= 2;
			diff -= step;
			vpdiff += step;
		}
		step >>= 1;
		if( diff >= step )
		{
			delta |= 1;
			vpdiff += step;
		}

		/* Step 3 - Update previous value */
		if ( sign )	valpred -= vpdiff;
		else		valpred += vpdiff;

		/* Step 4 - Clamp previous value to 16 bits */
		if ( valpred > 32767 )			valpred = 32767;
		else if ( valpred < -32768 )	valpred = -32768;

		/* Step 5 - Assemble value, update index and step values */
		delta |= sign;

		index += indexTable[delta];
		if ( index < 0 ) index = 0;
		if ( index > 88 ) index = 88;
		step = stepsizeTable[index];

		/* Step 6 - Output value */
		if( bufferstep )	outputbuffer = (delta << 4) & 0xf0;
		else				*outp++ = (delta & 0x0f) | outputbuffer;

		bufferstep = !bufferstep;
	}

	/* Output last step, if needed */
	if ( !bufferstep )		*outp++ = outputbuffer;

	state->valprev = valpred;
	state->index = index;
}

void adpcm_decoder(char indata[], short outdata[], int len, adpcm_state *state) {
	signed char *inp;		// Input buffer pointer
	short *outp;			// output buffer pointer
	int sign;				// Current adpcm sign bit
	int delta;				// Current adpcm output value
	int step;				// Stepsize
	int valpred;			// Predicted value
	int vpdiff;				// Current change to valpred
	int index;				// Current step change index
	int inputbuffer;		// place to keep next 4-bit value
	int bufferstep;			// toggle between inputbuffer/input

	outp = outdata;
	inp = (signed char *)indata;

	valpred = state->valprev;
	index = state->index;
	step = stepsizeTable[index];

	bufferstep = 0;

	for ( ; len > 0 ; len-- )
	{
		/* Step 1 - get the delta value */
		if( bufferstep )	delta = inputbuffer & 0xf;
		else
		{
			inputbuffer = *inp++;
			delta = (inputbuffer >> 4) & 0xf;
		}
		bufferstep = !bufferstep;

		/* Step 2 - Find new index value (for later) */
		index += indexTable[delta];
		if ( index < 0 ) index = 0;
		if ( index > 88 ) index = 88;

		/* Step 3 - Separate sign and magnitude */
		sign = delta & 8;
		delta = delta & 7;

		/* Step 4 - Compute difference and new predicted value */
		/*
		** Computes 'vpdiff = (delta+0.5)*step/4', but see comment
		** in adpcm_coder.
		*/
		vpdiff = step >> 3;
		if( delta & 4 ) vpdiff += step;
		if( delta & 2 ) vpdiff += step>>1;
		if( delta & 1 ) vpdiff += step>>2;

		if( sign )	valpred -= vpdiff;
		else		valpred += vpdiff;

		/* Step 5 - clamp output value */
		if ( valpred > 32767 )			valpred = 32767;
		else if ( valpred < -32768 )	valpred = -32768;

		/* Step 6 - Update step value */
		step = stepsizeTable[index];

		/* Step 7 - Output value */
		*outp++ = valpred;
	}

	state->valprev = valpred;
	state->index = index;
}

// Wrapper for adpcm_coder
Napi::Value Encode(const Napi::CallbackInfo &info) {
    Napi::Env env = info.Env();

    // printf("========================= %d\n", info[0].IsArray());

    if (info.Length() < 3 || !info[0].IsArray() || !info[1].IsArray() || !info[2].IsObject()) {
        Napi::TypeError::New(env, "Expected: (indata: number[], outdata: number[], state: { valprev: number, index: number })").ThrowAsJavaScriptException();
        return env.Null();
    }

    Napi::Array indata = info[0].As<Napi::Array>();
    Napi::Array outdata = info[1].As<Napi::Array>();
    Napi::Object stateObj = info[2].As<Napi::Object>();

    int len = indata.Length();
    std::vector<short> input(len);
    std::vector<char> output(len / 2);

    for (int i = 0; i < len; i++) {
        input[i] = indata.Get(i).ToNumber().Int32Value();
    }

    adpcm_state state;
    state.valprev = stateObj.Get("valprev").ToNumber().Int32Value();
    state.index = stateObj.Get("index").ToNumber().Int32Value();

    adpcm_coder(input.data(), output.data(), len, &state);

    for (int i = 0; i < len / 2; i++) {
        outdata.Set(i, Napi::Number::New(env, output[i]));
    }

    stateObj.Set("valprev", Napi::Number::New(env, state.valprev));
    stateObj.Set("index", Napi::Number::New(env, state.index));

    return env.Null();
}

// Wrapper for adpcm_decoder
Napi::Value Decode(const Napi::CallbackInfo &info) {
    Napi::Env env = info.Env();

    // printf("========================= %d\n", info[0].IsArray());

    if (info.Length() < 3 || !info[0].IsArray() || !info[1].IsArray() || !info[2].IsObject()) {
        Napi::TypeError::New(env, "Expected: (indata: number[], outdata: number[], state: { valprev: number, index: number })").ThrowAsJavaScriptException();
        return env.Null();
    }

    Napi::Array indata = info[0].As<Napi::Array>();
    Napi::Array outdata = info[1].As<Napi::Array>();
    Napi::Object stateObj = info[2].As<Napi::Object>();

    int len = indata.Length();
    std::vector<char> input(len);
    std::vector<short> output(len * 2);

    for (int i = 0; i < len; i++) {
        input[i] = indata.Get(i).ToNumber().Int32Value();
		// printf("========================= %d - %d\n", indata.Get(i).ToNumber().Int32Value(), input[i]);
    }

    adpcm_state state;
    state.valprev = stateObj.Get("valprev").ToNumber().Int32Value();
    state.index = stateObj.Get("index").ToNumber().Int32Value();

	// printf("+++++++++++++++++++++++++++ %d\n", state.valprev);
	// printf("+++++++++++++++++++++++++++ %d\n", state.index);

    adpcm_decoder(input.data(), output.data(), len * 2, &state);

    for (int i = 0; i < len * 2; i++) {
        outdata.Set(i, Napi::Number::New(env, output[i]));
    }

    stateObj.Set("valprev", Napi::Number::New(env, state.valprev));
    stateObj.Set("index", Napi::Number::New(env, state.index));

    return env.Null();
}

// Initialize the addon
Napi::Object Init(Napi::Env env, Napi::Object exports) {
    exports.Set("encode", Napi::Function::New(env, Encode));
    exports.Set("decode", Napi::Function::New(env, Decode));
    return exports;
}

NODE_API_MODULE(adpcm, Init)