package jamel.basicModel.households;
import java.util.function.Consumer;
import jamel.Jamel;
import jamel.basicModel.banks.Account;
import jamel.basicModel.banks.Amount;
import jamel.basicModel.banks.Bank;
import jamel.basicModel.banks.Cheque;
import jamel.basicModel.firms.Employer;
import jamel.basicModel.firms.Goods;
import jamel.basicModel.firms.JobOffer;
import jamel.basicModel.firms.LaborContract;
import jamel.basicModel.firms.Supplier;
import jamel.basicModel.firms.Supply;
import jamel.util.Agent;
import jamel.util.AgentDataset;
import jamel.util.JamelObject;
import jamel.util.NotUsedException;
import jamel.util.Sector;
/**
* Represents a worker.
*/
public class BasicWorker extends JamelObject implements Agent, Worker {
/**
* The 'employed' status.
*/
private static final int EMPLOYED = 1;
/**
* THe 'unemployed' status.
*/
private static final int UNEMPLOYED = 0;
/**
* The wage flexibility parameter.
*/
private static final int wageFlexParam = 0;
/**
* THe wage resistance parameter.
*/
private static final int wageResistanceParam = 0;
/**
* Selects the best offer among the selection.
*
* @param employerSector
* the employer sector.
* @return the best offer, or <code>null</code> if a valid offer could not
* be found.
*/
private static JobOffer selectJobOffer(Sector employerSector) {
final Agent[] employers = employerSector.select(10);
// TODO 10 should be an argument of this method
JobOffer result = null;
// On retient la meilleur des offres.
for (int i = 0; i < employers.length; i++) {
if (employers[i] != null) {
final JobOffer jobOffer = ((Employer) employers[i]).getJobOffer();
if (jobOffer != null) {
if (result == null || jobOffer.getWage() > result.getWage()) {
result = jobOffer;
}
}
}
}
return result;
}
/**
* Returns the specified action.
*
* @param phaseName
* the name of the action.
* @return the specified action.
*/
static public Consumer<? super Agent> getAction(final String phaseName) {
final Consumer<? super Agent> action;
switch (phaseName) {
case "opening":
action = (agent) -> {
((BasicWorker) agent).open();
};
break;
case "jobSearch":
action = (agent) -> {
((BasicWorker) agent).jobSearch();
};
break;
case "consumption":
action = (agent) -> {
((BasicWorker) agent).consumption();
};
break;
case "closure":
action = (agent) -> {
((BasicWorker) agent).close();
};
break;
default:
throw new IllegalArgumentException(phaseName);
}
return action;
}
/**
* The bank account of this worker.
*/
private final Account account;
/**
* The dataset.
*/
final private AgentDataset dataset;
/**
* The sector of the employers.
*/
private final Sector employerSector;
/**
* A flag that indicates whether this worker is exhausted or not.
*/
private boolean exhausted = false;
/**
* The id of this agent.
*/
final private int id;
/**
* The labor contract.
*/
private LaborContract jobContract = null;
/**
* A flag that indicates whether this worker is open or not.
*/
private boolean open;
/**
* The reservation wage of this worker.
*/
private double reservationWage = 0;
/**
* The parent sector.
*/
final private Sector sector;
/**
* The employment status of this worker.
*/
private int status = UNEMPLOYED;
/**
* The sector of the suppliers.
*/
private final Sector supplierSector;
/**
* The duration of unemployment.
*/
private int unempDuration = 0;
/**
* The amount of the wage received for this period.
*/
private final Amount wage = new Amount();
/**
* THe flexibility of the wage.
*/
private float wageFlex;
/**
* Creates a new worker.
*
* @param sector
* the sector of this worker.
* @param id
* the id of this worker.
*/
public BasicWorker(final Sector sector, final int id) {
super(sector.getSimulation());
this.sector = sector;
this.id = id;
this.account = ((Bank) this.getSimulation().getSector("Banks").select(1)[0]).openAccount(this);
this.employerSector = this.getSimulation().getSector("Firms");
// TODO "Firms" sould be a parameter
if (this.employerSector == null) {
throw new RuntimeException("Employer sector is missing.");
}
this.supplierSector = this.getSimulation().getSector("Firms");
// TODO "Firms" sould be a parameter
if (this.supplierSector == null) {
throw new RuntimeException("Supplier sector is missing.");
}
this.dataset = new AgentDataset(this);
}
/**
* Closes the worker at the end of the period.
*/
private void close() {
if (!this.open) {
throw new RuntimeException("Already closed.");
}
if (this.exhausted && (this.wage.isEmpty() || this.jobContract == null
|| this.jobContract.getWage() != this.wage.getAmount())) {
Jamel.println();
Jamel.println("this.wage.isEmpty()", this.wage.isEmpty());
Jamel.println("this.jobContract", this.jobContract);
Jamel.println("this.jobContract.getWage()", this.jobContract.getWage());
Jamel.println("this.wage.getAmount()", this.wage.getAmount());
Jamel.println();
throw new RuntimeException("This worker is exhausted but there is a problem with its labor contract.");
}
this.dataset.put("countAgent", 1);
if (this.exhausted) {
this.dataset.put("employed", 1);
} else {
this.dataset.put("employed", 0);
}
this.dataset.put("money", this.account.getAmount());
this.open = false;
}
/**
* The consumption phase.
*/
private void consumption() {
if (!this.open) {
throw new RuntimeException("Closed.");
}
long consumptionVolume = 0;
long consumptionValue = 0;
long budget = this.account.getAmount();
this.dataset.put("consumptionBudget", budget);
if (budget > 0) {
final Agent[] selection = this.supplierSector.select(10);
while (budget > 0) {
Agent supplier = null;
for (int i = 0; i < selection.length; i++) {
if (selection[i] != null) {
final Supply supply_i = ((Supplier) selection[i]).getSupply();
if (supply_i == null || supply_i.getPrice() > budget || supply_i.getVolume() == 0) {
selection[i] = null;
} else if (supplier == null) {
supplier = selection[i];
selection[i] = null;
} else if (((Supplier) supplier).getSupply().getPrice() > supply_i.getPrice()) {
final Agent disappointing = supplier;
supplier = selection[i];
selection[i] = disappointing;
}
}
}
if (supplier == null) {
break;
}
final Supply supply = ((Supplier) supplier).getSupply();
final long spending;
final int consumVol;
if (supply.getTotalValue() <= budget) {
consumVol = supply.getVolume();
spending = (long) (consumVol * supply.getPrice());
if (spending != supply.getTotalValue()) {
throw new RuntimeException("Inconsistency.");
}
} else {
consumVol = (int) (budget / supply.getPrice());
spending = (long) (consumVol * supply.getPrice());
}
final Goods goods = supply.purchase(consumVol,
this.account.issueCheque(supply.getSupplier(), spending));
if (goods.getVolume() != consumVol) {
throw new RuntimeException("Bad volume");
}
budget -= spending;
consumptionValue += spending;
consumptionVolume += goods.getVolume();
goods.consume();
}
}
this.dataset.put("consumptionVolume", consumptionVolume);
this.dataset.put("consumptionValue", consumptionValue);
// TODO updater les chiffres de la consommation et de l'épargne.
}
/**
* Search for job opportunities.
*/
private void jobSearch() {
if ((this.jobContract == null) || !(this.jobContract.isValid())) {
this.status = UNEMPLOYED;
} else {
this.status = EMPLOYED;
}
// Different behaviors according the status.
switch (this.status) {
case UNEMPLOYED:
this.unempDuration++;
if (this.reservationWage > 0 && this.unempDuration > wageResistanceParam) {
this.reservationWage -= this.wageFlex * getRandom().nextFloat();
}
final JobOffer jobOffer = selectJobOffer(this.employerSector);
if (jobOffer != null && jobOffer.getWage() >= this.reservationWage) {
// Jamel.println(this.getName(), " I've got a job!");
this.jobContract = jobOffer.apply(this);
this.status = EMPLOYED;
this.unempDuration = 0;
}
break;
case EMPLOYED:
this.unempDuration = 0;
this.reservationWage = this.jobContract.getWage();
this.wageFlex = wageFlexParam * this.jobContract.getWage();
break;
default:
throw new RuntimeException("Unexpected status: " + this.status);
}
}
/**
* Opens the worker at the beginning of the period.
*/
private void open() {
if (this.open) {
throw new RuntimeException("Already open.");
}
this.open = true;
this.exhausted = false;
this.wage.cancel();
}
@Override
public void acceptPayCheque(Cheque cheque) {
if (this.jobContract == null || !this.exhausted || this.jobContract.getWage() != cheque.getAmount()
|| this.jobContract.getEmployer() != cheque.getDrawer() || this.wage.getAmount() != 0) {
throw new RuntimeException("Pay cheque trouble");
}
this.account.deposit(cheque);
this.wage.plus(cheque.getAmount());
// Comptabiliser ce paiement, à des fins statisques mais aussi pour que
// le travailleur vérifie s'il a été payé en fin de période.
}
@Override
public Long getAssetTotalValue() {
throw new NotUsedException();
}
@Override
public int getBorrowerStatus() {
throw new NotUsedException();
}
@Override
public Double getData(String dataKey, String period) {
return this.dataset.getData(dataKey);
}
@Override
public String getName() {
return "Worker_" + this.id;
}
@Override
public Sector getSector() {
return this.sector;
}
@Override
public void goBankrupt() {
throw new NotUsedException();
}
@Override
public boolean isBankrupted() {
throw new NotUsedException();
}
@Override
public boolean isSolvent() {
throw new NotUsedException();
}
@Override
public boolean work() {
if (this.jobContract == null || this.exhausted == true) {
Jamel.println();
Jamel.println("this.name == " + this.getName());
Jamel.println("this.laborContract == " + this.jobContract);
Jamel.println("this.exhausted == " + this.exhausted);
Jamel.println();
throw new RuntimeException("Something went wrong while working.");
}
this.exhausted = true;
return true;
}
}