/******************************************************************************
r.sun: This program was written by Jaro Hofierka in Summer 1993 and
 re-engineered in 1996-1999. In cooperation with Marcel Suri and Thomas Huld
 from JRC in Ispra a new version of r.sun was prepared using ESRA solar
 radiation formulas.
See manual pages for details.
(C) 2002 Copyright Jaro Hofierka, Gresaka 22, 085 01 Bardejov, Slovakia,
              and GeoModel, s.r.o., Bratislava, Slovakia
email: hofierka@geomodel.sk,marcel.suri@jrc.it,
       suri@geomodel.sk Thomas.Huld@jrc.it
(c) 2003-2013 by The GRASS Development Team
******************************************************************************/
/*
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the
 *   Free Software Foundation, Inc.,
 *   51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
 */

/*v. 2.0 July 2002, NULL data handling, JH */
/*v. 2.1 January 2003, code optimization by Thomas Huld, JH */
/*v. 3.0 February 2006, several changes (shadowing algorithm, earth's curvature
 * JH */
/*v. 4.0 December 2016, OpenMP support Stanislav Zubal, Michal Lacko */

#if defined(_OPENMP)
#include <omp.h>
#endif

#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#ifdef USE_OPENCL
#ifdef __APPLE__
#include <OpenCL/opencl.h>
#else
#include <CL/cl.h>
#endif
#endif

#include <grass/gis.h>
#include <grass/raster.h>
#include <grass/gmath.h>
#include <grass/gprojects.h>
#include <grass/glocale.h>
#include "sunradstruct.h"
#include "local_proto.h"
#include "rsunglobals.h"

/* default values */
#define NUM_PARTITIONS "1"
#define SKIP           "1"
#define BIG            1.e20
#define LINKE          "3.0"
#define SLOPE          "0.0"
#define ASPECT         "270"
#define ALB            "0.2"
#define STEP           "0.5"
#define BSKY           1.0
#define DSKY           1.0
#define DIST           "1.0"

#define SCALING_FACTOR 150.
const double invScale = 1. / SCALING_FACTOR;

#define AMAX1(arg1, arg2) ((arg1) >= (arg2) ? (arg1) : (arg2))
#define AMIN1(arg1, arg2) ((arg1) <= (arg2) ? (arg1) : (arg2))
#define DISTANCE1(x1, x2, y1, y2) \
    (sqrt((x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2)))
#define DISTANCE2(x00, y00) \
    ((xx0 - x00) * (xx0 - x00) + (yy0 - y00) * (yy0 - y00))

const double pihalf = M_PI * 0.5;
const double pi2 = M_PI * 2.;
const double deg2rad = M_PI / 180.;
const double rad2deg = 180. / M_PI;

FILE *felevin, *faspin, *fslopein, *flinkein, *falbedo, *flatin;
FILE *fincidout, *fbeam_rad, *finsol_time, *fdiff_rad, *frefl_rad;

const char *elevin;
const char *aspin;
const char *slopein;
const char *civiltime = NULL;
const char *linkein = NULL;
const char *albedo = NULL;
const char *latin = NULL;
const char *coefbh = NULL;
const char *coefdh = NULL;
const char *incidout = NULL;
const char *longin = NULL;
const char *horizon = NULL;
const char *beam_rad = NULL;
const char *insol_time = NULL;
const char *diff_rad = NULL;
const char *refl_rad = NULL;
const char *glob_rad = NULL;
const char *mapset = NULL;
const char *per;

size_t decimals;
char *str_step;

struct Cell_head cellhd;
struct pj_info iproj, oproj, tproj;
struct History hist;

int INPUT_part(int offset, double *zmax);
int OUTGR(void);
int min(int, int);
int max(int, int);

/*void cube(int, int);
   void (*func) (int, int); */

void joules2(struct SunGeometryConstDay *sunGeom,
             struct SunGeometryVarDay *sunVarGeom,
             struct SunGeometryVarSlope *sunSlopeGeom,
             struct SolarRadVar *sunRadVar, struct GridGeometry *gridGeom,
             unsigned char *horizonpointer, double latitude, double longitude,
             double *Pbeam_e, double *Pdiff_e, double *Prefl_e,
             double *Pinsol_t);

void calculate(double singleSlope, double singleAspect, double singleAlbedo,
               double singleLinke, struct GridGeometry gridGeom);
double com_declin(int);

int n, m, ip, jp;
int d, day;
int saveMemory, numPartitions = 1;
long int shadowoffset = 0;
int varCount_global = 0;
int bitCount_global = 0;
int arrayNumInt = 1;
float **z = NULL, **o = NULL, **s = NULL, **li = NULL, **a = NULL,
      **latitudeArray = NULL, **longitudeArray, **cbhr = NULL, **cdhr = NULL;
double op, dp;
double invstepx, invstepy;
double sunrise_min = 24., sunrise_max = 0., sunset_min = 24., sunset_max = 0.;

float **lumcl, **beam, **insol, **diff, **refl, **globrad;
unsigned char *horizonarray = NULL;
double civilTime;

/*
 * double startTime, endTime;
 */
double xmin, xmax, ymin, ymax;
double declin, step, dist;
double linke_max = 0., linke_min = 100., albedo_max = 0., albedo_min = 1.0,
       lat_max = -90., lat_min = 90.;
double offsetx = 0.5, offsety = 0.5;
char *ttime;

/*
 * double slope;
 */
double o_orig, z1;

/*
 * double lum_C11, lum_C13, lum_C22, lum_C31, lum_C33;
 * double sinSolarAltitude; */

/* Sine of the solar altitude (angle above horizon)
 */
/*
 * double sunrise_time, sunset_time;
 * double solarAltitude;
 * double tanSolarAlt, solarAzimuth;
 * double stepsinangle, stepcosangle;
 * double angle;
 */
double horizonStep;
double ltime, tim, timo;
double declination; /* Contains the negative of the declination at the chosen
                       day */
double solar_constant;

/*
 * double lum_C31_l, lum_C33_l;
 */
double beam_e, diff_e, refl_e, rr, insol_t;
double cbh, cdh;
double TOLER;

/* 1852m/nm * 60nm/degree = 111120 m/deg */
#define DEGREEINMETERS 111120.

int ll_correction = FALSE;
double coslatsq;

/* why not use G_distance() here which switches to geodesic/great
   circle distace as needed? */
double distance(double x1, double x2, double y1, double y2)
{
    if (ll_correction) {
        return DEGREEINMETERS *
               sqrt(coslatsq * (x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2));
    }
    else {
        return sqrt((x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2));
    }
}

int main(int argc, char *argv[])
{
    double singleSlope;
    double singleAspect;
    double singleAlbedo;
    double singleLinke;

    int threads;

    struct GModule *module;
    struct {
        struct Option *elevin, *aspin, *aspect, *slopein, *slope, *linkein,
            *lin, *albedo, *longin, *alb, *latin, *coefbh, *coefdh, *incidout,
            *beam_rad, *insol_time, *diff_rad, *refl_rad, *glob_rad, *day,
            *step, *declin, *solar_cnst, *ltime, *dist, *horizon, *horizonstep,
            *numPartitions, *civilTime, *threads;
    } parm;

    struct {
        struct Flag *noshade, *saveMemory;
    } flag;

    struct GridGeometry gridGeom;

    G_gisinit(argv[0]);

    module = G_define_module();
    G_add_keyword(_("raster"));
    G_add_keyword(_("solar"));
    G_add_keyword(_("sun energy"));
    G_add_keyword(_("shadow"));
    G_add_keyword(_("parallel"));
    module->label = _("Solar irradiance and irradiation model.");
    module->description = _(
        "Computes direct (beam), diffuse and reflected solar irradiation "
        "raster "
        "maps for given day, latitude, surface and atmospheric conditions. "
        "Solar "
        "parameters (e.g. sunrise, sunset times, declination, extraterrestrial "
        "irradiance, daylight length) are saved in the map history file. "
        "Alternatively, a local time can be specified to compute solar "
        "incidence angle and/or irradiance raster maps. The shadowing effect "
        "of "
        "the topography is optionally incorporated.");

    parm.elevin = G_define_option();
    parm.elevin->key = "elevation";
    parm.elevin->type = TYPE_STRING;
    parm.elevin->required = YES;
    parm.elevin->gisprompt = "old,cell,raster";
    parm.elevin->description =
        _("Name of the input elevation raster map [meters]");
    parm.elevin->guisection = _("Input");

    parm.aspin = G_define_option();
    parm.aspin->key = "aspect";
    parm.aspin->type = TYPE_STRING;
    parm.aspin->required = NO;
    parm.aspin->gisprompt = "old,cell,raster";
    parm.aspin->description =
        _("Name of the input aspect map (terrain aspect or azimuth of the "
          "solar panel) [decimal degrees]");
    parm.aspin->guisection = _("Input");

    parm.aspect = G_define_option();
    parm.aspect->key = "aspect_value";
    parm.aspect->type = TYPE_DOUBLE;
    parm.aspect->answer = ASPECT;
    parm.aspect->required = NO;
    parm.aspect->description =
        _("A single value of the orientation (aspect), 270 is south");
    parm.aspect->guisection = _("Input");

    parm.slopein = G_define_option();
    parm.slopein->key = "slope";
    parm.slopein->type = TYPE_STRING;
    parm.slopein->required = NO;
    parm.slopein->gisprompt = "old,cell,raster";
    parm.slopein->description =
        _("Name of the input slope raster map (terrain slope or solar panel "
          "inclination) [decimal degrees]");
    parm.slopein->guisection = _("Input");

    parm.slope = G_define_option();
    parm.slope->key = "slope_value";
    parm.slope->type = TYPE_DOUBLE;
    parm.slope->answer = SLOPE;
    parm.slope->required = NO;
    parm.slope->description = _("A single value of inclination (slope)");
    parm.slope->guisection = _("Input");

    parm.linkein = G_define_option();
    parm.linkein->key = "linke";
    parm.linkein->type = TYPE_STRING;
    parm.linkein->required = NO;
    parm.linkein->gisprompt = "old,cell,raster";
    parm.linkein->description = _("Name of the Linke atmospheric turbidity "
                                  "coefficient input raster map [-]");
    parm.linkein->guisection = _("Input");

    parm.lin = G_define_option();
    parm.lin->key = "linke_value";
    parm.lin->type = TYPE_DOUBLE;
    parm.lin->answer = LINKE;
    parm.lin->required = NO;
    parm.lin->description =
        _("A single value of the Linke atmospheric turbidity coefficient [-]");
    parm.lin->guisection = _("Input");

    parm.albedo = G_define_option();
    parm.albedo->key = "albedo";
    parm.albedo->type = TYPE_STRING;
    parm.albedo->required = NO;
    parm.albedo->gisprompt = "old,cell,raster";
    parm.albedo->description =
        _("Name of the ground albedo coefficient input raster map [-]");
    parm.albedo->guisection = _("Input");

    parm.alb = G_define_option();
    parm.alb->key = "albedo_value";
    parm.alb->type = TYPE_DOUBLE;
    parm.alb->answer = ALB;
    parm.alb->required = NO;
    parm.alb->description =
        _("A single value of the ground albedo coefficient [-]");
    parm.alb->guisection = _("Input");

    parm.latin = G_define_option();
    parm.latin->key = "lat";
    parm.latin->type = TYPE_STRING;
    parm.latin->required = NO;
    parm.latin->gisprompt = "old,cell,raster";
    parm.latin->description =
        _("Name of input raster map containing latitudes [decimal degrees]");
    parm.latin->guisection = _("Input");

    parm.longin = G_define_option();
    parm.longin->key = "long";
    parm.longin->type = TYPE_STRING;
    parm.longin->required = NO;
    parm.longin->gisprompt = "old,cell,raster";
    parm.longin->description =
        _("Name of input raster map containing longitudes [decimal degrees]");
    parm.longin->guisection = _("Input");

    parm.coefbh = G_define_option();
    parm.coefbh->key = "coeff_bh";
    parm.coefbh->type = TYPE_STRING;
    parm.coefbh->required = NO;
    parm.coefbh->gisprompt = "old,cell,raster";
    parm.coefbh->description = _("Name of real-sky beam radiation coefficient "
                                 "(thick cloud) input raster map [0-1]");
    parm.coefbh->guisection = _("Input");

    parm.coefdh = G_define_option();
    parm.coefdh->key = "coeff_dh";
    parm.coefdh->type = TYPE_STRING;
    parm.coefdh->required = NO;
    parm.coefdh->gisprompt = "old,cell,raster";
    parm.coefdh->description = _("Name of real-sky diffuse radiation "
                                 "coefficient (haze) input raster map [0-1]");
    parm.coefdh->guisection = _("Input");

    parm.horizon = G_define_standard_option(G_OPT_R_BASENAME_INPUT);
    parm.horizon->key = "horizon_basename";
    parm.horizon->required = NO;
    parm.horizon->gisprompt = "old,cell,raster";
    parm.horizon->description = _("The horizon information input map basename");
    parm.horizon->guisection = _("Input");

    parm.horizonstep = G_define_option();
    parm.horizonstep->key = "horizon_step";
    parm.horizonstep->type = TYPE_DOUBLE;
    parm.horizonstep->required = NO;
    parm.horizonstep->description =
        _("Angle step size for multidirectional horizon [degrees]");
    parm.horizonstep->guisection = _("Input");

    parm.incidout = G_define_option();
    parm.incidout->key = "incidout";
    parm.incidout->type = TYPE_STRING;
    parm.incidout->required = NO;
    parm.incidout->gisprompt = "new,cell,raster";
    parm.incidout->description =
        _("Output incidence angle raster map (mode 1 only)");
    parm.incidout->guisection = _("Output");

    parm.beam_rad = G_define_option();
    parm.beam_rad->key = "beam_rad";
    parm.beam_rad->type = TYPE_STRING;
    parm.beam_rad->required = NO;
    parm.beam_rad->gisprompt = "new,cell,raster";
    parm.beam_rad->description =
        _("Output beam irradiance [W.m-2] (mode 1) or irradiation raster map "
          "[Wh.m-2.day-1] (mode 2)");
    parm.beam_rad->guisection = _("Output");

    parm.diff_rad = G_define_option();
    parm.diff_rad->key = "diff_rad";
    parm.diff_rad->type = TYPE_STRING;
    parm.diff_rad->required = NO;
    parm.diff_rad->gisprompt = "new,cell,raster";
    parm.diff_rad->description =
        _("Output diffuse irradiance [W.m-2] (mode 1) or irradiation raster "
          "map [Wh.m-2.day-1] (mode 2)");
    parm.diff_rad->guisection = _("Output");

    parm.refl_rad = G_define_option();
    parm.refl_rad->key = "refl_rad";
    parm.refl_rad->type = TYPE_STRING;
    parm.refl_rad->required = NO;
    parm.refl_rad->gisprompt = "new,cell,raster";
    parm.refl_rad->description =
        _("Output ground reflected irradiance [W.m-2] (mode 1) or irradiation "
          "raster map [Wh.m-2.day-1] (mode 2)");
    parm.refl_rad->guisection = _("Output");

    parm.glob_rad = G_define_option();
    parm.glob_rad->key = "glob_rad";
    parm.glob_rad->type = TYPE_STRING;
    parm.glob_rad->required = NO;
    parm.glob_rad->gisprompt = "new,cell,raster";
    parm.glob_rad->description =
        _("Output global (total) irradiance/irradiation [W.m-2] (mode 1) or "
          "irradiance/irradiation raster map [Wh.m-2.day-1] (mode 2)");
    parm.glob_rad->guisection = _("Output");

    parm.insol_time = G_define_option();
    parm.insol_time->key = "insol_time";
    parm.insol_time->type = TYPE_STRING;
    parm.insol_time->required = NO;
    parm.insol_time->gisprompt = "new,cell,raster";
    parm.insol_time->description =
        _("Output insolation time raster map [h] (mode 2 only)");
    parm.insol_time->guisection = _("Output");

    parm.day = G_define_option();
    parm.day->key = "day";
    parm.day->type = TYPE_INTEGER;
    parm.day->required = YES;
    parm.day->description = _("No. of day of the year (1-365)");
    parm.day->options = "1-365";
    parm.day->guisection = _("Time");

    parm.step = G_define_option();
    parm.step->key = "step";
    parm.step->type = TYPE_DOUBLE;
    parm.step->answer = STEP;
    parm.step->required = NO;
    parm.step->description =
        _("Time step when computing all-day radiation sums [decimal hours]");
    parm.step->guisection = _("Time");

    parm.declin = G_define_option();
    parm.declin->key = "declination";
    parm.declin->type = TYPE_DOUBLE;
    parm.declin->required = NO;
    parm.declin->description = _("Declination value (overriding the internally "
                                 "computed value) [radians]");

    parm.solar_cnst = G_define_option();
    parm.solar_cnst->key = "solar_constant";
    parm.solar_cnst->type = TYPE_DOUBLE;
    parm.solar_cnst->required = NO;
    parm.solar_cnst->answer = "1367";
    parm.solar_cnst->description = _("Solar constant [W/m^2]");

    parm.ltime = G_define_option();
    parm.ltime->key = "time";
    parm.ltime->type = TYPE_DOUBLE;
    /*          parm.ltime->answer = TIME; */
    parm.ltime->required = NO;
    parm.ltime->description =
        _("Local (solar) time (to be set for mode 1 only) [decimal hours]");
    parm.ltime->options = "0-24";
    parm.ltime->guisection = _("Time");

    parm.threads = G_define_option();
    parm.threads->key = "nprocs";
    parm.threads->type = TYPE_INTEGER;
    parm.threads->answer = "1";
    parm.threads->options = "1-1000";
    parm.threads->required = NO;
    parm.threads->description =
        _("Number of threads which will be used for parallel computing");

    /*
     * parm.startTime = G_define_option();
     * parm.startTime->key = "start_time";
     * parm.startTime->type = TYPE_DOUBLE;
     * parm.startTime->required = NO;
     * parm.startTime->description = _("Starting time for calculating results
     * for several different times.");
     *
     * parm.endTime = G_define_option();
     * parm.endTime->key = "end_time";
     * parm.endTime->type = TYPE_DOUBLE;
     * parm.endTime->required = NO;
     * parm.endTime->description = _("End time for calculating results for
     * several different times.)";
     */

    parm.dist = G_define_option();
    parm.dist->key = "distance_step";
    parm.dist->type = TYPE_DOUBLE;
    parm.dist->answer = DIST;
    parm.dist->required = NO;
    parm.dist->description = _("Sampling distance step coefficient (0.5-1.5)");

    parm.numPartitions = G_define_option();
    parm.numPartitions->key = "npartitions";
    parm.numPartitions->type = TYPE_INTEGER;
    parm.numPartitions->answer = NUM_PARTITIONS;
    parm.numPartitions->required = NO;
    parm.numPartitions->description =
        _("Read the input files in this number of chunks");

    parm.civilTime = G_define_option();
    parm.civilTime->key = "civil_time";
    parm.civilTime->type = TYPE_DOUBLE;
    parm.civilTime->required = NO;
    parm.civilTime->description =
        _("Civil time zone value, if none, the time will be local solar time");
    parm.civilTime->guisection = _("Time");

    flag.noshade = G_define_flag();
    flag.noshade->key = 'p';
    flag.noshade->description =
        _("Do not incorporate the shadowing effect of terrain");

    flag.saveMemory = G_define_flag();
    flag.saveMemory->key = 'm';
    flag.saveMemory->description =
        _("Use the low-memory version of the program");

    if (G_parser(argc, argv))
        exit(EXIT_FAILURE);

    G_get_set_window(&cellhd);

    gridGeom.stepx = cellhd.ew_res;
    gridGeom.stepy = cellhd.ns_res;
    invstepx = 1. / gridGeom.stepx;
    invstepy = 1. / gridGeom.stepy;
    n /*n_cols */ = cellhd.cols;
    m /*n_rows */ = cellhd.rows;
    xmin = cellhd.west;
    ymin = cellhd.south;
    xmax = cellhd.east;
    ymax = cellhd.north;
    gridGeom.deltx = fabs(cellhd.east - cellhd.west);
    gridGeom.delty = fabs(cellhd.north - cellhd.south);

    setUseShadow(!flag.noshade->answer);

    saveMemory = flag.saveMemory->answer;

    elevin = parm.elevin->answer;
    aspin = parm.aspin->answer;
    slopein = parm.slopein->answer;
    linkein = parm.linkein->answer;
    albedo = parm.albedo->answer;
    latin = parm.latin->answer;
    longin = parm.longin->answer;

    civiltime = parm.civilTime->answer;

    sscanf(parm.threads->answer, "%d", &threads);
    if (threads < 1) {
        G_warning(_("<%d> is not valid number of threads. Number of threads "
                    "will be set to <%d>"),
                  threads, abs(threads));
        threads = abs(threads);
    }
#if defined(_OPENMP)
    omp_set_num_threads(threads);
#else
    threads = 1;
#endif
    if (threads > 1 && G_find_raster("MASK", G_mapset()) != NULL) {
        G_warning(_("Parallel processing disabled due to active MASK."));
        threads = 1;
    }
    G_message(_("Number of threads <%d>"), threads);

    if (civiltime != NULL) {
        setUseCivilTime(TRUE);

        if (longin == NULL)
            G_fatal_error(
                _("You must give the longitude raster if you use civil time"));

        if (sscanf(parm.civilTime->answer, "%lf", &civilTime) != 1)
            G_fatal_error(_("Error reading civil time zone value"));

        if (civilTime < -24. || civilTime > 24.)
            G_fatal_error(_("Invalid civil time zone value"));

        /* Normalize if somebody should be weird enough to give more than +- 12
         * hours offset. */
        if (civilTime < -12.)
            civilTime += 24.;
        else if (civilTime > 12.)
            civilTime -= 24;
    }
    else {
        setUseCivilTime(FALSE);
    }

    coefbh = parm.coefbh->answer;
    coefdh = parm.coefdh->answer;
    incidout = parm.incidout->answer;
    horizon = parm.horizon->answer;
    setUseHorizonData(horizon != NULL);
    beam_rad = parm.beam_rad->answer;
    insol_time = parm.insol_time->answer;
    diff_rad = parm.diff_rad->answer;
    refl_rad = parm.refl_rad->answer;
    glob_rad = parm.glob_rad->answer;

    if ((insol_time != NULL) && (incidout != NULL))
        G_fatal_error(_("insol_time and incidout are incompatible options"));

    sscanf(parm.day->answer, "%d", &day);

    if (sscanf(parm.step->answer, "%lf", &step) != 1)
        G_fatal_error(_("Error reading time step size"));
    if (step <= 0.0 || step > 24.0)
        G_fatal_error(_("Invalid time step size"));

    if (parm.horizonstep->answer != NULL) {
        if (sscanf(parm.horizonstep->answer, "%lf", &horizonStep) != 1)
            G_fatal_error(_("Error reading horizon step size"));
        str_step = parm.horizonstep->answer;
        if (horizonStep > 0.)
            setHorizonInterval(deg2rad * horizonStep);
        else
            G_fatal_error(_("The horizon step size must be greater than 0."));
    }
    else if (useHorizonData()) {
        G_fatal_error(_("If you use the horizon option you must also set the "
                        "'horizonstep' parameter."));
    }

    ttime = parm.ltime->answer;
    if (parm.ltime->answer != NULL) {
        if (insol_time != NULL)
            G_fatal_error(_("Time and insol_time are incompatible options"));

        G_message(_("Mode 1: instantaneous solar incidence angle & irradiance "
                    "using a set local time"));
        sscanf(parm.ltime->answer, "%lf", &timo);
    }
    else {
        if (incidout != NULL)
            G_fatal_error(_("incidout requires time parameter to be set"));

        G_message(_("Mode 2: integrated daily irradiation for a given day of "
                    "the year"));
    }

    /*
     * if (parm.startTime->answer != NULL) sscanf(parm.startTime->answer, "%lf",
     * &startTime); if (parm.endTime->answer != NULL)
     * sscanf(parm.endTime->answer, "%lf", &endTime);
     *
     * if((parm.startTime->answer != NULL) ||(parm.endTime->answer != NULL))
     * {
     */
    /* The start and end times should only be defined if the single
     * time is not defined, and if the step size *is* defined. */
    /*
     * if(parm.step->answer==NULL)
     *    G_fatal_error("If you want to use a time interval you must also define
     * a step size."); if(parm.ltime->answer!=NULL) G_fatal_error("If you want
     * to use a time interval you can't define a single time value.\n");
     * if((parm.startTime->answer==NULL)||(parm.endTime->answer==NULL))
     *    G_fatal_error("If you want to use a time interval both the start and
     * end times must be defined.\n");
     * }
     */
    if (parm.linkein->answer == NULL) {
        sscanf(parm.lin->answer, "%lf", &singleLinke);
        G_message(_("Using Linke constant: %lf"), singleLinke);
    }
    else {
        G_message(_("Using Linke map <%s>"), parm.linkein->answer);
    }

    if (parm.albedo->answer == NULL) {
        sscanf(parm.alb->answer, "%lf", &singleAlbedo);
        G_message(_("Using albedo constant: %lf"), singleAlbedo);
    }
    else {
        G_message(_("Using albedo map <%s>"), parm.albedo->answer);
    }

    if (parm.slopein->answer == NULL) {
        sscanf(parm.slope->answer, "%lf", &singleSlope);
        G_message(_("Using slope constant: %lf"), singleSlope);
        singleSlope *= deg2rad;
    }
    else {
        G_message(_("Using slope map <%s>"), parm.slopein->answer);
    }

    if (parm.aspin->answer == NULL) {
        sscanf(parm.aspect->answer, "%lf", &singleAspect);
        G_message(_("Using aspect constant: %lf"), singleAspect);
        singleAspect *= deg2rad;
    }
    else {
        G_message(_("Using aspect map <%s>"), parm.aspin->answer);
    }

    if (parm.coefbh->answer == NULL)
        cbh = BSKY;
    if (parm.coefdh->answer == NULL)
        cdh = DSKY;
    sscanf(parm.dist->answer, "%lf", &dist);

    if (parm.numPartitions->answer != NULL) {
        sscanf(parm.numPartitions->answer, "%d", &numPartitions);
        if (useShadow() && (!useHorizonData()) && (numPartitions != 1)) {
            /* If you calculate shadows on the fly, the number of partitions
             * must be one.
             */
            G_fatal_error(_("If you use -s and no horizon rasters, "
                            "numpartitions must be =1"));
        }
    }

    gridGeom.stepxy = dist * 0.5 * (gridGeom.stepx + gridGeom.stepy);
    TOLER = gridGeom.stepxy * EPS;

    /* The save memory scheme will not work if you want to calculate shadows
     * on the fly. If you calculate without shadow effects or if you have the
     * shadows pre-calculated, there is no problem. */

    if (saveMemory && useShadow() && (!useHorizonData()))
        G_fatal_error(_("If you want to save memory and to use shadows, "
                        "you must use pre-calculated horizons."));

    if (parm.declin->answer == NULL)
        declination = com_declin(day);
    else {
        sscanf(parm.declin->answer, "%lf", &declin);
        declination = -declin;
    }

    if (parm.solar_cnst->answer)
        sscanf(parm.solar_cnst->answer, "%lf", &solar_constant);
    else
        solar_constant = 1367;

    if (ttime != 0) {
        /* Shadow for just one time during the day */
        if (horizon == NULL) {
            arrayNumInt = 1;
        }
        else if (useHorizonData()) {
            arrayNumInt = (int)(360. / horizonStep);
        }
    }
    else {
        if (useHorizonData()) {
            /*        Number of bytes holding the horizon information */
            arrayNumInt = (int)(360. / horizonStep);
        }
    }

    if (ttime != NULL) {

        tim = (timo - 12) * 15;
        /* converting to degrees */
        /* Jenco (12-timeAngle) * 15 */
        if (tim < 0)
            tim += 360;
        tim = M_PI * tim / 180;
        /* conv. to radians */
    }

    /* Set up parameters for projection to lat/long if necessary */
    /*  we need to do this even if latin= and longin= were given because
       rsunlib's com_par() needs iproj and oproj */
    if ((G_projection() != PROJECTION_LL)) {
        struct Key_Value *in_proj_info, *in_unit_info;

        if ((in_proj_info = G_get_projinfo()) == NULL)
            G_fatal_error(_("Can't get projection info of current project"));

        if ((in_unit_info = G_get_projunits()) == NULL)
            G_fatal_error(_("Can't get projection units of current project"));

        if (pj_get_kv(&iproj, in_proj_info, in_unit_info) < 0)
            G_fatal_error(
                _("Can't get projection key values of current project"));

        G_free_key_value(in_proj_info);
        G_free_key_value(in_unit_info);

        oproj.pj = NULL;
        tproj.def = NULL;

        if (GPJ_init_transform(&iproj, &oproj, &tproj) < 0)
            G_fatal_error(_("Unable to initialize coordinate transformation"));
    }

    if ((latin != NULL || longin != NULL) && (G_projection() == PROJECTION_LL))
        G_warning(_("latin and longin raster maps have no effect when in a "
                    "Lat/Lon project"));
    /* true about longin= when civiltime is used? */
    /* civiltime needs longin= but not latin= for non-LL projections -
       better would be it just use pj_proj() if it needs those?? */
    if (latin != NULL && longin == NULL)
        G_fatal_error(
            _("Both latin and longin raster maps must be given, or neither"));

    /**********end of parser - ******************************/

    if ((G_projection() == PROJECTION_LL))
        ll_correction = TRUE;

    G_debug(3, "calculate() starts...");
    calculate(singleSlope, singleAspect, singleAlbedo, singleLinke, gridGeom);
    G_debug(3, "OUTGR() starts...");
    OUTGR();

    exit(EXIT_SUCCESS);
}

int INPUT_part(int offset, double *zmax)
{
    int finalRow, rowrevoffset;
    int numRows;
    FCELL *cell1 = NULL, *cell2 = NULL;
    FCELL *cell3 = NULL, *cell4 = NULL, *cell5 = NULL, *cell6 = NULL,
          *cell7 = NULL;
    FCELL *rast1 = NULL, *rast2 = NULL;
    static FCELL **horizonbuf;
    unsigned char *horizonpointer;
    int fd1 = -1, fd2 = -1, fd3 = -1, fd4 = -1, fd5 = -1, fd6 = -1, fd7 = -1,
        row, row_rev;
    static int *fd_shad;
    int fr1 = -1, fr2 = -1;
    int i, j;
    char *shad_filename;
    double angle_deg = 0.;

    finalRow = m - offset - m / numPartitions;
    if (finalRow < 0) {
        finalRow = 0;
    }

    numRows = m / numPartitions;

    cell1 = Rast_allocate_f_buf();

    if (z == NULL) {
        if (!(z = G_alloc_fmatrix(numRows, n))) {
            G_fatal_error(_("Out of memory"));
        }
    }

    fd1 = Rast_open_old(elevin, "");

    if (slopein != NULL) {
        cell3 = Rast_allocate_f_buf();
        if (s == NULL) {
            if (!(s = G_alloc_fmatrix(numRows, n))) {
                G_fatal_error(_("Out of memory"));
            }
        }
        fd3 = Rast_open_old(slopein, "");
    }

    if (aspin != NULL) {
        cell2 = Rast_allocate_f_buf();

        if (o == NULL) {
            if (!(o = G_alloc_fmatrix(numRows, n))) {
                G_fatal_error(_("Out of memory"));
            }
        }
        fd2 = Rast_open_old(aspin, "");
    }

    if (linkein != NULL) {
        cell4 = Rast_allocate_f_buf();
        if (li == NULL) {
            if (!(li = G_alloc_fmatrix(numRows, n))) {
                G_fatal_error(_("Out of memory"));
            }
        }
        fd4 = Rast_open_old(linkein, "");
    }

    if (albedo != NULL) {
        cell5 = Rast_allocate_f_buf();
        if (a == NULL) {
            if (!(a = G_alloc_fmatrix(numRows, n))) {
                G_fatal_error(_("Out of memory"));
            }
        }
        fd5 = Rast_open_old(albedo, "");
    }

    if (latin != NULL) {
        cell6 = Rast_allocate_f_buf();
        if (latitudeArray == NULL) {
            if (!(latitudeArray = G_alloc_fmatrix(numRows, n))) {
                G_fatal_error(_("Out of memory"));
            }
        }
        fd6 = Rast_open_old(latin, "");
    }

    if (longin != NULL) {
        cell7 = Rast_allocate_f_buf();
        if (longitudeArray == NULL) {
            if (!(longitudeArray = G_alloc_fmatrix(numRows, n))) {
                G_fatal_error(_("Out of memory"));
            }
        }

        fd7 = Rast_open_old(longin, "");
    }

    if (coefbh != NULL) {
        rast1 = Rast_allocate_f_buf();
        if (cbhr == NULL) {
            if (!(cbhr = G_alloc_fmatrix(numRows, n))) {
                G_fatal_error(_("Out of memory"));
            }
        }
        fr1 = Rast_open_old(coefbh, "");
    }

    if (coefdh != NULL) {
        rast2 = Rast_allocate_f_buf();
        if (cdhr == NULL) {
            if (!(cdhr = G_alloc_fmatrix(numRows, n))) {
                G_fatal_error(_("Out of memory"));
            }
        }
        fr2 = Rast_open_old(coefdh, "");
    }

    if (useHorizonData()) {
        if (horizonarray == NULL) {
            horizonarray = (unsigned char *)G_calloc(arrayNumInt * numRows * n,
                                                     sizeof(char));

            horizonbuf = (FCELL **)G_calloc(arrayNumInt, sizeof(FCELL *));
            fd_shad = (int *)G_calloc(arrayNumInt, sizeof(int));
        }
        /*
         * if(ttime != NULL)
         * {
         *
         * horizonbuf[0]=Rast_allocate_f_buf();
         * sprintf(shad_filename, "%s_%02d", horizon, arrayNumInt);
         * if((mapset=G_find_raster2(shad_filename,""))==NULL)
         * G_message("Horizon file no. %d not found\n", arrayNumInt);
         *
         * fd_shad[0] = Rast_open_old(shad_filename,mapset);
         * }
         * else
         * {
         */
        decimals = G_get_num_decimals(str_step);
        angle_deg = 0;
        for (i = 0; i < arrayNumInt; i++) {
            horizonbuf[i] = Rast_allocate_f_buf();
            shad_filename =
                G_generate_basename(horizon, angle_deg, 3, decimals);
            fd_shad[i] = Rast_open_old(shad_filename, "");
            angle_deg += horizonStep;
            G_free(shad_filename);
        }
        /*
           numDigits = (int)(log10(1. * arrayNumInt)) + 1;
           sprintf(formatString, "%%s_%%0%dd", numDigits);
           for (i = 0; i < arrayNumInt; i++) {
           horizonbuf[i] = Rast_allocate_f_buf();
           sprintf(shad_filename, formatString, horizon, i);
           fd_shad[i] = Rast_open_old(shad_filename, "");
           } */
    }
    /*
     * }
     */

    if (useHorizonData()) {

        for (i = 0; i < arrayNumInt; i++) {
            for (row = m - offset - 1; row >= finalRow; row--) {

                row_rev = m - row - 1;
                rowrevoffset = row_rev - offset;
                Rast_get_f_row(fd_shad[i], horizonbuf[i], row);
                horizonpointer =
                    horizonarray + (ssize_t)arrayNumInt * n * rowrevoffset;
                for (j = 0; j < n; j++) {

                    horizonpointer[i] =
                        (char)(rint(SCALING_FACTOR *
                                    AMIN1(horizonbuf[i][j], 256 * invScale)));
                    horizonpointer += arrayNumInt;
                }
            }
        }
    }

    for (row = m - offset - 1; row >= finalRow; row--) {
        Rast_get_f_row(fd1, cell1, row);
        if (aspin != NULL)
            Rast_get_f_row(fd2, cell2, row);
        if (slopein != NULL)
            Rast_get_f_row(fd3, cell3, row);
        if (linkein != NULL)
            Rast_get_f_row(fd4, cell4, row);
        if (albedo != NULL)
            Rast_get_f_row(fd5, cell5, row);
        if (latin != NULL)
            Rast_get_f_row(fd6, cell6, row);
        if (longin != NULL)
            Rast_get_f_row(fd7, cell7, row);
        if (coefbh != NULL)
            Rast_get_f_row(fr1, rast1, row);
        if (coefdh != NULL)
            Rast_get_f_row(fr2, rast2, row);

        row_rev = m - row - 1;
        rowrevoffset = row_rev - offset;

        for (j = 0; j < n; j++) {
            if (!Rast_is_f_null_value(cell1 + j))
                z[rowrevoffset][j] = (float)cell1[j];
            else
                z[rowrevoffset][j] = UNDEFZ;

            if (aspin != NULL) {
                if (!Rast_is_f_null_value(cell2 + j))
                    o[rowrevoffset][j] = (float)cell2[j];
                else
                    o[rowrevoffset][j] = UNDEFZ;
            }
            if (slopein != NULL) {
                if (!Rast_is_f_null_value(cell3 + j))
                    s[rowrevoffset][j] = (float)cell3[j];
                else
                    s[rowrevoffset][j] = UNDEFZ;
            }

            if (linkein != NULL) {
                if (!Rast_is_f_null_value(cell4 + j))
                    li[rowrevoffset][j] = (float)cell4[j];
                else
                    li[rowrevoffset][j] = UNDEFZ;
            }

            if (albedo != NULL) {
                if (!Rast_is_f_null_value(cell5 + j))
                    a[rowrevoffset][j] = (float)cell5[j];
                else
                    a[rowrevoffset][j] = UNDEFZ;
            }

            if (latin != NULL) {
                if (!Rast_is_f_null_value(cell6 + j))
                    latitudeArray[rowrevoffset][j] = (float)cell6[j];
                else
                    latitudeArray[rowrevoffset][j] = UNDEFZ;
            }

            if (longin != NULL) {
                if (!Rast_is_f_null_value(cell7 + j))
                    longitudeArray[rowrevoffset][j] = (float)cell7[j];
                else
                    longitudeArray[rowrevoffset][j] = UNDEFZ;
            }

            if (coefbh != NULL) {
                if (!Rast_is_f_null_value(rast1 + j))
                    cbhr[rowrevoffset][j] = (float)rast1[j];
                else
                    cbhr[rowrevoffset][j] = UNDEFZ;
            }

            if (coefdh != NULL) {
                if (!Rast_is_f_null_value(rast2 + j))
                    cdhr[rowrevoffset][j] = (float)rast2[j];
                else
                    cdhr[rowrevoffset][j] = UNDEFZ;
            }
        }
    }

    Rast_close(fd1);
    G_free(cell1);

    if (aspin != NULL) {
        G_free(cell2);
        Rast_close(fd2);
    }
    if (slopein != NULL) {
        G_free(cell3);
        Rast_close(fd3);
    }
    if (linkein != NULL) {
        G_free(cell4);
        Rast_close(fd4);
    }
    if (albedo != NULL) {
        G_free(cell5);
        Rast_close(fd5);
    }
    if (latin != NULL) {
        G_free(cell6);
        Rast_close(fd6);
    }
    if (longin != NULL) {
        G_free(cell7);
        Rast_close(fd7);
    }
    if (coefbh != NULL) {
        G_free(rast1);
        Rast_close(fr1);
    }
    if (coefdh != NULL) {
        G_free(rast2);
        Rast_close(fr2);
    }

    if (useHorizonData()) {
        for (i = 0; i < arrayNumInt; i++) {
            Rast_close(fd_shad[i]);
            G_free(horizonbuf[i]);
        }
    }

    /*******transformation of angles from 0 to east counterclock
            to 0 to north clocwise, for ori=0 upslope flowlines
            turn the orientation 2*M_PI ************/

    /* needs to be eliminated */

    /*for (i = 0; i < m; ++i) */
    for (i = 0; i < numRows; i++) {
        for (j = 0; j < n; j++) {
            *zmax = AMAX1(*zmax, z[i][j]);
            if (aspin != NULL) {
                if (o[i][j] != 0.) {
                    if (o[i][j] < 90.)
                        o[i][j] = 90. - o[i][j];
                    else
                        o[i][j] = 450. - o[i][j];
                }
                /*   G_debug(3,"o,z = %d  %d i,j, %d %d \n",
                 * o[i][j],z[i][j],i,j); */

                if ((aspin != NULL) && o[i][j] == UNDEFZ)
                    z[i][j] = UNDEFZ;
                if ((slopein != NULL) && s[i][j] == UNDEFZ)
                    z[i][j] = UNDEFZ;
                if (linkein != NULL && li[i][j] == UNDEFZ)
                    z[i][j] = UNDEFZ;
                if (albedo != NULL && a[i][j] == UNDEFZ)
                    z[i][j] = UNDEFZ;
                if (latin != NULL && latitudeArray[i][j] == UNDEFZ)
                    z[i][j] = UNDEFZ;
                if (coefbh != NULL && cbhr[i][j] == UNDEFZ)
                    z[i][j] = UNDEFZ;
                if (coefdh != NULL && cdhr[i][j] == UNDEFZ)
                    z[i][j] = UNDEFZ;
            }
        }
    }

    return 1;
}

int OUTGR(void)
{
    FCELL *cell7 = NULL, *cell8 = NULL, *cell9 = NULL, *cell10 = NULL,
          *cell11 = NULL, *cell12 = NULL;
    int fd7 = -1, fd8 = -1, fd9 = -1, fd10 = -1, fd11 = -1, fd12 = -1;
    int i, iarc, j;

    if (incidout != NULL) {
        cell7 = Rast_allocate_f_buf();
        fd7 = Rast_open_fp_new(incidout);
    }

    if (beam_rad != NULL) {
        cell8 = Rast_allocate_f_buf();
        fd8 = Rast_open_fp_new(beam_rad);
    }

    if (insol_time != NULL) {
        cell11 = Rast_allocate_f_buf();
        fd11 = Rast_open_fp_new(insol_time);
    }

    if (diff_rad != NULL) {
        cell9 = Rast_allocate_f_buf();
        fd9 = Rast_open_fp_new(diff_rad);
    }

    if (refl_rad != NULL) {
        cell10 = Rast_allocate_f_buf();
        fd10 = Rast_open_fp_new(refl_rad);
    }

    if (glob_rad != NULL) {
        cell12 = Rast_allocate_f_buf();
        fd12 = Rast_open_fp_new(glob_rad);
    }

    if (m != Rast_window_rows())
        G_fatal_error("OOPS: rows changed from %d to %d", m,
                      Rast_window_rows());
    if (n != Rast_window_cols())
        G_fatal_error("OOPS: cols changed from %d to %d", n,
                      Rast_window_cols());

    for (iarc = 0; iarc < m; iarc++) {
        i = m - iarc - 1;
        if (incidout != NULL) {
            for (j = 0; j < n; j++) {
                if (lumcl[i][j] == UNDEFZ)
                    Rast_set_f_null_value(cell7 + j, 1);
                else
                    cell7[j] = (FCELL)lumcl[i][j];
            }
            Rast_put_f_row(fd7, cell7);
        }

        if (beam_rad != NULL) {
            for (j = 0; j < n; j++) {
                if (beam[i][j] == UNDEFZ)
                    Rast_set_f_null_value(cell8 + j, 1);
                else
                    cell8[j] = (FCELL)beam[i][j];
            }
            Rast_put_f_row(fd8, cell8);
        }

        if (glob_rad != NULL) {
            for (j = 0; j < n; j++) {
                if (globrad[i][j] == UNDEFZ)
                    Rast_set_f_null_value(cell12 + j, 1);
                else
                    cell12[j] = (FCELL)globrad[i][j];
            }
            Rast_put_f_row(fd12, cell12);
        }

        if (insol_time != NULL) {
            for (j = 0; j < n; j++) {
                if (insol[i][j] == UNDEFZ)
                    Rast_set_f_null_value(cell11 + j, 1);
                else
                    cell11[j] = (FCELL)insol[i][j];
            }
            Rast_put_f_row(fd11, cell11);
        }

        if (diff_rad != NULL) {
            for (j = 0; j < n; j++) {
                if (diff[i][j] == UNDEFZ)
                    Rast_set_f_null_value(cell9 + j, 1);
                else
                    cell9[j] = (FCELL)diff[i][j];
            }
            Rast_put_f_row(fd9, cell9);
        }

        if (refl_rad != NULL) {
            for (j = 0; j < n; j++) {
                if (refl[i][j] == UNDEFZ)
                    Rast_set_f_null_value(cell10 + j, 1);
                else
                    cell10[j] = (FCELL)refl[i][j];
            }
            Rast_put_f_row(fd10, cell10);
        }
    }

    if (incidout != NULL) {
        Rast_close(fd7);
        Rast_write_history(incidout, &hist);
    }
    if (beam_rad != NULL) {
        Rast_close(fd8);
        Rast_write_history(beam_rad, &hist);
    }
    if (diff_rad != NULL) {
        Rast_close(fd9);
        Rast_write_history(diff_rad, &hist);
    }
    if (refl_rad != NULL) {
        Rast_close(fd10);
        Rast_write_history(refl_rad, &hist);
    }
    if (insol_time != NULL) {
        Rast_close(fd11);
        Rast_write_history(insol_time, &hist);
    }
    if (glob_rad != NULL) {
        Rast_close(fd12);
        Rast_write_history(glob_rad, &hist);
    }

    return 1;
}

/**********************************************************/

void joules2(struct SunGeometryConstDay *sunGeom,
             struct SunGeometryVarDay *sunVarGeom,
             struct SunGeometryVarSlope *sunSlopeGeom,
             struct SolarRadVar *sunRadVar, struct GridGeometry *gridGeom,
             unsigned char *horizonpointer, double latitude, double longitude,
             double *Pbeam_e, double *Pdiff_e, double *Prefl_e,
             double *Pinsol_t)
{

    double s0, dfr, dfr_rad;
    double ra, dra;
    int ss = 1;
    double firstTime;
    double firstAngle, lastAngle;
    int srStepNo;
    double bh;
    double rr;

    com_par(sunGeom, sunVarGeom, gridGeom, latitude, longitude);

    if (ttime != NULL) { /*irradiance */

        s0 = lumcline2(sunGeom, sunVarGeom, sunSlopeGeom, gridGeom,
                       horizonpointer);

        if (sunVarGeom->solarAltitude > 0.) {
            if ((!sunVarGeom->isShadow) && (s0 > 0.)) {
                ra = brad(s0, &bh, sunVarGeom, sunSlopeGeom,
                          sunRadVar); /* beam radiation */
                *Pbeam_e += ra;
            }
            else {
                *Pbeam_e = 0.;
                bh = 0.;
            }

            if ((diff_rad != NULL) || (glob_rad != NULL)) {
                dra = drad(s0, bh, &rr, sunVarGeom, sunSlopeGeom,
                           sunRadVar); /* diffuse rad. */
                *Pdiff_e += dra;
            }
            if ((refl_rad != NULL) || (glob_rad != NULL)) {
                if ((diff_rad == NULL) && (glob_rad == NULL))
                    drad(s0, bh, &rr, sunVarGeom, sunSlopeGeom, sunRadVar);
                *Prefl_e += rr; /* reflected rad. */
            }
        } /* solarAltitude */
    }
    else {
        /* all-day radiation */

        srStepNo = (int)(sunGeom->sunrise_time / step);

        if ((sunGeom->sunrise_time - srStepNo * step) > 0.5 * step) {
            firstTime = (srStepNo + 1.5) * step;
        }
        else {
            firstTime = (srStepNo + 0.5) * step;
        }

        firstAngle = (firstTime - 12) * HOURANGLE;
        lastAngle = (sunGeom->sunset_time - 12) * HOURANGLE;

        dfr_rad = step * HOURANGLE;

        sunGeom->timeAngle = firstAngle;

        varCount_global = 0;

        dfr = step;

        while (ss == 1) {

            com_par(sunGeom, sunVarGeom, gridGeom, latitude, longitude);
            s0 = lumcline2(sunGeom, sunVarGeom, sunSlopeGeom, gridGeom,
                           horizonpointer);

            if (sunVarGeom->solarAltitude > 0.) {

                if ((!sunVarGeom->isShadow) && (s0 > 0.)) {
                    *Pinsol_t += dfr;
                    ra = brad(s0, &bh, sunVarGeom, sunSlopeGeom, sunRadVar);
                    *Pbeam_e += dfr * ra;
                    ra = 0.;
                }
                else {
                    bh = 0.;
                }
                if ((diff_rad != NULL) || (glob_rad != NULL)) {
                    dra =
                        drad(s0, bh, &rr, sunVarGeom, sunSlopeGeom, sunRadVar);
                    *Pdiff_e += dfr * dra;
                    dra = 0.;
                }
                if ((refl_rad != NULL) || (glob_rad != NULL)) {
                    if ((diff_rad == NULL) && (glob_rad == NULL)) {
                        drad(s0, bh, &rr, sunVarGeom, sunSlopeGeom, sunRadVar);
                    }
                    *Prefl_e += dfr * rr;
                    rr = 0.;
                }
            } /* illuminated */

            sunGeom->timeAngle = sunGeom->timeAngle + dfr_rad;

            if (sunGeom->timeAngle > lastAngle) {
                ss = 0; /* we've got the sunset */
            }
        } /* end of while */
    }     /* all-day radiation */
}

/*////////////////////////////////////////////////////////////////////// */

/*
 * void where_is_point(void)
 * {
 * double sx, sy;
 * double dx, dy;
 * double adx, ady;
 * int i, j;
 *
 * sx = xx0 * invstepx + TOLER;
 * sy = yy0 * invstepy + TOLER;
 *
 * i = (int)sx;
 * j = (int)sy;
 * if (i < n - 1 && j < m - 1) {
 *
 * dx = xx0 - (double)i *stepx;
 * dy = yy0 - (double)j *stepy;
 *
 * adx = fabs(dx);
 * ady = fabs(dy);
 *
 * if ((adx > TOLER) && (ady > TOLER)) {
 * cube(j, i);
 * return;
 * }
 * else if ((adx > TOLER) && (ady < TOLER)) {
 * line_x(j, i);
 * return;
 * }
 * else if ((adx < TOLER) && (ady > TOLER)) {
 * line_y(j, i);
 * return;
 * }
 * else if ((adx < TOLER) && (ady < TOLER)) {
 * vertex(j, i);
 * return;
 * }
 *
 *
 * }
 * else {
 * func = NULL;
 * }
 * }
 *
 */

void where_is_point(double *length, struct SunGeometryVarDay *sunVarGeom,
                    struct GridGeometry *gridGeom)
{
    double sx, sy;
    double dx, dy;

    /*              double adx, ady; */
    int i, j;

    sx = gridGeom->xx0 * invstepx +
         offsetx; /* offset 0.5 cell size to get the right cell i, j */
    sy = gridGeom->yy0 * invstepy + offsety;

    i = (int)sx;
    j = (int)sy;

    /*      if (i < n-1  && j < m-1) {    to include last row/col */
    if (i <= n - 1 && j <= m - 1) {

        dx = (double)i * gridGeom->stepx;
        dy = (double)j * gridGeom->stepy;

        *length = distance(
            gridGeom->xg0, dx, gridGeom->yg0,
            dy); /* dist from orig. grid point to the current grid point */

        sunVarGeom->zp = z[j][i];

        /*
         * cube(j, i);
         */
    }
}

/*
 * void vertex(jmin, imin)
 * int jmin, imin;
 * {
 * zp = z[jmin][imin];
 * if ((zp == UNDEFZ))
 * func = NULL;
 * }
 * void line_x(jmin, imin)
 * int jmin, imin;
 * {
 * double c1, c2;
 * double d1, d2, e1, e2;
 * e1 = (double)imin *stepx;
 * e2 = (double)(imin + 1) * stepx;
 *
 * c1 = z[jmin][imin];
 * c2 = z[jmin][imin + 1];
 * if (!((c1 == UNDEFZ) || (c2 == UNDEFZ))) {
 *
 * if (dist <= 1.0) {
 * d1 = (xx0 - e1) / (e2 - e1);
 * d2 = 1 - d1;
 * if (d1 < d2)
 * zp = c1;
 * else
 * zp = c2;
 * }
 *
 * if (dist > 1.0)
 * zp = AMAX1(c1, c2);
 * }
 * else
 * func = NULL;
 * }
 *
 *
 * void line_y(jmin, imin)
 * int jmin, imin;
 * {
 * double c1, c2;
 * double d1, d2, e1, e2;
 * e1 = (double)jmin *stepy;
 * e2 = (double)(jmin + 1) * stepy;
 *
 * c1 = z[jmin][imin];
 * c2 = z[jmin + 1][imin];
 * if (!((c1 == UNDEFZ) || (c2 == UNDEFZ))) {
 *
 * if (dist <= 1.0) {
 * d1 = (yy0 - e1) / (e2 - e1);
 * d2 = 1 - d1;
 * if (d1 < d2)
 * zp = c1;
 * else
 * zp = c2;
 * }
 *
 * if (dist > 1.0)
 * zp = AMAX1(c1, c2);
 *
 * }
 * else
 * func = NULL;
 *
 * }
 *
 * void cube(jmin, imin)
 * int jmin, imin;
 * {
 * int i, ig = 0;
 * double x1, x2, y1, y2;
 * double v[4], vmin = BIG;
 * double c[4], cmax = -BIG;
 *
 * x1 = (double)imin *stepx;
 * x2 = x1 + stepx;
 *
 * y1 = (double)jmin *stepy;
 * y2 = y1 + stepy;
 *
 * v[0] = DISTANCE2(x1, y1);
 *
 * if (v[0] < vmin) {
 * ig = 0;
 * vmin = v[0];
 * }
 * v[1] = DISTANCE2(x2, y1);
 *
 * if (v[1] < vmin) {
 * ig = 1;
 * vmin = v[1];
 * }
 *
 * v[2] = DISTANCE2(x2, y2);
 * if (v[2] < vmin) {
 * ig = 2;
 * vmin = v[2];
 * }
 *
 * v[3] = DISTANCE2(x1, y2);
 * if (v[3] < vmin) {
 * ig = 3;
 * vmin = v[3];
 * }
 *
 * c[0] = z[jmin][imin];
 * c[1] = z[jmin][imin + 1];
 * c[2] = z[jmin + 1][imin + 1];
 * c[3] = z[jmin + 1][imin];
 *
 *
 * if (dist <= 1.0) {
 *
 * if (c[ig] != UNDEFZ)
 * zp = c[ig];
 * else
 * func = NULL;
 * return;
 * }
 *
 * if (dist > 1.0) {
 * for (i = 0; i < 4; i++) {
 * if (c[i] != UNDEFZ) {
 * cmax = AMAX1(cmax, c[i]);
 * zp = cmax;
 * }
 * else
 * func = NULL;
 * }
 * }
 * }
 */

/*
 * void cube(jmin, imin)
 * int jmin, imin;
 * {
 * zp = z[jmin][imin];
 *
 * }
 */

/*void cube(jmin, imin)
   {
   } */

/*////////////////////////////////////////////////////////////////////// */

void calculate(double singleSlope, double singleAspect, double singleAlbedo,
               double singleLinke, struct GridGeometry gridGeom)
{
    int i, j, l;

    /*                      double energy; */
    int someRadiation;
    int numRows;
    int arrayOffset;
    double lum = 0.0, q1 = 0.0;
    double dayRad;
    double latid_l = 0.0, cos_u = 0.0, cos_v = 0.0, sin_u = 0.0, sin_v = 0.0;
    double sin_phi_l = 0.0, tan_lam_l = 0.0;
    double zmax = 0;
    double longitTime = 0.;
    double locTimeOffset;
    double latitude, longitude;
    double coslat = 0.0;
    bool shouldBeBestAM = false;
    bool isBestAM = false;

    struct SunGeometryConstDay sunGeom;
    struct SunGeometryVarDay sunVarGeom;
    struct SunGeometryVarSlope sunSlopeGeom;
    struct SolarRadVar sunRadVar;

    sunSlopeGeom.slope = singleSlope;
    sunSlopeGeom.aspect = singleAspect;
    sunRadVar.alb = singleAlbedo;
    sunRadVar.linke = singleLinke;
    sunRadVar.cbh = 1.0;
    sunRadVar.cdh = 1.0;

    sunGeom.sindecl = sin(declination);
    sunGeom.cosdecl = cos(declination);

    someRadiation = (beam_rad != NULL) || (insol_time != NULL) ||
                    (diff_rad != NULL) || (refl_rad != NULL) ||
                    (glob_rad != NULL);

    if (incidout != NULL) {
        lumcl = (float **)G_calloc((m), sizeof(float *));
        for (l = 0; l < m; l++) {
            lumcl[l] = (float *)G_calloc((n), sizeof(float *));
        }
        for (j = 0; j < m; j++) {
            for (i = 0; i < n; i++)
                lumcl[j][i] = UNDEFZ;
        }
    }

    if (beam_rad != NULL) {
        beam = (float **)G_calloc((m), sizeof(float *));
        for (l = 0; l < m; l++) {
            beam[l] = (float *)G_calloc((n), sizeof(float *));
        }

        for (j = 0; j < m; j++) {
            for (i = 0; i < n; i++)
                beam[j][i] = UNDEFZ;
        }
    }

    if (insol_time != NULL) {
        insol = (float **)G_calloc((m), sizeof(float *));
        for (l = 0; l < m; l++) {
            insol[l] = (float *)G_calloc((n), sizeof(float *));
        }

        for (j = 0; j < m; j++) {
            for (i = 0; i < n; i++)
                insol[j][i] = UNDEFZ;
        }
    }

    if (diff_rad != NULL) {
        diff = (float **)G_calloc((m), sizeof(float *));
        for (l = 0; l < m; l++) {
            diff[l] = (float *)G_calloc((n), sizeof(float *));
        }

        for (j = 0; j < m; j++) {
            for (i = 0; i < n; i++)
                diff[j][i] = UNDEFZ;
        }
    }

    if (refl_rad != NULL) {
        refl = (float **)G_calloc((m), sizeof(float *));
        for (l = 0; l < m; l++) {
            refl[l] = (float *)G_calloc((n), sizeof(float *));
        }

        for (j = 0; j < m; j++) {
            for (i = 0; i < n; i++)
                refl[j][i] = UNDEFZ;
        }
    }

    if (glob_rad != NULL) {
        globrad = (float **)G_calloc((m), sizeof(float *));
        for (l = 0; l < m; l++) {
            globrad[l] = (float *)G_calloc((n), sizeof(float *));
        }

        for (j = 0; j < m; j++) {
            for (i = 0; i < n; i++)
                globrad[j][i] = UNDEFZ;
        }
    }

    sunRadVar.G_norm_extra = com_sol_const(day);

    numRows = m / numPartitions;

    if (useCivilTime()) {
        /* We need to calculate the deviation of the local solar time from the
         * "local clock time". */
        dayRad = 2. * M_PI * day / 365.25;
        locTimeOffset =
            +0.128 * sin(dayRad - 0.04887) + 0.165 * sin(2 * dayRad + 0.34383);

        /* Time offset due to timezone as input by user */

        locTimeOffset += civilTime;
        setTimeOffset(locTimeOffset);
    }
    else {
        setTimeOffset(0.);
    }
    int shadowoffset_base = shadowoffset;

    for (j = 0; j < m; j++) {
        G_percent(j, m - 1, 2);

        if (j % (numRows) == 0) {
            INPUT_part(j, &zmax);
            arrayOffset = 0;
            shadowoffset = 0;
        }
        sunVarGeom.zmax = zmax;
        shadowoffset_base = (j % (numRows)) * n * arrayNumInt;
#pragma omp parallel firstprivate(                                             \
    q1, tan_lam_l, z1, i, shadowoffset, longitTime, coslat, coslatsq,          \
    latitude, longitude, sin_phi_l, latid_l, sin_u, cos_u, sin_v, cos_v, lum,  \
    gridGeom, elevin, aspin, slopein, civiltime, linkein, albedo, latin,       \
    coefbh, coefdh, incidout, longin, horizon, beam_rad, insol_time, diff_rad, \
    refl_rad, glob_rad, mapset, per, decimals, str_step, shouldBeBestAM,       \
    isBestAM)
        {
#pragma omp for schedule(dynamic) firstprivate(sunGeom, sunVarGeom,      \
                                               sunSlopeGeom, sunRadVar)  \
    lastprivate(sunGeom, sunVarGeom, sunSlopeGeom, sunRadVar) reduction( \
        max                                                              \
        : linke_max, albedo_max, lat_max, sunrise_max, sunset_max)       \
        reduction(min                                                    \
                  : linke_min, albedo_min, lat_min, sunrise_min, sunset_min)
            for (i = 0; i < n; i++) {
                shadowoffset = shadowoffset_base + (arrayNumInt * i);
                if (useCivilTime()) {
                    /* sun travels 15deg per hour, so 1 TZ every 15 deg and 15
                     * TZs * 24hrs = 360deg */
                    longitTime = -longitudeArray[arrayOffset][i] / 15.;
                }

                gridGeom.xg0 = gridGeom.xx0 = (double)i * gridGeom.stepx;
                gridGeom.yg0 = gridGeom.yy0 = (double)j * gridGeom.stepy;

                gridGeom.xp = xmin + gridGeom.xx0;
                gridGeom.yp = ymin + gridGeom.yy0;

                if (ll_correction) {
                    coslat = cos(deg2rad * gridGeom.yp);
                    coslatsq = coslat * coslat;
                }

                /* func = NULL; */

                sunVarGeom.z_orig = z1 = sunVarGeom.zp = z[arrayOffset][i];

                if (sunVarGeom.z_orig != UNDEFZ) {
                    if (aspin != NULL) {
                        o_orig = o[arrayOffset][i];
                        if (o[arrayOffset][i] != 0.)
                            sunSlopeGeom.aspect = o[arrayOffset][i] * deg2rad;
                        else
                            sunSlopeGeom.aspect = UNDEF;
                    }
                    if (slopein != NULL) {
                        sunSlopeGeom.slope = s[arrayOffset][i] * deg2rad;
                    }
                    if (linkein != NULL) {
                        sunRadVar.linke = li[arrayOffset][i];
                        linke_max = AMAX1(linke_max, sunRadVar.linke);
                        linke_min = AMIN1(linke_min, sunRadVar.linke);
                    }
                    if (albedo != NULL) {
                        sunRadVar.alb = a[arrayOffset][i];
                        albedo_max = AMAX1(albedo_max, sunRadVar.alb);
                        albedo_min = AMIN1(albedo_min, sunRadVar.alb);
                    }
                    if (latin != NULL) {
                        latitude = latitudeArray[arrayOffset][i];
                        lat_max = AMAX1(lat_max, latitude);
                        lat_min = AMIN1(lat_min, latitude);
                        latitude *= deg2rad;
                    }
                    if (longin != NULL) {
                        longitude = longitudeArray[arrayOffset][i];
                        /* lon_max = AMAX1(lon_max, longitude); */
                        /* lon_min = AMIN1(lon_min, longitude); */
                        longitude *= deg2rad;
                    }

                    if ((G_projection() != PROJECTION_LL)) {

                        if (latin == NULL || longin == NULL) {
                            /* if either is missing we have to calc both from
                             * current projection */
                            longitude = gridGeom.xp;
                            latitude = gridGeom.yp;

                            if (GPJ_transform(&iproj, &oproj, &tproj, PJ_FWD,
                                              &longitude, &latitude, NULL) < 0)
                                G_fatal_error(_("Error in %s (projection of "
                                                "input coordinate pair)"),
                                              "GPJ_transform()");

                            lat_max = AMAX1(lat_max, latitude);
                            lat_min = AMIN1(lat_min, latitude);
                            latitude *= deg2rad;
                            longitude *= deg2rad;
                        }
                    }
                    else { /* ll projection */
                        latitude = gridGeom.yp;
                        longitude = gridGeom.xp;
                        lat_max = AMAX1(lat_max, latitude);
                        lat_min = AMIN1(lat_min, latitude);
                        latitude *= deg2rad;
                        longitude *= deg2rad;
                    }

                    if (coefbh != NULL) {
                        sunRadVar.cbh = cbhr[arrayOffset][i];
                    }
                    if (coefdh != NULL) {
                        sunRadVar.cdh = cdhr[arrayOffset][i];
                    }
                    cos_u =
                        cos(M_PI / 2 - sunSlopeGeom.slope); /* = sin(slope) */
                    sin_u =
                        sin(M_PI / 2 - sunSlopeGeom.slope); /* = cos(slope) */
                    cos_v = cos(M_PI / 2 + sunSlopeGeom.aspect);
                    sin_v = sin(M_PI / 2 + sunSlopeGeom.aspect);

                    if (ttime != NULL)
                        sunGeom.timeAngle = tim;

                    gridGeom.sinlat = sin(-latitude);
                    gridGeom.coslat = cos(-latitude);

                    sin_phi_l = -gridGeom.coslat * cos_u * sin_v +
                                gridGeom.sinlat * sin_u;
                    latid_l = asin(sin_phi_l);

                    q1 = gridGeom.sinlat * cos_u * sin_v +
                         gridGeom.coslat * sin_u;

                    if (q1 != 0.0) {
                        tan_lam_l = -cos_u * cos_v / q1;
                        sunSlopeGeom.longit_l = atan(tan_lam_l);
                        isBestAM = (tan_lam_l > 0);
                    }
                    else {
                        sunSlopeGeom.longit_l = pihalf;
                        isBestAM = true;
                    }

                    shouldBeBestAM = (0.0 < sunSlopeGeom.aspect &&
                                      sunSlopeGeom.aspect <= M_PI);
                    sunSlopeGeom.shift12hrs = (shouldBeBestAM != isBestAM);

                    sunSlopeGeom.lum_C31_l = cos(latid_l) * sunGeom.cosdecl;
                    sunSlopeGeom.lum_C33_l = sin_phi_l * sunGeom.sindecl;

                    if ((incidout != NULL) || someRadiation) {
                        com_par_const(longitTime, &sunGeom, &gridGeom);
                        sunrise_min = AMIN1(sunrise_min, sunGeom.sunrise_time);
                        sunrise_max = AMAX1(sunrise_max, sunGeom.sunrise_time);
                        sunset_min = AMIN1(sunset_min, sunGeom.sunset_time);
                        sunset_max = AMAX1(sunset_max, sunGeom.sunset_time);
                    }

                    if (incidout != NULL) {
                        com_par(&sunGeom, &sunVarGeom, &gridGeom, latitude,
                                longitude);
                        lum = lumcline2(&sunGeom, &sunVarGeom, &sunSlopeGeom,
                                        &gridGeom, horizonarray + shadowoffset);
                        if (lum > 0.) {
                            lum = rad2deg * asin(lum);
                            lumcl[j][i] = (float)lum;
                        }
                        else
                            lumcl[j][i] = UNDEFZ;
                    }

                    double Pbeam_e = 0.;
                    double Pdiff_e = 0.;
                    double Prefl_e = 0.;
                    double Pinsol_t = 0.;

                    if (someRadiation) {
                        joules2(
                            &sunGeom, &sunVarGeom, &sunSlopeGeom, &sunRadVar,
                            &gridGeom, horizonarray + shadowoffset, latitude,
                            longitude, &Pbeam_e, &Pdiff_e, &Prefl_e, &Pinsol_t);
                        if (beam_rad != NULL)
                            beam[j][i] = (float)Pbeam_e;
                        if (insol_time != NULL)
                            insol[j][i] = (float)Pinsol_t;
                        /*  G_debug(3,"\n %f",insol[j][i]); */
                        if (diff_rad != NULL)
                            diff[j][i] = (float)Pdiff_e;
                        if (refl_rad != NULL)
                            refl[j][i] = (float)Prefl_e;
                        if (glob_rad != NULL)
                            globrad[j][i] =
                                (float)(Pbeam_e + Pdiff_e + Prefl_e);
                    }

                } /* undefs */
            }
        }
        arrayOffset++;
    }

    /* re-use &hist, but try all to initiate it for any case */
    /*   note this will result in incorrect map titles       */
    if (incidout != NULL) {
        Rast_short_history(incidout, "raster", &hist);
    }
    else if (beam_rad != NULL) {
        Rast_short_history(beam_rad, "raster", &hist);
    }
    else if (diff_rad != NULL) {
        Rast_short_history(diff_rad, "raster", &hist);
    }
    else if (refl_rad != NULL) {
        Rast_short_history(refl_rad, "raster", &hist);
    }
    else if (insol_time != NULL) {
        Rast_short_history(insol_time, "raster", &hist);
    }
    else if (glob_rad != NULL) {
        Rast_short_history(glob_rad, "raster", &hist);
    }
    else
        G_fatal_error("Failed to init map history: no output maps requested!");

    Rast_append_format_history(
        &hist,
        " ----------------------------------------------------------------");
    Rast_append_format_history(
        &hist, " Day [1-365]:                              %d", day);

    if (ttime != NULL)
        Rast_append_format_history(
            &hist, " Local (solar) time (decimal hr.):         %.4f", timo);

    Rast_append_format_history(
        &hist, " Solar constant (W/m^2):                   %f", solar_constant);
    Rast_append_format_history(&hist,
                               " Extraterrestrial irradiance (W/m^2):      %f",
                               sunRadVar.G_norm_extra);
    Rast_append_format_history(
        &hist, " Declination (rad):                        %f", -declination);

    Rast_append_format_history(
        &hist, " Latitude min-max(deg):                    %.4f - %.4f",
        lat_min, lat_max);

    if (ttime != NULL) {
        Rast_append_format_history(
            &hist, " Sunrise time (hr.):                       %.2f",
            sunGeom.sunrise_time);
        Rast_append_format_history(
            &hist, " Sunset time (hr.):                        %.2f",
            sunGeom.sunset_time);
        Rast_append_format_history(
            &hist, " Daylight time (hr.):                      %.2f",
            sunGeom.sunset_time - sunGeom.sunrise_time);
    }
    else {
        Rast_append_format_history(
            &hist, " Sunrise time min-max (hr.):               %.2f - %.2f",
            sunrise_min, sunrise_max);
        Rast_append_format_history(
            &hist, " Sunset time min-max (hr.):                %.2f - %.2f",
            sunset_min, sunset_max);
        Rast_append_format_history(
            &hist, " Time step (hr.):                          %.4f", step);
    }

    if (incidout != NULL || ttime != NULL) {
        Rast_append_format_history(
            &hist, " Solar altitude (deg):                     %.4f",
            sunVarGeom.solarAltitude * rad2deg);
        Rast_append_format_history(
            &hist, " Solar azimuth (deg):                      %.4f",
            sunVarGeom.solarAzimuth * rad2deg);
    }

    if (linkein == NULL)
        Rast_append_format_history(
            &hist, " Linke turbidity factor:                   %.1f",
            sunRadVar.linke);
    else
        Rast_append_format_history(
            &hist, " Linke turbidity factor min-max:           %.1f-%.1f",
            linke_min, linke_max);

    if (albedo == NULL)
        Rast_append_format_history(
            &hist, " Ground albedo:                            %.3f",
            sunRadVar.alb);
    else
        Rast_append_format_history(
            &hist, " Ground albedo min-max:                    %.3f-%.3f",
            albedo_min, albedo_max);

    Rast_append_format_history(
        &hist,
        " -----------------------------------------------------------------");

    Rast_command_history(&hist);
    /* don't call Rast_write_history() until after Rast_close() or it just gets
     * overwritten */

} /* End of ) function */

double com_declin(int no_of_day)
{
    double d1, decl;

    /* stretch day number in the following calculation for siderial effect? */
    /*   ? double siderial_day = no_of_day + ((no_of_day * 0.25) / 365.) ? */
    /* or just change d1 to : d1 = pi2 * no_of_day / 365.0;  ? */
    d1 = pi2 * no_of_day / 365.25;
    decl = asin(0.3978 * sin(d1 - 1.4 + 0.0355 * sin(d1 - 0.0489)));
    decl = -decl;
    /*      G_debug(3," declination : %lf\n", decl); */

    return (decl);
}

int test(void)
{
    /* not finished yet */
    int dej;

    G_message("\n ddd: %f", declin);
    dej = asin(-declin / 0.4093) * 365. / pi2 + 81;
    /*        dej = asin(-declin/23.35 * deg2rad) / 0.9856 - 284; */
    /*      dej = dej - 365; */
    G_message("\n d: %d ", dej);
    if (dej < day - 5 || dej > day + 5)
        return 0;
    else
        return 1;
}
