using System;
using System.IO;
using System.Collections.Generic;
using System.Text;
using System.Threading;
using System.Linq;
using System.Runtime.InteropServices;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Barotrauma.Sounds;

namespace Barotrauma.Media
{
    public class Video : IDisposable
    {
        [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
        public delegate IntPtr LibVLCVideoLockCb(IntPtr opaque, IntPtr planes);
        private static LibVLCVideoLockCb VideoLockDelegate;

        [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
        public delegate void LibVLCVideoUnlockCb(IntPtr opaque, IntPtr picture, IntPtr planes);
        private static LibVLCVideoUnlockCb VideoUnlockDelegate;

        [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
        public delegate void LibVLCVideoDisplayCb(IntPtr opaque, IntPtr picture);
        private static LibVLCVideoDisplayCb VideoDisplayDelegate;

        [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
        public delegate void LibVLCAudioPlayCb(IntPtr data, IntPtr samples, uint count, long pts);
        private static LibVLCAudioPlayCb AudioPlayDelegate;

        [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
        public delegate void LibVLCAudioPauseCb(IntPtr data, long pts);
        private static LibVLCAudioPauseCb AudioPauseDelegate;

        [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
        public delegate void LibVLCAudioResumeCb(IntPtr data, long pts);
        private static LibVLCAudioResumeCb AudioResumeDelegate;

        [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
        public delegate void LibVLCAudioFlushCb(IntPtr data, long pts);
        private static LibVLCAudioFlushCb AudioFlushDelegate;

        [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
        public delegate void LibVLCAudioDrainCb(IntPtr data);
        private static LibVLCAudioDrainCb AudioDrainDelegate;


        private static IntPtr lib = IntPtr.Zero;

        private static Dictionary<int, Video> videos;
        private static int latestVideoId;
        public static void Init()
        {
            if (lib == IntPtr.Zero)
            {
                string[] parameters = {
                    //"--no-xlib"
                };

                lib = LibVlcWrapper.LibVlcMethods.libvlc_new(parameters.Length, parameters);

                videos = new Dictionary<int, Video>();
                latestVideoId = 0;

                VideoLockDelegate = VideoLockCallback;
                VideoUnlockDelegate = VideoUnlockCallback;
                VideoDisplayDelegate = VideoDisplayCallback;

                AudioPlayDelegate = AudioPlayCallback;
                AudioPauseDelegate = AudioPauseCallback;
                AudioResumeDelegate = AudioResumeCallback;
                AudioFlushDelegate = AudioFlushCallback;
                AudioDrainDelegate = AudioDrainCallback;
            }
        }

        public static void Close()
        {
            if (lib != IntPtr.Zero)
            {
                List<int> keys = videos.Keys.ToList();
                foreach (int key in keys)
                {
                    videos[key].Dispose();
                }

                LibVlcWrapper.LibVlcMethods.libvlc_free(lib);

                lib = IntPtr.Zero;
            }
        }

        private int videoId;
        private IntPtr unmanagedData;

        private IntPtr media;
        private IntPtr mediaPlayer;
        private Texture2D texture;
        private byte[] textureData;
        private object mutex;
        private VideoSound sound;

        /*private IntPtr videoLockDelegate;
        private IntPtr videoUnlockDelegate;
        private IntPtr videoDisplayDelegate;*/

        public uint Width { get; private set; }
        public uint Height { get; private set; }

        public Video(GraphicsDevice graphicsDevice,SoundManager soundManager,string filename,uint width,uint height)
        {
            Init();

            mutex = new object();
            sound = new VideoSound(soundManager, filename, 44100, this);
            videos.Add(latestVideoId, this);
            videoId = latestVideoId; latestVideoId++;

            /*using (var stream = File.OpenRead(filename))
            {*/
            byte[] filenameBytes = Encoding.UTF8.GetBytes(filename);
            byte[] filenameBytesNullTerminated = new byte[filenameBytes.Length + 1];
            filenameBytesNullTerminated[filenameBytes.Length] = 0;
            Array.Copy(filenameBytes, filenameBytesNullTerminated, filenameBytes.Length);
            media = LibVlcWrapper.LibVlcMethods.libvlc_media_new_path(lib, filenameBytesNullTerminated);
            LibVlcWrapper.LibVlcMethods.libvlc_media_parse(media);

            mediaPlayer = LibVlcWrapper.LibVlcMethods.libvlc_media_player_new(lib);
            LibVlcWrapper.LibVlcMethods.libvlc_media_player_set_media(mediaPlayer, media);
            //LibVlcWrapper.LibVlcMethods.libvlc_media_release(media);

            //LibVlcWrapper.LibVlcMethods.libvlc_video_get_size(mediaPlayer, 0, out width, out height);
            texture = new Texture2D(graphicsDevice, (int)width, (int)height);
            Width = width; Height = height;

            unmanagedData = Marshal.AllocHGlobal(sizeof(int)*2+(int)(width*height*3));
            int[] arr = { videoId, 1 };
            Marshal.Copy(arr, 0, unmanagedData, 2);
            textureData = new byte[width * height * 3];
            for (int i = 0; i < width*height*3; i++)
            {
                textureData[i] = 0;
            }

            IntPtr videoLockDelegatePtr = Marshal.GetFunctionPointerForDelegate(VideoLockDelegate);
            IntPtr videoUnlockDelegatePtr = Marshal.GetFunctionPointerForDelegate(VideoUnlockDelegate);
            IntPtr videoDisplayDelegatePtr = Marshal.GetFunctionPointerForDelegate(VideoDisplayDelegate);
            LibVlcWrapper.LibVlcMethods.libvlc_video_set_callbacks(mediaPlayer, videoLockDelegatePtr, videoUnlockDelegatePtr, videoDisplayDelegatePtr, unmanagedData);
            LibVlcWrapper.LibVlcMethods.libvlc_video_set_format(mediaPlayer, Encoding.UTF8.GetBytes("RV24"), (int)width, (int)height, (int)width * 3);

            IntPtr audioPlayDelegatePtr = Marshal.GetFunctionPointerForDelegate(AudioPlayDelegate);
            IntPtr audioPauseDelegatePtr = Marshal.GetFunctionPointerForDelegate(AudioPauseDelegate);
            IntPtr audioResumeDelegatePtr = Marshal.GetFunctionPointerForDelegate(AudioResumeDelegate);
            IntPtr audioFlushDelegatePtr = Marshal.GetFunctionPointerForDelegate(AudioFlushDelegate);
            IntPtr audioDrainDelegatePtr = Marshal.GetFunctionPointerForDelegate(AudioDrainDelegate);

            LibVlcWrapper.LibVlcMethods.libvlc_audio_set_callbacks(mediaPlayer, audioPlayDelegatePtr, audioPauseDelegatePtr, audioResumeDelegatePtr, audioFlushDelegatePtr, audioDrainDelegatePtr, unmanagedData);
            LibVlcWrapper.LibVlcMethods.libvlc_audio_set_format(mediaPlayer, Encoding.UTF8.GetBytes("S16N"), 44100, 2);

            LibVlcWrapper.LibVlcMethods.libvlc_audio_set_delay(mediaPlayer, 0);

            LibVlcWrapper.LibVlcMethods.libvlc_media_player_play(mediaPlayer);

            //}
        }

        public void Dispose()
        {
            LibVlcWrapper.LibVlcMethods.libvlc_media_player_stop(mediaPlayer);

            Monitor.Enter(mutex);
            //just waiting for callbacks to be done
            Monitor.Exit(mutex);

            Marshal.FreeHGlobal(unmanagedData);

            sound.Dispose();

            LibVlcWrapper.LibVlcMethods.libvlc_media_release(media);
            LibVlcWrapper.LibVlcMethods.libvlc_media_player_release(mediaPlayer);
            
            texture.Dispose();

            videos.Remove(videoId);
        }

        public bool IsPlaying
        {
            get
            {
                return LibVlcWrapper.LibVlcMethods.libvlc_media_player_is_playing(mediaPlayer)!=0;
            }
        }

        public Texture2D GetTexture()
        {
            Monitor.Enter(mutex);

            int[] arr = { -1 };
            IntPtr changedPtr = (IntPtr)(unmanagedData.ToInt64() + sizeof(int));
            Marshal.Copy(changedPtr, arr, 0, 1);

            if (arr[0]!=0)
            {
                IntPtr colorLocation = (IntPtr)(unmanagedData.ToInt64() + sizeof(int) * 2);
                Marshal.Copy(colorLocation, textureData, 0, (int)(Width * Height * 3));
                Color[] colors = new Color[Width * Height];
                for (int y = 0; y < Height;y++)
                {
                    for (int x = 0; x < Width;x++)
                    {
                        colors[x + y * Width] = new Color(textureData[3 * (x + y * Width) + 0],
                                                          textureData[3 * (x + y * Width) + 1],
                                                          textureData[3 * (x + y * Width) + 2],
                                                          (byte)255);
                    }
                }
                texture.SetData(colors);
                arr[0] = 0;
                Marshal.Copy(arr, 0, changedPtr, 1);
            }

            if (!sound.IsPlaying()) videos[videoId].sound.Play();

            Monitor.Exit(mutex);

            return texture;
        }

        static IntPtr VideoLockCallback(IntPtr opaque, IntPtr planes)
        {
            int[] arr = { -1 };
            Marshal.Copy(opaque, arr, 0, 1);
            int mutexIndex = arr[0];

            Monitor.Enter(videos[mutexIndex].mutex);

            IntPtr unmanagedData = (IntPtr)(opaque.ToInt64() + sizeof(int) * 2);
            IntPtr[] ptrPtr = { unmanagedData };
            Marshal.Copy(ptrPtr, 0, planes, 1);

            return IntPtr.Zero;
        }

        static void VideoUnlockCallback(IntPtr opaque, IntPtr picture, IntPtr planes)
        {
            int[] arr = { -1 };
            Marshal.Copy(opaque, arr, 0, 1);
            int mutexIndex = arr[0];
            arr[0] = 1;
            IntPtr changedPtr = (IntPtr)(opaque.ToInt64() + sizeof(int));
            Marshal.Copy(arr, 0, changedPtr, 1);

            Monitor.Exit(videos[mutexIndex].mutex);
        }

        static void VideoDisplayCallback(IntPtr opaque,IntPtr picture)
        {
            //Assert(picture == IntPtr.Zero);
        }

        static void AudioPlayCallback(IntPtr data, IntPtr samples, uint count, long pts)
        {
            short[] buf = new short[count*2];
            Marshal.Copy(samples, buf, 0, (int)count*2);
            int[] arr = { -1 };
            Marshal.Copy(data, arr, 0, 1);
            int mutexIndex = arr[0];

            Monitor.Enter(videos[mutexIndex].mutex);

            videos[mutexIndex].sound.Enqueue(buf);

            Monitor.Exit(videos[mutexIndex].mutex);
        }

        static void AudioPauseCallback(IntPtr data, long pts)
        {

        }

        static void AudioResumeCallback(IntPtr data, long pts)
        {

        }

        static void AudioFlushCallback(IntPtr data, long pts)
        {

        }

        static void AudioDrainCallback(IntPtr data)
        {

        }
    }
}
