/**
 * package for game objects that are buildings
 */
package simogl.gameobjects.building;

import java.util.ArrayList;

import simogl.effect.particle.AbstractParticleEffect;
import simogl.effect.sound.SampledSound;
import simogl.gameobjects.AbstractGameObject;
import simogl.gameobjects.character.AbstractCharacter;
import simogl.geometry.Point3D;
import simogl.gui.ResourcePanel;
import simogl.level.AbstractLevel;

/**
 * This abstract class is the basic version of a building. Any 
 * specialized building must extend this class.
 * @author Frank Bruns
 *
 */
public abstract class AbstractBuilding extends AbstractGameObject
{
	// different types of buildings
	public static final int HOUSE = 0;
	public static final int FARM = 1;
	public static final int CITY_HALL = 2;
	public int type = -1;
	
	private static int totalNumberInhabitants = 0;
	private int numberInhabitants = 0;
	private int inhabitantLimit = 0;
	
	private int price = 0;
	private int regCosts = 0;
	private int taxRate = 0;
	
	/**
	 * this factor defines how much food an inhabitant of this building
	 * will consume during the according time intervall
	 */
	private float foodConsumptionFactor = 0f;
	
	AbstractCharacter character = null;
		
	protected ArrayList<AbstractParticleEffect> partEffects = null;
	
	protected SampledSound soundCollision = null;
	protected SampledSound soundConstruct = null;
	
	/**
	 * Creates a new abstract building
	 * @param type the constant describing the building. E.g. AbstractBuilding.HOUSE
	 * @param x x-coordinate
	 * @param y y-coordinate
	 * @param z z-coordinate
	 * @param price cost for construction of this building
	 * @param regCosts regluar costs for running the building
	 * @param taxRate taxes per inhabitant of the building
	 * @param inhabLimit maximaum possible number of inhabitants for the building.
	 * Inhabitants are people that pay taxes, so only asign a number greater 0
	 * to building that shall be considered for tax paying 
	 */
	public AbstractBuilding(int type, float x, float y, float z, int price, int regCosts, int taxRate, int inhabLimit)
	{
		super(x,y,z);
		this.type = type;
		this.price = price;
		this.regCosts = regCosts;
		this.taxRate = taxRate;
		this.inhabitantLimit = inhabLimit;
		this.soundCollision = new SampledSound("./resources/sounds/samples/collision.wav");
		this.soundConstruct= new SampledSound("./resources/sounds/samples/build.wav");
		this.partEffects = new ArrayList<AbstractParticleEffect>();
	}
	
	/**
	 * This method changes the position of the building to center it
	 * right on its point of construction. The earliest time to call this
	 * method would be right after a md2 model was assigned to the building.
	 * Don't call it earlier in order to avoid null pointer exceptions,
	 * because no md2 model is assigned to the building.
	 *
	 */
	protected void centerBuildingOnConstructionPoint()
	{
		// center the building
		this.setX(this.getX() - this.getModel().getCurrentAnim().getCurrentFrame().getMesh().getBoundingBox().getWidth()/2f);
		this.setZ(this.getZ() + this.getModel().getCurrentAnim().getCurrentFrame().getMesh().getBoundingBox().getLength()/2f);
	}
	
	/**
	 * Decrease the number of inhabitants of the building by the
	 * specified amount. But the number can't fall below 0.
	 * @param amount number of inhabitants to subtract from the building
	 */
	public void decreaseNumberInhabitants(int amount)
	{
		if(this.numberInhabitants-amount >= 0)
		{
			this.numberInhabitants -= amount;
			totalNumberInhabitants -= amount;
		}
		else
		{
			totalNumberInhabitants -= this.numberInhabitants;
			this.numberInhabitants = 0;
		}
	}
	
	/**
	 * Increase the number of inhabitants of the building by the
	 * specified amount. But the number can't increase above the 
	 * inhabitant limit of the building.
	 * @param amount number of inhabitants to add to the building
	 */
	public void increaseNumberInhabitants(int amount)
	{
		if(this.numberInhabitants+amount <= this.inhabitantLimit)
		{
			this.numberInhabitants += amount;
			totalNumberInhabitants += amount;
		}			
		else
		{
			totalNumberInhabitants -= this.numberInhabitants;
			totalNumberInhabitants += this.inhabitantLimit;
		}
	}
	
	/**
	 * Call this method to delete the building and everything that is
	 * connected to it (inhabitants, particle effects) from the scene.
	 *
	 */
	public void deleteBuilding()
	{			
		// remove the particle effects of this building
		for(AbstractParticleEffect effect :this.partEffects)
		{
			AbstractLevel.partEffects.remove(effect);
		}
		
		// remove inhabitants of this building from total number
		totalNumberInhabitants -= this.numberInhabitants;
		if(this.character != null) AbstractLevel.characters.remove(this.character);
		
		// decrease the city hall counter, if building was a city hall
		// in order to keep the correct limit for the maximum collectable taxes
		if(this.type == CITY_HALL) CityHall.decreaseNumberOfCityHall(1);
		
		// remove the building from the scene
		AbstractLevel.buildings.remove(this);
	}
	
	/**
	 * Adds the building to the scene, if there is no collision 
	 * with other buildings.
	 */
	public void addToScene()
	{			
		boolean collision = false;
		
		// don't construct building, if it collides with another 
		//building. Instead call delete building to remove the particle
		// effect and the inhabitant from the scene that hve already
		// been added to the scene
		for(AbstractGameObject building : AbstractLevel.buildings)
		{
			if(this.collFinder.collided(this, building))
			{
				this.deleteBuilding();
				collision = true;
				this.soundCollision.play(false);
				System.out.println("Collision detected: " +
						"Cannot construct building here.");
				break;
			}
		}	
		
		if(collision == false)
		{
			AbstractLevel.buildings.add(this);
			
			AbstractLevel.gold.decrease(this.getPrice());
			// update the resource panel immediately, don't wait for the
			// update method call
			ResourcePanel.setLabelGold(Integer.toString(AbstractLevel.gold.getCurrentAmount()));
			
			this.soundConstruct.play(false);
			System.out.println("Bulding of type '"+this.getName()+"' successfully created. ");
		}
	}
	
	/**
	 * Determines whether the (bounding) box of the bilding should
	 * be displayed. Only works, if the building just has one animation
	 * frame.
	 * @param showBox show (bounding) box? true=yes, false=no
	 */
	public void setVisibleBox(boolean showBox)
	{
		if(showBox == true)
		{
			this.md2Model.getCurrentAnim().getCurrentFrame().getMesh().showBoundingBox(true);
		}
		else
		{
			this.md2Model.getCurrentAnim().getCurrentFrame().getMesh().showBoundingBox(false);
		}
	}
	
	/**
	 * Returns the type of the building as a constant.
	 * E.g. the return value could be something like AbstractBuilding.HOUSE
	 * or AbstractBuilding.FARM
	 * @return type of the building according to its constant
	 */
	public int getType()
	{
		return this.type;
	}
	
	
	/**
	 * Returns the number of inhabitants belonging to this building
	 * @return number of inhabitants of this building
	 */
	public int getNumberInhabitants()
	{
		return this.numberInhabitants;
	}
	
	/**
	 * Returns the total number of inhabitant. This is the sum of every
	 * inhabitant belonging to a building.
	 * @return total number of inhabitants
	 */
	public static int getTotalNumberInhabitants()
	{
		return totalNumberInhabitants;
	}
	
	/**
	 * Returns the sum of food that gets consumed during every time
	 * intervall. It is calculated by multiplying the number of inhabitants
	 * of the building by the its food consuption factor. Afterwards a
	 * rounding operation is applied to the result to get an integer value.
	 * @return amount of food that gets consumed during every time intervall
	 */
	public int getFoodConsumption()
	{
		return Math.round(this.foodConsumptionFactor * this.getNumberInhabitants());
	}
	
	/**
	 * Returns the food consuption factor of the building. If it is not
	 * explicitly set, its value will be 0.0f
	 *
	 */
	protected float getFoodConsumptionFactor()
	{
		return this.foodConsumptionFactor;
	}
	
	/**
	 * Sets the food consumption factor of the building to the
	 * specified value
	 * @param factor new food consumption factor
	 */
	protected void setFoodConsumptionFactor(float factor)
	{
		this.foodConsumptionFactor = factor;
	}
	
	/**
	 * Returns the costs for contruction of the building
	 * @return price of the building
	 */
	public int getPrice()
	{
		return this.price;
	}
	
	/**
	 * Sets the price for the building
	 * @param price price for the building
	 */
	public void setPrice(int price)
	{
		this.price = price;
	}
	
	/**
	 * Returns the regular cost for running the building
	 * @return regular cost for running the building
	 */
	public int getRegularCosts()
	{
		return this.regCosts;
	}
	
	/**
	 * Sets the regular costs for running the building
	 * @param regCosts regular costs for running the building
	 */
	public void setRegularCost(int regCosts)
	{
		this.regCosts = regCosts;
	}
	
	/**
	 * Returns the taxes per inhabitant of the building
	 * @return tax rate of the building
	 */
	public int getTaxRate()
	{
		return this.taxRate;
	}
	
	/**
	 * Sets the price for the building
	 * @param taxRate tax rate for the building
	 */
	public void setTaxRate(int taxRate)
	{
		this.taxRate = taxRate;
	}
	
	/**
	 * Returns the total tax payments of this building, according to the
	 * tax rate multiplied by the number of inhabitants.
	 * @return total tax payment of the building
	 */
	public int getTaxes()
	{
		return this.taxRate * this.numberInhabitants;
	}

	@Override
	public synchronized void update(long elapsedTime)
	{
		this.getModel().update(elapsedTime);		
	}
}
