/******************************************************************************
 *
 * Purpose:  Implementation of ReflectanceCalculator class. Calculate
 *           reflectance values from radiance, for visual bands.
 * Author:   Bas Retsios, retsios@itc.nl
 *
 ******************************************************************************
 * Copyright (c) 2004, ITC
 *
 * SPDX-License-Identifier: MIT
 ******************************************************************************/

#include "cpl_port.h"  // Must be first.

#include "reflectancecalculator.h"
#include <cmath>
#include <cstdlib>

using namespace std;

//////////////////////////////////////////////////////////////////////
// Construction/Destruction
//////////////////////////////////////////////////////////////////////

ReflectanceCalculator::ReflectanceCalculator(const std::string &sTimeStamp,
                                             double rRTOA)
    : m_rRTOA(rRTOA)
{
    std::string sYear(sTimeStamp.substr(0, 4));
    std::string sMonth(sTimeStamp.substr(4, 2));
    std::string sDay(sTimeStamp.substr(6, 2));
    std::string sHours(sTimeStamp.substr(8, 2));
    std::string sMins(sTimeStamp.substr(10, 2));

    m_iYear = atoi(sYear.c_str());
    int iMonth = atoi(sMonth.c_str());
    m_iDay = atoi(sDay.c_str());
    for (int i = 1; i < iMonth; ++i)
        m_iDay += iDaysInMonth(i, m_iYear);
    int iHours = atoi(sHours.c_str());
    int iMins = atoi(sMins.c_str());

    m_rHours = iHours + iMins / 60.0;
}

ReflectanceCalculator::~ReflectanceCalculator()
{
}

double ReflectanceCalculator::rGetReflectance(double rRadiance, double rLat,
                                              double rLon) const
{
    double phi = rLat * M_PI / 180;
    // double lam = rLon * M_PI / 180;
    double rSunDist = rSunDistance();
    double ReflectanceNumerator = rRadiance * rSunDist * rSunDist;
    double zenithAngle = rZenithAngle(phi, rDeclination(), rHourAngle(rLon));
    double ReflectanceDenominator = m_rRTOA * cos(zenithAngle * M_PI / 180);
    double Reflectance = ReflectanceNumerator / ReflectanceDenominator;
    return Reflectance;
}

double ReflectanceCalculator::rZenithAngle(double phi, double rDeclin,
                                           double l_rHourAngle)
{
    double rCosZen =
        (sin(phi) * sin(rDeclin) + cos(phi) * cos(rDeclin) * cos(l_rHourAngle));
    double zenithAngle = acos(rCosZen) * 180 / M_PI;
    return zenithAngle;
}

double ReflectanceCalculator::rDeclination() const
{
    double rJulianDay = m_iDay - 1;
    double yearFraction = (rJulianDay + m_rHours / 24) / iDaysInYear(m_iYear);
    double T = 2 * M_PI * yearFraction;

    double declin = 0.006918 - 0.399912 * cos(T) + 0.070257 * sin(T) -
                    0.006758 * cos(2 * T) + 0.000907 * sin(2 * T) -
                    0.002697 * cos(3 * T) + 0.00148 * sin(3 * T);
    return declin;
}

double ReflectanceCalculator::rHourAngle(double rLon) const
{
    // In: rLon (in degrees)
    // Out: hourAngle (in radians)
    double rJulianDay = m_iDay - 1;
    double yearFraction = (rJulianDay + m_rHours / 24) / iDaysInYear(m_iYear);
    double T = 2 * M_PI * yearFraction;

    double EOT2 = 229.18 * (0.000075 + 0.001868 * cos(T) - 0.032077 * sin(T));
    double EOT3 = 229.18 * (-0.014615 * cos(2 * T) - 0.040849 * sin(2 * T));
    double EOT = EOT2 + EOT3;
    double TimeOffset = EOT + (4. * rLon);
    // True solar time in minutes:
    double TrueSolarTime = m_rHours * 60 + TimeOffset;
    // Solar hour angle in degrees and in radians:
    double HaDegr = (TrueSolarTime / 4. - 180.);
    double hourAngle = HaDegr * M_PI / 180;
    return hourAngle;
}

double ReflectanceCalculator::rSunDistance() const
{
    int iJulianDay = m_iDay - 1;
    double theta = 2 * M_PI * (iJulianDay - 3) / 365.25;
    // rE0 is the inverse of the square of the sun-distance ratio
    double rE0 = 1.000110 + 0.034221 * cos(theta) + 0.00128 * sin(theta) +
                 0.000719 * cos(2 * theta) + 0.000077 * sin(2 * theta);
    // The calculated distance is expressed as a factor of the "average
    // sun-distance" (on 1 Jan approx. 0.98, on 1 Jul approx. 1.01)
    return 1 / sqrt(rE0);
}

int ReflectanceCalculator::iDaysInYear(int iYear)
{
    bool fLeapYear = iDaysInMonth(2, iYear) == 29;

    if (fLeapYear)
        return 366;
    else
        return 365;
}

int ReflectanceCalculator::iDaysInMonth(int iMonth, int iYear)
{
    int iDays;

    if ((iMonth == 4) || (iMonth == 6) || (iMonth == 9) || (iMonth == 11))
        iDays = 30;
    else if (iMonth == 2)
    {
        iDays = 28;
        if (iYear % 100 == 0)  // century year
        {
            if (iYear % 400 == 0)  // century leap year
                ++iDays;
        }
        else
        {
            if (iYear % 4 == 0)  // normal leap year
                ++iDays;
        }
    }
    else
        iDays = 31;

    return iDays;
}
