/*
 * PipedAviWriter.cs
 * Copyright (c) 2008-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.
 */
#if !MONO
#define TEST
using System;
using System.Diagnostics;
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
//using System.IO.Pipes;
using System.Runtime.InteropServices;
using System.Threading;

using bocoree;

namespace Boare.Lib.Media {

    public class PipedAviWriter {
        private const string _PIPE_NAME = "fifo";
        private uint m_scale = 1;
        private uint m_rate = 30;
#if TEST
        private FileStream m_stream = null;
#else
        private NamedPipeServerStream m_stream = null;
#endif
        private MainAVIHeader m_main_avi_header;
        private uint m_bitmapsize;
        private bool m_header_written = false;
        private ulong m_frames;
        private Thread m_ffmpeg = null;
        private PixelFormat m_pix_fmt = PixelFormat.Format24bppRgb;
        private int m_bit_count = 24;

        public bool AddFrame( Bitmap bmp ) {
            if ( bmp.PixelFormat != m_pix_fmt ) {
                return false;
            }
            BitmapData bdat = bmp.LockBits( new Rectangle( 0, 0, bmp.Width, bmp.Height ),
                                            ImageLockMode.ReadOnly,
                                            bmp.PixelFormat );
            BinaryWriter bw = new BinaryWriter( new MemoryStream() );
            if ( !m_header_written ) {
                m_bitmapsize = (uint)(bdat.Stride * bdat.Height);
                //m_stream.SetLength( (long)(0xdc + (m_bitmapsize + 0x8) * m_frames) );

                /*m_main_avi_header.dwWidth = (uint)bdat.Width;
                m_main_avi_header.dwHeight = (uint)bdat.Height;// bmp%infoHeader%Height
                m_main_avi_header.dwMaxBytesPerSec = (uint)((float)bdat.Stride * (float)bdat.Height * (float)m_rate / (float)m_scale);
                m_main_avi_header.dwStreams = 1;
                m_main_avi_header.dwSuggestedBufferSize = (uint)(bdat.Stride * bdat.Height);

                AVIStreamHeader stream_header = new AVIStreamHeader();
                stream_header.fccType = Util.mmioFOURCC( "vids" );
                stream_header.fccHandler = 0;
                stream_header.dwFlags = 0;
                stream_header.dwReserved1 = 0;
                stream_header.dwInitialFrames = 0;
                stream_header.dwScale = m_scale;
                stream_header.dwRate = m_rate;
                stream_header.dwStart = 0;
                stream_header.dwSuggestedBufferSize = m_main_avi_header.dwSuggestedBufferSize;
                stream_header.dwQuality = 0;
                stream_header.dwSampleSize = 0;

                BITMAPINFOHEADER bih = new BITMAPINFOHEADER(); //(BITMAPINFOHEADER)Marshal.PtrToStructure( Marshal.AllocHGlobal( sizeof( BITMAPINFOHEADER ) ), typeof( BITMAPINFOHEADER ) );
                bih.biSize = (uint)(Marshal.SizeOf( bih ));
                bih.biWidth = bdat.Width;
                bih.biHeight = bdat.Height;
                bih.biPlanes = 1;
                bih.biBitCount = m_pix_fmt == PixelFormat.Format24bppRgb ? (short)24 : (short)32;
                bih.biCompression = 0;//BI_RGB
                bih.biSizeImage = (uint)(bdat.Stride * bdat.Height);
                bih.biXPelsPerMeter = 0;
                bih.biYPelsPerMeter = 0;
                bih.biClrUsed = 0;
                bih.biClrImportant = 0;

                bw.Write( "RIFF".ToCharArray() );
                bw.Write( (uint)(0xdc + (m_bitmapsize + 0x8) * m_frames) );
                bw.Write( "AVI ".ToCharArray() );
                bw.Write( "LIST".ToCharArray() );
                bw.Write( (uint)0xc0 );
                bw.Write( "hdrl".ToCharArray() );
                bw.Write( "avih".ToCharArray() );
                bw.Write( (uint)0x38 );
                m_main_avi_header.Write( bw.BaseStream );
                bw.Write( "LIST".ToCharArray() );
                bw.Write( (uint)0x7C );
                bw.Write( "strl".ToCharArray() );
                bw.Write( "strh".ToCharArray() );
                bw.Write( (uint)0x38 );
                stream_header.Write( bw.BaseStream );
                bw.Write( (uint)0x0 );
                bw.Write( (uint)0x0 );
                bw.Write( "strf".ToCharArray() );
                bw.Write( (uint)0x28 );
                bih.Write( bw.BaseStream );
                bw.Write( "LIST".ToCharArray() );
                bw.Write( (uint)((m_bitmapsize + 0x8) * m_frames) );
                bw.Write( "movi".ToCharArray() );*/

                m_header_written = true;
            }

            //bw.Write( "00db" );
            //bw.Write( (uint)m_bitmapsize );
            int address = bdat.Scan0.ToInt32();
            byte[] bitmapData = new byte[bdat.Stride * bdat.Height];
            Marshal.Copy( new IntPtr( address ), bitmapData, 0, bitmapData.Length );
            //bw.Write( (uint)m_main_avi_header.dwSuggestedBufferSize );
            bw.Write( "BM".ToCharArray() );
            bw.Write( m_bitmapsize );
            bw.Write( (uint)0x0 );
            bw.Write( (uint)0x36 );
            BITMAPINFOHEADER bih = new BITMAPINFOHEADER(); //(BITMAPINFOHEADER)Marshal.PtrToStructure( Marshal.AllocHGlobal( sizeof( BITMAPINFOHEADER ) ), typeof( BITMAPINFOHEADER ) );
            bih.biSize = (uint)(Marshal.SizeOf( bih ));
            bih.biWidth = bdat.Width;
            bih.biHeight = bdat.Height;
            bih.biPlanes = 1;
            bih.biBitCount = m_pix_fmt == PixelFormat.Format24bppRgb ? (short)24 : (short)32;
            bih.biCompression = 0;//BI_RGB
            bih.biSizeImage = (uint)(bdat.Stride * bdat.Height);
            bih.biXPelsPerMeter = 0;
            bih.biYPelsPerMeter = 0;
            bih.biClrUsed = 0;
            bih.biClrImportant = 0;
            bih.Write( bw );
            bw.Write( bitmapData, 0, bitmapData.Length );
            //const int _BUF_LEN = 512;
            byte[] buf = new byte[m_bitmapsize + 6];
            bw.BaseStream.Seek( 0, SeekOrigin.Begin );
            int len = bw.BaseStream.Read( buf, 0, (int)(m_bitmapsize + 6) );
            if ( len > 0 ) {
                m_stream.BeginWrite( buf, 0, len, null, null );
            }
            m_stream.Flush();
            bw.Close();

            bmp.UnlockBits( bdat );
            return true;
        }


        private void WriteFourCC( string value ) {
            byte[] b = new byte[4];
            for ( int i = 0; i < 4; i++ ) {
                b[i] = (byte)value[i];
            }
            m_stream.Write( b, 0, 4 );
        }


        private void Write4Byte( uint value ) {
            byte[] b;
            b = BitConverter.GetBytes( value );
            if ( !BitConverter.IsLittleEndian ) {
                Array.Reverse( b );
            }
            m_stream.Write( b, 0, 4 );
        }


        public void Close() {
            if ( m_stream != null ) {
#if !TEST
                m_stream.Disconnect();
#endif
                m_stream.Close();
            }
            if ( m_ffmpeg != null ) {
                if ( m_ffmpeg.IsAlive ) {
                    m_ffmpeg.Abort();
                    while ( m_ffmpeg.IsAlive ) {
                    }
                }
            }
        }


        public void Open( uint scale, uint rate, ulong frames, PixelFormat pix_fmt ) {
            if ( m_stream != null ) {
                m_stream.Close();
            }
#if TEST
            m_stream = new FileStream( "test.out.avi", FileMode.Create );
#else
            m_stream = new NamedPipeServerStream( _PIPE_NAME, PipeDirection.Out, 1, PipeTransmissionMode.Byte, PipeOptions.None, 1, 1 );
#endif
            m_scale = scale;
            m_rate = rate;
            m_frames = frames;

            m_main_avi_header.dwMicroSecPerFrame = (uint)(1.0e6 * (double)scale / (double)rate);//  ! 1秒は10^6μ秒
            m_main_avi_header.dwReserved1 = 0;
            m_main_avi_header.dwFlags = 2064;
            m_main_avi_header.dwInitialFrames = 0;
            m_main_avi_header.dwStreams = 0;
            m_main_avi_header.dwScale = scale;
            m_main_avi_header.dwRate = rate;
            m_main_avi_header.dwStart = 0;
            m_main_avi_header.dwLength = 0;
            m_main_avi_header.dwTotalFrames = (uint)m_frames;

            m_pix_fmt = pix_fmt;
            if ( m_pix_fmt != PixelFormat.Format24bppRgb && m_pix_fmt != PixelFormat.Format32bppArgb ) {
                throw new ApplicationException( "pixel format not supported" );
            }
#if !TEST
            m_ffmpeg = new Thread( new ThreadStart( FFmpegEnc ) );
            m_ffmpeg.Start();
            m_stream.WaitForConnection();
#endif
        }


        private void FFmpegEnc() {
            Thread.Sleep( 1000 );
            Process client = new Process();
            client.StartInfo.FileName = "ffmpeg";

            // windowsの場合
            client.StartInfo.Arguments = @"-f image2pipe -vcodec bmp -i \\.\pipe\" + _PIPE_NAME + " -isync -an -f avi -y test.avi";

            // その他
            // client.StartInfo.Arguments = @"-i " + _PIPE_NAME + " -y test.mp3";

            client.StartInfo.RedirectStandardOutput = true;
            client.StartInfo.UseShellExecute = false;
            client.Start();
            StreamReader sr = client.StandardOutput;
            string line = "";
            while ( (line = sr.ReadLine()) != null ) {
                Console.WriteLine( line );
            }
        }
    }

}
#endif
