/*
 * Copyright (C) 2005-2020 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 "otbSIXSTraits.h"

#include "otb_6S.h"
#include "main_6s.h"
#include "otbMacro.h"

#include <iomanip>

namespace otb
{

void SIXSTraits::ComputeAtmosphericParameters(
    const double            SolarZenithalAngle,     /** The Solar zenithal angle */
    const double            SolarAzimutalAngle,     /** The Solar azimutal angle */
    const double            ViewingZenithalAngle,   /** The Viewing zenithal angle */
    const double            ViewingAzimutalAngle,   /** The Viewing azimutal angle */
    const unsigned int      Month,                  /** The Month */
    const unsigned int      Day,                    /** The Day (in the month) */
    const double            AtmosphericPressure,    /** The Atmospheric pressure */
    const double            WaterVaporAmount,       /** The Water vapor amount (Total water vapor content over vertical atmospheric column) */
    const double            OzoneAmount,            /** The Ozone amount (Stratospheric ozone layer content) */
    const AerosolModelType& AerosolModel,           /** The Aerosol model */
    const double            AerosolOptical,         /** The Aerosol optical (radiative impact of aerosol for the reference wavelength 550-nm) */
    WavelengthSpectralType* WavelengthSpectralBand, /** Wavelength for the spectral band definition */
    /** Note : The Max wavelength spectral band value must be updated ! */
    double& AtmosphericReflectance,                /** Atmospheric reflectance */
    double& AtmosphericSphericalAlbedo,            /** atmospheric spherical albedo */
    double& TotalGaseousTransmission,              /** Total gaseous transmission */
    double& DownwardTransmittance,                 /** downward transmittance */
    double& UpwardTransmittance,                   /** upward transmittance */
    double& UpwardDiffuseTransmittance,            /** upward diffuse transmittance */
    double& UpwardDirectTransmittance,             /** Upward direct transmittance */
    double& UpwardDiffuseTransmittanceForRayleigh, /** upward diffuse transmittance for rayleigh */
    double& UpwardDiffuseTransmittanceForAerosol   /** supward diffuse transmittance for aerosols */
    )
{
  // geometrical conditions
  otb_6s_doublereal asol(static_cast<otb_6s_doublereal>(SolarZenithalAngle));
  otb_6s_doublereal phi0(static_cast<otb_6s_doublereal>(SolarAzimutalAngle));
  otb_6s_doublereal avis(static_cast<otb_6s_doublereal>(ViewingZenithalAngle));
  otb_6s_doublereal phiv(static_cast<otb_6s_doublereal>(ViewingAzimutalAngle));
  otb_6s_integer    month(static_cast<otb_6s_integer>(Month));
  otb_6s_integer    jday(static_cast<otb_6s_integer>(Day));
  otb_6s_doublereal pressure(static_cast<otb_6s_doublereal>(AtmosphericPressure));
  otb_6s_doublereal uw(static_cast<otb_6s_doublereal>(WaterVaporAmount));
  otb_6s_doublereal uo3(static_cast<otb_6s_doublereal>(OzoneAmount));
  // atmospheric model
  otb_6s_integer    iaer(static_cast<otb_6s_integer>(AerosolModel));
  otb_6s_doublereal taer55(static_cast<otb_6s_doublereal>(AerosolOptical));

  // Init output parameters
  AtmosphericReflectance                = 0.;
  AtmosphericSphericalAlbedo            = 0.;
  TotalGaseousTransmission              = 0.;
  DownwardTransmittance                 = 0.;
  UpwardTransmittance                   = 0.;
  UpwardDiffuseTransmittance            = 0.;
  UpwardDirectTransmittance             = 0.;
  UpwardDiffuseTransmittanceForRayleigh = 0.;
  UpwardDiffuseTransmittanceForAerosol  = 0.;

  otb_6s_doublereal wlinf(0.), wlsup(0.);
  otb_6s_doublereal otb_ratm__(0.), sast(0.), tgasm(0.), sdtott(0.), sutott(0.);
  otb_6s_doublereal tdif_up(0.), tdir_up(0.), tdif_up_ray(0.), tdif_up_aer(0.);

  // 6S official Wavelength Spectral Band step value
  const float SIXSStepOfWavelengthSpectralBandValues = .0025;
  // Generate 6s Wavelength Spectral Band with the offcicial step value
  ComputeWavelengthSpectralBandValuesFor6S(SIXSStepOfWavelengthSpectralBandValues,
                                           WavelengthSpectralBand // Update
                                           );
  try
  {

    // 6S official tab size Wavelength Spectral
    const otb_6s_integer S_6S_SIZE = 1501;
    // Generate WavelengthSpectralBand  in 6S compatible buffer s[1501]
    wlinf                        = static_cast<otb_6s_doublereal>(WavelengthSpectralBand->GetMinSpectralValue());
    wlsup                        = static_cast<otb_6s_doublereal>(WavelengthSpectralBand->GetMaxSpectralValue());
    otb_6s_integer          iinf = static_cast<otb_6s_integer>((wlinf - (float).25) / SIXSStepOfWavelengthSpectralBandValues + (float)1.5);
    otb_6s_integer          cpt  = iinf - 1;
    otb_6s_doublereal       s[S_6S_SIZE];
    const ValuesVectorType& FilterFunctionValues6S = WavelengthSpectralBand->GetFilterFunctionValues6S();
    // Set the values of FilterFunctionValues6S in s between [iinf-1; isup]
    for (unsigned int i = 0; i < FilterFunctionValues6S.size() && cpt < S_6S_SIZE; ++i)
    {
      s[cpt] = FilterFunctionValues6S[i];
      ++cpt;
    }

    // Call 6s main function
    otbMsgDevMacro(<< "Start call 6S main function ...");
    otb_6s_ssssss_otb_main_function(&asol, &phi0, &avis, &phiv, &month, &jday, &pressure, &uw, &uo3, &iaer, &taer55, &wlinf, &wlsup, s, &otb_ratm__, &sast,
                                    &tgasm, &sdtott, &sutott, &tdif_up, &tdir_up, &tdif_up_ray, &tdif_up_aer);
    otbMsgDevMacro(<< "Done call 6S main function!");
  }
  catch (std::bad_alloc& err)
  {
    itkGenericExceptionMacro(<< "Exception bad_alloc in SIXSTraits class: " << (char*)err.what());
  }
  catch (...)
  {
    itkGenericExceptionMacro(<< "Unknown exception in SIXSTraits class (catch(...)");
  }

  // Set outputs parameters
  AtmosphericReflectance                = static_cast<double>(otb_ratm__);
  AtmosphericSphericalAlbedo            = static_cast<double>(sast);
  TotalGaseousTransmission              = static_cast<double>(tgasm);
  DownwardTransmittance                 = static_cast<double>(sdtott);
  UpwardTransmittance                   = static_cast<double>(sutott);
  UpwardDiffuseTransmittance            = static_cast<double>(tdif_up);
  UpwardDirectTransmittance             = static_cast<double>(tdir_up);
  UpwardDiffuseTransmittanceForRayleigh = static_cast<double>(tdif_up_ray);
  UpwardDiffuseTransmittanceForAerosol  = static_cast<double>(tdif_up_aer);
}

void SIXSTraits::ComputeWavelengthSpectralBandValuesFor6S(const double SIXSStepOfWavelengthSpectralBandValues, WavelengthSpectralType* WavelengthSpectralBand)
{
  const double            epsilon(.000001);
  const double            L_min                = static_cast<double>(WavelengthSpectralBand->GetMinSpectralValue());
  const double            L_max                = static_cast<double>(WavelengthSpectralBand->GetMaxSpectralValue());
  const double            L_userStep           = static_cast<double>(WavelengthSpectralBand->GetUserStep());
  const ValuesVectorType& FilterFunctionValues = WavelengthSpectralBand->GetFilterFunctionValues();
  unsigned int            i                    = 1;
  unsigned int            j                    = 1;
  const double            invStep              = static_cast<double>(1. / L_userStep);
  double                  value(0.);

  if (FilterFunctionValues.size() <= 1)
  {
    itkGenericExceptionMacro(<< "The FilterFunctionValues vector must have more than 1 values !");
  }
  if (!(L_min + static_cast<double>(FilterFunctionValues.size() - 1) * L_userStep < (L_max + epsilon)))
  {
    itkGenericExceptionMacro(<< "The following condition: " << L_min << "+(" << FilterFunctionValues.size() << "-1)*" << L_userStep << " < (" << L_max << "+"
                             << epsilon << ") is not respected !"
                             << "val1 " << L_min + static_cast<double>(FilterFunctionValues.size() - 1) * L_userStep << " val2 " << L_max - epsilon);
  }

  // Generate WavelengthSpectralBand if the step is not the official 6S step value
  if (std::abs(L_userStep - SIXSStepOfWavelengthSpectralBandValues) > epsilon)
  {
    ValuesVectorType values(1, FilterFunctionValues[0]); // vector size 1 with the value vect[0]

    // Stop the interpolation at the max spectral value.
    value = SIXSStepOfWavelengthSpectralBandValues;
    while (L_min + value <= L_max)
    {
      // Search the User interval that surround the StepOfWavelengthSpectralBandValues current value.

      // removed the <= here, might be wrong
      while (j * L_userStep < value)
      {
        ++j;
      }

      // Check if we are not out of bound
      if (j >= FilterFunctionValues.size())
      {
        itkGenericExceptionMacro(<< "Index " << j << " out of bound for FilterFunctionValues vector (size: " << FilterFunctionValues.size() << ")."
                                 << " and value is " << value << " and SIXSStepOfWavelengthSpectralBandValues is " << SIXSStepOfWavelengthSpectralBandValues
                                 << " and i is " << i);
      }

      double valueTemp;
      valueTemp =
          static_cast<double>(FilterFunctionValues[j - 1]) +
          ((static_cast<double>(FilterFunctionValues[j]) - static_cast<double>(FilterFunctionValues[j - 1])) * invStep) * (value - L_userStep * (j - 1));
      values.push_back(static_cast<WavelengthSpectralBandType>(valueTemp));

      ++i;
      value += SIXSStepOfWavelengthSpectralBandValues;
    }

    if (L_min + (i - 1) * SIXSStepOfWavelengthSpectralBandValues != L_max)
    {
      values.push_back(0);
    }
    // Store this values
    WavelengthSpectralBand->SetFilterFunctionValues6S(values);
    // Store the new Max MaxSpectralValue
    WavelengthSpectralBand->SetMaxSpectralValue(static_cast<WavelengthSpectralBandType>(L_min + i * SIXSStepOfWavelengthSpectralBandValues));
  }
  else
  {
    // Init with copy of FilterFunctionValues input vector values
    WavelengthSpectralBand->SetFilterFunctionValues6S(FilterFunctionValues);
  }
}

void SIXSTraits::ComputeEnvironmentalContribution(const double diffuseTransmittanceForRayleighScattering, const double diffuseTransmittanceForAerosolScattering,
                                                  const double radiusInKilometers, const double altitude, const double cosineOfViewingAngle,
                                                  double& rayleighEstimation, double& aerosolEstimation, double& globalEstimation)
{
  otb_6s_doublereal difr(static_cast<otb_6s_doublereal>(diffuseTransmittanceForRayleighScattering));
  otb_6s_doublereal difa(static_cast<otb_6s_doublereal>(diffuseTransmittanceForAerosolScattering));
  otb_6s_doublereal rad(static_cast<otb_6s_doublereal>(radiusInKilometers));
  otb_6s_doublereal palt(static_cast<otb_6s_doublereal>(altitude));
  otb_6s_doublereal xmuv(static_cast<otb_6s_doublereal>(cosineOfViewingAngle));
  otb_6s_doublereal fra(0.), fae(0.), fr(0.);
  otb_6s_enviro_(&difr, &difa, &rad, &palt, &xmuv, &fra, &fae, &fr);
  rayleighEstimation = static_cast<double>(fra);
  aerosolEstimation  = static_cast<double>(fae);
  globalEstimation   = static_cast<double>(fr);
}

} // namespace otb
