////////////////////////////////////////////////////////////////////////////////
// SmackerView.cpp
// Class for playing back SMK media files
// Copyright Bjoern Ganster
////////////////////////////////////////////////////////////////////////////////

#include "Audio.h"
#include "SmackerView.h"
#include "Sound.h"

#include <algorithm>

#define FILE_NUMBER FILE_NO_SMACKERVIEW_CPP

// Constructor
SmackerView::SmackerView(GLWindow* _parent, int _left, int _top, int _width, int _height)
: GLWindow("SmkView", SmackerView_ID, _parent, _left, _top, _width, _height),
  mVideoRemaining(false),
  mSmk(nullptr),
  mTexture(nullptr),
  mFrame(0),
  usf(0.0),
  mVideoStartTicks(0),
  mAudioStreamOffset(0),
  mSoundMutex(0)
{
}

// Destructor
SmackerView::~SmackerView()
{
	SDL_LockMutex(mSoundMutex);
	setAudioProvider(nullptr);
	close();
	mDepth[0] = 0;
	mChannels[0] = 0;
	mRate[0] = 0;
	SDL_UnlockMutex(mSoundMutex);
	SDL_DestroyMutex(mSoundMutex);
}

// Convert smk decoded into the format required by the audio device
void SmackerView::processAudio(const unsigned char* audioData, int dataSize)
{
	if (mChannels[0] == 0
	||  dataSize <= 0
	||  audioData == nullptr)
	{
		return;
	}

	// Construct and convert sound already!
	SET_SCOPE;
	Sound sound;
	sound.mAudioFormat.freq = mRate[0];
	if (mDepth[0] == 16) {
		sound.mAudioFormat.format = AUDIO_S16LSB;
	} else {
		sound.mAudioFormat.format = AUDIO_U8;
	}
	sound.mAudioFormat.channels = mChannels[0];
	sound.mData.write(0, audioData, dataSize);

	Audio* audio = getAudio();
	auto& audioSpec = audio->getAudioSpec();
	Sound convertedSound;
	convertedSound.copyConvert(sound, audioSpec);

	if (audioSpec.callback != nullptr) {
		// Specify sound via callback, prepare data for that
		SET_OP;
		SDL_LockMutex(mSoundMutex);
		if (mAudioStreamOffset > 0) {
			// Loose audio data that has been sent to the device already
			mAudioBuf.write(0, mAudioBuf.getPtr<char>(mAudioStreamOffset), mAudioBuf.getSize()-mAudioStreamOffset);
			mAudioBuf.resize(mAudioBuf.getSize()-mAudioStreamOffset+convertedSound.mData.getSize());
		}
		mAudioBuf.write(mAudioBuf.getSize()-mAudioStreamOffset, convertedSound.mData.getPtr<char>(), convertedSound.mData.getSize());
		mAudioStreamOffset = 0;
		SDL_UnlockMutex(mSoundMutex);
	} else {
		// Specify sound by pushing data ...
		audio->queue(convertedSound);
	}
	RETURN;
}

// Open SMK file, given its path
bool SmackerView::open(const char* path)
{
	SET_SCOPE;
	close();
	mSmk = smk_open_file(path, SMK_MODE_DISK);
	bool result = (mSmk != nullptr);

	if (result) {
		smk_info_all(mSmk, NULL, &mFrames, &usf);
		smk_info_video(mSmk, &mVideoWidth, &mVideoHeight, NULL);
		smk_enable_video(mSmk, 1);
		smk_enable_audio(mSmk, 0, 1);
		smk_info_audio(mSmk, &mTrackMask, mChannels, mDepth, mRate);

		double fps = 1000000.0 / usf;
		COUT << path << ": " << mVideoWidth << "x" << mVideoHeight << "@" << fps << " fps, " 
			 << mFrames << " frames" << std::endl;

		if (smk_first(mSmk) >= 0) {
			mVideoRemaining = true;
		}

		mVideoStartTicks = GetTickCount();
		mSoundMutex = SDL_CreateMutex();

		Audio* audio = getAudio();
		if ((mTrackMask & 1) != 1) {
			audio->pause();
			COUT << "No SMK audio." << std::endl;
		} else {
			audio->unpause();
			setAudioProvider(this);
		}
	}

	RETURN result;
}

void SmackerView::close ()
{
	SET_SCOPE;
	if (mSmk != nullptr) {
		smk_close(mSmk);
		mSmk = nullptr;
	}
	safeDelete(mTexture);
	RETURN;
}

void SmackerView::render(int x, int y, int w, int h)
{
	// Check if next frame is needed
	SET_SCOPE;
	DWORD currTicks = GetTickCount();
	bool nextFrame = false;
	if (1000 * (currTicks - mVideoStartTicks) >= mFrame * usf) {
		nextFrame = true;
	}

	// Decode video frame to mTexture, if enough time has passed
	// Apparently, all buffers are closed/freed when smk_close is called
	if (mVideoRemaining && nextFrame) {
		const unsigned char* palette_data = smk_get_palette(mSmk);
		const unsigned char* image_data = smk_get_video(mSmk);
		int nonBlackPixels = 0;

		safeDelete(mTexture);
		mTexture = new Texture((TextureDim)mVideoWidth, (TextureDim)mVideoHeight, GL_RGB);

		if (palette_data != nullptr && image_data != nullptr) {
			for (TextureDim y = 0; y < (TextureDim)mVideoHeight; ++y) {
				for (TextureDim x = 0; x < (TextureDim)mVideoWidth; ++x) {
					auto j = image_data[y*mVideoWidth+x];
					unsigned char r, g, b;
					r = palette_data[3*j];
					g = palette_data[3*j+1];
					b = palette_data[3*j+2];
					mTexture->setRGB(x, y, r, g, b);
				}
			}
		}

		const unsigned char* audioData = smk_get_audio(mSmk,0);
		size_t audioSize = smk_get_audio_size(mSmk, 0);
		processAudio(audioData, audioSize);

		auto next = smk_next(mSmk);
		if (next > 0)
			mVideoRemaining = true;
		else
			mVideoRemaining = false;
		++mFrame;
	}

	// Render mTexture into viewport
	if (mTexture != nullptr) {
		prepareToRender(x, y, w, h);
		glEnable(GL_TEXTURE_2D);
		glColor3d(1.0, 1.0, 1.0);
		glTranslated (-1.0, -1.0, 0.0);
		glScaled(2.0, 2.0, 1.0);

		double u1 = 0.0;
		double u2 = 1.0;
		double v1 = 0.0;
		double v2 = 1.0;
		mTexture->translateCoords(u1, v1);
		mTexture->translateCoords(u2, v2);
		mTexture->use();

		glBegin(GL_QUADS);
			glTexCoord2d(u1, v1);
			glVertex2d(0.0, 1.0);

			glTexCoord2d(u2, v1);
			glVertex2d(1.0, 1.0);

			glTexCoord2d(u2, v2);
			glVertex2d(1.0, 0.0);

			glTexCoord2d(u1, v2);
			glVertex2d(0.0, 0.0);
		glEnd();
	}

	RETURN;
}

// Provide audio via SDL callback
void SmackerView::provideAudio(void* userdata, Uint8* stream, int len)
{
	// Ensure we can cast len to size_t ...
	if (len < 0 ) {
		return;
	}

	Audio* audio = getAudio();
	if (audio == nullptr) {
		SDL_memset(stream, 0, len);
		return;
	}

	SDL_LockMutex(mSoundMutex);
	auto& audioSpec = audio->getAudioSpec();
	int samplesToProvide = len / (audio->getChannels() * getBytesPerSample(audioSpec.format));

	// Sound was converted already, all we need to do is copy it
	size_t audioProvided = 0;
	if (mAudioBuf.getSize() > 0) {
		audioProvided = std::min<size_t>((size_t)len, mAudioBuf.getSize());
		SDL_memcpy(stream, mAudioBuf.getPtr<char>(mAudioStreamOffset), audioProvided);
		mAudioStreamOffset += audioProvided;
	}
	if (audioProvided < (size_t)len) {
		SDL_memset(stream+audioProvided, 0, len-audioProvided);
	}

	SDL_UnlockMutex(mSoundMutex);
}
