#include "TBackgroundVuMeter.h"

//using namespace bgslibrary::algorithms::vumeter;

namespace bgslibrary
{
  namespace algorithms
  {
    namespace vumeter
    {
      const int PROCESS_PAR_COUNT = 3;

      TBackgroundVuMeter::TBackgroundVuMeter(void)
        : m_pHist(NULL)
        , m_nBinCount(0)
        , m_nBinSize(8)
        , m_nCount(0)
        , m_fAlpha(0.995)
        , m_fThreshold(0.03)
      {
        std::cout << "TBackgroundVuMeter()" << std::endl;
      }

      TBackgroundVuMeter::~TBackgroundVuMeter(void)
      {
        Clear();
        std::cout << "~TBackgroundVuMeter()" << std::endl;
      }

      void TBackgroundVuMeter::Clear(void)
      {
        TBackground::Clear();

        if (m_pHist != NULL)
        {
          for (int i = 0; i < m_nBinCount; ++i)
          {
            if (m_pHist[i] != NULL)
              cvReleaseImage(&m_pHist[i]);
          }

          delete[] m_pHist;
          m_pHist = NULL;
          m_nBinCount = 0;
        }

        m_nCount = 0;
      }

      void TBackgroundVuMeter::Reset(void)
      {
        float fVal = 0.0;

        TBackground::Reset();

        if (m_pHist != NULL)
        {
          //		fVal = (m_nBinCount != 0) ? (float)(1.0 / (double)m_nBinCount) : (float)0.0;
          fVal = 0.0;

          for (int i = 0; i < m_nBinCount; ++i)
          {
            if (m_pHist[i] != NULL)
            {
              cvSetZero(m_pHist[i]);
              cvAddS(m_pHist[i], cvScalar(fVal), m_pHist[i]);
            }
          }
        }

        m_nCount = 0;
      }

      int TBackgroundVuMeter::GetParameterCount(void)
      {
        return TBackground::GetParameterCount() + PROCESS_PAR_COUNT;
      }

      std::string TBackgroundVuMeter::GetParameterName(int nInd)
      {
        std::string csResult;
        int nNb;

        nNb = TBackground::GetParameterCount();

        if (nInd >= nNb)
        {
          nInd -= nNb;

          switch (nInd)
          {
          case 0: csResult = "Bin size"; break;
          case 1: csResult = "Alpha"; break;
          case 2: csResult = "Threshold"; break;
          }
        }
        else
          csResult = TBackground::GetParameterName(nInd);

        return csResult;
      }

      std::string TBackgroundVuMeter::GetParameterValue(int nInd)
      {
        std::string csResult;
        int nNb;

        nNb = TBackground::GetParameterCount();

        if (nInd >= nNb)
        {
          nInd -= nNb;

          char buff[100];

          switch (nInd)
          {
          case 0: sprintf(buff, "%d", m_nBinSize); break;
          case 1: sprintf(buff, "%.3f", m_fAlpha); break;
          case 2: sprintf(buff, "%.2f", m_fThreshold); break;
          }

          csResult = buff;
        }
        else
          csResult = TBackground::GetParameterValue(nInd);

        return csResult;
      }

      int TBackgroundVuMeter::SetParameterValue(int nInd, std::string csNew)
      {
        int nErr = 0;

        int nNb;

        nNb = TBackground::GetParameterCount();

        if (nInd >= nNb)
        {
          nInd -= nNb;

          switch (nInd)
          {
          case 0: SetBinSize(atoi(csNew.c_str())); break;
          case 1: SetAlpha(atof(csNew.c_str())); break;
          case 2: SetThreshold(atof(csNew.c_str())); break;
          default: nErr = 1;
          }
        }
        else
          nErr = TBackground::SetParameterValue(nInd, csNew);

        return nErr;
      }

      int TBackgroundVuMeter::Init(IplImage * pSource)
      {
        int nErr = 0;
        int nbl, nbc;

        Clear();

        nErr = TBackground::Init(pSource);

        if (pSource == NULL)
          nErr = 1;

        // calcul le nb de bin
        if (!nErr)
        {
          nbl = pSource->height;
          nbc = pSource->width;
          m_nBinCount = (m_nBinSize != 0) ? 256 / m_nBinSize : 0;

          if (m_nBinCount <= 0 || m_nBinCount > 256)
            nErr = 1;
        }

        // creation du tableau de pointeur
        if (!nErr)
        {
          m_pHist = new IplImage *[m_nBinCount];

          if (m_pHist == NULL)
            nErr = 1;
        }

        // creation des images
        if (!nErr)
        {
          for (int i = 0; i < m_nBinCount; ++i)
          {
            m_pHist[i] = cvCreateImage(cvSize(nbc, nbl), IPL_DEPTH_32F, 1);

            if (m_pHist[i] == NULL)
              nErr = 1;
          }
        }

        if (!nErr)
          Reset();
        else
          Clear();

        return nErr;
      }

      bool TBackgroundVuMeter::isInitOk(IplImage * pSource, IplImage *pBackground, IplImage *pMotionMask)
      {
        bool bResult = true;
        int i;

        bResult = TBackground::isInitOk(pSource, pBackground, pMotionMask);

        if (pSource == NULL)
          bResult = false;

        if (m_nBinSize == 0)
          bResult = false;

        if (bResult)
        {
          i = (m_nBinSize != 0) ? 256 / m_nBinSize : 0;

          if (i != m_nBinCount || m_pHist == NULL)
            bResult = false;
        }

        if (bResult)
        {
          int nbl = pSource->height;
          int nbc = pSource->width;

          for (i = 0; i < m_nBinCount; ++i)
          {
            if (m_pHist[i] == NULL || m_pHist[i]->width != nbc || m_pHist[i]->height != nbl)
              bResult = false;
          }
        }

        return bResult;
      }

      int TBackgroundVuMeter::UpdateBackground(IplImage *pSource, IplImage *pBackground, IplImage *pMotionMask)
      {
        int nErr = 0;
        unsigned char *ptrs, *ptrb, *ptrm;
        float *ptr1, *ptr2;

        if (!isInitOk(pSource, pBackground, pMotionMask))
          nErr = Init(pSource);

        if (!nErr)
        {
          m_nCount++;
          int nbc = pSource->width;
          int nbl = pSource->height;
          unsigned char v = m_nBinSize;

          // multiplie tout par alpha
          for (int i = 0; i < m_nBinCount; ++i)
            cvConvertScale(m_pHist[i], m_pHist[i], m_fAlpha, 0.0);

          for (int l = 0; l < nbl; ++l)
          {
            ptrs = (unsigned char *)(pSource->imageData + pSource->widthStep * l);
            ptrm = (unsigned char *)(pMotionMask->imageData + pMotionMask->widthStep * l);
            ptrb = (unsigned char *)(pBackground->imageData + pBackground->widthStep * l);

            for (int c = 0; c < nbc; ++c, ptrs++, ptrb++, ptrm++)
            {
              // recherche le bin à augmenter
              int i = *ptrs / v;

              if (i < 0 || i >= m_nBinCount)
                i = 0;

              ptr1 = (float *)(m_pHist[i]->imageData + m_pHist[i]->widthStep * l);
              ptr1 += c;

              *ptr1 += (float)(1.0 - m_fAlpha);
              *ptrm = (*ptr1 < m_fThreshold) ? 255 : 0;

              // recherche le bin du fond actuel
              i = *ptrb / v;

              if (i < 0 || i >= m_nBinCount)
                i = 0;

              ptr2 = (float *)(m_pHist[i]->imageData + m_pHist[i]->widthStep * l);
              ptr2 += c;

              if (*ptr2 < *ptr1)
                *ptrb = *ptrs;
            }
          }

          if (m_nCount < 5)
            cvSetZero(pMotionMask);
        }

        return nErr;
      }

      IplImage *TBackgroundVuMeter::CreateTestImg()
      {
        IplImage *pImage = NULL;

        if (m_nBinCount > 0)
          pImage = cvCreateImage(cvSize(m_nBinCount, 100), IPL_DEPTH_8U, 3);

        if (pImage != NULL)
          cvSetZero(pImage);

        return pImage;
      }

      int TBackgroundVuMeter::UpdateTest(IplImage *pSource, IplImage *pBackground, IplImage *pTest, int nX, int nY, int nInd)
      {
        int nErr = 0;
        float *ptrf;

        if (pTest == NULL || !isInitOk(pSource, pBackground, pSource))
          nErr = 1;

        if (!nErr)
        {
          int nbl = pTest->height;
          int nbc = pTest->width;

          if (nbl != 100 || nbc != m_nBinCount)
            nErr = 1;

          if (nX < 0 || nX >= pSource->width || nY < 0 || nY >= pSource->height)
            nErr = 1;
        }

        if (!nErr)
        {
          cvSetZero(pTest);

          for (int i = 0; i < m_nBinCount; ++i)
          {
            ptrf = (float *)(m_pHist[i]->imageData + m_pHist[i]->widthStep * nY);
            ptrf += nX;

            if (*ptrf >= 0 || *ptrf <= 1.0) {
              cvLine(pTest, cvPoint(i, 100), cvPoint(i, (int)(100.0 * (1.0 - *ptrf))), cvScalar(0, 255, 0));
            }
          }

          cvLine(pTest, cvPoint(0, (int)(100.0 * (1.0 - m_fThreshold))), cvPoint(m_nBinCount, (int)(100.0 * (1.0 - m_fThreshold))), cvScalar(0, 128, 0));
        }

        return nErr;
      }
    }
  }
}
