//--------------------------------------
//               PowerUI
//
//        For documentation or 
//    if you have any issues, visit
//        powerUI.kulestar.com
//
//    Copyright  2013 Kulestar Ltd
//          www.kulestar.com
//--------------------------------------

using System;
using System.IO;
using UnityEngine;
using System.Collections;
using System.Collections.Generic;

namespace Spa{
	
	/// <summary>
	/// SPA (SPrite Animation) is a custom file format which holds
	/// optimized sprite animations and also supports alpha. As it's a custom
	/// format, we also have a freely available tool on the website which will
	/// encode a series of images into an .SPA file.
	/// Important Note: You *must* add .bytes onto the end of any .spa file in your
	/// Resources folder, otherwise it won't work! (e.g. countdown.spa.bytes).
	/// </summary>
	
	public partial class SPA{
		
		/// <summary>The tail of the linked list of actively playing animations.</summary>
		public static SPAInstance LastInstance;
		/// <summary>The head of the linked list of actively playing animations.</summary>
		public static SPAInstance FirstInstance;
		/// <summary>A cache of all loaded SPA animations. If the garbage truck is active, it clears this.</summary>
		private static Dictionary<string,SPA> Instances=new Dictionary<string,SPA>();
		
		/// <summary>Clears all active SPA animations.</summary>
		public static void Clear(){
			LastInstance=FirstInstance=null;
		}
		
		/// <summary>Gets an already loaded SPA from the cache.</summary>
		/// <returns>An SPA object if found; null otherwise.</returns>
		public static SPA Get(string name){
			SPA result=null;
			Instances.TryGetValue(name,out result);
			return result;
		}
		
		/// <summary>Gets how many animations are actively playing.</summary>
		/// <returns>The number of animations currently playing.</returns>
		public static int ActiveCount(){
			int count=0;
			SPAInstance current=FirstInstance;
			while(current!=null){
				count++;
				current=current.InstanceAfter;
			}
			return count;
		}
		
		/// <summary>Advances playback of any running animations and performs some garbage collection.
		/// Called by <see cref="UI.Update"/>.</summary>
		public static void Update(float deltaTime){
			// Advance any active animations.
			
			// These are all animations in our Instance chain.
			if(FirstInstance!=null){
				SPAInstance current=FirstInstance;
				
				while(current!=null){
					current.Update(deltaTime);
					current=current.InstanceAfter;
				}
			}
			
		}
		
		/// <summary>The frame rate in frames/second.</summary>
		public int FrameRate;
		/// <summary>The width of a frame.</summary>
		public int FrameWidth;
		/// <summary>The height of a frame.</summary>
		public int FrameHeight;
		/// <summary>The number of frames in this animation.</summary>
		public uint FrameCount;
		
		/// <summary>The set of sprites (images) in this animation. Note that a sprite can hold more than 1 frame.</summary>
		public SPASprite[] Sprites;
		/// <summary>Font meta, if this is being used as a font.</summary>
		public SPAFontMeta FontMeta;
		/// <summary>A map which essentially describes the location of invididual images on each frame.
		/// Used mainly when SPA's are utilised as fonts.</summary>
		public Dictionary<int,SPAMapEntry> Map;
		
		/// <summary>Creates a new empty SPA animation.</summary>
		/// <param name="name">The name of the animation. Used for accessing the SPA from e.g. HTML.</param>
		/// <param name="spriteCount">The number of sprites in this animation.</param>
		/// <param name="frameRate">The framerate in fps.</param>
		public SPA(int frameWidth,int frameHeight,int frameRate):this(null,frameWidth,frameHeight,frameRate){}
		
		/// <summary>Creates a new empty SPA animation.</summary>
		/// <param name="name">The name of the animation. Used for accessing the SPA from e.g. HTML.</param>
		/// <param name="spriteCount">The number of sprites in this animation.</param>
		/// <param name="frameRate">The framerate in fps.</param>
		public SPA(string name,int frameWidth,int frameHeight,int frameRate){
			
			if(name!=null){
				Instances[name]=this;
			}
			
			FrameWidth=frameWidth;
			FrameHeight=frameHeight;
			FrameRate=frameRate;
		}
		
		/// <summary>Changes the framerate of this animation.</summary>
		public void ChangeFrameRate(int newRate){
			FrameRate=newRate;
			
			// For each actively running animation..
			if(FirstInstance!=null){
				// What's the delay in seconds of a frame?
				float newDelay=1f/(float)FrameRate;
			
				// For each..
				SPAInstance current=FirstInstance;
				
				while(current!=null){
					
					if(current.Animation==this){
						current.FrameDelay=newDelay;
					}
					
					// Hop to the next one:
					current=current.InstanceAfter;
				}
				
			}
			
		}
		
		/// <summary>Creates space for the given number of sprites which hold the given total number of frames.</summary>
		/// <param name="spriteCount">The number of sprites.</param>
		/// <param name="frameCount">The total number of frames held by the sprites.</param>
		public void CreateSprites(int spriteCount,uint frameCount){
			// Apply the frame count:
			FrameCount=frameCount;
			
			// Setup the sprite array:
			Sprites=new SPASprite[spriteCount];
			
			// Create each one:
			for(int i=0;i<spriteCount;i++){
				Sprites[i]=new SPASprite(this,i);
			}
		}
		
		/// <summary>Creates a new SPA animation with the given name from the given binary data.</summary>
		/// <param name="name">The name of the animation. Used for caching purposes so the binary doesn't
		/// have to be reloaded if the animation is displayed multiple times.</param>
		///	<param name="binaryData">The raw binary data of the spa file.</param>	
		public SPA(string name,byte[] binaryData){
			Instances[name]=this;
			
			SPAReader br=new SPAReader(new MemoryStream(binaryData));
			
			if(br.ReadChar()!='S' || br.ReadChar()!='P' || br.ReadChar()!='A'){
				throw new Exception("This is not an SPA file.");
			}
			
			byte version=br.ReadByte();
			
			if(version!=2 && version!=3){
				throw new Exception("This reader supports SPA versions 2 and 3. The file you have given is version "+version+".");
			}
			
			int spriteFrames;
			
			if(version==2){
				
				// FR:
				FrameRate=br.ReadByte();
				
				// Total frame count:
				FrameCount=br.ReadUInt32();
				
				// Frame width:
				FrameWidth=br.ReadUInt16();
				
				// Frame height:
				FrameHeight=br.ReadUInt16();
				
				// Sprite frame count:
				spriteFrames=br.ReadInt32();
				
			}else{
				
				// FR:
				FrameRate=(int)br.ReadCompressed();
				
				// Total frame count:
				FrameCount=(uint)br.ReadCompressed();
				
				// Frame width:
				FrameWidth=br.ReadUInt16();
				
				// Frame height:
				FrameHeight=br.ReadUInt16();
				
				// Sprite frame count:
				spriteFrames=(int)br.ReadCompressed();
				
			}
			
			Sprites=new SPASprite[spriteFrames];
			
			// Next, read each of the sprite frames:
			for(int i=0;i<spriteFrames;i++){
				
				Sprites[i]=new SPASprite(this,br,i);
				
			}
			
		}
		
		/// <summary>True if this SPA has a map.</summary>
		public bool HasMap{
			get{
				return (Map!=null);
			}
		}
		
		/// <summary>Gets a character from this SPA, if it exists.</summary>
		public SPACharacter GetCharacter(int charcode){
			
			if(Map==null){
				// No map!
				return null;
			}
			
			SPAMapEntry entry;
			Map.TryGetValue(charcode,out entry);
			return entry as SPACharacter;
			
		}
		
		/// <summary>Gets the entry from the map. Index is most often e.g. charcode.</summary>
		public SPAMapEntry GetFromMap(int index){
			
			if(Map==null){
				// No map!
				return null;
			}
			
			SPAMapEntry entry;
			Map.TryGetValue(index,out entry);
			return entry;
			
		}
		
		/// <summary>Adds the given map entry to the map.</summary>
		public void AddToMap(int index,SPAMapEntry entry){
			
			if(Map==null){
				
				// Create the map now:
				Map=new Dictionary<int,SPAMapEntry>();
				
			}
			
			// Add to map:
			Map[index]=entry;
			
		}
		
		/// <summary>Creates a playable instance of this animation.
		/// Instances are required as the same animation could be visible multiple times.</summary>
		/// <returns>A playable form of this animation.</returns>
		public SPAInstance GetInstance(){
			// Create the instance:
			SPAInstance instance=new SPAInstance(this);
			// Push the instance to the global animate queue.
			if(FirstInstance==null){
				FirstInstance=LastInstance=instance;
			}else{
				instance.InstanceBefore=LastInstance;
				LastInstance=LastInstance.InstanceAfter=instance;
			}
			return instance;
		}
		
		/// <summary>Gets a sprite with the given ID.</summary>
		public SPASprite GetSprite(int id){
			return Sprites[id];
		}
		
		/// <summary>Gets a sprite with the given ID.</summary>
		public SPASprite this[int id]{
			get{
				return Sprites[id];
			}
		}
		
	}
	
}