
/* C++ header */
#include <iostream>

/* C header */
#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <cstdint>
#include <cmath>

#include <endian.h>

/* UNIX header */
#include <unistd.h>

/* ffmpeg */
extern "C" {
#include <libavutil/cpu.h>
}

/* AVIFILE header */
#include <avifile.h>
#include <aviplay.h>
#include <avm_fourcc.h>
#include <avm_cpuinfo.h>
#include <avm_except.h>
#include <avm_creators.h>
#include <utils.h>
#include <version.h>
#include <videodecoder.h>
#include <audiodecoder.h>
#include <renderer.h>

/* AUBIO */
#include <aubio/aubio.h>
#include <aubio/fvec.h>

float getFreq(const char *audioBuf, int audioBufSize, int bitsPerSample, int channels, int sampleRate) {
  int i;
  int n=0;
  float freq=0.0;
  fvec_t *input;
  fvec_t *out;
  aubio_pitch_t *pitch1;

  freq = nanf("");
  if (audioBuf == NULL || audioBufSize <= 0) {
    return freq;
  }
  
  if (bitsPerSample == 32) {
    n = audioBufSize / 4;
    n = n / channels;
    pitch1 = new_aubio_pitch ("default", n, n, sampleRate);
    input = new_fvec(n);
    out = new_fvec(1);
    for (i = 0; i<n; i++) {
      uint32_t *p2;
      uint32_t data2;
      int32_t data1 = 0;
      int i1;
      i1 = i*4*channels;
      if (i1 < audioBufSize) {
	p2 = (uint32_t *)&(audioBuf[i1]);
	data2 = *p2;
	data1 = (int32_t) le32toh(data2);
      }
      fvec_set_sample(input, ((float)(data1)) / (((float)(INT32_MAX))+1.0), i);
    }
    aubio_pitch_do(pitch1, input, out);
    freq = fvec_get_sample(out, 0);
    del_fvec (out);
    del_fvec (input);
    del_aubio_pitch (pitch1);
    return freq;
  } else if (bitsPerSample == 16) {
    n = audioBufSize / 2;
    n = n / channels;
    pitch1 = new_aubio_pitch ("default", n, n, sampleRate);
    input = new_fvec(n);
    out = new_fvec(1);
    for (i = 0; i<n; i++) {
      uint16_t *p2;
      uint16_t data2;
      int16_t data1 = 0;
      int i1;
      i1 = i*2*channels;
      if (i1 < audioBufSize) {
	p2 = (uint16_t *)&(audioBuf[i1]);
	data2 = *p2;
	data1 = (int16_t) le16toh(data2);
      }
      fvec_set_sample(input, ((float)(data1)) / (((float)(INT16_MAX))+1.0), i);
    }
    aubio_pitch_do(pitch1, input, out);
    freq = fvec_get_sample(out, 0);
    del_fvec (out);
    del_fvec (input);
    del_aubio_pitch (pitch1);
    aubio_cleanup ();
    return freq;
  } else if (bitsPerSample == 8) {
    n = audioBufSize;
    n = n / channels;
    pitch1 = new_aubio_pitch ("default", n, n, sampleRate);
    input = new_fvec(n);
    out = new_fvec(1);
    for (i = 0; i<n; i++) {
      int8_t *p2;
      int8_t data1 = 0;
      int i1;
      i1 = i*channels;
      if (i1 < audioBufSize) {
	p2 = (int8_t *)&(audioBuf[i1]);
	data1 = *p2;
      }
      fvec_set_sample(input, ((float)(data1)) / (((float)(INT8_MAX))+1.0), i);
    }
    aubio_pitch_do(pitch1, input, out);
    freq = fvec_get_sample(out, 0);
    del_fvec (out);
    del_fvec (input);
    del_aubio_pitch (pitch1);
    aubio_cleanup ();
    return freq;
  }
  return freq;
}

int main(int argc, char *argv[]) {
  int version;
  char *filename;
  avm::IReadFile *file = NULL;
  avm::IReadStream *videoStream;
  avm::IReadStream *audioStream;
  BITMAPINFOHEADER bh;
  BITMAPINFOHEADER bhy;
  WAVEFORMATEX wave_fmt;
  fourcc_t fcc;
  avm::IVideoDecoder::CAPS caps;
  int width = 0;
  int height = 0;
  char audioBuf[102400];
  size_t totalAudioSamplesRead = 0;
  size_t totalAudioBytesRead = 0;
  bool audioCheckedFlag = false;
  char *arch;
  
  /* check version */
  version = GetAvifileVersion();
  std::cout << "This binary was compiled for Avifile ver. " << AVIFILE_VERSION
	    << ", "
	    << "the library is ver. " << GetAvifileVersion()
	    << std::endl;
  if (version != AVIFILE_VERSION) {
    std::cerr << "Version mismatched. " << " -  Aborting." << std::endl;
    return 1;
  }

  /* disable neon when architecture is armhf */
  arch = getenv("DEB_BUILD_ARCH");
  if (arch && (strcmp(arch, "armhf") == 0)) {
    unsigned flags;

    flags = av_get_cpu_flags();
    std::cout << "Old cpu flags: 0x" << std::hex << flags
	      << std::dec << std::endl;
    if (av_parse_cpu_caps(&flags, "-neon") >= 0) {
      av_force_cpu_flags(flags);
      std::cout << "New cpu flags: 0x" << std::hex << flags
                << std::dec << std::endl;
    } else {
      std::cout << "failed disable neon, ffmpeg built without support for it?" << std::endl;
    }
  }

  /* get filename */
  if (argc != 2) {
    std::cerr << "No filename provided" << std::endl;
    return 2;
  }
  filename = argv[1];

  /* open file */
  file = avm::CreateReadFile(filename);
  if (!file) {
    std::cerr << "Open file " << filename << " error" << std::endl;
    return 3;
  }

  /* open video stream */
  videoStream = file->GetStream(0, avm::IStream::Video);
  if (!videoStream) {
    std::cerr << "Cannot get video stream" << std::endl;
    return 4;
  }
  if (videoStream->StartStreaming() != 0) {
    std::cerr << "Cannot decode video stream" << std::endl;
    return 5;
  }

  /* open audio stream */
  audioStream = file->GetStream(0, avm::IStream::Audio);
  if (!audioStream) {
    std::cerr << "Cannot get audio stream" << std::endl;
    return 4;
  }
  if (audioStream->StartStreaming() != 0) {
    std::cerr << "Cannot decode audio stream" << std::endl;
    return 5;
  }

  /* get information of video stream */
  videoStream->GetOutputFormat(&bh, sizeof(bh));
  std::cout << "bh.biHeight = " << bh.biHeight << ", "
	    << "bh.biWidth = " << bh.biWidth << std::endl;
  if (bh.biHeight < 0) {
    bh.biHeight = -bh.biHeight;
    std::cout << "bh.biHeight < 0, correct the value to "
	      << bh.biHeight << std::endl;
  }
  std::cout << "Movie size: " << bh.biWidth << " x " << bh.biHeight
	    << "  [" << ((char *)(&bh.biCompression)) << "]" << std::endl;
  width = bh.biWidth;
  height = bh.biHeight;
  if (width <= 0 || height <= 0) {
    return 6;
  }

  /* get information of audio stream */
  audioStream->GetAudioDecoder()->GetOutputFormat(&wave_fmt);
  std::cout << "audio format: " << wave_fmt.wFormatTag << ", "
	    << "Channels: " << wave_fmt.nChannels << ", "
	    << "Samples/sec: " << wave_fmt.nSamplesPerSec << ", "
	    << "Bits/Sample: " << wave_fmt.wBitsPerSample << ", "
	    << "Bytes/sec: " << wave_fmt.nAvgBytesPerSec
	    << std::endl;
  if (wave_fmt.nChannels <= 0 || wave_fmt.nSamplesPerSec <= 0) {
    return 6;
  }
  
  /* video length */
  std::cout << "Video Length: " << videoStream->GetLength() << std::endl;
  std::cout << "Video Pos: " << videoStream->GetPos() << std::endl;

  if (videoStream->GetLength() <= 0) {
    return 7;
  }

  /* audio length */
  std::cout << "Audio Length: " << audioStream->GetLength() << std::endl;
  std::cout << "Audio Pos: " << audioStream->GetPos() << std::endl;

  if (audioStream->GetLength() <= 0) {
    return 7;
  }

  /* video format */
  videoStream->GetVideoFormat(&bhy, sizeof(bhy));
  caps = videoStream->GetVideoDecoder()->GetCapabilities();
  std::cout << "Decoder YUV capabilities: 0x" << std::hex << caps
	    << std::dec << std::endl;
  if (caps & avm::IVideoDecoder::CAP_YUY2) {
    fcc = fccYUY2;
    std::cout << "CAPS is CAP_YUY2" << std::endl;
  } else if (caps & avm::IVideoDecoder::CAP_YV12) {
    fcc = fccYV12;
    std::cout << "CAPS is CAP_YV12" << std::endl;
  } else if (caps & avm::IVideoDecoder::CAP_UYVY) {
    fcc = fccUYVY;
    std::cout << "CAPS is CAP_UYVY" << std::endl;
  } else {
    std::cerr << "YUV format unsupported by decoder" << std::endl;
    return 8;
  }

  if (fcc) {
    if (videoStream->GetVideoDecoder()->SetDestFmt(0, fcc)) {
      std::cerr << "Error setting YUV decoder output" << std::endl;
      return 9;
    }
  }

  /* decoding video/audio */
  videoStream->SetBuffering(4, 0);
  while (!videoStream->Eof() || !audioStream->Eof()) {
    avm::CImage* im;
    uint8_t *pixelData;
    
    std::cout << "TIME " << videoStream->GetTime() << " " << videoStream->GetPos() << std::endl;

    if (!videoStream->Eof()) {
      videoStream->ReadFrame(true);
      im = videoStream->GetFrame();
    } else {
      im = NULL;
    }
    if (im) {
      if (im->Width() != width || im->Height() != height) {
	std::cerr << "IMAGE " << im->Width() << " x " << im->Height() << std::endl;
	return 10;
      }

      avm::BitmapInfo bi1(im->Width(), im->Height(), 24);
      bi1.SetRGB();
      avm::CImage imRGB(im, &bi1);

      if (imRGB.Width() != im->Width() || imRGB.Height() != im->Height()) {
	std::cerr << "imRGB size (" << imRGB.Width()
		  << " x " << imRGB.Height() << ")"
		  << " != " << "im size (" << im->Width()
		  << " x " << im->Height() << ")" << std::endl;
	return 11;
      }

      if (imRGB.Bpp() != 3) {
	std::cerr << "imRGB.Bpp() = " << imRGB.Bpp()
		  << " != 3" << std::endl;
	return 12;
      }

      if (imRGB.Depth() != 24) {
	std::cerr << "imRGB.Depth() = " << imRGB.Depth()
		  << " != 24" << std::endl;
	return 13;
      }

      /* Test for frame no. 100 ~ 120 */
      if (100 <= videoStream->GetPos() && videoStream->GetPos() <= 120) {
	/* check for Red */
	pixelData = imRGB.At(im->Width()*11/14,im->Height()*1/3);
	if (!(pixelData[0] > 128 && pixelData[1] < 128 && pixelData[2] < 128)) {
	  std::cerr << "Red check failed at frame " << videoStream->GetPos() << std::endl;
	  return 14;
	}
	/* check for Green */
	pixelData = imRGB.At(im->Width()*7/14,im->Height()*1/3);
	if (!(pixelData[0] < 128 && pixelData[1] > 128 && pixelData[2] < 128)) {
	  std::cerr << "Green check failed at frame " << videoStream->GetPos() << std::endl;
	  return 15;
	}
	/* check for Blue */
	pixelData = imRGB.At(im->Width()*13/14,im->Height()*1/3);
	if (!(pixelData[0] < 128 && pixelData[1] < 128 && pixelData[2] > 128)) {
	  std::cerr << "Blue check failed at frame " << videoStream->GetPos() << std::endl;
	  return 16;
	}
	std::cout << "RGB check passed" << std::endl;
      }
      im->Release();
    } else {
      std::cout << "Zero image!" << std::endl;
    }
    while (!audioStream->Eof() && (audioStream->GetTime() <= videoStream->GetTime() || videoStream->Eof()) ) {
      size_t samples_read;
      size_t bytes_read;
      audioStream->ReadFrames(audioBuf, sizeof(audioBuf), 1024, samples_read, bytes_read);
      std::cout << "Audio read " << samples_read << " samples, "
		<< bytes_read << " bytes." << std::endl;

      float freq;
      freq = getFreq(audioBuf, bytes_read, wave_fmt.wBitsPerSample, wave_fmt.nChannels, wave_fmt.nSamplesPerSec);
      std::cout << "Audio frequency " << freq << " hz" << std::endl;
      if (350.0 <= freq && freq <= 500.0) {
	audioCheckedFlag = true;
      }
      totalAudioSamplesRead += samples_read;
      totalAudioBytesRead += bytes_read;
    }
  }
  std::cout << "audio: totalAudioSamplesRead = " << totalAudioSamplesRead
	    << std::endl;
  std::cout << "audio: totalAudioBytesRead = " << totalAudioBytesRead
	    << std::endl;
  if (totalAudioSamplesRead != audioStream->GetLength()) {
    std::cerr << "totalAudioSampleRead " << totalAudioSamplesRead
	      << " != " << audioStream->GetLength() << std::endl;
    return 17;
  }
  if (!audioCheckedFlag) {
    std::cerr << "Didn't detect audio track with 440 hz" << std::endl;
    return 18;
  }
  std::cout << "Test Pass" << std::endl;
  aubio_cleanup ();
  return 0;

}
