/* -*- mia-c++  -*-
 *
 * This file is part of MIA - a toolbox for medical image analysis 
 * Copyright (c) Leipzig, Madrid 1999-2017 Gert Wollny
 *
 * MIA is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with MIA; if not, see <http://www.gnu.org/licenses/>.
 *
 */


#include <mia/core/filter.hh>
#include <vector> 
#include <type_traits>
#include <limits> 

NS_MIA_BEGIN

/**
   \ingroup utils 
    
   \brief A sparse histogram 

   This class implements a sparse histogram. As input only 
   8 and 16 bit (un)signed data is allowed. 
*/

class EXPORT_CORE CSparseHistogram : public TFilter<size_t> {
        
public: 
	typedef std::vector<std::pair<int, unsigned long>> Compressed; 

	CSparseHistogram();

        /**
           Fees pixels to the histogram
           \tparam InIterator input iterator, must be a forward iterator 
           \param begin start of range 
           \param end end of range 
           \returns number of added pixels
        */
        template <typename InIterator>
        size_t operator ()(InIterator begin, InIterator end); 

	template <typename Image>
	size_t operator ()(const Image& image) {
		return (*this)(image.begin(), image.end()); 
	}
	
        /**
           \returns the histogram as a vector of <value, count> pairs 
         */
        Compressed get_compressed_histogram()const; 
 private: 
        std::vector<uint64_t> m_histogram; 
        int64_t m_shift; 
        EPixelType m_pixeltype; 
}; 

EXPORT_CORE std::ostream& operator << (std::ostream& os, const std::pair<short, uint64_t>& pair); 

//   Implementation

///  @cond INTERNAL

template <typename InIterator, bool sig> 
struct dispatch_by_pixeltype {
        static size_t  apply(InIterator MIA_PARAM_UNUSED(begin), InIterator MIA_PARAM_UNUSED(end),
                             std::vector<uint64_t>& MIA_PARAM_UNUSED(histogram)){
                throw std::invalid_argument("Input pixel type not supported"); 
        }
}; 

template <typename InIterator> 
struct dispatch_by_pixeltype<InIterator, false> {
        static size_t apply(InIterator begin, InIterator end, std::vector<uint64_t>& histogram){
                size_t n = 0; 
                while ( begin != end) {
                        ++histogram[*begin];
                        ++begin;
                        ++n; 
                }
                return n; 
        }
}; 

template <typename InIterator> 
struct dispatch_by_pixeltype<InIterator, true> {
        static size_t apply(InIterator begin, InIterator end, std::vector<uint64_t>& histogram){
                typedef typename InIterator::value_type in_pixels;
		int64_t shift = -std::numeric_limits<in_pixels>::min();
                size_t n = 0; 
                while ( begin != end) {
                        ++histogram[*begin + shift];
                        ++begin;
                        ++n; 
                }
                return n; 
        }
}; 


template <typename InIterator>
size_t CSparseHistogram::operator ()(InIterator begin, InIterator end)
{
        typedef typename InIterator::value_type in_pixeltype; 
        if (m_pixeltype ==it_none) {
                m_pixeltype = pixel_type<in_pixeltype>::value;
		m_shift = -std::numeric_limits<in_pixeltype>::min(); 
                switch (m_pixeltype) {
                case it_sbyte:
                case it_ubyte:
                        m_histogram.resize(256);
                        break; 
                case it_sshort:
                case it_ushort:
                        m_histogram.resize(65536);
                        break; 
                default:
                        throw create_exception<std::invalid_argument>("Input pixel type '",
                                                                 CPixelTypeDict.get_name(m_pixeltype),
                                                                 "' not supported."); 
                }
                
        } else if (m_pixeltype != pixel_type<in_pixeltype>::value){
                throw create_exception<std::invalid_argument>("Input pixels not of consisted type, started with ",
                                                         CPixelTypeDict.get_name(m_pixeltype), ", but got now ",
                                                         CPixelTypeDict.get_name(pixel_type<in_pixeltype>::value)); 
        }

        const bool is_signed = std::is_signed<in_pixeltype>::value; 

        size_t n = 0; 
        n += dispatch_by_pixeltype<InIterator, is_signed>::apply(begin, end, m_histogram);
        return n; 
}

/// @endcond

NS_MIA_END
