/****************************************************************************
 * 
 *  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 __3SCAN_H
#define __3SCAN_H

#include <iostream>

#include <grass/iostream/ami.h>
#include "types.h"


/* ********************************************************************** */
template<class T, class BASETYPE, class FUN>
void
scan3line(FUN &funobj, 
		  AMI_STREAM<T> *prev, 
		  AMI_STREAM<T> *crt, 
		  AMI_STREAM<T> *next,
		  BASETYPE nodata,
		  dimension_type i) {
  dimension_type j=0;
  AMI_err ae;
  BASETYPE a[3], b[3], c[3];
  T *tmp, center[2];
  int done;
  
  /* LOG_DEBUG_ID("scan3line: begin");
	 if(prev) LOG_DEBUG_ID(prev->sprint());
	 if(crt) LOG_DEBUG_ID(crt->sprint());
	 if(next) LOG_DEBUG_ID(next->sprint());
  */
  /* seek 0 */
  if (prev) {
    ae = prev->seek(0);
    assert(ae == AMI_ERROR_NO_ERROR);
  }
  assert(crt);
  ae = crt->seek(0);
  assert(ae == AMI_ERROR_NO_ERROR);
  if (next) {
    ae = next->seek(0);
    assert(ae == AMI_ERROR_NO_ERROR);
  }
  /* read first 2 elements of each line */
  /* read prev */
  a[0] = b[0] = c[0] = nodata;
  if (prev) {
    ae = prev->read_item(&tmp);
    assert(ae == AMI_ERROR_NO_ERROR);
    a[1] = *tmp;
    ae = prev->read_item(&tmp);
    assert(ae == AMI_ERROR_NO_ERROR);
    a[2] = *tmp;
  } else {
    a[1] = a[2] = nodata;
  }
  /* read crt */
  ae = crt->read_item(&tmp);
  assert(ae == AMI_ERROR_NO_ERROR);
  center[0] = *tmp;
  b[1] = *tmp;
  ae = crt->read_item(&tmp);
  assert(ae == AMI_ERROR_NO_ERROR);
  center[1] = *tmp;
  b[2] = *tmp;
  /* read next */
  if (next) {
    ae = next->read_item(&tmp);
    assert(ae == AMI_ERROR_NO_ERROR);
    c[1] = *tmp;
    ae = next->read_item(&tmp);
    assert(ae == AMI_ERROR_NO_ERROR);
    c[2] = *tmp;
  } else {
    c[1] = c[2] = nodata;
  }
  
  done = 0;
  do {
    funobj.processWindow(i, j, center[0], a, b, c); /* process current window*/
	
	/* slide one over */
    a[0] = a[1]; a[1] = a[2];
    b[0] = b[1]; b[1] = b[2];
	center[0] = center[1];
    c[0] = c[1]; c[1] = c[2];

    j++;
	
    /* read next item from crt and check for EOS */
    ae = crt->read_item(&tmp);
    if  (ae == AMI_ERROR_END_OF_STREAM) {
      done = 1;
      b[2] = nodata;
      center[1] = T();
    } else {
      assert(ae == AMI_ERROR_NO_ERROR);
      b[2] = *tmp;  
      center[1] = *tmp;
    }
    
    /* read next item from prev */
    if (prev) {
      ae = prev->read_item(&tmp);
      if (!done) {
	assert(ae == AMI_ERROR_NO_ERROR);
	a[2]=*tmp;
      } else {
	a[2] = nodata;
	assert(ae == AMI_ERROR_END_OF_STREAM);
      }
    } else {
      a[2] = nodata;
    }
    
    /* read next item from next */
    if (next) {
      ae = next->read_item(&tmp);
      if (!done) {
	assert(ae == AMI_ERROR_NO_ERROR);
	c[2] = *tmp;
      } else {
	c[2] = nodata;
	assert(ae == AMI_ERROR_END_OF_STREAM);
      }
    } else {
      c[2] = nodata;
    }
    
  } while (!done);
  
  /* write last window */
  funobj.processWindow(i, j, center[0], a, b, c); /* process current window */

  /* LOG_DEBUG_ID("scan3line: end"); */
  return;
}







/****************************************************************/
/* amis0 = data (T) in
 */
template<class T, class BASETYPE, class FUN>
void 
scan3(AMI_STREAM<T> &amis0, 
      const dimension_type nr, const dimension_type nc, BASETYPE nodata,
      FUN &funobj) {
  AMI_STREAM<T> *l_prev, *l_crt, *l_next;
  AMI_err ae;

  ae = amis0.seek(0);
  assert( ae == AMI_ERROR_NO_ERROR);  

  /* scan simultaneously 3 lines in the grid and fill in the windows
	 use a substream for each line to trigger prefetching */
  
  /* initialize first 2 lines */
  ae = amis0.new_substream(AMI_READ_STREAM, 0, nc-1, &l_crt);
  assert( ae == AMI_ERROR_NO_ERROR);
  ae = amis0.new_substream(AMI_READ_STREAM, nc, 2*nc-1, &l_next);
  assert( ae == AMI_ERROR_NO_ERROR);
  /* LOG_DEBUG_ID("substreams created"); */

  /* process lines, 3 at a time */
  l_prev = NULL;
  for (dimension_type i=0;i<nr;i++) {
    /* process current line */
    scan3line(funobj, l_prev, l_crt, l_next, nodata, i);
    
    /* advance lines */
    if (l_prev) delete l_prev;
    l_prev = l_crt;
    l_crt = l_next;
    if (i < nr-2) {
      ae = amis0.new_substream(AMI_READ_STREAM, (off_t)(i+2)*nc, (off_t)(i+3)*nc-1,
			      &l_next);
      assert(ae == AMI_ERROR_NO_ERROR);
    } else {
      l_next = NULL;
    }
  }
  if (l_prev) delete l_prev;
  assert(!l_crt);  
  /* LOG_DEBUG_ID("scan3: end");   */
  return;
}




/* ********************************************************************** */
template<class T>
T *
readLine(T *buf, AMI_STREAM<T> &str, const dimension_type len, 
		 const T &nodata) {
  AMI_err ae;
  buf[0] = nodata;
  buf[len+1] = nodata;
  for(dimension_type i=0; i<len; i++) {
	T *tmp;
	ae = str.read_item(&tmp);
	assert(ae==AMI_ERROR_NO_ERROR);
	buf[i+1] = *tmp;
  }
  return buf;
}


/* ********************************************************************** */
template<class T>
T *
setNodata(T *buf, const dimension_type len, const T &nodata) {
  for(dimension_type j=0; j<len+2; j++) {
	buf[j] = nodata;
  }
  return buf;
}



/* ********************************************************************** */
/* memoryScan
 *
 * calls fo.processWindow(i,j, a,b,c) once for each element of the
 * grid.  i,j are the coordinates in the grid.  a,b,c together point
 * to the grid surrounding the cell. (b[1] corresponds to cell i,j).
 * */
template<class T, class FUN>
void
memoryScan(AMI_STREAM<T> &str, 
		   const dimension_type nrows, const dimension_type ncols,
		   const T nodata,
		   FUN &fo) {
  T *a, *b, *c;
  T *buf[3];
  
  str.seek(0);
  
  assert(nrows > 1);
  assert((off_t)nrows * ncols == str.stream_len());
  buf[0] = new T[ncols+2];
  buf[1] = new T[ncols+2];
  buf[2] = new T[ncols+2];
  unsigned int k;
  a = setNodata(buf[0], ncols, nodata);
  b = readLine(buf[1], str, ncols, nodata);
  k = 2;
  for(dimension_type i=0; i<nrows-1; i++) {
	c = readLine(buf[k], str, ncols, nodata);
	for(dimension_type j=0; j<ncols; j++) {
	  fo.processWindow(i, j, a+j, b+j, c+j);  
	}
	k = (k+1)%3;
	a = b;
	b = c;
  }
  c = setNodata(buf[k], ncols, nodata);
  for(dimension_type j=0; j<ncols; j++) {
	fo.processWindow(nrows-1, j, a+j, b+j, c+j);  
  }
  delete [] buf[2];
  delete [] buf[1];
  delete [] buf[0];
}




/* ********************************************************************** */
/* this version uses two streams to make the windows 
 * FUN should provide a function:
 * processWindow(dimension_type i, dimension_type j,
 *   T1* a1, T1* b1, T1* c1, 
 *   T2* a2, T2* b2, T2* c2)
 */

template<class T1, class T2, class FUN>
void
memoryScan(AMI_STREAM<T1> &str1, AMI_STREAM<T2> &str2, 
		   const dimension_type nrows, const dimension_type ncols,
		   const T1 nodata1, const T2 nodata2,
		   FUN &fo) {
  T1 *a1, *b1, *c1;
  T1 *buf1[3];
  T2 *a2, *b2, *c2;
  T2 *buf2[3];
  
  str1.seek(0);
  str2.seek(0);

  assert(nrows > 1);
  assert((off_t)nrows * ncols == str1.stream_len());
  assert((off_t)nrows * ncols == str2.stream_len());
  buf1[0] = new T1[ncols+2];
  buf1[1] = new T1[ncols+2];
  buf1[2] = new T1[ncols+2];
  buf2[0] = new T2[ncols+2];
  buf2[1] = new T2[ncols+2];
  buf2[2] = new T2[ncols+2];
  unsigned int k;
  a1 = setNodata(buf1[0], ncols, nodata1);
  a2 = setNodata(buf2[0], ncols, nodata2);
  b1 = readLine(buf1[1], str1, ncols, nodata1);
  b2 = readLine(buf2[1], str2, ncols, nodata2);
  k = 2;
  for(dimension_type i=0; i<nrows-1; i++) {
	c1 = readLine(buf1[k], str1, ncols, nodata1);
	c2 = readLine(buf2[k], str2, ncols, nodata2);
	for(dimension_type j=0; j<ncols; j++) {
	  fo.processWindow(i, j, a1+j, b1+j, c1+j,
					   a2+j, b2+j, c2+j);  
	}
	k = (k+1)%3;
	a1 = b1;
	a2 = b2;
	b1 = c1;
	b2 = c2;
  }
  c1 = setNodata(buf1[k], ncols, nodata1);
  c2 = setNodata(buf2[k], ncols, nodata2);
  for(dimension_type j=0; j<ncols; j++) {
	fo.processWindow(nrows-1, j, a1+j, b1+j, c1+j,
					 a2+j, b2+j, c2+j);  
  }
  delete [] buf1[2];
  delete [] buf1[1];
  delete [] buf1[0];
  delete [] buf2[2];
  delete [] buf2[1];
  delete [] buf2[0];
}



#endif

