/*
 * AviReader.cs
 * Copyright (c) 2007-2009 kbinani
 *
 * This file is part of Boare.Lib.Media.
 *
 * Boare.Lib.Media is free software; you can redistribute it and/or
 * modify it under the terms of the BSD License.
 *
 * Boare.Lib.Media 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.
 */
using System;
using System.Drawing;
using System.Drawing.Imaging;
using System.Runtime.InteropServices;
using System.IO;
using System.Windows.Forms;

using bocoree;

namespace Boare.Lib.Media {

    /// <summary>
    /// Extract bitmaps from AVI files
    /// </summary>
    public class AviReader {
        private int firstFrame = 0, countFrames = 0;
        private int aviFile = 0;
        private int getFrameObject;
        private IntPtr aviStream;
        private bool m_opened = false;
        private AVISTREAMINFOW streamInfo;
        private string m_fileName = "";

        public string FileName {
            get {
                return m_fileName;
            }
        }

        public int CountFrames {
            get {
                return countFrames;
            }
        }

        public uint dwRate {
            get {
                return streamInfo.dwRate;
            }
        }

        public uint dwScale {
            get {
                return streamInfo.dwScale;
            }
        }

        [Obsolete]
        public float FrameRate {
            get {
                return  (float)streamInfo.dwRate / (float)streamInfo.dwScale;
            }
        }

        public Size BitmapSize {
            get {
                return new Size( (int)streamInfo.rect_right, (int)streamInfo.rect_bottom );
            }
        }

        /// <summary>
        /// Opens an AVI file and creates a GetFrame object
        /// </summary>
        /// <param name="fileName">Name of the AVI file</param>
        public void Open( string fileName ) {
            //Intitialize AVI library
            VFW.AVIFileInit();

            //Open the file
            int result = VFW.AVIFileOpenW(
                ref aviFile, fileName,
                (int)windows.OF_SHARE_DENY_WRITE, 0 );

            if ( result != 0 ) {
                throw new Exception( "Exception in AVIFileOpen: " + result.ToString() );
            }

            //Get the video stream
            result = VFW.AVIFileGetStream(
                aviFile,
                out aviStream,
                (int)VFW.streamtypeVIDEO, 0 );

            if ( result != 0 ) {
                throw new Exception( "Exception in AVIFileGetStream: " + result.ToString() );
            }

            firstFrame = VFW.AVIStreamStart( aviStream.ToInt32() );
            countFrames = VFW.AVIStreamLength( aviStream.ToInt32() );

            streamInfo = new AVISTREAMINFOW();
            result = VFW.AVIStreamInfo( aviStream.ToInt32(), ref streamInfo, Marshal.SizeOf( streamInfo ) );

            if ( result != 0 ) {
                throw new Exception( "Exception in AVIStreamInfo: " + result.ToString() );
            }

            //Open frames

            BITMAPINFOHEADER bih = new BITMAPINFOHEADER();
            bih.biBitCount = 24;
            bih.biClrImportant = 0;
            bih.biClrUsed = 0;
            bih.biCompression = 0; //BI_RGB;
            bih.biHeight = (Int32)streamInfo.rect_bottom;
            bih.biWidth = (Int32)streamInfo.rect_right;
            bih.biPlanes = 1;
            bih.biSize = (UInt32)Marshal.SizeOf( bih );
            bih.biXPelsPerMeter = 0;
            bih.biYPelsPerMeter = 0;

            getFrameObject = VFW.AVIStreamGetFrameOpen( aviStream, ref bih ); //force function to return 24bit DIBS
            //getFrameObject = Avi.AVIStreamGetFrameOpen(aviStream, 0); //return any bitmaps
            if ( getFrameObject == 0 ) {
                throw new Exception( "Exception in AVIStreamGetFrameOpen!" );
            }
            m_opened = true;
            m_fileName = fileName;
        }

        /// <summary>Closes all streams, files and libraries</summary>
        public void Close() {
            if ( getFrameObject != 0 ) {
                VFW.AVIStreamGetFrameClose( getFrameObject );
                getFrameObject = 0;
            }
            if ( aviStream != IntPtr.Zero ) {
                VFW.AVIStreamRelease( aviStream );
                aviStream = IntPtr.Zero;
            }
            if ( aviFile != 0 ) {
                VFW.AVIFileRelease( aviFile );
                aviFile = 0;
            }
            VFW.AVIFileExit();
            m_opened = false;
            m_fileName = "";
        }


        private void dispTime( string message, DateTime start ) {
            DateTime current = DateTime.Now;
            TimeSpan ts = current.Subtract( start );
            System.Diagnostics.Debug.WriteLine( message + "; " + ts.TotalSeconds + "s" );
        }


        public bool Opened {
            get {
                return m_opened;
            }
        }


        public void ExportToArray( int position, out byte[] bitmapData, out int width, out int height, out int bit_count ) {
            if ( position > countFrames ) {
                throw new Exception( "Invalid frame position" );
            }
            //Decompress the frame and return a pointer to the DIB
            int pDib = VFW.AVIStreamGetFrame( getFrameObject, firstFrame + position );
            //Copy the bitmap header into a managed struct
            BITMAPINFOHEADER bih = new BITMAPINFOHEADER();
            bih = (BITMAPINFOHEADER)Marshal.PtrToStructure( new IntPtr( pDib ), bih.GetType() );

            if ( bih.biSizeImage < 1 ) {
                throw new Exception( "Exception in AVIStreamGetFrame: Not bitmap decompressed." );
            }

            //Copy the image
            bitmapData = new byte[bih.biSizeImage];
            int address = pDib + Marshal.SizeOf( bih );
            Marshal.Copy( new IntPtr( address ), bitmapData, 0, bitmapData.Length );
            width = bih.biWidth;
            height = bih.biHeight;
            bit_count = bih.biBitCount;
        }


        public Bitmap Export( int position ) {
            if ( position > countFrames ) {
                throw new Exception( "Invalid frame position" );
            }
            //Decompress the frame and return a pointer to the DIB
            int pDib = VFW.AVIStreamGetFrame( getFrameObject, firstFrame + position );
            if ( pDib == 0 ) {
                //throw new Exception( "AVIStreamGetFrame; failed" );
                return null;
            }
            //Copy the bitmap header into a managed struct
            BITMAPINFOHEADER bih = new BITMAPINFOHEADER();
            bih = (BITMAPINFOHEADER)Marshal.PtrToStructure( new IntPtr( pDib ), bih.GetType() );
            if ( bih.biSizeImage < 1 ) {
                throw new Exception( "Exception in AVIStreamGetFrame: Not bitmap decompressed." );
            }
            Bitmap result = new Bitmap( bih.biWidth, bih.biHeight );
            BitmapData dat = result.LockBits(
                new Rectangle( 0, 0, bih.biWidth, bih.biHeight ),
                ImageLockMode.ReadWrite,
                PixelFormat.Format24bppRgb );
            //Copy the image
            int address = pDib + Marshal.SizeOf( bih );
            int length = dat.Stride * dat.Height;
            byte[] target = new byte[length];
            Marshal.Copy( new IntPtr( address ), target, 0, length );
            Marshal.Copy( target, 0, dat.Scan0, length );
            result.UnlockBits( dat );
            result.RotateFlip( RotateFlipType.RotateNoneFlipY );
            return result;
        }


        public unsafe void ExportEx( Bitmap bmp, int position ) {
            if ( position > countFrames ) {
                throw new Exception( "Invalid frame position" );
            }
            //Decompress the frame and return a pointer to the DIB
            int pDib = VFW.AVIStreamGetFrame( getFrameObject, firstFrame + position );
            if ( pDib == 0 ) {
                //throw new Exception( "AVIStreamGetFrame; failed" );
                return;
            }
            //Copy the bitmap header into a managed struct
            BITMAPINFOHEADER bih = new BITMAPINFOHEADER();
            bih = (BITMAPINFOHEADER)Marshal.PtrToStructure( new IntPtr( pDib ), bih.GetType() );
            if ( bih.biSizeImage < 1 ) {
                throw new Exception( "Exception in AVIStreamGetFrame: Not bitmap decompressed." );
            }
            BitmapData dat = bmp.LockBits(
                new Rectangle( 0, 0, bih.biWidth, bih.biHeight ),
                ImageLockMode.ReadWrite,
                PixelFormat.Format24bppRgb );
            //Copy the image
            int address = pDib + Marshal.SizeOf( bih );
            int length = dat.Stride * dat.Height;
            CopyMemory( dat.Scan0, new IntPtr( address ), length );
            //Marshal.Copy( new IntPtr( address ), target, 0, length );
            //Marshal.Copy( target, 0, dat.Scan0, length );
            bmp.UnlockBits( dat );
            bmp.RotateFlip( RotateFlipType.RotateNoneFlipY );
        }


        [DllImport( "kernel32.dll" )]
        private static extern void CopyMemory(
            IntPtr destination, IntPtr source, int length );
        
        public unsafe void Export( Bitmap bmp, int position ) {
            if ( position > countFrames ) {
                throw new Exception( "Invalid frame position" );
            }
            //Decompress the frame and return a pointer to the DIB
            int pDib = VFW.AVIStreamGetFrame( getFrameObject, firstFrame + position );
            if ( pDib == 0 ) {
                //throw new Exception( "AVIStreamGetFrame; failed" );
                return;
            }
            //Copy the bitmap header into a managed struct
            BITMAPINFOHEADER bih = new BITMAPINFOHEADER();
            bih = (BITMAPINFOHEADER)Marshal.PtrToStructure( new IntPtr( pDib ), bih.GetType() );
            if ( bih.biSizeImage < 1 ) {
                throw new Exception( "Exception in AVIStreamGetFrame: Not bitmap decompressed." );
            }
            BitmapData dat = bmp.LockBits(
                new Rectangle( 0, 0, bih.biWidth, bih.biHeight ),
                ImageLockMode.WriteOnly,
                bmp.PixelFormat );
            //Copy the image
            int address = pDib + Marshal.SizeOf( bih );
            int length = dat.Stride * dat.Height;
            byte[] target = new byte[length];
            Marshal.Copy( new IntPtr( address ), target, 0, length );
            Marshal.Copy( target, 0, dat.Scan0, length );
            bmp.UnlockBits( dat );
            bmp.RotateFlip( RotateFlipType.RotateNoneFlipY );
        }
        


        /// <summary>Exports a frame into a bitmap file</summary>
        /// <param name="position">Position of the frame</param>
        /// <param name="dstFileName">Name ofthe file to store the bitmap</param>
        public void ExportBitmap( int position, String dstFileName ) {
            if ( position > countFrames ) {
                throw new Exception( "Invalid frame position" );
            }

            //Decompress the frame and return a pointer to the DIB
            int pDib = VFW.AVIStreamGetFrame( getFrameObject, firstFrame + position );
            //Copy the bitmap header into a managed struct
            BITMAPINFOHEADER bih = new BITMAPINFOHEADER();
            bih = (BITMAPINFOHEADER)Marshal.PtrToStructure( new IntPtr( pDib ), bih.GetType() );

            /*if(bih.biBitCount < 24){
                throw new Exception("Not enough colors! DIB color depth is less than 24 bit.");
            }else */
            if ( bih.biSizeImage < 1 ) {
                throw new Exception( "Exception in AVIStreamGetFrame: Not bitmap decompressed." );
            }

            //Copy the image
            byte[] bitmapData = new byte[bih.biSizeImage];
            int address = pDib + Marshal.SizeOf( bih );
            for ( int offset = 0; offset < bitmapData.Length; offset++ ) {
                bitmapData[offset] = Marshal.ReadByte( new IntPtr( address ) );
                address++;
            }

            //Copy bitmap info
            byte[] bitmapInfo = new byte[Marshal.SizeOf( bih )];
            IntPtr ptr;
            ptr = Marshal.AllocHGlobal( bitmapInfo.Length );
            Marshal.StructureToPtr( bih, ptr, false );
            address = ptr.ToInt32();
            for ( int offset = 0; offset < bitmapInfo.Length; offset++ ) {
                bitmapInfo[offset] = Marshal.ReadByte( new IntPtr( address ) );
                address++;
            }

            //Create file header
            BITMAPFILEHEADER bfh = new BITMAPFILEHEADER();
            bfh.bfType = Util.BMP_MAGIC_COOKIE;
            bfh.bfSize = (Int32)(55 + bih.biSizeImage); //size of file as written to disk
            bfh.bfReserved1 = 0;
            bfh.bfReserved2 = 0;
            bfh.bfOffBits = Marshal.SizeOf( bih ) + Marshal.SizeOf( bfh );

            //Create or overwrite the destination file
            FileStream fs = new FileStream( dstFileName, System.IO.FileMode.Create );
            BinaryWriter bw = new BinaryWriter( fs );

            //Write header
            bw.Write( bfh.bfType );
            bw.Write( bfh.bfSize );
            bw.Write( bfh.bfReserved1 );
            bw.Write( bfh.bfReserved2 );
            bw.Write( bfh.bfOffBits );
            //Write bitmap info
            bw.Write( bitmapInfo );
            //Write bitmap data
            bw.Write( bitmapData );
            bw.Close();
            fs.Close();
        }
    }

}
