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

namespace Framefield.Core.ID17298b2c_de5a_4c3d_bcb1_388bec9da8be
{
    public class Class_Camera : OperatorPart.Function, Framefield.Core.OperatorPartTraits.ICameraProvider
    {
        //>>> _inputids
        private enum InputId
        {
            SceneInput = 0,
            PositionX = 1,
            PositionY = 2,
            PositionZ = 3,
            TargetX = 4,
            TargetY = 5,
            TargetZ = 6,
            UpX = 7,
            UpY = 8,
            UpZ = 9,
            AspectRatio = 10,
            ClipNear = 11,
            ClipFar = 12,
            FOV = 13,
            Roll = 14,
            PositionOffsetX = 15,
            PositionOffsetY = 16,
            PositionOffsetZ = 17,
            RotateOffsetX = 18,
            RotateOffsetY = 19,
            RotateOffsetZ = 20
        }
        //<<< _inputids
        //>>> _outputids
        private enum OutputId
        {
            Output = 0,
            This = 1
        }
        //<<< _outputids

        public override OperatorPartContext Eval(OperatorPartContext context, List<OperatorPart> inputs, int outputIdx)
        {
            //>>> _params
            var SceneInput = inputs[(int)InputId.SceneInput];
            var PositionX = inputs[(int)InputId.PositionX].Eval(context).Value;
            var PositionY = inputs[(int)InputId.PositionY].Eval(context).Value;
            var PositionZ = inputs[(int)InputId.PositionZ].Eval(context).Value;
            var Position = new Vector3(PositionX, PositionY, PositionZ);
            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 PositionOffsetX = inputs[(int)InputId.PositionOffsetX].Eval(context).Value;
            var PositionOffsetY = inputs[(int)InputId.PositionOffsetY].Eval(context).Value;
            var PositionOffsetZ = inputs[(int)InputId.PositionOffsetZ].Eval(context).Value;
            var PositionOffset = new Vector3(PositionOffsetX, PositionOffsetY, PositionOffsetZ);
            var RotateOffsetX = inputs[(int)InputId.RotateOffsetX].Eval(context).Value;
            var RotateOffsetY = inputs[(int)InputId.RotateOffsetY].Eval(context).Value;
            var RotateOffsetZ = inputs[(int)InputId.RotateOffsetZ].Eval(context).Value;
            var RotateOffset = new Vector3(RotateOffsetX, RotateOffsetY, RotateOffsetZ);
            //<<< _params

            _fov = FOV;
            FOV = (float)Utilities.DegreeToRad(FOV);

            if (AspectRatio < 0) {
                if (!context.Variables.TryGetValue("AspectRatio", out AspectRatio)) {
                    AspectRatio = (float)context.Viewport.Width / context.Viewport.Height;
                }
            }

            _roll = (float)Utilities.DegreeToRad(Roll);
            _eye = Position;
            _target = Target;

            _worldToCamera = CalcWorldToCamera(_eye, _target, (float)_roll, PositionOffset, RotateOffset);

            var prevTransform = context.WorldToCamera;
            context.WorldToCamera = _worldToCamera;

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

            switch (outputIdx)
            {
                case (int)OutputId.Output:
                    SceneInput.Eval(context);
                    break;
                case (int)OutputId.This:
                    dynamic obj = new ExpandoObject();
                    obj.This = this;
                    context.Dynamic = obj;
                    break;
            }

            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[(int)InputId.PositionX], time, pos.X);
            Animation.SetOperatorPartValue(OperatorPart.Parent.Inputs[(int)InputId.PositionY], time, pos.Y);
            Animation.SetOperatorPartValue(OperatorPart.Parent.Inputs[(int)InputId.PositionZ], time, pos.Z);
        }

        public Vector3 GetLastTarget() {
            return _target;
        }
        public void SetTarget(double time, Vector3 target) {
            Animation.SetOperatorPartValue(OperatorPart.Parent.Inputs[(int)InputId.TargetX], time, target.X);
            Animation.SetOperatorPartValue(OperatorPart.Parent.Inputs[(int)InputId.TargetY], time, target.Y);
            Animation.SetOperatorPartValue(OperatorPart.Parent.Inputs[(int)InputId.TargetZ], time, target.Z);
        }

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

        public double GetLastFOV()
        {
            return _fov;
        }

        public double CalculateFOV(OperatorPartContext context) {
            var invalidator = new OperatorPart.InvalidateTimeAccessors();
            OperatorPart.Connections[13].TraverseWithFunction(invalidator, null);
            var fov = OperatorPart.Connections[13].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[(int)InputId.PositionX].Eval(context).Value;
            var y = OperatorPart.Connections[(int)InputId.PositionY].Eval(context).Value;
            var z = OperatorPart.Connections[(int)InputId.PositionZ].Eval(context).Value;
            var position = new Vector3(x, y, z);

            x = OperatorPart.Connections[(int)InputId.PositionOffsetX].Eval(context).Value;
            y = OperatorPart.Connections[(int)InputId.PositionOffsetY].Eval(context).Value;
            z = OperatorPart.Connections[(int)InputId.PositionOffsetZ].Eval(context).Value;
            var positionOffset = new Vector3(x, y, z);

            x = OperatorPart.Connections[(int)InputId.TargetX].Eval(context).Value;
            y = OperatorPart.Connections[(int)InputId.TargetY].Eval(context).Value;
            z = OperatorPart.Connections[(int)InputId.TargetZ].Eval(context).Value;
            var target = new Vector3(x, y, z);

            x = OperatorPart.Connections[(int)InputId.RotateOffsetX].Eval(context).Value;
            y = OperatorPart.Connections[(int)InputId.RotateOffsetY].Eval(context).Value;
            z = OperatorPart.Connections[(int)InputId.RotateOffsetZ].Eval(context).Value;
            var rotateOffset = new Vector3(x, y, z);

            var roll = (float)Utilities.DegreeToRad(OperatorPart.Connections[14].Eval(context).Value);

            return CalcWorldToCamera(position, target, roll, positionOffset, rotateOffset);
        }

        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 = (float)Utilities.DegreeToRad(OperatorPart.Connections[13].Eval(context).Value);

            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);
        }

        Matrix CalcWorldToCamera(Vector3 position, Vector3 target, float roll, Vector3 positionOffset, Vector3 rotateOffset)
        {
            var worldToCamera = Matrix.LookAtLH(position, target, new Vector3(0, 1, 0));
            var rollRotation = Matrix.RotationAxis(new Vector3(0, 0, 1), -(float)roll);
            var additionalTranslation = Matrix.Translation(positionOffset);
            var additionalRotation = Matrix.RotationYawPitchRoll((float)Utilities.DegreeToRad(rotateOffset.Y),
                                                                 (float)Utilities.DegreeToRad(rotateOffset.X),
                                                                 (float)Utilities.DegreeToRad(rotateOffset.Z));

            return worldToCamera*rollRotation*additionalTranslation*additionalRotation;
        }

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

