/**
* @file CachedExample.h
* @brief data caching of several feature images and many more
* @author Erik Rodner, Sven Sickert
* @date 04/21/2008 (modified 03/18/2016)

*/
#ifndef CACHEDEXAMPLEINCLUDE
#define CACHEDEXAMPLEINCLUDE

#include <assert.h>
#include <map>

#include "core/vector/SparseVectorT.h"
#include "core/image/MultiChannelImageT.h"
#include "core/image/MultiChannelImage3DT.h"
#include "core/basics/FileMgt.h"

namespace OBJREC
{

/** data caching of several feature images and many more,
    can be used in conjunction with Example
    to share image data between different sliding windows within
    an image
    @see Example */
class CachedExample
{

  protected:
    /** resize image to this fixed width */
    int newWidth;
    /** resize image to this fixed height */
    int newHeight;
    /** resize image to this fixed depth */
    int newDepth;

    /** original image width */
    int oxsize;
    /** original image height */
    int oysize;
    /** original image depth */
    int ozsize;

    /** list of filenames of images */
    std::vector<std::string> imgfn;

    /** array of double images */
    NICE::MultiChannelImageT<double> *dchannels;
    NICE::MultiChannelImage3DT<double> *dchannels3;

    /** array of integer images */
    NICE::MultiChannelImageT<int> *ichannels;
    NICE::MultiChannelImage3DT<int> *ichannels3;

    /** array of histogram images */
    NICE::MultiChannelImageT<long> *lchannels;
    NICE::MultiChannelImage3DT<long> *lchannels3;

    /** maps for temporary files */
    std::map<int, std::string> dtemps;
    std::map<int, std::string> itemps;
    std::map<int, std::string> ltemps;

    /** read standard image data from file */
    void readImageData ();

    /** read rgb image data from file */
    void readImageDataRGB ();

    /** calc grayvalue integral image */
    void calcIntegralImage ();

    /** array of histogram images */
    NICE::SparseVector **svmap;

    /** sizes of histogram images */
    int *svmap_xsize;
    int *svmap_ysize;

    bool hasColorInformation;

  public:

    /** whether one can obtain color information */
    bool colorInformationAvailable() const;

    enum
    {
      L_INTEGRALIMAGE = 0,
      L_NUMCHANNELS
    };

    /** integer channel types */
    enum
    {
      I_GRAYVALUES = 0,
      I_COLOR,
      I_EDGES,
      I_NUMCHANNELS
    };

    /** double value channels */
    enum
    {
      D_EOH = 0,
      D_INTEGRALPRIOR,
      D_INTEGRALEOH,
      D_INTEGRALCOLOR,
      D_NUMCHANNELS
    };


    /** sparse histogram channel types */
    enum
    {
      SVTEXTON = 0,
      SVNUMCHANNELS
    };

    /** default init method */
    void init ();

    /** simple constructor
        @param imgfn image filename
        @param newWidth resize raw image to this width
        @param newHeight resize raw image to this height
    */
    CachedExample ( const std::string & imgfn,
                    int newWidth = -1,
                    int newHeight = -1 );

    /** simple 3d constructor
        @param imgfn image filename
        @param newWidth resize raw image to this width
        @param newHeight resize raw image to this height
        @param newDepth resize raw image to this depth
    */
    CachedExample ( const std::vector<std::string> & imgfn,
                    int newWidth = -1,
                    int newHeight = -1,
                    int newDepth = -1 );

    /** constructor (disabled buffering)
        @param img gray-value image
    */
    CachedExample ( const NICE::Image & img );

    /** constructor (disabled buffering)
        @param img gray-value multi channel image
    */
    CachedExample ( const NICE::MultiChannelImageT<int> & img );

    /** constructor (disabled buffering)
        @param img rgb image
        @param disableGrayConversion whether to provide gray values or not
    */
    CachedExample ( const NICE::ColorImage & img, bool disableGrayConversion = false );

    /** constructor (disabled buffering)
        @param img multi channel image 3D
        @param disableGrayConversion whether to provide gray values or not
    */
    CachedExample ( const NICE::MultiChannelImage3DT<int> & img,
                    bool disableGrayConversion = false );

    /** simple destructor */
    virtual ~CachedExample();

    /**
     * get the NICE::Image Filename
     * @return NICE::Image Filename
     */
    inline std::string getFilename(const int z = 0);

    /**
     * @brief get amount of images
     * @return amount of images
     */
    inline int getNumImages ();

    /** get double image channel
        @param channel channel type (choose from enum type)
        @return buffer to image data
    */
    inline NICE::MultiChannelImageT<double> & getDChannel ( int channel );

    /** get double image channel 3d
        @param channel channel type (choose from enum type)
        @return buffer to image data
    */
    inline NICE::MultiChannelImage3DT<double> & getDChannel3 ( int channel );

    /** get integer image channel
        @param channel channel type (choose from enum type)
        @param[out] xsize width of image
        @param[out] ysize height of image
        @return buffer to image data
    */
    inline NICE::MultiChannelImageT<int> & getIChannel ( int channel );

    /** get integer image channel 3d
        @param channel channel type (choose from enum type)
        @return buffer to image data
    */
    inline NICE::MultiChannelImage3DT<int> & getIChannel3 ( int channel );

    /** get long image channel
        @param channel channel type (choose from enum type)
        @param[out] xsize width of image
        @param[out] ysize height of image
        @return buffer to image data
    */
    inline NICE::MultiChannelImageT<long> & getLChannel ( int channel );

    /** get long image channel 3d
        @param channel channel type (choose from enum type)
        @return buffer to image data
    */
    inline NICE::MultiChannelImage3DT<long> & getLChannel3 ( int channel );

    /** get histogram image
        @param svchannel channel type (choose from histogram channel enum)
        @param[out] xsize width of raw image
        @param[out] ysize height of raw image
        @param[out] tm_xsize width of histogram channel buffer
        @param[out] tm_ysize height of histogram channel buffer
        @remark buffer will be not copied !!
        @return pointer to histogram channel buffer
    */
    NICE::SparseVector *getSVMap ( int svchannel, int & xsize, int & ysize, int & tm_xsize, int & tm_ysize ) const;

    /** assign histogram channel buffer and compute integral image
        @param svchannel
        @param _map pointer to histogram channel buffer
        @param xsize_s width of histogram channel buffer
        @param ysize_s height of histogram channel buffer
        @remark buffer will be not copied !!
    */
    void buildIntegralSV ( int svchannel, NICE::SparseVector *_map, int xsize_s, int ysize_s );

    /** assign histogram channel buffer
    @param svchannel
    @param _map pointer to histogram channel buffer
    @param xsize_s width of histogram channel buffer
    @param ysize_s height of histogram channel buffer
    @remark buffer will be not copied !!
    */
    void setSVMap ( int svchannel, NICE::SparseVector *_map, int xsize_s, int ysize_s );

    /** get image sizes */
    void getImageSize ( int & xsize, int & ysize ) const
    {
      xsize = oxsize;
      ysize = oysize;
    }

    /** get image sizes 3d */
    void getImageSize3 ( int & xsize, int & ysize, int & zsize ) const
    {
        xsize = oxsize;
        ysize = oysize;
        zsize = ozsize;
    }


    /** drop precached data:
     (1) this is only possible if an image filename is given
     (2) only data channels are deleted that can be reproduced by CachedExample itself
    */
    void dropPreCached();

    template<class ImgPixelValue>
    void dropImages ( NICE::MultiChannelImageT<ImgPixelValue> *images,
                      std::map<int, std::string> & temps,
                      int numImages );
};


/********************** INLINE FUNCTIONS *****************************/
inline std::string CachedExample::getFilename( const int z )
{
    return imgfn[z];
}

inline int CachedExample::getNumImages ()
{
    return imgfn.size();
}

inline NICE::MultiChannelImageT<double> & CachedExample::getDChannel ( int channel )
{
  assert ( ( channel >= 0 ) && ( channel < D_NUMCHANNELS ) );

  if ( dchannels[channel].channels() == 0 )
  {
    std::map<int, std::string>::const_iterator j = dtemps.find ( channel );
    if ( j == dtemps.end() )
    {
      //fprintf (stderr, "NICE::MultiChannelImageT: unable to recover data channel %s (double %d)!\n",
      // imgfn.c_str(), channel);
    }
    else
    {
      //fprintf (stderr, "NICE::MultiChannelImageT: restoring data from %s ", j->second.c_str() );
      dchannels[channel].restore ( j->second );
      //fprintf (stderr, "(%d x %d)\n", dchannels[channel].xsize, dchannels[channel].ysize );
    }
  }

  return dchannels[channel];
}

inline NICE::MultiChannelImage3DT<double> & CachedExample::getDChannel3 ( int channel )
{
    assert ( ( channel >= 0 ) && ( channel < D_NUMCHANNELS ) );

    if ( dchannels3[channel].channels() == 0 )
    {
        std::map<int, std::string>::const_iterator j = dtemps.find ( channel );
        if ( j == dtemps.end() )
        {
            //fprintf (stderr, "NICE::MultiChannelImageT: unable to recover data channel %s (double %d)!\n",
            //imgfn[0].c_str(), channel);
        }
        else
        {
            //fprintf (stderr, "NICE::MultiChannelImageT: restoring data from %s ", j->second.c_str() );
            dchannels3[channel].restore ( j->second );
            //fprintf (stderr, "(%d x %d)\n", dchannels[channel].xsize, dchannels[channel].ysize );
        }
    }

    return dchannels3[channel];
}

inline NICE::MultiChannelImageT<int> & CachedExample::getIChannel ( int channel )
{
  assert ( ( channel >= 0 ) && ( channel < I_NUMCHANNELS ) );

  if ( ( ichannels[channel].channels() == 0 ) )
  {
      if ( ( imgfn[0] != "" ) && ( channel == I_GRAYVALUES ) )
    {
      readImageData();
    }
      else if ( ( imgfn[0] != "" ) && ( channel == I_COLOR ) )
    {
      readImageDataRGB();
      assert ( hasColorInformation );
    }
    else
    {
      std::map<int, std::string>::const_iterator j = itemps.find ( channel );
      if ( j == itemps.end() )
      {
        //fprintf (stderr, "NICE::MultiChannelImageT: unable to recover data channel (int %d)!\n", channel);
        //exit(-1);
      }
      else
      {
        //fprintf (stderr, "NICE::MultiChannelImageT: restoring data from %s\n", j->second.c_str() );
        ichannels[channel].restore ( j->second );
      }
    }
  }

  return ichannels[channel];
}

inline NICE::MultiChannelImage3DT<int> & CachedExample::getIChannel3 ( int channel )
{
    assert ( ( channel >= 0 ) && ( channel < I_NUMCHANNELS ) );

    if ( ( ichannels3[channel].channels() == 0 ) )
    {
        if ( ( imgfn[0] != "" ) && ( channel == I_GRAYVALUES ) )
        {
            readImageData();
        }
        else if ( ( imgfn[0] != "" ) && ( channel == I_COLOR ) )
        {
            readImageDataRGB();
            assert ( hasColorInformation );
        }
        else
        {
            std::map<int, std::string>::const_iterator j = itemps.find ( channel );
            if ( j == itemps.end() )
            {
                //fprintf (stderr, "NICE::MultiChannelImageT: unable to recover data channel (int %d)!\n", channel);
                //exit(-1);
            }
            else
            {
                //fprintf (stderr, "NICE::MultiChannelImageT: restoring data from %s\n", j->second.c_str() );
                ichannels3[channel].restore ( j->second );
            }
        }
    }

    return ichannels3[channel];
}

inline NICE::MultiChannelImageT<long> & CachedExample::getLChannel ( int channel )
{
  assert ( ( channel >= 0 ) && ( channel < L_NUMCHANNELS ) );

  if ( lchannels[channel].channels() == 0 )
  {
    std::map<int, std::string>::const_iterator j = ltemps.find ( channel );
    if ( j == ltemps.end() )
    {

      if ( channel == L_INTEGRALIMAGE )
      {
        calcIntegralImage();
      }
      else
      {
        //fprintf (stderr, "NICE::MultiChannelImageT: unable to recover data channel (long %d)!\n", channel);
        //exit(-1);
      }
    }
    else
    {
      //fprintf (stderr, "NICE::MultiChannelImageT: restoring data from %s\n", j->second.c_str() );
      lchannels[channel].restore ( j->second );
    }
  }

  return lchannels[channel];
}

inline NICE::MultiChannelImage3DT<long> & CachedExample::getLChannel3 ( int channel )
{
    assert ( ( channel >= 0 ) && ( channel < L_NUMCHANNELS ) );

    if ( lchannels3[channel].channels() == 0 )
    {
        std::map<int, std::string>::const_iterator j = ltemps.find ( channel );
        if ( j == ltemps.end() )
        {

            if ( channel == L_INTEGRALIMAGE )
            {
                calcIntegralImage();
            }
            else
            {
                //fprintf (stderr, "NICE::MultiChannelImageT: unable to recover data channel (long %d)!\n", channel);
                //exit(-1);
            }
        }
        else
        {
            //fprintf (stderr, "NICE::MultiChannelImageT: restoring data from %s\n", j->second.c_str() );
            lchannels3[channel].restore ( j->second );
        }
    }

    return lchannels3[channel];
}

template<class ImgPixelValue>
void CachedExample::dropImages ( NICE::MultiChannelImageT<ImgPixelValue> *images, std::map<int, std::string> & temps, int numImages )
{
  for ( int i = 0 ; i < numImages; i++ )
  {
    std::map<int, std::string>::iterator j = temps.find ( i );
    if ( j == temps.end() )
    {
      std::string tempfilename = NICE::FileMgt::createTempFile ( "tmp/cachedexample_%s" );
      //fprintf (stderr, "CachedExample: dumping channel %d/%d to %s (%d x %d)\n", i, numImages, tempfilename.c_str(),
      // images[i].xsize, images[i].ysize );
      images[i].store ( tempfilename );
      temps[i] = tempfilename;
    }
    images[i].freeData();
  }

}



} // namespace

#endif