package com.brashmonkey.spriter;

/**
 * Represents a mainline in a Spriter SCML file.
 * A mainline holds only keys and occurs only once in an animation.
 * The mainline is responsible for telling which draw order the sprites have
 * and how the objects are related to each other, i.e. which bone is the root and which objects are the children.
 * @author Trixt0r
 *
 */
public class Mainline {

    final Key[] keys;
    private int keyPointer = 0;
    
    public Mainline(int keys){
    	this.keys = new Key[keys];
    }
    
    public String toString(){
    	String toReturn = getClass().getSimpleName()+"|";
    	for(Key key: keys)
    		toReturn += "\n"+key;
    	toReturn+="]";
    	return toReturn;
    }
    
    public void addKey(Key key){
    	this.keys[keyPointer++] = key;
    }
    
    /**
     * Returns a {@link Key} at the given index.
     * @param index the index of the key
     * @return the key with the given index
     * @throws IndexOutOfBoundsException if index is out of range
     */
    public Key getKey(int index){
    	return this.keys[index];
    }
    
    /**
     * Returns a {@link Key} before the given time.
     * @param time the time a key has to be before
     * @return a key which has a time value before the given one.
     * The first key is returned if no key was found.
     */
    public Key getKeyBeforeTime(int time){
    	Key found = this.keys[0];
    	for(Key key: this.keys){
    		if(key.time <= time) found = key;
    		else break;
    	}
    	return found;
    }
    
    /**
     * Represents a mainline key in a Spriter SCML file.
     * A mainline key holds an {@link #id}, a {@link #time}, a {@link #curve}
     * and lists of bone and object references which build a tree hierarchy.
     * @author Trixt0r
     *
     */
    public static class Key{
    	
    	public final int id, time;
    	final BoneRef[] boneRefs;
    	final ObjectRef[] objectRefs;
    	private int bonePointer = 0, objectPointer = 0;
    	public final Curve curve;
    	
    	public Key(int id, int time, Curve curve, int boneRefs, int objectRefs){
    		this.id = id;
    		this.time = time;
    		this.curve = curve;
    		this.boneRefs = new BoneRef[boneRefs];
    		this.objectRefs = new ObjectRef[objectRefs];
    	}
    	
    	/**
    	 * Adds a bone reference to this key.
    	 * @param ref the reference to add
    	 */
    	public void addBoneRef(BoneRef ref){
    		this.boneRefs[bonePointer++] = ref;
    	}
    	
    	/**
    	 * Adds a object reference to this key.
    	 * @param ref the reference to add
    	 */
    	public void addObjectRef(ObjectRef ref){
    		this.objectRefs[objectPointer++] = ref;
    	}
    	
    	/**
    	 * Returns a {@link BoneRef} with the given index.
    	 * @param index the index of the bone reference
    	 * @return the bone reference or null if no reference exists with the given index
    	 */
    	public BoneRef getBoneRef(int index){
    		if(index < 0 || index >= this.boneRefs.length) return null;
    		else return this.boneRefs[index];
    	}
    	
    	/**
    	 * Returns a {@link ObjectRef} with the given index.
    	 * @param index the index of the object reference
    	 * @return the object reference or null if no reference exists with the given index
    	 */
    	public ObjectRef getObjectRef(int index){
    		if(index < 0 || index >= this.objectRefs.length) return null;
    		else return this.objectRefs[index];
    	}
    	
    	/**
    	 * Returns a {@link BoneRef} for the given reference.
    	 * @param ref the reference to the reference in this key
    	 * @return a bone reference with the same time line as the given one
    	 */
        public BoneRef getBoneRef(BoneRef ref){
        	return getBoneRefTimeline(ref.timeline);
        }
        
        /**
    	 * Returns a {@link BoneRef} with the given time line index.
         * @param timeline the time line index
         * @return the bone reference with the given time line index or null if no reference exists with the given time line index
         */
        public BoneRef getBoneRefTimeline(int timeline){
    		for(BoneRef boneRef: this.boneRefs)
    			if(boneRef.timeline == timeline) return boneRef;
        	return null;
        }
        
        /**
    	 * Returns an {@link ObjectRef} for the given reference.
    	 * @param ref the reference to the reference in this key
    	 * @return an object reference with the same time line as the given one
    	 */
        public ObjectRef getObjectRef(ObjectRef ref){
        	return getObjectRefTimeline(ref.timeline);
        }
        
        /**
    	 * Returns a {@link ObjectRef} with the given time line index.
         * @param timeline the time line index
         * @return the object reference with the given time line index or null if no reference exists with the given time line index
         */
        public ObjectRef getObjectRefTimeline(int timeline){
    		for(ObjectRef objRef: this.objectRefs)
    			if(objRef.timeline == timeline) return objRef;
        	return null;
        }
    	
    	public String toString(){
        	String toReturn = getClass().getSimpleName()+"|[id:"+id+", time: "+time+", curve: ["+curve+"]";
        	for(BoneRef ref: boneRefs)
        		toReturn += "\n"+ref;
        	for(ObjectRef ref: objectRefs)
        		toReturn += "\n"+ref;
        	toReturn+="]";
        	return toReturn;
        }
    	
    	/**
    	 * Represents a bone reference in a Spriter SCML file.
    	 * A bone reference holds an {@link #id}, a {@link #timeline} and a {@link #key}.
    	 * A bone reference may have a parent reference.
    	 * @author Trixt0r
    	 *
    	 */
    	public static class BoneRef{
    		public final int id, key, timeline;
    		public final BoneRef parent;
    		
    		public BoneRef(int id, int timeline, int key, BoneRef parent){
    			this.id = id;
    			this.timeline = timeline;
    			this.key = key;
    			this.parent = parent;
    		}
    		
    		public String toString(){
    			int parentId = (parent != null) ? parent.id:-1;
    			return getClass().getSimpleName()+"|id: "+id+", parent:"+parentId+", timeline: "+timeline+", key: "+key;
    		}
    	}
    	
    	/**
    	 * Represents an object reference in a Spriter SCML file.
    	 * An object reference extends a {@link BoneRef} with a {@link #zIndex},
    	 * which indicates when the object has to be drawn.
    	 * @author Trixt0r
    	 *
    	 */
    	public static class ObjectRef extends BoneRef implements Comparable<ObjectRef>{
    		public final int zIndex;
    		
    		public ObjectRef(int id, int timeline, int key, BoneRef parent, int zIndex){
    			super(id, timeline, key, parent);
    			this.zIndex = zIndex;
    		}
    		
    		public String toString(){
    			return super.toString()+", z_index: "+zIndex;
    		}

			@Override
			public int compareTo(ObjectRef o) {
				return (int)Math.signum(zIndex-o.zIndex);
			}
    	}
    }

}
