#    Copyright 2023 Alireza Aghamohammadi

#    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.
import math


class SunPosition:
    r"""
    A class to model sun position.
    """

    def solar_declination(self, day_of_year: int) -> float:
        r"""
        Calculate the solar declination angle in radians [1]_.

        The solar declination angle is the angle between the rays of the sun
        and the plane of the Earth's equator.

        The formula used to calculate the solar declination angle is:

        .. math::
            \delta = \frac{23.45 \pi}{180} \times
            \sin \left(\frac{2\pi~(284 + n)}{365}\right)

        | :math:`n` is the day of the year (i.e., ``day_of_year``)

        :param day_of_year: The day of the year.
        :type day_of_year: int
        :return: The solar declination angle in radians.
        :rtype: float

        References
        ----------
        .. [1] Cooper, P. (1969). The absorption of radiation in solar stills.
                Solar Energy, 12(3), 333-346.
        """
        # tilt of the Earth's axis (in degrees)
        earth_tilt_degrees = 23.45

        # Convert the tilt to radians
        earth_tilt_radians = math.radians(earth_tilt_degrees)

        # Offset to ensure declination angle is zero at the March equinox
        equinox_offset_days = 284

        # Calculate the solar declination angle
        solar_declination = (
            math.sin((2 * math.pi) * (equinox_offset_days + day_of_year) / 365)
            * earth_tilt_radians
        )

        return solar_declination

    def hour_angle(self, solar_time: float) -> float:
        r"""
        Calculate the hour angle based on the solar time.

        The hour angle is a measure of time, expressed in angular terms,
        from solar noon.

        The formula used to calculate the hour angle is:

        .. math::
            \omega = (t - 12\times 60 \times 60)
            \times \frac{\pi}{12\times 60 \times 60}

        :param solar_time: The solar time in seconds.
        :type solar_time: float
        :return: The hour angle in radians.
        :rtype: float
        """
        # The number of seconds in half a day (12 hours)
        seconds_in_half_day = 12 * 60 * 60

        # The Earth rotates by pi/seconds_in_half_day radians per second
        earth_rotation_rate = math.pi / seconds_in_half_day

        # Calculate the hour angle
        hour_angle = (solar_time - seconds_in_half_day) * earth_rotation_rate

        return hour_angle

    def solar_time(self, hour_angle: float) -> float:
        r"""
        Calculate the solar time based on the hour angle.

        The solar time is a measure of time, expressed in seconds, from solar noon.

        The formula used to calculate the solar time is:

        .. math::
            t = \omega \times \frac{12 \times 60 \times 60}{\pi}
            + 12 \times 60 \times 60

        :param hour_angle: The hour angle in radians.
        :type hour_angle: float
        :return: The solar time in seconds.
        :rtype: float
        """
        # The number of seconds in half a day (12 hours)
        seconds_in_half_day = 12 * 60 * 60

        # The Earth rotates by pi/seconds_in_half_day radians per second
        earth_rotation_rate = math.pi / seconds_in_half_day

        # Calculate the solar time
        solar_time = hour_angle / earth_rotation_rate + seconds_in_half_day

        return solar_time
