#include <ICLFilter/WarpOp.h>

namespace icl{

  template<class T>
  T interpolate_pixel_nn(float x, float y, const Channel<T> &src){
    if(x < 0) return T(0);
    return src(round(x),round(y));
  }
  
  template<class T>
  T interpolate_pixel_lin(float x, float y, const Channel<T> &src){
    float fX0 = x - floor(x), fX1 = 1.0 - fX0;
    float fY0 = y - floor(y), fY1 = 1.0 - fY0;
    int xll = (int) x;
    int yll = (int) y;
   
    const T* pLL = &src(xll,yll);
    float a = *pLL;        //  a b
    float b = *(++pLL);    //  c d
    pLL += src.getWidth();
    float d = *pLL;
    float c = *(--pLL);

    return fX1 * (fY1*a + fY0*c) + fX0 * (fY1*b + fY0*d);
  }
  

  template<class T, T (*interpolator)(float x, float y, const Channel<T> &src)>
  static inline void apply_warp_2(const Channel32f warpMap[2],
                                  const Channel<T> &src,
                                  Channel<T> &dst,
                                  const Size &size){

    for(int x=0;x<size.width;++x){
      for(int y=0;y<size.height;++y){
        int idx = x+size.width*y;
        dst[idx] = interpolator(warpMap[0][idx],warpMap[1][idx],src);
      }
    }
  }
                                

  template<class T>
  static void apply_warp(const Channel32f warpMap[2], 
                         const Img<T>&src, 
                         Img<T> &dst,
                         scalemode mode){
    for(int c=0;c<src.getChannels();++c){
      const Channel<T> s = src[c];
      Channel<T> d = dst[c];
      if(mode == interpolateNN){
        apply_warp_2<T,interpolate_pixel_nn<T> >(warpMap,s,d,s.getSize());
      }else if(mode == interpolateLIN){
        apply_warp_2<T,interpolate_pixel_lin<T> >(warpMap,s,d,s.getSize());
      }else{
        ERROR_LOG("region average interpolation mode does not work here!");
        return;
      }
    }
  }

#ifdef HAVE_IPP
  template<>  
  void apply_warp<icl8u>(const Channel32f warpMap[2], 
                                const Img<icl8u> &src, 
                                Img<icl8u> &dst,
                                scalemode mode){
    for(int c=0;c<src.getChannels();++c){
      IppStatus s = ippiRemap_8u_C1R(src.begin(c),src.getSize(),src.getLineStep(),
                                     src.getImageRect(),warpMap[0].begin(),sizeof(icl32f)*warpMap[0].getWidth(),
                                     warpMap[1].begin(),sizeof(icl32f)*warpMap[1].getWidth(),dst.begin(c),
                                     dst.getLineStep(),dst.getSize(),(int)mode);
      if(s != ippStsNoErr){
        ERROR_LOG("IPP-Error:" << ippGetStatusString(s));
        return;
      }
    }
  }
  template<>  
  void apply_warp<icl32f>(const Channel32f warpMap[2], 
                                 const Img<icl32f> &src, 
                                 Img<icl32f> &dst,
                                 scalemode mode){
    for(int c=0;c<src.getChannels();++c){
      IppStatus s = ippiRemap_32f_C1R(src.begin(c),src.getSize(),src.getLineStep(),
                                     src.getImageRect(),warpMap[0].begin(),sizeof(icl32f)*warpMap[0].getWidth(),
                                     warpMap[1].begin(),sizeof(icl32f)*warpMap[1].getWidth(),dst.begin(c),
                                     dst.getLineStep(),dst.getSize(),(int)mode);
      if(s != ippStsNoErr){
        ERROR_LOG("IPP-Error:" << ippGetStatusString(s));
        return;
      }
    }
  }

  
  // specialization for apply_warp<icl8u> and <icl32f>
#endif

  void prepare_warp_table_inplace(Img32f &warpMap){
    const Rect r = warpMap.getImageRect();
    
    Channel32f cs[2];
    warpMap.extractChannels(cs);
    const Size size = warpMap.getSize();

    for(int x=0;x<size.width;++x){
      for(int y=0;y<size.height;++y){
        if(!r.contains(round(cs[0](x,y)),round(cs[1](x,y)))){
          cs[0](x,y) = cs[1](x,y) = -1;
        }
      }
    } 
  }
  

  WarpOp::WarpOp(const Img32f &warpMap,scalemode mode, bool allowWarpMapScaling):
    m_allowWarpMapScaling(allowWarpMapScaling),m_scaleMode(mode){
    warpMap.deepCopy(&m_warpMap);
    prepare_warp_table_inplace(m_warpMap);
  }
  
  void WarpOp::setScaleMode(scalemode scaleMode){
    m_scaleMode = scaleMode;
  }
  void WarpOp::setWarpMap(const Img32f &warpMap){
    warpMap.deepCopy(&m_warpMap);
    prepare_warp_table_inplace(m_warpMap);
    m_scaledWarpMap = Img32f();
  }
  void WarpOp::setAllowWarpMapScaling(bool allow){
    m_allowWarpMapScaling = allow;
  }
  
  
  void WarpOp::apply(const ImgBase *src, ImgBase **dst){
    ICLASSERT_RETURN(src);
    ICLASSERT_RETURN(dst);
    ICLASSERT_RETURN(src != *dst);
    ICLASSERT_RETURN(m_warpMap.getSize() != Size::null);

    if(!src->hasFullROI()){
      ERROR_LOG("warp op does currently not support ROI");
      return;
    }

    if(!UnaryOp::prepare(dst,src->getDepth(),src->getSize(),
                         src->getFormat(),src->getChannels(),
                         src->getROI(),src->getTime())){
      ERROR_LOG("unable to prepare destination image (returning)");
      return;
    }
    
    Channel32f cwm[2];
    
    if(src->getSize() != m_warpMap.getSize()){
      if(m_allowWarpMapScaling){
        if(m_scaledWarpMap.getSize() != src->getSize()){
          m_scaledWarpMap.setSize(src->getSize());
          m_warpMap.scaledCopy(&m_scaledWarpMap);
          prepare_warp_table_inplace(m_scaledWarpMap);
        }
        m_scaledWarpMap.extractChannels(cwm);
      }else{
        ERROR_LOG("warp map size and image size are not equal\n"
                  "warp map can be scaled using\n"
                  "setAllowWarpMapScaling(true)");
        return;
      }
    }else{
      m_warpMap.extractChannels(cwm);
    }

    
    switch(src->getDepth()){
#define ICL_INSTANTIATE_DEPTH(D)                                 \
      case depth##D:                                             \
      apply_warp<icl##D>(cwm,*src->asImg<icl##D>(),              \
                         *(*dst)->asImg<icl##D>(),m_scaleMode);  \
      break;
      ICL_INSTANTIATE_ALL_DEPTHS;
      default:
        ICL_INVALID_DEPTH;
#undef ICL_INSTANTIATE_DEPTH

    }
  }



}