/*
 * Created on 23.11.2004
 *
 */
package de.farafin.snEADy.world;

import java.util.LinkedList;

import de.farafin.snEADy.GameParameter;
import de.farafin.snEADy.communication.D_Vec2D;
import de.farafin.snEADy.communication.I_Constants;
import de.farafin.snEADy.communication.I_PlayFieldConstants;
import de.farafin.snEADy.communication.RingVector;

/**
 * all objects in the world should extend fromthis class. this is the
 * representation of the Objects on the playfield. so to say their body.
 * 
 * @author roland, lars
 * 
 * @version $Revision: 1.20 $
 */
public abstract class C_GameObject implements I_Constants, I_PlayFieldConstants
{
	/** the position of the head. even if the object is larger than one field, it needs
	 * a position */
	protected D_Vec2D headPosition = null;
	
	/** the direction the Object faces to */
	protected int faceDirection = FACE_NORTH;
	
	/** all positions of the object, inlcuding the head! */
	//protected LinkedList objPositions = null;
	
	protected RingVector objPositions = null;
	
	/** the own representation on the playField
	 * 
	 * @see I_PlayFieldConstants
	 */
	protected char ownChar = 0;
	
	/** when is the object updated next time? if its negative, the object
	 * will never be updated */
	protected long nextUpdateTime = 0;
	
	/** the freuency with what the gaObject should be updated.
	 * if its negative, the nextUpdateTime never changes */
	protected long waitCycles = 1;

	/** parameter set of GameParameter */
	protected final GameParameter parameter;
	
	
	/** default constructor */
	protected C_GameObject()
	{
		super();
		this.parameter = null;
		this.headPosition = null;		
		this.faceDirection = FACE_NORTH;		
		this.objPositions = null;		
		this.ownChar = 0;
		this.waitCycles = 1;
		this.nextUpdateTime = 0;
	}
	

	/** default constructor */
	protected C_GameObject(GameParameter parameter)
	{
		super();
		this.parameter = parameter;
		this.headPosition = null;		
		this.faceDirection = FACE_NORTH;		
		this.objPositions = null;		
		this.ownChar = 0;
		this.waitCycles = 1;
		this.nextUpdateTime = 0;
	}
	
	/** create constructor
	 * @param headPosition
	 * @param faceDirection
	 * @param objPositions
	 * @param ownChar
	 * @param waitCycles
	 * @param parameter
	 */
	protected C_GameObject(D_Vec2D headPosition, int faceDirection, RingVector objPositions, char ownChar, long waitCycles, GameParameter parameter)
	{
		super();
		this.parameter = parameter;
		this.headPosition = (D_Vec2D)headPosition.clone();
		this.faceDirection = faceDirection;
		this.objPositions = (RingVector) objPositions.clone();
		this.ownChar = ownChar;
		this.waitCycles = waitCycles;
		this.nextUpdateTime = 0;
	}

	/** copy constructor
	 * reference-copy of attrbutes: use copy() if you need a full copy
	 * @param obj
	 */
	protected C_GameObject(C_GameObject obj)
	{
		super();
		this.parameter = obj.parameter;
		this.headPosition = (D_Vec2D)obj.headPosition.clone();
		this.faceDirection = obj.faceDirection;
		this.objPositions = obj.objPositions;		
		this.ownChar = obj.ownChar;
		this.waitCycles = obj.waitCycles;
		this.nextUpdateTime = 0;
	}
	
	
	/** constructor
	 * @param headPosition
	 * @param faceDirection
	 * @param ownChar
	 * @param waitCycles
	 */
	protected C_GameObject(D_Vec2D headPosition, int faceDirection, char ownChar, long waitCycles, GameParameter parameter)
	{
		super();
		this.parameter = parameter;
		this.headPosition = (D_Vec2D)headPosition.clone();
		this.faceDirection = faceDirection;
		this.objPositions = new RingVector();
		this.objPositions.addLast(headPosition.clone());
		this.ownChar = ownChar;
		this.waitCycles = waitCycles;
		this.nextUpdateTime = 0;
	}
	
	
	/** moves the object with all its positions to the specific position.
	 * @param pos the desteny position of the head.	 
	 * @param arena
	 */
	abstract protected void jumpTo(D_Vec2D pos, C_Arena arena);
	
	/** @return an unsorted array of all positions of the object. (it is no Copy!) */
	protected D_Vec2D[] getAllPosArray()
	{
		//if(DEBUG) System.out.println("DEBUG C_GameObject: getAllPosArray(): size " + objPositions.size());
		//if(DEBUG) System.out.println("DEBUG C_GameObject: getAllPosArray(): classname " + objPositions.element().getClass().getName());
		//if(DEBUG) System.out.println("DEBUG C_GameObject: getAllPosArray(): arrayclassname " + ((Object[])objPositions.toArray())[0].getClass().getName());
		D_Vec2D[] vecArray = new D_Vec2D[this.objPositions.size()];
		for(int i = 0; i < this.objPositions.size(); i++){vecArray[i] = (D_Vec2D)this.objPositions.getElementAt(i);}
		return vecArray;
	}

	/** @return an unsorted  LinkedList of all positions of the object.(it is no Copy!) */
	protected LinkedList getAllPosList() {return (LinkedList)objPositions.clone();}
	
	/** @return Returns the own char. */
	protected char getOwnChar(){return ownChar;}
	
	
	/** 
	 * @param sort maybe there are more than one way to sort the object positions, this
	 * parameter specifies the kind of sort.
	 * @return an sorted array of all posistions of the object. */
	abstract protected D_Vec2D[] getAllPosSorted(int sort);
	
	/** @return Returns the faceDirection. */
	protected int getFaceDirection(){return faceDirection;}
	/** @param faceDirection The faceDirection to set. */
	protected void setFaceDirection(int faceDirection)	{this.faceDirection = faceDirection;}
	/** @return Returns a copy of the headPosition. */
	protected D_Vec2D getHeadPosition(){return (D_Vec2D)headPosition.clone();}
	/** @param headPosition a copy of the headPosition to set.*/
	protected void setHeadPosition(D_Vec2D headPosition){this.headPosition = (D_Vec2D)headPosition.clone();}
	 
	/** returns the next position if the object would move one fieled in its face direction. That includes
	 * the case that he is at the end of the world and would jump to the oppiside side. remember
	 * the world is no disc, still a squared shpere!! :D
	 * @param arena the arena the object is in
	 * @return the new position
	 */
	protected D_Vec2D getNextPosInFaceDirection(C_Arena arena)
	{
		D_Vec2D pos = this.getHeadPosition();
		
		switch(this.getFaceDirection())
		{
			case FACE_NORTH:
				pos.y -= 1;
				if(pos.y < 0) pos.y = arena.getHeight()-1; // if the object is at the end of the world, it starts at the other 
				break;
			case FACE_EAST:
				pos.x += 1;
				if(pos.x >= arena.getWidth()) pos.x = 0; // if the object is at the end of the world, it starts at the other
				break;
			case FACE_SOUTH:
				pos.y += 1;
				if(pos.y >= arena.getHeight()) pos.y = 0; // if the object is at the end of the world, it starts at the other
				break;
			case FACE_WEST:
				pos.x -= 1;
				if(pos.x < 0) pos.x = arena.getWidth()-1; // if the object is at the end of the world, it starts at the other
				break;
			default:;
		}
		
		return pos;
	}
 
	/** @return Returns the nextUpdateTime. */
	protected long getNextUpdateTime(){return nextUpdateTime;}
	/** the update call meens that the object should do what ever it does usually
	 * 
	 * @param arena the arena the object is living in
	 */
	protected abstract void update(C_Arena arena);
		
	/** Moves only the head and the zero-element of the position-list to the new position 
	 *  @param dest the destination
	 */
	protected void jumpHeadPos(D_Vec2D dest)
	{
		this.headPosition.copyOnMe(dest);
		((D_Vec2D)this.objPositions.getFirst()).copyOnMe(dest);
	}
	
	/* (non-Javadoc)
	 * @see java.lang.Object#toString()
	 */
	public abstract String toString();
	
	/* (non-Javadoc)
	 * @see java.lang.Object#clone()
	 */
	protected abstract Object clone();
}