#define STB_DEFINE
#define STB_ONLY
#include "stb.h"           /*  http://nothings.org/stb.h  */
#include "stb_vorbis.c"

void write_floats(FILE *g, int len, float *left, float *right);
void show_info(stb_vorbis *v);

// stb_vorbis_decode_filename: decode an entire file to interleaved shorts
void test_decode_filename(FILE *g, char *filename)
{
   short *decoded;
   int channels, len;
   len = stb_vorbis_decode_filename(filename, &channels, &decoded);
   if (len)
      fwrite(decoded, 2, len*channels, g);
   else
      stb_fatal("Couldn't open {%s}", filename);
}

void test_get_frame_short_interleaved(FILE *g, char *filename)
{
   short sbuffer[8000];
   int n, error;
   stb_vorbis *v = stb_vorbis_open_filename(filename, &error, NULL);
   if (!v) stb_fatal("Couldn't open {%s}", filename);
   show_info(v);

   while (0 != (n=stb_vorbis_get_frame_short_interleaved(v, 2, sbuffer, 8000))) {
      fwrite(sbuffer, 2, n*2, g);
   }
   stb_vorbis_close(v);
}

void test_get_samples_short_interleaved(FILE *g, char *filename)
{
   int error;
   stb_vorbis *v = stb_vorbis_open_filename(filename, &error, NULL);
   if (!v) stb_fatal("Couldn't open {%s}", filename);
   show_info(v);

   for(;;) {
      int16 sbuffer[333];
      int n;
      n = stb_vorbis_get_samples_short_interleaved(v, 2, sbuffer, 333);
      if (n == 0)
         break;
      fwrite(sbuffer, 2, n*2, g);
   }
   stb_vorbis_close(v);
}

void test_get_frame_float(FILE *g, char *filename)
{
   int error;
   stb_vorbis *v = stb_vorbis_open_filename(filename, &error, NULL);
   if (!v) stb_fatal("Couldn't open {%s}", filename);
   show_info(v);

   for(;;) {
      int n;
      float *left, *right;
      float **outputs;
      int num_c;
      n = stb_vorbis_get_frame_float(v, &num_c, &outputs);
      if (n == 0)
         break;
      left = outputs[0];
      right = outputs[num_c > 1];
      write_floats(g, n, left, right);
   }
   stb_vorbis_close(v);
}


// in push mode, you can load your data any way you want, then
// feed it a little bit at a time. this is the preferred way to
// handle reading from a packed file or some custom stream format;
// instead of putting callbacks inside stb_vorbis, you just keep
// a little buffer (it needs to be big enough for one packet of
// audio, except at the beginning where you need to buffer up the
// entire header).
//
// for this test, I just load all the data and just lie to stb_vorbis
// and claim I only have a little of it
void test_decode_frame_pushdata(FILE *g, char *filename)
{
   int p,q, len, error, used;
   stb_vorbis *v;
   uint8 *data = stb_file(filename, &len);

   if (!data) stb_fatal("Couldn't open {%s}", filename);

   p = 0;
   q = 1;
  retry:
   v = stb_vorbis_open_pushdata(data, q, &used, &error, NULL);
   if (v == NULL) {
      if (error == VORBIS_need_more_data) {
         q += 1;
         goto retry;
      }
      fprintf(stderr, "Error %d\n", error);
      exit(1);
   }
   p += used;

   show_info(v);

   for(;;) {
      int k=0;
      int n;
      float *left, *right;
      uint32 next_t=0;

      float **outputs;
      int num_c;
      q = 32;
     retry3:
      if (q > len-p) q = len-p;
      used = stb_vorbis_decode_frame_pushdata(v, data+p, q, &num_c, &outputs, &n);
      if (used == 0) {
         if (p+q == len) break; // no more data, stop
         if (q < 128) q = 128;
         q *= 2;
         goto retry3;
      }
      p += used;
      if (n == 0) continue; // seek/error recovery
      left = outputs[0];
      right = num_c > 1 ? outputs[1] : outputs[0];
      write_floats(g, n, left, right);
   }
   stb_vorbis_close(v);
}

void write_floats(FILE *g, int len, float *left, float *right)
{
   const float scale = 32768;
   int j;
   for (j=0; j < len; ++j) {
      int16 x,y;
      x = (int) stb_clamp((int) (scale * left[j]), -32768, 32767);
      y = (int) stb_clamp((int) (scale * right[j]), -32768, 32767);
      fwrite(&x, 2, 1, g);
      fwrite(&y, 2, 1, g);
   }
}

void show_info(stb_vorbis *v)
{
   if (v) {
      stb_vorbis_info info = stb_vorbis_get_info(v);
      printf("%d channels, %d samples/sec\n", info.channels, info.sample_rate);
      printf("Predicted memory needed: %d (%d + %d)\n", info.setup_memory_required + info.temp_memory_required,
                info.setup_memory_required, info.temp_memory_required);
   }
}

int main(int argc, char **argv)
{
   FILE *g=NULL;
   char *outfile = "vorbis_test.out";
   char *infile;
   int n=0;

   if (argc > 1 && argv[1][1] == 0 && argv[1][0] >= '1' && argv[1][0] <= '5')
      n = atoi(argv[1]);

   if (argc < 3 || argc > 4 || n == 0) {
      stbprint("Usage: sample {code} {vorbis-filename} [{output-filename}]\n"
                           "Code is one of:\n"
                           "    1  -  test stb_vorbis_decode_filename\n"
                           "    2  -  test stb_vorbis_get_frame_short_interleaved\n"
                           "    3  -  test stb_vorbis_get_samples_short_interleaved\n"
                           "    4  -  test stb_vorbis_get_frame_float\n"
                           "    5  -  test stb_vorbis_decode_frame_pushdata\n");
      return argc != 1;
   }

   infile = argv[2];

   if (argc > 3)
      outfile = argv[3];
   if (strlen(outfile) >= 4 && 0==stb_stricmp(outfile + strlen(outfile) - 4, ".ogg"))
      stb_fatal("You specified a .ogg file as your output file, which you probably didn't actually want.");

   if (!strcmp(outfile, "stdout") || !strcmp(outfile, "-") || !strcmp(outfile, "-stdout"))
      g = stdout;
   else
      g = fopen(outfile, "wb");
   if (!g) stb_fatal("Couldn't open {%s} for writing", outfile);

   switch (n) {
      case 1: test_decode_filename(g, infile); break;
      case 2: test_get_frame_short_interleaved(g, infile); break;
      case 3: test_get_samples_short_interleaved(g, infile); break;
      case 4: test_get_frame_float(g, infile); break;
      case 5: test_decode_frame_pushdata(g, infile); break;
      default: stb_fatal("Unknown option {%d}", n);
   }
   fclose(g);
   return 0;
}

void test_push_mode_forever(FILE *g, char *filename)
{
   int p,q, len, error, used;
   uint8 *data = stb_file(filename, &len);
   stb_vorbis *v;

   if (!data) stb_fatal("Couldn't open {%s}", filename);

   p = 0;
   q = 1;
  retry:
   v = stb_vorbis_open_pushdata(data, q, &used, &error, NULL);
   if (v == NULL) {
      if (error == VORBIS_need_more_data) {
         q += 1;
         goto retry;
      }
      printf("Error %d\n", error);
      exit(1);
   }
   p += used;

   show_info(v);

   for(;;) {
      int k=0;
      int n;
      float *left, *right;
      float **outputs;
      int num_c;
      q = 32;
      retry3:
      if (q > len-p) q = len-p;
      used = stb_vorbis_decode_frame_pushdata(v, data+p, q, &num_c, &outputs, &n);
      if (used == 0) {
         if (p+q == len) {
            // seek randomly when at end... this makes sense when listening to it, but dumb when writing to file
            p = stb_rand();
            if (p < 0) p = -p;
            p %= (len - 8000);
            stb_vorbis_flush_pushdata(v);
            q = 128;
            goto retry3;
         }
         if (q < 128) q = 128;
         q *= 2;
         goto retry3;
      }
      p += used;
      if (n == 0) continue;
      left = outputs[0];
      right = num_c > 1 ? outputs[1] : outputs[0];
      write_floats(g, n, left, right);
   }
   stb_vorbis_close(v);
}

