#if !defined HAVE_ARRAY2D_H__
#define      HAVE_ARRAY2D_H__
// This file is part of the FXT library.
// Copyright (C) 2010 Joerg Arndt
// License: GNU General Public License version 3 or later,
// see the file COPYING.txt in the main directory.

#include "fxttypes.h"

#include "aux2/copy2d.h"
#include "aux2/copy2d.h"
#include "aux2/shift2d.h"
#include "aux2/reverse2d.h"
#include "aux2/rotate2d.h"
#include "aux2/transpose.h"
#include "aux2/scale2d.h"
#include "aux2/minmax2d.h"

#include "aux2/applyfunc2d.h"
#include "aux0/swap.h"

#include "jjassert.h"

//#include "aux0/swap.h"
#include "sort/minmaxmed23.h" // max2()


//#define PARANOIA  // define to enable for this file
#ifdef PARANOIA
#warning  'PARANOIA tests enabled in array2d.h'
//
// whether to make range checks on index of requested row
#define  ROW_ASSERTS   // define to catch access beyond size
#endif // PARANOIA


#define  OWN_ROW_PTR  1  // 0 (use supplied rowptr) or 1 (use own copy, default)
// with 1 transposition is always possible
// iff we have both contiguous mem and rows unpermuted
// with 0 transposition is impossible if nr != nc



#ifdef ROW_ASSERTS
#warning 'FYI: row range jjasserts for array2d are activated'
// aux2/array2d.cc:
void row_assert(ulong r, ulong nr);
#endif


template <typename Type>
class array2d
{
public:
    ulong nc_, nr_;  // #rows, #cols
    ulong n_;        // #elems (== #rows * #cols)
    ulong maxrc_;    // max(#rows, #cols)
    ulong minrc_;    // min(#rows, #cols)
    Type **rowp_;    // (pointer to) array of pointers to rows
    Type *f_;        // pointer to data if contiguous rows, else 0

protected:
    bool myrowpq_;  // whether rowp_ was allocated by constructor
    bool myfq_;     // whether f_ was allocated by constructor
    bool mcq_;      // whether rowp_[i+1]-rowp_[i] == nc_ for all i


public:
    array2d(ulong nr, ulong nc, Type *f=0);
    array2d(ulong nr, ulong nc, Type **rowp);
    ~array2d();

    ulong nelem()  const  { return n_; }
    ulong ncols()  const  { return nc_; }
    ulong nrows()  const  { return nr_; }

    Type get(ulong r, ulong c)  const  { return rowp_[r][c]; }
    void set(ulong r, ulong c, Type v)  { rowp_[r][c]=v; }

#ifdef  ROW_ASSERTS
    Type *row(ulong r) const  { row_assert(r,nr_);  return rowp_[r]; }
    Type *operator [] (ulong y) const  { row_assert(y,nr_); return rowp_[y]; }
#else
    Type *row(ulong r) const  { return rowp_[r]; }
    Type *operator [] (ulong y) const  { return rowp_[y]; }
#endif

    void setup_rowp()
    {
        rowp_[0] = f_;
        for (ulong i=1; i<nr_; ++i)  rowp_[i] = rowp_[i-1] + nc_;
    }

    void null()  { ::null(rowp_, nr_, nc_); }
    void fill(Type v)  { ::fill(rowp_, nr_, nc_, v); }

    Type min()  const  { return ::min((const Type **)rowp_, nr_, nc_); }
    Type max()  const  { return ::max((const Type **)rowp_, nr_, nc_); }
    void min_max(Type *mi, Type *ma)  const
    { ::min_max((const Type **)rowp_, nr_, nc_, mi, ma); }

    void linear_scale(Type mi, Type ma, Type nmi, Type nma)
    { ::linear_scale(rowp_, nr_, nc_, mi, ma, nmi, nma); }

    void linear_scale(Type nmi, Type nma)
    { ::linear_scale(rowp_, nr_, nc_, nmi, nma); }

    void log_scale(double xi, double xa, Type nma)
    { ::log_scale(rowp_, nr_, nc_, xi, xa, nma); }

    void apply_func(Type (*func)(Type))
    { for (ulong r=0; r<nr_; ++r)  ::apply_func(rowp_[r], nc_, func); }

    void shift_up(ulong r)  { ::shift_up(rowp_, nr_, nc_, r); }
    void shift_down(ulong r)  { ::shift_down(rowp_, nr_, nc_, r); }
    void shift_vert(long r)  { ::shift_vert(rowp_, nr_, nc_, r); }
    void shift_left(ulong r)  { ::shift_left(rowp_, nr_, nc_, r); }
    void shift_right(ulong r)  { ::shift_right(rowp_, nr_, nc_, r); }
    void shift_horiz(long r)  { ::shift_horiz(rowp_, nr_, nc_, r); }
    void shift_vh(long vert, long horiz)  { ::shift_vh(rowp_, nr_, nc_, vert, horiz); }

    void reverse_vert()  { ::reverse_vert(rowp_, nr_, nc_); }
    void reverse_horiz()  { ::reverse_horiz(rowp_, nr_, nc_); }

    void rotate_up(ulong r)  { ::rotate_up(rowp_, nr_, nc_, r); }
    void rotate_down(ulong r)  { ::rotate_down(rowp_, nr_, nc_, r); }
    void rotate_vert(long r)  { ::rotate_vert(rowp_, nr_, nc_, r); }
    void rotate_left(ulong r)  { ::rotate_left(rowp_, nr_, nc_, r); }
    void rotate_right(ulong r)  { ::rotate_right(rowp_, nr_, nc_, r); }
    void rotate_horiz(long r)    { ::rotate_horiz(rowp_, nr_, nc_, r); }

    void rotate_vh(long vert, long horiz)  { ::rotate_vh(rowp_, nr_, nc_, vert, horiz); }

    void zero2center()  { ::zero2center(rowp_, nr_, nc_); }

    void transpose();
};
// -------------------------

template <typename Type>
array2d<Type>::array2d(ulong nr, ulong nc, Type *f/*=0*/)
    : nc_(nc), nr_(nr), n_(nc*nr),
      maxrc_( ::max2(nr_, nc_) ),
      minrc_( ::min2(nr_, nc_) )
{
    if ( 0!=f )  // data supplied
    {
        f_ = f;
        myfq_ = false;
    }
    else
    {
        f_ = new Type[n_];
        myfq_ = true;
    }

    rowp_ = new Type*[maxrc_];  // max for transpose
    myrowpq_ = true;

    mcq_ = true;
    setup_rowp();
}
// -------------------------

template <typename Type>
array2d<Type>::array2d(ulong nr, ulong nc, Type **rowp)
    : nc_(nc), nr_(nr), n_(nc*nr),
      maxrc_( ::max2(nr_, nc_) ),
      minrc_( ::min2(nr_, nc_) )
{
    // test if supplied memory is contiguous:
    ulong r = 0;
    Type *p = rowp[0];
    while ( r<nr )
    {
        ++r;
        p += nc_;
        if ( p!=rowp[r] )  break;
    }

    if ( r==nr_ )
    {
        mcq_ = true;  // memory is contiguous
        f_ = rowp[0];
    }
    else
    {
        mcq_ = false;
        f_ = 0;
    }

#if  OWN_ROW_PTR == 0
    rowp_ = rowp;
    myrowpq_ = false;
#else
    rowp_ = new Type*[maxrc_];
    copy( rowp, nr ); // values beyond nr are undefined
    myrowpq_ = true;
#endif

    myfq_ = false;
}
// -------------------------

template <typename Type>
array2d<Type>::~array2d()
{
    if ( myrowpq_ )  delete [] rowp_;
    if ( myfq_ )  delete [] f_;
}
// -------------------------

template <typename Type1, typename Type2>
void copy(const array2d<Type1> &src, array2d<Type2> &dst)
{
    ::offset_copy((const Type1 **)src.rowp_, src.nr_, src.nc_,
                  dst.rowp_, dst.nr_, dst.nc_);
}
// -------------------------


template <typename Type1, typename Type2>
void copy(const array2d<Type1> &src, array2d<Type2> &dst,
          ulong coff, ulong roff)
{
    ::offset_copy((const Type1 **)src.rowp_, src.nr_, src.nc_,
                  dst.rowp_, dst.nr_, dst.nc_,
                  coff, roff);
}
// -------------------------


template <typename Type>
void transpose(const array2d<Type> &src, array2d<Type> &dst)
{
    // note: todo: check compatibility
    ::transpose((const Type *)src.f_, dst.f_, src.nr_, src.nc_);
}
// -------------------------

template <typename Type>
void array2d<Type>::transpose()
{
    ulong nr = nr_, nc = nc_;
    if ( nr==nc )  // trivial for square array
    {
//        ::transpose(rowp_, nr, nc);
        ::transpose_square(rowp_, nr);
    }
    else  // rectangular array ...  jjtodo: complete transpose for array2d
    {

        // FIXME: asserts in lib
#if  OWN_ROW_PTR == 0
        jjassert2( (myrowpq_!=0), "sorry, no transpose for "
                   "nonsquare array2d with borrowed row pointers.");
#endif

        if ( !mcq_ )
        {
            jjassert2(0, "sorry, no transpose for "
                      "nonsquare array2d with noncontiguous memory.");
        }

        ::transpose(f_, nr, nc);
        swap2(nr, nc);
        setup_rowp();
    }
}
// -------------------------



#endif  // !defined HAVE_ARRAY2D_H__
