using System;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Imaging;
using System.Reflection;
using System.Runtime.InteropServices;
using Microsoft.Win32;
using Nativ;


namespace Avi
{

    interface IRecorded
    {
        int Length();
    }


    public abstract class VideoStream : Stream
    {
        // Указатель на сжатый поток
        protected BITMAPINFOHEADER bh;
        protected IntPtr streamCompressed = IntPtr.Zero;
        internal override void Close()
        {
            // Освободить сжатый поток

            if (streamCompressed != IntPtr.Zero)
            {
                NativeMethods.AVIStreamRelease(streamCompressed);
                streamCompressed = IntPtr.Zero;
            }
            base.Close();
        }
        public int Width { get { return streaminfo.rectFrame.right - streaminfo.rectFrame.left; } }
        public int Height { get { return streaminfo.rectFrame.bottom - streaminfo.rectFrame.top; } }
        public int Rate { get { return streaminfo.rate; } }
        protected int FrameCount { get { return streaminfo.length; } }
        internal string Codec { get { return NativeMethods.decode_mmioFOURCC(streaminfo.handler); } }

        protected VideoStream() { }

        internal VideoStream(IntPtr instream, AVISTREAMINFO instreaminfo)
            : base(instream, instreaminfo)
        {
        }

        public int TimeToPos(int ms)
        {
            if (streamCompressed == IntPtr.Zero)
                throw new VideoException("Поток не открыт");
            //int tmp = NativeMethods.AVIStreamTimeToSample(stream, ms);

            int tmp = (ms * Rate) / 1000;
            if (tmp >= FrameCount)
            {
                return -1;
            }
            return tmp;
        }



    }
    //public class VideoStreamRead : VideoStream, IRecorded
    //{
    //    object sync = new object();
    //    public VideoStreamRead(IntPtr instream, AVISTREAMINFO instreaminfo)
    //        : base(instream, instreaminfo)
    //    {

    //        int firstFrame = NativeMethods.AVIStreamStart(stream);
    //        int lStreamSize = 0;
    //        NativeMethods.AVIStreamReadFormat(stream, firstFrame, ref  bh, ref lStreamSize);
    //        NativeMethods.AVIStreamReadFormat(stream, firstFrame, ref  bh, ref lStreamSize);
    //        bh.bitCount = 24;
    //        bh.compression = 0;
    //        bh.sizeImage = 0;
    //        bh.planes = 1;
    //        if ((streamCompressed = NativeMethods.AVIStreamGetFrameOpen(stream, ref bh)) == IntPtr.Zero)
    //        {
    //            bh.height = -bh.height;

    //            if ((streamCompressed = NativeMethods.AVIStreamGetFrameOpen(stream, ref bh)) == IntPtr.Zero)
    //                throw new VideoException("Failed initializing decompressor.");
    //        }
    //    }

    //    public Bitmap GetNextFrame()
    //    {
    //        return GetFrame(position++);

    //    }

    //    public Bitmap GetFrame(int position)
    //    {
    //        lock (sync)
    //        {
    //            // create new bitmap
    //            Bitmap image = new Bitmap(Width, Height, PixelFormat.Format24bppRgb);

    //            if (position == -1)
    //            {
    //                Graphics g = Graphics.FromImage(image);
    //                SizeF size = g.MeasureString("No Frame", new Font("Arial", 16));
    //                float x = (image.Width - size.Width) / 2;
    //                float y = image.Height / 2 - size.Height;
    //                g.DrawString("No Frame", new Font("Arial", 16), new SolidBrush(Color.Red), x, y);
    //            }
    //            else
    //            {


    //                IntPtr DIB = NativeMethods.AVIStreamGetFrame(streamCompressed, position);
    //                if (DIB == IntPtr.Zero)
    //                {
    //                    image.Dispose();
    //                    throw new VideoException("Failed getting frame.");
    //                }
    //                //  BITMAPINFOHEADER bitmapInfoHeader;

    //                // copy BITMAPINFOHEADER from unmanaged memory
    //                //    bitmapInfoHeader = (BITMAPINFOHEADER)Marshal.PtrToStructure(DIB, typeof(BITMAPINFOHEADER));

    //                // lock bitmap data
    //                BitmapData imageData = image.LockBits(
    //                    new Rectangle(0, 0, Width, Height),
    //                    ImageLockMode.ReadWrite,
    //                    PixelFormat.Format24bppRgb);

    //                int stride = Width * 3;
    //                if ((stride % 4) != 0)
    //                    stride += (4 - stride % 4);

    //                // copy image data
    //                int srcStride = stride;
    //                int dstStride = imageData.Stride;
    //                // check image direction
    //                if (Height > 0)
    //                {
    //                    // it`s a bottom-top image
    //                    int dst = imageData.Scan0.ToInt32() + dstStride * (Height - 1);
    //                    int src = DIB.ToInt32() + Marshal.SizeOf(bh);

    //                    for (int y = 0; y < Height; y++)
    //                    {
    //                        NativeMethods.memcpy(dst, src, srcStride);
    //                        dst -= dstStride;
    //                        src += srcStride;
    //                    }
    //                }
    //                else
    //                {
    //                    // it`s a top bootom image
    //                    int dst = imageData.Scan0.ToInt32();
    //                    int src = DIB.ToInt32() + Marshal.SizeOf(bh);

    //                    // copy the whole image
    //                    NativeMethods.memcpy(dst, src, srcStride * Height);
    //                }

    //                image.UnlockBits(imageData);

    //            }

    //            // unlock bitmap data
    //            // move position to the next frame
    //            return image;
    //        }
    //    }

    //    internal override void Close()
    //    {
    //        base.Close();
    //    }

    //    public int Length()
    //    {

    //        return FrameCount * 1000 / Rate;

    //    }

    //    //public int TimeToPos(int time)
    //    //{
    //    //     return time * Rate / 1000;

    //    //}
    //}

    public class VideoStreamWrite : VideoStream
    {
        private int stride;
        //   private IntPtr buffer = IntPtr.Zero;
        // Bitmap _backup = null;
        //  private Thread t = null;
        //  Timer tmr;
        //     ManualResetEvent saveEvent = new ManualResetEvent(true);
        //   volatile bool exitEvent = false;
        //public Bitmap Image
        //{
        //    get { return framesToSave.Peek(); }

        //}
        //  private MagicQueue<Bitmap> framesToSave;
        // private object sync;// = framesToSave;//new object();
        //public int Length
        //{
        //    get { return framesToSave.count; }
        //}
        internal VideoStreamWrite(IntPtr file, int Iwidth, int Iheight, int framerate, string codec = "DIB ", string name = "", Bitmap screen = null)
        {
            Log.LogClass.Enqueue(MethodBase.GetCurrentMethod(), "  Создание видеопотока " + name + " " + codec + " " + Iwidth + "x" + Iheight + "x" + framerate);

            // Рассчитываем шаг
            stride = Iwidth * 3;
            if ((stride % 4) != 0)
                stride += (4 - stride % 4);

            streaminfo = new AVISTREAMINFO();
            streaminfo.type = (int)StreamType.VIDEO;//NativeMethods.mmioFOURCC("vids"); //тип потока.
            streaminfo.handler = NativeMethods.mmioFOURCC(codec);  //Алгоритм сжатия
            streaminfo.scale = 1;
            streaminfo.rate = framerate; //Количество сэмплов в секунду
            streaminfo.suggestedBufferSize = stride * Iheight;  //Размер буфера
            streaminfo.rectFrame = new RECT();
            streaminfo.rectFrame.left = 0;
            streaminfo.rectFrame.top = 0;
            streaminfo.rectFrame.right = Iwidth;
            streaminfo.rectFrame.bottom = Iheight;
            streaminfo.name = name;

            AviError err;
            Log.LogClass.Enqueue(MethodBase.GetCurrentMethod(), "  Создание потока в файле");
            if ((err = (AviError)NativeMethods.AVIFileCreateStream(file, out stream, ref streaminfo)) != 0)
                throw new VideoException("Failed creating stream. " + err.ToString());
            AVICOMPRESSOPTIONS options = new AVICOMPRESSOPTIONS();
            options.handler = NativeMethods.mmioFOURCC(codec);  //Имя кодека
            options.quality = -1; //Качество
            Log.LogClass.Enqueue(MethodBase.GetCurrentMethod(), "  Создание сжатого потока");

            if ((err = (AviError)NativeMethods.AVIMakeCompressedStream(out streamCompressed, stream, ref options, IntPtr.Zero)) != 0)
                throw new VideoException("Failed creating compressed stream." + err);
            // Установить формат кадра

            // Описание форматаа кадра(фрейма)
            bh = new BITMAPINFOHEADER();
            bh.size = Marshal.SizeOf(bh.GetType());  //Размер структуры
            bh.width = Iwidth;  //Ширина 
            bh.height = Iheight;  //Высота
            bh.planes = 1;
            bh.bitCount = 24; //Определяет количество бит на пиксель.
            bh.sizeImage = 0;// bh.width * bh.height * bh.bitCount / 8;  //Размер изображения в байтах
            bh.compression = 0; // Указывается тип сжатия для сжатых снизу вверх растрового изображения (сверху вниз DIBs не могут быть сжаты).
            Log.LogClass.Enqueue(MethodBase.GetCurrentMethod(), "  Установка параметров");

            if ((err = (AviError)NativeMethods.AVIStreamSetFormat(streamCompressed, 0, ref bh, Marshal.SizeOf(bh.GetType()))) != 0)
                throw new VideoException("Failed setting format of the compressed stream." + err);

            //   Bitmap _backup = null;
            //   if (screen == null)
            // {
            //    _backup = NoSignal(Iwidth, Iheight);
            //     _backup.RotateFlip(RotateFlipType.RotateNoneFlipY);
            // }
            //else
            // {
            // lock (screen)
            // {
            //    _backup = (Bitmap)screen.Clone();
            //}
            //  }
            //  framesToSave = new MagicQueue<Bitmap>(_backup, size.Height);

            position = 0;


        }
        internal override void Close()
        {
            Log.LogClass.Enqueue(MethodBase.GetCurrentMethod(), "  Закрытие видеопотока");

            //exitEvent = true;
            base.Close();
        }
        //public void WaitClosing()
        //{
        //    if (t != null)
        //        t.Join();
        //    base.Close();
        //}
        //void Exec()
        //{
        //    while (!exitEvent)
        //    {
        //        if (framesToSave.CanWrite())
        //        {
        //            Save();
        //        }
        //        Thread.Sleep(40);
        //        //if (saveEvent.WaitOne(20))
        //        //{
        //        //    saveEvent.Reset();
        //        //    Save();
        //        //}
        //    }

        //    framesToSave.Close();
        //    //if (tmr != null)
        //    //{
        //    //    tmr.Dispose();
        //    //    tmr = null;
        //    //}
        //    while (framesToSave.CanWrite())
        //    {
        //        Save();
        //    }
        //    framesToSave.Dispose();

        //}

        //internal void Save()
        //{

        //  //  lock (sync)
        //  //  {
        //      //  lock (_backup)
        //       // {

        //        // Проверка размеров кадра

        //            Bitmap _backup = framesToSave.Dequeue();

        //            lock (_backup)
        //            {
        //                if ((_backup.Width != Width) || (_backup.Height != Height))
        //                {
        //                    Graphics.FromImage(_backup).DrawImage(_backup, 0, 0, Width, Height);
        //                    throw new ArgumentException("Bitmap size must be of the same as video size, which was specified on opening video file.");
        //                    // Блокирование области изображения
        //                }

        //                BitmapData imageData = _backup.LockBits(
        //                     new Rectangle(0, 0, Width, Height),
        //                     ImageLockMode.ReadWrite, PixelFormat.Format24bppRgb);



        //                int res = NativeMethods.AVIStreamWrite(streamCompressed, position, 1, imageData.Scan0,
        //                stride * (Height), 0, IntPtr.Zero, IntPtr.Zero);
        //                if (res != 0)
        //                    throw new VideoException("Failed adding frame." + (AviError)res);

        //                _backup.UnlockBits(imageData);
        //            }
        //         //   _backup.Dispose();
        //            position++;

        //  //  }

        //}


        public void AddFrame(Bitmap frameImage, int count = 1)
        {

            //   lock (frameImage)
            //{
            if ((frameImage.Width != Width) || (frameImage.Height != Height))
            {
                Graphics.FromImage(frameImage).DrawImage(frameImage, 0, 0, Width, Height);
                Log.LogClass.Enqueue(MethodBase.GetCurrentMethod(), "  Изменение размеров изображения ");

                //  throw new ArgumentException("Bitmap size must be of the same as video size, which was specified on opening video file.");
                // Блокирование области изображения
            }

            BitmapData imageData = frameImage.LockBits(
                 new Rectangle(0, 0, Width, Height),
                 ImageLockMode.ReadWrite, PixelFormat.Format24bppRgb);


            for (int i = 0; i < count; ++i)
            {
                int res = NativeMethods.AVIStreamWrite(streamCompressed, position, 1, imageData.Scan0,
                stride * (Height), 0, IntPtr.Zero, IntPtr.Zero);
                if (res != 0)
                {
                    Log.LogClass.Enqueue(MethodBase.GetCurrentMethod(), "  Ошибка добаления фрейма  " + (AviError)res);
                    throw new VideoException("Failed adding frame." + (AviError)res);
                }
                position++;
            }
            frameImage.UnlockBits(imageData);
            //  }
            //   _backup.Dispose();


            //  lock (sync)
            // {

            //Bitmap tmp = (Bitmap)frameImage.Clone();
            //tmp.RotateFlip(RotateFlipType.RotateNoneFlipY);
            //framesToSave.Enqueue(tmp);

            // _backup.Dispose();
            //  _backup = (Bitmap)tmp.Clone();
            //_backup = tmp;
            //}
        }

        //public void AddFrame(NewFramePtrEventArgs frameImage, int count = 1)
        //{
        //   // Bitmap.FromHbitmap(frameImage.buf);
        //   // AddFrame(Bitmap.FromHbitmap(frameImage.buf), count);
        //    //return;

        //    //   lock (frameImage)
        //    //{
        //    for (int i = 0; i < count; ++i)
        //    {
        //        int res = NativeMethods.AVIStreamWrite(streamCompressed, position, 1, frameImage.buf,
        //        stride * (Height), 0, IntPtr.Zero, IntPtr.Zero);
        //        if (res != 0)
        //            throw new VideoException("Failed adding frame." + (AviError)res);
        //        position++;
        //    }

        //    //  }
        //    //   _backup.Dispose();


        //    //  lock (sync)
        //    // {

        //    //Bitmap tmp = (Bitmap)frameImage.Clone();
        //    //tmp.RotateFlip(RotateFlipType.RotateNoneFlipY);
        //    //framesToSave.Enqueue(tmp);

        //    // _backup.Dispose();
        //    //  _backup = (Bitmap)tmp.Clone();
        //    //_backup = tmp;
        //    //}
        //}

        static public Bitmap NoSignal(int width, int height)
        {
            using (Font font = new Font("Arial", 16))
            using (Brush brush = new SolidBrush(Color.Red))
            {
                Bitmap result = new Bitmap(width, height);
                using (Graphics g = Graphics.FromImage(result))
                {
                    using (Brush back = new SolidBrush(Color.Black))
                        g.FillRectangle(back, 0, 0, width, height);
                    g.DrawString("Нет сигнала", font, brush, (width - g.MeasureString("Нет сигнала", font).Width) / 2, (height - g.MeasureString("Нет сигнала", font).Height) / 2);
                }
                return result;
            }
        }
    }



    class CodecInfo
    {
        public string Fcc { get; private set; }
        public string Description { get; private set; }
        public override string ToString()
        {
            return Description + " (" + Fcc + ")";
        }

        CodecInfo(string fcc, string desc)
        {
            Fcc = fcc.ToUpper();
            Description = desc;
        }


        public static List<CodecInfo> ReadCodecList()
        {
            string basePath = @"Software\Microsoft\ActiveMovie\devenum\{33D9A760-90C8-11D0-BD43-00A0C911CE86}";

            List<CodecInfo> codecs = new List<CodecInfo>();
            RegistryKey key = null;

            try
            {
                key = Registry.CurrentUser.OpenSubKey(basePath);
                string[] subkeys = key.GetSubKeyNames();

                foreach (string subkey in subkeys)
                {
                    RegistryKey skey = null;
                    try
                    {
                        skey = key.OpenSubKey(subkey);
                        codecs.Add
                        (
                            new CodecInfo
                            (
                                skey.GetValue("FccHandler").ToString(),
                                skey.GetValue("FriendlyName").ToString()
                            )
                        );
                    }
                    catch (Exception)
                    {
                    }
                    if (skey != null)
                        skey.Close();
                }
            }
            catch (Exception)
            {
            }

            if (key != null)
                key.Close();

            return codecs;
        }


    }


}
