/**********************************************************************
 * File:        rect.h  (Formerly box.h)
 * Description: Bounding box class definition.
 * Author:      Phil Cheatle
 *
 * (C) Copyright 1991, Hewlett-Packard Ltd.
 ** Licensed under the Apache License, Version 2.0 (the "License");
 ** you may not use this file except in compliance with the License.
 ** You may obtain a copy of the License at
 ** http://www.apache.org/licenses/LICENSE-2.0
 ** Unless required by applicable law or agreed to in writing, software
 ** distributed under the License is distributed on an "AS IS" BASIS,
 ** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 ** See the License for the specific language governing permissions and
 ** limitations under the License.
 *
 **********************************************************************/

#ifndef RECT_H
#define RECT_H

#include "points.h"     // for ICOORD, FCOORD
#include "scrollview.h" // for ScrollView, ScrollView::Color
#include "tesstypes.h"  // for TDimension
#include "tprintf.h"    // for tprintf

#include <tesseract/export.h> // for DLLSYM

#include <algorithm> // for std::max, std::min
#include <cmath>     // for std::ceil, std::floor
#include <cstdint>   // for INT16_MAX
#include <cstdio>    // for FILE
#include <string>    // for std::string

namespace tesseract {

class TESS_API TBOX { // bounding box
public:
  TBOX()
      : // empty constructor making a null box
      bot_left(INT16_MAX, INT16_MAX)
      , top_right(-INT16_MAX, -INT16_MAX) {}

  TBOX(                  // constructor
      const ICOORD pt1,  // one corner
      const ICOORD pt2); // the other corner

  //*********************************************************************
  // TBOX::TBOX()  Constructor from 4 integer values.
  //  Note: It is caller's responsibility to provide values
  //        in the right order.
  //*********************************************************************
  TBOX( // constructor
      TDimension left, TDimension bottom, TDimension right, TDimension top)
      : bot_left(left, bottom), top_right(right, top) {}

  TBOX( // box around FCOORD
      const FCOORD pt);

  bool null_box() const { // Is box null
    return ((left() >= right()) || (top() <= bottom()));
  }

  bool operator==(const TBOX &other) const {
    return bot_left == other.bot_left && top_right == other.top_right;
  }

  TDimension top() const { // coord of top
    return top_right.y();
  }
  void set_top(int y) {
    top_right.set_y(y);
  }

  TDimension bottom() const { // coord of bottom
    return bot_left.y();
  }
  void set_bottom(int y) {
    bot_left.set_y(y);
  }

  TDimension left() const { // coord of left
    return bot_left.x();
  }
  void set_left(int x) {
    bot_left.set_x(x);
  }

  TDimension right() const { // coord of right
    return top_right.x();
  }
  void set_right(int x) {
    top_right.set_x(x);
  }
  int x_middle() const {
    return (bot_left.x() + top_right.x()) / 2;
  }
  int y_middle() const {
    return (bot_left.y() + top_right.y()) / 2;
  }

  const ICOORD &botleft() const { // access function
    return bot_left;
  }

  ICOORD botright() const { // ~ access function
    return ICOORD(top_right.x(), bot_left.y());
  }

  ICOORD topleft() const { // ~ access function
    return ICOORD(bot_left.x(), top_right.y());
  }

  const ICOORD &topright() const { // access function
    return top_right;
  }

  TDimension height() const { // how high is it?
    if (!null_box()) {
      return top_right.y() - bot_left.y();
    } else {
      return 0;
    }
  }

  TDimension width() const { // how high is it?
    if (!null_box()) {
      return top_right.x() - bot_left.x();
    } else {
      return 0;
    }
  }

  int32_t area() const { // what is the area?
    if (!null_box()) {
      return width() * height();
    } else {
      return 0;
    }
  }

  // Pads the box on either side by the supplied x,y pad amounts.
  // NO checks for exceeding any bounds like 0 or an image size.
  void pad(int xpad, int ypad) {
    ICOORD pad(xpad, ypad);
    bot_left -= pad;
    top_right += pad;
  }

  void move_bottom_edge( // move one edge
      const TDimension y) { // by +/- y
    bot_left += ICOORD(0, y);
  }

  void move_left_edge(   // move one edge
      const TDimension x) { // by +/- x
    bot_left += ICOORD(x, 0);
  }

  void move_right_edge(  // move one edge
      const TDimension x) { // by +/- x
    top_right += ICOORD(x, 0);
  }

  void move_top_edge(    // move one edge
      const TDimension y) { // by +/- y
    top_right += ICOORD(0, y);
  }

  void move(              // move box
      const ICOORD vec) { // by vector
    bot_left += vec;
    top_right += vec;
  }

  void move(              // move box
      const FCOORD vec) { // by float vector
    bot_left.set_x(static_cast<TDimension>(std::floor(bot_left.x() + vec.x())));
    // round left
    bot_left.set_y(static_cast<TDimension>(std::floor(bot_left.y() + vec.y())));
    // round down
    top_right.set_x(static_cast<TDimension>(std::ceil(top_right.x() + vec.x())));
    // round right
    top_right.set_y(static_cast<TDimension>(std::ceil(top_right.y() + vec.y())));
    // round up
  }

  void scale(          // scale box
      const float f) { // by multiplier
    // round left
    bot_left.set_x(static_cast<TDimension>(std::floor(bot_left.x() * f)));
    // round down
    bot_left.set_y(static_cast<TDimension>(std::floor(bot_left.y() * f)));
    // round right
    top_right.set_x(static_cast<TDimension>(std::ceil(top_right.x() * f)));
    // round up
    top_right.set_y(static_cast<TDimension>(std::ceil(top_right.y() * f)));
  }
  void scale(             // scale box
      const FCOORD vec) { // by float vector
    bot_left.set_x(static_cast<TDimension>(std::floor(bot_left.x() * vec.x())));
    bot_left.set_y(static_cast<TDimension>(std::floor(bot_left.y() * vec.y())));
    top_right.set_x(static_cast<TDimension>(std::ceil(top_right.x() * vec.x())));
    top_right.set_y(static_cast<TDimension>(std::ceil(top_right.y() * vec.y())));
  }

  // rotate doesn't enlarge the box - it just rotates the bottom-left
  // and top-right corners. Use rotate_large if you want to guarantee
  // that all content is contained within the rotated box.
  void rotate(const FCOORD &vec) { // by vector
    bot_left.rotate(vec);
    top_right.rotate(vec);
    *this = TBOX(bot_left, top_right);
  }
  // rotate_large constructs the containing bounding box of all 4
  // corners after rotating them. It therefore guarantees that all
  // original content is contained within, but also slightly enlarges the box.
  void rotate_large(const FCOORD &vec);

  bool contains( // is pt inside box
      const FCOORD pt) const;

  bool contains( // is box inside box
      const TBOX &box) const;

  bool overlap( // do boxes overlap
      const TBOX &box) const;

  bool major_overlap( // do boxes overlap more than half
      const TBOX &box) const;

  // Do boxes overlap on x axis.
  bool x_overlap(const TBOX &box) const;

  // Return the horizontal gap between the boxes. If the boxes
  // overlap horizontally then the return value is negative, indicating
  // the amount of the overlap.
  int x_gap(const TBOX &box) const {
    return std::max(bot_left.x(), box.bot_left.x()) - std::min(top_right.x(), box.top_right.x());
  }

  // Return the vertical gap between the boxes. If the boxes
  // overlap vertically then the return value is negative, indicating
  // the amount of the overlap.
  int y_gap(const TBOX &box) const {
    return std::max(bot_left.y(), box.bot_left.y()) - std::min(top_right.y(), box.top_right.y());
  }

  // Do boxes overlap on x axis by more than
  // half of the width of the narrower box.
  bool major_x_overlap(const TBOX &box) const;

  // Do boxes overlap on y axis.
  bool y_overlap(const TBOX &box) const;

  // Do boxes overlap on y axis by more than
  // half of the height of the shorter box.
  bool major_y_overlap(const TBOX &box) const;

  // fraction of current box's area covered by other
  double overlap_fraction(const TBOX &box) const;

  // fraction of the current box's projected area covered by the other's
  double x_overlap_fraction(const TBOX &box) const;

  // fraction of the current box's projected area covered by the other's
  double y_overlap_fraction(const TBOX &box) const;

  // Returns true if the boxes are almost equal on x axis.
  bool x_almost_equal(const TBOX &box, int tolerance) const;

  // Returns true if the boxes are almost equal
  bool almost_equal(const TBOX &box, int tolerance) const;

  TBOX intersection( // shared area box
      const TBOX &box) const;

  TBOX bounding_union( // box enclosing both
      const TBOX &box) const;

  // Sets the box boundaries to the given coordinates.
  void set_to_given_coords(int x_min, int y_min, int x_max, int y_max) {
    bot_left.set_x(x_min);
    bot_left.set_y(y_min);
    top_right.set_x(x_max);
    top_right.set_y(y_max);
  }

  void print() const { // print
    tprintf("Bounding box=(%d,%d)->(%d,%d)\n", left(), bottom(), right(), top());
  }
  // Appends the bounding box as (%d,%d)->(%d,%d) to a string.
  void print_to_str(std::string &str) const;

#ifndef GRAPHICS_DISABLED
  void plot(                  // use current settings
      ScrollView *fd) const { // where to paint
    fd->Rectangle(bot_left.x(), bot_left.y(), top_right.x(), top_right.y());
  }

  void plot(                                  // paint box
      ScrollView *fd,                         // where to paint
      ScrollView::Color fill_colour,          // colour for inside
      ScrollView::Color border_colour) const; // colour for border
#endif
  // Writes to the given file. Returns false in case of error.
  bool Serialize(FILE *fp) const;
  bool Serialize(TFile *fp) const;

  // Reads from the given file. Returns false in case of error.
  // If swap is true, assumes a big/little-endian swap is needed.
  bool DeSerialize(bool swap, FILE *fp);
  bool DeSerialize(TFile *fp);

  friend TBOX &operator+=(TBOX &, const TBOX &);
  // in place union
  friend TBOX &operator&=(TBOX &, const TBOX &);
  // in place intersection

private:
  ICOORD bot_left;  // bottom left corner
  ICOORD top_right; // top right corner
};

/**********************************************************************
 * TBOX::TBOX()  Constructor from 1 FCOORD
 *
 **********************************************************************/

inline TBOX::TBOX(  // constructor
    const FCOORD pt // floating centre
) {
  bot_left =
      ICOORD(static_cast<TDimension>(std::floor(pt.x())), static_cast<TDimension>(std::floor(pt.y())));
  top_right =
      ICOORD(static_cast<TDimension>(std::ceil(pt.x())), static_cast<TDimension>(std::ceil(pt.y())));
}

/**********************************************************************
 * TBOX::contains()  Is point within box
 *
 **********************************************************************/

inline bool TBOX::contains(const FCOORD pt) const {
  return ((pt.x() >= bot_left.x()) && (pt.x() <= top_right.x()) && (pt.y() >= bot_left.y()) &&
          (pt.y() <= top_right.y()));
}

/**********************************************************************
 * TBOX::contains()  Is box within box
 *
 **********************************************************************/

inline bool TBOX::contains(const TBOX &box) const {
  return (contains(box.bot_left) && contains(box.top_right));
}

/**********************************************************************
 * TBOX::overlap()  Do two boxes overlap?
 *
 **********************************************************************/

inline bool TBOX::overlap( // do boxes overlap
    const TBOX &box) const {
  return ((box.bot_left.x() <= top_right.x()) && (box.top_right.x() >= bot_left.x()) &&
          (box.bot_left.y() <= top_right.y()) && (box.top_right.y() >= bot_left.y()));
}

/**********************************************************************
 * TBOX::major_overlap()  Do two boxes overlap by at least half of the smallest?
 *
 **********************************************************************/

inline bool TBOX::major_overlap( // Do boxes overlap more that half.
    const TBOX &box) const {
  int overlap = std::min(box.top_right.x(), top_right.x());
  overlap -= std::max(box.bot_left.x(), bot_left.x());
  overlap += overlap;
  if (overlap < std::min(box.width(), width())) {
    return false;
  }
  overlap = std::min(box.top_right.y(), top_right.y());
  overlap -= std::max(box.bot_left.y(), bot_left.y());
  overlap += overlap;
  if (overlap < std::min(box.height(), height())) {
    return false;
  }
  return true;
}

/**********************************************************************
 * TBOX::overlap_fraction()  Fraction of area covered by the other box
 *
 **********************************************************************/

inline double TBOX::overlap_fraction(const TBOX &box) const {
  double fraction = 0.0;
  if (this->area()) {
    fraction = this->intersection(box).area() * 1.0 / this->area();
  }
  return fraction;
}

/**********************************************************************
 * TBOX::x_overlap()  Do two boxes overlap on x-axis
 *
 **********************************************************************/

inline bool TBOX::x_overlap(const TBOX &box) const {
  return ((box.bot_left.x() <= top_right.x()) && (box.top_right.x() >= bot_left.x()));
}

/**********************************************************************
 * TBOX::major_x_overlap()  Do two boxes overlap by more than half the
 *                          width of the narrower box on the x-axis
 *
 **********************************************************************/

inline bool TBOX::major_x_overlap(const TBOX &box) const {
  TDimension overlap = box.width();
  if (this->left() > box.left()) {
    overlap -= this->left() - box.left();
  }
  if (this->right() < box.right()) {
    overlap -= box.right() - this->right();
  }
  return (overlap >= box.width() / 2 || overlap >= this->width() / 2);
}

/**********************************************************************
 * TBOX::y_overlap()  Do two boxes overlap on y-axis
 *
 **********************************************************************/

inline bool TBOX::y_overlap(const TBOX &box) const {
  return ((box.bot_left.y() <= top_right.y()) && (box.top_right.y() >= bot_left.y()));
}

/**********************************************************************
 * TBOX::major_y_overlap()  Do two boxes overlap by more than half the
 *                          height of the shorter box on the y-axis
 *
 **********************************************************************/

inline bool TBOX::major_y_overlap(const TBOX &box) const {
  TDimension overlap = box.height();
  if (this->bottom() > box.bottom()) {
    overlap -= this->bottom() - box.bottom();
  }
  if (this->top() < box.top()) {
    overlap -= box.top() - this->top();
  }
  return (overlap >= box.height() / 2 || overlap >= this->height() / 2);
}

/**********************************************************************
 * TBOX::x_overlap_fraction() Calculates the horizontal overlap of the
 *                            given boxes as a fraction of this boxes
 *                            width.
 *
 **********************************************************************/

inline double TBOX::x_overlap_fraction(const TBOX &other) const {
  int low = std::max(left(), other.left());
  int high = std::min(right(), other.right());
  int width = right() - left();
  if (width == 0) {
    int x = left();
    if (other.left() <= x && x <= other.right()) {
      return 1.0;
    } else {
      return 0.0;
    }
  } else {
    return std::max(0.0, static_cast<double>(high - low) / width);
  }
}

/**********************************************************************
 * TBOX::y_overlap_fraction() Calculates the vertical overlap of the
 *                            given boxes as a fraction of this boxes
 *                            height.
 *
 **********************************************************************/

inline double TBOX::y_overlap_fraction(const TBOX &other) const {
  int low = std::max(bottom(), other.bottom());
  int high = std::min(top(), other.top());
  int height = top() - bottom();
  if (height == 0) {
    int y = bottom();
    if (other.bottom() <= y && y <= other.top()) {
      return 1.0;
    } else {
      return 0.0;
    }
  } else {
    return std::max(0.0, static_cast<double>(high - low) / height);
  }
}

} // namespace tesseract

#endif
