/*****
* bbox.h
* Andy Hammerlindl 2002/06/06
*
* Stores a rectangle that encloses a drawing object.
*****/

#ifndef BBOX_H
#define BBOX_H

#include "pair.h"
#include "settings.h"

namespace camp {

template<class T>
inline T min(T a, T b)
{
 return (a < b) ? a : b;
}

template<class T>
inline T max(T a, T b)
{
 return (a > b) ? a : b;
}

// The box that encloses a path
struct bbox {
 bool empty;
 double left;
 double bottom;
 double right;
 double top;

 // Start bbox about the origin
 bbox()
   : empty(true), left(0.0), bottom(0.0), right(0.0), top(0.0)
 {
 }

 bbox(double left, double bottom, double right, double top)
   : empty(false), left(left), bottom(bottom), right(right), top(top)
 {
 }

 // Start a bbox with a point
 bbox(const pair& z)
   : empty(false), left(z.getx()), bottom(z.gety()),
     right(z.getx()), top(z.gety())
 {
 }

 bool nonempty() const {
   return !empty;
 }

 // Add a point to a bbox
 bbox add(const pair& z)
 {
   double x = z.getx(), y = z.gety();

   if (empty) {
     left = right = x;
     top = bottom = y;
     empty = false;
   }
   else {
     if (x < left)
       left = x;
     else if (x > right)
       right = x;
     if (y < bottom)
       bottom = y;
     else if (y > top)
       top = y;
   }

   return *this;
 }

 // Add a point to a nonempty bbox
 void addnonempty(const pair& z)
 {
   double x = z.getx(), y = z.gety();
   if (x < left)
     left = x;
   else if (x > right)
     right = x;
   if (y < bottom)
     bottom = y;
   else if (y > top)
     top = y;
 }

 // Add a point to a nonempty bbox, updating bounding times
 void addnonempty(const pair& z, bbox& times, double t)
 {
   double x = z.getx(), y = z.gety();

   if (x < left) {
     left = x;
     times.left = t;
   }
   else if (x > right) {
     right = x;
     times.right = t;
   }
   if (y < bottom) {
     bottom = y;
     times.bottom = t;
   }
   else if (y > top) {
     top = y;
     times.top = t;
   }
 }

 bbox operator+= (const pair& z)
 {
   add(z);
   return *this;
 }

 bbox operator*= (double x)
 {
   left *= x;
   right *= x;
   top *= x;
   bottom *=x;
   return *this;
 }

 // Add two bounding boxes
 friend bbox operator+ (const bbox& b1, const bbox& b2)
 {
   if (b1.empty)
     return b2;
   else if (b2.empty)
     return b1;
   else
     return bbox(min(b1.left, b2.left),
                 max(b1.right, b2.right),
                 min(b1.bottom, b2.bottom),
                 max(b1.top, b2.top));
 }

 // Add one bounding box to another
 void add(const bbox& b)
 {
   if (this->empty)
     *this = b;
   else if (!b.empty) {
     left = min(left, b.left);
     right = max(right, b.right);
     bottom = min(bottom, b.bottom);
     top = max(top, b.top);
   }
 }

 bbox operator+= (const bbox& b)
 {
   add(b);
   return *this;
 }

 void clip(const bbox& b) {
   if(this->empty) return;
   left = max(left, b.left);
   right = min(right, b.right);
   bottom = max(bottom, b.bottom);
   top = min(top, b.top);
   if(left > right || bottom > top)
     *this=bbox();
 }

 bbox shift(const pair& p) const {
   bbox b=*this;
   b.left += p.getx();
   b.right += p.getx();
   b.bottom += p.gety();
   b.top += p.gety();
   return b;
 }

 pair Min() const {
   return pair(left,bottom);
 }

 pair Max() const {
   return pair(right,top);
 }

 double diameter() {
   return (Max()-Min()).length();
 }

 bbox LowRes() const
 {
   return bbox(floor(left),floor(bottom),ceil(right),ceil(top));
 }

 friend ostream& operator<< (ostream& out, const bbox& b)
 {
   out << b.left << " " << b.bottom << " " << b.right << " " << b.top;
   return out;
 }
};

// Add results of both bounding boxes, say for a path and a pen.
inline bbox pad(bbox b1, bbox b2)
{
 if (b1.empty)
   return b2;
 else if (b2.empty)
   return b1;
 else {
   bbox b;
   b.empty = false;
   b.left = b1.left + b2.left;
   b.right = b1.right + b2.right;
   b.top = b1.top + b2.top;
   b.bottom = b1.bottom + b2.bottom;
   return b;
 }
}

inline bbox svgbbox(const bbox& B, pair shift=pair(0,0))
{
 bbox b=B;
 double height=b.top-b.bottom;
 double threshold=12.0*settings::tex2ps;
 if(height < threshold) {
   double offset=threshold-height;
   b.top += offset;
   b.bottom += offset;
 }
 return b.shift(pair(1.99*settings::cm,1.9*settings::cm)+shift);
}

} // namespace camp

GC_DECLARE_PTRFREE(camp::bbox);

#endif