/* 
 * XEvil(TM) Copyright (C) 1994,2000 Steve Hardt and Michael Judge
 * http://www.xevil.com
 * satan@xevil.com
 *
 * 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 2 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, the file "gpl.txt"; if not, write to the Free
 * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
 * 02111-1307, USA, or visit http://www.gnu.org.
 */

// "area.h" 

#ifndef AREA_H
#define AREA_H

#if X11
	#ifndef NO_PRAGMAS
	#pragma interface
	#endif
#endif


// Include Files
#include "utils.h"
#include "coord.h"
#include "streams.h"


// Defines
#define AR_WSQUARES_MAX 20
enum ARshape {AR_RECT,AR_UNDEFINED};
enum ARsig {AR_NO_SIG, AR_CLOSE, AR_FAILURE, AR_BAD_SHAPE};



// Data structures
struct TouchingListItem {
  Loc list[AR_WSQUARES_MAX];
  int num;
};

struct TouchingList {
  TouchingListItem r,dn,l,up; 
};


class Area;
class Avoid;



// Class Declarations
class Area {
 public:
  Size operator - (const Area &) const;
  /* EFFECTS: Subtract areas according to their upper left corners. */
  
  Boolean operator == (const Area &) const;
  
  Area operator + (const Size &) const;
  Area operator - (const Size &) const;
  /* EFFECTS: Return new Area with pos shifted by shift. */
  

  Area() {shape = AR_UNDEFINED;}
  /* EFFECTS: Create undefined area. */

  Area(const Pos &p,const Size &s) {shape = AR_RECT,pos = p,size = s;}
  Area(ARshape sh,const Pos &p,const Size &s) {assert(sh == AR_RECT);  
      shape = AR_RECT; pos = p; size = s; }
  /* REQUIRES: c is AR_RECT. */
  /* EFFECTS: Creates a rectangle area at position p of size s. */

  Area(ARshape c,const Loc &loc,const Dim &dim);
  /* REQUIRES: c is AR_RECT. */
  /* EFFECTS: Creates a rectangle area at loc and dim. */

  Area(InStreamP);
  void read(InStreamP);
  static int get_write_length()
    {return Pos::get_write_length() + Size::get_write_length();}
  void write(OutStreamP) const;

  Boolean is_defined() const {return shape != AR_UNDEFINED;}

  void get_rect(Pos &p,Size &s) const 
    {assert (shape == AR_RECT); p = pos; s = size;}

  /* MODIFIES: p,s */

  Pos get_pos() const 
    {assert(shape == AR_RECT); return pos;}
  Size get_size() const
    {assert(shape == AR_RECT); return size;}
  
  Box get_box() const;
  /* EFFECTS: Returns the box of wsquares that contains the area. */

  void set_pos(const Pos &p) 
	{assert(shape == AR_RECT); pos = p;}
  void set_size(const Size &s)
    {assert(shape == AR_RECT); size = s;}

  void wsquares(Loc list[AR_WSQUARES_MAX],int &nitems) const;
  /* MODIFIES: list, nitems */
  /* EFFECTS: After completion, the first nitems of list contain the locations
     of all the wsquares covered by the area.  The initial values of list 
     and nitems are ignored.  wsquares are not necessarily inside the world. */

  Boolean overlap(const Area &) const;
  /* EFFECTS: Returns True if *this overlaps with r, False otherwise. */

  Boolean overlap(const Loc &loc) const;
  /* EFFECTS: Returns True if loc overlaps with any of the area.  Returns 
     False otherwise. */

  Boolean overlap(const Box &box) const;
  /* EFFECTS: Returns True if box overlaps with any of the area.  Returns 
     False otherwise. */

  Boolean overlap(const Pos &pos) const;

  Loc middle_wsquare() const;
  /* EFFECTS: Returns the location of the middle wsquare in the area. */

  ARsig avoid_wsquare(Avoid &avoid,const Loc &loc) const;
  /* MODIFIES: avoid */
  /* EFFECTS: Returns AR_NO_SIG if the wsquare at loc does not overlap with 
     the area.  If loc overlaps in the area, but not in a nearby area, returns
     AR_CLOSE and sets avoid to be the offsets necessary to make the area no 
     longer touch the wsquare. */

  int avoid_wsquare_dir(const Loc &loc,Dir dir) const;
  /* EFFECTS: Returns the amount *this will have to be shited in dir to avoid 
     loc. */

  int avoid_area_dir(const Area &area,Dir dir) const;
  /* EFFECTS: Returns the amount *this will have to be shited in dir to avoid 
      area. */

  Size avoid(const Area &area) const;
  /* EFFECTS: Returns the smallest offset necessary to move *this in order to
     avoid area. */

  Size avoid_no_up(const Area &) const;
  /* EFFECTS: Like avoid(), but will not avoid in the up direction. */

  Pos adjacent_rect(const Size &s,Dir dir) const;
  /* EFFECTS: Return the upper left corner of the rectangle of size s that
     touches *this such that the direction from *this to the rectangle is 
     dir. */

  Pos get_middle() const{  assert(shape == AR_RECT);
              return Pos(pos.x+(size.width>>1),pos.y+(size.height>>1));}
  /* EFFECTS: Returns the middle of the area. */

  Boolean touches(const Area &other,Touching touching) const;
  /* EFFECTS: Does other touch this on this's side specified by touching. */

  void touching_wsquares(TouchingList &list) const;
  /* MODIFIES: list */
  /* EFFECTS: Sets list.{r,dn,l,up} to be the lists of all wsquares that are exactly 
     touching the area.  The directions are set from the area to the touching 
     wsquare.  I.e. A loc listed under list.r would be touching the 
     right side of the area.  The list are ordered such that the smallest 
     row and column indexes come first.  I.e. top->bottom and left->right. */

  void edge_wsquares(TouchingList &list) const;
  /* MODIFIES: list */
  /* EFFECTS: Like touching_wsquares except squares on edge don't have to be 
      exactly touching.  They might just overlap area.  I.e. returns 
      Area::wsquares() - {all squares entirely inside the Area} */
      
  Boolean corner_offset(Size &offset,Loc list[3],Loc &inner,Dir corner) const;
 /* MODIFIES: offset, list, inner */
 /* EFFECTS: If the corner of the Area in the given direction covers 3 wsquares, set offset
	  to be the amount to shift the Area so it is touching on the corner, set list to be the 3 
	  covered wsquares, set inner to be the wsquare of the inside of the corner,
	  and return True.  Else return False.  corner is one of 
	  {CO_DN_R,CO_DN_L,CO_UP_L,CO_UP_R}. */

 void shift(const Size &offset)
    {assert(shape == AR_RECT); pos = pos + offset;}
  /* EFFECTS: Shift the area by offset. */

  void set_middle(const Pos &pos);
  /* EFFECTS: Move the area so that pos will be the middle. */

  Dir dir_to(const Pos &pos) const;
  Dir dir_to(const Area &area) const;
  /* EFFECTS: Returns the direction from this to pos or to area. 
     Will return one of {CO_R .. CO_UP_R, CO_air}.  I.e. 8 directions or 
     CO_air. */

  Area combine(const Area &a) const;
  /* EFFECTS: Returns minimum sized rectangular area that contains a and 
     this. */

  Area wsquare_allign(const Size &offset) const;
  /* EFFECTS: For each dimension of offset that is non-zero, wsquare_allign
	 makes sure that the returned Area is wsquare_alligned in that dimension.
	 I.e. the edge of the Area in that direction will be on a wsquare boundary.
	 The returned area will not cover any wsquares that weren't already 
	 covered. */
  
  Boolean wsquare_alligned() const;
  /* EFFECTS: Is the Area alligned in all four directions with wsquares? */
  
  Area grow_by(int delta) const;
  /* EFFECTS: Increase area by delta on all four sides.  I.e. size will increase by
     2*delta in both directions. */
  
  Size clip_top(int val);
  /* REQUIRES: Don't clip everything away. */
  /* EFFECTS: Clip top to be <= val.  Return offset (width,height) such that height is the
     amount that was clipped away. */
  
  Size clip_left(int val);
  /* REQUIRES: Don't clip everything away. */
  /* EFFECTS: Clip left to be <= val.  Return offset (width,height) such that width is the
     amount that was clipped away. */

  Size clip(const Size &size);
  /* REQUIRES: Don't clip everything away. */
  /* EFFECTS: Clip this to clipper.  Return False if nothing left. 
     In the second version, clipper is an area of size size with pos (0,0). */


 private:
  ARshape shape;
  Pos pos;
  Size size;
};



class Avoid {
friend class Area;

public:    
  void maximize(const Avoid &avoid);
  /* EFFECTS: Set all directions of *this to be to max of *this and avoid. */

  Size offset_rank(int rank = 0) const;
  /* REQUIRES: 0 <= rank < 4 */
  /* EFFECTS: Return the offset corresponding to the rankth minimum direction
     of *this using one dimensions.  E.g. rank = 0 gives the minimum.  rank
     = 1 gives the second minimum, etc. */
  
  Size offset_dir(Dir d) const;
  /* REQUIRES: d in {CO_R, CO_DN, CO_L, CO_UP} */
  /* EFFECTS: Return the offset corresponding to d. */

  int get_dir(Dir d) const;
  /* REQUIRES: d in {CO_R, CO_DN, CO_L, CO_UP} */
  /* EFFECTS: Returns the offset in the specified direction. */


private:
  /* Direction from area to wsquare.  All are positive.  r and dn are 
     therefore the negative of the corresponding offsets. */
  int r,dn,l,up; 
  // Note: A bit counter-intuitive.
};



// All values are world (unstretched) coordinates.
class ViewportRegion {
  friend class ViewportInfo;

public:
  ViewportRegion() {};
  /* NOTE: Make the fucking compiler happy. */

  Boolean visibleOverlap(const Area &) const;
  /* EFFECTS: Does the area overlap the visible region of the  viewport? */

  Boolean peripheralOverlap(const Area &) const;
  /* EFFECTS: Does the area overlap the peripheral region of the viewport, 
     including the visible subset of that. */     

  void grow_by(int val);
  /* EFFECTS: Grow the ViewportRegion by val, visible and peripheral. */


private:
  ViewportRegion(const Area &a); 
  /* EFFECTS: V_AREA constructor. */

  ViewportRegion(const Pos &periphCenter,int periphRadius,
                 const Area &visibleArea);
  /* EFFECTS: V_DISK constructor. */


  enum {V_AREA,V_DISK};
  char type;
  Area area;   // both, for V_DISK this gives the inner,visible area
  Pos pos;     // for V_DISK
  int radius;  // for V_DISK
};
typedef ViewportRegion *ViewportRegionP;



// A Factory for generating ViewportRegions, given a Physical to follow.
//
// All values are world (unstretched) coordinates.
class ViewportInfo {
public:
  ViewportInfo() {}
  /* EFFECTS: So we can create an uninitialized object to call
     ViewportInfo::read(). */

  ViewportInfo(int radius,const Size &visibleSize);
  /* EFFECTS: Create smooth scroll ViewportInfo. */
  
  ViewportInfo(int radius,const Size &logicalSize,const Size &extraSize);
  /* EFFECTS: Create non-smooth scroll ViewportInfo. */

  void read(InStreamP in);
  static int get_write_length();
  void write(OutStreamP out) const;

  ViewportRegion get_viewport_for(const Pos &pos) const;
  /* EFFECTS: The factory's creation method.  pos is the center of the
     Physical to follow. */

  Dim get_viewport_dim() const;
  /* REQUIRES: Viewport dimensions must be multiples of wsquares. */
  /* EFFECTS: Get the dimensions of a viewport in wsquares. */


private:
  Boolean smoothScroll; // both
  Size logicalSize;     // both
  Size extraSize;       // if !smoothScroll
  int radius;           // both
};
typedef ViewportInfo *ViewportInfoP;



// An interface to lazily provide a ViewportInfo object.
class IViewportInfo {
public:
  virtual ViewportInfo get_info() = 0;
};

#endif



