//=============================================================================
//
// fed.cpp
// Authors: Pablo F. Alcantarilla (1), Jesus Nuevo (2)
// Institutions: Georgia Institute of Technology (1)
//               TrueVision Solutions (2)
// Date: 15/09/2013
// Email: pablofdezalc@gmail.com
//
// AKAZE Features Copyright 2013, Pablo F. Alcantarilla, Jesus Nuevo
// All Rights Reserved
// See LICENSE for the license information
//=============================================================================

/**
 * @file fed.cpp
 * @brief Functions for performing Fast Explicit Diffusion and building the
 * nonlinear scale space
 * @date Sep 15, 2013
 * @author Pablo F. Alcantarilla, Jesus Nuevo
 * @note This code is derived from FED/FJ library from Grewenig et al.,
 * The FED/FJ library allows solving more advanced problems
 * Please look at the following papers for more information about FED:
 * [1] S. Grewenig, J. Weickert, C. Schroers, A. Bruhn. Cyclic Schemes for
 * PDE-Based Image Analysis. Technical Report No. 327, Department of Mathematics,
 * Saarland University, Saarbrücken, Germany, March 2013
 * [2] S. Grewenig, J. Weickert, A. Bruhn. From box filtering to fast explicit diffusion.
 * DAGM, 2010
 *
*/
#include "../precomp.hpp"
#include "fed.h"

using namespace std;

//*************************************************************************************
//*************************************************************************************

/**
 * @brief This function allocates an array of the least number of time steps such
 * that a certain stopping time for the whole process can be obtained and fills
 * it with the respective FED time step sizes for one cycle
 * The function returns the number of time steps per cycle or 0 on failure
 * @param T Desired process stopping time
 * @param M Desired number of cycles
 * @param tau_max Stability limit for the explicit scheme
 * @param reordering Reordering flag
 * @param tau The vector with the dynamic step sizes
 */
int fed_tau_by_process_time(const float& T, const int& M, const float& tau_max,
                            const bool& reordering, std::vector<float>& tau) {
  // All cycles have the same fraction of the stopping time
  return fed_tau_by_cycle_time(T/(float)M,tau_max,reordering,tau);
}

//*************************************************************************************
//*************************************************************************************

/**
 * @brief This function allocates an array of the least number of time steps such
 * that a certain stopping time for the whole process can be obtained and fills it
 * it with the respective FED time step sizes for one cycle
 * The function returns the number of time steps per cycle or 0 on failure
 * @param t Desired cycle stopping time
 * @param tau_max Stability limit for the explicit scheme
 * @param reordering Reordering flag
 * @param tau The vector with the dynamic step sizes
 */
int fed_tau_by_cycle_time(const float& t, const float& tau_max,
                          const bool& reordering, std::vector<float> &tau) {
  int n = 0;          // Number of time steps
  float scale = 0.0;  // Ratio of t we search to maximal t

  // Compute necessary number of time steps
  n = cvCeil(sqrtf(3.0f*t/tau_max+0.25f)-0.5f-1.0e-8f);
  scale = 3.0f*t/(tau_max*(float)(n*(n+1)));

  // Call internal FED time step creation routine
  return fed_tau_internal(n,scale,tau_max,reordering,tau);
}

//*************************************************************************************
//*************************************************************************************

/**
 * @brief This function allocates an array of time steps and fills it with FED
 * time step sizes
 * The function returns the number of time steps per cycle or 0 on failure
 * @param n Number of internal steps
 * @param scale Ratio of t we search to maximal t
 * @param tau_max Stability limit for the explicit scheme
 * @param reordering Reordering flag
 * @param tau The vector with the dynamic step sizes
 */
int fed_tau_internal(const int& n, const float& scale, const float& tau_max,
                     const bool& reordering, std::vector<float> &tau) {
  float c = 0.0, d = 0.0;     // Time savers
  vector<float> tauh;    // Helper vector for unsorted taus

  if (n <= 0) {
    return 0;
  }

  // Allocate memory for the time step size
  tau = vector<float>(n);

  if (reordering) {
    tauh = vector<float>(n);
  }

  // Compute time saver
  c = 1.0f / (4.0f * (float)n + 2.0f);
  d = scale * tau_max / 2.0f;

  // Set up originally ordered tau vector
  for (int k = 0; k < n; ++k) {
    float h = cosf((float)CV_PI * (2.0f * (float)k + 1.0f) * c);

    if (reordering) {
      tauh[k] = d / (h * h);
    }
    else {
      tau[k] = d / (h * h);
    }
  }

  // Permute list of time steps according to chosen reordering function
  int kappa = 0, prime = 0;

  if (reordering == true) {
    // Choose kappa cycle with k = n/2
    // This is a heuristic. We can use Leja ordering instead!!
    kappa = n / 2;

    // Get modulus for permutation
    prime = n + 1;

    while (!fed_is_prime_internal(prime)) {
      prime++;
    }

    // Perform permutation
    for (int k = 0, l = 0; l < n; ++k, ++l) {
      int index = 0;
      while ((index = ((k+1)*kappa) % prime - 1) >= n) {
        k++;
      }

      tau[l] = tauh[index];
    }
  }

  return n;
}

//*************************************************************************************
//*************************************************************************************

/**
 * @brief This function checks if a number is prime or not
 * @param number Number to check if it is prime or not
 * @return true if the number is prime
 */
bool fed_is_prime_internal(const int& number) {
  bool is_prime = false;

  if (number <= 1) {
    return false;
  }
  else if (number == 1 || number == 2 || number == 3 || number == 5 || number == 7) {
    return true;
  }
  else if ((number % 2) == 0 || (number % 3) == 0 || (number % 5) == 0 || (number % 7) == 0) {
    return false;
  }
  else {
    is_prime = true;
    int upperLimit = (int)sqrt(1.0f + number);
    int divisor = 11;

    while (divisor <= upperLimit ) {
      if (number % divisor == 0)
      {
        is_prime = false;
      }

      divisor +=2;
    }

    return is_prime;
  }
}
