// coffeemachine.c - simulation of a coffee machine

#include <stdio.h>

// declarations of functions that the API Coffee-O'Prog V 0.7
// provides. The implementation is further down in this file.

void activateCoffeeMachine();
void grindCoffee(unsigned amount);
void insertSugar(unsigned amount);
void insertMilk(unsigned amount);
void brewCoffee(unsigned amount);
void returnCoins();
void keepCoins();
void display(char message[]);

// declarations of callback functions that are needed by the
// API Coffee-O'Prog V 0.7.
//
// It would not really be necessary to have these
// declarations here, but it is easier to read if you know 
// which functions are callbacks that are dictated by the API
// and which functions come from our design

void waterContainerStatus(int status);
void coffeeContainerStatus(int status);
void milkContainerStatus(int status);
void sugarContainerStatus(int status);
void mugInserted();
void mugRemoved();
void keyPressed(unsigned short keymask);
void coinInserted(unsigned value);
void machineOpened();
void machineClosed();

// declarations of program-internal functions

void changePrices(unsigned keymask);


// Constants

#define WATER_EMPTY    0x0001
#define COFFEE_EMPTY   0x0002
#define MILK_EMPTY     0x0004
#define SUGAR_EMPTY    0x0008
#define MUG_INSERTED   0x0010
#define MACHINE_OPENED 0x0020

#define BUTTON_1       0x01
#define BUTTON_2       0x02
#define BUTTON_3       0x04
#define BUTTON_4       0x08

#define STRONG   BUTTON_1
#define MILK     BUTTON_2
#define SUGAR    BUTTON_3
#define BREW     BUTTON_4

#define COFFEE_AMOUNT    5
#define WATER_AMOUNT   125
#define SUGAR_AMOUNT     1
#define MILK_AMOUNT     30

// Status store

unsigned coins_ = 0;
unsigned machine_status_ = 0x0000;

//------------------------------------------------------------
int main()
{
  activateCoffeeMachine();
  return(0);
}

//************************************************************
// Implementation of all the callbacks

void waterContainerStatus(int status)
{
  if (!status)
  {
    machine_status_ |= WATER_EMPTY;
    display("----------------------------------------------");
    display("please refill water container");
    display("----------------------------------------------");
    return;
  }
  display("water refilled");
  machine_status_ &= ~WATER_EMPTY;
}

//------------------------------------------------------------
void coffeeContainerStatus(int status)
{
  if (!status)
  {
    machine_status_ |= COFFEE_EMPTY;
    display("----------------------------------------------");
    display("please refill coffee container");
    display("----------------------------------------------");
    return;
  }
  display("coffee refilled");
  machine_status_ &= ~COFFEE_EMPTY;
}

//------------------------------------------------------------
void milkContainerStatus(int status)
{
  if (!status)
  {
    machine_status_ |= MILK_EMPTY;
    display("----------------------------------------------");
    display("please refill milk container");
    display("----------------------------------------------");
    return;
  }
  display("milk refilled");
  machine_status_ &= ~MILK_EMPTY;
}

//------------------------------------------------------------
void sugarContainerStatus(int status)
{
  if (!status)
  {
    machine_status_ |= SUGAR_EMPTY;
    display("----------------------------------------------");
    display("please refill sugar container");
    display("----------------------------------------------");
    return;
  }
  display("sugar refilled");
  machine_status_ &= ~SUGAR_EMPTY;
}

//------------------------------------------------------------
void mugInserted()
{
  machine_status_ |= MUG_INSERTED;
  display("mug inserted, please choose your coffee");
}

//------------------------------------------------------------
void mugRemoved()
{
  machine_status_ &= ~MUG_INSERTED;
  display("mug removed");
}

//------------------------------------------------------------
void keyPressed(unsigned short keymask)
{
      // always need water and coffee
  unsigned int check_choice = WATER_EMPTY | COFFEE_EMPTY;

  if (machine_status_ & MACHINE_OPENED)
  {
    changePrices(keymask);
    return;
  }

      // "brew coffee" was not pressed.. ignoring
  if (!(keymask & BREW))
    return;

      // setup bitmask for checking milk and sugar if necessary
  if (keymask & MILK)
    check_choice |= MILK_EMPTY;
  if (keymask & SUGAR)
    check_choice |= SUGAR_EMPTY;

      // check if anything is empty
  if (check_choice & machine_status_)
  {
        // yes, I know, this is against the requirements,
        // but I leave it up to the interested reader to
        // correct this implementation and give a detailed
        // error message
    display("Sorry, not all ingredients available");
    return;
  }
  if (!(machine_status_ & MUG_INSERTED))
  {  
    display("Sorry, you have to insert your mug first");
    return;
  }
  grindCoffee(COFFEE_AMOUNT);
  if (keymask & SUGAR)
    insertSugar(SUGAR_AMOUNT);
  if (keymask & MILK)
    insertMilk(MILK_AMOUNT);
  if (keymask & STRONG)
    brewCoffee(WATER_AMOUNT / 2);
  else
    brewCoffee(WATER_AMOUNT);
  keepCoins();
  coins_ = 0;
      // price display and price-checking not implemented...
      // I leave this up to the interested reader
  display("Something for you to play around with:");
  display("Checking the price and displaying the price");
  display("is not implemented yet");
}

//------------------------------------------------------------
void coinInserted(unsigned value)
{
  display("coin inserted");
  coins_ += value;
}

//------------------------------------------------------------
void machineOpened()
{
  display("machine open - changing to price-balance mode");
  machine_status_ |= MACHINE_OPENED;
}

//------------------------------------------------------------
void machineClosed()
{
  display("machine closed - normal coffee mode again");
  machine_status_ &= ~MACHINE_OPENED;
}

//------------------------------------------------------------
void changePrices(unsigned keymask)
{
  display("Something for you to play around with:");
  display("changing the price is not implemented yet");
}

//------------------------------------------------------------
// Coffee-O'Prog V 0.7 API
// This API is included here, because we did not learn until 
// now, how to deal with multiple C-files. Usually you would
// find this code in a separate file (or in a library)

// declarations of internal API functions

void dispatch(unsigned char kbd_input);
void displayHelp();

// declarations of internal constants

#define _EMPTY_ 0
#define _FULL_  1

#define _INITIAL_WATER_AMOUNT_     500
#define _INITIAL_COFFEE_AMOUNT_     50
#define _INITIAL_MILK_AMOUNT_       90
#define _INITIAL_SUGAR_AMOUNT_       7

// status flags

#define _FRONT_OPEN_       0x0001
#define _MUG_INSERTED_     0x0002

// internal variables

unsigned _status_ = 0x0000;
unsigned _water_ =    _INITIAL_WATER_AMOUNT_;
unsigned _coffee_ =   _INITIAL_COFFEE_AMOUNT_;
unsigned _milk_ =     _INITIAL_MILK_AMOUNT_;
unsigned _sugar_ =    _INITIAL_SUGAR_AMOUNT_;



//------------------------------------------------------------
// The main function that activates the machine. Here the 
// endless loop for the keyboard input dispatcher of the 
// simulator is located.

void activateCoffeeMachine()
{
  unsigned char kbd_input = '\0';

  for(;;)
  {
    printf("Coffee Machine> ");
    kbd_input = getchar();
    getchar(); // eat the newline character and ignore it
    dispatch(kbd_input);
  }
}

//------------------------------------------------------------
void grindCoffee(unsigned amount)
{
      // see assumption in the SRD: it can't happen that
      // coffee is out during grinding.
  if (_coffee_ >= 0)
  {
    printf("***** grinding %u grams of coffee\n",amount);
    _coffee_ = (amount <= _coffee_) ? (_coffee_ - amount) : 0;
    if (!_coffee_)
      coffeeContainerStatus(_EMPTY_);
    return;
  }
  printf("*** Oops - out of coffee\n");
}

//------------------------------------------------------------
void insertSugar(unsigned amount)
{
  if (_sugar_ >= 0) // see assumption in the SRD
  {
    printf("***** inserting %u spoons of sugar\n",amount);
    _sugar_ = (amount <= _sugar_) ? (_sugar_ - amount) : 0;
    if (!_sugar_)
      sugarContainerStatus(_EMPTY_);
    return;
  }
  printf("*** Oops - out of sugar\n");
}

//------------------------------------------------------------
void insertMilk(unsigned amount)
{
  if (_milk_ >= 0) // see assumption in the SRD
  {
    printf("***** inserting %u milliliters of milk\n",amount);
    _milk_ = (amount <= _milk_) ? (_milk_ - amount) : 0;
    if (!_milk_)
      milkContainerStatus(_EMPTY_);
    return;
  }
  printf("*** Oops - out of milk\n");
}

//------------------------------------------------------------
void brewCoffee(unsigned amount)
{
  if (_water_ >= 0) // see assumption in the SRD
  {
    printf("***** brewing coffee with %u milliliters of water\n",
           amount);
    _water_ = (amount <= _water_) ? (_water_ - amount) : 0;
    if (!_water_)
      waterContainerStatus(_EMPTY_);
    return;
  }
  printf("*** Oops - out of water\n");
}

//------------------------------------------------------------
void returnCoins()
{
  printf("ok, you get your coins back\n");
}

//------------------------------------------------------------
void keepCoins()
{
  printf("thanks for the coins, enjoy your coffee\n");
}

//------------------------------------------------------------
void display(char message[])
{
  printf("### %s\n",message);
}


//------------------------------------------------------------
// dispatches the keyboard input

void dispatch(unsigned char kbd_input)
{
  switch(kbd_input)
  {
    case 'h':
      displayHelp();
      break;
    case 'P':
      if (!(_status_ & _FRONT_OPEN_))
      {
        _status_ |= _FRONT_OPEN_;
        machineOpened();
      }
      break;
    case 'p':
      if (_status_ & _FRONT_OPEN_)
      {
        _status_ &= ~_FRONT_OPEN_;
        machineClosed();
      }
      break;
    case 'i':
          // I know, the URD says 1, 5 and 10 S coins...
          // but for demonstration purposes this works too
      coinInserted(1); 
      break;
    case 'm':
      if (!(_status_ & _MUG_INSERTED_))
      {
        _status_ |= _MUG_INSERTED_;
        mugInserted();
      }
      break;
    case 'r':
      if (_status_ & _MUG_INSERTED_)
      {
        _status_ &= ~_MUG_INSERTED_;
        mugRemoved();
      }
      break;
    case '1':
    case '2':
    case '3':
    case '4':
    case '5':
    case '6':
    case '7':
    case '8':
    case '9':
      keyPressed((unsigned short)(kbd_input - '0'));
      break;
    case 'a':
    case 'b':
    case 'c':
    case 'd':
    case 'e':
    case 'f':
      keyPressed((unsigned short)(kbd_input - 'a' + 10));
      break;
    case 'W':
      if (_water_ <= 0)
      {
        _water_ = _INITIAL_WATER_AMOUNT_;
        waterContainerStatus(_FULL_);
      }
      break;
    case 'C':
      if (_coffee_ <= 0)
      {
        _coffee_ = _INITIAL_WATER_AMOUNT_;
        coffeeContainerStatus(_FULL_);
      }
      break;
    case 'M':
      if (_milk_ <= 0)
      {
        _milk_ = _INITIAL_WATER_AMOUNT_;
        milkContainerStatus(_FULL_);
      }
      break;
    case 'S':
      if (_sugar_ <= 0)
      {
        _sugar_ = _INITIAL_WATER_AMOUNT_;
        sugarContainerStatus(_FULL_);
      }
      break;
    case 'q':
      exit(0);
      break;
    default:
      printf("unknown keyboard input: %c\n",kbd_input);
      break;
  }
}

//------------------------------------------------------------
// displays help for operating the simulator

void displayHelp()
{
  printf("\n");
  printf("The following input is supported:\n");
  printf("h ... displays this help text\n");
  printf("P ... open front panel\n");
  printf("p ... close front panel\n");
  printf("i ... insert coin\n");
  printf("m ... insert mug\n");
  printf("r ... remove mug\n");
  printf("1 ... press button 1 (strong)\n");
  printf("2 ... press button 2 (milk)\n");
  printf("4 ... press button 3 (sugar)\n");
  printf("8 ... press button 4 (brew coffee)\n");
  printf("      if you want to press more than one button,\n");
  printf("      just add the values in hex!!\n");
  printf("      e.g. if you want strong coffee with milk\n");
  printf("      this corresponds to b\n");
  printf("W ... fill in water\n");
  printf("C ... fill in coffee\n");
  printf("M ... fill in milk\n");
  printf("S ... fill in sugar\n");
  printf("q ... quit\n");
  printf("\n");
}
