/*
 * Copyright (C) 2005-2022 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.
 */

#ifndef otbNoDataHelper_h
#define otbNoDataHelper_h
#include <vector>
#include <cassert>
#include "vnl/vnl_math.h"
#include <itkVariableLengthVector.h>
#include "OTBMetadataExport.h"

#include "otbImageMetadata.h"

namespace itk
{
class MetaDataDictionary;
}

namespace otb
{
/**
 * Reads no data flag from the ImageMetadata to flags and values
 * vectors. Returns true upon success.
 */
bool OTBMetadata_EXPORT ReadNoDataFlags(const ImageMetadata & imd, std::vector<bool>& flags, std::vector<double>& values);

/**
 * Write no data flags to the ImageMetadata from flags and values
 * vectors. Returns true upon success.
 */
void OTBMetadata_EXPORT WriteNoDataFlags(const std::vector<bool>& flags, const std::vector<double>& values, ImageMetadata & imd);



/**
* Test if the pixel corresponds to a no data pixel according to a
* vector of no data flags, and a vector of no data values.
* \param pixel The pixel to test
* \param flags A vector of size > 1 containing a flag per band to
* indicate if a no data value is available for this band
* \param values A vector of size > 1 corresponding to the no data
* value for each band. If flag is 0, the value will be ignored.
* \param nanIsNoData If true, NaN values will be reported as no-data.
*/
template <typename T>
bool IsNoData(const T& pixel, const std::vector<bool>& flags, const std::vector<double>& values, bool nanIsNoData = false)
{
  assert(flags.size() > 0);
  assert(values.size() > 0);

  return ((nanIsNoData && vnl_math_isnan(pixel)) || (flags[0] && (pixel == values[0])));
}

/**
* Reads a pixel and change the no data value if it is found. No data
* value is changed either if the pixel value is NaN or if the pixel
* value equals the no data value and flag is true.
*
* \param pixel The pixel to process \param flags A vector of size > 1
* containing a flag per band to indicate if a no data value is
* available for this band
* \param values A vector of size > 1 corresponding to the current no data
* value for each band. If flag is 0, the value will be ignored.
* \param newValues A vector of size > 1 corresponding to the new no data
* value for each band. If flag is 0, the value will be ignored
* \param nanIsNoData If true, NaN values will be considered as no-data
* and changed as well.
*
*/
template <typename T>
T ChangeNoData(const T& pixel, const std::vector<bool>& flags, const std::vector<double>& values, const std::vector<double>& newValues,
               bool nanIsNoData = false)
{
  assert(flags.size() > 0);
  assert(values.size() > 0);
  assert(newValues.size() > 0);

  if (nanIsNoData && vnl_math_isnan(pixel))
  {
    return static_cast<T>(newValues[0]);
  }

  if (flags[0] && pixel == values[0])
  {
    return static_cast<T>(newValues[0]);
  }
  return pixel;
}


/**
 * Specialization of IsNoData function to handle itk::VariableLengthVector
 */
template <typename T>
bool IsNoData(const itk::VariableLengthVector<T>& pixel, const std::vector<bool>& flags, const std::vector<double>& values, bool nanIsNoData = false)
{
  assert(flags.size() >= pixel.Size());
  assert(values.size() >= pixel.Size());

  for (unsigned int i = 0; i < pixel.Size(); ++i)
  {
    if ((nanIsNoData && vnl_math_isnan(pixel[i])) || (flags[i] && (pixel[i] == values[i])))
    {
      return true;
    }
  }
  return false;
}


/**
 * Specialization of ChangeNoData function to handle itk::VariableLengthVector
 */
template <typename T>
itk::VariableLengthVector<T> ChangeNoData(const itk::VariableLengthVector<T>& pixel, const std::vector<bool>& flags, const std::vector<double>& values,
                                          const std::vector<double>& newValues, bool nanIsNoData = false)
{
  assert(flags.size() >= pixel.Size());
  assert(values.size() >= pixel.Size());
  assert(newValues.size() >= pixel.Size());

  itk::VariableLengthVector<T> outPixel(pixel.Size());

  for (unsigned int i = 0; i < pixel.Size(); ++i)
  {
    if ((nanIsNoData && vnl_math_isnan(pixel[i])) || (flags[i] && (pixel[i] == values[i])))
    {
      outPixel[i] = newValues[i];
    }
    else
    {
      outPixel[i] = pixel[i];
    }
  }

  return outPixel;
}

} // End namespace otb

#endif
