/*
 * File: gevents.h
 * ---------------
 * This file defines the event types used in the StanfordCPPLib graphics
 * libraries.  The structure of this package is adapted from the Java event
 * model.
 */

/*************************************************************************/
/* Stanford Portable Library                                             */
/* Copyright (c) 2014 by Eric Roberts <eroberts@cs.stanford.edu>         */
/*                                                                       */
/* This program 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.                                   */
/*                                                                       */
/* This program 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 this program.  If not, see <http://www.gnu.org/licenses/>. */
/*************************************************************************/

#ifndef _gevents_h
#define _gevents_h

#include <string>
#include "gtimer.h"
#include "gwindow.h"

/*
 * Type: EventClassType
 * --------------------
 * This enumeration type defines the event classes.  The element values are
 * each a single bit and can be added or ORed together to generate an event
 * mask.  The CLICK_EVENT class responds only to the MOUSE_CLICKED event
 * type.  The ANY_EVENT class selects any event.
 */

enum EventClassType {
   NULL_EVENT   = 0x000,
   ACTION_EVENT = 0x010,
   KEY_EVENT    = 0x020,
   TIMER_EVENT  = 0x040,
   WINDOW_EVENT = 0x080,
   MOUSE_EVENT  = 0x100,
   CLICK_EVENT  = 0x200,
   ANY_EVENT    = 0x3F0
};

/*
 * Type: EventType
 * ---------------
 * This enumeration type defines the event types for all events.
 */

typedef enum {
   WINDOW_CLOSED    = WINDOW_EVENT + 1,
   WINDOW_RESIZED   = WINDOW_EVENT + 2,
   ACTION_PERFORMED = ACTION_EVENT + 1,
   MOUSE_CLICKED    = MOUSE_EVENT + 1,
   MOUSE_PRESSED    = MOUSE_EVENT + 2,
   MOUSE_RELEASED   = MOUSE_EVENT + 3,
   MOUSE_MOVED      = MOUSE_EVENT + 4,
   MOUSE_DRAGGED    = MOUSE_EVENT + 5,
   KEY_PRESSED      = KEY_EVENT + 1,
   KEY_RELEASED     = KEY_EVENT + 2,
   KEY_TYPED        = KEY_EVENT + 3,
   TIMER_TICKED     = TIMER_EVENT + 1,
} EventType;

/*
 * Type: ModifierCodes
 * -------------------
 * This enumeration type defines a set of constants used to check whether
 * modifiers are in effect.
 */

enum ModifierCodes {
   SHIFT_DOWN     = 1 << 0,
   CTRL_DOWN      = 1 << 1,
   META_DOWN      = 1 << 2,
   ALT_DOWN       = 1 << 3,
   ALT_GRAPH_DOWN = 1 << 4,
   BUTTON1_DOWN   = 1 << 5,
   BUTTON2_DOWN   = 1 << 6,
   BUTTON3_DOWN   = 1 << 7
};

/*
 * Type: KeyCodes
 * --------------
 * This type defines the names of the key codes returned in a key event.
 */

enum KeyCodes {
   BACKSPACE_KEY = 8,
   TAB_KEY = 9,
   ENTER_KEY = 10,
   CLEAR_KEY = 12,
   ESCAPE_KEY = 27,
   PAGE_UP_KEY = 33,
   PAGE_DOWN_KEY = 34,
   END_KEY = 35,
   HOME_KEY = 36,
   LEFT_ARROW_KEY = 37,
   UP_ARROW_KEY = 38,
   RIGHT_ARROW_KEY = 39,
   DOWN_ARROW_KEY = 40,
   F1_KEY = 112,
   F2_KEY = 113,
   F3_KEY = 114,
   F4_KEY = 115,
   F5_KEY = 116,
   F6_KEY = 117,
   F7_KEY = 118,
   F8_KEY = 119,
   F9_KEY = 120,
   F10_KEY = 121,
   F11_KEY = 122,
   F12_KEY = 123,
   DELETE_KEY = 127,
   HELP_KEY = 156
};

/* Forward definitions */

class GWindowEvent;
class GActionEvent;
class GMouseEvent;
class GKeyEvent;
class GTimerEvent;
class GObject;

/*
 * Class: GEvent
 * -------------
 * This class is the root of the hierarchy for all events.
 *
 * The standard paradigm for using GEvent is illustrated by the following
 * program, which allows the user to draw lines on the graphics window:
 *
 *    int main() {
 *       GWindow gw;
 *       GLine *line;
 *       cout << "This program lets the user draw lines by dragging." << endl;
 *       while (true) {
 *          GMouseEvent e = waitForEvent(MOUSE_EVENT);
 *          if (e.getEventType() == MOUSE_PRESSED) {
 *             line = new GLine(e.getX(), e.getY(), e.getX(), e.getY());
 *             gw.add(line);
 *          } else if (e.getEventType() == MOUSE_DRAGGED) {
 *             line->setEndPoint(e.getX(), e.getY());
 *          }
 *       }
 *    }
 */

class GEvent {

public:

/*
 * Friend constructor: GEvent
 * Usage: GEvent event;
 * --------------------
 * Ensures that an event is properly initialized to a NULL event.
 */

   GEvent();

/*
 * Method: getEventClass
 * Usage: EventClassType eventClass = e.getEventClass();
 * -----------------------------------------------------
 * Returns the enumerated type constant indicating the class of the event.
 */

   EventClassType getEventClass() const;

/*
 * Method: getEventType
 * Usage: EventType type = e.getEventType();
 * -----------------------------------------
 * Returns the enumerated type constant corresponding to the specific event
 * type.
 */

   EventType getEventType() const;

/*
 * Method: getEventTime
 * Usage: double time = e.getEventTime();
 * --------------------------------------
 * Returns the system time in milliseconds at which the event occurred.  To
 * ensure portability among systems that represent time in different ways,
 * the StanfordCPPLib packages use type double to represent time, which is
 * always encoded as the number of milliseconds that have elapsed since
 * 00:00:00 UTC on January 1, 1970, which is the conventional zero point
 * for computer-based time systems.
 */

   double getEventTime() const;

/*
 * Method: getModifiers
 * Usage: int modifiers = e.getModifiers();
 * ----------------------------------------
 * Returns an integer whose bits indicate what modifiers are in effect.  To
 * check whether the shift key is down, for example, one could use the
 * following code:
 *
 *    if (e.getModifiers() & SHIFT_DOWN) ...
 */

   int getModifiers() const;

/*
 * Method: toString
 * Usage: string str = e.toString();
 * ---------------------------------
 * Converts the event to a human-readable representation of the event.
 */

   virtual std::string toString() const;

/*
 * Method: isValid
 * Usage: if (e.isValid()) ...
 * ---------------------------
 * Returns true if the event is valid.
 */

   bool isValid();

/* Private section */

/**********************************************************************/
/* Note: Everything below this point in the file is logically part    */
/* of the implementation and should not be of interest to clients.    */
/**********************************************************************/

/*
 * Method: setEventTime
 * Usage: e.setEventTime(time);
 * ----------------------------
 * Sets the event time field for this event.  The event system needs access
 * to this method, but conventional clients don't.
 */

   void setEventTime(double time);

/*
 * Method: setModifiers
 * Usage: e.setModifiers(modifiers);
 * ---------------------------------
 * Sets the modifiers field for this event.  The event system needs access
 * to this method, but conventional clients don't.
 */

   void setModifiers(int modifiers);

private:

/*
 * Instance variables
 * ------------------
 * Implementation note: All the variables from the subclasses are included
 * in the outer class to make it possible to convert between general events
 * and the various subclasses.  By keeping all event classes the same size,
 * this design avoids any issues of slicing off parts of the data during
 * such conversions.
 */

/* General events */

   EventClassType eventClass;
   int eventType;
   int modifiers;
   double eventTime;
   bool valid;
   std::string sourceKey;

/* Window, mouse, and key events */

   GWindowData *gwd;

/* Action events */

   GObject *source;
   std::string actionCommand;

/* Mouse events */

   double x;
   double y;

/* Key events */

   int keyChar;
   int keyCode;

/* Timer events */

   GTimerData *gtd;

/* Friend specifications */

friend class GWindowEvent;
friend class GActionEvent;
friend class GMouseEvent;
friend class GKeyEvent;
friend class GTimerEvent;

};

/*
 * Function: waitForClick
 * Usage: waitForClick();
 * ----------------------
 * Waits for a mouse click in any window, discarding any other events.
 */

void waitForClick();

/*
 * Function: waitForEvent
 * Usage: GEvent e = waitForEvent(mask);
 * -------------------------------------
 * Dismisses the process until an event occurs whose type is covered by the
 * event mask.  The mask parameter is a combination of the events of
 * interest.  For example, to wait for a mouse event or an action event,
 * clients can use the following call:
 *
 *    e = waitForEvent(MOUSE_EVENT + ACTION_EVENT);
 *
 * The mask parameter is optional.  If it is missing, waitForEvent accepts
 * any event.
 *
 * As a more sophisticated example, the following code is the canonical
 * event loop for an animated application that needs to respond to mouse,
 * key, and timer events:
 *
 *    GTimer timer(ANIMATION_DELAY_IN_MILLISECONDS);
 *    timer.start();
 *    while (true) {
 *       GEvent e = waitForEvent(TIMER_EVENT + MOUSE_EVENT + KEY_EVENT);
 *       switch (e.getEventClass()) {
 *        case TIMER_EVENT:
 *          takeAnimationStep();
 *          break;
 *        case MOUSE_EVENT:
 *          handleMouseEvent(GMouseEvent(e));
 *          break;
 *        case KEY_EVENT:
 *          handleKeyEvent(GKeyEvent(e));
 *          break;
 *       }
 *    }
 */

GEvent waitForEvent(int mask = ANY_EVENT);

/*
 * Function: getNextEvent
 * Usage: GEvent e = getNextEvent(mask);
 * -------------------------------------
 * Checks to see if there are any events of the desired type waiting on the
 * event queue.  If so, this function returns the event in exactly the same
 * fashion as waitForEvent; if not, getNextEvent returns an invalid event. 
 * The mask parameter is optional.  If it is missing, getNextEvent accepts
 * any event.
 */

GEvent getNextEvent(int mask = ANY_EVENT);

/*
 * Class: GWindowEvent
 * -------------------
 * This event subclass represents a window event.  Each GWindowEvent keeps
 * track of the event type (WINDOW_CLOSED, WINDOW_RESIZED) along with the
 * identity of the window.
 */

class GWindowEvent : public GEvent {

public:

/*
 * Constructor: GWindowEvent
 * Usage: GWindowEvent windowEvent(type, gw);
 * ------------------------------------------
 * Creates a GWindowEvent using the specified parameters.
 */

   GWindowEvent(EventType type, const GWindow & gw);

/*
 * Method: getGWindow
 * Usage: GWindow gw = e.getGWindow();
 * -----------------------------------
 * Returns the graphics window in which this event occurred.
 */

   GWindow getGWindow() const;

/*
 * Method: toString
 * Usage: string str = e.toString();
 * ---------------------------------
 * Converts the event to a human-readable representation of the event.
 */

   std::string toString() const;

/* Private section */

   GWindowEvent();
   GWindowEvent(GEvent e);

};

/*
 * Class: GActionEvent
 * -------------------
 * This event subclass represents an action event.  Action events are
 * generated by the classes in the GInteractor hierarchy.  As an example,
 * the following program displays a button that, when pushed, generates the
 * message Please do not press this button again (with thanks to Douglas
 * Adamss Hitchhikers Guide to the Galaxy):
 *
 *    int main() {
 *       GWindow gw;
 *       GButton *button = new GButton("RED");
 *       gw.addToRegion(button, "SOUTH");
 *       while (true) {
 *          GEvent e = waitForEvent(ACTION_EVENT | CLICK_EVENT);
 *          if (e.getEventType() == MOUSE_CLICKED) break;
 *          cout << "Please do not press this button again." << endl;
 *       }
 *       return 0;
 *    }
 */

class GActionEvent : public GEvent {

public:

/*
 * Constructor: GActionEvent
 * Usage: GActionEvent actionEvent(type, source, actionCommand);
 * -------------------------------------------------------------
 * Creates a GActionEvent using the specified parameters.
 */

   GActionEvent(EventType type, GObject *source, std::string actionCommand);

/*
 * Method: getSource
 * Usage: GObject *gobj = e.getSource();
 * -------------------------------------
 * Returns a pointer to the GObject that generated this event.
 */

   GObject *getSource() const;

/*
 * Method: getActionCommand
 * Usage: string cmd = e.getActionCommand();
 * -----------------------------------------
 * Returns the action command associated with this event.
 */

   std::string getActionCommand() const;

/*
 * Method: toString
 * Usage: string str = e.toString();
 * ---------------------------------
 * Converts the event to a human-readable representation of the event.
 */

   std::string toString() const;

/* Private section */

   GActionEvent();
   GActionEvent(GEvent e);

};

/*
 * Class: GMouseEvent
 * ------------------
 * This event subclass represents a mouse event.  Each mouse event records
 * the event type (MOUSE_PRESSED, MOUSE_RELEASED, MOUSE_CLICKED,
 * MOUSE_MOVED, MOUSE_DRAGGED) along with the coordinates of the event. 
 * Clicking the mouse generates three events in the following order:
 * MOUSE_PRESSED, MOUSE_RELEASED, MOUSE_CLICKED.
 *
 * As an example, the following program uses mouse events to let the user
 * draw rectangles on the graphics window.  The only complexity in this
 * code is the use of the library functions min and abs to ensure that the
 * dimensions of the rectangle are positive.
 *
 *    int main() {
 *       GWindow gw;
 *       cout << "This program lets the user draw rectangles." << endl;
 *       GRect *rect;
 *       double startX;
 *       double startY;
 *       while (true) {
 *          GMouseEvent e = waitForEvent();
 *          if (e.getEventType() == MOUSE_PRESSED) {
 *             startX = e.getX();
 *             startY = e.getY();
 *             rect = new GRect(startX, startY, 0, 0);
 *             rect->setFilled(true);
 *             gw.add(rect);
 *          } else if (e.getEventType() == MOUSE_DRAGGED) {
 *             double x = min(e.getX(), startX);
 *             double y = min(e.getY(), startY);
 *             double width = abs(e.getX() - startX);
 *             double height = abs(e.getY() - startY);
 *             rect->setBounds(x, y, width, height);
 *          }
 *       }
 *    }
 */

class GMouseEvent : public GEvent {

public:

/*
 * Constructor: GMouseEvent
 * Usage: GMouseEvent mouseEvent(type, gw, x, y);
 * ----------------------------------------------
 * Creates a GMouseEvent using the specified parameters.
 */

   GMouseEvent(EventType type, const GWindow & gw, double x, double y);

/*
 * Method: getGWindow
 * Usage: GWindow gw = e.getGWindow();
 * -----------------------------------
 * Returns the graphics window in which this event occurred.
 */

   GWindow getGWindow() const;

/*
 * Method: getX
 * Usage: double x = getX();
 * -------------------------
 * Returns the x coordinate at which the event occurred relative to the
 * window origin at the upper left corner of the window.
 */

   double getX() const;

/*
 * Method: getY
 * Usage: double y = getY();
 * -------------------------
 * Returns the y coordinate at which the event occurred relative to the
 * window origin at the upper left corner of the window.
 */

   double getY() const;

/*
 * Method: toString
 * Usage: string str = e.toString();
 * ---------------------------------
 * Converts the event to a human-readable representation of the event.
 */

   std::string toString() const;

/* Private section */

   GMouseEvent();
   GMouseEvent(GEvent e);

};

/*
 * Class: GKeyEvent
 * ----------------
 * This event subclass represents a key event.  Each key event records the
 * event type along with two representations of the key.  The getKeyChar
 * function is more generally useful and returns the character after taking
 * into account modifier keys.  The getKeyCode function returns an integer
 * identifying the key, which can be a function key as well as a standard
 * key.  The codes return by getKeyCode are listed in the KeyCodes
 * enumeration.
 */

class GKeyEvent : public GEvent {

public:

/*
 * Constructor: GKeyEvent
 * Usage: GKeyEvent keyEvent(type, gw, keyChar, keyCode);
 * ------------------------------------------------------
 * Creates a GKeyEvent using the specified parameters.
 */

   GKeyEvent(EventType type, const GWindow & gw, int keyChar, int keyCode);

/*
 * Method: getGWindow
 * Usage: GWindow gw = e.getGWindow();
 * -----------------------------------
 * Returns the graphics window in which this event occurred.
 */

   GWindow getGWindow() const;

/*
 * Method: getKeyChar
 * Usage: char ch = e.getKeyChar();
 * --------------------------------
 * Returns the character represented by the keystroke, taking the modifier
 * keys into account.  For example, if the user types the 'a' key with the
 * shift key down, getKeyChar will return 'A'.  If the key code in the
 * event does not correspond to a character, getKeyChar returns the null
 * character.
 */

   char getKeyChar() const;

/*
 * Method: getKeyCode
 * Usage: int key = getKeyCode();
 * ------------------------------
 * Returns the integer code associated with the key in the event.
 */

   int getKeyCode() const;

/*
 * Method: toString
 * Usage: string str = e.toString();
 * ---------------------------------
 * Converts the event to a human-readable representation of the event.
 */

   std::string toString() const;

/* Private section */

   GKeyEvent();
   GKeyEvent(GEvent e);

};

/*
 * Class: GTimerEvent
 * ------------------
 * This event subclass represents a timer event.  Timer events are
 * generated by a GTimer object, which produces a new event at a fixed
 * interval measured in milliseconds.  As an example, the following program
 * generates a timer event every two seconds, stopping when the user clicks
 * somewhere in the window:
 *
 *    int main() {
 *       cout << "This program generates timer events." << endl;
 *       GTimer timer(2000);
 *       timer.start();
 *       while (true) {
 *          GEvent e = waitForEvent(CLICK_EVENT | TIMER_EVENT);
 *          if (e.getEventType() == MOUSE_CLICKED) break;
 *          cout << "Timer ticked" << endl;
 *       }
 *       return 0;
 *    }
 */

class GTimerEvent : public GEvent {

public:

/*
 * Constructor: GTimerEvent
 * Usage: GTimerEvent timerEvent(type, timer);
 * -------------------------------------------
 * Creates a GTimerEvent for the specified timer.
 */

   GTimerEvent(EventType type, const GTimer & timer);

/*
 * Method: getGTimer
 * Usage: GTimer timer = e.getGTimer();
 * ------------------------------------
 * Returns the timer that generated this event.
 */

   GTimer getGTimer() const;

/*
 * Method: toString
 * Usage: string str = e.toString();
 * ---------------------------------
 * Converts the event to a human-readable representation of the event.
 */

   std::string toString() const;

/* Private section */

   GTimerEvent();
   GTimerEvent(GEvent e);

};

#endif