// Copyright (c) 2005-2008  INRIA Sophia-Antipolis (France).
//               2008 GeometryFactory
// All rights reserved.
//
// This file is part of CGAL (www.cgal.org)
//
// $URL$
// $Id$
// SPDX-License-Identifier: LGPL-3.0-or-later OR LicenseRef-Commercial
//
//
// Author(s)     : Laurent Rineau, Pierre Alliez

#ifndef CGAL_IMAGE_3_H
#define CGAL_IMAGE_3_H

#include <CGAL/disable_warnings.h>

#include <CGAL/basic.h>
#include <CGAL/array.h>

#include <boost/shared_ptr.hpp>
#include <boost/format.hpp>
#include <boost/unordered_set.hpp>
#include <CGAL/ImageIO.h>
#include <CGAL/function_objects.h>

#include <limits>
#include <set>
#include <cstdlib>

#if defined(BOOST_MSVC)
#  pragma warning(push)
#  pragma warning(disable:4244 4251) // double float conversion loss of data and dll linkage
#endif

class vtkImageData;

namespace CGAL {

namespace ImageIO {

template <typename T>
struct Indicator_factory
{
  class Indicator : public CGAL::cpp98::unary_function<T, double>
  {
    const T label;
  public:
    Indicator(T i) : label(i) {}

    double operator()(T x) const
    {
      return (x == label) ? 1. : 0.;
    }
  }; // end nested class Indicator

  Indicator indicator(T i) const {
    return Indicator(i);
  }
};

} // end namespace CGAL::ImageIO

class CGAL_IMAGEIO_EXPORT Image_3
{
  class Image_deleter {
    const bool own_the_data;

  public:
    Image_deleter(bool own_the_data) : own_the_data(own_the_data) {}

    void operator()(_image* image)
    {
      if(!own_the_data && image != 0) {
        image->data = 0;
      }
      ::_freeImage(image);
    }
  };
public:
  enum Own { OWN_THE_DATA, DO_NOT_OWN_THE_DATA };

  typedef boost::shared_ptr<_image> Image_shared_ptr;
  typedef Image_shared_ptr Pointer;

protected:
  Image_shared_ptr image_ptr;

   // implementation in src/CGAL_ImageIO/Image_3.cpp
  bool private_read(_image* im, Own own_the_data = OWN_THE_DATA);

public:
  Image_3()
    : image_ptr()
  {
  }

  Image_3(const Image_3& bi)
    : image_ptr(bi.image_ptr)
  {
//     std::cerr << "Image_3::copy_constructor\n";
  }

  Image_3(_image* im, Own own_the_data = OWN_THE_DATA)
  {
    private_read(im, own_the_data);
  }

  ~Image_3()
  {
  }

  const _image* image() const
  {
    return image_ptr.get();
  }

  _image* image()
  {
    return image_ptr.get();
  }

  const void* data() const
  {
    return image()->data;
  }

  void* data()
  {
    return image()->data;
  }

  void set_data(void* d)
  {
    image()->data = d;
  }

  std::size_t xdim() const { return image_ptr->xdim; }
  std::size_t ydim() const { return image_ptr->ydim; }
  std::size_t zdim() const { return image_ptr->zdim; }

  std::size_t size() const { return xdim() * ydim() * zdim(); }

  double vx() const { return image_ptr->vx; }
  double vy() const { return image_ptr->vy; }
  double vz() const { return image_ptr->vz; }

  double tx() const { return image_ptr->tx; }
  double ty() const { return image_ptr->ty; }
  double tz() const { return image_ptr->tz; }

  float value(const std::size_t i,
              const std::size_t j,
              const std::size_t k) const
  {
    return ::evaluate(image(),i,j,k);
  }

public:

  bool read(const char* file)
  {
    return private_read(::_readImage(file));
  }

  bool read_raw(const char* file,
                const unsigned int rx,
                const unsigned int ry,
                const unsigned int rz,
                const double vx = 1,
                const double vy = 1,
                const double vz = 1,
                const unsigned int offset = 0,
                std::size_t wdim=1,
                WORD_KIND wk = WK_FIXED,
                SIGN sign = SGN_UNSIGNED
                )
  {
    return private_read(::_readImage_raw(file,
                                         rx,ry,rz,
                                         vx,vy,vz,offset,
                                         wdim, wk, sign));
  }

public:
  template <typename Image_word_type,
	    typename Target_type,
	    typename Coord_type,
	    class Image_transform>
  Target_type
  trilinear_interpolation(const Coord_type&x, 
			  const Coord_type&y, 
			  const Coord_type&z,
			  const Target_type& value_outside =
			    Target_type(),
			  Image_transform transform = 
			    Image_transform() ) const;

  // default Image_transform = CGAL::Identity
  template <typename Image_word_type,
	    typename Target_type,
	    typename Coord_type>
  Target_type
  trilinear_interpolation(const Coord_type&x, 
			  const Coord_type&y, 
			  const Coord_type&z,
			  const Target_type& value_outside =
			  Target_type()) const
  {
    return trilinear_interpolation<
      Image_word_type,
      Target_type>(x, y, z, value_outside,
			  CGAL::Identity<Image_word_type>());
  }

  template <typename Image_word_type,
	    typename Coord_type,
            typename Target_type>
  Target_type
  labellized_trilinear_interpolation(const Coord_type&x,
				     const Coord_type&y,
				     const Coord_type&z,
				     const Target_type& value_outside =
  				       Target_type()) const
  {
    CGAL::ImageIO::Indicator_factory<Image_word_type> indicator_factory;
    return labellized_trilinear_interpolation<Image_word_type>
      (x, y, z, value_outside, indicator_factory);
  }

  template <typename Image_word_type,
	    typename Coord_type,
            typename Target_type,
            typename Indicator_factory>
  Target_type
  labellized_trilinear_interpolation
    (const Coord_type&x,
     const Coord_type&y,
     const Coord_type&z,
     const Target_type& value_outside,
     const Indicator_factory indicator_factory) const;

}; // end Image_3

template <typename Image_word_type,
	  typename Target_type,
	  typename Coord_type,
	  class Image_transform>
Target_type
Image_3::trilinear_interpolation(const Coord_type& x, 
				 const Coord_type& y, 
				 const Coord_type& z,
				 const Target_type& value_outside,
				 Image_transform transform) const 
{
  // Check on double/float coordinates, because (int)-0.1 gives 0
  if ( x < 0 || y < 0 || z < 0 )
    return value_outside;
  
  const Coord_type lx = static_cast<Coord_type>(x / image()->vx);
  const Coord_type ly = static_cast<Coord_type>(y / image()->vy);
  const Coord_type lz = static_cast<Coord_type>(z / image()->vz);
  const std::size_t dimx = xdim();
  const std::size_t dimy = ydim();
  const std::size_t dimz = zdim();
  const std::size_t dimxy = dimx*dimy;
  
  if(lx < 0 ||
     ly < 0 ||
     lz < 0 ||
     lz >= Coord_type(dimz-1) ||
     ly >= Coord_type(dimy-1) ||
     lx >= Coord_type(dimx-1))
  {
    return value_outside;
  }  

  // images are indexed by (z,y,x)
  const int i1 = (int)(lz); 
  const int j1 = (int)(ly);
  const int k1 = (int)(lx);
  const int i2 = i1 + 1;
  const int j2 = j1 + 1;
  const int k2 = k1 + 1;

  /*   We assume (x,y,z) lies in the following cube.
   *   a, b, c, d, e, f, g, h are the value of the image at the corresponding
   *   voxels:
   *
   *
   *     x        z
   *     |       /
   *        f___ __ g
   *       /|      /|
   *     e/_|____h/ |
   *     |  |    |  |
   *     |  |b___|_c|
   *     | /     | /
   *    a|/_____d|/  _y
   *    
   *
   * a = val(i1, j1, k1)
   * b = val(i2, j1, k1)
   * c = val(i2, j2, k1)
   * d = val(i1, j2, k1)
   * e = val(i1, j1, k2)
   * f = val(i2, j1, k2)
   * g = val(i2, j2, k2)
   * h = val(i1, j2, k2)
   */

  Image_word_type* ptr = (Image_word_type*)image()->data;
  ptr += i1 * dimxy + j1 * dimx + k1;
  const Target_type a = Target_type(transform(*ptr));
  const Target_type e = Target_type(transform(*(ptr+1)));
  ptr += dimxy; // i2 * dimxy + j1 * dimx + k1;
  const Target_type b = Target_type(transform(*ptr));
  const Target_type f = Target_type(transform(*(ptr+1)));
  ptr += dimx; // i2 * dimxy + j2 * dimx + k1
  const Target_type c = Target_type(transform(*ptr));
  const Target_type g = Target_type(transform(*(ptr+1)));
  ptr -= dimxy; // i1 * dimxy + j2 * dimx + k1
  const Target_type d = Target_type(transform(*ptr));
  const Target_type h = Target_type(transform(*(ptr+1)));
  

//   const Target_type a = ((Image_word_type*)image()->data)[i1 * dimxy + j1 * dimx + k1];
//   const Target_type b = ((Image_word_type*)image()->data)[i2 * dimxy + j1 * dimx + k1];
//   const Target_type c = ((Image_word_type*)image()->data)[i2 * dimxy + j2 * dimx + k1];
//   const Target_type d = ((Image_word_type*)image()->data)[i1 * dimxy + j2 * dimx + k1];
//   const Target_type e = ((Image_word_type*)image()->data)[i1 * dimxy + j1 * dimx + k2];
//   const Target_type f = ((Image_word_type*)image()->data)[i2 * dimxy + j1 * dimx + k2];
//   const Target_type g = ((Image_word_type*)image()->data)[i2 * dimxy + j2 * dimx + k2];
//   const Target_type h = ((Image_word_type*)image()->data)[i1 * dimxy + j2 * dimx + k2];

//   const Target_type outside = Target_type(transform(value_outside);

//   if(x < 0.f ||
//      y < 0.f ||
//      z < 0.f ||
//      i1 >= dimz ||
//      j1 >= dimy ||
//      k1 >= dimx)
//   {
//     return outside;
//   }

//   Target_type a, b, c, d, e, f, g, h;

//   if(k1 < 0) {
//     a = b = c = d = outside;
//   }
//   else {
//     if(j1 < 0) {
//       a = b = outside;
//     }
//     else {
//       if(i1 < 0)
// 	a = outside;
//       else
// 	a = ((Image_word_type*)image()->data)[i1 * dimxy + j1 * dimx + k1];

//       if(i2 >= dimz)
// 	b = outside;
//       else 
// 	b = ((Image_word_type*)image()->data)[i2 * dimxy + j1 * dimx + k1];
//     }

//     if(j2 >= dimy) {
//       c = d = outside;
//     }
//     else {
//       if(i1 < 0)
// 	d = outside;
//       else
// 	d = ((Image_word_type*)image()->data)[i1 * dimxy + j2 * dimx + k1];

//       if(i2 >= dimz)
// 	c = outside;
//       else
// 	c = ((Image_word_type*)image()->data)[i2 * dimxy + j2 * dimx + k1];
//     }
//   }

//   if(k2 >= dimx) {
//     e = f = g = h = outside;
//   }
//   else {
//     if(j1 < 0) {
//       e = f = outside;
//     }
//     else {
//       if(i1 < 0)
// 	e = outside;
//       else
// 	e = ((Image_word_type*)image()->data)[i1 * dimxy + j1 * dimx + k2];

//       if(i2 >= dimz)
// 	f = outside;
//       else 
// 	f = ((Image_word_type*)image()->data)[i2 * dimxy + j1 * dimx + k2];
//     }

//     if(j2 >= dimy) {
//       g = h = outside;
//     }
//     else {
//       if(i1 < 0)
// 	h = outside;
//       else
// 	h = ((Image_word_type*)image()->data)[i1 * dimxy + j2 * dimx + k2];

//       if(i2 >= dimz)
// 	g = outside;
//       else
// 	g = ((Image_word_type*)image()->data)[i2 * dimxy + j2 * dimx + k2];
//     }
//   }

  const Target_type di2 = i2 - lz;
  const Target_type di1 = lz - i1;
  const Target_type dj2 = j2 - ly;
  const Target_type dj1 = ly - j1;
  const Target_type dk2 = k2 - lx;
  const Target_type dk1 = lx - k1;
//   std::cerr << di2 << " " << di1 << "\n";
//   std::cerr << dj2 << " " << dj1 << "\n";
//   std::cerr << dk2 << " " << dk1 << "\n";

  return ( (  ( a * di2 + b * di1 ) * dj2 + 
	      ( d * di2 + c * di1 ) * dj1   ) * dk2 +
	   (  ( e * di2 + f * di1 ) * dj2 + 
	      ( h * di2 + g * di1 ) * dj1   ) * dk1 );
} // end trilinear_interpolation


template <typename Image_word_type,
	  typename Coord_type,
          typename Target_type,
          typename Indicator_factory>
Target_type
Image_3::labellized_trilinear_interpolation
  (const Coord_type& x,
   const Coord_type& y,
   const Coord_type& z,
   const Target_type& value_outside,
   const Indicator_factory indicator_factory) const
{
  // Check on double/float coordinates, because (int)-0.1 gives 0
  if ( x < 0 || y < 0 || z < 0 ) return value_outside;
  
  Coord_type lx = x / image()->vx;
  Coord_type ly = y / image()->vy;
  Coord_type lz = z / image()->vz;
  const std::size_t dimx = xdim();
  const std::size_t dimy = ydim();
  const std::size_t dimz = zdim();
  
  if( lx < 0 ||
      ly < 0 ||
      lz < 0 ||
     lz >= Coord_type(dimz-1) ||
     ly >= Coord_type(dimy-1) ||
     lx >= Coord_type(dimx-1))
  {
    return value_outside;
  }  

  // images are indexed by (z,y,x)
  const int i1 = (int)(lz); 
  const int j1 = (int)(ly);
  const int k1 = (int)(lx);
  const int i2 = i1 + 1;
  const int j2 = j1 + 1;

  std::array<std::size_t,8> index;
  index[0] = (i1 * dimy + j1) * dimx + k1;
  index[1] = index[0] + 1;
  index[2] = (i1 * dimy + j2) * dimx + k1;
  index[3] = index[2] + 1;
  index[4] = (i2 * dimy + j1) * dimx + k1;
  index[5] = index[4] + 1;
  index[6] = (i2 * dimy + j2) * dimx + k1;
  index[7] = index[6] + 1;

  std::array<Image_word_type,8> labels;
  
  labels[0] = ((Image_word_type*)image()->data)[index[0]];
  int lc = 1;
  for(int lci=1; lci<8; ++lci){
    bool found = false;
    Image_word_type iwt = ((Image_word_type*)image()->data)[index[lci]];
    for(int lcj=0; lcj < lc; ++lcj){
      if(iwt == labels[lcj]){
        found = true;
        break;
      }
    }
    if(found) continue;
    labels[lc] = iwt;
    ++lc;
  }

  CGAL_HISTOGRAM_PROFILER(
    "Number of labels around a vertex, Image_3::labellized_trilinear_interpolation()", 
    static_cast<unsigned int>(lc));

  if(lc == 1) {
    return static_cast<Target_type>(labels[0]);
  }
    
  double best_value = 0.;
  Image_word_type best = 0;
  for(int i = 0; i < lc; ++i)
  {
    Image_word_type iwt = labels[i];
    const double r = 
      trilinear_interpolation<Image_word_type,double,Coord_type>(
        x, y, z, value_outside, indicator_factory.indicator(iwt));

    if(r > best_value) {
      best = iwt;
      best_value = r;
    }
  }
//   CGAL_assertion(best_value > 0.5);
  return static_cast<Target_type>(best);
}

} // end namespace CGAL

#ifdef CGAL_HEADER_ONLY
#include <CGAL/Image_3_impl.h>
#endif // CGAL_HEADER_ONLY

#if defined(BOOST_MSVC)
#  pragma warning(pop)
#endif

#include <CGAL/enable_warnings.h>

#endif // CGAL_IMAGE_3_H
