/* ScummVM - Graphic Adventure Engine
 *
 * ScummVM is the legal property of its developers, whose names
 * are too numerous to list here. Please refer to the COPYRIGHT
 * file distributed with this source distribution.
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.

 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.

 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 *
 * $URL$
 * $Id$
 *
 */

#ifndef GOB_COKTELVIDEO_H
#define GOB_COKTELVIDEO_H

#include "common/stream.h"
#include "common/array.h"
#include "graphics/dither.h"
#include "sound/mixer.h"
#include "sound/audiostream.h"

namespace Gob {

class Indeo3;

/** Common interface for handling Coktel Vision videos and derivated formats. */
class CoktelVideo {
public:
	enum Features {
		kFeaturesNone = 0,
		/** Has an own palette. */
		kFeaturesPalette = 8,
		/** Suggests a data size. */
		kFeaturesDataSize = 0x20,
		/** Has sound. */
		kFeaturesSound = 0x40,
		/** Has specific frame coordinates. */
		kFeaturesFrameCoords = 0x80,
		/** Has general standard coordinates. */
		kFeaturesStdCoords = 0x100,
		/** Has a frame positions table. */
		kFeaturesFramesPos = 0x200,
		/** Has video. */
		kFeaturesVideo = 0x400,
		/** Is a full color (non-paletted) video. */
		kFeaturesFullColor = 0x4000
	};

	enum StateFlags {
		kStateNone = 0,
		/** Changed the palette. */
		kStatePalette = 0x10,
		/** Performed a jump to another frame. */
		kStateJump = 0x200,
		/** Updated according to the specific frame coordinates. */
		kStateFrameCoords = 0x400,
		/** Got no frame data. */
		kStateNoVideoData = 0x800,
		/** Updated according to the general standard coordinates. */
		kStateStdCoords = 0x1000,
		/** Had to explicitely seek to the frame. */
		kStateSeeked = 0x2000,
		/** Reached a break-point. */
		kStateBreak = 0x8000
	};

	struct State {
		/** Left-most value of the updated rectangle. */
		int16 left;
		/** Top-most value of the updated rectangle. */
		int16 top;
		/** Right-most value of the updated rectangle. */
		int16 right;
		/** Bottom-most value of the updated rectangle. */
		int16 bottom;
		/** Set accordingly to what was done. */
		uint32 flags;

		State() : left(0), top(0), right(0), bottom(0), flags(0) { }
	};

	virtual ~CoktelVideo() { }

	/** Returns the features the loaded video possesses. */
	virtual uint16 getFeatures() const = 0;
	/** Returns the flags the loaded video possesses. */
	virtual uint16 getFlags() const = 0;
	/** Returns the x coordinate of the video. */
	virtual int16 getX() const = 0;
	/** Returns the y coordinate of the video. */
	virtual int16 getY() const = 0;
	/** Returns the width of the video. */
	virtual int16 getWidth() const = 0;
	/** Returns the height of the video. */
	virtual int16 getHeight() const = 0;
	/** Returns the number of frames the loaded video has. */
	virtual uint16 getFramesCount() const = 0;
	/** Returns the current frame number.
	 *
	 *  This is the current frame after the last nextFrame()-call,
	 *  i.e. it's 0 after loading, 1 after the first nextFrame()-call, etc..
	 */
	virtual uint16 getCurrentFrame() const = 0;
	/** Returns the frame rate. */
	virtual int16 getFrameRate() const = 0;
	/** Returns the number of frames the video lags behind the audio. */
	virtual uint32 getSyncLag() const = 0;
	/** Returns the current frame's palette. */
	virtual const byte *getPalette() const = 0;

	/** Reads the video's anchor pointer */
	virtual bool getAnchor(int16 frame, uint16 partType,
			int16 &x, int16 &y, int16 &width, int16 &height) = 0;

	/** Returns whether that extra data file exists */
	virtual bool hasExtraData(const char *fileName) const = 0;
	/** Returns an extra data file */
	virtual Common::MemoryReadStream *getExtraData(const char *fileName) = 0;

	/** Load a video out of a stream. */
	virtual bool load(Common::SeekableReadStream &stream) = 0;
	/** Unload the currently loaded video. */
	virtual void unload() = 0;

	/** Set the frame rate. */
	virtual void setFrameRate(int16 frameRate) = 0;

	/** Set the coordinations where to draw the video. */
	virtual void setXY(int16 x, int16 y) = 0;
	/** Use a specific memory block as video memory. */
	virtual void setVideoMemory(byte *vidMem, uint16 width, uint16 height) = 0;
	/** Use an own memory block as video memory. */
	virtual void setVideoMemory() = 0;

	/** Play sound (if the video has sound). */
	virtual void enableSound(Audio::Mixer &mixer) = 0;
	/** Don't play sound or stop currently playing sound. */
	virtual void disableSound() = 0;

	/** Is sound currently playing? */
	virtual bool isSoundPlaying() const = 0;

	/** Seek to a specific frame.
	 *
	 *  @param frame The frame to which to seek.
	 *  @param whence The offset from whence the frame is given.
	 *  @param restart Restart the video to reach an otherwise inaccessible frame?
	 */
	virtual void seekFrame(int32 frame, int16 whence = SEEK_SET, bool restart = false) = 0;

	/** Render the next frame. */
	virtual State nextFrame() = 0;
	/** Wait for the frame to end. */
	virtual void waitEndFrame() = 0;

	/** Notifies the video that it was paused for duration ms. */
	virtual void notifyPaused(uint32 duration) = 0;

	/** Copy the current frame.
	 *
	 *  @param dest The memory to which to copy the current frame.
	 *  @param left The x position within the frame.
	 *  @param top The y position within the frame.
	 *  @param width The width of the area to copy.
	 *  @param height The height of the area to copy.
	 *  @param x The x position to where to copy.
	 *  @param y The y position to where to copy.
	 *  @param pitch The buffer's width.
	 *  @param transp Which color should be seen as transparent?
	 */
	virtual void copyCurrentFrame(byte *dest,
			uint16 left, uint16 top, uint16 width, uint16 height,
			uint16 x, uint16 y, uint16 pitch, int16 transp = -1) = 0;
};

/** Coktel Vision's IMD files.
 */
class Imd : public CoktelVideo {
public:
	Imd();
	~Imd();

	uint16 getFeatures() const { return _features; }
	uint16 getFlags() const { return _flags; }
	int16 getX() const { return _x; }
	int16 getY() const { return _y; }
	int16 getWidth() const { return _width; }
	int16 getHeight() const { return _height; }
	uint16 getFramesCount() const { return _framesCount; }
	uint16 getCurrentFrame() const { return _curFrame; }
	int16 getFrameRate() const {
		if (_hasSound)
			return 1000 / (_soundSliceLength >> 16);
		return _frameRate;
	}
	uint32 getSyncLag() const { return _skipFrames; }
	const byte *getPalette() const { return _palette; }

	bool getAnchor(int16 frame, uint16 partType,
			int16 &x, int16 &y, int16 &width, int16 &height) { return false; }

	bool hasExtraData(const char *fileName) const { return false; }
	Common::MemoryReadStream *getExtraData(const char *fileName) { return 0; }

	void notifyPaused(uint32 duration) { }

	void setFrameRate(int16 frameRate);

	bool load(Common::SeekableReadStream &stream);
	void unload();

	void setXY(int16 x, int16 y);
	void setVideoMemory(byte *vidMem, uint16 width, uint16 height);
	void setVideoMemory();

	void enableSound(Audio::Mixer &mixer);
	void disableSound();

	bool isSoundPlaying() const;

	void seekFrame(int32 frame, int16 whence = SEEK_SET, bool restart = false);

	State nextFrame();
	void waitEndFrame();

	void copyCurrentFrame(byte *dest,
			uint16 left, uint16 top, uint16 width, uint16 height,
			uint16 x, uint16 y, uint16 pitch, int16 transp = -1);

protected:
	struct Coord {
		int16 left;
		int16 top;
		int16 right;
		int16 bottom;
	} PACKED_STRUCT;

	Common::SeekableReadStream *_stream;
	uint16 _version;
	uint16 _features;
	uint16 _flags;
	int16 _x, _y, _width, _height;
	int16 _stdX, _stdY, _stdWidth, _stdHeight;
	uint16 _framesCount, _curFrame;
	uint32 *_framesPos;
	uint32 _firstFramePos;
	Coord *_frameCoords;

	uint32 _frameDataSize, _vidBufferSize;
	byte *_frameData, *_vidBuffer;
	uint32 _frameDataLen;

	byte _palette[768];

	bool _hasOwnVidMem;
	byte *_vidMem;
	uint16 _vidMemWidth, _vidMemHeight;

	bool _hasSound;
	bool _soundEnabled;
	uint8 _soundStage; // (0: no sound, 1: loaded, 2: playing)
	uint32 _skipFrames;

	uint16 _soundFlags;
	int16 _soundFreq;
	int16 _soundSliceSize;
	int16 _soundSlicesCount;
	uint32 _soundSliceLength;

	Audio::AppendableAudioStream *_audioStream;
	Audio::SoundHandle _audioHandle;

	int16 _frameRate;
	uint32 _frameLength;
	uint32 _lastFrameTime;

	Audio::Mixer *_mixer;

	void unsignedToSigned(byte *buffer, int length) {
		while (length-- > 0) *buffer++ ^= 0x80;
	}

	void deleteVidMem(bool del = true);
	void clear(bool del = true);

	State processFrame(uint16 frame);
	uint32 renderFrame(int16 left, int16 top, int16 right, int16 bottom);
	void deLZ77(byte *dest, byte *src);
};

class Vmd : public Imd {
public:
	Vmd(Graphics::PaletteLUT *palLUT = 0);
	~Vmd();

	bool getAnchor(int16 frame, uint16 partType,
			int16 &x, int16 &y, int16 &width, int16 &height);

	bool hasExtraData(const char *fileName) const;
	Common::MemoryReadStream *getExtraData(const char *fileName);

	bool load(Common::SeekableReadStream &stream);
	void unload();

	int16 getWidth() const;

	void setXY(int16 x, int16 y);

	void seekFrame(int32 frame, int16 whence = SEEK_SET, bool restart = false);

	State nextFrame();

protected:
	enum PartType {
		kPartTypeSeparator = 0,
		kPartTypeAudio = 1,
		kPartTypeVideo = 2,
		kPartTypeExtraData = 3
	};
	struct ExtraData {
		char name[16];
		uint32 offset;
		uint32 size;
		uint32 realSize;
	} PACKED_STRUCT;
	struct Part {
		PartType type;
		byte field_1;
		byte field_E;
		uint32 size;
		int16 left;
		int16 top;
		int16 right;
		int16 bottom;
		byte flags;
	} PACKED_STRUCT;
	struct Frame {
		uint32 offset;
		Part *parts;

		Frame() : parts(0) { }
		~Frame() { delete[] parts; }
	} PACKED_STRUCT;

	static const uint16 _tableADPCM[128];

	bool _hasVideo;

	uint32 _frameInfoOffset;
	uint16 _partsPerFrame;
	Frame *_frames;

	Common::Array<ExtraData> _extraData;

	byte _soundBytesPerSample;
	byte _soundStereo; // (0: mono, 1: old-style stereo, 2: new-style stereo)

	bool _externalCodec;
	byte _blitMode;
	byte _bytesPerPixel;
	byte _preScaleX;
	byte _postScaleX;
	byte _scaleExternalX;
	byte *_vidMemBuffer;

	Graphics::PaletteLUT *_palLUT;
	Indeo3 *_codecIndeo3;

	void clear(bool del = true);

	State processFrame(uint16 frame);
	uint32 renderFrame(int16 &left, int16 &top, int16 &right, int16 &bottom);

	void deRLE(byte *&srcPtr, byte *&destPtr, int16 len);

	inline int32 preScaleX(int32 x) const;
	inline int32 postScaleX(int32 x) const;

	void blit(byte *dest, byte *src, int16 width, int16 height);
	void blit16(byte *dest, byte *src, int16 srcPitch, int16 width, int16 height);
	void blit24(byte *dest, byte *src, int16 srcPitch, int16 width, int16 height);

	void emptySoundSlice(uint32 size);
	void soundSlice8bit(uint32 size);
	void soundSlice16bit(uint32 size, int16 &init);
	void filledSoundSlice(uint32 size);
	void filledSoundSlices(uint32 size, uint32 mask);
	void deADPCM(byte *soundBuf, byte *dataBuf, int16 &init, uint32 n);
};

} // End of namespace Gob

#endif // GOB_COKTELVIDEO_H