#include <iclRegionDetector.h>
#include <algorithm>

namespace icl{
  
  namespace{
    template<class T>
    static inline bool eqfunc(const T &a,const T&b){
      return a == b;
    }
  }
  
  RegionDetector::RegionDetector(unsigned int minSize, unsigned int maxSize, icl64f minVal, icl64f maxVal):
    m_uiMinSize(minSize),m_uiMaxSize(maxSize),m_dMinVal(minVal),m_dMaxVal(maxVal){
  }
  void RegionDetector::setRestrictions(unsigned int minSize, unsigned int maxSize, icl64f minVal, icl64f maxVal){
    m_uiMinSize = minSize;
    m_uiMaxSize = maxSize;
    m_dMinVal = minVal;
    m_dMaxVal = maxVal;
  }
  void RegionDetector::setRestrictions(const Range<unsigned int> &sizeRange, const Range<icl64f> &valueRange){
    setRestrictions(sizeRange.minVal,sizeRange.maxVal,valueRange.minVal, valueRange.maxVal);
  }
  
  const std::vector<Region> &RegionDetector::detect(const ImgBase *image){
    m_vecBlobData.clear();
    ICLASSERT_RETURN_VAL(image,m_vecBlobData);
    ICLASSERT_RETURN_VAL(image->getChannels()==1,m_vecBlobData);
    ICLASSERT_RETURN_VAL(image->getROISize().getDim(),m_vecBlobData);
    switch(image->getDepth()){
#define ICL_INSTANTIATE_DEPTH(D) case depth##D: detect_intern(*image->asImg<icl##D>(),eqfunc<icl##D>); break;
      ICL_INSTANTIATE_ALL_DEPTHS;
#undef ICL_INSTANTIATE_DEPTH
    }
    return m_vecBlobData;
  }

  void RegionDetector::adaptLIM(int w, int h){
    if((int)m_vecLIM.size() < w*h){
      m_vecLIM.resize(w*h);
    }
  }
  void RegionDetector::resetPartList(){
    std::for_each(m_vecParts.begin(),m_vecParts.end(),RegionPart::del_func);
    m_vecParts.clear();    
  }
  inline RegionPart *RegionDetector::newPart(){
    RegionPart *p = new RegionPart;
    m_vecParts.push_back(p);
    return p;
  }

  namespace{
    template<class T>
    class ImgChannelROI{
    public:
      inline ImgChannelROI(const Img<T> &image, int channel):
        chan(image[channel]),xOffs(image.getROI().x),yOffs(image.getROI().y){
      }
      inline const T &operator()(int x, int y) const{
        return chan(x+xOffs,y+yOffs);
      }
      
    private:
      const Channel<T> chan;
      int xOffs;
      int yOffs;
    };
    
    template<class T> struct EQFunctor{
      EQFunctor(const T &ref):ref(ref){}
      T ref;
      inline bool operator()(const T &val) const{ return ref == val; }
    };
  }
  
  template<class T, typename Compare>
  void RegionDetector::detect_intern(const Img<T> &image, Compare cmp){
    const int xOffs = image.getROI().x;
    const int yOffs = image.getROI().y;
    const int w = image.getROI().width;
    const int h = image.getROI().height;

    adaptLIM(w,h);
    resetPartList();
    RegionPart **lim = &m_vecLIM[0];
    ImgChannelROI<T> data(image,0);

    
    // first pixel:
    lim[0] = newPart();
    
    // first line
    register int slX = 0;
    for(int x=1;x<w;++x){
      if( cmp( data(x,0), data(x-1,0))){
        lim[x] = lim[x-1];
      }else{
        lim[x-1]->add(ScanLine(slX+xOffs,yOffs,x-slX));
        slX = x;
        lim[x] = newPart();
      }
    }
    // first line end
    lim[w-1]->add(ScanLine(slX+xOffs,yOffs,w-slX));
    
    // rest pixles
    for(int y=1;y<h;++y){
       
      //1st pix
      if( eqfunc( data(0,y), data(0,y-1)) ){
        lim[w*y] = lim[w*(y-1)];
      }else{
        lim[w*y] = newPart();
      }

      RegionPart **limC = lim+w*y;
      RegionPart **limL = limC-w;
      const T *dataC = &(data(0,y));
      const T *dataL = &(data(0,y-1));
      
      //rest of pixels
      slX = 0;
         
      for(int x=1;x<w;++x){
        if( eqfunc( dataC[x] , dataC[x-1] ) ){
          if( eqfunc( dataC[x] , dataL[x] ) && limC[x-1] != limL[x] ){
            RegionPart *pOld = limL[x];
            RegionPart *pNew = limC[x-1];

            limC[x] = pNew;
            pNew->add(pOld);
        
            std::replace_if(&limL[x+1],&limC[x],EQFunctor<RegionPart*>(pOld),pNew);
        
          }else{
            limC[x] = limC[x-1];
          }   
        }else{
          if( eqfunc( dataC[x] , dataL[x] )){
            limC[x] = limL[x];
          }else{
            limC[x] = newPart();
          }
       
          limC[x-1]->add(ScanLine(slX+xOffs,y+yOffs,x-slX));
          slX = x; 
        }
      }
      limC[w-1]->add(ScanLine(slX+xOffs,y+yOffs,w-slX));
    }

    for(unsigned int i=0;i<m_vecParts.size();i++){
      if(m_vecParts[i]->top){
        const T &val = image(m_vecParts[i]->scanlines[0].x,m_vecParts[i]->scanlines[0].y,0);
        if(val >= m_dMinVal && val <= m_dMaxVal){
          m_vecBlobData.push_back(Region(m_vecParts[i],m_uiMaxSize,val,&image));
          Region &b = m_vecBlobData.back();
          if((unsigned int)b.getSize() > m_uiMaxSize || (unsigned int)b.getSize() < m_uiMinSize){
            m_vecBlobData.pop_back();
          }      
        }
      }
    }
  }
}