//--------------------------------------
//               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 PowerUI;


namespace Spa{

	/// <summary>
	/// Represents a single sprite in an SPA animation.
	/// Note that a single sprite isn't always a single frame; one sprite can
	/// contain multiple frames itself. The frames are stored on the sprite from top to bottom,
	/// left to right. I.e. The first frame is in the top left corner; the next is below it.
	/// If there is no more height left, the next frame is at the top again
	/// but one column further right.
	/// </summary>

	public class SPASprite{
		
		/// <summary>Which number sprite this is in the spa. Starts from 0.</summary>
		public int ID;
		/// <summary>The width of the sprite.</summary>
		public int Width;
		/// <summary>The height of the sprite.</summary>
		public int Height;
		/// <summary>The delay to apply for this particular sprite.</summary>
		public float Delay;
		/// <summary>The animation this sprite belongs to.</summary>
		public SPA Animation;
		/// <summary>How many frames this sprite holds.</summary>
		public int FrameCount;
		/// <summary>The sprite graphic itself.</summary>
		public Texture2D Sprite;
		/// <summary>The scale of the sprite that should be used when the sprite is applied to a material.</summary>
		public Vector2 TextureScale;
		/// <summary>How many frames are down the height of the image.Sprite height/Frame height.</summary>
		public int VerticalFrameCount;
		/// <summary>An already created texture atlas.</summary>
		private Blaze.TextureAtlas LoadedAtlas;
		/// <summary>Gets this sprite as a texture atlas.</summary>
		public Blaze.TextureAtlas Atlas{
			get{
				
				if(LoadedAtlas==null){
					
					// Create one now:
					LoadedAtlas=new Blaze.TextureAtlas(Sprite);
					
				}
				
				return LoadedAtlas;
			}
		}
		
		
		/// <summary>Creates an empty sprite.</summary>
		public SPASprite(SPA animation,int id){
			Animation=animation;
			ID=id;
		}
		
		/// <summary>Loads a new sprite from the given binary stream.</summary>
		/// <param name="animation">The animation this sprite belongs to.</param>
		/// <param name="reader">The binary stream that contains this sprites data.</param>
		/// <param name="id">The ID of the sprite in the animation.</param>
		public SPASprite(SPA animation,SPAReader reader,int id){
			ID=id;
			Animation=animation;
			
			// Read some bit flags - these give information about this frame:
			byte flags=reader.ReadByte();
			
			// Does it also have an alpha frame? 
			// If it does, there are two images in this frame (alpha one second).
			bool hasAlphaFrame=((flags&1)==1);
			
			// Does it have a map?
			// That says where objects are on this sprite.
			bool hasMap=((flags&2)==2);
			
			// How many frames this sprite holds:
			FrameCount=reader.ReadUInt16();
			
			// How big is the image, in bytes:
			int dataSize=reader.ReadInt32();
			
			// Setup the sprite now:
			Sprite=new Texture2D(0,0);
			
			// And load the image data:
			Sprite.LoadImage(reader.ReadBytes(dataSize));
			Width=Sprite.width;
			Height=Sprite.height;
			
			// Make sure it filters correctly.
			// This is so we don't see parts of other frames around the edge of the image onscreen:
			Sprite.filterMode=FilterMode.Point;
			
			// Setup the scale:
			TextureScale=new Vector2((float)Animation.FrameWidth/(float)Width,(float)Animation.FrameHeight/(float)Height);
			VerticalFrameCount=Height/Animation.FrameHeight;
			
			if(hasAlphaFrame){
				int alphaImageSize=reader.ReadInt32();
				
				// Setup the temporary alpha texture:
				Texture2D alphaImage=new Texture2D(0,0);
				
				// And load it's data:
				alphaImage.LoadImage(reader.ReadBytes(alphaImageSize));
				
				// Next, merge the alpha pixels into our main sprite.
				Color[] spritePixels=Sprite.GetPixels();
				Color[] alphaPixels=alphaImage.GetPixels();
				
				if(spritePixels.Length!=alphaPixels.Length){
					throw new Exception("Invalid SPA alpha channel image.");
				}
				
				// Set each alpha value from the grayscale of the alpha image:
				for(int i=spritePixels.Length-1;i>=0;i--){
					Color pixel=spritePixels[i];
					pixel.a=alphaPixels[i].grayscale;
					spritePixels[i]=pixel;
				}
				
				// Write the pixels back:
				Sprite.SetPixels(spritePixels);
			}
			
			// Has it got a map?
			if(hasMap){
				
				// Reset the readers X/Y position:
				reader.ResetCoordinates();
				
				// Yep! Read map flags:
				byte mapFlags=reader.ReadByte();
				
				// Acting as a font?
				bool isFont=((mapFlags&1)==1);
				
				// How many entries:
				int count=(int)reader.ReadCompressed();
				
				// For each map entry..
				for(int i=0;i<count;i++){
					
					SPAMapEntry entry;
					
					if(isFont){
						
						// It's a font entry:
						entry=new SPACharacter(this,reader);
						
					}else{
						
						// It's a "normal" mapping:
						entry=new SPAMapEntry(this,reader);
						
					}
					
					// Add the mapping to the SPA:
					animation.AddToMap(entry.ID,entry);
					
				}
				
				// If it's acting as a font, we've also got things like kerning info.
				if(isFont){
					
					// Got kerning?
					bool hasKerning=((mapFlags&2)==2);
					
					// Got additional charcodes?
					bool additionalCharcodes=((mapFlags&4)==4);
					
					// Font meta?
					bool fontMeta=((mapFlags&8)==8);
					
					if(hasKerning){
						
						// Get the # of kerning pairs:
						int kernCount=(int)reader.ReadCompressed();
						
						// Previous char - stored relative for better compression.
						int previousChar=0;
						
						// For each one..
						for(int i=0;i<kernCount;i++){
							
							// Read the first:
							int firstChar=(int)reader.ReadCompressed() + previousChar;
							int secondChar=(int)reader.ReadCompressed();
							
							// Advance amount:
							int advance=(int)reader.ReadCompressedSigned();
							
							// Get the char and add a pair to it:
							SPACharacter before=animation.GetCharacter(firstChar);
							SPACharacter after=animation.GetCharacter(secondChar);
							
							if(after!=null && before!=null){
								
								// Add the pair!
								after.AddKerningPair(before,advance);
								
							}
							
							// Update previous:
							previousChar=firstChar;
							
						}
						
					}
					
					// Got additional charcodes?
					// -> 1 letter with multiple charcodes that use it.
					
					if(additionalCharcodes){
						
						// Get the # of additionals:
						int addCount=(int)reader.ReadCompressed();
						
						// Previous char - stored relative for better compression.
						int previousChar=0;
						
						// For each one..
						for(int i=0;i<addCount;i++){
							
							// Read the character ID:
							int firstChar=(int)reader.ReadCompressed() + previousChar;
							int extraCharcode=(int)reader.ReadCompressed();
							
							// Get the char:
							SPACharacter character=animation.GetCharacter(firstChar);
							
							// Add the CC to it:
							character.AddCharcode(extraCharcode);
							
							// Add as another charcode:
							animation.AddToMap(extraCharcode,character);
							
							// Update previous:
							previousChar=firstChar;
							
						}
						
					}
					
					if(fontMeta){
						
						// Load the font meta:
						Animation.FontMeta=new SPAFontMeta(reader);
						
					}
					
				}
				
			}
			
		}
		
		/// <summary>Sets the given sprite texture to this SPA sprite.</summary>
		public void SetSprite(Texture2D sprite,int totalFrameCount){
			
			// Set the sprite and the dimensions:
			Sprite=sprite;
			Width=sprite.width;
			Height=sprite.height;
			
			// Set the frame count:
			FrameCount=totalFrameCount;
			
			// Setup the scale:
			TextureScale=new Vector2((float)Animation.FrameWidth/(float)Width,(float)Animation.FrameHeight/(float)Height);
			
			// Figure out the vertical frame count:
			VerticalFrameCount=Height/Animation.FrameHeight;
			
		}
		
	}
	
}