/*****************************************************************************
 * $CAMITK_LICENCE_BEGIN$
 *
 * CamiTK - Computer Assisted Medical Intervention ToolKit
 * (c) 2001-2025 Univ. Grenoble Alpes, CNRS, Grenoble INP - UGA, TIMC, 38000 Grenoble, France
 *
 * Visit http://camitk.imag.fr for more information
 *
 * This file is part of CamiTK.
 *
 * CamiTK is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License version 3
 * only, as published by the Free Software Foundation.
 *
 * CamiTK 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 Lesser General Public License version 3 for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * version 3 along with CamiTK.  If not, see <http://www.gnu.org/licenses/>.
 *
 * $CAMITK_LICENCE_END$
 ****************************************************************************/

#ifndef NUMPY_UTILS_H
#define NUMPY_UTILS_H

#include <pybind11/pybind11.h>
#include <pybind11/numpy.h>

namespace py = pybind11;

#include <CamiTKAPI.h>
#include <ImageComponent.h>
#include <MeshComponent.h>
#include <vtkImageData.h>
#include <vtkDataArray.h>
#include <vtkTransform.h>

namespace camitk {

/// Note about memory management on numpy vs VTK
/// In numpy, the indexing is [row, col] for two dimensions, or [slice, row, col] for three dimensions.
/// In VTK, the indexing is [col, row] for two dimensions and [col, row, slice] for three dimensions.
///
/// A numpy array has an ‘array-style’ == 'matrix-style' interface, and is indexed by [row, col] or [slice, row, col].
/// A vtkImageData has an ‘image-style’ interface, and is indexed by (x, y) or (x, y, z).
///
/// In numpy’s terms, VTK expects Fortran-order, while numpy defaults to C-order for its memory layout.
///
/// Beware that numpy also use shapes and strides to define the way it accesses the element memory:
/// - shape is the number of elements in all dimensions
/// - stride is the number of bytes that separate two consecutive elements in each dimension
/// Memory is basically always a 1D array.
///
/// For instance if the memory contains (raw pointer): 0 1 2 3 4 5
/// That can represent two different "images":
//
///              image 1                                 image 2
///    ^      +-----+-----+              ^         +-----+-----+-----+
///    |      |  0  |  1  |              |         |  0  |  1  |  2  |
/// shape[0]  +-----+-----+           shape[0]     +-----+-----+-----+
///    |      |  2  |  3  |              |         |  3  |  4  |  5  |
///    |      +-----+-----+              v         +-----+-----+-----+
///    |      |  4  |  5  |                        <-----shape[1]---->
///    v      +-----+-----+
///           <--shape[1]-->
///
/// For image 1: shape[0] is 3, shape[1] is 2
/// For image 2: shape[0] is 2, shape[1] is 3
///
/// Elements are stored using itemsize bytes (char = 1 byte, default int = 8 bytes = 64 bits)
/// numpy also uses stride to iterate in the data structure. Strides are expressed in bytes.
///
/// stride for 2D images is composed by
/// - stride[0] = number of bytes to jump to the next row
/// - stride[1] = number of bytes to jump to the next column
///
/// For image 1 : stride[0] = 2 * itemsize, stride[1] = itemsize
/// For image 2 : stride[0] = 3 * itemsize, stride[1] = itemsize
///
/// But beware when numpy is asked to transpose an array, it does NOT modify the
/// memory storage/layout, it just modifies the stride and shape values
///
/// for instance image3 = np.transpose(image1) = image2, all have the same raw memory representation
///
/// CamiTK conversion is able to take all these cases into account.
///
/// But you have to be extra-careful when mixing numpy with other python image libraries like nibabel,
/// because they may transpose the array in order to use image-style indexing with the arrays.

/// convert a vtkImageData to a numpy array
py::array vtkImageDataToNumpy(vtkSmartPointer<vtkImageData> image);

/// Get the spacing between voxels of a vtkImageData as a python tuple (e.g. (1.5, 1.5, 2.0))
py::array getVtkImageDataSpacing(vtkSmartPointer<vtkImageData> image);

/// Convert a VTKPointSet array to a numpy array.
py::array vtkPointSetToNumpy(vtkSmartPointer<vtkPointSet> pointSet);

/// Convert a numpy array to a vtkImageData
vtkSmartPointer<vtkImageData> numpyToVTKImageData(const pybind11::array& numpyArray);

/// Create a new camitk::ImageComponent from a numpy array and an optional voxel spacing array.
camitk::ImageComponent* newImageComponentFromNumpy(const py::array& numpyArray, const std::string& name, py::object spacingObj = py::none());

/// Convert a numpy array of 3D coordinates to vtkPoints
vtkSmartPointer<vtkPoints> numpyToVtkPoints(py::array_t < double, py::array::c_style | py::array::forcecast > points_array);

/// Convert an array of points (3D coordinates) and an array of cells (3 indices of the points for triangles, 2 indices for lines, 4 for quads, tetrahedrons are not supported) into a vtkPointSet (e.g. a triangle mesh). Only vktPolyData are supported for now.
vtkSmartPointer<vtkPointSet> numpyToVtkPointSet(py::array_t < double, py::array::c_style | py::array::forcecast > points_array, py::array_t < vtkIdType, py::array::c_style | py::array::forcecast > polys_array = py::array());

/// Create a camitk::MeshComponent from an array of points (3D coordinates) and an array of triangles (3 index of the points)
camitk::MeshComponent* newMeshComponentFromNumpy(const std::string& name, py::array_t < double, py::array::c_style | py::array::forcecast > points_array, py::array_t < vtkIdType, py::array::c_style | py::array::forcecast > polys_array = py::array());

/// Create a numpy array from a vtkDataArray (useful for data associated to points/cells in a mesh). Int/Float/Double are supported
py::array vtkDataArrayToNumpy(vtkSmartPointer<vtkDataArray> array);

/// Create a vtkDataArray from a numpy array (usefull to associate data to points/cells of a mesh). Supports Int/Float/Double
vtkSmartPointer<vtkDataArray> numpyToVtkDataArray(py::array array);

/// Create a vtkTransform from a numpy array (the method checks that array is really a 4x4 array)
vtkSmartPointer<vtkTransform> numpyToVtkTransform(py::array array);

} // namespace camitk

#endif // NUMPY_UTILS_H