package example;

import java.util.Random;

import hc.*;
import hc.game.*;

/**
 * A microgrid demand--side management game, as described in [1].
 *
 * [1] H. Hildmann and F. Saffre. Influence of variable supply and load
 * flexibility on Demand-Side Management. In Proc. 8th International
 * Conference on the European Energy Market (EEM'11), pages 63-68. 2011.
 */
public class Microgrid {
    /**
     * Number of days.
     */
    public static final int DAYS = 3;
    /**
     * Number of intervals per day.
     */
    public static final int INTERVALS = 16;
    /**
     * Number of rounds.
     */
    public static final int MAX_TIME = DAYS*INTERVALS;
    
    /**
     * Demand curve.
     */
    public static final double[] DEMAND = {
        0.0614, 0.0392, 0.0304, 0.0304,
        0.0355, 0.0518, 0.0651, 0.0643,
        0.0625, 0.0618, 0.0614, 0.0695,
        0.0887, 0.1013, 0.1005, 0.0762,
    };
    /**
     * Number of households.
     */
    public static final int NUM_HOUSEHOLDS = 3;
    /**
     * Households.
     */
    protected static Household[] households;
    /**
     * Current round.
     */
    public static int/*@(0, MAX_TIME)*/ time;
    /**
     * Current number of jobs.
     */
    public static int/*@(0, NUM_HOUSEHOLDS)*/ numJobs;

    /**
     * Returns the current load demand.
     */
    public static double getDemand() {
        return DEMAND[time%INTERVALS];
    }
    /**
     * A potential price of a load to be bought.
     */
    public static double getPrice() {
      return numJobs + 1.0;
    }
    /**
     * Called by the scheduler at the end of each turn.
     *
     * @return if the simulation is finished
     */
    public static boolean turnEnd() {
        // reduce job counters
        for(int i = 0; i < Microgrid.NUM_HOUSEHOLDS; ++i)
              households[i].turnEnd();
        return ++time < MAX_TIME;
    }
    public static void main(String[] args) {
        // player ids at the table: 0 schedule, 1..NUM_HOUSEHOLDS households
        TurnGame table = new TurnGame();
        Model.name(table);
        // create an array of households
        households = new Household[NUM_HOUSEHOLDS];
        for(int i = 0; i < NUM_HOUSEHOLDS; ++i) {
            Household h = new Household(table, 1 + i);
            h.start();
            Model.name(h, "", "" + (i + 1));
            Model.player(h, "h" + (i + 1));
            households[i] = h;
        }
        // create the scheduler
        Scheduler s = new Scheduler(table, households);
        s.start();
        Model.name(s);
        Model.player(s, "scheduler");
        // reset time
        time = 0;
        // players consist of the scheduler and of the households
        table.start(1 + NUM_HOUSEHOLDS);
        Model.check("<<1>> R{\"value1\"}max=? [F time=MAX_TIME]");
        Model.check("all", "<<1, 2, 3>> R{\"value123\"}max=? [F time=MAX_TIME]");
    }
}

class Scheduler extends TurnPlayer {
    protected final static boolean NON_DETERMINISTIC = false;
    
    protected final Household[] households;
    protected final Random random;

    public Scheduler(TurnGame table, Household[] households) {
        super(table, 0);
        this.households = households;
        random = new Random();
    }
    @Override
    public void run() {
        do {
            // how many households currently generate a load
            Microgrid.numJobs = 0;
            for(int i = 0; i < Microgrid.NUM_HOUSEHOLDS; ++i)
                if(households[i].job > 0)
                    ++Microgrid.numJobs;
            Model.stateOr("measure");
            // select a household
            int address;
            if(NON_DETERMINISTIC)
                // non--deterministic choice
                address = random.nextInt(Microgrid.NUM_HOUSEHOLDS);
            else
                // probabilistic choice
                address = (int)
                    (Math.random()*Microgrid.NUM_HOUSEHOLDS);
            // contact the selected household
            turnNext(1 + address);
            turnWait();
            // end of a round
        } while(Microgrid.turnEnd());
    }
    private static final double INTERVALS = 1.1;
}

class Household extends TurnPlayer {
    /**
     * Maximum time of running a single job, in intervals.
     */
    protected final static int MAX_JOB_TIME = 4;
    /**
     * Expected number of jobs per day.
     */
    protected final static int EXPECTED_JOBS = 9;
    /**
     * Price limit, above which this household may
     * back--off.
     */
    protected final static double PRICE_LIMIT = 1.5;
    /**
     * Probability of starting a task independently of the cost.
     */
    protected final static double P_OVER_LIMIT = 0.8;
    /**
     * A running job, in intervals.
     */
    public int/*@(0, MAX_JOB_TIME)*/ job = 0;
    protected final Random random;
    
    public Household(TurnGame table, int playerNum) {
        super(table, playerNum);
        random = new Random();
    }
    /**
     * Called by the scheduler after each turn.
     */
    public void turnEnd() {
        if(job > 0)
          --job;
    }
    @Override
    public void run() {
        while(true) {
            turnWait();
            if(job == 0 && Math.random() < Microgrid.getDemand()*
                  EXPECTED_JOBS) {
                int demand = 1 + (int)(Math.random()*MAX_JOB_TIME);
                // decide if to generate a load
                if(Microgrid.getPrice() < PRICE_LIMIT ||
                     Math.random() < P_OVER_LIMIT ||
                     // decide, if to generate a load anyway
                     random.nextInt(2) == 0)
                  job = demand;
            }
            turnNext(0);
        }
    }
}

/*
   @modelAppend(
       rewards "value1"
               measure & job1>0 : 1/numJobs;
       endrewards

       rewards "value12"
               measure & job1>0 : 1/numJobs;
               measure & job2>0 : 1/numJobs;
       endrewards

       rewards "value123"
               measure & job1>0 : 1/numJobs;
               measure & job2>0 : 1/numJobs;
               measure & job3>0 : 1/numJobs;
       endrewards
   )
 */
