package seatrading.ships;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import seatrading.main.Container;
import seatrading.main.Goods;
import seatrading.main.Harbor;
import seatrading.main.SeaTrading;

/**
 * @author Daniel "3ICE" Berezvai
 */
public class Freighter extends Ship implements IShip {

  public List<Container> containers;
  private double cargoCapacity;

  public Freighter(String name, double weight, double cargoCapacity,
          Harbor homeHarbor) {
    super(name, weight, homeHarbor);
    System.out.println("\tCreating a freighter called \"" + name
            + "\". It weighs " + weight + " tons and has " + cargoCapacity
            + " cargo capacity. It currently resides in "
            + homeHarbor.getName() + ".");
    this.containers = new ArrayList<>(0);
    this.cargoCapacity = cargoCapacity;
  }

  /**
   * Splits a huge pile of trade goods into small containers and stores them on
   * the ship. Large ships can hond tens of thousands of containers each.
   *
   * @param goods a huge pile of trade goods
   */
  public void addGoods(Goods goods) {
    while (goods.getWeight() > 0.1) {
      Container c = new Container(new Goods(goods.getName(),
              goods.rmWeight(Container.MAX_WEIGHT), goods.getPrice()));
      containers.add(c);
    }
  }

  /**
   * Generates some random tradegoods to have in stock at initialization time.
   */
  public void initialLoad() {
    int upper = SeaTrading.rnd.nextInt(10) + 1;
    for (int i = 0; i < upper; i++) {
      addGoods(new Goods(Goods.rndName(),
              SeaTrading.rnd.nextInt((int) cargoCapacity) / 20.0));
    }
  }

  public void printCurrentLoad() {
    System.out.print("\tFreighter " + getName() + " has");
    for (Goods good : tallyContainers()) {
      System.out.print("; " + good.getWeight() + " tons of " + good.getName());
    }
    System.out.println(". (" + containers.size() + " containers, "
            + getTotalWeight() + "/" + cargoCapacity + " tons)");
  }

//  public void printCapacityAndPercentFull() {
//    System.out.println(weighContainers() + "/" + cargoCapacity + " ("
//            + weighContainers() / cargoCapacity + "% full");
//  }
  /**
   * Weighs the containers
   *
   * This includes the + 2t / container extra.
   *
   * @return
   */
  public double weighContainers() {
    double sum = 0;
    for (Container c : containers) {
      sum += c.getWeight();
    }
    return sum;
  }

  public double getTotalWeight() {
    return SeaTrading.round(weight + fuel + weighContainers());
  }

  /**
   * Exchange goods for a profit.
   *
   * <p>First sells everything profitable on board. The harbor pays extra for
   * some tradegoods, those it has 0 tons of, to be specific.</p>
   *
   * <p>Then buys underpriced goods from the harbor, until every stock runs out
   * or the ship is full.</p>
   *
   * @return the profit/loss
   * @note Often returns a loss because of the new cargo brought on board.
   */
  public double tradeAtHarbor() {
    double profit = 0;
    double dropOff = 0;
    List<Container> newContainers = new ArrayList<>(0);
    for (Container c : containers) {
      if (SeaTrading.rnd.nextInt(10) > 3
              || c.goods.getPrice() < harbor.offerGoodsForSale(c.goods)) {
        dropOff += c.goods.getWeight();
        profit += harbor.sell(c.goods);
        //I'd get a ConcurrentModificationException if I try removing elements.
        c = null;//Gets eaten by garbage collector. This signifies that it has been "sold".
      } else {
//        System.err.println("I refuse to sell " + c.goods.toString() + " because "
//                + c.goods.getPrice() + " < " + harbor.offerGoodsForSale(c.goods)
//                + " (getFreeSpace(): " + getFreeSpace() + ")");
        newContainers.add(c);
      }
    }
    containers.clear();
    containers.addAll(newContainers);
    double pickUp = 0;
    while (roomForAtLeastOneMoreFullContainer() && harbor.hasGoodsForSale()
            && (pickUp == 0 || SeaTrading.rnd.nextInt(10) > 2)) {
      Goods g = harbor.buyGoods(getFreeSpaceWithContainers());
      profit -= g.getPrice();
      pickUp += g.getWeight();
//      System.err.print("Bought getFreeSpace() = " + getFreeSpace() + ", pickUp = " + pickUp + " tons.");
      addGoods(g);
//      System.err.println(" Have getFreeSpace() = " + getFreeSpace() + " room left.");
    }
    profit = SeaTrading.round(profit);
    pickUp = SeaTrading.round(pickUp);
    dropOff = SeaTrading.round(dropOff);
    System.out.println("Freighter " + getName() + " made $" + profit
            + " profit by dropping off " + dropOff
            + " tons of trade goods at " + harbor.getName() + ". It also picked up " + pickUp
            + " tons of new goods.");
//    if (pickUp < 100) {
//      System.err.println(getName() + " is not buying these goods for some reason! (" + harbor.goods.size() + ")");
//      for (Goods g : harbor.goods) {
//        System.err.println("\t" + g.toString());
//      }
//      System.err.println("      getFreeSpace() = " + getFreeSpace());
//
//      System.err.println(harbor.buyGoods(999999999).toString());
//    }
    return profit;
  }

  private boolean roomForAtLeastOneMoreFullContainer() {
    return getFreeSpace() >= Container.MAX_WEIGHT;
  }

  private double getFreeSpace() {
    return SeaTrading.round(cargoCapacity - weighContainers());
  }

  private double getFreeSpaceWithContainers() {
    return ((getFreeSpace() / Container.MAX_WEIGHT) - 1)
            * Container.CAPACITY;
  }

  /**
   * Opens up each container on the ship and sums up the goods within.
   *
   * @return the list of goods
   */
  public ArrayList<Goods> tallyContainers() {
    HashMap<String, Double> map = new HashMap<>(containers.size());
    Double tmp;
    for (Container c : containers) {
      tmp = map.get(c.goods.getName());
      if (tmp != null) {
        map.put(c.goods.getName(), SeaTrading.round(tmp + c.goods.getWeight()));
      } else {
        map.put(c.goods.getName(), c.goods.getWeight());
      }
    }
    ArrayList<Goods> result = new ArrayList<>(map.size());
    for (Map.Entry<String, Double> e : map.entrySet()) {
      result.add(new Goods(e.getKey(), e.getValue(), 0));//Price is ignored here
    }
    return result;
  }
}
