﻿using System;
using System.Collections.Generic;
using Oni.Akira;
using Oni.Motoko;

namespace Oni.Physics
{
    internal class ObjectDaeImporter
    {
        private readonly TextureImporter3 textureImporter;
        private readonly Dictionary<string, AkiraDaeNodeProperties> properties;
        private readonly List<ObjectNode> nodes = new List<ObjectNode>();

        public ObjectDaeImporter(TextureImporter3 textureImporter, Dictionary<string, AkiraDaeNodeProperties> properties)
        {
            this.textureImporter = textureImporter;
            this.properties = properties;
        }

        public List<ObjectNode> Nodes
        {
            get { return nodes; }
        }

        public void Import(Dae.Scene scene)
        {
            ImportNode(scene, null, GetNodeProperties(scene));
        }

        private void ImportNode(Dae.Node node, List<ObjectAnimationKey> parentAnimation, ObjectDaeNodeProperties parentNodeProperties)
        {
            Console.WriteLine("\t{0}", node.Id);

            var animation = ImportNodeAnimation(node, parentAnimation);
            var nodeProperties = GetNodeProperties(node);

            if (nodeProperties == null && parentNodeProperties != null)
            {
                nodeProperties = new ObjectDaeNodeProperties
                {
                    HasPhysics = parentNodeProperties.HasPhysics,
                    ScriptId = parentNodeProperties.ScriptId,
                    ObjectFlags = parentNodeProperties.ObjectFlags
                };

                //
                // We can't use the same anim name when we inherit the properties of the parent,
                // generate a name by appending "_anim" to the node name.
                //

                nodeProperties.Animations.AddRange(
                    from a in parentNodeProperties.Animations
                    select new ObjectAnimationClip
                    {
                        Name = node.Name + "_anim",
                        Start = a.Start,
                        Stop = a.Stop,
                        End = a.End,
                        Flags = a.Flags
                    });
            }

            if (nodeProperties != null && nodeProperties.HasPhysics)
            {
                var geometries = GeometryDaeReader.Read(node, textureImporter).ToList();

                if (animation.Count > 0 || geometries.Count > 0)
                {
                    nodes.Add(new ObjectNode(from g in geometries select new ObjectGeometry(g))
                    {
                        Name = node.Name,
                        FileName = node.FileName,
                        Animations = CreateAnimations(animation, nodeProperties),
                        ScriptId = nodeProperties.ScriptId,
                        Flags = nodeProperties.ObjectFlags
                    });
                }
            }

            foreach (var child in node.Nodes)
            {
                ImportNode(child, animation, nodeProperties);
            }
        }

        private List<ObjectAnimationKey> ImportNodeAnimation(Dae.Node node, List<ObjectAnimationKey> parentAnimation)
        {
            var scale = Vector3.One;
            var scaleTransform = node.Transforms.OfType<Dae.TransformScale>().FirstOrDefault();

            if (scaleTransform != null)
            {
                scale.X = scaleTransform.Values[0];
                scale.Y = scaleTransform.Values[1];
                scale.Z = scaleTransform.Values[2];
            }

            if (parentAnimation != null && parentAnimation.Count > 0)
            {
                scale *= parentAnimation[0].Scale;
            }

            var rotateTransforms = new List<Dae.TransformRotate>();
            var angles = new List<float[]>();

            foreach (var rotate in node.Transforms.OfType<Dae.TransformRotate>())
            {
                rotateTransforms.Add(rotate);

                var angleAnimation = rotate.AngleAnimation;

                if (angleAnimation != null)
                    angles.Add(angleAnimation.Sample());
                else
                    angles.Add(new[] { rotate.Angle });
            }

            var translateTransforms = node.Transforms.OfType<Dae.TransformTranslate>().FirstOrDefault();
            var positions = new List<float[]>();

            if (translateTransforms != null)
            {
                for (int i = 0; i < 3; i++)
                {
                    var positionAnimation = translateTransforms.Animations[i];

                    if (positionAnimation != null)
                        positions.Add(positionAnimation.Sample());
                    else
                        positions.Add(new[] { translateTransforms.Translation[i] });
                }
            }

            var frames = new List<ObjectAnimationKey>();
            int frameCount = Math.Max(angles.Max(a => a.Length), positions.Max(a => a.Length));

            for (int time = 0; time < frameCount; time++)
            {
                var rotation = Quaternion.Identity;

                for (int i = 0; i < rotateTransforms.Count; i++)
                {
                    float angle;

                    float[] values = angles[i];

                    if (time >= values.Length)
                        angle = values.Last();
                    else
                        angle = values[time];

                    rotation *= Quaternion.CreateFromAxisAngle(rotateTransforms[i].Axis, MathHelper.ToRadians(angle));
                }

                var translation = Vector3.Zero;

                if (translateTransforms != null)
                {
                    translation.X = positions[0][MathHelper.Clamp(time, 0, positions[0].Length - 1)];
                    translation.Y = positions[1][MathHelper.Clamp(time, 0, positions[1].Length - 1)];
                    translation.Z = positions[2][MathHelper.Clamp(time, 0, positions[2].Length - 1)];
                }

                if (parentAnimation != null)
                {
                    var parentFrame = time < parentAnimation.Count ? parentAnimation[time] : parentAnimation.LastOrDefault();

                    if (parentFrame != null)
                    {
                        rotation = parentFrame.Rotation * rotation;
                        translation = parentFrame.Translation + Vector3.Transform(translation * parentFrame.Scale, parentFrame.Rotation);
                    }
                }

                frames.Add(new ObjectAnimationKey
                {
                    Time = time,
                    Scale = scale,
                    Rotation = rotation,
                    Translation = translation
                });
            }

            return frames;
        }

        private ObjectAnimation[] CreateAnimations(List<ObjectAnimationKey> allFrames, ObjectDaeNodeProperties props)
        {
            var anims = new List<ObjectAnimation>();

            foreach (var clip in props.Animations)
            {
                int start = clip.Start;
                int end = clip.End != int.MaxValue ? clip.End : allFrames.Last().Time;

                var clipFrames = (from f in allFrames
                                  where start <= f.Time && f.Time <= end
                                  select new ObjectAnimationKey
                                  {
                                      Time = f.Time - start,
                                      Scale = f.Scale,
                                      Rotation = f.Rotation,
                                      Translation = f.Translation
                                  }).ToArray();

                if (clipFrames.Length == 0)
                    continue;

                anims.Add(new ObjectAnimation
                {
                    Name = clip.Name,
                    Flags = clip.Flags,
                    Stop = clip.Stop,
                    Length = end - start + 1,
                    Keys = clipFrames
                });
            }

            return anims.ToArray();
        }

        private ObjectDaeNodeProperties GetNodeProperties(Dae.Node node)
        {
            AkiraDaeNodeProperties nodeProperties;

            if (properties == null || !properties.TryGetValue(node.Id, out nodeProperties))
                return null;

            return nodeProperties as ObjectDaeNodeProperties;
        }
    }
}
