/FileExtraction/Java_unseen/crisis-economics_CRISIS/CRISIS/test/eu/crisis_economics/abm/firm/TestExogenousFirm.java
/*
 * This file is part of CRISIS, an economics simulator.
 * 
 * Copyright (C) 2015 Ross Richardson
 * Copyright (C) 2015 John Kieran Phillips
 *
 * CRISIS is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * CRISIS is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with CRISIS.  If not, see <http://www.gnu.org/licenses/>.
 */
package eu.crisis_economics.abm.firm;

import java.util.HashMap;
import java.util.Map;
import java.util.Random;

import org.apache.commons.math3.analysis.UnivariateFunction;
import org.testng.Assert;
import org.testng.annotations.AfterMethod;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;

import eu.crisis_economics.abm.agent.InstanceIDAgentNameFactory;
import eu.crisis_economics.abm.algorithms.matching.ForagerMatchingAlgorithm;
import eu.crisis_economics.abm.algorithms.matching.HomogeneousRationingAlgorithm;
import eu.crisis_economics.abm.bank.StockTradingBank;
import eu.crisis_economics.abm.contracts.DepositHolder;
import eu.crisis_economics.abm.contracts.InsufficientFundsException;
import eu.crisis_economics.abm.contracts.stocks.StockHolder;
import eu.crisis_economics.abm.firm.plugins.CreditDemandFunction;
import eu.crisis_economics.abm.inventory.goods.DurableGoodsRepository.ManualGoodsClassifier;
import eu.crisis_economics.abm.markets.clearing.SimpleGoodsMarket;
import eu.crisis_economics.abm.markets.clearing.SimpleLabourMarket;
import eu.crisis_economics.abm.model.parameters.FunctionParameter;
import eu.crisis_economics.abm.model.parameters.ModelParameter;
import eu.crisis_economics.abm.model.parameters.TimeseriesParameter;
import eu.crisis_economics.abm.simulation.CustomSimulationCycleOrdering;
import eu.crisis_economics.abm.simulation.EmptySimulation;
import eu.crisis_economics.abm.simulation.NamedEventOrderings;
import eu.crisis_economics.abm.simulation.ScheduleIntervals;
import eu.crisis_economics.abm.simulation.SimulatedEventOrder;
import eu.crisis_economics.abm.simulation.Simulation;
import eu.crisis_economics.utilities.Pair;

/**
  * Lightweight unit tests for the class {@link FromFileTimeseriesParameter}.
  * 
  * @author phillips
  */
public class TestExogenousFirm {
   
   private Simulation
      state;
   
   @BeforeMethod
   public void setUp() {
      System.out.println("Testing " + getClass().getSimpleName() + "...");
      state = new EmptySimulation(0L);
      state.start();
   }
   
   private void advanceTimeByOneCycle() {
      final double timeNow = state.schedule.getTime();
      while(state.schedule.getTime() < timeNow + 1)
         state.schedule.step(state);
   }
   
   void advanceUntilJustAfterTime(final SimulatedEventOrder order) {
      final SimulatedEventOrder haltPoint = CustomSimulationCycleOrdering.create(order, 1);
      class DummyEvent { 
         boolean hasFired = false;
         public DummyEvent() {
            if(Simulation.getTime() - Simulation.getFloorTime() > order.getUnitIntervalTime())
               Simulation.onceCustom(this, "emptyEvent", haltPoint, ScheduleIntervals.create(1));
            else
               Simulation.onceCustom(this, "emptyEvent", haltPoint, ScheduleIntervals.create(0));
         }
         @SuppressWarnings("unused")
         public void emptyEvent() { hasFired = true; }
      }
      DummyEvent event = new DummyEvent();
      while(true) {
         state.schedule.step(state);
         if(event.hasFired) return;          // This is a false positive FindBugs mark point.
         else continue;
      }
   }
   
   /**
     * Create a {@link ModelParameter} from a {@link UnivariateFunction}.
     * 
     * @param function
     *        The {@link UnivariateFunction} used to generate the {@link ModelParameter}.
     */
   private TimeseriesParameter<Double>
      createParameterFromUnivariateFunction(
         final UnivariateFunction function) {
      return new TimeseriesParameter<Double>(
         new FunctionParameter(function, "")
         );
   }
   
   /**
     * Test whether an instance of {@link ExogenousFirm} behaves as expected. This
     * test operates as follows:<br><br>
     * 
     * {@code (a)}
     *   One {@link StockTradingBank} is created, to serve as a {@link StockHolder}
     *   and {@link DepositHolder} for an {@link ExogenousFirm};<br>
     * {@code (b)}
     *   One {@link ExogenousFirm} is created, as well as one goods market and one
     *   labour market;<br>
     * {@code (c)}
     *   {@link UnivariateFunction}{@code s} are created for every decision on which
     *   the {@link ExogenousFirm} depends;<br>
     * {@code (d)}
     *   One {@link ExogenousGoodsBuyer} and one {@link ExogenousEmployee} are 
     *   created. Both are connected to the relevant markets;<br>
     * {@code (e)}
     *   The {@link ExogenousGoodsBuyer} is credited and debited several times, 
     *   and made subject to cash injections. It is asserted that the financial
     *   state of the {@link DepositHolder} is unchanged;<br>
     * {@code (f)}
     *   Several simulation cycles elapse. In each cycle, it is asserted that the
     *   balance sheet of the {@link ExogenousFirm} is as expected and that the
     *   {@link ExogenousFirm} has interacted with the markets as expected.
     */
   @Test
   public void testExogenousFirmBehaviour() {
      final StockTradingBank
         bank = new StockTradingBank(1.0);                  // £1 to pay for initial shares
      
      final Map<String, Pair<Double, Double>> goodsTypes =  // One durable good,
         new HashMap<String, Pair<Double, Double>>();       //  one non-durable good.
      goodsTypes.put("type 1", Pair.create(1.0, 1.0));      // 100% time/use decay
      goodsTypes.put("type 2", Pair.create(0.1, 0.2));      // 10% time decay, 20% use decay.
      
      final SimpleGoodsMarket goodsMarket =
         new SimpleGoodsMarket(
            new ManualGoodsClassifier(goodsTypes),
            new ForagerMatchingAlgorithm(new HomogeneousRationingAlgorithm())
            );
      goodsMarket.addInstrument("type 1");
      goodsMarket.addInstrument("type 2");
      
      final SimpleLabourMarket labourMarket =
         new SimpleLabourMarket(
            new ForagerMatchingAlgorithm(
               new HomogeneousRationingAlgorithm()));
      
      final Random
         dice = new Random(1L);
      
      final class TestFunction implements UnivariateFunction {
         private final double
            grad;
         private TestFunction(Random dice) {
            grad = dice.nextDouble() + 1.;
         }
         @Override
         public double value(final double t) {
            return grad * (Math.floor(t) + 1.);
         }
      }
      
      final TimeseriesParameter<Double>
         deposits = createParameterFromUnivariateFunction(new TestFunction(dice)),
         commercialLoanDemand = createParameterFromUnivariateFunction(new TestFunction(dice)),
         desiredLabour = createParameterFromUnivariateFunction(new TestFunction(dice)),
         wageBidPrice = createParameterFromUnivariateFunction(new TestFunction(dice)),
         productionYield = createParameterFromUnivariateFunction(new TestFunction(dice)),
         sellingPrice = createParameterFromUnivariateFunction(new TestFunction(dice)),
         dividendPayment = createParameterFromUnivariateFunction(new TestFunction(dice)),
         maximumLendingRate = createParameterFromUnivariateFunction(new TestFunction(dice));
      
      final ExogenousFirm
         firm = new ExogenousFirm(
            bank,
            bank, 
            1.,                                                      // 1.0 shares emitted
            1.,                                                      // £1.0 per share
            "type 1",
            labourMarket,
            goodsMarket,
            deposits,
            commercialLoanDemand,
            desiredLabour,
            wageBidPrice,
            productionYield,
            sellingPrice,
            dividendPayment,
            maximumLendingRate,
            new CreditDemandFunction.RiskNeutralCreditDemandFunction(),
            new InstanceIDAgentNameFactory()
            );
      
      // Check whether debits, credits and cash flow injections are exogenous:
      
      {
      final double
         startingBankCashReserve = bank.getCashReserveValue(),
         startingFirmEquity = firm.getEquity();
      
      firm.debit(Double.MAX_VALUE);
      firm.debit(-1.);
      
      try {
         firm.credit(Double.MAX_VALUE / 2.);
         firm.credit(-1.);
      } catch (InsufficientFundsException e) {
         Assert.fail();
      }
      
      firm.cashFlowInjection(Double.MAX_VALUE / 4.);
      firm.cashFlowInjection(-1.);
      
      Assert.assertEquals(bank.getCashReserveValue(), startingBankCashReserve, 1.e-12);
      Assert.assertEquals(firm.getEquity(), startingFirmEquity, 1.e-12);
      }
      
      // Check whether cash allocation has memory:
      
      firm.allocateCash(100.);
      Assert.assertEquals(firm.getAllocatedCash(), 100., 1.e-12);
      firm.allocateCash(50.);
      Assert.assertEquals(firm.getAllocatedCash(), 150., 1.e-12);
      firm.disallocateCash(151.);
      Assert.assertEquals(firm.getAllocatedCash(), 0., 1.e-12);
      
      final ExogenousEmployee
         employee = new ExogenousEmployee(labourMarket);
      final ExogenousGoodsBuyer
         goodsBuyer = new ExogenousGoodsBuyer(goodsMarket);
      
      advanceTimeByOneCycle();
      
      double
         productionYieldLastValue = 0.;
      
      for(int i = 0; i< 10; ++i) {
         employee.setLabourSupply(100., 1.);
         goodsBuyer.setGoodsDemand("type 1", 100.);
         
         advanceUntilJustAfterTime(NamedEventOrderings.GOODS_INPUT_MARKET_MATCHING);
         
         final double
            depositsValueProvided = deposits.get(),
            desiredLabourValueProvided = desiredLabour.get(),
            wageBidPriceValueProvided = wageBidPrice.get(),
            productionYieldValueProvided = productionYield.get(),
            sellingPriceValueProvided = sellingPrice.get(),
            dividendPaymentValueProvided = dividendPayment.get();
         
         if(i != 0)                                                        // No trade in the first
            Assert.assertEquals(                                           //  simulation cycle, as
               goodsMarket.getInstrument("type 1")                         //  goods are yet to
                  .getWorstAskPriceAmongSellers(),                         //  appear in inventory.
               sellingPriceValueProvided,
               1.e-12
               );
         Assert.assertEquals(
            goodsMarket.getInstrument("type 1").getTotalSupply(), productionYieldLastValue, 1.e-12);
         
         
         advanceUntilJustAfterTime(NamedEventOrderings.AFTER_ALL);
         
         Assert.assertEquals(firm.getDepositValue(), depositsValueProvided, 1.e-12);
         Assert.assertEquals(firm.getAllocatedCash(), 0., 1.e-12);
         
         Assert.assertEquals(
            labourMarket.getLastLabourTotalDemand(), desiredLabourValueProvided, 1.e-12);
         Assert.assertEquals(
            firm.getCurrentDividend(), dividendPaymentValueProvided, 1.e-12);
         Assert.assertEquals(
            labourMarket.getInstrument(1).getDemandWeightedBidWage(),
            wageBidPriceValueProvided, 1.e-12);
         
         productionYieldLastValue = productionYieldValueProvided;
      }
   }
   
   @AfterMethod
   public void tearDown() {
      System.out.println(getClass().getSimpleName() + " tests pass.");
      state.finish();
   }
   
   /*
    * Manual entry point.
    */
   static public void main(final String[] args) {
      try {
         final TestExogenousFirm
            test = new TestExogenousFirm();
         test.setUp();
         test.testExogenousFirmBehaviour();
         test.tearDown();
      }
      catch(final Exception e) {
         Assert.fail();
      }
   }
}
