/*

 * Copyright 2011 Steven Gribble

 *

 *  This file is the solution to an exercise problem posed during

 *  one of the UW CSE 333 lectures (333exercises).

 *

 *  333exercises 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.

 *

 *  333exercises 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 333exercises.  If not, see <http://www.gnu.org/licenses/>.

 */



// Lecture 4 exercise 1



// Write and test a program that defines:

// - a new structured type Point

//   - represent it with floats for the x, y coordinate

// - a new structured type Rectangle

//   - assume its sides are parallel to the x-axis and y-axis

//   - represent it with the bottom-left and top-right Points

// - a function that computes/returns the area of a Rectangle

// - a function that tests whether a Point is in a Rectangle



#include <assert.h>  // needed for assert()



// let's use typedef to declare that "Point" is a new

// type, and that the meaning of a "Point" is the same

// as "struct Point_st { float x; float y; }"

typedef struct Point_st {

  float x;

  float y;

} Point;



// let's use typedef to declare that "Rectangle" is a new

// type, and that the meaning of a "Rectangle" is the same as

// "struct Rectangle_st { Point bottom_left; Point bottom_right; }"

typedef struct Rectangle_st {

  Point bottom_left;

  Point top_right;

} Rectangle;





// Declare a function prototype for RectangleArea.  We'll

// define the function somewhere below main().

//

// Arguments:   rect is the Rectangle whose area we'll compute.

// Returns:  the area of the rectangle, or -1.0 on error (i.e.,

//           the rectangle is ill-formed).

float RectangleArea(Rectangle rect);



// Declare a function prototype for PointInRect.  We'll

// define the function somwhere below main().

//

// Arguments: rect is a Rectangle, p is a Point.

// Returns: 1 if the point is in the Rectangle, 0 if

//          the point is not in the Rectangle, -1 if

//          the Rectangle is ill-formed.

int PointInRect(Rectangle rect, Point p);



// Here's a helper function that we use to make sure

// a rectangle is well-formed.

//

// Returns 1 if the rectangle is well-formed, 0 otherwise.

int IsRectSensible(Rectangle rect);



// Here's a helper function that tests for approximate

// equality of two floats.

#define EPSILON 0.00000001

int FloatEqualEpsilon(float x, float y);



// we'll write some test code in main.

int main(int argc, char **argv) {

  Rectangle bad_rect, line_rect, point_rect, good_rect;

  Point outside, border, inside;



  // make bad_rect's bottom_left be above and right of top_right

  bad_rect = (Rectangle) { {1, 1}, {0.1, 0.1} };



  // make line_rect be a line

  line_rect = (Rectangle) { {0.1, 0.1}, {0.1, 1} };



  // make zero_rect be a point

  point_rect = (Rectangle) { {0.1, 0.1}, {0.1, 0.1} };



  // make good_rect be nice

  good_rect = (Rectangle) { {0.1, 0.1}, {1, 1}};

  outside = (Point) {0, 0};

  border = (Point) {0.1, 0.5};

  inside = (Point) {0.5, 0.5};



  // our tests of the helper function

  assert(IsRectSensible(good_rect));

  assert(IsRectSensible(point_rect));

  assert(IsRectSensible(line_rect));

  assert(!IsRectSensible(bad_rect));



  // Our tests of RectangleArea.  Nasty issue: never

  // test a float for precise equality, since floats

  // are approximate representations.  Need to write our

  // own comparator that tests for a difference < epsilon.

  assert(FloatEqualEpsilon(RectangleArea(good_rect), 0.81));

  assert(FloatEqualEpsilon(RectangleArea(line_rect), 0.0));

  assert(FloatEqualEpsilon(RectangleArea(point_rect), 0.0));

  assert(FloatEqualEpsilon(RectangleArea(bad_rect), -1));



  // our tests of PointInRect

  assert(PointInRect(good_rect, inside));

  assert(PointInRect(good_rect, border));

  assert(PointInRect(line_rect, border));

  assert(!PointInRect(good_rect, outside));



  return EXIT_SUCCESS;

}



float RectangleArea(Rectangle rect) {

  float area;



  // let's make sure that rect (which is passed-by-value,

  // i.e., is a copy) is sensible.

  if (!IsRectSensible(rect))

    return -1;



  // area is length * width

  area = (rect.top_right.x - rect.bottom_left.x) *

    (rect.top_right.y - rect.bottom_left.y);

  assert(area >= 0.0);

  return area;

}



int PointInRect(Rectangle rect, Point p) {

  // let's make sure that rect is sensible.

  if (!IsRectSensible(rect))

    return -1;



  // check the x-axis

  if (p.x < rect.bottom_left.x)

    return 0;

  if (p.x > rect.top_right.x)

    return 0;



  // check the y-axis

  if (p.y < rect.bottom_left.y)

    return 0;

  if (p.y > rect.top_right.y)

    return 0;



  // it's in!

  return 1;

}



int IsRectSensible(Rectangle rect) {

  // make sure the bottom_left field is actually

  // below and to the left of the top_right field.

  // (it's also OK if they are precisely the same

  // point -- that's a zero-area rectangle.)

  if (rect.bottom_left.x > rect.top_right.x)

    return 0;



  if (rect.bottom_left.y > rect.top_right.y)

    return 0;



  return 1;

}



int FloatEqualEpsilon(float x, float y) {

  if (x > y) {

    if (x - y < EPSILON)

      return 1;

    return 0;

  }



  if (y - x < EPSILON)

    return 1;

  return 0;

}