#include <stdint.h>
#include <stdbool.h>
#include "const.h"
#include "utilities.h"
#include "input.h"

/*****************************************************************************
  NAME:  open_band

  PURPOSE:  Open the specified file and allocate the memory for the filename.

  RETURN VALUE:  None
*****************************************************************************/
static int open_band
(
    char *filename,          /* I: input filename */
    Input_Data_t *input,     /* I/O: updated with information from XML */
    Input_Bands_e band_index /* I: index to place the band into */
)
{
    const char *FUNC_NAME = "open_band";
    char msg[256];

    /* Grab the name from the input */
    input->band_name[band_index] = strdup(filename);

    /* Open a file descriptor for the band */
    input->band_fd[band_index] =
        fopen(input->band_name[band_index], "rb");

    if (input->band_fd[band_index] == NULL)
    {
        snprintf(msg, sizeof(msg), "Failed to open (%s)",
                 input->band_name[band_index]);
        RETURN_ERROR(msg, FUNC_NAME, FAILURE);
    }

    return SUCCESS;
}


#define INVALID_INSTRUMENT_COMBO ("invalid instrument/satellite combination")


/*****************************************************************************
Description: 'GetXMLInput' pulls input values from the XML structure.

Input Parameters:
    input        'Input_t' data structure to be populated
    metadata     'Espa_internal_meta_t' data structure with XML info

Output Parameters:
    (returns)      status:
                  'true' = okay (always returned)
                  'false' = error getting metadata from the XML file

Design Notes:
    1. This replaces the previous GetInputMeta so the input values are pulled
       from the XML file instead of the HDF and MTL files.
*****************************************************************************/
static bool GetXMLInput
(
    Input_Data_t *input,
    Espa_internal_meta_t *metadata,
    bool modtran_flag            /* I: 0=RTTOV, 1=MODTRAN */
)
{
    const char *FUNC_NAME = "GetXMLInput";
    char msg[MAX_STR_LEN];
    int index;
    Espa_global_meta_t *global = &metadata->global; /* pointer to global meta */

    /* Initialize the input fields.  Set file type to binary, since that is
       the ESPA internal format for the input L1G/T products. */
    input->meta.satellite = SAT_NULL;
    input->meta.instrument = INST_NULL;
    input->thermal_rad_gain = GAIN_BIAS_FILL;
    input->thermal_rad_bias = GAIN_BIAS_FILL;

    /* Determine satellite */
    if (strcmp (global->satellite, "LANDSAT_4") == 0)
    {
        input->meta.satellite = SAT_LANDSAT_4;
    }
    else if (strcmp (global->satellite, "LANDSAT_5") == 0)
    {
        input->meta.satellite = SAT_LANDSAT_5;
    }
    else if (strcmp (global->satellite, "LANDSAT_7") == 0)
    {
        input->meta.satellite = SAT_LANDSAT_7;
    }
    else if (strcmp (global->satellite, "LANDSAT_8") == 0)
    {
        input->meta.satellite = SAT_LANDSAT_8;
    }
    else if (strcmp (global->satellite, "LANDSAT_9") == 0)
    {
        input->meta.satellite = SAT_LANDSAT_9;
    }
    else
    {
        snprintf (msg, sizeof (msg),
                  "(Satellite not supported with ST processing)");
        RETURN_ERROR (msg, FUNC_NAME, true);
    }

    /* Determine sensor */
    if (!strcmp (global->instrument, "TM"))
    {
        input->meta.instrument = INST_TM;
    }
    else if (!strncmp (global->instrument, "ETM", 3))
    {
        input->meta.instrument = INST_ETM;
    }
    else if (!strncmp (global->instrument, "OLI_TIRS", 8))
    {
        input->meta.instrument = INST_OLI_TIRS;
    }
    else if (!strncmp (global->instrument, "TIRS", 8))
    {
        input->meta.instrument = INST_TIRS;
    }
    else
    {
        snprintf (msg, sizeof (msg),
                  "(Sensor not supported with ST processing)");
        RETURN_ERROR (msg, FUNC_NAME, false);
    }

    /* Check satellite/instrument combination */
    if (input->meta.instrument == INST_TM)
    {
        if (input->meta.satellite != SAT_LANDSAT_4 &&
            input->meta.satellite != SAT_LANDSAT_5)
        {
            RETURN_ERROR (INVALID_INSTRUMENT_COMBO, FUNC_NAME, false);
        }

        /* Specify the band name for the thermal band to use */
        snprintf (input->reference_band_name,
                  sizeof (input->reference_band_name), "b6");
    }
    else if (input->meta.instrument == INST_ETM)
    {
        if (input->meta.satellite != SAT_LANDSAT_7)
        {
            RETURN_ERROR (INVALID_INSTRUMENT_COMBO, FUNC_NAME, false);
        }

        /* Specify the band name for the thermal band to use */
        snprintf (input->reference_band_name,
                  sizeof (input->reference_band_name), "b61");
        snprintf (input->reference_band_name_2,
                  sizeof (input->reference_band_name_2), "b62");
    }
    else if ((input->meta.instrument == INST_OLI_TIRS)
             || (input->meta.instrument == INST_TIRS))
    {
        if (input->meta.satellite != SAT_LANDSAT_8 &&
            input->meta.satellite != SAT_LANDSAT_9)
        {
            RETURN_ERROR (INVALID_INSTRUMENT_COMBO, FUNC_NAME, false);
        }

        /* Specify the band name for the thermal band to use */
        snprintf (input->reference_band_name,
                  sizeof (input->reference_band_name), "b10");
    }

    for (index = 0; index < metadata->nbands; index++)
    {
        /* Only look at the ones with the product name we are looking for */
        if (strcmp (metadata->band[index].product, "L1T") == 0
            || strcmp (metadata->band[index].product, "L1G") == 0
            || strcmp (metadata->band[index].product, "L1TP") == 0
            || strcmp (metadata->band[index].product, "L1GT") == 0
            || strcmp (metadata->band[index].product, "L1GS") == 0)
        {
            if (strcmp (metadata->band[index].name,
                        input->reference_band_name) == 0)
            {
                if (open_band(metadata->band[index].file_name,
                              input, I_BAND_THERMAL) != SUCCESS)
                {
                    RETURN_ERROR("Error opening thermal", FUNC_NAME, false);
                }

                /* Always use this one for the lines and samples since
                   they will be the same for us, along with the pixel
                   size values */
                input->lines = metadata->band[index].nlines;
                input->samples = metadata->band[index].nsamps;
                input->x_pixel_size = metadata->band[index].pixel_size[0];
                input->y_pixel_size = metadata->band[index].pixel_size[1];

                input->thermal_rad_gain = metadata->band[index].rad_gain;
                input->thermal_rad_bias = metadata->band[index].rad_bias;

                /* Grab the fill value for this band */
                input->fill_value[I_BAND_THERMAL] =
                    metadata->band[index].fill_value;
            }

            /* If ETM, process the second band */
            if (input->meta.instrument == INST_ETM &&
                input->meta.satellite == SAT_LANDSAT_7)
            {
                if (strcmp (metadata->band[index].name,
                            input->reference_band_name_2) == 0)
                {
                    if (open_band(metadata->band[index].file_name,
                                  input, I_BAND_THERMAL_2) != SUCCESS)
                    {
                        RETURN_ERROR("Error opening thermal_2", FUNC_NAME,
                                     false);
                    }

                    /* Use the reference band for some values, but not
                       gain/bias */
                    input->thermal_rad_gain_2 = metadata->band[index].rad_gain;
                    input->thermal_rad_bias_2 = metadata->band[index].rad_bias;

                    /* Grab the fill value for this band */
                    input->fill_value[I_BAND_THERMAL_2] =
                        metadata->band[index].fill_value;
                }
            }
        }

        if (modtran_flag)
        {
            /* Only look at the ones with the product name we are looking for */
            if (strcmp (metadata->band[index].product, "elevation") == 0)
            {
                if (strcmp (metadata->band[index].name, "elevation") == 0)
                {
                    if (open_band(metadata->band[index].file_name,
                                  input, I_BAND_ELEVATION) != SUCCESS)
                    {
                        RETURN_ERROR("Error opening elevation", FUNC_NAME,
                            false);
                    }

                    /* Grab the fill value for this band */
                    input->fill_value[I_BAND_ELEVATION] =
                        metadata->band[index].fill_value;
                }
            }
        }
    }

    /* Get the product ID */
    input->meta.product_id = strdup(metadata->global.product_id);

    /* Get the map projection coordinates */
    input->meta.ul_map_corner.x = metadata->global.proj_info.ul_corner[0];
    input->meta.ul_map_corner.y = metadata->global.proj_info.ul_corner[1];
    input->meta.ul_map_corner.is_fill = true;
    input->meta.lr_map_corner.x = metadata->global.proj_info.lr_corner[0];
    input->meta.lr_map_corner.y = metadata->global.proj_info.lr_corner[1];
    input->meta.lr_map_corner.is_fill = true;

    return true;
}


/*****************************************************************************
Description: 'open_input' sets up the 'input' data structure, opens the
             input raw binary files for read access.

Input Parameters:
    metadata     'Espa_internal_meta_t' data structure with XML info

Output Parameters:
    (returns)      'input' data structure or NULL when an error occurs

*****************************************************************************/
Input_Data_t *open_input
(
    Espa_internal_meta_t *metadata,
    bool modtran_flag            /* I: 0=RTTOV, 1=MODTRAN */
)
{
    const char *FUNC_NAME = "open_input";
    Input_Data_t *input = NULL;
    int index;

    /* Create the Input data structure */
    input = calloc(1, sizeof(Input_Data_t));
    if (input == NULL)
    {
        RETURN_ERROR("allocating Input data structure", FUNC_NAME, NULL);
    }

    /* Initialize the band fields */
    for (index = 0; index < MAX_INPUT_BANDS; index++)
    {
        input->band_name[index] = NULL;
        input->band_fd[index] = NULL;
    }

    input->lines = 0;
    input->samples = 0;

    /* Open the input images from the XML file */
    if (!GetXMLInput(input, metadata, modtran_flag))
    {
        free(input);
        input = NULL;
        RETURN_ERROR("getting input from header file", FUNC_NAME, NULL);
    }

    return input;
}


/*****************************************************************************
  NAME:  close_input

  PURPOSE:  Close all the input files and free associated memory that resides
            in the data structure.

  RETURN VALUE:  Type = int
      Value    Description
      -------  ---------------------------------------------------------------
      SUCCESS  No errors were encountered.
      ERROR    An error was encountered.
*****************************************************************************/
int close_input 
(
    Input_Data_t *input,
    bool modtran_flag
)
{
    int index;

    for (index = 0; index < MAX_INPUT_BANDS; index++)
    {
        /* Elevation isn't opened in this phase for RTTOV so don't close it. */
        if ((!modtran_flag) && (index == I_BAND_ELEVATION))
        {
            continue;
        }
        if (input->band_fd[index])
        {
            fclose (input->band_fd[index]);
            free (input->band_name[index]);
        }
    }

    free(input->meta.product_id);
    free(input);

    return SUCCESS;
}


/*****************************************************************************
  NAME: read_input

  PURPOSE: To read the specified input bands into memory for later processing.

  RETURN VALUE:  Type = bool
      Value    Description
      -------  ---------------------------------------------------------------
      true     Success with reading all of the bands into memory.
      false    Failed to read a band into memory.
*****************************************************************************/
int read_input
(
    const Input_Data_t *input,
    float *band_thermal,
    int16_t *band_elevation,
    int pixel_count,
    bool modtran_flag            /* I: 0=RTTOV, 1=MODTRAN */
)
{
    const char *FUNC_NAME = "read_input";
    int count;
    int index;
    /* ETM B6L thermal and TM thermal */
    uint8_t *thermal_uint8 = NULL;
    /* ETM B6H thermal */
    uint8_t *thermal_uint8_2 = NULL;
    /* OLITIRS thermal */
    uint16_t *thermal_uint16 = NULL;

    if ((input->meta.instrument == INST_OLI_TIRS
        || input->meta.instrument == INST_TIRS)
        && (input->meta.satellite == SAT_LANDSAT_8
            || input->meta.satellite == SAT_LANDSAT_9))
    {
        thermal_uint16 = malloc(sizeof(uint16_t) * pixel_count);
        if (thermal_uint16 == NULL)
        {
            RETURN_ERROR("error allocating thermal memory",
                         FUNC_NAME, FAILURE);
        }

        count = fread(thermal_uint16, sizeof(uint16_t), pixel_count,
                      input->band_fd[I_BAND_THERMAL]);
        if (count != pixel_count)
        {
            free(thermal_uint16);

            RETURN_ERROR("Failed reading thermal band data",
                         FUNC_NAME, FAILURE);
        }

        /* Copy the data to the output buffer manually while converting to
           radiance and float */
        for (index = 0; index < pixel_count; index++)
        {
            if (thermal_uint16[index] == input->fill_value[I_BAND_THERMAL])
            {
                band_thermal[index] = ST_NO_DATA_VALUE;
            }
            else
            {
                band_thermal[index] =
                    (float)((input->thermal_rad_gain * thermal_uint16[index])
                            + input->thermal_rad_bias);
            }
        }

        free(thermal_uint16);
    }
    else
    {
        thermal_uint8 = malloc(sizeof(uint8_t) * pixel_count);
        if (thermal_uint8 == NULL)
        {
            RETURN_ERROR("error allocating thermal memory",
                         FUNC_NAME, FAILURE);
        }

        count = fread(thermal_uint8, sizeof(uint8_t), pixel_count,
                      input->band_fd[I_BAND_THERMAL]);
        if (count != pixel_count)
        {
            free(thermal_uint8);

            RETURN_ERROR("Failed reading thermal band data",
                         FUNC_NAME, FAILURE);
        }

        /* If ETM+, read b6h and merge with b6l*/
        if (input->meta.instrument == INST_ETM
            && input->meta.satellite == SAT_LANDSAT_7)
        {
            thermal_uint8_2 = malloc(sizeof(uint8_t) * pixel_count);

            if (thermal_uint8_2 == NULL)
            {
                free(thermal_uint8);

                RETURN_ERROR("error allocating thermal_2 memory",
                             FUNC_NAME, FAILURE);
            }

            count = fread(thermal_uint8_2, sizeof(uint8_t), pixel_count,
                          input->band_fd[I_BAND_THERMAL_2]);
            if (count != pixel_count)
            {
                free(thermal_uint8);
                free(thermal_uint8_2);

                RETURN_ERROR("Failed reading thermal_2 band data",
                             FUNC_NAME, FAILURE);
            }

            /* Merge the b6h data with the b6l data, while converting to
               radiance and float */
            for (index = 0; index < pixel_count; index++)
            {
                /* If b6l or b6h is fill, set the merged band to fill */
                if ((thermal_uint8[index] == input->fill_value[I_BAND_THERMAL])
                    || (thermal_uint8_2[index] ==
                        input->fill_value[I_BAND_THERMAL_2]))
                {
                    band_thermal[index] = ST_NO_DATA_VALUE;
                }
                /* At locations that are saturated in the high band,
                   use the low band converted to radiance and float */
                else if ((thermal_uint8_2[index] == LOW_SATURATION) ||
                         (thermal_uint8_2[index] == HIGH_SATURATION))
                {
                    band_thermal[index] = (float)((input->thermal_rad_gain
                                * thermal_uint8[index])
                                + input->thermal_rad_bias);
                }
                /* At locations that are not saturated in the high band,
                   use the high band converted to radiance and float */
                else
                {
                    band_thermal[index] = (float)((input->thermal_rad_gain_2
                                * thermal_uint8_2[index])
                                + input->thermal_rad_bias_2);
                }
            }
            free(thermal_uint8_2);
        }
        else
        {
            /* Copy the data to the output buffer manually while converting to
               radiance and float */
            for (index = 0; index < pixel_count; index++)
            {
                if (thermal_uint8[index] == input->fill_value[I_BAND_THERMAL])
                {
                    band_thermal[index] = ST_NO_DATA_VALUE;
                }
                else
                {
                    band_thermal[index] =
                        (float)((input->thermal_rad_gain * thermal_uint8[index])
                                + input->thermal_rad_bias);
                }
            }
        }
        free(thermal_uint8);
    }

    if (modtran_flag)
    {
        count = fread(band_elevation, sizeof(int16_t), pixel_count,
                      input->band_fd[I_BAND_ELEVATION]);
        if (count != pixel_count)
        {
            RETURN_ERROR("Failed reading elevation band data",
                         FUNC_NAME, FAILURE);
        }
    }

    return SUCCESS;
}


/*****************************************************************************
  NAME: read_unc_input_float

  PURPOSE: Read the specified float input band into memory.

  RETURN VALUE:  Pointer to allocated buffer (NULL on failure)
*****************************************************************************/
float *read_unc_input_float
(
    char *input_filename,
    int pixel_count
)
{
    const char *FUNC_NAME = "read_unc_input_float";
    char message[MAX_STR_LEN];
    int count;
    float *buffer = NULL;
    FILE *fp = NULL;

    if ((fp = fopen(input_filename, "rb")) == NULL)
    {
        sprintf(message, "Failed to open %s.", input_filename);
        RETURN_ERROR(message, FUNC_NAME, NULL);
    }

    buffer = malloc(pixel_count*sizeof(float));
    if (buffer == NULL)
    {
        fclose(fp);
        sprintf(message, "Failed to allocate buffer for %s.", input_filename);
        RETURN_ERROR(message, FUNC_NAME, NULL);
    }

    count = fread(buffer, sizeof(float), pixel_count, fp);
    if (count != pixel_count)
    {
        free(buffer);
        fclose(fp);
        sprintf(message, "Failed to read %s.", input_filename);
        RETURN_ERROR(message, FUNC_NAME, NULL);
    }

    fclose(fp);

    return buffer;
}


/*****************************************************************************
  NAME: read_unc_input_uint8

  PURPOSE: Read the specified uint8 input band into memory.

  RETURN VALUE:  Pointer to allocated buffer (NULL on failure)
*****************************************************************************/
uint8_t *read_unc_input_uint8
(
    char *input_filename,
    int pixel_count
)
{
    const char *FUNC_NAME = "read_unc_input_uint8";
    char message[MAX_STR_LEN];
    int count;
    uint8_t *buffer = NULL;
    FILE *fp = NULL;

    if ((fp = fopen(input_filename, "rb")) == NULL)
    {
        sprintf(message, "Failed to open %s.", input_filename);
        RETURN_ERROR(message, FUNC_NAME, NULL);
    }

    buffer = malloc(pixel_count*sizeof(uint8_t));
    if (buffer == NULL)
    {
        fclose(fp);
        sprintf(message, "Failed to allocate buffer for %s.", input_filename);
        RETURN_ERROR(message, FUNC_NAME, NULL);
    }

    count = fread(buffer, sizeof(uint8_t), pixel_count, fp);
    if (count != pixel_count)
    {
        free(buffer);
        fclose(fp);
        sprintf(message, "Failed to read %s.", input_filename);
        RETURN_ERROR(message, FUNC_NAME, NULL);
    }

    fclose(fp);

    return buffer;
}


/*****************************************************************************
  NAME: read_unc_input_uint16

  PURPOSE: Read the specified uint16 input band into memory.

  RETURN VALUE:  Pointer to allocated buffer (NULL on failure)
*****************************************************************************/
uint16_t *read_unc_input_uint16
(
    char *input_filename,
    int pixel_count
)
{
    const char *FUNC_NAME = "read_unc_input_uint16";
    char message[MAX_STR_LEN];
    int count;
    uint16_t *buffer = NULL;
    FILE *fp = NULL;

    if ((fp = fopen(input_filename, "rb")) == NULL)
    {
        sprintf(message, "Failed to open %s.", input_filename);
        RETURN_ERROR(message, FUNC_NAME, NULL);
    }

    buffer = malloc(pixel_count*sizeof(uint16_t));
    if (buffer == NULL)
    {
        fclose(fp);
        sprintf(message, "Failed to allocate buffer for %s.", input_filename);
        RETURN_ERROR(message, FUNC_NAME, NULL);
    }

    count = fread(buffer, sizeof(uint16_t), pixel_count, fp);
    if (count != pixel_count)
    {
        free(buffer);
        fclose(fp);
        sprintf(message, "Failed to read %s.", input_filename);
        RETURN_ERROR(message, FUNC_NAME, NULL);
    }

    fclose(fp);

    return buffer;
}


