// -*-C++-*-
//  Copyright 1994, 1995
//  Maurice LeBrun			mjl@dino.ph.utexas.edu
//  Institute for Fusion Studies	University of Texas at Austin
//
//  Copyright (C) 2004 Maurice LeBrun
//  Copyright (C) 2016 Alan W. Irwin
//
//  This file is part of PLplot.
//
//  PLplot 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.
//
//  PLplot 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 PLplot; if not, write to the Free Software
//  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
//
//--------------------------------------------------------------------------
//
//  Contains declarations for Tcl "Matrix" command.
//  C functions that need access to the matrix data will need
//  to include this file.
//

#ifndef __TCLMATRIX_H__
#define __TCLMATRIX_H__

#include "plplot.h"
#include <tcl.h>

typedef PLFLT   Mat_float;

#if defined ( MSDOS )
typedef long    Mat_int;
#else
typedef int     Mat_int;
#endif

enum { TYPE_FLOAT, TYPE_INT };

// Arrays are column dominant (normal C ordering)
// Array elements are stored contiguously
// Require dimension <= 3 for simplicity

#define MAX_ARRAY_DIM    3

// Useful macros for index calculations

#define I3D( i, j, k )    k + matPtr->n[2] * ( I2D( i, j ) )
#define I2D( i, j )       j + matPtr->n[1] * ( I1D( i ) )
#define I1D( i )          i

// Matrix operator data

typedef struct
{
    int type;                    // Data type
    // It is tempting to declare the next 3 as size_t, but the second
    // limit on slices must be -1 for one particular case (negative
    // step with second limit of -1 corresponding to actual index of
    // 0) so it is better to keep all these as int to reduce casting.
    int        len;              // Total length of array
    int        dim;              // Number of dimensions
    int        n[MAX_ARRAY_DIM]; // Holds array length in each dimension
    int        tracing;          // Set if not persistent

    char       *name;            // Matrix operator name, malloc'ed

    Mat_float  *fdata;           // Floating point data, malloc'ed
    Mat_int    *idata;           // Integer data, malloc'ed

    Tcl_Interp *interp;          // Interpreter where command is installed

    // These do the put/get operations for each supported type

    void ( *put )( ClientData clientData, Tcl_Interp* interp, int index, const char *string );
    void ( *get )( ClientData clientData, Tcl_Interp* interp, int index, char *string );
    // number of indices that will be either put or get.  If indices is NULL, this
    // should be set to the product of the (MAX_ARRAY_DIM=3) values in the above n array.
    int nindices;
    // Array of those indices (malloc'ed if used, NULL if all raw indices
    // are used in the normal C [row-major] order without reference to the indices
    // array).
    int *indices;
} tclMatrix;

// Function prototypes

#ifdef __cplusplus
//--------------------------------------------------------------------------
// // Since C++ does not generally have a per-platform ABI the way C
// // does, we stick to a totally inline class declaration and
// // definition.  That way you don't have to keep a separate version of
// // libplplot*.a for each compiler you'd like to use.
//
// // Start by setting up some important macros.
//

#include <iostream>

#ifdef throw
#define TCL_NO_UNDEF
#endif

#ifndef throw
#ifdef __hpux
#if defined ( __GNUC__ ) || defined ( __lucid ) || defined ( __CENTERLINE__ ) \
    || defined ( CENTERLINE_CLPP )
#define NO_XCPT
#endif
#else
#define NO_XCPT
#endif

#ifdef NO_XCPT
#define try
#define throw( a )                                     \
    { cerr << "THROW: " << # a << " from " << __FILE__ \
           << " line " << __LINE__ << endl << flush; abort(); }
#define catch( a )    if ( 0 )
#define Throw
#else
#define Throw    throw
#endif
#endif

#define tMat_Assert( a, b )    if ( !( a ) )                   \
    { using namespace std;                                     \
      cerr << "Assertion " << # a << " failed in " << __FILE__ \
           << " at line " << __LINE__ << endl << flush;        \
      throw( b ); }

//--------------------------------------------------------------------------
// // class TclMatFloat
//
// // This class provides a convenient way to access the data of a
// // tclMatrix from within compiled code.  Someone should make clones of
// // this class for the other tclMatrix supported data types.
//--------------------------------------------------------------------------

class TclMatFloat {
    tclMatrix *matPtr;
public:
    TclMatFloat( tclMatrix * ptm )
        : matPtr( ptm )
    {
        tMat_Assert( matPtr->type == TYPE_FLOAT, "Type mismatch" );
    }

    int Dimensions() const
    {
        return matPtr->dim;
    }

    int dim_size( int d ) const
    {
        tMat_Assert( d < matPtr->dim, "Range error." );
        return matPtr->n[d];
    }

    void redim( int nx )
    {
        free( matPtr->fdata );
        matPtr->dim   = 1;
        matPtr->n[0]  = nx;
        matPtr->len   = nx;
        matPtr->fdata = (Mat_float *) malloc( matPtr->len *
            sizeof ( Mat_float ) );
    }

    void redim( int nx, int ny )
    {
        free( matPtr->fdata );
        matPtr->dim   = 2;
        matPtr->n[0]  = nx;
        matPtr->n[1]  = ny;
        matPtr->len   = nx * ny;
        matPtr->fdata = (Mat_float *) malloc( matPtr->len *
            sizeof ( Mat_float ) );
    }

    void redim( int nx, int ny, int nz )
    {
        free( matPtr->fdata );
        matPtr->dim   = 3;
        matPtr->n[0]  = nx;
        matPtr->n[1]  = ny;
        matPtr->n[2]  = nz;
        matPtr->len   = nx * ny * nz;
        matPtr->fdata = (Mat_float *) malloc( matPtr->len *
            sizeof ( Mat_float ) );
    }

    Mat_float& operator() ( int i )
    {
        tMat_Assert( matPtr->dim == 1, "Wrong number of indicies." );
        tMat_Assert( i >= 0 && i < matPtr->n[0],
            "Out of bounds reference" );

        return matPtr->fdata[i];
    }

    Mat_float& operator() ( int i, int j )
    {
        tMat_Assert( matPtr->dim == 2, "Wrong number of indicies." );
        tMat_Assert( i >= 0 && i < matPtr->n[0] &&
            j >= 0 && j < matPtr->n[1],
            "Out of bounds reference" );

        return matPtr->fdata[I2D( i, j )];
    }

    Mat_float& operator() ( int i, int j, int k )
    {
        tMat_Assert( matPtr->dim == 3, "Wrong number of indicies." );
        tMat_Assert( i >= 0 && i < matPtr->n[0] &&
            j >= 0 && j < matPtr->n[1] &&
            k >= 0 && k < matPtr->n[2],
            "Out of bounds reference" );

        return matPtr->fdata[I3D( i, j, k )];
    }
};

//--------------------------------------------------------------------------
// // class TclMatInt
//
// // This class provides a convenient way to access the data of a
// // tclMatrix from within compiled code.  This is just like TclMatFloat above,
// // but for ints.
//--------------------------------------------------------------------------

class TclMatInt {
    tclMatrix *matPtr;
public:
    TclMatInt( tclMatrix * ptm )
        : matPtr( ptm )
    {
        tMat_Assert( matPtr->type == TYPE_INT, "Type mismatch" );
    }

    int Dimensions() const
    {
        return matPtr->dim;
    }

    int dim_size( int d ) const
    {
        tMat_Assert( d < matPtr->dim, "Range error." );
        return matPtr->n[d];
    }

    void redim( int nx )
    {
        free( matPtr->idata );
        matPtr->dim   = 1;
        matPtr->n[0]  = nx;
        matPtr->len   = nx;
        matPtr->idata = (Mat_int *) malloc( matPtr->len * sizeof ( Mat_int ) );
    }

    void redim( int nx, int ny )
    {
        free( matPtr->idata );
        matPtr->dim   = 2;
        matPtr->n[0]  = nx;
        matPtr->n[1]  = ny;
        matPtr->len   = nx * ny;
        matPtr->idata = (Mat_int *) malloc( matPtr->len * sizeof ( Mat_int ) );
    }

    void redim( int nx, int ny, int nz )
    {
        free( matPtr->idata );
        matPtr->dim   = 3;
        matPtr->n[0]  = nx;
        matPtr->n[1]  = ny;
        matPtr->n[2]  = nz;
        matPtr->len   = nx * ny * nz;
        matPtr->idata = (Mat_int *) malloc( matPtr->len * sizeof ( Mat_int ) );
    }

    Mat_int& operator() ( int i )
    {
        tMat_Assert( matPtr->dim == 1, "Wrong number of indicies." );
        tMat_Assert( i >= 0 && i < matPtr->n[0],
            "Out of bounds reference" );

        return matPtr->idata[i];
    }

    Mat_int& operator() ( int i, int j )
    {
        tMat_Assert( matPtr->dim == 2, "Wrong number of indicies." );
        tMat_Assert( i >= 0 && i < matPtr->n[0] &&
            j >= 0 && j < matPtr->n[1],
            "Out of bounds reference" );

        return matPtr->idata[I2D( i, j )];
    }

    Mat_int& operator() ( int i, int j, int k )
    {
        tMat_Assert( matPtr->dim == 3, "Wrong number of indicies." );
        tMat_Assert( i >= 0 && i < matPtr->n[0] &&
            j >= 0 && j < matPtr->n[1] &&
            k >= 0 && k < matPtr->n[2],
            "Out of bounds reference" );

        return matPtr->idata[I3D( i, j, k )];
    }
};

#ifndef TCL_NO_UNDEF

#ifdef NO_XCPT
#undef NO_XCPT
#undef try
#undef throw
#undef Throw
#undef catch
#endif

#endif

#undef tMat_Assert

extern "C" {
//--------------------------------------------------------------------------
#endif

// Tcl package initialisation function

int PLDLLIMPEXP_TCLMAT Matrix_Init( Tcl_Interp* );

// This procedure is invoked to process the "matrix" Tcl command.

int
Tcl_MatrixCmd( ClientData clientData, Tcl_Interp *interp,
               int argc, const char **argv );

// Returns a pointer to the specified matrix operator's data.

tclMatrix PLDLLIMPEXP_TCLMAT *
Tcl_GetMatrixPtr( Tcl_Interp *interp, const char *matName );

// Some stuff for handling extension subcommands.

typedef int ( *tclMatrixXtnsnProc )( tclMatrix *pm, Tcl_Interp *interp,
                                     int argc, const char *argv[] );

typedef struct tclMatrixXtnsnDescr
{
    char *cmd;
    tclMatrixXtnsnProc cmdproc;
    struct tclMatrixXtnsnDescr *next;
} tclMatrixXtnsnDescr;

int PLDLLIMPEXP_TCLMAT Tcl_MatrixInstallXtnsn( const char *cmd, tclMatrixXtnsnProc proc );

#ifdef __cplusplus
}
#endif

#endif  // __TCLMATRIX_H__
