/*============================================================================
 *    Contains:
 *        S_solpos     (computes solar position and intensity
 *                      from time and place)
 *
 *            INPUTS:     (via posdata struct) year, daynum, hour,
 *                        minute, second, latitude, longitude, timezone,
 *                        intervl
 *            OPTIONAL:   (via posdata struct) month, day, press, temp, tilt,
 *                        aspect, function
 *            OUTPUTS:    EVERY variable in the struct posdata
 *                            (defined in solpos.h)
 *
 *                       NOTE: Certain conditions exist during which some of
 *                       the output variables are undefined or cannot be
 *                       calculated.  In these cases, the variables are
 *                       returned with flag values indicating such.  In other
 *                       cases, the variables may return a realistic, though
 *                       invalid, value. These variables and the flag values
 *                       or invalid conditions are listed below:
 *
 *                       amass     -1.0 at zenetr angles greater than 93.0
 *                                 degrees
 *                       ampress   -1.0 at zenetr angles greater than 93.0
 *                                 degrees
 *                       azim      invalid at zenetr angle 0.0 or latitude
 *                                 +/-90.0 or at night
 *                       elevetr   limited to -9 degrees at night
 *                       etr       0.0 at night
 *                       etrn      0.0 at night
 *                       etrtilt   0.0 when cosinc is less than 0
 *                       prime     invalid at zenetr angles greater than 93.0
 *                                 degrees
 *                       sretr     +/- 2999.0 during periods of 24 hour sunup or
 *                                 sundown
 *                       ssetr     +/- 2999.0 during periods of 24 hour sunup or
 *                                 sundown
 *                       ssha      invalid at the North and South Poles
 *                       unprime   invalid at zenetr angles greater than 93.0
 *                                 degrees
 *                       zenetr    limited to 99.0 degrees at night
 *
 *        S_init       (optional initialization for all input parameters in
 *                      the posdata struct)
 *           INPUTS:     struct posdata*
 *           OUTPUTS:    struct posdata*
 *
 *                     (Note: initializes the required S_solpos INPUTS above
 *                      to out-of-bounds conditions, forcing the user to
 *                      supply the parameters; initializes the OPTIONAL
 *                      S_solpos inputs above to nominal values.)
 *
 *       S_decode      (optional utility for decoding the S_solpos return code)
 *           INPUTS:     long integer S_solpos return value, struct posdata*
 *           OUTPUTS:    text to stderr
 *
 *    Usage:
 *         In calling program, just after other 'includes', insert:
 *
 *              #include "solpos.h"
 *
 *         Function calls:
 *              S_init(struct posdata*)  [optional]
 *              .
 *              .
 *              [set time and location parameters before S_solpos call]
 *              .
 *              .
 *              int retval = S_solpos(struct posdata*)
 *              S_decode(int retval, struct posdata*) [optional]
 *                  (Note: you should always look at the S_solpos return
 *                   value, which contains error codes. S_decode is one option
 *                   for examining these codes.  It can also serve as a
 *                   template for building your own application-specific
 *                   decoder.)
 *
 *    Martin Rymes
 *    National Renewable Energy Laboratory
 *    25 March 1998
 *
 *    27 April 1999 REVISION:  Corrected leap year in S_date.
 *    13 January 2000 REVISION:  SMW converted to structure posdata parameter
 *                               and subdivided into functions.
 *    01 February 2001 REVISION: SMW corrected ecobli calculation 
 *                               (changed sign). Error is small (max 0.015 deg
 *                               in calculation of declination angle)
 *==============================================================================*/
 
#include <math.h>
#include <string.h>
#include <stdio.h>
#include "solpos.h"

/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 * Structures defined for this module
 *++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
 
struct trigdata     // used to pass calculated values locally
{
    float cd;       // cosine of the declination
    float ch;       // cosine of the hour angle
    float cl;       // cosine of the latitude
    float sd;       // sine of the declination
    float sl;       // sine of the latitude
};

/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 * Temporary global variables used only in this file:
 *++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
 
static int  month_days[2][13] = {
   { 0,   0,  31,  59,  90, 120, 151, 181, 212, 243, 273, 304, 334 },
   { 0,   0,  31,  60,  91, 121, 152, 182, 213, 244, 274, 305, 335 } };
                      // Cumulative number of days prior to beginning of month

static float degrad = 57.295779513;      // converts from radians to degrees
static float raddeg = 0.0174532925;      // converts from degrees to radians

/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 * Local function prototypes
 *++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
 
static long int validate(struct posdata *pdat);
static void dom2doy(struct posdata *pdat);
static void doy2dom(struct posdata *pdat);
static void geometry(struct posdata *pdat);
static void zen_no_ref(struct posdata *pdat, struct trigdata *tdat);
static void ssha(struct posdata *pdat, struct trigdata *tdat);
static void sbcf(struct posdata *pdat, struct trigdata *tdat);
static void tst(struct posdata *pdat);
static void srss(struct posdata *pdat);
static void sazm(struct posdata *pdat, struct trigdata *tdat);
static void refrac(struct posdata *pdat);
static void amass(struct posdata *pdat);
static void prime(struct posdata *pdat);
static void etr(struct posdata *pdat);
static void tilt(struct posdata *pdat);
static void localtrig(struct posdata *pdat, struct trigdata *tdat);

/*============================================================================
 *    Long integer function S_solpos, adapted from the VAX solar libraries
 *
 *    This function calculates the apparent solar position and the
 *    intensity of the sun (theoretical maximum solar energy) from
 *    time and place on Earth.
 *
 *    Requires (from the struct posdata parameter):
 *        Date and time:
 *            year
 *            daynum   (requirement depends on the S_DOY switch)
 *            month    (requirement depends on the S_DOY switch)
 *            day      (requirement depends on the S_DOY switch)
 *            hour
 *            minute
 *            second
 *            interval  DEFAULT 0
 *        Location:
 *            latitude
 *            longitude
 *        Location/time adjuster:
 *            timezone
 *        Atmospheric pressure and temperature:
 *            press     DEFAULT 1013.0 mb
 *            temp      DEFAULT 10.0 degrees C
 *        Tilt of flat surface that receives solar energy:
 *            aspect    DEFAULT 180 (South)
 *            tilt      DEFAULT 0 (Horizontal)
 *        Function Switch (codes defined in solpos.h)
 *            function  DEFAULT S_ALL
 *
 *    Returns (via the struct posdata parameter):
 *        everything defined in the struct posdata in solpos.h.
 *============================================================================*/
 
long S_solpos (struct posdata *pdat)
{
  long int retval;

  struct trigdata trigdat, *tdat;

  tdat = &trigdat;                       // point to the structure

/* initialize the trig structure */

  tdat->sd = -999.0;                   // flag to force calculation of trig data
  tdat->cd =    1.0;
  tdat->ch =    1.0;                   // set the rest of these to something safe
  tdat->cl =    1.0;
  tdat->sl =    1.0;

  if ((retval = validate(pdat)) != 0)  // validate the inputs
    return retval; 

//   pdat->function &= ~S_DOY;  // sets month and day input rather than daynum
    dom2doy(pdat);                     // convert input month-day to doy

  if (pdat->function & L_DOY)
    doy2dom(pdat);                     // convert input doy to month-day
  else
    dom2doy(pdat);                     // convert input month-day to doy

  if (pdat->function & L_GEOM)
    geometry(pdat);                    // do basic geometry calculations

  if (pdat->function & L_ZENETR)       // etr at non-refracted zenith angle
    zen_no_ref(pdat, tdat);

  if (pdat->function & L_SSHA)         // Sunset hour calculation
    ssha(pdat, tdat);

  if (pdat->function & L_SBCF)         // Shadowband correction factor
    sbcf(pdat, tdat);

  if (pdat->function & L_TST)          // true solar time
    tst(pdat);

  if (pdat->function & L_SRSS)         // sunrise/sunset calculations
    srss(pdat);

  if (pdat->function & L_SOLAZM)       // solar azimuth calculations
    sazm(pdat, tdat);

  if (pdat->function & L_REFRAC)       // atmospheric refraction calculations
    refrac(pdat);

  if (pdat->function & L_AMASS)        // airmass calculations
    amass(pdat);

  if (pdat->function & L_PRIME)        // kt-prime/unprime calculations
    prime(pdat);

  if (pdat->function & L_ETR)          // ETR and ETRN (refracted)
    etr(pdat);

  if (pdat->function & L_TILT)         // tilt calculations
    tilt(pdat);

    return 0;
}

/*============================================================================
 *    Void function S_init
 *
 *    This function initiates all of the input parameters in the struct
 *    posdata passed to S_solpos().  Initialization is either to nominal
 *    values or to out of range values, which forces the calling program to
 *    specify parameters.
 *
 *    NOTE: This function is optional if you initialize ALL input parameters
 *          in your calling code.  Note that the required parameters of date
 *          and location are deliberately initialized out of bounds to force
 *          the user to enter real-world values.
 *
 *    Requires: Pointer to a posdata structure, members of which are
 *           initialized.
 *
 *    Returns: Void
 *============================================================================*/
 
void S_init(struct posdata *pdat)
{
  pdat->day       =    -99;   // Day of month (May 27 = 27, etc.)
  pdat->daynum    =   -999;   // Day number (day of year; Feb 1 = 32)
  pdat->hour      =    -99;   // Hour of day, 0 - 23
  pdat->minute    =    -99;   // Minute of hour, 0 - 59
  pdat->month     =    -99;   // Month number (Jan = 1, Feb = 2, etc.)
  pdat->second    =    -99;   // Second of minute, 0 - 59
  pdat->year      =    -99;   // 4-digit year
  pdat->interval  =      0;   // instantaneous measurement interval
  pdat->aspect    =  180.0;   // Azimuth of panel surface (direction it
                              // faces) N=0, E=90, S=180, W=270
  pdat->latitude  =  -99.0;   // Latitude, degrees north (south negative)
  pdat->longitude = -999.0;   // Longitude, degrees east (west negative)
  pdat->press     = 1013.0;   // Surface pressure, millibars
  pdat->solcon    = 1367.0;   // Solar constant, 1367 W/sq m
  pdat->temp      =   15.0;   // Ambient dry-bulb temperature, degrees C
  pdat->tilt      =    0.0;   // Degrees tilt from horizontal of panel
  pdat->timezone  =  -99.0;   // Time zone, east (west negative).
  pdat->sbwid     =    7.6;   // Eppley shadow band width
  pdat->sbrad     =   31.7;   // Eppley shadow band radius
  pdat->sbsky     =   0.04;   // Drummond factor for partly cloudy skies
  pdat->function  =  S_ALL;   // compute all parameters
}


/*============================================================================
 *    Local long int function validate
 *
 *    Validates the input parameters
 *============================================================================*/
 
static long int validate (struct posdata *pdat)
{

   long int retval = 0;  // start with no errors

/* No absurd dates, please */

   if (pdat->function & L_GEOM)
   {
      if ((pdat->year < 1950) || (pdat->year > 2050))    // limits of algoritm
         retval |= (1L << S_YEAR_ERROR);
      if (!(pdat->function & S_DOY) && ((pdat->month < 1) || (pdat->month > 12)))
         retval |= (1L << S_MONTH_ERROR);
      if (!(pdat->function & S_DOY) && ((pdat->day < 1) || (pdat->day > 31)))
         retval |= (1L << S_DAY_ERROR);
      if ((pdat->function & S_DOY) && ((pdat->daynum < 1) || (pdat->daynum > 366)))
         retval |= (1L << S_DOY_ERROR);

/* No absurd times, please */

      if ((pdat->hour < 0) || (pdat->hour > 24))
         retval |= (1L << S_HOUR_ERROR);
      if ((pdat->minute < 0) || (pdat->minute > 59))
         retval |= (1L << S_MINUTE_ERROR);
      if ((pdat->second < 0) || (pdat->second > 59))
         retval |= (1L << S_SECOND_ERROR);
      if ((pdat->hour == 24) && (pdat->minute > 0))      // no more than 24 hrs
         retval |= ((1L << S_HOUR_ERROR) | (1L << S_MINUTE_ERROR));
      if ((pdat->hour == 24) && (pdat->second > 0))      // no more than 24 hrs
         retval |= ((1L << S_HOUR_ERROR) | (1L << S_SECOND_ERROR));
      if (fabs (pdat->timezone) > 12.0)
         retval |= (1L << S_TZONE_ERROR);
      if ((pdat->interval < 0) || (pdat->interval > 28800))
         retval |= (1L << S_INTRVL_ERROR);

/* No absurd locations, please */

      if (fabs (pdat->longitude) > 180.0)
         retval |= (1L << S_LON_ERROR);
      if (fabs (pdat->latitude) > 90.0)
         retval |= (1L << S_LAT_ERROR);
   }

/* No silly temperatures or pressures, please */

   if ((pdat->function & L_REFRAC) && (fabs (pdat->temp) > 100.0))
      retval |= (1L << S_TEMP_ERROR);
   if ((pdat->function & L_REFRAC) &&
      (pdat->press < 0.0) || (pdat->press > 2000.0))
      retval |= (1L << S_PRESS_ERROR);

/* No out of bounds tilts, please */

   if ((pdat->function & L_TILT) && (fabs (pdat->tilt) > 180.0))
      retval |= (1L << S_TILT_ERROR);
   if ((pdat->function & L_TILT) && (fabs (pdat->aspect) > 360.0))
      retval |= (1L << S_ASPECT_ERROR);

/* No oddball shadowbands, please */

   if ((pdat->function & L_SBCF) &&
      (pdat->sbwid < 1.0) || (pdat->sbwid > 100.0))
      retval |= (1L << S_SBWID_ERROR);
   if ((pdat->function & L_SBCF) &&
      (pdat->sbrad < 1.0) || (pdat->sbrad > 100.0))
      retval |= (1L << S_SBRAD_ERROR);
   if ((pdat->function & L_SBCF) && (fabs (pdat->sbsky) > 1.0))
      retval |= (1L << S_SBSKY_ERROR);

   return retval;
}


/*============================================================================
*    Local Void function dom2doy
*
*    Converts day-of-month to day-of-year
*
*    Requires (from struct posdata parameter):
*            year
*            month
*            day
*
*    Returns (via the struct posdata parameter):
*            year
*            daynum
*============================================================================*/

static void dom2doy(struct posdata *pdat)
{
   pdat->daynum = pdat->day + month_days[0][pdat->month];

/* Adjust for leap year */

   if (((pdat->year % 4) == 0) &&
      (((pdat->year % 100) != 0) || ((pdat->year % 400) == 0)) &&
      (pdat->month > 2))
      pdat->daynum += 1;
}

/*============================================================================
*    Local void function doy2dom
*
*    This function computes the month/day from the day number.
*
*    Requires (from struct posdata parameter):
*        Year and day number:
*            year
*            daynum
*
*    Returns (via the struct posdata parameter):
*            year
*            month
*            day
*============================================================================*/

static void doy2dom(struct posdata *pdat)
{
   int  imon;                  // Month (month_days) array counter
   int  leap;                  // leap year switch

/* Set the leap year switch */

   if (((pdat->year % 4) == 0) &&
         (((pdat->year % 100) != 0) || ((pdat->year % 400) == 0)))
      leap = 1;
   else
      leap = 0;

/* Find the month */

   imon = 12;
   while (pdat->daynum <= month_days[leap][imon])
      --imon;

/* Set the month and day of month */

   pdat->month = imon;
   pdat->day = pdat->daynum - month_days[leap][imon];
}

/*============================================================================
*    Local Void function geometry
*
*    Does the underlying geometry for a given time and location
*=============================================================================*/

static void geometry (struct posdata *pdat)
{
   float bottom;      // denominator (bottom) of the fraction
   float c2;          // cosine of d2
   float cd;          // cosine of the day angle or delination
   float d2;          // pdat->dayang times two
   float delta;       // difference between current year and 1949
   float s2;          // sine of d2
   float sd;          // sine of the day angle
   float top;         // numerator (top) of the fraction
   int   leap;        // leap year counter

/* Day angle.
 * Iqbal, M.  1983.  An Introduction to Solar Radiation.
 * Academic Press, NY., page 3.
 */
   pdat->dayang = 360.0*(pdat->daynum - 1)/365.0;

/* Earth radius vector * solar constant = solar energy.
 * Spencer, J. W.  1971.  Fourier series representation of the
 * position of the sun.  Search 2 (5), page 172.
 */
            
   sd     = sin(raddeg*pdat->dayang);
   cd     = cos(raddeg*pdat->dayang);
   d2     = 2.0*pdat->dayang;
   c2     = cos(raddeg*d2);
   s2     = sin(raddeg*d2);

   pdat->erv = 1.000110 + 0.034221*cd + 0.001280*sd;
   pdat->erv += 0.000719*c2 + 0.000077*s2;

/* Universal Coordinated (Greenwich standard) time.
 * Michalsky, J.  1988.  The Astronomical Almanac's algorithm for
 * approximate solar position (1950-2050).  Solar Energy 40 (3),
 * pp. 227-235.
 */
   pdat->utime =
        pdat->hour*3600.0 + pdat->minute*60.0 + pdat->second - (float)pdat->interval/2.0;
   pdat->utime = pdat->utime/3600.0 - pdat->timezone;

/* Julian Day minus 2,400,000 days (to eliminate roundoff errors).
 * Michalsky, J.  1988.  The Astronomical Almanac's algorithm for
 * approximate solar position (1950-2050).  Solar Energy 40 (3),
 * pp. 227-235.
 */

/* No adjustment for century non-leap years since this function is
 * bounded by 1950 - 2050
 */
   delta = pdat->year - 1949;
   leap = (int)(delta/4.0);
   pdat->julday = 32916.5 + delta*365.0 + leap + pdat->daynum + pdat->utime/24.0;

/* Time used in the calculation of ecliptic coordinates  
 * Noon 1 JAN 2000 = 2,400,000 + 51,545 days Julian Date  
 * Michalsky, J.  1988.  The Astronomical Almanac's algorithm for
 * approximate solar position (1950-2050).  Solar Energy 40 (3),
 * pp. 227-235.
 */
   pdat->ectime = pdat->julday - 51545.0;

/* Mean longitude.
 * Michalsky, J.  1988.  The Astronomical Almanac's algorithm for
 * approximate solar position (1950-2050).  Solar Energy 40 (3),
 * pp. 227-235.
 */
   pdat->mnlong  = 280.460 + 0.9856474*pdat->ectime;

/* Dump the multiples of 360, so the answer is between 0 and 360 */

   pdat->mnlong -= 360.0*(int)(pdat->mnlong/360.0);
   if (pdat->mnlong < 0.0) pdat->mnlong += 360.0;

/* Mean anomaly.
 * Michalsky, J.  1988.  The Astronomical Almanac's algorithm for
 * approximate solar position (1950-2050).  Solar Energy 40 (3),
 * pp. 227-235.
 */
   pdat->mnanom = 357.528 + 0.9856003*pdat->ectime;

/* Dump the multiples of 360, so the answer is between 0 and 360 */

   pdat->mnanom -= 360.0*(int) (pdat->mnanom/360.0);
   if (pdat->mnanom < 0.0) pdat->mnanom += 360.0;

/* Ecliptic longitude.
 * Michalsky, J.  1988.  The Astronomical Almanac's algorithm for
 * approximate solar position (1950-2050).  Solar Energy 40 (3),
 * pp. 227-235.
 */
   pdat->eclong = pdat->mnlong + 1.915*sin(pdat->mnanom*raddeg) +
                  0.020*sin(2.0*pdat->mnanom*raddeg);

/* Dump the multiples of 360, so the answer is between 0 and 360 */

   pdat->eclong -= 360.0*(int)(pdat->eclong/360.0);
   if (pdat->eclong < 0.0) pdat->eclong += 360.0;

/* Obliquity of the ecliptic.
 * Michalsky, J.  1988.  The Astronomical Almanac's algorithm for
 * approximate solar position (1950-2050).  Solar Energy 40 (3),
 * pp. 227-235.
 */

/* 02 Feb 2001 SMW corrected sign in the following line
 *  pdat->ecobli = 23.439 + 4.0e-07*pdat->ectime;
 */
   pdat->ecobli = 23.439 - 4.0e-07*pdat->ectime;

/* Declination.
 * Michalsky, J.  1988.  The Astronomical Almanac's algorithm for
 * approximate solar position (1950-2050).  Solar Energy 40 (3),
 * pp. 227-235.
 */
   pdat->declin = degrad*asin(sin(pdat->ecobli*raddeg)*sin(pdat->eclong*raddeg));

/* Right ascension.
 * Michalsky, J.  1988.  The Astronomical Almanac's algorithm for
 * approximate solar position (1950-2050).  Solar Energy 40 (3),
 * pp. 227-235.
 */
   top = cos(raddeg*pdat->ecobli)*sin(raddeg*pdat->eclong);
   bottom = cos(raddeg*pdat->eclong);
   pdat->rascen =  degrad*atan2(top, bottom);

/* Make it a positive angle */

   if (pdat->rascen < 0.0) pdat->rascen += 360.0;

/* Greenwich mean sidereal time.
 * Michalsky, J.  1988.  The Astronomical Almanac's algorithm for
 * approximate solar position (1950-2050).  Solar Energy 40 (3),
 * pp. 227-235.
 */
   pdat->gmst  = 6.697375 + 0.0657098242*pdat->ectime + pdat->utime;

/* Dump the multiples of 24, so the answer is between 0 and 24 */

   pdat->gmst -= 24.0*(int)(pdat->gmst/24.0);
   if (pdat->gmst < 0.0) pdat->gmst += 24.0;

/* Local mean sidereal time.
 * Michalsky, J.  1988.  The Astronomical Almanac's algorithm for
 * approximate solar position (1950-2050).  Solar Energy 40 (3),
 * pp. 227-235.
 */
   pdat->lmst  = pdat->gmst*15.0 + pdat->longitude;

/* Dump the multiples of 360, so the answer is between 0 and 360 */

   pdat->lmst -= 360.0*(int)(pdat->lmst/360.0);
   if (pdat->lmst < 0.)
      pdat->lmst += 360.0;

/* Hour angle.
 * Michalsky, J.  1988.  The Astronomical Almanac's algorithm for
 * approximate solar position (1950-2050).  Solar Energy 40 (3),
 * pp. 227-235.
 */
    pdat->hrang = pdat->lmst - pdat->rascen;

/* Force it between -180 and 180 degrees */

   if (pdat->hrang < -180.0) pdat->hrang += 360.0;
   else if (pdat->hrang > 180.0) pdat->hrang -= 360.0;
}

/*============================================================================
*    Local Void function zen_no_ref
*
*    ETR solar zenith angle
*       Iqbal, M.  1983.  An Introduction to Solar Radiation.
*            Academic Press, NY., page 15
*============================================================================*/

static void zen_no_ref (struct posdata *pdat, struct trigdata *tdat)
{
   float cz;                          // cosine of the solar zenith angle

   localtrig(pdat, tdat);
   cz = tdat->sd*tdat->sl + tdat->cd*tdat->cl*tdat->ch;

/* Watch out for the roundoff errors */

   if (fabs (cz) > 1.0)
   {
      if (cz >= 0.0)
         cz =  1.0;
      else
         cz = -1.0;
   }

   pdat->zenetr = acos(cz)*degrad;

/* Limit the degrees below the horizon to 9 [+90 -> 99] */

   if (pdat->zenetr > 99.0) pdat->zenetr = 99.0;
   pdat->elevetr = 90.0 - pdat->zenetr;
}

/*============================================================================
*    Local Void function ssha
*
*    Sunset hour angle, degrees
*       Iqbal, M.  1983.  An Introduction to Solar Radiation.
*            Academic Press, NY., page 16
*============================================================================*/

static void ssha(struct posdata *pdat, struct trigdata *tdat)
{
   float cssha;                        // cosine of the sunset hour angle
   float cdcl;                         // (cd*cl)

   localtrig(pdat, tdat);
   cdcl = tdat->cd*tdat->cl;

   if (fabs (cdcl) >= 0.001)
   {
      cssha = -tdat->sl*tdat->sd/cdcl;

/* This keeps the cosine from blowing on roundoff */

      if (cssha < -1.0 )
          pdat->ssha = 180.0;
      else if (cssha > 1.0)
          pdat->ssha = 0.0;
      else
          pdat->ssha = degrad*acos(cssha);
   }
   else if (((pdat->declin >= 0.0) && (pdat->latitude > 0.0)) ||
              ((pdat->declin <  0.0) && (pdat->latitude < 0.0)))
      pdat->ssha = 180.0;
   else
      pdat->ssha = 0.0;
}

/*============================================================================
*    Local Void function sbcf
*
*    Shadowband correction factor
*       Drummond, A. J.  1956.  A contribution to absolute pyrheliometry.
*            Q. J. R. Meteorol. Soc. 82, pp. 481-493
*============================================================================*/

static void sbcf(struct posdata *pdat, struct trigdata *tdat)
{
   float p, t1, t2;                                 // used to compute sbcf

   localtrig(pdat, tdat);
   p = 0.6366198*pdat->sbwid/pdat->sbrad*pow (tdat->cd,3);
   t1 = tdat->sl*tdat->sd*pdat->ssha*raddeg;
   t2 = tdat->cl*tdat->cd*sin(pdat->ssha*raddeg);
   pdat->sbcf = pdat->sbsky + 1.0/(1.0 - p*(t1 + t2));

}

/*============================================================================
*    Local Void function tst
*
*    TST -> True Solar Time = local standard time + TSTfix, time
*      in minutes from midnight.
*        Iqbal, M.  1983.  An Introduction to Solar Radiation.
*            Academic Press, NY., page 13
*============================================================================*/

static void tst(struct posdata *pdat)
{
   pdat->tst = (180.0 + pdat->hrang)*4.0;
   pdat->tstfix = pdat->tst - (float)pdat->hour*60.0 - pdat->minute -
                  (float)pdat->second/60.0 + (float)pdat->interval/120.0;

/* Bound tstfix to this day */

   while (pdat->tstfix >  720.0)
      pdat->tstfix -= 1440.0;
        
   while (pdat->tstfix < -720.0)
      pdat->tstfix += 1440.0;

   pdat->eqntim = pdat->tstfix + 60.0*pdat->timezone - 4.0*pdat->longitude;
}

/*============================================================================
*    Local Void function srss
*
*    Sunrise and sunset times (minutes from midnight)
*============================================================================*/

static void srss(struct posdata *pdat)
{
   if (pdat->ssha <= 1.0)
   {
      pdat->sretr = 2999.0;
      pdat->ssetr = -2999.0;
   }
   else if (pdat->ssha >= 179.0)
   {
      pdat->sretr = -2999.0;
      pdat->ssetr = 2999.0;
   }
   else
   {
      pdat->sretr = 720.0 - 4.0*pdat->ssha - pdat->tstfix;
      pdat->ssetr = 720.0 + 4.0*pdat->ssha - pdat->tstfix;
   }
}

/*============================================================================
*    Local Void function sazm
*
*    Solar azimuth angle
*       Iqbal, M.  1983.  An Introduction to Solar Radiation.
*            Academic Press, NY., page 15
*=============================================================================*/

static void sazm(struct posdata *pdat, struct trigdata *tdat)
{
   float ca;          // cosine of the solar azimuth angle
   float ce;          // cosine of the solar elevation
   float cecl;        // (ce*cl)
   float se;          // sine of the solar elevation

   localtrig(pdat, tdat);
   ce = cos(raddeg*pdat->elevetr);
   se = sin(raddeg*pdat->elevetr);

   pdat->azim = 180.0;
   cecl = ce*tdat->cl;
   if (fabs (cecl) >= 0.001)
   {
      ca = (se*tdat->sl - tdat->sd)/cecl;
      if (ca > 1.0)
         ca = 1.0;
      else if (ca < -1.0)
         ca = -1.0;

      pdat->azim = 180.0 - acos(ca)*degrad;
      if (pdat->hrang > 0) pdat->azim  = 360.0 - pdat->azim;
   }
}

/*============================================================================
*    Local Int function refrac
*
*    Refraction correction, degrees
*        Zimmerman, John C.  1981.  Sun-pointing programs and their
*            accuracy.
*            SAND81-0761, Experimental Systems Operation Division 4721,
*            Sandia National Laboratories, Albuquerque, NM.
*============================================================================*/

static void refrac(struct posdata *pdat)
{
   float prestemp;    // temporary pressure/temperature correction
   float refcor;      // temporary refraction correction
   float tanelev;     // tangent of the solar elevation angle

/* If the sun is near zenith, the algorithm bombs; refraction near 0 */

    if (pdat->elevetr > 85.0) refcor = 0.0;

/* Otherwise, we have refraction */

    else
    {
       tanelev = tan (raddeg*pdat->elevetr);
       
       if (pdat->elevetr >= 5.0)
          refcor = 58.1/tanelev - 0.07/(pow (tanelev,3)) + 0.000086/(pow (tanelev,5));
       else if (pdat->elevetr >= -0.575)
          refcor = 1735.0 +
                   pdat->elevetr*(-518.2 + pdat->elevetr*(103.4 +
                   pdat->elevetr*(-12.79 + pdat->elevetr*0.711)));
       else
          refcor = -20.774/tanelev;

        prestemp = (pdat->press*283.0)/(1013.0*(273.0 + pdat->temp));
        refcor *= prestemp/3600.0;
    }

/* Refracted solar elevation angle */

    pdat->elevref = pdat->elevetr + refcor;

/* (limit the degrees below the horizon to 9) */

    if (pdat->elevref < -9.0) pdat->elevref = -9.0;

/* Refracted solar zenith angle */

    pdat->zenref = 90.0 - pdat->elevref;
    pdat->coszen = cos(raddeg*pdat->zenref);
}


/*============================================================================
*    Local Void function  amass
*
*    Airmass
*       Kasten, F. and Young, A.  1989.  Revised optical air mass
*            tables and approximation formula.  Applied Optics 28 (22),
*            pp. 4735-4738
*----------------------------------------------------------------------------*/
static void amass(struct posdata *pdat)
{
    if (pdat->zenref > 93.0)
    {
        pdat->amass   = -1.0;
        pdat->ampress = -1.0;
    }
    else
    {
        pdat->amass =
            1.0/(cos(raddeg*pdat->zenref) + 0.50572 *
            pow ((96.07995 - pdat->zenref),-1.6364));

        pdat->ampress   = pdat->amass*pdat->press/1013.0;
    }
}


/*============================================================================
*    Local Void function prime
*
*    Prime and Unprime
*    Prime  converts Kt to normalized Kt', etc.
*       Unprime deconverts Kt' to Kt, etc.
*            Perez, R., P. Ineichen, Seals, R., & Zelenka, A.  1990.  Making
*            full use of the clearness index for parameterizing hourly
*            insolation conditions. Solar Energy 45 (2), pp. 111-114
*----------------------------------------------------------------------------*/
static void prime(struct posdata *pdat)
{
    pdat->unprime = 1.031*exp (-1.4/(0.9 + 9.4/pdat->amass)) + 0.1;
    pdat->prime   = 1.0/pdat->unprime;
}


/*============================================================================
*    Local Void function etr
*
*    Extraterrestrial (top-of-atmosphere) solar irradiance
*----------------------------------------------------------------------------*/
static void etr(struct posdata *pdat)
{
    if (pdat->coszen > 0.0) {
        pdat->etrn = pdat->solcon*pdat->erv;
        pdat->etr  = pdat->etrn*pdat->coszen;
    }
    else {
        pdat->etrn = 0.0;
        pdat->etr  = 0.0;
    }
}


/*============================================================================
*    Local Void function localtrig
*
*    Does trig on internal variable used by several functions
*----------------------------------------------------------------------------*/
static void localtrig(struct posdata *pdat, struct trigdata *tdat)
{
/* define masks to prevent calculation of uninitialized variables */
#define SD_MASK (L_ZENETR | L_SSHA | S_SBCF | S_SOLAZM)
#define SL_MASK (L_ZENETR | L_SSHA | S_SBCF | S_SOLAZM)
#define CL_MASK (L_ZENETR | L_SSHA | S_SBCF | S_SOLAZM)
#define CD_MASK (L_ZENETR | L_SSHA | S_SBCF)
#define CH_MASK (L_ZENETR)

    if (tdat->sd < -900.0)  /* sd was initialized -999 as a flag */
    {
      tdat->sd = 1.0;  /* reflag as having completed calculations */
      if (pdat->function | CD_MASK)
        tdat->cd = cos(raddeg*pdat->declin);
      if (pdat->function | CH_MASK)
        tdat->ch = cos(raddeg*pdat->hrang);
      if (pdat->function | CL_MASK)
        tdat->cl = cos(raddeg*pdat->latitude);
      if (pdat->function | SD_MASK)
        tdat->sd = sin(raddeg*pdat->declin);
      if (pdat->function | SL_MASK)
        tdat->sl = sin(raddeg*pdat->latitude);
    }
}


/*============================================================================
*    Local Void function tilt
*
*    ETR on a tilted surface
*----------------------------------------------------------------------------*/
static void tilt(struct posdata *pdat)
{
  float ca;          /* cosine of the solar azimuth angle */
  float cp;          /* cosine of the panel aspect */
  float ct;          /* cosine of the panel tilt */
  float sa;          /* sine of the solar azimuth angle */
  float sp;          /* sine of the panel aspect */
  float st;          /* sine of the panel tilt */
  float sz;          /* sine of the refraction corrected solar zenith angle */


    /* Cosine of the angle between the sun and a tipped flat surface,
       useful for calculating solar energy on tilted surfaces */
    ca      = cos(raddeg*pdat->azim);
    cp      = cos(raddeg*pdat->aspect);
    ct      = cos(raddeg*pdat->tilt);
    sa      = sin(raddeg*pdat->azim);
    sp      = sin(raddeg*pdat->aspect);
    st      = sin(raddeg*pdat->tilt);
    sz      = sin(raddeg*pdat->zenref);
    pdat->cosinc  = pdat->coszen*ct + sz*st*(ca*cp + sa*sp);

    if (pdat->cosinc > 0.0)
        pdat->etrtilt = pdat->etrn*pdat->cosinc;
    else
        pdat->etrtilt = 0.0;
    
}


/*============================================================================
*    Void function S_decode
*
*    This function decodes the error codes from S_solpos return value
*
*    Requires the long integer return value from S_solpos
*
*    Returns descriptive text to stderr
*----------------------------------------------------------------------------*/
void S_decode(long code, struct posdata *pdat)
{
  if (code & (1L << S_YEAR_ERROR))
    fprintf(stderr, "S_decode ==> Please fix the year: %d [1950-2050]\n",
      pdat->year);
  if (code & (1L << S_MONTH_ERROR))
    fprintf(stderr, "S_decode ==> Please fix the month: %d\n",
      pdat->month);
  if (code & (1L << S_DAY_ERROR))
    fprintf(stderr, "S_decode ==> Please fix the day-of-month: %d\n",
      pdat->day);
  if (code & (1L << S_DOY_ERROR))
    fprintf(stderr, "S_decode ==> Please fix the day-of-year: %d\n",
      pdat->daynum);
  if (code & (1L << S_HOUR_ERROR))
    fprintf(stderr, "S_decode ==> Please fix the hour: %d\n",
      pdat->hour);
  if (code & (1L << S_MINUTE_ERROR))
    fprintf(stderr, "S_decode ==> Please fix the minute: %d\n",
      pdat->minute);
  if (code & (1L << S_SECOND_ERROR))
    fprintf(stderr, "S_decode ==> Please fix the second: %d\n",
      pdat->second);
  if (code & (1L << S_TZONE_ERROR))
    fprintf(stderr, "S_decode ==> Please fix the time zone: %f\n",
      pdat->timezone);
  if (code & (1L << S_INTRVL_ERROR))
    fprintf(stderr, "S_decode ==> Please fix the interval: %d\n",
      pdat->interval);
  if (code & (1L << S_LAT_ERROR))
    fprintf(stderr, "S_decode ==> Please fix the latitude: %f\n",
      pdat->latitude);
  if (code & (1L << S_LON_ERROR))
    fprintf(stderr, "S_decode ==> Please fix the longitude: %f\n",
      pdat->longitude);
  if (code & (1L << S_TEMP_ERROR))
    fprintf(stderr, "S_decode ==> Please fix the temperature: %f\n",
      pdat->temp);
  if (code & (1L << S_PRESS_ERROR))
    fprintf(stderr, "S_decode ==> Please fix the pressure: %f\n",
      pdat->press);
  if (code & (1L << S_TILT_ERROR))
    fprintf(stderr, "S_decode ==> Please fix the tilt: %f\n",
      pdat->tilt);
  if (code & (1L << S_ASPECT_ERROR))
    fprintf(stderr, "S_decode ==> Please fix the aspect: %f\n",
      pdat->aspect);
  if (code & (1L << S_SBWID_ERROR))
    fprintf(stderr, "S_decode ==> Please fix the shadowband width: %f\n",
      pdat->sbwid);
  if (code & (1L << S_SBRAD_ERROR))
    fprintf(stderr, "S_decode ==> Please fix the shadowband radius: %f\n",
      pdat->sbrad);
  if (code & (1L << S_SBSKY_ERROR))
    fprintf(stderr, "S_decode ==> Please fix the shadowband sky factor: %f\n",
      pdat->sbsky);
}
