Exchange.java

package edu.ntnu.idi.idatt.model;

import java.math.BigDecimal;
import java.util.*;

import edu.ntnu.idi.idatt.model.market.Stock;
import edu.ntnu.idi.idatt.model.player.Player;
import edu.ntnu.idi.idatt.model.portfolio.Share;
import edu.ntnu.idi.idatt.model.transaction.Purchase;
import edu.ntnu.idi.idatt.model.transaction.Sale;
import edu.ntnu.idi.idatt.model.transaction.Transaction;

/**
 * Exchange class
 *
 * <p>
 * Class that keeps the 'stock game' gameloop.
 * Contains methods for managing game states aswell as performing
 * all functionality.
 * </p>
 * 
 */
public class Exchange {

  private final String name;
  private int week;
  private HashMap<String, Stock> stockMap = new HashMap<>();

  /**
   * Constructor for Exchange class
   *
   * @param name   - Name of the current stock Exchange
   * @param stocks - List of stocks for this exchange
   */
  public Exchange(String name, List<Stock> stocks) {
    this.name = name;
    this.week = 1;

    stocks.forEach(stock -> stockMap.put(stock.getSymbol(), stock));

  }

  /**
   * Getter for name.
   * 
   * @return String;
   */
  public String getName() {
    return name;
  }

  /**
   * Getter for week.
   * 
   * @return int;
   */
  public int getWeek() {
    return week;
  }

  /**
   * Getter for all stocks.
   * 
   * @return List of Stocks.
   */
  public List<Stock> getStocks() {
    return stockMap.values().stream().toList();
  }

  /**
   * Method for checking if a specific stock exists in the exchange.
   *
   * @param symbol - String symbol of a specific stock.
   * @return - true/false if the exchange has the specific stock.
   */
  public boolean hasStock(String symbol) {
    return stockMap.containsKey(symbol);
  }

  /**
   * Getter for a specific stock.
   *
   * @param symbol - String symbol of a specific stock.
   * @return - The found stock if existant.
   * @throws IllegalArgumentException if invalid symbol given.
   */
  public Stock getStock(String symbol) {
    if (this.hasStock(symbol)) {
      return stockMap.get(symbol);
    }
    throw new IllegalArgumentException("This stock doesn't exist in [" + name + "] exchange.");
  }

  /**
   * Method for searching after stocks.
   * 
   * @param searchTerm - String or character sequence of corporation name /
   *                   corresponding symbol.
   * @return - List of found stocks.
   */
  public List<Stock> findStocks(String searchTerm) {
    ArrayList<Stock> stocksFound = new ArrayList<>();

    for (Stock stock : stockMap.values()) {
      if (stock.getCompany().contains(searchTerm) || stock.getSymbol().contains(searchTerm)) {
        stocksFound.add(stock);
      }
    }
    return stocksFound;
  }

  /**
   * Method for obtaining gainers
   *
   * <p>
   * Returns the stocks that have done it the best
   * (percent change) in the latest week.
   * </p>
   *
   * @param limit - Amount of stocks to be returned.
   * @return A list of stocks sorted in declining order.
   */
  public List<Stock> getGainers(int limit) {
    return stockMap.values().stream()
        .filter(stock -> stock.getLatestPriceChangePercent().compareTo(BigDecimal.ZERO) > 0)
        .sorted(Comparator.comparing(Stock::getLatestPriceChangePercent).reversed())
        .limit(limit)
        .toList();
  }

  /**
   * Method for obtaining losers
   *
   * <p>
   * Returns the stocks that have done it the worst
   * in percent change in the latest week.
   * </p>
   *
   * @param limit - Amount of stocks to be returned.
   * @return A list of stocks sorted in ascending order.
   */
  public List<Stock> getLosers(int limit) {
    return stockMap.values().stream()
        .filter(stock -> stock.getLatestPriceChangePercent().compareTo(BigDecimal.ZERO) < 0)
        .sorted(Comparator.comparing(Stock::getLatestPriceChangePercent))
        .limit(limit)
        .toList();
  }

  /**
   * Method to allow a player to buy a stock.
   *
   * <p>
   * Executes a purchase for a player which executes all logic
   * and management of money, portfolio and archive.
   * </p>
   * 
   * @see Purchase
   *
   * @param symbol   - The symbol of the bought stock.
   * @param quantity - The amount of a bought stock.
   * @param player   - which player did this event.
   * @return The given transaction details. (Transaction).
   * @see Transaction
   */
  public Transaction buy(String symbol, BigDecimal quantity, Player player) {
    Stock stock = getStock(symbol);
    Share share = new Share(stock, quantity, stock.getSalesPrice());
    Purchase purchase = new Purchase(share, this.week);
    purchase.commit(player);
    return player.getTransactionArchive().getPurchases(this.week).getLast();
  }

  /**
   * Method to allow a player to sell a stock.
   *
   * <p>
   * Executes a sale for a player which executes all logic
   * and management of money, portfolio and archive.
   * </p>
   * 
   * @see Sale
   *
   * @param share  - The instance of the sold share.
   * @param player - which player did this event.
   * @return The given transaction details. (Transaction).
   * @see Transaction
   */
  public Transaction sell(Share share, Player player) {
    Sale sale = new Sale(share, this.week);
    sale.commit(player);
    return player.getTransactionArchive().getSales(this.week).getLast();
  }

  /**
   * Method to advance the gameloop.
   *
   * <p>
   * Adds a new price to each of the stock array.
   * Progresses week counter.
   * </p>
   * 
   * @see Stock
   */
  public void advance() {
    stockMap.values()
        .forEach(stock -> stock.advancePrice());

    this.week += 1;
  }

}