//>>> _using
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using SharpDX;
using SharpDX.Direct3D11;
using SharpDX.Windows;
//<<< _using
using SharpDX.DXGI;
using System.Diagnostics;

namespace Framefield.Core.IDc733aa45_cac1_4c19_afe4_f6cba4e632b9
{
    public class Class_Pick : OperatorPart.Function
    {
        //>>> _outputids
        private enum OutputId
        {
            WorldPositionX = 0,
            WorldPositionY = 1,
            WorldPositionZ = 2,
            ColorR = 3,
            ColorG = 4,
            ColorB = 5,
            ColorA = 6
        }
        //<<< _outputids
        //>>> _inputids
        private enum InputId
        {
            Image = 0,
            Camera = 1,
            ScreenPositionX = 2,
            ScreenPositionY = 3,
            DepthOffset = 4
        }
        //<<< _inputids

        public override void Dispose()
        {
            Utilities.DisposeObj(ref _imageWithCPUAccess);
            Utilities.DisposeObj(ref _depthImageWithCPUAccess);
        }

        public override OperatorPartContext Eval(OperatorPartContext context, List<OperatorPart> inputs, int outputIdx) 
        {
            //>>> __params
            var ScreenPositionX = inputs[(int)InputId.ScreenPositionX].Eval(context).Value;
            var ScreenPositionY = inputs[(int)InputId.ScreenPositionY].Eval(context).Value;
            var ScreenPosition = new Vector2(ScreenPositionX, ScreenPositionY);
            var DepthOffset = inputs[(int)InputId.DepthOffset].Eval(context).Value;
            var Camera = inputs[(int)InputId.Camera].Eval(context).Dynamic;
            //<<< __params

            if (Changed)
            {
                var resultContextOfImageInput = inputs[(int)InputId.Image].Eval(context);
                var image = resultContextOfImageInput.Image;
                var depthImage = resultContextOfImageInput.DepthImage;
                _depthOffset = DepthOffset;
                _lastEvaluationSucceeded = DoPicking(context, ScreenPosition, image, depthImage, Camera);
                if (_lastEvaluationSucceeded)
                    Changed = false;
            }

            context.Value = 0.0f;
            if (!_lastEvaluationSucceeded)
                return context;

            switch (outputIdx)
            {
                case (int)OutputId.WorldPositionX: context.Value = _lastEvaluatedWorldTPickPosition.X; break;
                case (int)OutputId.WorldPositionY: context.Value = _lastEvaluatedWorldTPickPosition.Y; break;
                case (int)OutputId.WorldPositionZ: context.Value = _lastEvaluatedWorldTPickPosition.Z; break;
                case (int)OutputId.ColorR: context.Value = _lastEvaluatedColor.X; break;
                case (int)OutputId.ColorG: context.Value = _lastEvaluatedColor.Y; break;
                case (int)OutputId.ColorB: context.Value = _lastEvaluatedColor.Z; break;
                case (int)OutputId.ColorA: context.Value = _lastEvaluatedColor.W; break;
            }            
            return context;
        }

        bool DoPicking(OperatorPartContext context, Vector2 screenPosition, Texture2D image, Texture2D depthImage, dynamic camera)
        {
            if (screenPosition.X < -1 || screenPosition.X > 1 ||
                screenPosition.Y < -1 || screenPosition.Y > 1)
                return false;

            if (image == null || depthImage == null || camera == null)
                return false;

            Framefield.Core.OperatorPartTraits.ICameraProvider camProvider = camera.This as Framefield.Core.OperatorPartTraits.ICameraProvider;
            if (camProvider == null)
                return false;

            var worldToCamera = camProvider.GetLastWorldToCamera();
            var cameraToView = camProvider.GetLastCameraToView();
            var viewToWorld = Matrix.Invert(worldToCamera*cameraToView);

            if (_imageWithCPUAccess == null ||
                _imageWithCPUAccess.Description.Format != image.Description.Format ||
                _imageWithCPUAccess.Description.Width != image.Description.Width ||
                _imageWithCPUAccess.Description.Height != image.Description.Height ||
                _imageWithCPUAccess.Description.MipLevels != image.Description.MipLevels ||
                _depthImageWithCPUAccess.Description.Format != depthImage.Description.Format ||
                _depthImageWithCPUAccess.Description.Width != depthImage.Description.Width ||
                _depthImageWithCPUAccess.Description.Height != depthImage.Description.Height ||
                _depthImageWithCPUAccess.Description.MipLevels != depthImage.Description.MipLevels)
            {    
                var imageDesc = new Texture2DDescription
                                    {
                                        BindFlags = BindFlags.None,
                                        Format = image.Description.Format,
                                        Width = image.Description.Width,
                                        Height = image.Description.Height,
                                        MipLevels = image.Description.MipLevels,
                                        SampleDescription = new SampleDescription(1, 0),
                                        Usage = ResourceUsage.Staging,
                                        OptionFlags = ResourceOptionFlags.None,
                                        CpuAccessFlags = CpuAccessFlags.Read,
                                        ArraySize = 1
                                    };
    
                var depthImageDesc = new Texture2DDescription
                                         {
                                             BindFlags = BindFlags.None,
                                             Format = depthImage.Description.Format,                                           
                                             Width = depthImage.Description.Width,
                                             Height = depthImage.Description.Height,
                                             MipLevels = depthImage.Description.MipLevels,
                                             SampleDescription = new SampleDescription(1, 0),
                                             Usage = ResourceUsage.Staging,
                                             OptionFlags = ResourceOptionFlags.None,
                                             CpuAccessFlags = CpuAccessFlags.Read,
                                             ArraySize = 1
                                         };

                Utilities.DisposeObj(ref _imageWithCPUAccess);
                Utilities.DisposeObj(ref _depthImageWithCPUAccess);
                _imageWithCPUAccess = new Texture2D(D3DDevice.Device, imageDesc);
                _depthImageWithCPUAccess = new Texture2D(D3DDevice.Device, depthImageDesc);
            }

            var immediateContext = D3DDevice.Device.ImmediateContext;
            immediateContext.CopyResource(image, _imageWithCPUAccess);
            immediateContext.CopyResource(depthImage, _depthImageWithCPUAccess);

            int x = (int)Utilities.Clamp(image.Description.Width*(screenPosition.X + 1.0f)*0.5f, 0, image.Description.Width - 1);
            int y = (int)Utilities.Clamp(image.Description.Height*(screenPosition.Y - 1.0f)*-0.5f, 0, image.Description.Height - 1);
            
            DataStream imageStream;
            DataBox dataBox = immediateContext.MapSubresource(_imageWithCPUAccess, 0, 0, MapMode.Read, SharpDX.Direct3D11.MapFlags.None, out imageStream);
            using (imageStream)
            {
                imageStream.Position = (long)y*dataBox.RowPitch + (long)x*4;
                var color = new Color(imageStream.Read<Int32>());
                _lastEvaluatedColor = new Vector4(color.R/255.0f, color.G/255.0f, color.B/255.0f, color.A/255.0f);
                immediateContext.UnmapSubresource(_imageWithCPUAccess, 0);
            }

            dataBox = immediateContext.MapSubresource(_depthImageWithCPUAccess, 0, 0, MapMode.Read, SharpDX.Direct3D11.MapFlags.None, out imageStream);
            using (imageStream)
            {
                imageStream.Position = (long)y*dataBox.RowPitch + (long)x*4;
                float depth = imageStream.Read<float>();
                Vector4 viewTPickPositionNormalized = new Vector4(screenPosition.X, screenPosition.Y, depth, 1.0f);
                _lastEvaluatedWorldTPickPosition = Vector4.Transform(viewTPickPositionNormalized, viewToWorld);
                var v2=_lastEvaluatedWorldTPickPosition;
                v2.Normalize();
                
                _lastEvaluatedWorldTPickPosition /= _lastEvaluatedWorldTPickPosition.W;
                _lastEvaluatedWorldTPickPosition += v2 * _depthOffset;
                immediateContext.UnmapSubresource(_depthImageWithCPUAccess, 0);
            }
            return true;
        }

        bool _lastEvaluationSucceeded;
        float _depthOffset;
        Texture2D _imageWithCPUAccess;
        Texture2D _depthImageWithCPUAccess;
        Vector4 _lastEvaluatedColor;
        Vector4 _lastEvaluatedWorldTPickPosition;
    }
}

