/*
 * AnimationInfo.cs
 * Copyright (c) 2006 David Astle
 * 
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the
 * "Software"), to deal in the Software without restriction, including
 * without limitation the rights to use, copy, modify, merge, publish,
 * distribute, sublicense, and/or sell copies of the Software, and to
 * permit persons to whom the Software is furnished to do so, subject to
 * the following conditions:
 *
 * The above copyright notice and this permission notice shall be included
 * in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
 * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
 * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
 * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
 * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
 * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */

using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.Xna.Framework;
using System.Collections.ObjectModel;
using Microsoft.Xna.Framework.Graphics;

namespace Xclna.Xna.Animation
{
    /// <summary>
    /// A collection of BoneKeyFrames that represents an animation track.
    /// </summary>
    public class BoneKeyframeCollection : ReadOnlyCollection<BoneKeyframe>
    {
        #region Member Variables
        // The name of the bone represented by this animation track
        private string boneName;
        // Duration of the track
        private long duration;
        #endregion

        #region Constructors
        // Only allow creation from inside the library (only in AnimationReader)
        internal BoneKeyframeCollection(string boneName,
            IList<BoneKeyframe> list) : base(list)
        {
            this.boneName = boneName;
            duration = list[list.Count - 1].Time;
        }
        #endregion

        #region Properties
        /// <summary>
        /// Gets the duration of the animation track.
        /// </summary>
        public long Duration
        {
            get { return duration; }
        }

        /// <summary>
        /// Gets the name of the bone associated with the animation track.
        /// </summary>
        public string BoneName
        { get { return boneName; } }
        #endregion

        #region Methods
        /// <summary>
        /// Gets the index in the track at the given time.
        /// </summary>
        /// <param name="ticks">The time for which the index is found.</param>
        /// <returns>The index in the track at the given time.</returns>
        public int GetIndexByTime(long ticks)
        {
            // Since the animation is usually interpolated to 60 fps, this will
            // almost always be the index to return
            int firstFrameIndexToCheck = (int)(ticks / Util.TICKS_PER_60FPS);
            // Do out of bounds checking
            if (firstFrameIndexToCheck >= base.Count)
                firstFrameIndexToCheck = base.Count - 1;
            // Increment the index until the time at the next index is greater than the
            // specified time
            while (firstFrameIndexToCheck < base.Count - 1
                    && base[firstFrameIndexToCheck+1].Time < ticks)
            {
                ++firstFrameIndexToCheck;
            }
            // Decrement the index till the time at the index is not greater than the
            // specified time
            while (firstFrameIndexToCheck >= 0 && base[firstFrameIndexToCheck].Time >
                ticks)
            {
                --firstFrameIndexToCheck;
            }

            return firstFrameIndexToCheck;
        }

        #endregion
    }

    /// <summary>
    /// Represents a keyframe in an animation track.
    /// </summary>
    public struct BoneKeyframe
    {
        /// <summary>
        /// Creats a new BoneKeyframe.
        /// </summary>
        /// <param name="transform">The transform for the keyframe.</param>
        /// <param name="time">The time in ticks for the keyframe.</param>
        public BoneKeyframe(Matrix transform, long time)
        {
            this.Transform = transform;
            this.Time = time;
        }
        /// <summary>
        /// The transform for the keyframe.
        /// </summary>
        public readonly Matrix Transform;
        /// <summary>
        /// The time for the keyframe.
        /// </summary>
        public readonly long    Time;

    }

    /// <summary>
    /// A collection of animation channels or tracks, which are sections of an
    /// animation that run for one bone.
    /// </summary>
    public class AnimationChannelCollection : ReadOnlyCollection<BoneKeyframeCollection>
    {
        // Allow quick access to channels by BoneName
        private Dictionary<string, BoneKeyframeCollection> dict =
            new Dictionary<string, BoneKeyframeCollection>();

        // The bones affected by the tracks contained in this collection
        private ReadOnlyCollection<string> affectedBones;

        // This immutable data structure should not be created by the library user
        internal AnimationChannelCollection(IList<BoneKeyframeCollection> channels)
            : base(channels)
        {
            // Find the affected bones
            List<string> affected = new List<string>();
            foreach (BoneKeyframeCollection frames in channels)
            {
                dict.Add(frames.BoneName, frames);
                affected.Add(frames.BoneName);
            }
            affectedBones = new ReadOnlyCollection<string>(affected);

        }

        /// <summary>
        /// Gets the BoneKeyframeCollection that is associated with the given bone.
        /// </summary>
        /// <param name="boneName">The name of the bone that contains a track in this
        /// AnimationChannelCollection.</param>
        /// <returns>The track associated with the given bone.</returns>
        public BoneKeyframeCollection this[string boneName]
        {
            get { return dict[boneName]; }
        }

        // See AnimationInfo's equivalent method for documentation
        internal bool AffectsBone(string boneName)
        { return dict.ContainsKey(boneName); }

        // See AnimationInfo's equivalent method for documentation
        internal ReadOnlyCollection<string> AffectedBones
        {
            get { return affectedBones; }
        }
    }

    /// <summary>
    /// Contains information about an animation.
    /// </summary>
    public class AnimationInfo
    {
        private long duration = 0;
        private string animationName;

        // The bone animation tracks
        private AnimationChannelCollection boneAnimations;
        
        // Internal because it should only be created by the AnimationReader
        internal AnimationInfo(string animationName, AnimationChannelCollection 
            anims)
        {
            this.animationName = animationName;
            boneAnimations = anims;
            foreach (BoneKeyframeCollection channel in anims)
            {
                if (channel.Duration > duration)
                    duration = channel.Duration;
            }
        }

        /// <summary>
        /// Gets a collection of channels that represent the bone animation
        /// tracks for this animation.
        /// </summary>
        public AnimationChannelCollection AnimationChannels
        { get { return boneAnimations; } }


        /// <summary>
        /// Gets a collection of bones that have tracks in this animation.
        /// </summary>
        public ReadOnlyCollection<string> AffectedBones
        { get { return boneAnimations.AffectedBones; } }

        /// <summary>
        /// Gets the total duration of this animation in ticks.
        /// </summary>
        public long Duration
        {
            get { return duration; }
        }

        /// <summary>
        /// Gets the name of the animation.
        /// </summary>
        public string Name
        {
            get { return animationName; }
        }


        /// <summary>
        /// Returns true if the animation contains any tracks that affect the given
        /// bone.
        /// </summary>
        /// <param name="boneName">The bone to test for track information.</param>
        /// <returns>True if the animation contains any tracks that affect the given
        /// bone.</returns>
        public bool AffectsBone(string boneName)
        { return boneAnimations.AffectsBone(boneName); }
    }

    /// <summary>
    /// A collection of AnimationInfo objects.
    /// </summary>
    public class AnimationInfoCollection : SortedList<string, AnimationInfo>
    {
        // New instances should only be created by the AnimationReader
        internal AnimationInfoCollection()
        {
        }

        /// <summary>
        /// Gets a collection of animations stored in the model.
        /// </summary>
        /// <param name="model">The model that contains the animations.</param>
        /// <returns>The animations stored in the model.</returns>
        public static AnimationInfoCollection FromModel(Model model)
        {
            // Grab the tag that was set in the processor; this is a dictionary so that users can extend
            // the processor and pass their own data into the program without messing up the animation data
            Dictionary<string, object> modelTagData = (Dictionary<string, object>)model.Tag;
            if (modelTagData == null || !modelTagData.ContainsKey("Animations"))
            {
                return new AnimationInfoCollection();
            }
            else
            {
                AnimationInfoCollection animations = (AnimationInfoCollection)modelTagData["Animations"];
                return animations;
            }
        }

        /// <summary>
        /// Gets the AnimationInfo object at the given index.
        /// </summary>
        /// <param name="index">The index of the AnimationInfo object.</param>
        /// <returns>The AnimationInfo object at the given index.</returns>
        public AnimationInfo this[int index]
        {
            get
            {
                return this.Values[index];
            }
        }

    }




}