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

namespace Framefield.Core.ID0de36237_52cd_4c63_97d7_bc0513641089
{
    public class Class_TwoCamerasBlender : OperatorPart.Function, Framefield.Core.OperatorPartTraits.ICameraProvider
    {
        //>>> _inputids
        private enum InputId
        {
            SceneInput = 0,
            EyeX = 1,
            EyeY = 2,
            EyeZ = 3,
            TargetX = 4,
            TargetY = 5,
            TargetZ = 6,
            UpX = 7,
            UpY = 8,
            UpZ = 9,
            AspectRatio = 10,
            ClipNear = 11,
            ClipFar = 12,
            FOV = 13,
            Roll = 14,
            Pos2X = 15,
            Pos2Y = 16,
            Pos2Z = 17,
            Target2X = 18,
            Target2Y = 19,
            Target2Z = 20,
            Blend = 21
        }
        //<<< _inputids

        public override OperatorPartContext Eval(OperatorPartContext context, List<OperatorPart> inputs, int outputIdx)
        {
            //>>> _params
            var SceneInput = inputs[(int)InputId.SceneInput];
            var EyeX = inputs[(int)InputId.EyeX].Eval(context).Value;
            var EyeY = inputs[(int)InputId.EyeY].Eval(context).Value;
            var EyeZ = inputs[(int)InputId.EyeZ].Eval(context).Value;
            var Eye = new Vector3(EyeX, EyeY, EyeZ);
            var TargetX = inputs[(int)InputId.TargetX].Eval(context).Value;
            var TargetY = inputs[(int)InputId.TargetY].Eval(context).Value;
            var TargetZ = inputs[(int)InputId.TargetZ].Eval(context).Value;
            var Target = new Vector3(TargetX, TargetY, TargetZ);
            var UpX = inputs[(int)InputId.UpX].Eval(context).Value;
            var UpY = inputs[(int)InputId.UpY].Eval(context).Value;
            var UpZ = inputs[(int)InputId.UpZ].Eval(context).Value;
            var Up = new Vector3(UpX, UpY, UpZ);
            var AspectRatio = inputs[(int)InputId.AspectRatio].Eval(context).Value;
            var ClipNear = inputs[(int)InputId.ClipNear].Eval(context).Value;
            var ClipFar = inputs[(int)InputId.ClipFar].Eval(context).Value;
            var Clip = new Vector2(ClipNear, ClipFar);
            var FOV = inputs[(int)InputId.FOV].Eval(context).Value;
            var Roll = inputs[(int)InputId.Roll].Eval(context).Value;
            var Pos2X = inputs[(int)InputId.Pos2X].Eval(context).Value;
            var Pos2Y = inputs[(int)InputId.Pos2Y].Eval(context).Value;
            var Pos2Z = inputs[(int)InputId.Pos2Z].Eval(context).Value;
            var Pos2 = new Vector3(Pos2X, Pos2Y, Pos2Z);
            var Target2X = inputs[(int)InputId.Target2X].Eval(context).Value;
            var Target2Y = inputs[(int)InputId.Target2Y].Eval(context).Value;
            var Target2Z = inputs[(int)InputId.Target2Z].Eval(context).Value;
            var Target2 = new Vector3(Target2X, Target2Y, Target2Z);
            var Blend = inputs[(int)InputId.Blend].Eval(context).Value;
            //<<< _params

            _fov = FOV;
            FOV *= (float)Math.PI/180.0f;

            _roll= Roll * (float)Math.PI / 180.0f;
        
            if (AspectRatio < 0) {
                if (!context.Variables.TryGetValue("AspectRatio", out AspectRatio)) {
                    AspectRatio = (float)context.Viewport.Width / context.Viewport.Height;
                }
            }

            _eye= new Vector3(EyeX, EyeY, EyeZ);
            _target= new Vector3(TargetX, TargetY, TargetZ);

            var eyeB = new Vector3(
                            (1-Blend) * EyeX + Blend * Pos2X,
                            (1-Blend) * EyeY + Blend * Pos2Y,
                            (1-Blend) * EyeZ + Blend * Pos2Z
                           );
            var targetB = new Vector3(
                            (1-Blend) * TargetX + Blend * Target2X,
                            (1-Blend) * TargetY + Blend * Target2Y,
                            (1-Blend) * TargetZ + Blend * Target2Z
                           );
            
            
            var rolledUp = Vector3.Transform(new Vector3(0, 1, 0), Matrix.RotationAxis(targetB - eyeB, (float)_roll));


            _worldToCamera = Matrix.LookAtLH(eyeB, targetB, rolledUp.ToVector3());
            var prevTransform = context.WorldToCamera;
            context.WorldToCamera = _worldToCamera;

            _cameraToView = Matrix.PerspectiveFovLH(FOV, AspectRatio, ClipNear, ClipFar);
            var prevCamProj = context.CameraProjection;
            context.CameraProjection = _cameraToView;

            SceneInput.Eval(context);

            context.CameraProjection = prevCamProj;
            context.WorldToCamera = prevTransform;

            return context;
        }

        public Vector3 GetLastPosition() {
            return _eye;
        }
        public void SetPosition(double time, Vector3 pos) {
            Animation.SetOperatorPartValue(OperatorPart.Parent.Inputs[1], time, pos.X);
            Animation.SetOperatorPartValue(OperatorPart.Parent.Inputs[2], time, pos.Y);
            Animation.SetOperatorPartValue(OperatorPart.Parent.Inputs[3], time, pos.Z);
        }

        public Vector3 GetLastTarget() {
            return _target;
        }
        public void SetTarget(double time, Vector3 target) {
            Animation.SetOperatorPartValue(OperatorPart.Parent.Inputs[4], time, target.X);
            Animation.SetOperatorPartValue(OperatorPart.Parent.Inputs[5], time, target.Y);
            Animation.SetOperatorPartValue(OperatorPart.Parent.Inputs[6], time, target.Z);
        }

        public double GetLastRoll() {
            return _roll;
        }
        public void SetRoll(double time, double roll) {
            Animation.SetOperatorPartValue(OperatorPart.Parent.Inputs[(int)InputId.Roll], time, (float)roll);
        }

        public double GetLastFOV()
        {
            return _fov;
        }

        public double CalculateFOV(OperatorPartContext context)
        {
            var invalidator = new OperatorPart.InvalidateTimeAccessors();
            OperatorPart.Connections[(int)InputId.FOV].TraverseWithFunction(invalidator, null);
            var fov = OperatorPart.Connections[(int)InputId.FOV].Eval(context).Value ;
            return fov;
        }

        public Matrix GetLastWorldToCamera()
        {
            return _worldToCamera;
        }

        public Matrix CalculateWorldToCamera(OperatorPartContext context)
        {
            var invalidator = new OperatorPart.InvalidateTimeAccessors();
            OperatorPart.Connections[1].TraverseWithFunction(invalidator, null);
            OperatorPart.Connections[2].TraverseWithFunction(invalidator, null);
            OperatorPart.Connections[3].TraverseWithFunction(invalidator, null);
            OperatorPart.Connections[4].TraverseWithFunction(invalidator, null);
            OperatorPart.Connections[5].TraverseWithFunction(invalidator, null);
            OperatorPart.Connections[6].TraverseWithFunction(invalidator, null);
            OperatorPart.Connections[7].TraverseWithFunction(invalidator, null);
            OperatorPart.Connections[8].TraverseWithFunction(invalidator, null);
            OperatorPart.Connections[9].TraverseWithFunction(invalidator, null);
            OperatorPart.Connections[14].TraverseWithFunction(invalidator, null);

            var x = OperatorPart.Connections[1].Eval(context).Value;
            var y = OperatorPart.Connections[2].Eval(context).Value;
            var z = OperatorPart.Connections[3].Eval(context).Value;
            var position = new Vector3(x, y, z);

            x = OperatorPart.Connections[4].Eval(context).Value;
            y = OperatorPart.Connections[5].Eval(context).Value;
            z = OperatorPart.Connections[6].Eval(context).Value;
            var target = new Vector3(x, y, z);

            var up = new Vector3(0, 1, 0);

            var roll = OperatorPart.Connections[14].Eval(context).Value * (float)Math.PI / 180.0f;

            var rolledUp = Vector3.Transform(up, Matrix.RotationAxis(target - position, (float)roll));

            return Matrix.LookAtLH(position, target, rolledUp.ToVector3());
        }

        public Matrix GetLastCameraToView()
        {
            return _cameraToView;
        }

        public Matrix CalculateCameraToView(OperatorPartContext context)
        {
            var invalidator = new OperatorPart.InvalidateTimeAccessors();
            OperatorPart.Connections[10].TraverseWithFunction(invalidator, null);
            OperatorPart.Connections[11].TraverseWithFunction(invalidator, null);
            OperatorPart.Connections[12].TraverseWithFunction(invalidator, null);
            OperatorPart.Connections[13].TraverseWithFunction(invalidator, null);

            var aspectRatio = OperatorPart.Connections[10].Eval(context).Value;
            var clipNear = OperatorPart.Connections[11].Eval(context).Value;
            var clipFar = OperatorPart.Connections[12].Eval(context).Value;
                        var fov = OperatorPart.Connections[(int)InputId.FOV].Eval(context).Value / (float)Math.PI / 180.0f ;

            if (aspectRatio < 0) {
                if (!context.Variables.TryGetValue("AspectRatio", out aspectRatio)) {
                    aspectRatio = (float)context.Viewport.Width / context.Viewport.Height;
                }
            }
            return Matrix.PerspectiveFovLH(fov, aspectRatio, clipNear, clipFar);
        }

        private Vector3 _eye;
        private Vector3 _target;
        private double _roll;
        private double _fov;
        private Matrix _worldToCamera;
        private Matrix _cameraToView;
    }
}

