#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>

#include <grass/gis.h>
#include <grass/glocale.h>

#include "local_proto.h"
#include "earth_sun.h"

#define PI            M_PI
#define D2R           M_PI / 180.

#define MAX_STR       256
#define METADATA_SIZE 65535 /* MTL.txt file size  65535 bytes */
#define TM5_MET_SIZE  28700 /* .met file size 28686 bytes */

static void chrncpy(char *dest, char src[], int n)
{
    int i;

    for (i = 0; i < n && src[i] != '\0' && src[i] != '\"'; i++)
        dest[i] = src[i];
    dest[i] = '\0';
}

static void date_replace_slash(char *str)
{
    while (*str) {
        if (*str == '/')
            *str = '-';
        str++;
    }
}

/* OLD Metadata Files */
void get_metformat(const char metadata[], char *key, char value[])
{
    int i = 0;
    char *ptrmet = strstr(metadata, key);
    char *ptr;

    if (ptrmet != NULL) {
        ptr = strstr(ptrmet, " VALUE ");
        if (ptr != NULL) {
            while (*ptr++ != '=')
                ;
            while (*ptr <= ' ')
                ptr++;
            if (*ptr == '\"')
                ptr++;
            while (i < MAX_STR && *ptr != '\"' && *ptr >= ' ')
                value[i++] = *ptr++;
        }
    }
    value[i] = '\0';
}

/* future implementation
   double get_metdouble(const char metadata[], char *format, int code, char
   value[])
   {
   char key[MAX_STR];

   sprintf(key, format, code);
   get_metformat(metadata, key, value);
   return atof(value);
   }
 */

/* NEW Metadata Files */
void get_mtlformat(const char metadata[], char *key, char value[])
{
    int i = 0;
    char *ptr = strstr(metadata, key);

    if (ptr != NULL) {
        while (*ptr++ != '=')
            ;
        while (*ptr <= ' ')
            ptr++;
        if (*ptr == '\"')
            ptr++;
        while (i < MAX_STR && *ptr != '\"' && *ptr > ' ')
            value[i++] = *ptr++;
    }
    value[i] = '\0';
}

/* future implementation
   double get_mtldouble(const char metadata[], char *format, int code, char
   value[])
   {
   char key[MAX_STR];

   sprintf(key, format, code);
   get_mtlformat(metadata, key, value);
   return atof(value);
   }
 */

/****************************************************************************
 * PURPOSE:     Read parameters from Landsat metadata files
 *****************************************************************************/

void lsat_metadata(char *metafile, lsat_data *lsat)
{
    FILE *f;
    char mtldata[METADATA_SIZE];
    char key[MAX_STR], value[MAX_STR];
    void (*get_mtldata)(const char[], char *, char[]);

    /* void (*get_mtlreal) (const char[], char *, int, char[]); */
    int i, j, ver_mtl;

    /* store metadata in ram */
    if ((f = fopen(metafile, "r")) == NULL)
        G_fatal_error(_("Metadata file <%s> not found"), metafile);
    i = fread(mtldata, METADATA_SIZE, 1, f);
    (void)fclose(f);

    /* set version of the metadata file */
    get_mtldata =
        (strstr(mtldata, " VALUE ") != NULL) ? get_metformat : get_mtlformat;
    /* future implementation
       if (strstr(mtldata, " VALUE ") != NULL)
       {
       get_mtldata = get_metformat;
       get_mtlreal = get_metdouble;
       }
       else
       {
       get_mtldata = get_mtlformat;
       get_mtlreal = get_mtldouble;
       }
     */
    ver_mtl = (strstr(mtldata, "QCALMAX_BAND") != NULL) ? 0 : 1;

    /* Fill with product metadata */
    get_mtldata(mtldata, "SPACECRAFT_ID", value);
    if (value[0] == '\0') {
        get_mtldata(mtldata, "PLATFORMSHORTNAME", value);
    }
    i = 0;
    while (value[i] && (value[i] < '0' || value[i] > '9'))
        i++;
    lsat->number = atoi(value + i);

    get_mtldata(mtldata, "SENSOR_ID", value);
    if (value[0] == '\0') {
        get_mtldata(mtldata, "SENSORSHORTNAME", value);
    }
    chrncpy(lsat->sensor, value, 8);

    get_mtldata(mtldata, "DATE_ACQUIRED", value);
    if (value[0] == '\0') {
        get_mtldata(mtldata, "ACQUISITION_DATE", value);
        if (value[0] == '\0') {
            get_mtldata(mtldata, "CALENDARDATE", value);
        }
    }
    if (value[0] != '\0') {
        date_replace_slash(value);
        chrncpy(lsat->date, value, 10);
    }
    else
        G_warning("Using adquisition date from the command line 'date'");

    get_mtldata(mtldata, "FILE_DATE", value);
    if (value[0] == '\0') {
        get_mtldata(mtldata, "CREATION_TIME", value);
        if (value[0] == '\0') {
            get_mtldata(mtldata, "PRODUCTIONDATETIME", value);
        }
    }
    if (value[0] != '\0') {
        date_replace_slash(value);
        chrncpy(lsat->creation, value, 10);
    }
    else
        G_warning("Using production date from the command line 'product_date'");

    get_mtldata(mtldata, "SUN_AZIMUTH", value);
    lsat->sun_az = atof(value);
    if (lsat->sun_az == 0.)
        G_warning("Sun azimuth is %f", lsat->sun_az);

    get_mtldata(mtldata, "SUN_ELEVATION", value);
    if (value[0] == '\0') {
        get_mtldata(mtldata, "SolarElevation", value);
    }
    lsat->sun_elev = atof(value);
    if (lsat->sun_elev == 0.)
        G_warning("Sun elevation is %f", lsat->sun_elev);

    get_mtldata(mtldata, "SCENE_CENTER_TIME", value);
    if (value[0] == '\0') {
        get_mtldata(mtldata, "SCENE_CENTER_SCAN_TIME", value);
    }
    if (value[0] != '\0') {
        value[strlen(value) - 1] = '\0';  /* Remove trailing 'z' */
        G_llres_scan(value, &lsat->time); /* Cast from hh:mm:ss into hh.hhh */
    }
    if (lsat->time == 0.)
        G_warning("Scene time is %f", lsat->time);

    /* Fill the data with the basic/default sensor parameters */
    switch (lsat->number) {
    case 1:
        set_MSS1(lsat);
        break;
    case 2:
        set_MSS2(lsat);
        break;
    case 3:
        set_MSS3(lsat);
        break;
    case 4:
        if (lsat->sensor[0] == 'M')
            set_MSS4(lsat);
        else
            set_TM4(lsat);
        break;
    case 5:
        if (lsat->sensor[0] == 'M')
            set_MSS5(lsat);
        else
            set_TM5(lsat);
        break;
    case 7: {
        char *fmt_gain[] = {"BAND%d_GAIN%d", " GAIN_BAND_%d_VCID_%d"};
        for (i = 1, j = 0; i < 9; i++) {
            sprintf(key, fmt_gain[ver_mtl], i, 1);
            if (i != 6)
                key[ver_mtl == 0 ? 10 : 12] = '\0';
            get_mtldata(mtldata, key, value + j++);
            if (i == 6) {
                sprintf(key, fmt_gain[ver_mtl], i, 2);
                get_mtldata(mtldata, key, value + j++);
            }
        }
        value[j] = '\0';
        G_debug(1, "ETM+ gain = [%s]", value);
        set_ETM(lsat, value);
        break;
    }
    case 8:
        set_OLI(lsat);
        break;
    default:
        G_warning("Unable to recognize satellite platform [%d]", lsat->number);
        break;
    }

    /* Update the information from metadata file, if infile */
    if (get_mtldata == get_mtlformat) {
        if (ver_mtl == 0) {
            G_verbose_message("Metada file is MTL file: old format");
            for (i = 0; i < lsat->bands; i++) {
                sprintf(key, "LMAX_BAND%d", lsat->band[i].code);
                get_mtldata(mtldata, key, value);
                lsat->band[i].lmax = atof(value);
                sprintf(key, "LMIN_BAND%d", lsat->band[i].code);
                get_mtldata(mtldata, key, value);
                lsat->band[i].lmin = atof(value);
                sprintf(key, "QCALMAX_BAND%d", lsat->band[i].code);
                get_mtldata(mtldata, key, value);
                lsat->band[i].qcalmax = atof(value);
                sprintf(key, "QCALMIN_BAND%d", lsat->band[i].code);
                get_mtldata(mtldata, key, value);
                lsat->band[i].qcalmin = atof(value);
            }
        }
        else {
            G_verbose_message("Metada file is MTL file: new format");
            /* Other possible values in the metadata file */
            get_mtldata(mtldata, "EARTH_SUN_DISTANCE",
                        value); /* Necessary after */
            if (value[0] != '\0')
                lsat->dist_es = atof(value);
            if (strstr(mtldata, "RADIANCE_MAXIMUM_BAND") != NULL) {
                G_verbose_message(
                    "RADIANCE & QUANTIZE from  MIN_MAX_(RADIANCE|PIXEL_VALUE)");
                for (i = 0; i < lsat->bands; i++) {
                    if (lsat->number == 7 && lsat->band[i].thermal) {
                        sprintf(key, "RADIANCE_MAXIMUM_BAND_6_VCID_%d",
                                lsat->band[i].code - 60);
                        get_mtldata(mtldata, key, value);
                        lsat->band[i].lmax = atof(value);
                        sprintf(key, "RADIANCE_MINIMUM_BAND_6_VCID_%d",
                                lsat->band[i].code - 60);
                        get_mtldata(mtldata, key, value);
                        lsat->band[i].lmin = atof(value);
                        sprintf(key, "QUANTIZE_CAL_MAX_BAND_6_VCID_%d",
                                lsat->band[i].code - 60);
                        get_mtldata(mtldata, key, value);
                        lsat->band[i].qcalmax = atof(value);
                        sprintf(key, "QUANTIZE_CAL_MIN_BAND_6_VCID_%d",
                                lsat->band[i].code - 60);
                        get_mtldata(mtldata, key, value);
                        lsat->band[i].qcalmin = atof(value);
                    }
                    else {
                        sprintf(key, "RADIANCE_MAXIMUM_BAND_%d",
                                lsat->band[i].code);
                        get_mtldata(mtldata, key, value);
                        lsat->band[i].lmax = atof(value);
                        sprintf(key, "RADIANCE_MINIMUM_BAND_%d",
                                lsat->band[i].code);
                        get_mtldata(mtldata, key, value);
                        lsat->band[i].lmin = atof(value);
                        sprintf(key, "QUANTIZE_CAL_MAX_BAND_%d",
                                lsat->band[i].code);
                        get_mtldata(mtldata, key, value);
                        lsat->band[i].qcalmax = atof(value);
                        sprintf(key, "QUANTIZE_CAL_MIN_BAND_%d",
                                lsat->band[i].code);
                        get_mtldata(mtldata, key, value);
                        lsat->band[i].qcalmin = atof(value);
                    }
                    /* other possible values of each band */
                    if (lsat->band[i].thermal) {
                        sprintf(key, "K1_CONSTANT_BAND_%d", lsat->band[i].code);
                        get_mtldata(mtldata, key, value);
                        if (value[0] != '\0')
                            lsat->band[i].K1 = atof(value);
                        sprintf(key, "K2_CONSTANT_BAND_%d", lsat->band[i].code);
                        get_mtldata(mtldata, key, value);
                        if (value[0] != '\0')
                            lsat->band[i].K2 = atof(value);
                    }
                    else if (lsat->number == 8) {
                        /* ESUN from  REFLECTANCE and RADIANCE ADD_BAND */
                        sprintf(key, "REFLECTANCE_MAXIMUM_BAND_%d",
                                lsat->band[i].code);
                        get_mtldata(mtldata, key, value);
                        if (value[0] != '\0')
                            lsat->band[i].esun =
                                (double)(PI * lsat->dist_es * lsat->dist_es *
                                         lsat->band[i].lmax) /
                                atof(value);
                    }
                }
                if (lsat->number == 8)
                    G_warning("ESUN evaluated from REFLECTANCE_MAXIMUM_BAND");
            }
            else {
                G_verbose_message(
                    "RADIANCE & QUANTIZE from RADIOMETRIC_RESCALING");
                for (i = 0; i < lsat->bands; i++) {
                    sprintf(key, "RADIANCE_MULT_BAND_%d", lsat->band[i].code);
                    get_mtldata(mtldata, key, value);
                    lsat->band[i].gain = atof(value);
                    sprintf(key, "RADIANCE_ADD_BAND_%d", lsat->band[i].code);
                    get_mtldata(mtldata, key, value);
                    lsat->band[i].bias = atof(value);
                    /* reversing to calculate the values of Lmin and Lmax ... */
                    lsat->band[i].lmin =
                        lsat->band[i].gain * lsat->band[i].qcalmin +
                        lsat->band[i].bias;
                    lsat->band[i].lmax =
                        lsat->band[i].gain * lsat->band[i].qcalmax +
                        lsat->band[i].bias;
                    /* ... qcalmax and qcalmin loaded in previous sensor_
                     * function */
                    if (lsat->number == 8) {
                        if (lsat->band[i].thermal) {
                            sprintf(key, "K1_CONSTANT_BAND_%d",
                                    lsat->band[i].code);
                            get_mtldata(mtldata, key, value);
                            lsat->band[i].K1 = atof(value);
                            sprintf(key, "K2_CONSTANT_BAND_%d",
                                    lsat->band[i].code);
                            get_mtldata(mtldata, key, value);
                            lsat->band[i].K2 = atof(value);
                        }
                        else {
                            sprintf(key, "REFLECTANCE_MULT_BAND_%d",
                                    lsat->band[i].code);
                            get_mtldata(mtldata, key, value);
                            lsat->band[i].K1 = atof(value);
                            sprintf(key, "REFLECTANCE_ADD_BAND_%d",
                                    lsat->band[i].code);
                            get_mtldata(mtldata, key, value);
                            lsat->band[i].K2 = atof(value);
                            /* ESUN from  REFLECTANCE_ADD_BAND */
                            lsat->band[i].esun =
                                (double)(PI * lsat->dist_es * lsat->dist_es *
                                         lsat->band[i].bias) /
                                lsat->band[i].K2;
                            /*
                               double esun1 = (double) (PI * lsat->dist_es *
                               lsat->dist_es * lsat->band[i].bias) /
                               lsat->band[i].K2; double esun2 = (double) (PI *
                               lsat->dist_es * lsat->dist_es *
                               lsat->band[i].gain) / lsat->band[i].K1;
                               lsat->band[i].esun = (esun1 + esun2) / 2.;
                             */
                        }
                    }
                }
                G_warning(
                    "ESUN evaluated from REFLECTANCE_ADDITIVE_FACTOR_BAND");
            }
        }
    }
    else {
        G_verbose_message("Metadata file is MET file");
        G_verbose_message(
            "RADIANCE & QUANTIZE from band setting of the metadata file");
        for (i = 0; i < lsat->bands; i++) {
            sprintf(key, "Band%dGainSetting", lsat->band[i].code);
            get_mtldata(mtldata, key, value);
            if (value[0] == '\0') {
                G_warning("%s", key);
                continue;
            }
            lsat->band[i].gain = atof(value);
            sprintf(key, "Band%dBiasSetting", lsat->band[i].code);
            get_mtldata(mtldata, key, value);
            if (value[0] == '\0') {
                G_warning("%s", key);
                continue;
            }
            lsat->band[i].bias = atof(value);

            lsat->band[i].qcalmax = 255.;
            lsat->band[i].qcalmin = 1.;
            lsat->band[i].lmin =
                lsat->band[i].gain * lsat->band[i].qcalmin + lsat->band[i].bias;
            lsat->band[i].lmax =
                lsat->band[i].gain * lsat->band[i].qcalmax + lsat->band[i].bias;
        }
    }

    return;
}
