﻿using System;
using System.Collections.Generic;

namespace Oni.Totoro
{
    internal class AnimationDaeReader
    {
        private Animation animation;
        private Dae.Scene scene;
        private int startFrame;
        private int endFrame;
        private Body body;
        private int frameCount;

        public void Read(Animation targetAnimation)
        {
            animation = targetAnimation;

            body = BodyDaeReader.Read(scene);

            ComputeFrameCount();
            ImportTranslation();
            ImportRotations();
            animation.ComputeExtents(body);
        }

        public Dae.Scene Scene
        {
            get { return scene; }
            set { scene = value; }
        }

        public int StartFrame
        {
            get { return startFrame; }
            set { startFrame = value; }
        }

        public int EndFrame
        {
            get { return endFrame; }
            set { endFrame = value; }
        }

        private void ComputeFrameCount()
        {
            float maxTime = float.MinValue;

            var inputs = body.Nodes
                .SelectMany(n => n.DaeNode.Transforms).Where(t => t.HasAnimations)
                .SelectMany(t => t.Animations).Where(a => a != null)
                .SelectMany(a => a.Inputs).Where(i => i.Semantic == Dae.Semantic.Input);

            foreach (var input in inputs)
                maxTime = Math.Max(maxTime, input.Source.FloatData.Max());

            float maxFrameF = maxTime * 60.0f;
            int maxFrame;

            if (maxFrameF - Math.Round(maxFrameF) < 0.0005)
                maxFrame = FMath.RoundToInt32(maxFrameF);
            else
                maxFrame = FMath.TruncateToInt32(maxFrameF);

            //Console.Error.WriteLine("Info: The last keyframe time is {0}s. The animation length is {1} (at 60fps).",
            //    maxTime, maxFrame + 1);

            if (endFrame == 0)
            {
                endFrame = maxFrame;
            }
            else if (endFrame > maxFrame)
            {
                Console.Error.WriteLine("Warning: the specified animation end frame ({0}) is beyond the last key frame ({1}), using the last frame instead", endFrame, maxFrame);
                endFrame = maxFrame;
            }

            if (startFrame >= maxFrame)
            {
                Console.Error.WriteLine("Warning: the specified animation start frame ({0}) is beyond the last key frame ({1}), using 0 instead", startFrame, maxFrame);
                startFrame = 0;
            }

            frameCount = endFrame - startFrame;
        }

        private void ImportTranslation()
        {
            var rootNode = body.Nodes[0].DaeNode;
            var translate = rootNode.Transforms[0] as Dae.TransformTranslate;

            if (translate == null)
            {
                animation.Heights.AddRange(Enumerable.Repeat(0.0f, frameCount));
                animation.Velocities.AddRange(Enumerable.Repeat(Vector2.Zero, frameCount));
            }
            else
            {
                animation.Heights.AddRange(Sample(translate, 1, endFrame - 1));

                var x = Sample(translate, 0, endFrame);
                var z = Sample(translate, 2, endFrame);

                for (int i = 1; i < x.Length; i++)
                    animation.Velocities.Add(new Vector2(x[i] - x[i - 1], z[i] - z[i - 1]));
            }
        }

        private void ImportRotations()
        {
            animation.FrameSize = 16;

            foreach (var node in body.Nodes.Select(n => n.DaeNode))
            {
                var keys = new List<KeyFrame>();
                animation.Rotations.Add(keys);

                var rotations = new List<Dae.TransformRotate>();
                var angles = new List<float[]>();

                foreach (var transform in node.Transforms)
                {
                    var rotate = transform as Dae.TransformRotate;

                    if (rotate != null)
                    {
                        rotations.Add(rotate);
                        angles.Add(Sample(rotate, 3, endFrame - 1));
                    }
                }

                for (int i = 0; i < frameCount; i++)
                {
                    var q = Quaternion.Identity;

                    for (int j = 0; j < rotations.Count; j++)
                        q *= Quaternion.CreateFromAxisAngle(rotations[j].Axis, MathHelper.ToRadians(angles[j][i]));

                    keys.Add(new KeyFrame {
                        Duration = 1,
                        Rotation = q.ToVector4()
                    });
                }
            }
        }

        private float[] Sample(Dae.Transform transform, int index, int endFrame)
        {
            Dae.Sampler sampler = null;

            if (transform.HasAnimations)
                sampler = transform.Animations[index];

            if (sampler == null)
            {
                var value = transform.Values[index];
                var values = new float[endFrame - startFrame + 1];

                for (int i = 0; i < values.Length; i++)
                    values[i] = value;

                return values;
            }

            return sampler.Sample(startFrame, endFrame);
        }
    }
}
