using Microsoft.Iris.Library;
using Microsoft.Iris.Render.Animation;
using Microsoft.Iris.Render.Common;
using Microsoft.Iris.Render.Graphics;
using Microsoft.Iris.Render.Protocol;
using Microsoft.Iris.Render.Protocols.Splash.Desktop;
using Microsoft.Iris.Render.Protocols.Splash.Desktop.Nt;
using Microsoft.Iris.Render.Protocols.Splash.Rendering;
using Microsoft.Iris.Render.Protocols.Splash.Rendering.Nt;
using Microsoft.Iris.Render.Remote;
using Microsoft.Iris.Render.Sound;
using System;

namespace Microsoft.Iris.Render.Internal
{
    internal class RenderSession : RenderObject, IRenderSession, IRenderObject, IDisposable
    {
        private RenderEngine m_renderEngine;
        private MessagingSession m_owningSession;
        private VisualPropertyManager m_visPropManager;
        private ProtocolSplashDesktop m_protocolDesktop;
        private ProtocolSplashDesktopNt m_protocolDesktopNt;
        private ProtocolSplashRendering m_protocolRendering;
        private ProtocolSplashRenderingNt m_protocolRenderingNt;
        private ProtocolSplashRendering m_protocolRenderingLocal;
        private AnimationSystem m_animationSystem;
        private ObjectCacheManager m_cacheManager;
        private Common.ObjectCache m_cacheSprite;
        private Common.ObjectCache m_cacheContainer;

        internal RenderSession(RenderEngine renderEngine)
        {
            Debug2.Validate(renderEngine != null, typeof(ArgumentNullException), nameof(renderEngine));
            this.RegisterOwningThread();
            this.m_protocolRendering = ProtocolSplashRendering.Bind(this.Messaging.RenderingPort);
            this.m_protocolDesktop = ProtocolSplashDesktop.Bind(this.Messaging.WindowingPort);
            this.m_protocolRenderingNt = ProtocolSplashRenderingNt.Bind(this.Messaging.RenderingPort);
            this.m_protocolDesktopNt = ProtocolSplashDesktopNt.Bind(this.Messaging.WindowingPort);
            if (this.Messaging.WindowingPort != this.Messaging.RenderingPort)
                this.m_protocolRenderingLocal = ProtocolSplashRendering.Bind(this.Messaging.WindowingPort);
            this.m_renderEngine = renderEngine;
            this.m_visPropManager = new VisualPropertyManager();
            this.m_cacheManager = null;
        }

        internal void Initialize() => this.m_animationSystem = new AnimationSystem(this);

        protected override void Dispose(bool fInDispose)
        {
            try
            {
                if (fInDispose && this.m_animationSystem != null)
                    this.m_animationSystem.UnregisterUsage(this);
                this.UnregisterOwningThread();
                this.m_animationSystem = null;
                this.m_visPropManager.Dispose();
            }
            finally
            {
                base.Dispose(fInDispose);
            }
        }

        IGraphicsDevice IRenderSession.GraphicsDevice => null;// m_renderEngine.Window.GraphicsDevice;

        ISoundDevice IRenderSession.SoundDevice => m_renderEngine.SoundDevice;

        internal SoundDevice SoundDevice => this.m_renderEngine.SoundDevice;

        IAnimationSystem IRenderSession.AnimationSystem => m_animationSystem;

        IInputSystem IRenderSession.InputSystem => m_renderEngine.InputSystem;

        internal MessagingSession Messaging => this.m_owningSession;

        internal RenderPort RenderingPort => this.m_owningSession.RenderingPort;

        internal RenderPort WindowingPort => this.m_owningSession.WindowingPort;

        internal ProtocolSplashDesktop DesktopProtocol => this.m_protocolDesktop;

        internal ProtocolSplashDesktopNt NtDesktopProtocol => this.m_protocolDesktopNt;

        internal ProtocolSplashRendering RenderingProtocol => this.m_protocolRendering;

        internal ProtocolSplashRenderingNt NtRenderingProtocol => this.m_protocolRenderingNt;

        internal ProtocolSplashRendering LocalRenderingProtocol => this.m_protocolRenderingLocal;

        internal bool IsValid => this.m_owningSession.IsConnected;

        internal bool IsForeignByteOrderOnWindowing => this.WindowingPort.ForeignByteOrder;

        internal VisualPropertyManager VisualPropertyManager => this.m_visPropManager;

        internal ObjectCacheManager CacheManager => this.m_cacheManager;

        public IEffectTemplate CreateEffectTemplate(object objUser, string stName)
        {
            Debug2.Validate(objUser != null, typeof(ArgumentNullException), nameof(objUser));
            EffectTemplate effectTemplate = this.m_renderEngine.Window.GraphicsDevice.CreateEffectTemplate(stName);
            effectTemplate.RegisterUsage(objUser);
            return effectTemplate;
        }

        public IVideoStream CreateVideoStream(object objUser)
        {
            Debug2.Validate(objUser != null, typeof(ArgumentNullException), nameof(objUser));
            VideoStream videoStream = new VideoStream(this, this.m_renderEngine.Window.GraphicsDevice);
            videoStream.RegisterUsage(objUser);
            return videoStream;
        }

        public IVisualContainer CreateVisualContainer(
          object objUser,
          object objOwnerData)
        {
            Debug2.Validate(objUser != null, typeof(ArgumentNullException), nameof(objUser));
            VisualContainer visualContainer = null;
            if (this.m_cacheContainer != null)
            {
                visualContainer = (VisualContainer)this.m_cacheContainer.Remove(objUser);
                if (visualContainer != null)
                    visualContainer.OwnerData = objOwnerData;
            }
            if (visualContainer == null)
            {
                RemoteVisual remoteVisual;
                visualContainer = new VisualContainer(false, this, this.m_renderEngine.Window, objOwnerData, out remoteVisual);
                visualContainer.RegisterUsage(objUser);
                visualContainer.Cache = this.m_cacheContainer;
            }
            return visualContainer;
        }

        public ICamera CreateCamera(object objUser)
        {
            Debug2.Validate(objUser != null, typeof(ArgumentNullException), nameof(objUser));
            Camera camera = new Camera(this);
            camera.RegisterUsage(objUser);
            return camera;
        }

        public IGradient CreateGradient(object objUser)
        {
            Debug2.Validate(objUser != null, typeof(ArgumentNullException), nameof(objUser));
            Gradient gradient = new Gradient(this, this.m_renderEngine.Window.GraphicsDevice.RemoteDevice);
            gradient.RegisterUsage(objUser);
            return gradient;
        }

        public ISprite CreateSprite(object objUser, object objOwnerData)
        {
            Debug2.Validate(objUser != null, typeof(ArgumentNullException), nameof(objUser));
            Sprite sprite = null;
            if (this.m_cacheSprite != null)
            {
                sprite = (Sprite)this.m_cacheSprite.Remove(objUser);
                if (sprite != null)
                    sprite.OwnerData = objOwnerData;
            }
            if (sprite == null)
            {
                sprite = new Sprite(this, this.m_renderEngine.Window, objOwnerData);
                sprite.RegisterUsage(objUser);
                sprite.Cache = this.m_cacheSprite;
            }
            return sprite;
        }

        public IImage CreateImage(
          object objUser,
          string identifier,
          ContentNotifyHandler handler)
        {
            Debug2.Validate(objUser != null, typeof(ArgumentNullException), nameof(objUser));
            Debug2.Validate(handler != null, typeof(InvalidOperationException), "Invalid handler");
            Image image = this.m_renderEngine.Window.GraphicsDevice.CreateImage(identifier, handler);
            image.RegisterUsage(objUser);
            return image;
        }

        internal GraphicsCaps GetGraphicsCaps(GraphicsDeviceType type)
        {
            foreach (GraphicsCaps graphicsCap in this.m_renderEngine.GraphicsCaps)
            {
                if (graphicsCap.DeviceType.FulfillsRequirement(type))
                    return graphicsCap;
            }
            return new GraphicsCaps();
        }

        private void RegisterOwningThread() => this.m_owningSession = MessagingSession.Current;

        private void UnregisterOwningThread() => this.m_owningSession = null;

        internal void AssertOwningThread()
        {
        }

        internal bool IsOwningThread() => this.m_owningSession == MessagingSession.Current;

        public void DeferredInvoke(Delegate method, object args, DeferredInvokePriority priority)
        {
            if (this.m_owningSession == null)
                return;
            this.m_owningSession.DeferredInvoke(method, args, priority, TimeSpan.FromMilliseconds(0.0));
        }

        public void DeferredInvoke(
          SharedRenderObject owner,
          Delegate method,
          object args,
          DeferredInvokePriority priority)
        {
            if (this.m_owningSession == null)
                return;
            this.m_owningSession.DeferredInvoke(owner, method, args, priority, TimeSpan.FromMilliseconds(0.0));
        }

        internal void DeferredInvoke(
          Delegate method,
          object args,
          DeferredInvokePriority priority,
          TimeSpan delay)
        {
            if (this.m_owningSession == null)
                return;
            this.m_owningSession.DeferredInvoke(method, args, priority, delay);
        }

        internal void ReleaseSharedResources()
        {
            if (this.m_cacheSprite != null)
            {
                this.m_cacheManager.UnregisterCache(this.m_cacheSprite);
                this.m_cacheSprite.Dispose();
                this.m_cacheSprite = null;
            }
            if (this.m_cacheContainer == null)
                return;
            this.m_cacheManager.UnregisterCache(this.m_cacheContainer);
            this.m_cacheContainer.Dispose();
            this.m_cacheContainer = null;
        }

        internal RENDERHANDLE AllocateRenderHandle(IRenderHandleOwner owner) => this.RenderingPort.AllocHandle(owner);

        internal void FreeRenderHandle(RENDERHANDLE handle) => this.RenderingPort.FreeHandle(handle);

        internal IVisual TryGetHandleOwner(uint dwHandleData) => this.WindowingPort.TryGetHandleOwner(RENDERHANDLE.FromUInt32(dwHandleData)) as IVisual;

        public RemoteDesktopManager BuildRemoteDesktopManager(
          DisplayManager owner,
          bool fEnumDisplayModes)
        {
            Debug2.Validate(this.IsOwningThread(), typeof(InvalidOperationException), "Must be called on the session's owning client thread");
            return this.NtDesktopProtocol.BuildRemoteDesktopManager(owner, this.NtDesktopProtocol.LocalDesktopManagerCallbackHandle, fEnumDisplayModes);
        }

        internal RemoteFormWindow BuildRemoteFormWindow(
          RenderWindow window,
          DisplayManager displayManager)
        {
            Debug2.Validate(this.IsOwningThread(), typeof(InvalidOperationException), "Must be called on the session's owning client thread");
            return this.NtDesktopProtocol.BuildRemoteFormWindow(window, displayManager.RemoteStub, this.DesktopProtocol.LocalFormWindowCallbackHandle);
        }

        internal RemoteInputRouter BuildRemoteInputRouter(
          InputSystem inputSystem,
          RenderWindow window)
        {
            Debug2.Validate(this.IsOwningThread(), typeof(InvalidOperationException), "Must be called on the session's owning client thread");
            RemoteInputRouter remoteInputRouter = this.DesktopProtocol.BuildRemoteInputRouter(inputSystem, this.DesktopProtocol.LocalInputCallbackHandle);
            remoteInputRouter?.SendRegisterWithInputSource(this.WindowingPort.DefaultGroupID, ((IRenderHandleOwner)window).RenderHandle);
            return remoteInputRouter;
        }

        internal RemoteNtDevice BuildRemoteNtDevice(NtGraphicsDevice device) => this.NtRenderingProtocol.BuildRemoteNtDevice(device, this.RenderingProtocol.LocalDeviceCallbackHandle);

        internal RemoteGdiDevice BuildRemoteGdiDevice(GdiGraphicsDevice device) => this.NtRenderingProtocol.BuildRemoteGdiDevice(device, this.RenderingProtocol.LocalDeviceCallbackHandle);

        internal RemoteNullDevice BuildRemoteNullDevice(
          NullGraphicsDevice device,
          bool fUseSplitWindowingPort)
        {
            return fUseSplitWindowingPort ? this.LocalRenderingProtocol.BuildRemoteNullDevice(device, this.LocalRenderingProtocol.LocalDeviceCallbackHandle) : this.RenderingProtocol.BuildRemoteNullDevice(device, this.RenderingProtocol.LocalDeviceCallbackHandle);
        }

        internal unsafe DataBuffer BuildDataBuffer(byte[] pData, uint cbSize) => new DataBuffer(this.RenderingPort, pData, cbSize);

        internal void LoadSurface(DataBuffer buffer, Surface surface, ImageHeader header) => RemoteRasterizer.SendLoadRawImage(this.RenderingProtocol, surface.RemoteStub, buffer.RemoteStub, header);

        internal RemoteDx9EffectResource BuildRemoteDx9EffectResource(
          Dx9EffectResource effectResource,
          Dx9GraphicsDevice dx9Device,
          DataBuffer dataBuffer)
        {
            return this.RenderingProtocol.BuildRemoteDx9EffectResource(effectResource, dx9Device.RemoteDevice, dataBuffer.RemoteStub);
        }

        internal RemoteDx9Effect BuildRemoteDx9Effect(Dx9Effect effect) => RemoteDx9Effect.Create(this.RenderingProtocol, effect);

        internal RemoteGdiEffect BuildRemoteGdiEffect(GdiEffect effect) => RemoteGdiEffect.Create(this.NtRenderingProtocol, effect);

        internal RemoteCamera BuildRemoteCamera(Camera camera) => this.RenderingProtocol.BuildRemoteCamera(camera);

        internal RemoteVisualContainer BuildRemoteVisualContainer(Visual visual) => this.RenderingProtocol.BuildRemoteVisualContainer(visual);

        internal RemoteSprite BuildRemoteSprite(Sprite sprite, RenderWindowBase window)
        {
            switch (window.GraphicsDeviceType)
            {
                case GraphicsDeviceType.Gdi:
                    return this.NtRenderingProtocol.BuildRemoteGdiSprite(sprite);
                case GraphicsDeviceType.Direct3D9:
                case GraphicsDeviceType.XeDirectX9:
                    return this.RenderingProtocol.BuildRemoteDx9Sprite(sprite);
                default:
                    Debug2.Throw(false, "Unrecognized device type: {0}", window.GraphicsDeviceType);
                    return null;
            }
        }

        internal RemoteSoundBuffer BuildSoundBufferFromHandle(RENDERHANDLE handle) => RemoteSoundBuffer.CreateFromHandle(this.RenderingPort, handle);

        internal RemoteSound BuildSoundFromHandle(RENDERHANDLE handle) => RemoteSound.CreateFromHandle(this.RenderingPort, handle);

        public RemoteAnimationManager BuildRemoteAnimationManager(
          AnimationSystem owner)
        {
            Debug2.Validate(this.IsOwningThread(), typeof(InvalidOperationException), "Must be called on the session's owning client thread");
            return this.RenderingProtocol.BuildRemoteAnimationManager(owner);
        }

        public RemoteAnimation BuildRemoteAnimation(
          KeyframeAnimation owner,
          AnimationInputType animationType)
        {
            Debug2.Validate(this.IsOwningThread(), typeof(InvalidOperationException), "Must be called on the session's owning client thread");
            return this.RenderingProtocol.BuildRemoteAnimation(owner, animationType, this.RenderingProtocol.LocalAnimationCallbackHandle);
        }

        public RemoteExternalAnimationInput BuildRemoteExternalAnimationInput(
          ExternalAnimationInput owner,
          uint uniqueId)
        {
            Debug2.Validate(this.IsOwningThread(), typeof(InvalidOperationException), "Must be called on the session's owning client thread");
            return this.RenderingProtocol.BuildRemoteExternalAnimationInput(owner, uniqueId);
        }

        public RemoteAnimationInputProvider BuildRemoteAnimationInputProvider(
          AnimationInputProvider owner,
          uint externalAnimationInputId)
        {
            Debug2.Validate(this.IsOwningThread(), typeof(InvalidOperationException), "Must be called on the session's owning client thread");
            return this.RenderingProtocol.BuildRemoteAnimationInputProvider(owner, externalAnimationInputId);
        }

        protected override void Invariant()
        {
            this.AssertOwningThread();
            Debug2.Validate(this.m_owningSession != null, typeof(InvalidOperationException), this.GetType().ToString());
        }
    }
}
