/**
 * ADPCM decoder
 * Copyright (c) 2020 Adam Higerd <chighland@gmail.com>
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy of
 * this software and associated documentation files (the "Software"), to deal in
 * the Software without restriction, including without limitation the rights to
 * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
 * of the Software, and to permit persons to whom the Software is furnished to do
 * so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
 * OR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
 * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
 * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
 * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */

#include "adpcmdecoder.h"
#include <iostream>

template <typename T, typename T2>
static inline T clamp(T2 value, T lower, T upper)
{
  return value < lower ? lower : (value > upper ? upper : value);
}

template <typename T, typename Container>
static inline T parseInt(const Container& buffer, int offset)
{
  uint64_t result = 0;
  for (int i = sizeof(T) - 1; i >= 0; --i) {
    result = (result << 8) | uint8_t(buffer[offset + i]);
  }
  return T(result);
}

static const int16_t adpcmIndex[] = { -1, -1, -1, -1, 2, 4, 6, 8, };

static const int16_t adpcmStep[] = {
      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,
};
static const int16_t maxAdpcmStep = (sizeof(adpcmStep) >> 1) - 1;

AdpcmDecoder::AdpcmDecoder(int16_t initialPredictor, int16_t initialStep)
: predictor(initialPredictor), index(clamp<int8_t>(initialStep, 0, maxAdpcmStep))
{
  // initializers only
}

int16_t AdpcmDecoder::getNextSample(uint8_t value)
{
  int16_t step = adpcmStep[index];
  int32_t delta = step >> 3;
  if (value & 0x04) delta += step;
  if (value & 0x02) delta += step >> 1;
  if (value & 0x01) delta += step >> 2;
  if (value & 0x08) delta = -delta;
  /*
  // This implementation is simpler and obeys the spec, but it suffers from low-bit rounding
  // errors that introduce a slow DC drift that causes looping to produce pops.
  int32_t delta = (int32_t(value & 0x08 ? -step : step) * ((value << 1 & 0x0f) + 0x01)) >> 3;
  */
  if (predictor + delta == -0x8000) {
    predictor = -0x8000;
  } else {
    predictor = clamp<int16_t>(predictor + delta, -0x7fff, 0x7fff);
  }
  index = clamp<int8_t>(index + adpcmIndex[value & 0x07], 0, maxAdpcmStep);
  return predictor;
}

std::vector<int16_t> AdpcmDecoder::decodeFile(const std::vector<char>& data, uint32_t offset, uint32_t length)
{
  if (!length) {
    length = data.size() - offset;
  }
  AdpcmDecoder adpcm(parseInt<int16_t>(data, offset), parseInt<int16_t>(data, offset + 2));
  return adpcm.decode(data, offset + 4, length - 4);
}

std::vector<int16_t> AdpcmDecoder::decode(const std::vector<char>& data, uint32_t offset, uint32_t length)
{
  if (!length) {
    length = data.size() - offset;
  }
  std::vector<int16_t> sample;
  sample.reserve(length << 1);
  for (int i = 0; i < length; i++) {
    sample.push_back(getNextSample(data[offset + i] & 0x0f));
    sample.push_back(getNextSample(uint8_t(data[offset + i] & 0xf0) >> 4));
  }
  return sample;
}
