""" Formats the final level 2 product by creating an XML file of the
    appropriate bands and sending it through the conversion application.
"""

import os
import shutil
import subprocess
import logging
import re
from datetime import datetime

from espa import Metadata
from espa import ODL
from espa import System

# Global variables
logger = logging.getLogger(__name__)

# Expected SR XML band for any sensor in the format Name: Product
SR = {
    "sr_band1": "SR_B1",
    "sr_band2": "SR_B2",
    "sr_band3": "SR_B3",
    "sr_band4": "SR_B4",
    "sr_band5": "SR_B5",
    "sr_band7": "SR_B7"
}

# Expected non split window ST XML band for any sensor in the format
# Name: Product
ST = {
    "st_qa": "ST_QA",
    "emis": "ST_EMIS",
    "emis_stdev": "ST_EMSD",
    "st_cloud_distance": "ST_CDIST",
    "st_thermal_radiance": "ST_TRAD",
    "st_upwelled_radiance": "ST_URAD",
    "st_downwelled_radiance": "ST_DRAD",
    "st_atmospheric_transmittance": "ST_ATRAN"
}

# Expected BT XML band for OLI_TIRS in the format Name: Product
TIRS_BT = {
    "bt_band10": "BT_B10",
    "bt_band11": "BT_B11"
}

# Expected TOA XML band for any sensor in the format Name: Product
TOA = {
    "toa_band1": "TOA_B1",
    "toa_band2": "TOA_B2",
    "toa_band3": "TOA_B3",
    "toa_band4": "TOA_B4",
    "toa_band5": "TOA_B5",
    "toa_band7": "TOA_B7"
}
# Expected BQA XML band in the format Name: Product
BQA = {
    "bqa_pixel": "QA_PIXEL",
    "bqa_radsat": "QA_RADSAT"
}

# Expected split window ST XML band for any sensor in the format Name: Product
SPLIT_WINDOW_ST = {
    "surface_temperature": "ST_SW",
    'emis_band10': 'ST_EMIS_B10',
    'emis_band11': 'ST_EMIS_B11'
}


def format_product(xml_filename, l2_product_id, output_dir, product_type,
                   convert_cog=True, include_angle_bands=False,
                   hdf_filename=None, hdf5_filename=None, check_for_bqa=True):
    """ Determines the bands to be formatted, creates a temporary XML file
        containing just the pertinent files, then formats them.

        Parameters:
            xml_filename: Name of the main ESPA metadata XML file.
            l2_product_id: The level 2 product ID.
            output_dir: Directory to place the final product.
            product_type: String to determine if formatting is SR, ST, SR_ST,
                          SR_TOA_BT or SR_ST_TOA_BT.
            convert_cog:  Flag to determine if output files should be converted
                          to COG format
            include_angle_band:  Include Band 4 angle bands in the output
                                 product
            hdf_filename: Used to specify the if HDF4 is to be used and what
                          the filename will be if it is used. Defaults to None.
            hdf5_filename: Used to specify the if HDF5 is to be used and what
                          the filename will be if it is used. Defaults to None.
            check_for_bqa: Flag to determine if bqa should be used or not.

        Raises:
            CalledProcessError: If the call to convert_espa_to_<hdf/tiff>
                fails. To get the output from this error, the output property
                can be used.
            ValueError: If the product type or sensor are invalid, or if an
                        expected band is missing.
    """
    logger.debug('Starting format product using product: ' + product_type)

    # Verify we have a valid product type
    if 'st' not in product_type.lower() and 'sr' not in product_type.lower():
        raise ValueError('Invalid product type: ' + product_type)

    # Create XML object for the bands to format XML. Use the ESPA metadata
    # XML file as a starting point
    logger.debug('Parsing Metadata file: ' + xml_filename)
    bands_to_format = Metadata(xml_filename)
    bands_to_format.parse()

    # Get the sensor info for sensor specific bands
    sensor = bands_to_format.xml_object.global_metadata.instrument.text
    logger.debug('Sensor is: ' + sensor)

    # Append any sensor specific bands to the bands we are looking for
    product = {}
    cog_product = []
    if sensor == 'OLI_TIRS':
        if 'st' in product_type.lower():
            generate_st_products = ODL(file_name='GENERATE_ST_PRODUCTS.odl')
            generate_st_products.parse()
            try:
                algorithm = generate_st_products.get_value('ST_ALGORITHM')
            except KeyError:
                logger.debug('ST_ALGORITHM is missing from '
                             'GENERATE_ST_PRODUCTS.odl. Defaulting to '
                             'single_channel.')
                algorithm = 'single_channel'
            if 'split_window' in algorithm.lower():
                product.update(SPLIT_WINDOW_ST)
                product['surface_temperature'] = 'ST_SW'
            else:
                product.update(ST)
                product['surface_temperature'] = 'ST_B10'
        if 'sr' in product_type.lower():
            product.update(SR)
            product["sr_band6"] = "SR_B6"
            product["sr_aerosol"] = "SR_QA_AEROSOL"
        if 'toa' in product_type.lower():
            product.update(TOA)
            # Band6 and Band9 are included in OLITIRS TOA bands
            product["toa_band6"] = "TOA_B6"
            product["toa_band9"] = "TOA_B9"
        if 'bt' in product_type.lower():
            product.update(TIRS_BT)
    elif sensor in ('ETM', 'TM'):
        if 'st' in product_type.lower():
            product.update(ST)
            product['surface_temperature'] = 'ST_B6'
        if 'sr' in product_type.lower():
            product.update(SR)
            product["sr_cloud_qa"] = "SR_CLOUD_QA"
            product["sr_atmos_opacity"] = "SR_ATMOS_OPACITY"
        if 'toa' in product_type.lower():
            product.update(TOA)
        if 'bt' in product_type.lower():
            product["bt_band6"] = "BT_B6"
    else:
        raise ValueError('Invalid sensor: ' + sensor)

    # Add bqa bands if that's requested
    if check_for_bqa:
        product.update(BQA)

    # Add the angle bands if requested
    if (include_angle_bands):
        product["sensor_azimuth_band4"] = "VAA"
        product["sensor_zenith_band4"] = "VZA"
        product["solar_azimuth_band4"] = "SAA"
        product["solar_zenith_band4"] = "SZA"

    # Determine the bands to be kept from the main XML file
    for band in bands_to_format.xml_object.bands.band:
        band_name = band.get('name')

        if band_name in product:
            # Change the band name so that the output file name is correct
            new_name = product[band_name]
            band.set('name', new_name)
            logger.debug('Found band: ' + band_name + '. Changed name to: ' +
                         new_name)

            # Add to name to the list of files to convert to COG format,
            # if the conversion to COG is requested
            if convert_cog:
                cog_product.append(new_name)

            # Remove the name from the dict to look for since we found it
            del product[band_name]

            continue

        # Remove any bands from the XML that don't match the desired bands
        band.getparent().remove(band)

    # Make sure that we got all the bands we were looking for
    if len(product) > 0:
        error_msg = ('Not all expected bands were found. The following are '
                     'missing:')
        for band in product:
            error_msg += '\n' + band
        raise ValueError(error_msg)

    # Create a temp file of bands to process
    temp_filename = 'temp_' + product_type + '_bands_to_format.xml'
    logger.debug('Creating temp band file: ' + temp_filename)
    bands_to_format.write(xml_filename=temp_filename)

    working_dir = os.getcwd()
    xml_file_name = os.path.join(working_dir, temp_filename)

    logger.debug('Creating output directory: ' + output_dir)
    System.create_directory(output_dir)
    System.empty_directory(output_dir)

    logger.debug('Changing directory to output directory: ' + output_dir)
    os.chdir(output_dir)

    # Build the format command
    if hdf_filename:
        fmt = 'hdf'
        app_call = ['--' + fmt, hdf_filename, '--for_ias']
    elif hdf5_filename:
        fmt = 'hdf5'
        app_call = ['--' + fmt, hdf5_filename]
    else:
        fmt = 'gtif'
        app_call = ['--' + fmt, l2_product_id]

    cmd = ['convert_espa_to_' + fmt, '--xml', xml_file_name] + app_call
    logger.debug('Command to run: ' + ' '.join(cmd))
    try:
        response = subprocess.check_output(cmd, universal_newlines=True,
                                           stderr=subprocess.STDOUT)
    except subprocess.CalledProcessError:
        os.chdir(working_dir)
        raise

    logger.debug(response)

    # convert_espa_to_hdf/hdf5 generates an xml that we don't need. Cleaning
    # it up.
    if hdf_filename:
        hdf_product_id = os.path.splitext(hdf_filename)[0]
        hdf_temp_xml_filename = hdf_product_id + '_hdf.xml'
        os.remove(hdf_temp_xml_filename)
        logger.debug('Removed ' + hdf_temp_xml_filename)
    elif hdf5_filename:
        hdf5_product_id = os.path.splitext(hdf5_filename)[0]
        hdf5_temp_xml_filename = hdf5_product_id + '_hdf5.xml'
        os.remove(hdf5_temp_xml_filename)
        logger.debug('Removed ' + hdf5_temp_xml_filename)
    # Copy the level 1 angle coefficient file to the level 2 geotiff product
    else:
        l1_mtl_filename = os.path.join(working_dir,
                                       bands_to_format.xml_object.
                                       global_metadata.
                                       lpgs_metadata_file.text)
        l1_ang_filename = l1_mtl_filename.replace("MTL.txt", "ANG.txt")
        l2_ang_filename = l2_product_id + '_ANG.txt'
        logger.debug('Copying ' + l1_ang_filename + " to " + l2_ang_filename)
        if os.path.exists(l1_ang_filename):
            shutil.copyfile(l1_ang_filename, l2_ang_filename)
        else:
            raise ValueError("Missing angle coefficient file " +
                             l1_ang_filename)
        # Convert the GEOTIFFs to COG files
        for band_name in cog_product:
                tiff_file = l2_product_id + "_" + band_name + ".TIF"
                cmd = ['convert_geotiff_to_cog.py', '--input_filename',
                       tiff_file, '--output_filename', tiff_file]
                try:
                    response = subprocess.check_output(cmd,
                                                       universal_newlines=True,
                                                       stderr=subprocess.STDOUT
                                                       )
                except subprocess.CalledProcessError:
                    os.chdir(working_dir)
                    raise

    # Cleaning up the temp xml file that was passed to convert_espa_to_*
    os.chdir(working_dir)
    os.remove(temp_filename)
    logger.debug('Removed ' + temp_filename)
