/****************************************************************************
 * 
 *  MODULE:	r.terraflow
 *
 *  COPYRIGHT (C) 2007 Laura Toma
 *   
 *  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.
 *
 *****************************************************************************/


#ifndef WATER_H
#define WATER_H

/* watershed analysis related structures */

#include "types.h"
#include "plateau.h"
#include "nodata.h"
#include "genericWindow.h"

char directionSymbol(direction_type dir);

class labelElevTypePrintLabel;

/* ---------------------------------------------------------------------- */

class labelElevType : public ijBaseType {
  friend class labelElevTypePrintLabel;
 protected:
  elevation_type el;
  cclabel_type label;
 public:
  labelElevType() : label(LABEL_UNDEF) {};
  
  labelElevType(dimension_type gi,
		dimension_type gj,
		elevation_type gel,
		cclabel_type glabel) :
    ijBaseType(gi, gj), el(gel), label(glabel) {};
  
  cclabel_type getLabel() const { return label; };
  
  elevation_type getElevation() const { return el; };
  
  friend ostream& operator << (ostream& s, const labelElevType &p);

  static char *printLabel(const labelElevType &p);

  friend class printLabel;
};


class ijCmpLabelElevType {
public:
  static int compare(const labelElevType &a, const labelElevType &b) {
	return ijBaseType::compare(a, b);
  }
};


class labelCmpLabelElevType {
public:
  static int compare(const labelElevType &a, const labelElevType &b) {
	return a.getLabel() - b.getLabel();
	/* return (a.getLabel() == b.getLabel() ? 0
	   : (a.getLabel() < b.getLabel() ? -1 : 1)); */
  }
};


class labelElevTypePrintLabel {
 public:
  cclabel_type  operator()(const labelElevType &p) {
    return p.label;
  }
};

/* ---------------------------------------------------------------------- */
class waterCmpBoundaryType;
class elevCmpBoundaryType;
class boundaryCmpBoundaryType;
class ijCmpBoundaryType;

class boundaryType : public labelElevType {
  friend class waterCmpBoundaryType;
  friend class elevCmpBoundaryType;
  friend class boundaryCmpBoundaryType;
protected:
  cclabel_type label2;
public:
  boundaryType() : label2(LABEL_UNDEF) {};
  
  boundaryType(dimension_type gi,
	       dimension_type gj,
	       elevation_type gel,
	       cclabel_type glabel1,
	       cclabel_type glabel2) :
    labelElevType(gi, gj, gel, glabel1), label2(glabel2) {
    assert(glabel1 < glabel2);
  };

  boundaryType(labelElevType let, cclabel_type glabel2) : labelElevType(let) {
    if(let.getLabel() < glabel2) {
      label2 = glabel2;
    } else {
      label2 = label;
      label = glabel2;
    }
  };

  boundaryType(labelElevType let, elevation_type gel, cclabel_type glabel2)
	: labelElevType(let) {
	el = gel;
	if(let.getLabel() < glabel2) {
	  label2 = glabel2;
	} else {
	  label2 = label;
	  label = glabel2;
	}
  };
  bool isValid() const { return label2 != LABEL_UNDEF; }
  cclabel_type getLabel1() const { return getLabel(); }
  cclabel_type getLabel2() const { return label2; }
  friend ostream& operator << (ostream& s, const boundaryType &p);
  static char *print(const boundaryType &p);
};

class ijCmpBoundaryType {
public:
  static int compare(const boundaryType &a, const boundaryType &b) {
	return ijBaseType::compare(a, b);
  }
}
;
class waterCmpBoundaryType {
public:
  static int compare(const boundaryType &a, const boundaryType &b) {
	if(a.label < b.label) return -1;
	if(a.label > b.label) return 1;

	if(a.label2 < b.label2) return -1;
	if(a.label2 > b.label2) return 1;

	if(a.el < b.el) return -1;
	if(a.el > b.el) return 1;

	return 0;
  }
};

class elevCmpBoundaryType {
public:
  static int compare(const boundaryType &a, const boundaryType &b) {
	if(a.el < b.el) return -1;
	if(a.el > b.el) return 1;

	return 0;
  }
};

class boundaryCmpBoundaryType {
public:
  static int compare(const boundaryType &a, const boundaryType &b) {
	if(a.label < b.label) return -1;
	if(a.label > b.label) return 1;

	if(a.label2 < b.label2) return -1;
	if(a.label2 > b.label2) return 1;

	return 0;
  }
};

/* ---------------------------------------------------------------------- */

class fillPriority : public ijBaseType {
protected:
  elevation_type el;
  bfs_depth_type depth;
public:
  fillPriority() : ijBaseType(-1,-1), el(-1), depth(DEPTH_INITIAL) {};
  fillPriority(elevation_type gel,
			   bfs_depth_type gdepth,
			   dimension_type gi,
			   dimension_type gj) :
    ijBaseType(gi, gj), el(gel), depth(gdepth) {}

  friend int operator<(const fillPriority &a, const fillPriority &b);
  friend int operator<=(const fillPriority &a, const fillPriority &b);
  friend int operator>(const fillPriority &a, const fillPriority &b);
  friend int operator==(const fillPriority &a, const fillPriority &b);
  friend int operator!=(const fillPriority &a, const fillPriority &b);
  friend ostream& operator << (ostream& s, const fillPriority &p);
  static int compare(const fillPriority &a, const fillPriority &b);
  static int qscompare(const void *a, const void *b);
};

/* ---------------------------------------------------------------------- */

class fillPLabel : public fillPriority {
  cclabel_type label;
public:
  fillPLabel() : label(LABEL_UNDEF) {};
  fillPLabel(const fillPriority &gpriority, const cclabel_type glabel) :
	fillPriority(gpriority), label(glabel) {}
  fillPriority getPriority() const { return (fillPriority)(*this); }
  cclabel_type getLabel() const { return label; }
  /* static char *print(const fillPLabel &p); */
  boundaryType getBoundary(cclabel_type label2) {
	if(label < label2) {
	  return boundaryType(i, j, el, label, label2);
	} else {
	  return boundaryType(i, j, el, label2, label);
	}
  }
  friend ostream& operator << (ostream& s, const fillPLabel &p) {
	return s << "[" << (fillPriority)p << ", " << p.label << "]";
  }
};


/* ---------------------------------------------------------------------- */


class waterWindowBaseType {
public:
  elevation_type el;
  direction_type dir;
  bfs_depth_type depth;
  waterWindowBaseType() : el(nodataType::ELEVATION_NODATA), dir(0), 
	depth(DEPTH_INITIAL) {};
  waterWindowBaseType(elevation_type gel, 
					  direction_type gdir, bfs_depth_type gdepth) :
    el(gel), dir(gdir), depth(gdepth) {};
  friend int 
  operator==(const waterWindowBaseType &a, const waterWindowBaseType &b) {
    return (a.el == b.el) && (a.dir == b.dir) && (a.depth == b.depth);
  }
  
  friend ostream& operator << (ostream& s, const waterWindowBaseType &p) {
	return s << "[" 
			 << "el=" << p.el << ", "
			 << "dir=" << p.dir << ", "
			 << "depth=" << p.depth << "]";
  }
};

/* ---------------------------------------------------------------------- */


class waterType;
class waterGridType;
class waterType : public ijBaseType {
  friend int ijCmp_waterType(const waterType &a, const waterType &b);
  friend class waterGridType;
protected:
  direction_type dir;
  bfs_depth_type depth;
  cclabel_type label;
public:
  waterType() : label(LABEL_UNDEF) {};		      	/* needed to sort */
  waterType(dimension_type gi,
	    dimension_type gj,
	    direction_type gdir,
	    cclabel_type glabel=LABEL_UNDEF,
	    bfs_depth_type gdepth=DEPTH_INITIAL) :
    ijBaseType(gi, gj), dir(gdir), depth(gdepth), label(glabel) {
  }
  waterType(plateauType &data) : 
    ijBaseType(data.i, data.j),
    dir(data.dir), depth(DEPTH_INITIAL), label(data.cclabel) {
  };
  direction_type getDirection() const { return dir; }
  bfs_depth_type getDepth() const { return depth; }
  cclabel_type getLabel() const { return label; }
  static char *printLabel(const waterType &p);
  friend ostream& operator << (ostream& s, const waterType &p) {
    return s << "[waterType" << (ijBaseType)p
	     << ", dir=" << p.dir
	     << ", bfs=" << p.depth 
	     << ", lab=" << p.label << "]";
  }
};

class ijCmpWaterType {
public:
  static int compare(const waterType &a, const waterType &b) {
	return ijBaseType::compare(a, b);
  }
};

#if(0)
class waterType2direction_type {
public:
  direction_type operator()(waterType p) { return p.getDirection(); }
  direction_type operator()(direction_type p) { return p; }
};
#endif

/* ---------------------------------------------------------------------- */

class waterGridType : public waterWindowBaseType {
protected:
  cclabel_type label;
public:
  waterGridType() : label(LABEL_UNDEF) {};
  waterGridType(elevation_type gel, 
		direction_type gdir=DIRECTION_UNDEF,
		cclabel_type glabel=LABEL_UNDEF,
		bfs_depth_type gdepth=DEPTH_INITIAL) :
    waterWindowBaseType(gel, gdir, gdepth), label(glabel) {
  }
  waterGridType(elevation_type gel, const waterType &w) :
    waterWindowBaseType(gel, w.dir, w.depth), label(w.label) {}
  
  cclabel_type getLabel() const { return label; };
  void setLabel(cclabel_type lbl) { label=lbl; };
  friend ostream& operator << (ostream& s, const waterGridType &p) {
    return s << directionSymbol(p.dir);
  }
};


/* ---------------------------------------------------------------------- */

class packed8bit {
protected:
  unsigned char value;
public:  
  packed8bit() : value(0) {};
  void setBit(int k, int v=1) { value = (int) value | ((v?1:0)<<k); };
  void resetBit(int k) { value &= ~(0x1<<k); };
  int getBit(int k) const { return (value >> k) & 1; };
};

static int linear(int i, int j) {
  assert(i>=-1 && i<=1 && j>=-1 && j<=1);
  return ((i+1)*3+(j+1));  
}

static int norm(int k) {
  return (k<4 ? k : (k>4?k-1:8)); 
}

static int norm(int i, int j) { 
  return norm(linear(i,j)); 
}




class compressedWaterWindowBaseType : public ijBaseType {
protected:						/* 4 */
  bfs_depth_type depth;			/* 4 */
  elevation_type el[9];			/* 18 */
  direction_type dir;			/* 2 */
  /* we only need the depth if the elevation is the same. if so, the
   variation is bounded by +/-1. if a cell receives a label from a
   lower elevation (ie the center is lower than the cell), then the
   recipients depth must be 1 (it's on the edge). if the center is
   higher, then we have no interest in the cell. */
  unsigned short depth_delta;	/* 2 */
  packed8bit points;			/* 1 whether neighbor points to me */
  /* cells are numbered 0,1,2/3,4,5/6,7,8 */
  /* bits are 0,1,2,3,5,6,7,8 (skip 4) */
public:
  compressedWaterWindowBaseType() : depth(DEPTH_INITIAL),
				    dir(0) {
    for(int i=0; i<9; i++) { el[i] = nodataType::ELEVATION_NODATA; }
  };
  compressedWaterWindowBaseType(dimension_type gi,
				dimension_type gj,
				waterWindowBaseType *a, 
				waterWindowBaseType *b, 
				waterWindowBaseType *c);
  fillPriority getPriority() const;
  elevation_type getElevation(int k=4) const { return el[k]; };
  elevation_type getElevation(int i,int j) const{return el[linear(i,j)];};
  direction_type getDirection() const { return dir; };
  int drainsFrom(int i, int j) const { 
	/* whether we drain water from cell i,j */
	return points.getBit(norm(i, j));
  };
  bfs_depth_type getDepth() const { return depth; };
  bfs_depth_type getDepth(int k) const;
  bfs_depth_type getDepth(int i, int j) const { return getDepth(norm(i,j)); };
  void sanityCheck();
  
  friend ostream& operator<<(ostream& s, 
			     const compressedWaterWindowBaseType &p);
private:
  int computeDelta(waterWindowBaseType *center,
		   int index, waterWindowBaseType *p) const;
};



class compressedWaterWindowType : public compressedWaterWindowBaseType {
protected:
  cclabel_type label;
public:
  compressedWaterWindowType() : compressedWaterWindowBaseType(),
				label(LABEL_UNDEF) {};
  compressedWaterWindowType(dimension_type gi,
			    dimension_type gj,
			    cclabel_type glabel,
			    waterWindowBaseType *a, 
			    waterWindowBaseType *b, 
			    waterWindowBaseType *c) 
    : compressedWaterWindowBaseType(gi, gj, a, b, c), 
	label(glabel) {
    /* if nodata, labels are either depression labels, or undefined or
	   boundary (a pseudo depression). */
    assert(!(is_nodata(getElevation())) ||
	   (label==LABEL_BOUNDARY || label==LABEL_UNDEF));
  }
  cclabel_type getLabel() const { return label; };
  void setLabel(cclabel_type lbl) { label=lbl; };
  labelElevType getCenter() const;
  
  void sanityCheck();
  
  friend ostream& operator<<(ostream& s, const compressedWaterWindowType &p);
};

typedef compressedWaterWindowType waterWindowType;



class ijCmpWaterWindowType {
public:
  static int compare(const waterWindowType &a, const waterWindowType &b) {
	return ijBaseType::compare(a, b);
  }
};

class priorityCmpWaterWindowType {
public:
  static int compare(const waterWindowType &a, const waterWindowType &b) {
    return fillPriority::compare(a.getPriority(), b.getPriority());
  }
};



/* ********************************************************************** */

void 
createWaterWindows(AMI_STREAM<waterGridType> *mergedWaterStr, 
				   const dimension_type nrows, const dimension_type ncols,
				   AMI_STREAM<waterWindowType> *waterWindows);

/* ********************************************************************** */

void
generateWatersheds(AMI_STREAM<waterWindowType> **waterWindows,
				   const dimension_type nrows, const dimension_type ncols,
				   AMI_STREAM<labelElevType> *labeledWater, 
				   AMI_STREAM<boundaryType> *boundaryStr);

void
findBoundaries(AMI_STREAM<labelElevType> *labeledWater,
			   const dimension_type nrows, const dimension_type ncols,
			   AMI_STREAM<boundaryType> *boundaryStr);

/* ********************************************************************** */

#endif

