#ifndef NICOROFFMPEGPLAYER_H_
#define NICOROFFMPEGPLAYER_H_

#include <new>
#include <jni.h>

#ifndef UINT64_C
#define UINT64_C(c)     c ## ULL
#endif // UINT64_C

extern "C" {
#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
#include <libswscale/swscale.h>
}

#include "JniWrapper.h"
#include "jp_sourceforge_nicoro_FFmpegVideoDecoder.h"

class AutoPtrAvInputFile {
public:
	AutoPtrAvInputFile() : mP(NULL) {}
	AutoPtrAvInputFile(AVFormatContext* p) : mP(p) {}
	~AutoPtrAvInputFile() {
		release();
	}
	AVFormatContext*& get() {
		return mP;
	}
	void release() {
		if (mP != NULL) {
			av_close_input_file(mP);
			mP = NULL;
		}
	}
private:
	AVFormatContext* mP;
};

class AutoPtrAvInputStream {
public:
	AutoPtrAvInputStream() : mP(NULL) {}
	AutoPtrAvInputStream(AVFormatContext* p) : mP(p) {}
	~AutoPtrAvInputStream() {
		release();
	}
	AVFormatContext*& get() {
		return mP;
	}
	void release() {
		if (mP != NULL) {
			av_close_input_stream(mP);
			mP = NULL;
		}
	}
private:
	AVFormatContext* mP;
};

class AutoPtrAvCodecContext {
public:
	AutoPtrAvCodecContext() : mP(NULL) {}
	AutoPtrAvCodecContext(AVCodecContext* p) : mP(p) {}
	~AutoPtrAvCodecContext() {
		release();
	}
	AVCodecContext*& get() {
		return mP;
	}
	void set(AVCodecContext* p) {
		release();
		mP = p;
	}
	void release() {
		if (mP != NULL) {
			avcodec_close(mP);
			mP = NULL;
		}
	}
private:
	AVCodecContext* mP;
};

template <class T>
class AutoPtrAv {
public:
	AutoPtrAv() : mP(NULL) {}
	AutoPtrAv(T* p) : mP(p) {}
	~AutoPtrAv() {
		release();
	}
	T*& get() {
		return mP;
	}
	void set(T* p) {
		release();
		mP = p;
	}
private:
	T* mP;

	void release() {
		if (mP != NULL) {
			av_free(mP);
			mP = NULL;
		}
	}
};

class AutoPacket {
public:
	AutoPacket() : mpP(NULL) {}
	AutoPacket(AVPacket* p) : mpP(p) {}
	~AutoPacket() {
		release();
	}
	AVPacket* get() {
		return mpP;
	}
	void set(AVPacket* p) {
		release();
		mpP = p;
	}
	void release() {
		if (mpP != NULL) {
			av_free_packet(mpP);
			mpP = NULL;
		}
	}
private:
	AVPacket* mpP;
};

class AutoPacketRelease {
public:
	AutoPacketRelease(AutoPacket& r) : mrAP(r) {}
	~AutoPacketRelease() {
		mrAP.release();
	}
private:
	AutoPacket& mrAP;
};

class AutoSwsContext {
public:
	AutoSwsContext() : mpP(NULL) {}
	AutoSwsContext(SwsContext* p) : mpP(p) {}
	~AutoSwsContext() {
		release();
	}
	SwsContext* get() {
		return mpP;
	}
	void set(SwsContext* p) {
		release();
		mpP = p;
	}
	void release() {
		if (mpP != NULL) {
			sws_freeContext(mpP);
			mpP = NULL;
		}
	}
private:
	SwsContext* mpP;
};

//DEF_JFIELD_CLASS(jobject, mVideoLoader)
#if (AUDIO_BUFFER_DIRECT_ALLOC)
DEF_JFIELD_CLASS(jobject, mAudioBuffer)
#else
DEF_JFIELD_CLASS(jshortArray, mAudioBuffer)
#endif
DEF_JFIELD_CLASS(jint, mAudioBufferSize)
DEF_JFIELD_CLASS(jint, mTimeNumVideo)
DEF_JFIELD_CLASS(jint, mTimeDenVideo)
DEF_JFIELD_CLASS(jint, mTimeNumAudio)
DEF_JFIELD_CLASS(jint, mTimeDenAudio)
DEF_JFIELD_CLASS(jint, mFrameRateNumVideo);
DEF_JFIELD_CLASS(jint, mFrameRateDenVideo);
DEF_JMETHOD_CLASS(void, createAudioTrackFromNativeCallback)
DEF_JMETHOD_CLASS(void, createDrawBufferFromNativeCallback)
DEF_JFIELD_CLASS(jint, mWidth)
DEF_JFIELD_CLASS(jint, mHeight)

DEF_JMETHOD_CLASS(jlong, seekFromNativeCallback)
DEF_JMETHOD_CLASS(jint, readFromNativeCallback)

class NicoroFFmpegPlayer {
public:
	static const int CODE_DECODE_FRAME_INVALID = jp_sourceforge_nicoro_FFmpegVideoDecoder_CODE_DECODE_FRAME_INVALID;
	static const int CODE_DECODE_FRAME_PROGRESS = jp_sourceforge_nicoro_FFmpegVideoDecoder_CODE_DECODE_FRAME_PROGRESS;
	static const int CODE_DECODE_FRAME_VIDEO = jp_sourceforge_nicoro_FFmpegVideoDecoder_CODE_DECODE_FRAME_VIDEO;
	static const int CODE_DECODE_FRAME_AUDIO = jp_sourceforge_nicoro_FFmpegVideoDecoder_CODE_DECODE_FRAME_AUDIO;
	static const int CODE_DECODE_FRAME_VIDEO_SKIP = jp_sourceforge_nicoro_FFmpegVideoDecoder_CODE_DECODE_FRAME_VIDEO_SKIP;
	static const int CODE_DECODE_FRAME_SKIP = jp_sourceforge_nicoro_FFmpegVideoDecoder_CODE_DECODE_FRAME_SKIP;
	static const int CODE_DECODE_FRAME_ERROR_OR_END = jp_sourceforge_nicoro_FFmpegVideoDecoder_CODE_DECODE_FRAME_ERROR_OR_END;

	static const int CODE_FRAME_TYPE_VIDEO = jp_sourceforge_nicoro_FFmpegVideoDecoder_CODE_FRAME_TYPE_VIDEO;
	static const int CODE_FRAME_TYPE_AUDIO = jp_sourceforge_nicoro_FFmpegVideoDecoder_CODE_FRAME_TYPE_AUDIO;
	static const int CODE_FRAME_TYPE_UNKNOWN = jp_sourceforge_nicoro_FFmpegVideoDecoder_CODE_FRAME_TYPE_UNKNOWN;
	static const int CODE_FRAME_TYPE_ERROR_OR_END = jp_sourceforge_nicoro_FFmpegVideoDecoder_CODE_FRAME_TYPE_ERROR_OR_END;

	static const int CODE_SKIP_VIDEO_FRAME_NONE = jp_sourceforge_nicoro_FFmpegVideoDecoder_CODE_SKIP_VIDEO_FRAME_NONE;
	static const int CODE_SKIP_VIDEO_FRAME_NONREF = jp_sourceforge_nicoro_FFmpegVideoDecoder_CODE_SKIP_VIDEO_FRAME_NONREF;
	static const int CODE_SKIP_VIDEO_FRAME_BIDIR = jp_sourceforge_nicoro_FFmpegVideoDecoder_CODE_SKIP_VIDEO_FRAME_BIDIR;
	static const int CODE_SKIP_VIDEO_FRAME_NONKEY = jp_sourceforge_nicoro_FFmpegVideoDecoder_CODE_SKIP_VIDEO_FRAME_NONKEY;
	static const int CODE_SKIP_VIDEO_FRAME_SIMPLE = jp_sourceforge_nicoro_FFmpegVideoDecoder_CODE_SKIP_VIDEO_FRAME_SIMPLE;
	static const int CODE_SKIP_VIDEO_FRAME_UNTIL_KEY_FRAME = jp_sourceforge_nicoro_FFmpegVideoDecoder_CODE_SKIP_VIDEO_FRAME_UNTIL_KEY_FRAME;

	static const int CODE_RESCALE_POINT = jp_sourceforge_nicoro_FFmpegVideoDecoder_CODE_RESCALE_POINT;
	static const int CODE_RESCALE_FAST_BILINEAR = jp_sourceforge_nicoro_FFmpegVideoDecoder_CODE_RESCALE_FAST_BILINEAR;
	static const int CODE_RESCALE_BILINEAR = jp_sourceforge_nicoro_FFmpegVideoDecoder_CODE_RESCALE_BILINEAR;
	static const int CODE_RESCALE_BICUBLIN = jp_sourceforge_nicoro_FFmpegVideoDecoder_CODE_RESCALE_BICUBLIN;
	static const int CODE_RESCALE_BICUBIC = jp_sourceforge_nicoro_FFmpegVideoDecoder_CODE_RESCALE_BICUBIC;
	static const int CODE_RESCALE_SPLINE = jp_sourceforge_nicoro_FFmpegVideoDecoder_CODE_RESCALE_SPLINE;
	static const int CODE_RESCALE_SINC = jp_sourceforge_nicoro_FFmpegVideoDecoder_CODE_RESCALE_SINC;

	NicoroFFmpegPlayer(JavaVM* vm);
	~NicoroFFmpegPlayer();

	void startJNI(JNIEnv * env, jobject thiz) {
		mEnv = env;
		mThiz = thiz;
//		assert(mReadBuffer == NULL);
//		assert(mIOCallback == NULL);
		mReadBuffer = NULL;
		mIOCallback = NULL;

		new(&mJniFFmpegIOCallback) JniFFmpegIOCallback;
		new(&mJniFFmpegData) JniFFmpegData;
		new(&mJniFFmpegInfoCallback) JniFFmpegInfoCallback;
	}
	void endJNI() {
		mEnv = NULL;
		mThiz = NULL;
		mReadBuffer = NULL;
		mIOCallback = NULL;

		mJniFFmpegIOCallback.~JniFFmpegIOCallback();
		mJniFFmpegData.~JniFFmpegData();
		mJniFFmpegInfoCallback.~JniFFmpegInfoCallback();
	}

	bool loadFile(const char* file);
	bool loadFileUseStream(const char* file);
	bool loadStream(JNIEnv * env, jobject thiz, jbyteArray buffer, jboolean isOpened,
			jobject ioCallback, jobject data);
	bool loadStreamMP3(JNIEnv * env, jobject thiz, jbyteArray buffer, jobject ioCallback,
			int timeBaseNumerator, int timeBaseDenominator,
			int sampleRate, int channels);
	bool loadStreamADPCM(JNIEnv * env, jobject thiz, jbyteArray buffer, jobject ioCallback,
			int timeBaseNumerator, int timeBaseDenominator,
			int sampleRate, int channels);
	bool reopenInputStream(jbyteArray buffer, jobject ioCallback);
	void createAudioTrack(JNIEnv * env, jobject thiz, jobject infoCallback);
	void createDrawBuffer(JNIEnv * env, jobject thiz, jobject infoCallback);
	int decodeFrame(JNIEnv * env, jobject thiz, jintArray drawBuffer,
			jint skipVideoFrame, jbyteArray readBuffer, jobject ioCallback, jobject data);
	int readFrame(JNIEnv * env, jobject thiz,
			jbyteArray readBuffer, jobject ioCallback);
	int decodeVideo(JNIEnv * env, jobject thiz,
			jintArray drawBuffer, jobject data, jint skipVideoFrame);
	int decodeVideo16bit(JNIEnv * env, jobject thiz,
			jobject drawByteBuffer, jobject data, jint skipVideoFrame);
	bool decodeAudio(JNIEnv * env, jobject thiz,
			jobject data);
	int seekFrameBySecond(JNIEnv * env, jobject thiz,
			int second);
	void setRescaleFlag(jint flag);
private:
	static const PixelFormat SURFACE_PIXEL_FORMAT = PIX_FMT_BGRA;
//	static const PixelFormat SURFACE_PIXEL_FORMAT_16BIT = PIX_FMT_BGR565BE;
//	static const PixelFormat SURFACE_PIXEL_FORMAT_16BIT = PIX_FMT_BGR565LE;
//	static const PixelFormat SURFACE_PIXEL_FORMAT_16BIT = PIX_FMT_RGB565BE;
	static const PixelFormat SURFACE_PIXEL_FORMAT_16BIT = PIX_FMT_RGB565LE;
	static const int STREAM_BUFFER_SIZE = 1024 * 4;
	static const int PROBE_DATA_BUFFER_SIZE = 1024 * 8;

	AutoPtrAvInputFile mpFormatContextFile;
	AutoPtrAvInputStream mpFormatContextStream;
	AVInputFormat* mpInputFormat;
	AVFormatContext* mpFormatContext;
	AutoPtrAvCodecContext mpAutoCodecContextVideo;
	AutoPtrAvCodecContext mpAutoCodecContextAudio;
	AutoPtrAv<AVFrame> mpFrame;
	AutoPtrAv<AVFrame> mpFrameBitmap;
	AVPacket mPacket;
	AutoPacket mAutoPacket;
	AutoSwsContext mpAutoSwsContext;
	int mStreamIndexVideo;
	int mStreamIndexAudio;
	uint8_t mStreamBuffer[STREAM_BUFFER_SIZE + FF_INPUT_BUFFER_PADDING_SIZE];

	FILE* mfpFileStream;		// 仮
	ByteIOContext mByteIOContext;

	unsigned char mProbeDataBuffer[PROBE_DATA_BUFFER_SIZE];

	JavaVM* mVM;
	JNIEnv* mEnv;
	jobject mThiz;

	jbyteArray mReadBuffer;
	jobject mIOCallback;

	jint mLastSkipVideoFrame;

	int mSurfaceSwsFlags;

	struct JniFFmpegIOCallback{
		MEMBER_JCLASS_CLASS(FFmpegIOCallback)
		MEMBER_JMETHOD_CLASS(seekFromNativeCallback)
		MEMBER_JMETHOD_CLASS(readFromNativeCallback)
	} mJniFFmpegIOCallback;
	struct JniFFmpegData {
		MEMBER_JCLASS_CLASS(FFmpegData)
		MEMBER_JFIELD_CLASS(mAudioBuffer)
		MEMBER_JFIELD_CLASS(mAudioBufferSize)
		MEMBER_JFIELD_CLASS(mTimeNumVideo)
		MEMBER_JFIELD_CLASS(mTimeDenVideo)
		MEMBER_JFIELD_CLASS(mTimeNumAudio)
		MEMBER_JFIELD_CLASS(mTimeDenAudio)
		MEMBER_JFIELD_CLASS(mFrameRateNumVideo)
		MEMBER_JFIELD_CLASS(mFrameRateDenVideo)
		MEMBER_JFIELD_CLASS(mWidth)
		MEMBER_JFIELD_CLASS(mHeight)
	} mJniFFmpegData;
	struct JniFFmpegInfoCallback {
		MEMBER_JCLASS_CLASS(FFmpegInfoCallback)
		MEMBER_JMETHOD_CLASS(createAudioTrackFromNativeCallback)
		MEMBER_JMETHOD_CLASS(createDrawBufferFromNativeCallback)
	} mJniFFmpegInfoCallback;


	static int readFuncFile(void *opaque, uint8_t *buf, int buf_size);
	static int writeFuncFile(void *opaque, uint8_t *buf, int buf_size);
	static int64_t seekFuncFile(void *opaque, int64_t offset, int whence);

	static int readFuncStream(void *opaque, uint8_t *buf, int buf_size);
	static int64_t seekFuncStream(void *opaque, int64_t offset, int whence);

	bool loadStreamAudioCommon(JNIEnv * env, jobject thiz, jbyteArray buffer, jobject ioCallback,
			int timeBaseNumerator, int timeBaseDenominator,
			int sampleRate, int channels,
			const char* formatName);
	bool loadCommon();
	bool decodeVideoPrepareSkip(jint skipVideoFrame, bool* pDecodeOnly);
	int decodeVideoMain(JNIEnv * env, jobject thiz);
	int decodeVideoConvert(JNIEnv * env, jobject thiz,
			jintArray drawBuffer, jobject data);
	int decodeVideoConvert16bit(JNIEnv * env, jobject thiz,
			jobject drawByteBuffer, jobject data);
	int decodeVideoConvertMain(JNIEnv * env, jobject thiz,
			uint8_t* pDrawBuffer, int widthBitmap, int heightBitmap,
			PixelFormat surfacePixelFormat);
	void decodeVideoSetData(JNIEnv * env, jobject thiz,
			jobject data);

	int initPutByteFile() {
		return init_put_byte(&mByteIOContext, mStreamBuffer, STREAM_BUFFER_SIZE, 0,
				this, &readFuncFile, /*&writeFuncFile*/ NULL, &seekFuncFile);
	}
	int initPutByteStream() {
		return init_put_byte(&mByteIOContext, mStreamBuffer, STREAM_BUFFER_SIZE, 0,
				this, &readFuncStream, /*&writeFuncFile*/ NULL, &seekFuncStream);
	}
};

#if (DEBUG_LOGD_DUMP)
#define LOG_DUMP_AVPACKET(pa) \
	LOGD("AVPacket=%p pts=%lld dts=%lld data=%p size=%d stream_index=%d flags=%d " \
			"duration=%d destruct=%p priv=%p pos=%lld convergence_duration=%lld", \
			&(pa), (pa).pts, (pa).dts, (pa).data, (pa).size, (pa).stream_index, (pa).flags, \
			(pa).duration, (pa).destruct, (pa).priv, (pa).pos, (pa).convergence_duration)

#define LOG_DUMP_AVFRAME(fr) \
	LOGD("AVFrame=%p data={%p,%p,%p,%p} " \
			"linesize={%d,%d,%d,%d} " \
			"base={%p,%p,%p,%p} " \
			"key_frame=%d pict_type=%d pts=%lld coded_picture_number=%d " \
			"display_picture_number=%d quality=%d age=%d reference=%d " \
			"qscale_table=%p qstride=%d mbskip_table=%p " \
			"motion_val={%p,%p} " \
			"mb_type=%p motion_subsample_log2=%d opaque=%p " \
			"error={%lld,%lld,%lld,%lld} type=%d " \
			"repeat_pict=%d qscale_type=%d interlaced_frame=%d top_field_first=%d " \
			"pan_scan=%p palette_has_changed=%d buffer_hints=%d dct_coeff=%p " \
			"ref_index={%p,%p} reordered_opaque=%lld hwaccel_picture_private=%p", \
			&(fr), (fr).data[0], (fr).data[1], (fr).data[2], (fr).data[3], \
			(fr).linesize[0], (fr).linesize[1], (fr).linesize[2], (fr).linesize[3], \
			(fr).base[0], (fr).base[1], (fr).base[2], (fr).base[3], \
			(fr).key_frame, (fr).pict_type, (fr).pts, (fr).coded_picture_number, \
			(fr).display_picture_number, (fr).quality, (fr).age, (fr).reference, \
			(fr).qscale_table, (fr).qstride, (fr).mbskip_table, \
			(fr).motion_val[0], (fr).motion_val[1], \
			(fr).mb_type, (fr).motion_subsample_log2, (fr).opaque, \
			(fr).error[0], (fr).error[1], (fr).error[2], (fr).error[3], (fr).type, \
			(fr).repeat_pict, (fr).qscale_type, (fr).interlaced_frame, (fr).top_field_first, \
			(fr).pan_scan, (fr).palette_has_changed, (fr).buffer_hints, (fr).dct_coeff, \
			(fr).ref_index[0], (fr).ref_index[1], (fr).reordered_opaque, (fr).hwaccel_picture_private)

#define LOG_DUMP_AVCODECCONTEXT(co) \
	LOGD("AVCodecContext=%p bit_rate=%d time_base=(%d/%d) " \
			"width=%d height=%d gop_size=%d " \
			"pix_fmt=%d sample_rate=%d channels=%d sample_fmt=%d " \
			"frame_size=%d frame_number=%d codec_name=%s codec_type=%d codec_id=%d ", \
			&(co), (co).bit_rate, (co).time_base.num, (co).time_base.den, \
			(co).width, (co).height, (co).gop_size, \
			(co).pix_fmt, (co).sample_rate, (co).channels, (co).sample_fmt, \
			(co).frame_size, (co).frame_number, (co).codec_name, (co).codec_type, (co).codec_id)

#define LOG_DUMP_AVFORMATCONTEXT(fo) \
	LOGD("AVFormatContext=%p nb_streams=%d timestamp=%lld start_time=%lld " \
			"duration=%lld file_size=%lld bit_rate=%d max_index_size=%d " \
			"start_time_realtime=%lld ", \
			&(fo), (fo).nb_streams, (fo).timestamp, (fo).start_time, \
			(fo).duration, (fo).file_size, (fo).bit_rate, (fo).max_index_size, \
			(fo).start_time_realtime)

#define LOG_DUMP_AVSTREAM(st) \
	LOGD("AVStream=%p index=%d id=%d " \
			"r_frame_rate=(%d/%d) " \
			"first_dts=%lld time_base=(%d/%d) " \
			"quality=%f start_time=%lld duration=%lld nb_frames=%lld " \
			"disposition=%d sample_aspect_ratio=(%d/%d) " \
			"avg_frame_rate=(%d/%d) codec_info_nb_frames=%d ", \
			&(st), (st).index, (st).id, \
			(st).r_frame_rate.num, (st).r_frame_rate.den, \
			(st).first_dts, (st).time_base.num, (st).time_base.den, \
			(st).quality, (st).start_time, (st).duration, (st).nb_frames, \
			(st).disposition, (st).sample_aspect_ratio.num, (st).sample_aspect_ratio.den, \
			(st).avg_frame_rate.num, (st).avg_frame_rate.den, (st).codec_info_nb_frames)
#else
#define LOG_DUMP_AVPACKET(pa)
#define LOG_DUMP_AVFRAME(fr)
#define LOG_DUMP_AVCODECCONTEXT(co)
#define LOG_DUMP_AVFORMATCONTEXT(fo)
#define LOG_DUMP_AVSTREAM(st)
#endif /* DEBUG_DUMP */

#endif /* NICOROFFMPEGPLAYER_H_ */
