#include <GL/sgl.h>
#include <iostream>
#include "vendinglogic.h"


using namespace sgl;
using std::cout;
using std::endl;


//  Forward declaration
class VendingMachineGUI;

//  Radii of coins
const int QUARTER_RADIUS = 40,
          DIME_RADIUS    = 20,
          NICKEL_RADIUS  = 30,
          PENNY_RADIUS   = 22;

//  x coordinate of candy bars
const int CANDY_BAR_X = 100;

class Frame: public GraphicalObject
{
public:
    Frame(): GraphicalObject(10.0, 10.0, 380.0, 380.0) {}

    void paint() const
    {
        set_color(BLACK);
        fill_rectangle(10.0, 10.0, 280.0, 380.0);
        set_color(WHITE);
        fill_rectangle(CANDY_BAR_X - 10.0, 150.0, 84.0, 225.0);
        draw_rectangle(15.0, 15.0, 270.0, 370.0);
    }
};

class Coin: public BitmapObject
{
protected:
    Point home;  //  Home position
    VendingMachineGUI *machine;
public:
    Coin(const char *filename, double x, double y, 
         double radius, VendingMachineGUI *m):
           BitmapObject(filename, x - radius, y - radius, 
                        2*radius, 2*radius),
           home(x, y),
           machine(m) {}

    void mouse_released(double x, double y, MouseButton b);

    virtual int value() const = 0;
};

class Quarter: public Coin
{
public:
    Quarter(double x, double y, VendingMachineGUI *m): 
           Coin("quarter.bmp", x, y, QUARTER_RADIUS, m) {}

    int value() const { return 25; }
};

class Dime: public Coin
{
public:
    Dime(double x, double y, VendingMachineGUI *m): 
             Coin("dime.bmp", x, y, DIME_RADIUS, m) {}

    int value() const { return 10; }
};

class Nickel: public Coin
{
public:
    Nickel(double x, double y, VendingMachineGUI *m): 
            Coin("nickel.bmp", x, y, NICKEL_RADIUS, m) {}

    int value() const { return 5; }
};


class Penny: public Coin
{
public:
    Penny(double x, double y, VendingMachineGUI *m): 
               Coin("penny.bmp", x, y, PENNY_RADIUS, m) {}

    int value() const { return 1; }
};

class CandyBar: public BitmapObject
{
protected:
    Point home;
    VendingMachineGUI *machine;
public:
    CandyBar(const char *pic, double x, double y, 
             VendingMachineGUI *m):
        BitmapObject(pic, x, y, 64, 64), 
        home(x, y), machine(m) {}
    void mouse_released(double x, double y, MouseButton);
    //  Disallow dragging
    void mouse_dragged(double, double) {}
};


class VendingMachineGUI: public ObjectWindow
{
    VendingLogic vend;
    Frame *frame;

    Multidigit *display;

    Quarter *quarter;
    Dime *dime;
    Nickel *nickel;
    Penny *penny;

    // Products
    CandyBar *bar1;
    CandyBar *bar2;
    CandyBar *bar3;
    CandyBar *bar4;

    int bar_count;

    double scale;
public:
    VendingMachineGUI(int price): 
           ObjectWindow("Seven Segment Display", 100, 100, 600, 400,
                               0.0, 600.0, 0.0, 400.0), 
           frame(new Frame),
           vend(price),
           display(new Multidigit(2, RED, 70, 30, 100.0)),
           bar_count(4),
           scale(100.0) 
    {  
        set_background_color(WHITE);

        quarter = new Quarter(520, 300, this);
        dime = new Dime(520, 230, this);
        nickel = new Nickel(520, 170, this);
        penny = new Penny(520, 110, this);
        add(quarter);
        add(dime);
        add(nickel);
        add(penny);

        add(frame);

        bar1 = new CandyBar("almond-joy.bmp", CANDY_BAR_X, 150, this);
        bar2 = new CandyBar("snickers.bmp", CANDY_BAR_X, 200, this);
        bar3 = new CandyBar("twix.bmp", CANDY_BAR_X, 250, this);
        bar4 = new CandyBar("butterfinger.bmp", CANDY_BAR_X, 300, this);

        add(bar1);
        add(bar2);
        add(bar3);
        add(bar4);

        add(display);
    }

    virtual ~VendingMachineGUI()
    {   //  Free up memory from heap allocated objects
        delete display;
        delete quarter;
        delete dime;
        delete nickel;
        delete penny;
        delete bar1;
        delete bar2;
        delete bar3;
        delete bar4;
    }

    void paint()
    {
        //  Synchronize display to vending logic
        int due = vend.amount_due();
        //if ( bar_count <= 0 )
        //    display->set_color(GRAY);
        if ( bar_count <= 0 )
            remove(display);
        display->set_value((due >= 0)? due : 0);
    }

    void key_pressed(int key, double x, double y)
    {
        switch ( key )
        {
        case UP_KEY:
            scale *= 1.2;
            display->resize(1.0);
            break;
        case DOWN_KEY:
            display->resize(-1.0);
            break;
        case 'd':
            display->increment();
            break;
        case 'D':
            display->decrement();
            break;
        case 'z':
            display->set_leading_zeros(true);
            break;
        case 'Z':
            display->set_leading_zeros(false);
            break;
        default:
            display->increment(); 
        }
        Window::key_pressed(key, x, y);
        repaint();
    }

    void remove_bar(CandyBar *bar)
    {
        remove(bar);
        bar_count--;
    }

    bool insert_coin(Coin *coin)
    {
        bool result = false;
        if ( bar_count > 0 && vend.input(coin->value()) )
        {
            display->set_value(vend.amount_due());
            cout << '\a';
            result = true;
        }
        repaint();
        return result;
    }

    void reset()
    {
        vend.reset();
        repaint();
    }

    bool process_vend_request()
    {
        bool result = false;
        if ( vend.amount_due() <= 0 )
        {
            cout << '\a';
            cout << '\a';
            cout << '\a';
            result = true;
        }
        return result;
    }
};


void Coin::mouse_released(double x, double y, MouseButton b)
{
    if ( x + width < 350.0 )
        machine->insert_coin(this);
    double radius = width/2;
    move_to(home.x - radius, home.y - radius);
    BitmapObject::mouse_released(x, y, b);
}

void CandyBar::mouse_released(double x, double y, MouseButton b)
{
    if ( machine->process_vend_request() )
    {
        machine->reset();       //  Begin new transaction
        machine->remove_bar(this); //  Remove product
    }
    BitmapObject::mouse_released(x, y, b);
    machine->repaint();
}


int main()
{
    (new VendingMachineGUI(75))->run();
}
