//=====================================================================
//
// Copyright (c) 1999-2003  On2 Technologies Inc. All Rights Reserved. 
//
//---------------------------------------------------------------------
//
//  File:     $Workfile: AVI.hpp$
//
//  Date:     $Date: 2010/07/23 19:10:47 $
//
//  Revision: $Revision: 1.1 $
//
//---------------------------------------------------------------------

#ifndef AVI_HPP
#define AVI_HPP

#pragma warning(disable:4786)

#include "FourCC.hpp"
#include <exception>
#include <iosfwd>
#include <list>
#include <deque>
#include <string>
#include <vector>

#if defined WIN32
#include <windows.h>
#endif

namespace AVI
{
#if defined WIN32
    typedef unsigned __int64 size_type;
    typedef DWORD dword;
    typedef __int64 offset_t;
    typedef unsigned __int32 length_t;
#elif defined LINUX
    typedef unsigned long long size_type;
    typedef unsigned long dword;
    typedef long long offset_t;
    typedef unsigned int length_t;
#endif

    int asStreamId(const FourCC&);

    enum ChunkType
    {
        waveform,
        waveform_encrypted,
        DIB_compressed,
        DIB_uncompressed,
        DIB_encrypted,
        kChunkTypeUnknown
    };

    ChunkType asChunkType(const FourCC&);

    const FourCC asChunkId(int stream, ChunkType type);
    const FourCC asIndexChunkExId(int stream);

    size_type estimatedFileSize(
        int width,
        int height,
        int frameCount);

    const std::string offtoa(offset_t);

    class FileError : public std::exception
    {
    public:
        FileError(dword messageId);
        FileError(const char* message);
        ~FileError() throw();
        const char* what() const throw();
        dword id() const;
    private:
        std::string message;
        dword m_id;
    };



    struct MainHeader
    {
        enum Flag
        {
            hasIndex        = 0x00000010,
            mustUseIndex    = 0x00000020,
            isInterleaved   = 0x00000100,
            indexIsAbsolute = 0x00000800,  //? "trust cktype"
            wasCaptureFile  = 0x00010000,
            copyrighted     = 0x00020000
        };

        dword microSecPerFrame;
        dword maxBytesPerSec;
        dword paddingGranularity;
        dword flags;
        dword totalFrames;
        dword initialFrames;
        dword streams;
        dword suggestedBufferSize;
        dword width;
        dword height;
        dword reserved[4];

        const std::string flagsAsStr() const;
    };

    std::ostream& operator<<(std::ostream&, const MainHeader&);


    class Chunk
    {
    public:

        Chunk(const FourCC, length_t, const unsigned char* data = 0);

        const FourCC fcc() const;

        length_t length() const;

        const unsigned char* data() const;
        unsigned char* data();

        void data(const unsigned char* d);

    private:
        FourCC m_fcc;
//        length_t m_length;
//        unsigned char* m_data;
        
        typedef std::vector<unsigned char> data_t;
        data_t m_data;
    };

    std::ostream& operator<<(std::ostream& os, const Chunk&);

    typedef std::vector<Chunk> ExtraHeaderVector;



    struct Rectangle
    {
        typedef unsigned short T;

        T left;
        T top;
        T right;
        T bottom;

        Rectangle()
            : left(0), top(0), right(0), bottom(0)
        {
        }

        Rectangle(T l, T t, T r, T b)
            : left(l), top(t), right(r), bottom(b)
        {
        }
    };


    struct StreamHeader
    {
        enum Flag
        {
            disabled      = 0x00000001,
            formatChanges = 0x00010000
        };

        FourCC fccType;
        FourCC fccHandler;
        dword flags;
        unsigned short priority;
        unsigned short language;
        dword initialFrames;
        dword scale;
        dword rate;
        dword start;
        dword length;
        dword suggestedBufferSize;
        long quality;
        dword sampleSize;
        Rectangle frame;

        const std::string flagsAsStr() const;
    };

    std::ostream& operator<<(std::ostream&, const StreamHeader&);


    struct BitmapInfoHeader
    {
        dword size;
        long width;
        long height;
        unsigned short planes;
        unsigned short bitCount;
        FourCC compression;
        dword sizeImage;
        long xPelsPerMeter;
        long yPelsPerMeter;
        dword clrUsed;
        dword clrImportant;
    };

    std::ostream& operator<<(std::ostream&, const BitmapInfoHeader&);


//    namespace Compression
//    {
//        enum CompressionType
//        {
//            RGB,
//            RLE8,
//            RLE4,
//            bitfields,
//            unknown
//        };
//
//        bool operator==(CompressionType, const FourCC&);
//        bool operator==(const FourCC&, CompressionType);
//
//        CompressionType asCompression(const FourCC&);
//        const FourCC asFourCC(CompressionType);
//
//        std::ostream& operator<<(std::ostream&, CompressionType);
//    }


    struct PCMWaveFormat
    {
        unsigned short formatTag;
        unsigned short nChannels;
        dword samplesPerSec;
        dword avgBytesPerSec;
        unsigned short blockAlign;
        unsigned short bitsPerSample;
    };

    struct WaveFormatEx : public PCMWaveFormat
    {
        typedef std::vector<unsigned char> ByteArray;
        ByteArray extra;
    };

    std::ostream& operator<<(std::ostream&, const WaveFormatEx&);




    //not currently used; it's for palette changes,
    //which isn't implemented yet
    struct RGBQuad
    {
        unsigned char blue;
        unsigned char green;
        unsigned char red;
        unsigned char reserved;
    };


    struct IndexEntry
    {
        enum Flags
        {
            list     = 0x00000001,
            keyframe = 0x00000010,
            notime   = 0x00000100,
            compuse  = 0x0FFF0000
        };

        FourCC chunkId;
        dword flags;
        dword chunkOffset;
        dword chunkLength;

        const std::string flagsAsStr() const;
    };

    std::ostream& operator<<(std::ostream&, const IndexEntry&);

    typedef std::vector<IndexEntry> IEVector;


    struct FrameIndexEntry
    {
        union
        {
            offset_t offset;

            struct
            {
                unsigned long offset_low;
                unsigned long offset_high;
            };
        };

        size_t size;
        bool keyframe;
    };

    typedef std::vector<FrameIndexEntry> FrameIEVector;
    typedef std::list<FrameIndexEntry> FrameIEList;

    typedef std::deque<FrameIndexEntry> FrameIndex;


    struct IndexChunkEx
    {
        FourCC code;
        unsigned long length;
        unsigned short longsPerEntry;
        unsigned char subtype;
        unsigned char type;
        unsigned long entriesInUse;
        FourCC chunkId;
        unsigned long reserved[3];
    };

    std::ostream& operator<<(std::ostream&, const IndexChunkEx&);


    struct StandardIndexChunk
    {
        FourCC code;
        unsigned long length;
        unsigned short longsPerEntry;
        unsigned char subtype;
        unsigned char type;
        unsigned long entriesInUse;
        FourCC chunkId;
        unsigned long baseOffset_low;
        unsigned long baseOffset_high;
        unsigned long reserved;

        struct Entry
        {
            unsigned long offset;
            unsigned long size;
        } index[1];
    };

    std::ostream& operator<<(std::ostream&, const StandardIndexChunk&);
    std::ostream& operator<<(std::ostream&, const StandardIndexChunk::Entry&);


    struct SuperIndexChunk
    {
        FourCC code;
        unsigned long length;
        unsigned short longsPerEntry;
        unsigned char subtype;
        unsigned char type;
        unsigned long entriesInUse;
        FourCC chunkId;
        unsigned long reserved[3];

        struct Entry
        {
            offset_t offset;
            unsigned long size;
            unsigned long duration;
        } index[1];
    };

    std::ostream& operator<<(std::ostream&, const SuperIndexChunk&);
    std::ostream& operator<<(std::ostream&, const SuperIndexChunk::Entry&);


    class File
    {
    public:

        enum mode_t {in, out, inout};

        enum OutputType
        {
            OT_AVI,
            OT_On2
        };

        File();
        File(const char* name, mode_t mode);

        ~File();

        void open(const char* name, mode_t mode, dword flags = 0);
        void outputType(OutputType ot);

        void close();

        bool isOpen() const;
        mode_t mode() const;
        const char* name() const;

        void mapinit();
        void mapfinal();

        unsigned long map(
            offset_t offset,
            length_t size,
            unsigned char*& base,
            length_t& baseSize,
            length_t& offsetInView) const;

        void unmap(unsigned char* base, length_t size) const;

        const MainHeader& mainHeader() const;
        MainHeader& mainHeader();

        int extraHeaderCount() const;
        const Chunk& extraHeader(int nChunk) const;

        void extraHeader(FourCC fcc, length_t length, unsigned char* data);
        void extraHeader(int nStream, FourCC fcc, length_t length, unsigned char* data);

        dword totalFrames() const;
        dword& totalFrames();

        int streamCount() const;

        int makeStreamHeaderVideo(int superIndexEntryCount = 0);
        int makeStreamHeaderAudio(int superIndexEntryCount = 0);

        const StreamHeader& streamHeader(int stream) const;
        StreamHeader& streamHeader(int stream);

        const unsigned char* strf(int nStream) const;
        size_t strfSize(int nStream) const;

        const BitmapInfoHeader& videoFormat(int stream) const;
        BitmapInfoHeader& videoFormat(int stream);

        const WaveFormatEx& audioFormat(int stream) const;
        WaveFormatEx& audioFormat(int stream);

        dword streamDataSize(int stream) const;
        const unsigned char* streamData(int stream) const;
        void setStreamData(int nStream, dword sds, const unsigned char* psd);

        const char* streamName(int stream) const;
        void setStreamName(int nStream, const char* psn);

        SuperIndexChunk& superIndexChunk(int stream);
        const SuperIndexChunk& superIndexChunk(int stream) const;

        int superIndexEntryCount(int stream) const;

        offset_t tell() const;

        void seek(offset_t) const;

        void seekCurrent(offset_t) const;

        offset_t dataOffset() const;

        offset_t idx1Offset() const;
        length_t idx1Size() const;

        void rewriteHeaders();
        //For use by header updaters, throws if position of movi list
        //changes, positions at beginning of data.

        void seekMainHeader();
        void writeMainHeader();

        length_t seekStreamHeader(int stream);
        void writeStreamHeader(int stream);

        void seekVideoFormat(int stream);
        void writeVideoFormat(int stream);

        void seekAudioFormat(int stream);
        void writeAudioFormat(int stream);

        void seekStreamData(int stream);
        void writeStreamData(int stream);

        void seekSuperIndexChunk(int stream);
        void writeSuperIndexChunk(int stream);

        int indexCount() const;
        void seekIndex() const;
        void read(IndexEntry&) const;

        void load(int stream, IEVector& index) const;
        void load(int stream, FrameIndex& index) const;

        void loadIndex(int, FrameIndex&) const;
        void loadIndexEx(int, FrameIndex&) const;

        void writeIndexChunkHeader(int number_of_index_entries);
        
        void write(const IndexEntry&) const;

        void writeIndexChunk(const IEVector& index);

        void writeIndexChunk(
            const FourCC& chunkId,
            const FrameIndex& index);

        void writeStandardIndexChunk(
            int stream,
            const FrameIndex& index,
            offset_t baseOffset);

        void writeStandardIndexChunk(
            int stream,
            const FrameIndex& index);

        size_t makeSegment();
        int segmentCount() const;
        offset_t segmentOffset() const;

        const FourCC readFourCC() const;
        void writeFourCC(const FourCC&);

        const FourCC testFourCC() const;

        length_t readLength() const;
        void writeLength(length_t length);

        void read(void* buffer, size_t size) const;
        void write(const void* data, size_t size, bool adjust = true);

        void writeJunkChunk(length_t);

        int countIndexEntries(int stream) const;

        bool indexIsRelative() const;

        offset_t size() const;

    private:

        File(const File& rhs);
        File& operator=(const File& rhs);

        //void readUnknownChunk() const;
        void readJunkChunk() const;

        bool readInit();
        bool readHeaderList();
        void readMainHeader();
        void readExtraHeaders();
        void readStreamHeaderList();
        void readStreamHeader(StreamHeader& h);
        void readStreamVideoFormat(BitmapInfoHeader& f);
        struct StreamInfoVideo;
        void readStreamVideoFormat(StreamInfoVideo* psiv);
        void readStreamAudioFormat(WaveFormatEx& f);
        void readStreamName(std::string& name);

        void readExtendedAVIHeader();
        void writeExtendedAVIHeader();

        bool readDataList();
        void readDataRecChunk() const;
        void readDataChunk() const;

        void readIndexList();

        void writeInit();
        void writeFinal();

        void writeHeader();
        void writeStreamHeaderList(int stream);
        void writeStreamHeader(const StreamHeader& h);

        void writeStreamVideoFormatChunk(const BitmapInfoHeader& f);
        void writeStreamVideoFormat(const BitmapInfoHeader& f);

        void writeStreamVideoFormatChunk(const unsigned char* pData, size_t sizeData);
        void writeStreamVideoFormat(const unsigned char* pData, size_t sizeData);

        void writeStreamAudioFormatChunk(const WaveFormatEx&);
        void writeStreamAudioFormat(const WaveFormatEx&);

        int headerLength() const;
        int streamHeaderLength(int stream) const;

        void load(const SuperIndexChunk::Entry&, FrameIndex&) const;

        class handle_t
        {
        public:

            handle_t();

            void open(const char*, mode_t, dword) throw (FileError);
            void close();

            bool isOpen() const;
            offset_t size() const;

            void read(void*, size_t) const;
            void write(const void*, size_t) const;

            void truncate() const;

            void seekCurrent(offset_t) const;
            void seek(offset_t) const;

            offset_t tell() const;

            void mapinit();
            void mapfinal();

            unsigned long map(
                offset_t offset,
                length_t size,
                unsigned char*& view,
                length_t& viewSize,
                length_t& offsetWithinView) const;

            void unmap(unsigned char*, length_t) const;

        private:
#if defined WIN32
            HANDLE m_hFile;
            HANDLE m_hFileMappingObject;
#elif defined LINUX
            int m_fd;
#endif
        };

        handle_t m_handle;

        mode_t m_mode;

        std::string m_name;

        OutputType m_ot;

        MainHeader m_mainHeader;

        ExtraHeaderVector m_extraHeaderVector;

        class indx_t
        {
        public:

            indx_t();
            indx_t(int entryCount);

            ~indx_t();

            int entryCount() const;

            size_t size() const;

            operator SuperIndexChunk&() const;

            void read(File&);
            void write(File&) const;

        private:

            indx_t(const indx_t&);
            indx_t& operator=(const indx_t&);

            dword* m_rep;            
            
        };

        struct StreamInfo
        {
            virtual ~StreamInfo();

            void write(File&) const;

            int length() const;

            StreamHeader header;

            typedef std::vector<unsigned char> data_t;

            data_t m_data;

            std::string m_name;

            ExtraHeaderVector m_extraHeaderVector;

            indx_t m_indx;

        protected:
            
            StreamInfo(int);
            StreamInfo(const StreamHeader&);

            virtual int strf_length() const = 0;
            virtual void strf_write(File&) const = 0;

        private:

            StreamInfo(const StreamInfo&);
            StreamInfo& operator=(const StreamInfo&);

        };

        struct StreamInfoVideo : public StreamInfo
        {
            StreamInfoVideo(int entryCount);
            StreamInfoVideo(const StreamHeader&);

            ~StreamInfoVideo();

//            BitmapInfoHeader m_strf;
            unsigned char* m_strf;
            size_t m_strfSize;
        protected:
            int strf_length() const;
            void strf_write(File&) const;
        };

        struct StreamInfoAudio : public StreamInfo
        {
            StreamInfoAudio(int entryCount);
            StreamInfoAudio(const StreamHeader&);
            
            WaveFormatEx m_strf;
        protected:
            int strf_length() const;
            void strf_write(File&) const;
        };

        friend struct StreamInfo;
        friend struct StreamInfoVideo;
        friend struct StreamInfoAudio;

        void readStreamData(StreamInfo::data_t&);

        typedef std::vector<StreamInfo*> infoVector_t;
        infoVector_t infoVector;

        dword m_totalFrames;

        offset_t m_dataPosn;

        offset_t m_indexPosn;
        int m_indexCount;
        length_t m_idx1Size;

        struct SegmentInfo
        {
            offset_t offset;
            size_t size;

            SegmentInfo() {}
            SegmentInfo(offset_t offset_) : offset(offset_) {}
        };

        typedef std::vector<SegmentInfo> SegmentInfoVector;
        SegmentInfoVector m_segmentInfo;
    };

#if defined WIN32
    class MappedFile
    {
    public:

        enum { invalid_offset = -1 };

        MappedFile();
        MappedFile(const char* name);

        ~MappedFile();

        void open(const char* name);
        void close();

        bool isOpen() const;
        const char* name() const;

        const MainHeader& mainHeader() const;

        const StreamHeader& streamHeader(int stream) const;

        const BitmapInfoHeader& videoFormat(int stream) const;

        const WaveFormatEx& audioFormat(int stream) const;

        const char* streamName(int stream) const;

        dword totalFrames() const;

        offset_t dataOffset() const;

        offset_t indexOffset() const;
        size_t indexSize() const;

        offset_t indexChunkExOffset(int stream) const;
        size_t indexChunkExSize(int stream) const;

        const void* map(offset_t, size_t) const;
        void unmap(const void*) const;

        void load(int stream, FrameIEVector& index) const;

    private:

        MappedFile(const MappedFile&);
        MappedFile& operator=(const MappedFile&);

        void init();
        void unmapAllViews();

        offset_t offset() const;
        void setFilePointerCurrent(LONG) const;

        const FourCC readFourCC() const;
        const FourCC queryFourCC() const;

        dword readLength() const;

        void readHeaderList();
        void readDataList();
        void readIndexList();
        void readJunkChunk() const;
        void readUnknownChunk() const;
        void readMainHeaderChunk();
        void readStreamHeaderList(int stream);
        void readStreamHeaderChunk(StreamHeader&) const;
        void readStreamVideoFormatChunk(BitmapInfoHeader&) const;
        void readStreamAudioFormatChunk(WaveFormatEx&) const;
        void readStreamNameChunk(std::string&) const;
        void readIndexChunkEx(offset_t&, size_t&) const;
        void readExtendedAVIHeaderList() const;

        std::string m_name;

        HANDLE m_file;
        HANDLE m_fileMappingObject;

        DWORD m_allocationGranularity;

        MainHeader m_mainHeader;

        struct StreamInfo
        {
            StreamHeader streamHeader;

            union
            {
                BitmapInfoHeader* videoFormat;
                WaveFormatEx* audioFormat;
            };

            std::string name;

            offset_t indexChunkExOffset;
            size_t indexChunkExSize;
        };


        typedef std::vector<StreamInfo> StreamInfoVector;
        StreamInfoVector m_streamInfoVector;

        size_t readChunkSize(offset_t) const;
        int countEntries(int, const IndexEntry*, int) const;
        int countEntries(const SuperIndexChunk&) const;
        void loadIndex(int, FrameIEVector&) const;
        void loadIndexEx(int, FrameIEVector&) const;
        void load(const SuperIndexChunk::Entry&, FrameIEVector&) const;

        mutable dword m_totalFrames;

        offset_t m_dataOffset;

        offset_t m_indexOffset;
        size_t m_indexSize;

        struct ViewInfo
        {
            unsigned char* pView;
            unsigned char* pChunk;
        };

        typedef std::list<ViewInfo> Views;
        mutable Views m_views;

        Views::iterator findView(const void*) const;
    };
#endif

}

//inline HANDLE AVI::File::handle() const
//{
//    return m_handle;
//}

inline AVI::Chunk::Chunk(
    const FourCC fcc,
    length_t length,
    const unsigned char* d)
    : m_fcc(fcc)
{
//    if (m_length > 0)
//    {
//        m_data = new unsigned char[m_length];
//        if (m_data == 0)
//        {
//            throw FileError("Error allocating Chunk data.");
//        }
//        if (data != 0)
//        {
//            memcpy(m_data, data, m_length);
//        }
//    }

    if (length)
    {
        if (d)
        {
            //typedef data_t::const_iterator iter_t;

            //const iter_t first = iter_t(d);

            //const data_t::size_type n = length;

            //const iter_t last = first + n;

            m_data.assign(d, d + length);
        }
        else
        {
            const data_t::size_type n = length;

            m_data.assign(n, 0);
        }
    }
    else
    {
        m_data.assign(data_t::size_type(0), 0);
    }
}

inline const FourCC AVI::Chunk::fcc() const
{
    return m_fcc;
}

inline AVI::length_t AVI::Chunk::length() const
{
    const data_t::size_type n = m_data.size();

    return n;
}

inline const unsigned char* AVI::Chunk::data() const
{
    return &m_data[0];
}

inline unsigned char* AVI::Chunk::data()
{
    return &m_data[0];
}

inline void AVI::Chunk::data(const unsigned char* d)
{
    //typedef data_t::const_iterator iter_t;

    //const iter_t first = iter_t(d);

    //const data_t::size_type n = m_data.size();

    //const iter_t last = first + n;

    m_data.assign(d, d + m_data.size());
}

inline AVI::dword AVI::FileError::id() const
{
    return m_id;
}


#endif