/*
 * Copyright (C) 2005-2017 Centre National d'Etudes Spatiales (CNES)
 *
 * This file is part of Orfeo Toolbox
 *
 *     https://www.orfeo-toolbox.org/
 *
 * 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
 *
 * 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.
 */


#include "otbPatternSampler.h"
#include "otbMath.h"
#include "otbMacro.h"
#include <algorithm>

namespace otb
{

bool
PatternSampler::ParameterType::operator!=(const PatternSampler::ParameterType & param) const
{
  return bool((MaxPatternSize != param.MaxPatternSize)||
              (Pattern1 != param.Pattern1)||
              (Pattern2 != param.Pattern2)||
              (Seed != param.Seed));
}

PatternSampler::PatternSampler()
  : m_Index1(0UL)
  , m_Index2(0UL)
{
  this->m_Parameters.MaxPatternSize = 1000;
  this->m_Parameters.Pattern1.clear();
  this->m_Parameters.Pattern2.clear();
  this->m_Parameters.Seed = 121212;
}

void
PatternSampler::Reset(void)
{
  Superclass::Reset();
  m_Index1 = 0UL;
  m_Index2 = 0UL;

  // if seed is not 0, generate patterns
  // since the pattern depend on the sampling rate they should be regenerated
  // in order to keep existing patterns, the seed should be set to 0
  if (this->m_Parameters.Seed)
    {
    unsigned long T1 = FindBestSize(this->GetTotalElements());
    unsigned long N1 = static_cast<unsigned long>(vcl_floor( this->GetRate() * T1 ));

    double selected_ratio = static_cast<double>(N1)/static_cast<double>(T1);

    unsigned long taken = static_cast<unsigned long>(selected_ratio*static_cast<double>(this->GetTotalElements()));
    unsigned long left = this->GetNeededElements() - taken;
    unsigned long newtot = this->GetTotalElements() - taken;

    unsigned long T2 = 0;
    unsigned long N2 = 0;
    if (left>0)
      {
      double ratio2 = 0.0;
      if (newtot>0)
        ratio2 = static_cast<double>(left)/static_cast<double>(newtot);

      T2 = FindBestSize(this->GetTotalElements()/T1*(T1-N1));
      if (T2>0)
        {
        N2 = static_cast<unsigned long>(vcl_ceil( ratio2 * T2 ));
        }   
      }

    std::srand ( m_Parameters.Seed );
    this->m_Parameters.Pattern1 = RandArray(N1,T1);
    if (T2>0)
       this->m_Parameters.Pattern2 = RandArray(N2,T2);
    }
}

bool
PatternSampler::TakeSample(void)
{
  bool ret = false;
  this->m_ProcessedElements += 1UL;
  if (this->m_ChosenElements >= this->GetNeededElements())
    return false;    

  // Test selection with first pattern 
  ret = this->m_Parameters.Pattern1[m_Index1];
  m_Index1++;
  if (m_Index1 >= this->m_Parameters.Pattern1.size())
     m_Index1=0UL;

  if(!ret && this->m_Parameters.Pattern2.size())
    {
    // Test selection with second pattern
    ret = this->m_Parameters.Pattern2[m_Index2];
    m_Index2++;
    if (m_Index2 >= this->m_Parameters.Pattern2.size())
      m_Index2=0UL;
    }
  if (ret)
    {
    this->m_ChosenElements += 1UL;
    }
  return ret;
}

// [static]
void
PatternSampler::ImportPatterns(const std::string &data, ParameterType &param)
{
  // clear output patterns
  param.Pattern1.clear();
  param.Pattern2.clear();

  // split the string on slash caracters
  size_t sep1 = data.find('/');

  // convert string into bool sequence
  for (size_t pos=0 ; pos < data.size() ; ++pos)
    {
    if (pos == sep1)
      break;
    switch (ParseSymbol(data[pos]))
      {
      case 0:
        {
        param.Pattern1.push_back(false);
        break;
        }
      case 1:
        {
        param.Pattern1.push_back(true);
        break;
        }
      default:
        {
        break;
        }
      }
    }

  if (sep1 != std::string::npos)
    {
    size_t sep2 = data.find('/',sep1+1);
    for (size_t pos=(sep1+1) ; pos < data.size() ; ++pos)
      {
      if (pos == sep2)
        break;
      switch (ParseSymbol(data[pos]))
        {
        case 0:
          {
          param.Pattern2.push_back(false);
          break;
          }
        case 1:
          {
          param.Pattern2.push_back(true);
          break;
          }
        default:
          {
          break;
          }
        }
      }
    }
}

// [static]
void
PatternSampler::ExportPatterns(const ParameterType &param, std::string &data)
{
  // clear output string
  data.clear();
  // format output patterns
  for (unsigned int i=0 ; i< param.Pattern1.size() ; ++i)
    {
    if (param.Pattern1[i])
      {
      data.push_back('1');
      }
    else
      {
      data.push_back('0');
      }
    }
  if (param.Pattern2.size())
    {
    data.push_back('/');
    }
  for (unsigned int i=0 ; i< param.Pattern2.size() ; ++i)
    {
    if (param.Pattern2[i])
      {
      data.push_back('1');
      }
    else
      {
      data.push_back('0');
      }
    }
}

unsigned int
PatternSampler::ParseSymbol(const char &s)
{
  if ((s == '1') || (s == 'X') ||
      (s == 'y') || (s == 'Y') ||
      (s == '|') || (s == '+') )
    {
    return 1;
    }
  else if ((s == '0') || (s == '_') ||
           (s == 'n') || (s == 'N') ||
           (s == '.') || (s == '-') )
    {
    return 0;
    }
  return 2;
}

std::vector<bool>
PatternSampler::RandArray(unsigned long N,unsigned long T)
{
   if (N>T)
     itkGenericExceptionMacro(<< "N must be <= to T (aka m_SamplingTabSize)." << std::endl);

   std::vector<bool> res(T,0);
   for(unsigned long i=0; i<N;i++)
       res[i]=1;

   std::random_shuffle ( res.begin(), res.end() );
   
   return res;
}


unsigned long
PatternSampler::FindBestSize(unsigned long tot)
{
  // handle small values
  if (tot<=m_Parameters.MaxPatternSize)
    return tot;

  // try to find a sub-period in tot, between 20 and MaxPatternSize
  for(unsigned long size = m_Parameters.MaxPatternSize; size >= 2; size--)
    if (tot%size == 0)
      return size;
      
  otbWarningMacro(<<"prime number > m_Parameters.MaxPatternSize (" << tot << ">" << m_Parameters.MaxPatternSize << ")."<< std::endl );
  // fallback : return the maximum size
  return m_Parameters.MaxPatternSize;
}

} // namespace otb
