/*****************************************************************************
FILE: convert_espa_to_hdf5.c

PURPOSE: Contains functions for creating a HDF5 products for
the bands in the XML file.

NOTES:
  1. The IAS L1G I/O library is used to write the HDF5 file.

  2. The XML metadata format written via this library follows the ESPA internal
     metadata format found in ESPA Raw Binary Format v1.0.doc.  The schema for
     the ESPA internal metadata format is available at
     http://espa.cr.usgs.gov/schema/espa_internal_metadata_v1_0.xsd.
*****************************************************************************/
#include <stdio.h>
#include <unistd.h>
#include <stdbool.h>
#include "ias_l1g.h"
#include "ias_miscellaneous.h"
#include "espa_metadata.h"
#include "parse_metadata.h"
#include "write_metadata.h"
#include "raw_binary_io.h"
#include "espa_util.h"
#include "convert_espa_to_hdf5.h"
#include "setup_ias_l1g_metadata.h"

#define BAND_SKIPPED 2

/******************************************************************************
MODULE:  create_file_using_library

PURPOSE: Creates the overall HDF5 file.

RETURNS: L1GIO file pointer
         NULL on error
******************************************************************************/
static L1GIO *create_file_using_library
(
    const Espa_internal_meta_t *xml_metadata, /* I: Source XML metadata */
    const char *hdf5_filename                 /* I: Output HDF5 filename */
)
{
    char errmsg[STR_SIZE];      /* error message */
    char FUNC_NAME[] = "create_file_using_library";
    L1GIO *l1g_image;
    IAS_L1G_FILE_METADATA fmd;

    /* Set up file metadata using XML information */
    if (setup_file_metadata(xml_metadata, &fmd) != SUCCESS)
    {
        snprintf(errmsg, sizeof(errmsg), "Error setting up file metadata");
        error_handler(true, FUNC_NAME, errmsg);
        return NULL;
    }

    /* Create the HDF5 output file */
    l1g_image = ias_l1g_open_image(hdf5_filename, IAS_WRITE);
    if (l1g_image == NULL)
    {
        snprintf(errmsg, sizeof(errmsg), "Error opening output HDF5 file: %s",
                 hdf5_filename);
        error_handler(true, FUNC_NAME, errmsg);
        return NULL;
    }

    /* Set the file metadata */
    if (ias_l1g_set_file_metadata(l1g_image, &fmd) != SUCCESS)
    {
        snprintf(errmsg, sizeof(errmsg), 
                 "Error setting file metadata in HDF5 file: %s", hdf5_filename);
        error_handler(true, FUNC_NAME, errmsg);
        ias_l1g_close_image(l1g_image);
        return NULL;
    }

    return l1g_image;
}

/******************************************************************************
MODULE:  convert_band_using_library

PURPOSE: Converts the internal ESPA raw binary file to HDF5 file format
    using the IAS L1G I/O library HDF5 support.  The band is added to the
    HDF5 file which has already been opened.

RETURNS: SUCCESS/ERROR/BAND_SKIPPED
******************************************************************************/
static int convert_band_using_library
(
    const Espa_internal_meta_t *xml_metadata, /* I: Source XML metadata */
    char *espa_filename,                      /* I: Input ESPA filename */
    L1GIO *l1g_image,                         /* I: HDF5 L1G image pointer */
    const char *hdf5_filename,                /* I: Output HDF5 filename */
    bool all_bands,                           /* I: Flag to convert all bands
                                                 or SR/ST bands only */
    int band_index                            /* I: Index into xml_metadata of
                                                 band to convert */
)
{
    char errmsg[STR_SIZE];      /* error message */
    char FUNC_NAME[] = "convert_band_using_library";
    int nscas = 1;
    int nlines;
    int nsamps;
    int nbytes;
    int bn;
    IAS_DATA_TYPE ias_data_type;
    L1G_BAND_IO *l1g_band;
    IAS_L1G_BAND_METADATA bmd;
    FILE *fp_rb = NULL;         /* File pointer for raw binary file */
    unsigned char *image_buffer = NULL;

    memset(&bmd, 0, sizeof(bmd));

    /* Determine if this band should be converted */
    snprintf(bmd.band_name, sizeof(bmd.band_name),
             xml_metadata->band[band_index].name);
    if (all_bands)
    {
        bmd.band_number = band_index + 1;
    }
    else
    {
        /* Set the band number specially for surface reflectance and surface
         * temperature bands.  Look for either ESPA internal band names or
         * final level 2 product band names. Since this is converting to
         * HDF5, we assume OLI-TIRS is the sensor. */
        bmd.band_number = -1;
        if (sscanf(bmd.band_name, "sr_band%d", &bn) == 1)
            bmd.band_number = bn;
        else if (sscanf(bmd.band_name, "SR_B%d", &bn) == 1)
            bmd.band_number = bn;
        else if (sscanf(bmd.band_name, "surface_temperature_band%d", &bn) == 1)
            bmd.band_number = bn;
        else if (sscanf(bmd.band_name, "ST_B%d", &bn) == 1)
            bmd.band_number = bn;
        else if (strcmp(bmd.band_name, "surface_temperature") == 0)
            bmd.band_number = 10; /* Assume OLI-TIRS Thermal band 10 */
        else if (strcmp(bmd.band_name, "ST_SW") == 0)
            bmd.band_number = 10; /* Assume OLI-TIRS Thermal band 10 */
        if (bmd.band_number == -1)
        {
            printf("Skipping band %s\n", bmd.band_name);
            return BAND_SKIPPED;
        }
    }
    printf ("Adding band %d %s to %s\n", bmd.band_number, bmd.band_name,
        hdf5_filename);

    /* Read raw binary file */
    nlines = xml_metadata->band[band_index].nlines;
    nsamps = xml_metadata->band[band_index].nsamps;

    fp_rb = open_raw_binary(espa_filename, "rb");
    if ( fp_rb == NULL)
    {
        snprintf(errmsg, sizeof(errmsg), 
                 "Error opening input raw binary file: %s", espa_filename);
        error_handler(true, FUNC_NAME, errmsg);
        return ERROR;
    }

    /* Determine the IAS data type */
    ias_data_type = convert_to_ias_data_type(
        xml_metadata->band[band_index].data_type);
    if (ias_data_type == type_error)
    {
        snprintf(errmsg, sizeof(errmsg), "Unsupported ESPA data type: %d",
                 xml_metadata->band[band_index].data_type);
        error_handler(true, FUNC_NAME, errmsg);
        close_raw_binary(fp_rb);
        return ERROR;
    }

    /* Get the size of the selected data type */
    if (ias_misc_get_sizeof_data_type(ias_data_type, &nbytes) != SUCCESS)
    {
        snprintf(errmsg, sizeof(errmsg), 
                 "Error getting size of IAS data type %d", ias_data_type);
        error_handler(true, FUNC_NAME, errmsg);
        close_raw_binary(fp_rb);
        return ERROR;
    }

    /* Set up band metadata */
    if (setup_band_metadata(xml_metadata, band_index, &bmd) != SUCCESS)
    {
        snprintf(errmsg, sizeof(errmsg), "Error setting up band metadata");
        error_handler(true, FUNC_NAME, errmsg);
        close_raw_binary(fp_rb);
        return ERROR;
    }

    /* Set the band metadata */
    if (ias_l1g_set_band_metadata(l1g_image, &bmd, 1) != SUCCESS)
    {
        snprintf(errmsg, sizeof(errmsg), 
                 "Error setting band metadata in HDF file: %s", hdf5_filename);
        error_handler(true, FUNC_NAME, errmsg);
        close_raw_binary(fp_rb);
        return ERROR;
    }

    /* Open the output band */
    l1g_band = ias_l1g_open_band(l1g_image, bmd.band_number, &ias_data_type,
        &nscas, &nlines, &nsamps);
    if (l1g_band == NULL)
    {
        snprintf(errmsg, sizeof(errmsg), 
                 "Error opening output band in HDF5 file: %s", hdf5_filename);
        error_handler(true, FUNC_NAME, errmsg);
        close_raw_binary(fp_rb);
        return ERROR;
    }

    /* Allocate memory for the imagery */
    image_buffer = malloc(nlines * nsamps * nbytes);
    if (image_buffer == NULL)
    {
        snprintf(errmsg, sizeof(errmsg), 
                 "Error allocating image buffer for file: %s", hdf5_filename);
        error_handler(true, FUNC_NAME, errmsg);
        ias_l1g_close_band(l1g_band);
        close_raw_binary(fp_rb);
        return ERROR;
    }

    /* Read the source imagery */
    if (read_raw_binary(fp_rb, nlines, nsamps, nbytes, image_buffer)
            != SUCCESS)
    {
        snprintf(errmsg, sizeof(errmsg), 
                 "Error reading image data from raw binary file");
        error_handler(true, FUNC_NAME, errmsg);
        free(image_buffer);
        ias_l1g_close_band(l1g_band);
        close_raw_binary(fp_rb);
        return ERROR;
    }

    /* Close the input file */
    close_raw_binary(fp_rb);
    fp_rb = NULL;

    if (ias_l1g_write_image(l1g_band, 0, 0, 0, nlines, nsamps, image_buffer)
            != SUCCESS)
    {
        snprintf(errmsg, sizeof(errmsg), 
                 "Error writing image data to HDF5 file: %s", hdf5_filename);
        error_handler(true, FUNC_NAME, errmsg);
        free(image_buffer);
        ias_l1g_close_band(l1g_band);
        return ERROR;
    }

    free(image_buffer);
    image_buffer = NULL;

    /* Close the band */
    if (ias_l1g_close_band(l1g_band) != SUCCESS)
    {
        snprintf(errmsg, sizeof(errmsg), 
                "Error closing band in HDF5 file: %s", hdf5_filename);
        error_handler(true, FUNC_NAME, errmsg);
        return ERROR;
    }


    return SUCCESS;
}

/******************************************************************************
MODULE:  convert_espa_to_hdf5

PURPOSE: Converts the internal ESPA raw binary file to HDF5 file format.

RETURN VALUE:
Type = int
Value           Description
-----           -----------
ERROR           Error converting to HDF5
SUCCESS         Successfully converted to HDF5

NOTES:
  1. The IAS library will be used to write the output HDF5 file.
     The IAS library only supports WGS84 datums, UTM/PS/Albers/Geographic
     projections, and CENTER corner grid origin.  Any other specificiation
     will cause an error.
******************************************************************************/
int convert_espa_to_hdf5
(
    const char *espa_xml_file,   /* I: input ESPA XML metadata filename */
    const char *hdf5_file,       /* I: output HDF5 filename */
    bool del_src,          /* I: should the source files be removed after
                                 conversion? */
    bool all_bands         /* I: convert all bands, or SR/ST only ? */
)
{
    char FUNC_NAME[] = "convert_espa_to_hdf5";  /* function name */
    char errmsg[STR_SIZE];      /* error message */
    char espa_band[PATH_MAX];   /* name of the input raw binary for this band*/
    char source_dir[PATH_MAX] = "."; /* directory location of source bands */
    char xml_file[PATH_MAX];    /* new XML file for the HDF5 product */
    char *cptr = NULL;          /* pointer to empty space in the band name */
    int i,k;                    /* looping variables */
    int status;                 /* band conversion status */
    int count;                  /* number of chars copied in snprintf */
    Espa_internal_meta_t xml_metadata;  /* XML metadata structure to be
                                   populated by reading the XML metadata file */
    Espa_internal_meta_t output_xml_metadata;  /* XML metadata structure
                                   for converted bands */
    L1GIO *l1g_image = NULL;    /* HDF5 L1G image pointer */

    /* Validate the input metadata file */
    if (validate_xml_file (espa_xml_file) != SUCCESS)
    {  /* Error messages already written */
        return ERROR;
    }

    /* Initialize the metadata structure */
    init_metadata_struct (&xml_metadata);
    init_metadata_struct (&output_xml_metadata);

    /* Parse the metadata file into our internal metadata structure; also
       allocates space as needed for various pointers in the global and band
       metadata */
    if (parse_metadata (espa_xml_file, &xml_metadata) != SUCCESS)
    {  /* Error messages already written */
        return ERROR;
    }
    output_xml_metadata.global = xml_metadata.global;
    if (allocate_band_metadata (&output_xml_metadata, xml_metadata.nbands)
        != SUCCESS)
    {
        free_metadata (&xml_metadata);
        return ERROR;
    }
    output_xml_metadata.nbands = 0;

    /* Determine if the files are being read from a location other than cwd */
    if (strchr(espa_xml_file, '/') != NULL)
    {
        count = snprintf (source_dir, sizeof(source_dir), "%s", espa_xml_file);
        if (count < 0 || count >= sizeof (source_dir))
        {
            snprintf (errmsg, sizeof(errmsg), "Overflow of source_dir string");
            error_handler (true, FUNC_NAME, errmsg);
            free_metadata (&xml_metadata);
            free_metadata (&output_xml_metadata);
            return ERROR;
        }
        cptr = strrchr(source_dir, '/');
        if (cptr != NULL) *cptr = '\0';
    }

    /* Create the HDF5 file.  Bands will later be added to it. */
    l1g_image = create_file_using_library (&xml_metadata, hdf5_file);
    if (l1g_image == NULL)
    {
        snprintf (errmsg, sizeof(errmsg), "Error creating HDF5 file %s", 
                  hdf5_file);
        error_handler (true, FUNC_NAME, errmsg);
        free_metadata (&xml_metadata);
        free_metadata (&output_xml_metadata);
        return ERROR;
    }

    /* Loop through the bands in the XML file and convert them to HDF5.
       The dataset names will be the set to the band name from the XML file. */
    for (i = 0; i < xml_metadata.nbands; i++)
    {
        /* Determine the input band name and location */
        count = snprintf (espa_band, sizeof(espa_band), "%s/%s",
                source_dir, xml_metadata.band[i].file_name);
        if (count < 0 || count >= sizeof (espa_band))
        {
            snprintf (errmsg, sizeof(errmsg), "Overflow of espa_band string");
            error_handler (true, FUNC_NAME, errmsg);
            ias_l1g_close_image(l1g_image);
            free_metadata (&xml_metadata);
            free_metadata (&output_xml_metadata);
            return ERROR;
        }

        /* Use the IAS library L1G IO library to convert the band */
        status = convert_band_using_library(&xml_metadata, espa_band, l1g_image,
            hdf5_file, all_bands, i);
        if (status == ERROR)
        {
            snprintf(errmsg, sizeof(errmsg), 
                     "Adding espa source file %s to HDF5 %s",
                     espa_band, hdf5_file);
            error_handler (true, FUNC_NAME, errmsg);
            ias_l1g_close_image(l1g_image);
            free_metadata (&xml_metadata);
            free_metadata (&output_xml_metadata);
            return ERROR;
        }
        else if (status == SUCCESS)
        {
            /* Copy the input metadata to the output */
            int n = output_xml_metadata.nbands;
            memcpy(&output_xml_metadata.band[n],
                &xml_metadata.band[i], sizeof(output_xml_metadata.band[n]));
            if (output_xml_metadata.band[n].nbits != 0)
            {
                if (allocate_bitmap_metadata (&output_xml_metadata.band[n],
                        output_xml_metadata.band[n].nbits) != SUCCESS)
                {
                    snprintf(errmsg, sizeof(errmsg), 
                        "Error allocating bitmap metadata for band %s", 
                        xml_metadata.band[i].name);
                    error_handler(true, FUNC_NAME, errmsg);
                    ias_l1g_close_image(l1g_image);
                    free_metadata (&xml_metadata);
                    free_metadata (&output_xml_metadata);
                    return ERROR;
                }
                for (k = 0; k < output_xml_metadata.band[n].nbits; k++)
                {
                    snprintf(output_xml_metadata.band[n].bitmap_description[k],
                      STR_SIZE, xml_metadata.band[i].bitmap_description[k]);
                }
            }
            if (output_xml_metadata.band[n].nclass != 0)
            {
                if (allocate_class_metadata (&output_xml_metadata.band[n],
                        output_xml_metadata.band[n].nclass) != SUCCESS)
                {
                    snprintf(errmsg, sizeof(errmsg), 
                        "Error allocating bitmap metadata for band %s", 
                        xml_metadata.band[i].name);
                    error_handler(true, FUNC_NAME, errmsg);
                    ias_l1g_close_image(l1g_image);
                    free_metadata (&xml_metadata);
                    free_metadata (&output_xml_metadata);
                    return ERROR;
                }
                for (k = 0; k < output_xml_metadata.band[n].nclass; k++)
                {
                    output_xml_metadata.band[n].class_values[k].class =
                        xml_metadata.band[i].class_values[k].class;
                    snprintf(output_xml_metadata.band[n].class_values[k].
                        description,
                        sizeof(output_xml_metadata.band[n].class_values[k].
                        description),
                        xml_metadata.band[i].class_values[k].description);
                }
            }

            /* Update the XML file to use the new HDF5 filename */
            strcpy(output_xml_metadata.band[output_xml_metadata.nbands].
                file_name, hdf5_file);

            output_xml_metadata.nbands++;
        }
    }

    /* Close the file */
    if (ias_l1g_close_image(l1g_image) != SUCCESS)
    {
        snprintf(errmsg, sizeof(errmsg), 
                 "Error closing HDF5 file: %s", hdf5_file);
        error_handler(true, FUNC_NAME, errmsg);
        free_metadata (&xml_metadata);
        free_metadata (&output_xml_metadata);
        return ERROR;
    }

    /* Create the XML file for the HDF5 product */
    count = snprintf (xml_file, sizeof (xml_file), "%s", hdf5_file);
    if (count < 0 || count >= sizeof (xml_file))
    {
        snprintf (errmsg, sizeof(errmsg), "Overflow of xml_file string");
        error_handler (true, FUNC_NAME, errmsg);
        free_metadata (&xml_metadata);
        free_metadata (&output_xml_metadata);
        return ERROR;
    }

    cptr = strrchr (xml_file, '.');
    if (cptr != NULL)
    {
        /* File extension found.  Replace it with the new extension */
        *cptr = '\0';
        strcpy (cptr, "_hdf5.xml");
    }
    else
    {
        /* No file extension found.  Just append the new extension */
        strcat (xml_file, "_hdf5.xml");
    }

    /* Write the new XML file containing the HDF5 band names */
    if (write_metadata (&output_xml_metadata, xml_file) != SUCCESS)
    {
        snprintf (errmsg, sizeof(errmsg), 
                  "Error writing updated XML for the HDF5 product: %s",
                  xml_file);
        error_handler (true, FUNC_NAME, errmsg);
        free_metadata (&xml_metadata);
        free_metadata (&output_xml_metadata);
        return ERROR;
    }

    if (del_src)
    {
        /* Once all bands are converted, delete the source files */
        if (delete_raw_binary(&xml_metadata, source_dir, espa_xml_file) 
            != SUCCESS)
        {
            snprintf (errmsg, sizeof(errmsg), 
                      "Error deleting source raw binary files");
            error_handler (true, FUNC_NAME, errmsg);
            free_metadata (&xml_metadata);
            free_metadata (&output_xml_metadata);
            return ERROR;
        }
    }

    /* Free the metadata structure */
    free_metadata (&xml_metadata);
    free_metadata (&output_xml_metadata);

    /* Successful conversion */
    return SUCCESS;
}

