// File: crn_resampler.h
// RG: This is public domain code, originally derived from Graphics Gems 3, see: http://code.google.com/p/imageresampler/
#pragma once

namespace crnlib
{
   #define CRNLIB_RESAMPLER_DEBUG_OPS 0
   #define CRNLIB_RESAMPLER_DEFAULT_FILTER "lanczos4"

   #define CRNLIB_RESAMPLER_MAX_DIMENSION 16384

   // float or double
   typedef float Resample_Real;

   class Resampler
   {
   public:
      typedef Resample_Real Sample;

      struct Contrib
      {
         Resample_Real weight;
         unsigned short pixel;
      };

      struct Contrib_List
      {
         unsigned short n;
         Contrib* p;
      };

      enum Boundary_Op
      {
         BOUNDARY_WRAP = 0,
         BOUNDARY_REFLECT = 1,
         BOUNDARY_CLAMP = 2
      };

      enum Status
      {
         STATUS_OKAY = 0,
         STATUS_OUT_OF_MEMORY = 1,
         STATUS_BAD_FILTER_NAME = 2,
         STATUS_SCAN_BUFFER_FULL = 3
      };
      
      // src_x/src_y - Input dimensions
      // dst_x/dst_y - Output dimensions
      // boundary_op - How to sample pixels near the image boundaries
      // sample_low/sample_high - Clamp output samples to specified range, or disable clamping if sample_low >= sample_high
      // Pclist_x/Pclist_y - Optional pointers to contributor lists from another instance of a Resampler
      // src_x_ofs/src_y_ofs - Offset input image by specified amount (fractional values okay)
      Resampler(
         int src_x, int src_y,
         int dst_x, int dst_y,
         Boundary_Op boundary_op = BOUNDARY_CLAMP,
         Resample_Real sample_low = 0.0f, Resample_Real sample_high = 0.0f,		
         const char* Pfilter_name = CRNLIB_RESAMPLER_DEFAULT_FILTER,
         Contrib_List* Pclist_x = NULL,
         Contrib_List* Pclist_y = NULL,
         Resample_Real filter_x_scale = 1.0f,
         Resample_Real filter_y_scale = 1.0f,
         Resample_Real src_x_ofs = 0.0f, 
         Resample_Real src_y_ofs = 0.0f);

      ~Resampler();

      // Reinits resampler so it can handle another frame.
      void restart();

      // false on out of memory.
      bool put_line(const Sample* Psrc);

      // NULL if no scanlines are currently available (give the resampler more scanlines!)
      const Sample* get_line();

      Status status() const { return m_status; }

      // Returned contributor lists can be shared with another Resampler. 
      void get_clists(Contrib_List** ptr_clist_x, Contrib_List** ptr_clist_y);
      Contrib_List* get_clist_x() const {	return m_Pclist_x; }
      Contrib_List* get_clist_y() const {	return m_Pclist_y; }

      // Filter accessors.
      static int get_filter_num();
      static const char* get_filter_name(int filter_num);
      
      static Contrib_List* make_clist(
         int src_x, int dst_x, Boundary_Op boundary_op,
         Resample_Real (*Pfilter)(Resample_Real),
         Resample_Real filter_support,
         Resample_Real filter_scale,
         Resample_Real src_ofs);
      
   private:
      Resampler();
      Resampler(const Resampler& o);
      Resampler& operator= (const Resampler& o);

   #ifdef CRNLIB_RESAMPLER_DEBUG_OPS
      int total_ops;
   #endif

      int m_intermediate_x;

      int m_resample_src_x;
      int m_resample_src_y;
      int m_resample_dst_x;
      int m_resample_dst_y;

      Boundary_Op m_boundary_op;

      Sample* m_Pdst_buf;
      Sample* m_Ptmp_buf;

      Contrib_List* m_Pclist_x;
      Contrib_List* m_Pclist_y;

      bool m_clist_x_forced;
      bool m_clist_y_forced;

      bool m_delay_x_resample;

      int* m_Psrc_y_count;
      unsigned char* m_Psrc_y_flag;

      // The maximum number of scanlines that can be buffered at one time.
      enum { MAX_SCAN_BUF_SIZE = CRNLIB_RESAMPLER_MAX_DIMENSION };
      
      struct Scan_Buf
      {
         int scan_buf_y[MAX_SCAN_BUF_SIZE];
         Sample* scan_buf_l[MAX_SCAN_BUF_SIZE];
      };

      Scan_Buf* m_Pscan_buf;

      int m_cur_src_y;
      int m_cur_dst_y;

      Status m_status;

      void resample_x(Sample* Pdst, const Sample* Psrc);
      void scale_y_mov(Sample* Ptmp, const Sample* Psrc, Resample_Real weight, int dst_x);
      void scale_y_add(Sample* Ptmp, const Sample* Psrc, Resample_Real weight, int dst_x);
      void clamp(Sample* Pdst, int n);
      void resample_y(Sample* Pdst);

      static int reflect(const int j, const int src_x, const Boundary_Op boundary_op);
      
      inline int count_ops(Contrib_List* Pclist, int k)
      {
         int i, t = 0;
         for (i = 0; i < k; i++)
            t += Pclist[i].n;
         return (t);
      }

      Resample_Real m_lo;
      Resample_Real m_hi;

      inline Resample_Real clamp_sample(Resample_Real f) const
      {
         if (f < m_lo)
            f = m_lo;
         else if (f > m_hi)
            f = m_hi;
         return f;
      }   
   };

} // namespace crnlib