/*=========================================================================
 *
 *  Copyright Insight Software Consortium
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *         http://www.apache.org/licenses/LICENSE-2.0.txt
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 *
 *=========================================================================*/
// histogram from the moving histogram operations
#ifndef __itkTextureHistogram_h
#define __itkTextureHistogram_h
#include "itkNumericTraits.h"

namespace itk
{
namespace Function
{

/*
 *
 *
 */
template <typename TInputPixel, typename TOutputPixel>
class TextureHistogram
{
public:
  TextureHistogram() { m_Count = 0; }

  // ~TextureHistogram()  {} default is ok

  void
  AddPixel(const TInputPixel & p)
  {
    m_Map[p]++;
    ++m_Count;
  }

  void
  RemovePixel(const TInputPixel & p)
  {

    // insert new item if one doesn't exist
    typename MapType::iterator it = m_Map.find(p);

    assert(it != m_Map.end());

    if (--(it->second) == 0)
    {
      m_Map.erase(it);
    }
    --m_Count;
  }

  TOutputPixel
  GetValue(const TInputPixel &)
  {
    TOutputPixel out;
    NumericTraits<TOutputPixel>::SetLength(out, 8);

    double       sum = 0.0;
    double       sum2 = 0.0;
    double       sum3 = 0.0;
    double       sum4 = 0.0;
    const size_t count = m_Count;
    // double median = 0.0;

    double entropy = 0.0;
    size_t curCount = 0;
    for (typename MapType::iterator i = m_Map.begin(); i != m_Map.end(); ++i)
    {
      double t = double(i->first) * double(i->second);
      sum += t;
      sum2 += (t *= double(i->first));
      sum3 += (t *= double(i->first));
      sum4 += (t *= double(i->first));

      curCount += i->second;

      const double p_x = double(i->second) / count;
      entropy += -p_x * std::log(p_x);

      //       // this is wrong!
      //       if ( curCount == count / 2 )
      //         {
      //         median += i->first;
      //         medianIt = il
      //         // we have an even number so take the average
      //           if ( !(count % 2) )
      //             {
      //               median *= 0.5;
      //             }
      //         }
    }

    //     curCount = 0;
    //     typename MapType::iterator fmedianIt = medianIt;
    //     typename MapType::iterator rmedianIt = medianIt;
    //     double mad = 0.0;

    //     while (curCount < count/2 )
    //     {
    //     if ( std::fabs( fmedianIt->first - median ) < std::fabs( rmedianIt->first - median ) )
    //       {
    //       curCount += fmedianIt->second;
    //       ++fmedianIt;
    //       }
    //     else
    //       {
    //       curCount += rmedianIt->second;
    //       --rmedianIt;
    //       }
    //     }


    const double icount = 1.0 / count;
    const double mean = sum * icount;

    // unbiased estimate
    const double variance = (sum2 - (sum * sum * icount)) / (count - 1);
    const double sigma = std::sqrt(variance);
    double       skewness = 0.0;
    double       kurtosis = 0.0;
    if (std::abs(variance * sigma) > itk::NumericTraits<double>::min())
    {

      skewness = ((sum3 - 3.0 * mean * sum2) * icount + 2.0 * mean * mean * mean) / (variance * sigma);
    }
    if (std::abs(variance) > itk::NumericTraits<double>::min())
    {
      kurtosis = (sum4 * icount + mean * (-4.0 * sum3 * icount + mean * (6.0 * sum2 * icount - 3.0 * mean * mean))) /
                   (variance * variance) -
                 3.0;
    }

    unsigned int i = 0;
    out[i++] = mean;
    out[i++] = m_Map.begin()->first;
    out[i++] = m_Map.rbegin()->first;
    out[i++] = variance;
    out[i++] = sigma;
    out[i++] = skewness;
    out[i++] = kurtosis;
    out[i++] = entropy;
    return out;
  }

  void
  AddBoundary()
  {}

  void
  RemoveBoundary()
  {}

private:
  typedef typename std::map<TInputPixel, size_t> MapType;

  MapType m_Map;
  size_t  m_Count;
};

} // end namespace Function
} // end namespace itk
#endif
