content: /*
   * Copyright 2008-present, David Karnok & Contributors
   * The file is part of the Open Imperium Galactica project.
   *
   * The code should be distributed under the LGPL license.
   * See http://www.gnu.org/licenses/lgpl.html for details.
   */
  
  package hu.openig.model;
  
  import hu.openig.core.Location;
  import hu.openig.utils.Exceptions;
  
  import java.awt.Point;
  import java.awt.Rectangle;
  import java.awt.geom.Point2D;
  import java.awt.geom.Point2D.Double;
  import java.util.Comparator;
  
  /**
   * A building instance.
   * @author akarnokd, 2010.01.07.
   */
  public class Building implements HasLocation {
      /** The building's unique id. */
      public final int id;
      /** The building type definition. */
      public final BuildingType type;
      /** The race of the building. */
      public final String race;
      /** The tileset used when rendering the building. */
      public final TileSet tileset;
      /** The scaffolding used when rendering the building build. */
      public final Scaffolding scaffolding;
      /** The building's placement. */
      public Location location;
      /** The energy assigned to this building. */
      public int assignedEnergy;
      /** The worker assigned to this building. */
      public int assignedWorker;
      /** The buildup progress up to the top hit point. */
      public int buildProgress;
      /** The currently available hitpoints of this building. */
      public int hitpoints;
      /** The current upgrade. Can be null for plain buildings. */
      public Upgrade currentUpgrade;
      /** The current upgrade level. 0 means no upgrades. */
      public int upgradeLevel;
      /** Is the building enabled. */
      public boolean enabled = true;
      /** Is the building under repair. */
      public boolean repairing;
      /** The allocation worker object. */
      public final BuildingAllocationWorker allocationWorker;
      /**
       * Constructs a building instance and assigns the prototype model.
       * @param id the building's unique id
       * @param type the building type
       * @param race the technology id
       */
      public Building(int id, BuildingType type, String race) {
          this.id = id;
          this.type = type;
          this.race = race;
          this.tileset = type.tileset.get(race);
          this.scaffolding = type.scaffoldings.get(race);
          this.allocationWorker = new BuildingAllocationWorker(this);
      }
      /**
       * Tests whether the given location is within the base footprint of this placed building.
       * @param a the X coordinate
       * @param b the Y coordinate
       * @return does the (a,b) fall into this building?
       */
      public boolean containsLocation(int a, int b) {
          return a >= location.x && b <= location.y && a < location.x + tileset.normal.width && b > location.y - tileset.normal.height;
  
      }
      /**
       * @return Returns the required worker amount for this building, taking the upgrade level into account.
       */
      public int getWorkers() {
          return (int)getResource("worker");
      }
      /**
       * @return Returns the required (&lt;0) or produced (&gt;0) energy amount, taking the upgrade level into account
       */
      public int getEnergy() {
          int e = (int)getResource("energy");
          if (e >= 0) {
              e = (int)(e * getEfficiency());
          }
          return e;
      }
      /**
       * @return the operational efficiency
       */
      public double getEfficiency() {
          if (!enabled) {
              return 0.0;
          }
          // if the building is incomplete or is more than 50% damaged
          if (hitpoints * 2 < type.hitpoints) {
              return 0.0;
          }
          return getSpacewarEfficiency();
      }
      /**
       * Calculate the spacewar efficiency.
       * @return the efficiency value
       */
      public double getSpacewarEfficiency() {
          if (buildProgress < type.hitpoints) {
              return 0d;
          }
          int workerDemand = getWorkers();
          int energyDemand = (int)getResource("energy");
  
          // if the building doesn't need energy
          if (energyDemand >= 0) {
              return Math.min(assignedWorker / (double)workerDemand, hitpoints / (double)type.hitpoints);
          }
          if (assignedEnergy * 2 > energyDemand) {
              return 0.0f;
          }
          return Math.min(
                  Math.min(assignedEnergy / (double)energyDemand, assignedWorker / (double)workerDemand),
  
                  hitpoints / (double)type.hitpoints);
      }
      /**
       * @return test if the building is operational
       */
      public boolean isOperational() {
          return isOperational(getEfficiency());
      }
      /**
       * Check if the building is spacewar-ready.
       * @return true if spacewar-ready
       */
      public boolean isSpacewarOperational() {
          return isOperational(getSpacewarEfficiency());
      }
      /**
       * @param efficiency the efficiency value 0..1
  
       * @return tell if an efficiency value indicates an operational building.
  
       */
      public static boolean isOperational(double efficiency) {
          return efficiency > 0.0;
      }
      /**
       * @return is the building in construction phase?
       */
      public boolean isConstructing() {
          return buildProgress < type.hitpoints;
      }
      /** @return test if the building is damaged. */
      public boolean isDamaged() {
          return (isConstructing() ? buildProgress : type.hitpoints)  > hitpoints;
      }
      /**
       * @return is the building or construction severly damaged?
       */
      public boolean isSeverlyDamaged() {
          return hitpoints * 2 < (isConstructing() ? buildProgress : type.hitpoints);
      }
      /** @return the current damage ratio. */
      public double health() {
          return isConstructing() ? 1 : 1.0 * hitpoints / type.hitpoints;
      }
      /**
       * @return is the building destroyed?
       */
      public boolean isDestroyed() {
          return hitpoints <= 0 && buildProgress > 0;
      }
      /** Make the building fully built. */
      public void makeFullyBuilt() {
          buildProgress = type.hitpoints;
          hitpoints = type.hitpoints;
      }
      /**
       * @return is there an energy shortage situation?
       */
      public boolean isEnergyShortage() {
          return !isConstructing() && assignedEnergy * 2 > getEnergy() && enabled;
      }
      /**
       * @return is there a worker shortage situation?
       */
      public boolean isWorkerShortage() {
          return !isConstructing() && assignedWorker * 2 > getWorkers() && enabled;
      }
      /**
       * Test if a given resource is present at this building.
       * @param name the resource name
       * @return the resource is present?
       */
      public boolean hasResource(String name) {
          return type.resources.containsKey(name);
      }
      /**
       * Retrieve a resource for this building and apply any upgrade settings.
       * The resource must exist, test with hasResource() before using this
       * @param name the resource name
       * @return the resource amount
       */
      public double getResource(String name) {
          Resource res = type.resources.get(name);
          if (currentUpgrade != null) {
              Resource ru = currentUpgrade.getType(name);
              if (ru != null) {
                  return res.amount * ru.amount;
              }
          }
          return res.amount;
      }
      /** @return the primary resource adjusted by the efficiency level. */
      public double getPrimary() {
          double res = getResource(type.primary);
          return res * getEfficiency();
      }
      /**
       * Set the upgrade level.
       * @param level the upgrade level
       */
      public void setLevel(int level) {
          this.upgradeLevel = level;
          if (level > 0) {
              this.currentUpgrade = type.upgrades.get(level - 1);
          } else {
              this.currentUpgrade = null;
          }
      }
      /**
       * @return is the building ready to receive workers and energy, e.g. is enabled, not under construction and is not severly damaged?
       */
      public boolean isReady() {
          return enabled && !isConstructing() && !isSeverlyDamaged();
      }
      /**
       * @return returns an allocation worker object for this building
       */
      public BuildingAllocationWorker getAllocationWorker() {
          allocationWorker.read();
          return allocationWorker;
      }
      /**
       * Create a copy of this building.
       * @param id the unique id of the new building
       * @return the new building object
       */
      public Building copy(int id) {
          Building result = new Building(id, type, race);
  
          result.buildProgress = buildProgress;
          result.hitpoints = hitpoints;
          result.setLevel(upgradeLevel);
          result.enabled = enabled;
          result.repairing = repairing;
          result.location = location;
  
          return result;
      }
      /** @return is the building completed? */
      public boolean isComplete() {
          return buildProgress == type.hitpoints;
      }
      /** @return true if this building can be further upgraded */
      public boolean canUpgrade() {
          int maxUpgrades = type.upgrades.size();
          return upgradeLevel < maxUpgrades;
      }
      /**
       * Returns true if the given upgrade level is accessible to this building.
  
       * @param newLevel the upgrade level
       * @return true if this building can be further upgraded
  
       */
      public boolean canUpgrade(int newLevel) {
          int maxUpgrades = type.upgrades.size();
          return upgradeLevel < maxUpgrades && newLevel <= maxUpgrades;
      }
      /**
       * Returns the cost of upgrading to the given new level.
       * @param newLevel the new level
       * @return the cost
       */
      public int upgradeCost(int newLevel) {
          int diff = newLevel - upgradeLevel;
          return type.cost * diff;
      }
      @Override
      public String toString() {
          return type.id + " (hp = " + hitpoints + ")";
      }
      /** @return the instance width */
      public int width() {
          return tileset.normal.width;
      }
      /** @return the instance height. */
      public int height() {
          return tileset.normal.height;
      }
      @Override
      public Double exactLocation() {
          return new Point2D.Double(location.x + width() / 2d, location.y - height() / 2d);
      }
      @Override
      public Location location() {
          return location;
      }
  
      @Override
      public void setLocation(double x, double y) {
          Exceptions.add(new AssertionError(String.format("Building location is final. Building: %s", this)));
      }
  
      /**
       * Returns the bounding rectangle of this building (without offset).
       * @return the bounding rectangle
  
       */
      public Rectangle rectangle() {
          int a0 = location.x;
          int b0 = location.y;
          int x = Tile.toScreenX(a0, b0);
          int y = Tile.toScreenY(a0, b0 - tileset.normal.height + 1) + 27;
  
          return new Rectangle(x, y - tileset.normal.imageHeight,
  
                  tileset.normal.imageWidth, tileset.normal.imageHeight);
  
      }
      /**
       * Returns the center of the footprint's bounding rectangle (without offset).
       * @return the point
       */
      public Point center() {
          double cx = location.x + width() / 2d;
          double cy = location.y - height() / 2d;
  
          return new Point((int)(Tile.toScreenX(cx, cy) + 28),
  
                  (int)(Tile.toScreenY(cx, cy) + 14));
      }
      /**
       * Compare the levels of two buildings.
       */
      public static final Comparator<Building> COMPARE_LEVEL = new Comparator<Building>() {
          @Override
          public int compare(Building o1, Building o2) {
              return Integer.compare(o1.upgradeLevel, o2.upgradeLevel);
          }
      };
      /**
       * Compare the build costs of two buildings.
       */
      public static final Comparator<Building> COMPARE_COST = new Comparator<Building>() {
          @Override
          public int compare(Building o1, Building o2) {
              return BuildingType.COST.compare(o1.type, o2.type);
          }
      };
  
      /**
       * @return the selling price of this building
       */
      public long sellPrice() {
          return type.cost * (1 + upgradeLevel) / 2;
      }
  }
  