/* CartesianSystem.doc declares class CartesianSystem,
 *  a class for graphical display using the 
 *  Carnegie-Mellon University Graphics library.
 *
 *  Copyright Joel Adams, July 2001, All rights reserved.
 *
 * Note: many methods have an int parameter to define the drawing color,
 *       and the valid colors are defined in CMUgraphics' file 127Graphics.h:
 *       RED, GREEN, BLUE, YELLOW, PINK, PURPLE, BROWN, AQUA,
 *       WHITE, BLACK, DKGREY, LTBLUE, ALMOND, LTGREY, TAN, DKGREEN
 */

#ifndef CARTESIAN_SYSTEM
#define CARTESIAN_SYSTEM
                                      // CMU Graphics library headers
#include "Graphics.h"                 //   window + ops 
#include "127Graphics.h"              //   Colors
#include <iomanip>                    // setprecision()
#include <cassert>                    // assert()
using namespace std;

const int AXIS_MARGIN       = 16,     // pixels btwn axis endpoints and window edge
          AXIS_GAP          = 50,     // pixels between axis hash-marks
          DEFAULT_PRECISION = 3;      // #decimal digits on axis labels

          
typedef double FunctionOfX(double);   // the kind of function drawFunction() draws

class CartesianSystem : public window
{
 public:
   CartesianSystem(bool axisVisible = true);
   CartesianSystem(double minX, double minY, 
                   double maxX, double maxY, 
                   bool axisVisible = true);
   
   void setPenWidth(int width);
   void setFont(int size, int style, int family);
 
   void clear(int bgColor = WHITE, bool axisVisible = true);  
   void drawAxis(int axisColor = BLACK, int penSize = 1);
   void drawLine(double x0, double y0, double x1, double y1, int aColor = BLACK);
   void drawPixel(double x, double y, int aColor = BLACK);
   void drawRectangle(double x0, double y0, double x1, double y1,
                       int color = BLACK, drawstyle dsStyle = FILLED, 
                       int iWidth = 0, int iHeight = 0);
   void drawCircle(double x, double y, double itsRadius, 
                       int color = BLACK, drawstyle dsStyle = FILLED);
   void drawEllipse(double x0, double y0, double x1, double y1,
                    int aColor = BLACK, drawstyle dsStyle = FILLED);
   void drawArc(double x, double y, double itsRadius, 
                    int startAtDegrees, int arcDegrees, 
                    int aColor = BLACK, drawstyle dsStyle = FRAME);
   void drawString(double x, double y, const string& aString, int aColor = BLACK);
   void drawNumber(double x, double y, double aNumber, int color = BLACK);
   void drawFunction(FunctionOfX aFunction, int color = BLACK);
   
   void waitForMouseClick();
   void waitForMouseClick(double& x, double& y, int& column, int& row);
   void getMousePosition(double& x, double& y, int& column, int& row);
   void getKeyPress(char& aKey);
   
   double minX() const;
   double minY() const;
   double maxX() const;
   double maxY() const;
   
   double deltaX() const;
   double deltaY() const;
   
   int xToColumn(double x) const;
   int yToRow(double y) const;
   double columnToX(int column) const;
   double rowToY(int row) const;
   int distanceToPixels(double distance) const;
   
   int getWidthInPixels();
   int getHeightInPixels();
   
 private:
 
   void initialize(double minX, double minY, 
                   double maxX, double maxY, 
                   bool axisVisible);
   void setSizeDependentAttributes();

   double xMin, yMin,                  // our lower-left corner
          xMax, yMax,                  // our upper-right corner
          myColumnsPerX, myRowsPerY,   // mapping variables
          xInterval, yInterval,        // distance btwn axis hash-marks
          myXRange, myYRange;          // range of each dimension
   int    myRows, myColumns,           // # of USEABLE rows, columns
          myLastRow,                   // index of last row
          myDecimalDigits;             // precision for axis labels
   bool   myAxisVisible;               // draw axis (true) or not (false)?
};

inline CartesianSystem::CartesianSystem(bool axisVisible)
{
  initialize(-4.0, -2.0, 4.0, 2.0, axisVisible);   // some default settings
}

inline CartesianSystem::CartesianSystem(double minX, double minY, 
                                         double maxX, double maxY,
                                         bool axisVisible)
{
  initialize(minX, minY, maxX, maxY, axisVisible);
}

inline void CartesianSystem::setPenWidth(int width)
{
  assert(width > 0);
  SetPen(width);
}

inline void CartesianSystem::setFont(int size, int style, int family)
{
  SetFont(size, style, family);
}

inline void CartesianSystem::drawLine(double x0, double y0, double x1, double y1,
                                       int aColor)
{
  SetBrush(aColor);
  DrawLine(xToColumn(x0), yToRow(y0), xToColumn(x1), yToRow(y1));
}

inline void CartesianSystem::drawPixel(double x, double y, int aColor)
{
  SetBrush(aColor);
  DrawPixel(xToColumn(x), yToRow(y));
}

inline void CartesianSystem::drawRectangle(double x0, double y0, double x1, double y1,
                                            int aColor, drawstyle dsStyle, 
                                            int iWidth, int iHeight)
{
  SetBrush(aColor);
  DrawRectangle(xToColumn(x0), yToRow(y0), xToColumn(x1), yToRow(y1),
                  dsStyle, iWidth, iHeight);
}

inline int CartesianSystem::distanceToPixels(double distance) const
{
   return int(distance * abs(xToColumn(1) - xToColumn(0))); //  an ugly hack
}

inline void CartesianSystem::drawCircle(double x, double y, double radius, 
                                         int aColor, drawstyle dsStyle)
{
  SetBrush(aColor);
  DrawCircle(xToColumn(x), yToRow(y), 
               distanceToPixels(radius), 
             	dsStyle);
}

inline void CartesianSystem::drawEllipse(double x0, double y0, double x1, double y1,
                                            int aColor, drawstyle dsStyle)
{
  SetBrush(aColor);
  DrawEllipse(xToColumn(x0), yToRow(y0), xToColumn(x1), yToRow(y1), dsStyle);
}


inline void CartesianSystem::drawString(double x, double y, 
                                        const string& aString, int aColor)
{
  SetBrush(aColor);
  DrawString(xToColumn(x), yToRow(y), aString.data());
}

inline void CartesianSystem::drawArc(double x, double y, double radius, 
                                      int startAtDegrees, int arcDegrees, 
                                      int aColor, drawstyle dsStyle)
{
  SetBrush(aColor);
  DrawArc(xToColumn(x), yToRow(y), distanceToPixels(radius),
           startAtDegrees, arcDegrees, dsStyle);
} 

inline int CartesianSystem::getWidthInPixels()
{
  return GetWidth();
}

inline int CartesianSystem::getHeightInPixels()
{
  return GetHeight();
}

inline void CartesianSystem::waitForMouseClick()
{
  int column, row;
  WaitMouseClick(column, row);
}

inline void CartesianSystem::waitForMouseClick(double& x, double& y,
                                                int& column, int& row)
{
  WaitMouseClick(column, row);
  x = columnToX(column);
  y = rowToY(row);
}

inline void CartesianSystem::getMousePosition(double& x, double& y,
                                               int& column, int& row)
{
  GetMouseCoord(column, row);
  x = columnToX(column);
  y = rowToY(row);
}

inline void CartesianSystem::getKeyPress(char& key)
{
  GetKeyPress(key);
}

inline int CartesianSystem::xToColumn(double x) const
{
  return AXIS_MARGIN + std::round((x-xMin) * myColumns / myXRange);
}

inline int CartesianSystem::yToRow(double y) const
{
  return AXIS_MARGIN + myLastRow - std::round((y - yMin) * myRows / myYRange);
}

inline double CartesianSystem::columnToX(int column) const
{
  return xMin + (column - AXIS_MARGIN) * myXRange / myColumns;
}


inline double CartesianSystem::rowToY(int row) const
{
  return yMin - (row - AXIS_MARGIN - myLastRow) * myYRange / myRows;
}


inline double CartesianSystem::deltaX() const
{
  return myXRange / myColumns;
}

inline double CartesianSystem::deltaY() const
{
  return myYRange / myRows;
}

inline double CartesianSystem::minX() const
{
  return xMin;
}

inline double CartesianSystem::minY() const
{
  return yMin;
}

inline double CartesianSystem::maxX() const
{
  return xMax;
}

inline double CartesianSystem::maxY() const
{
  return yMax;
}


#endif