"""
Module with enum constants and utm utils
"""

import itertools as it
import mimetypes
import utm
import os.path

from pyproj import Proj
from enum import Enum, EnumMeta

from .config import SHConfig


mimetypes.add_type('application/json', '.json')


class PackageProps:
    """ Class for obtaining package properties. Currently it supports obtaining package version."""

    @staticmethod
    def get_version():
        for line in open(os.path.join(os.path.dirname(__file__), '__init__.py')):
            if line.find("__version__") >= 0:
                version = line.split("=")[1].strip()
                version = version.strip('"').strip("'")
        return version


class ServiceType(Enum):
    """ Enum constant class for type of service

    Supported types are WMS, WCS, WFS, AWS
    """
    WMS = 'wms'
    WCS = 'wcs'
    WFS = 'wfs'
    AWS = 'aws'


class _DataSourceMeta(EnumMeta):
    """ EnumMeta class for `DataSource` Enum class
    """
    def __iter__(cls):
        return (member for name, member in cls._member_map_.items() if isinstance(member.value, tuple))

    def __len__(cls):
        return len(list(cls.__iter__()))


class DataSource(Enum, metaclass=_DataSourceMeta):
    """ Enum constant class for types of satellite data

    Supported types are SENTINEL2_L1C, SENTINEL2_L2A, LANDSAT8, SENTINEL1_IW, SENTINEL1_EW, SENTINEL1_EW_SH, DEM, MODIS
    """
    # pylint: disable=invalid-name
    # pylint: disable=unsubscriptable-object

    class Source(Enum):
        """
        Types of satellite sources
        """
        SENTINEL2 = 'Sentinel-2'
        SENTINEL1 = 'Sentinel-1'
        LANDSAT8 = 'Landsat 8'
        MODIS = 'MODIS'
        DEM = 'Mapzen DEM'
        LANDSAT5 = 'Landsat 5'
        LANDSAT7 = 'Landsat 7'
        SENTINEL3 = 'Sentinel-3'
        SENTINEL5P = 'Sentinel-5P'
        ENVISAT_MERIS = 'Envisat Meris'

    class ProcessingLevel(Enum):
        """
        Types of processing level
        """
        L2 = 'L2'
        L1C = 'L1C'
        L2A = 'L2A'
        L3B = 'L3B'
        L1TP = 'L1TP'
        GRD = 'GRD'
        MCD43A4 = 'MCD43A4'

    class Acquisition(Enum):
        """
        Types of satellite acquisition
        """
        IW = 'IW'
        EW = 'EW'
        OLCI = 'OLCI'

    class Polarisation(Enum):
        """
        Types of Sentinel-1 polarisation
        """
        DV = 'VV+VH'
        DH = 'HH+HV'
        SV = 'VV'
        SH = 'HH'

    class Resolution(Enum):
        """
        Types of Sentinel-1 resolution
        """
        MEDIUM = 'medium'
        HIGH = 'high'

    class OrbitDirection(Enum):
        """
        Types of Sentinel-1 orbit direction
        """
        ASCENDING = 'ascending'
        DESCENDING = 'descending'
        BOTH = 'both'

    SENTINEL2_L1C = (Source.SENTINEL2, ProcessingLevel.L1C)
    SENTINEL2_L2A = (Source.SENTINEL2, ProcessingLevel.L2A)
    SENTINEL1_IW = (Source.SENTINEL1, ProcessingLevel.GRD, Acquisition.IW, Polarisation.DV, Resolution.HIGH,
                    OrbitDirection.BOTH)
    SENTINEL1_EW = (Source.SENTINEL1, ProcessingLevel.GRD, Acquisition.EW, Polarisation.DH, Resolution.MEDIUM,
                    OrbitDirection.BOTH)
    SENTINEL1_EW_SH = (Source.SENTINEL1, ProcessingLevel.GRD, Acquisition.EW, Polarisation.SH, Resolution.MEDIUM,
                       OrbitDirection.BOTH)
    DEM = (Source.DEM, )
    MODIS = (Source.MODIS, ProcessingLevel.MCD43A4)
    LANDSAT8 = (Source.LANDSAT8, ProcessingLevel.L1TP)
    # eocloud sources:
    LANDSAT5 = (Source.LANDSAT5, ProcessingLevel.GRD)
    LANDSAT7 = (Source.LANDSAT7, ProcessingLevel.GRD)
    SENTINEL3 = (Source.SENTINEL3, ProcessingLevel.L2, Acquisition.OLCI)
    SENTINEL5P = (Source.SENTINEL5P, ProcessingLevel.L2)
    ENVISAT_MERIS = (Source.ENVISAT_MERIS, )
    SENTINEL2_L3B = (Source.SENTINEL2, ProcessingLevel.L3B)
    LANDSAT8_L2A = (Source.LANDSAT8, ProcessingLevel.L2A)

    @classmethod
    def get_wfs_typename(cls, data_source):
        """ Maps data source to string identifier for WFS

        :param data_source: One of the supported data sources
        :type: DataSource
        :return: Product identifier for WFS
        :rtype: str
        """
        is_eocloud = SHConfig().is_eocloud_ogc_url()
        return {
            cls.SENTINEL2_L1C: 'S2.TILE',
            cls.SENTINEL2_L2A: 'SEN4CAP_S2L2A.TILE' if is_eocloud else 'DSS2',
            cls.SENTINEL1_IW: 'S1.TILE' if is_eocloud else 'DSS3',
            cls.SENTINEL1_EW: 'S1_EW.TILE' if is_eocloud else 'DSS3',
            cls.SENTINEL1_EW_SH: 'S1_EW_SH.TILE' if is_eocloud else 'DSS3',
            cls.DEM: 'DSS4',
            cls.MODIS: 'DSS5',
            cls.LANDSAT8: 'L8.TILE' if is_eocloud else 'DSS6',
            # eocloud sources only:
            cls.LANDSAT5: 'L5.TILE',
            cls.LANDSAT7: 'L7.TILE',
            cls.SENTINEL3: 'S3.TILE',
            cls.SENTINEL5P: 'S5p_L2.TILE',
            cls.ENVISAT_MERIS: 'ENV.TILE',
            cls.SENTINEL2_L3B: 'SEN4CAP_S2L3B.TILE',
            cls.LANDSAT8_L2A: 'SEN4CAP_L8L2A.TILE'
        }[data_source]

    def is_sentinel1(self):
        """Checks if source is Sentinel-1

        Example: ``DataSource.SENTINEL1_IW.is_sentinel1()`` or ``DataSource.is_sentinel1(DataSource.SENTINEL1_IW)``

        :param self: One of the supported data sources
        :type self: DataSource
        :return: ``True`` if source is Sentinel-1 and ``False`` otherwise
        :rtype: bool
        """
        return self.value[0] is DataSource.Source.value.SENTINEL1

    def is_timeless(self):
        """Checks if data source is time independent

        Example: ``DataSource.DEM.is_timeless()`` or ``DataSource.is_timeless(DataSource.DEM)``

        :param self: One of the supported data sources
        :type self: DataSource
        :return: ``True`` if data source is time independent and ``False`` otherwise
        :rtype: bool
        """
        return self.value[0] is DataSource.Source.value.DEM

    def is_uswest_source(self):
        """Checks if data source via Sentinel Hub services is available at US West server

        Example: ``DataSource.LANDSAT8.is_uswest_source()`` or ``DataSource.is_uswest_source(DataSource.LANDSAT8)``

        :param self: One of the supported data sources
        :type self: DataSource
        :return: ``True`` if data source exists at US West server and ``False`` otherwise
        :rtype: bool
        """
        return not SHConfig().is_eocloud_ogc_url() and self.value[0] in [DataSource.Source.value.LANDSAT8,
                                                                         DataSource.Source.value.MODIS,
                                                                         DataSource.Source.value.DEM]

    @classmethod
    def get_available_sources(cls):
        """ Returns which data sources are available for configured Sentinel Hub OGC URL

        :return: List of available data sources
        :rtype: list(sentinelhub.DataSource)
        """
        if SHConfig().is_eocloud_ogc_url():
            return [cls.SENTINEL2_L1C, cls.SENTINEL2_L2A, cls.SENTINEL2_L3B, cls.SENTINEL1_IW, cls.SENTINEL1_EW,
                    cls.SENTINEL1_EW_SH, cls.SENTINEL3, cls.SENTINEL5P, cls.LANDSAT5, cls.LANDSAT7, cls.LANDSAT8,
                    cls.LANDSAT8_L2A, cls.ENVISAT_MERIS]
        return [cls.SENTINEL2_L1C, cls.SENTINEL2_L2A, cls.SENTINEL1_IW, cls.SENTINEL1_EW, cls.SENTINEL1_EW_SH, cls.DEM,
                cls.MODIS, cls.LANDSAT8]


class _Direction(Enum):
    """ Enum constant class to encode NORTH/SOUTH direction """
    NORTH = 'N'
    SOUTH = 'S'


def _get_utm_code(zone, direction):
    """ Get UTM code given a zone and direction

    Direction is encoded as NORTH=6, SOUTH=7, while zone is the UTM zone number zero-padded.
    For instance, the code 32604 is returned for zone number 4, north direction.

    :param zone: UTM zone number
    :type zone: int
    :param direction: Direction enum type
    :type direction: Enum
    :return: UTM code
    :rtype: str
    """
    dir_dict = {_Direction.NORTH: '6', _Direction.SOUTH: '7'}
    return '{}{}{}'.format('32', dir_dict[direction], str(zone).zfill(2))


def _get_utm_name_value_pair(zone, direction=_Direction.NORTH):
    """ Get name and code for UTM coordinates

    :param zone: UTM zone number
    :type zone: int
    :param direction: Direction enum type
    :type direction: Enum, optional (default=NORTH)
    :return: Name and code of UTM coordinates
    :rtype: str, str
    """
    name = 'UTM_{}{}'.format(zone, direction.value)
    epsg = _get_utm_code(zone, direction)
    return name, epsg


class _BaseCRS(Enum):
    """ Coordinate Reference System enumerate class """
    def __str__(self):
        return self.ogc_string(self)

    @staticmethod
    def get_utm_from_wgs84(lng, lat):
        """ Convert from WGS84 to UTM coordinate system

        :param lng: Longitude
        :type lng: float
        :param lat: Latitude
        :type lat: float
        :return: UTM coordinates
        :rtype: tuple
        """
        _, _, zone, _ = utm.from_latlon(lat, lng)
        direction = 'N' if lat >= 0 else 'S'
        return CRS['UTM_{}{}'.format(str(zone), direction)]

    @staticmethod
    def ogc_string(crs):
        """ Returns a string of the form authority:id representing the CRS.

        :param crs: An enum constant representing a coordinate reference system.
        :type crs: Enum constant
        :return: A string representation of the CRS.
        :rtype: str
        """
        return 'EPSG:' + CRS(crs).value

    @staticmethod
    def is_utm(crs):
        """ Checks if crs is one of the 64 possible UTM coordinate reference systems.

        :param crs: An enum constant representing a coordinate reference system.
        :type crs: Enum constant
        :return: True if crs is UTM and False otherwise
        :rtype: bool
        """
        return crs.name.startswith('UTM')

    @classmethod
    def projection(cls, crs):
        """ Returns a projection in form of pyproj class

        :param crs: An enum constant representing a coordinate reference system.
        :type crs: Enum constant
        :return: pyproj projection class
        :rtype: pyproj.Proj
        """
        return Proj(init=cls.ogc_string(crs))

    @classmethod
    def has_value(cls, value):
        """ Tests whether CRS contains a constant defined with string ``value``.

        :param value: The string representation of the enum constant.
        :type value: str
        :return: ``True`` if there exists a constant with string value ``value``, ``False`` otherwise
        :rtype: bool
        """
        return any(value == item.value for item in cls)


# Look-up class with possible combinations of UTM zone and direction
CRS = _BaseCRS("CRS", dict(
    [_get_utm_name_value_pair(zone, direction) for zone, direction in it.product(range(1, 65), _Direction)] +
    [('WGS84', '4326'), ('POP_WEB', '3857')]
))


def _crs_parser(cls, value):
    """ Parses user input for class CRS

    :param cls: class object
    :param value: user input for CRS
    :type value: str, int or CRS
    """
    parsed_value = value
    if isinstance(parsed_value, int):
        parsed_value = str(parsed_value)
    if isinstance(parsed_value, str):
        parsed_value = parsed_value.strip('epsgEPSG: ')
    return super(_BaseCRS, cls).__new__(cls, parsed_value)


setattr(CRS, '__new__', _crs_parser)


class CustomUrlParam(Enum):
    """ Enum class to represent supported custom url parameters of OGC services

    Supported parameters are `ShowLogo`, `AtmFilter`, `EvalScript`, `EvalScriptUrl`, `Preview`, `Quality`, `Upsampling`,
    `Downsampling`, `Transparent`, `BgColor` and `Geometry`.

    See http://sentinel-hub.com/develop/documentation/api/custom-url-parameters and
    https://www.sentinel-hub.com/develop/documentation/api/ogc_api/wms-parameters for more information.
    """
    SHOWLOGO = 'ShowLogo'
    ATMFILTER = 'AtmFilter'
    EVALSCRIPT = 'EvalScript'
    EVALSCRIPTURL = 'EvalScriptUrl'
    PREVIEW = 'Preview'
    QUALITY = 'Quality'
    UPSAMPLING = 'Upsampling'
    DOWNSAMPLING = 'Downsampling'
    TRANSPARENT = 'Transparent'
    BGCOLOR = 'BgColor'
    GEOMETRY = 'Geometry'

    @classmethod
    def has_value(cls, value):
        """ Tests whether CustomUrlParam contains a constant defined with a string `value`

        :param value: The string representation of the enum constant
        :type value: str
        :return: `True` if there exists a constant with a string value `value`, `False` otherwise
        :rtype: bool
        """
        return any(value.lower() == item.value.lower() for item in cls)

    @staticmethod
    def get_string(param):
        """ Get custom url parameter name as string

        :param param: CustomUrlParam enum constant
        :type param: Enum constant
        :return: String describing the file format
        :rtype: str
        """
        return param.value


class MimeType(Enum):
    """ Enum class to represent supported image file formats

    Supported file formats are TIFF 8-bit, TIFF 16-bit, TIFF 32-bit float, PNG, JPEG, JPEG2000, JSON, CSV, ZIP, HDF5,
    XML, GML, RAW
    """
    TIFF = 'tiff'
    TIFF_d8 = 'tiff;depth=8'
    TIFF_d16 = 'tiff;depth=16'  # This is the same as TIFF
    TIFF_d32f = 'tiff;depth=32f'
    PNG = 'png'
    JPG = 'jpg'
    JP2 = 'jp2'
    JSON = 'json'
    CSV = 'csv'
    ZIP = 'zip'
    HDF = 'hdf'
    XML = 'xml'
    GML = 'gml'
    TXT = 'txt'
    RAW = 'raw'
    SAFE = 'safe'
    REQUESTS_RESPONSE = 'response'  # http://docs.python-requests.org/en/master/api/#requests.Response

    @staticmethod
    def canonical_extension(fmt_ext):
        """ Canonical extension of file format extension

        Converts the format extension fmt_ext into the canonical extension for that format. For example,
        ``canonical_extension('tif') == 'tiff'``. Here we agree that the canonical extension for format F is F.value

        :param fmt_ext: A string representing an extension (e.g. ``'txt'``, ``'png'``, etc.)
        :type fmt_ext: str
        :return: The canonical form of the extension (e.g. if ``fmt_ext='tif'`` then we return ``'tiff'``)
        :rtype: str
        """
        if MimeType.has_value(fmt_ext):
            return fmt_ext
        try:
            return {
                'tif': MimeType.TIFF.value,
                'jpeg': MimeType.JPG.value,
                'hdf5': MimeType.HDF.value,
                'h5': MimeType.HDF.value
            }[fmt_ext]
        except KeyError:
            raise ValueError('Data format .{} is not supported'.format(fmt_ext))

    def is_image_format(self):
        """ Checks whether file format is an image format

        Example: ``MimeType.PNG.is_image_format()`` or ``MimeType.is_image_format(MimeType.PNG)``

        :param self: File format
        :type self: MimeType
        :return: ``True`` if file is in image format, ``False`` otherwise
        :rtype: bool
        """
        return self in frozenset([MimeType.TIFF, MimeType.TIFF_d8, MimeType.TIFF_d16, MimeType.TIFF_d32f, MimeType.PNG,
                                  MimeType.JP2, MimeType.JPG])

    def is_tiff_format(self):
        """ Checks whether file format is a TIFF image format

        Example: ``MimeType.TIFF.is_tiff_format()`` or ``MimeType.is_tiff_format(MimeType.TIFF)``

        :param self: File format
        :type self: MimeType
        :return: ``True`` if file is in image format, ``False`` otherwise
        :rtype: bool
        """
        return self in frozenset([MimeType.TIFF, MimeType.TIFF_d8, MimeType.TIFF_d16, MimeType.TIFF_d32f])

    @classmethod
    def has_value(cls, value):
        """ Tests whether MimeType contains a constant defined with string ``value``

        :param value: The string representation of the enum constant
        :type value: str
        :return: ``True`` if there exists a constant with string value ``value``, ``False`` otherwise
        :rtype: bool
        """
        return any(value == item.value for item in cls)

    @staticmethod
    def get_string(fmt):
        """ Get file format as string

        :param fmt: MimeType enum constant
        :type fmt: Enum constant
        :return: String describing the file format
        :rtype: str
        """
        if fmt in [MimeType.TIFF_d8, MimeType.TIFF_d16, MimeType.TIFF_d32f]:
            return 'image/{}'.format(fmt.value)
        elif fmt is MimeType.JP2:
            return 'image/jpeg2000'
        elif fmt in [MimeType.RAW, MimeType.REQUESTS_RESPONSE]:
            return fmt.value
        return mimetypes.types_map['.' + fmt.value]


class RequestType(Enum):
    """ Enum constant class for GET/POST request type """
    GET = 'GET'
    POST = 'POST'


class OgcConstants:
    """ Initialisation of constants used by OGC request.

        Constants are LATEST
    """
    LATEST = 'latest'
    HEADERS = {'User-Agent': 'sentinelhub-py/v{}'.format(PackageProps.get_version())}


class AwsConstants:
    """ Initialisation of every constant used by AWS classes

    For each supported data source it contains lists of all possible bands and all posible metadata files:

        - S2_L1C_BANDS and S2_L1C_METAFILES
        - S2_L2A_BANDS and S2_L2A_METAFILES

    It also contains dictionary of all possible files and their formats: AWS_FILES
    """
    # General constants:
    SOURCE_ID_LIST = ['L1C', 'L2A']
    TILE_INFO = 'tileInfo'
    PRODUCT_INFO = 'productInfo'
    METADATA = 'metadata'
    PREVIEW = 'preview'
    PREVIEW_JP2 = 'preview*'
    QI_LIST = ['DEFECT', 'DETFOO', 'NODATA', 'SATURA', 'TECQUA']
    QI_MSK_CLOUD = 'qi/MSK_CLOUDS_B00'
    AUX_DATA = 'AUX_DATA'
    DATASTRIP = 'DATASTRIP'
    GRANULE = 'GRANULE'
    HTML = 'HTML'
    INFO = 'rep_info'
    QI_DATA = 'QI_DATA'
    IMG_DATA = 'IMG_DATA'
    INSPIRE = 'inspire'
    MANIFEST = 'manifest'
    TCI = 'TCI'
    PVI = 'PVI'
    ECMWFT = 'auxiliary/ECMWFT'
    AUX_ECMWFT = 'auxiliary/AUX_ECMWFT'
    DATASTRIP_METADATA = 'datastrip/*/metadata'

    # More constants about L2A
    AOT = 'AOT'
    WVP = 'WVP'
    SCL = 'SCL'
    VIS = 'VIS'
    L2A_MANIFEST = 'L2AManifest'
    REPORT = 'report'
    GIPP = 'auxiliary/GIP_TL'
    FORMAT_CORRECTNESS = 'FORMAT_CORRECTNESS'
    GENERAL_QUALITY = 'GENERAL_QUALITY'
    GEOMETRIC_QUALITY = 'GEOMETRIC_QUALITY'
    RADIOMETRIC_QUALITY = 'RADIOMETRIC_QUALITY'
    SENSOR_QUALITY = 'SENSOR_QUALITY'
    QUALITY_REPORTS = [FORMAT_CORRECTNESS, GENERAL_QUALITY, GEOMETRIC_QUALITY, RADIOMETRIC_QUALITY, SENSOR_QUALITY]
    CLASS_MASKS = ['SNW', 'CLD']
    R10m = 'R10m'
    R20m = 'R20m'
    R60m = 'R60m'
    RESOLUTIONS = [R10m, R20m, R60m]
    S2_L2A_BAND_MAP = {R10m: ['B02', 'B03', 'B04', 'B08', AOT, TCI, WVP],
                       R20m: ['B02', 'B03', 'B04', 'B05', 'B06', 'B07', 'B8A', 'B11', 'B12', AOT, SCL, TCI, VIS, WVP],
                       R60m: ['B01', 'B02', 'B03', 'B04', 'B05', 'B06', 'B07', 'B8A', 'B09', 'B11', 'B12', AOT, SCL,
                              TCI, WVP]}

    # Order of elements in following lists is important
    # Sentinel-2 L1C products:
    S2_L1C_BANDS = ['B01', 'B02', 'B03', 'B04', 'B05', 'B06', 'B07', 'B08', 'B8A', 'B09', 'B10', 'B11', 'B12']
    S2_L1C_METAFILES = [PRODUCT_INFO, TILE_INFO, METADATA, INSPIRE, MANIFEST, DATASTRIP_METADATA] +\
                       [PREVIEW, PREVIEW_JP2, TCI] +\
                       ['{}/{}'.format(preview, band) for preview, band in it.zip_longest([], S2_L1C_BANDS,
                                                                                          fillvalue=PREVIEW)] +\
                       [QI_MSK_CLOUD] +\
                       ['qi/MSK_{}_{}'.format(qi, band) for qi, band in it.product(QI_LIST, S2_L1C_BANDS)] + \
                       ['qi/{}'.format(qi_report) for qi_report in [FORMAT_CORRECTNESS, GENERAL_QUALITY,
                                                                    GEOMETRIC_QUALITY, SENSOR_QUALITY]] +\
                       [ECMWFT]

    # Sentinel-2 L2A products:
    S2_L2A_BANDS = ['{}/{}'.format(resolution, band) for resolution, band_list in sorted(S2_L2A_BAND_MAP.items())
                    for band in band_list]
    S2_L2A_METAFILES = [PRODUCT_INFO, TILE_INFO, METADATA, INSPIRE, MANIFEST, L2A_MANIFEST, REPORT,
                        DATASTRIP_METADATA] + ['datastrip/*/qi/{}'.format(qi_report)for qi_report in QUALITY_REPORTS] +\
                       ['qi/{}_PVI'.format(source_id) for source_id in SOURCE_ID_LIST] +\
                       ['qi/{}_{}'.format(mask, res.lstrip('R')) for mask, res in it.product(CLASS_MASKS,
                                                                                             [R20m, R60m])] +\
                       ['qi/MSK_{}_{}'.format(qi, band) for qi, band in it.product(QI_LIST, S2_L1C_BANDS)] +\
                       [QI_MSK_CLOUD] +\
                       ['qi/{}'.format(qi_report) for qi_report in [FORMAT_CORRECTNESS, GENERAL_QUALITY,
                                                                    GEOMETRIC_QUALITY, SENSOR_QUALITY]] +\
                       [ECMWFT, AUX_ECMWFT, GIPP]

    # Product files with formats:
    PRODUCT_FILES = {**{PRODUCT_INFO: MimeType.JSON,
                        METADATA: MimeType.XML,
                        INSPIRE: MimeType.XML,
                        MANIFEST: MimeType.SAFE,
                        L2A_MANIFEST: MimeType.XML,
                        REPORT: MimeType.XML,
                        DATASTRIP_METADATA: MimeType.XML},
                     **{'datastrip/*/qi/{}'.format(qi_report): MimeType.XML for qi_report in QUALITY_REPORTS}}
    # Tile files with formats:
    TILE_FILES = {**{TILE_INFO: MimeType.JSON,
                     PRODUCT_INFO: MimeType.JSON,
                     METADATA: MimeType.XML,
                     PREVIEW: MimeType.JPG,
                     PREVIEW_JP2: MimeType.JP2,
                     TCI: MimeType.JP2,
                     QI_MSK_CLOUD: MimeType.GML,
                     ECMWFT: MimeType.RAW,
                     AUX_ECMWFT: MimeType.RAW,
                     GIPP: MimeType.XML},
                  **{'qi/{}'.format(qi_report): MimeType.XML for qi_report in [FORMAT_CORRECTNESS, GENERAL_QUALITY,
                                                                               GEOMETRIC_QUALITY, SENSOR_QUALITY]},
                  **{'{}/{}'.format(preview, band): MimeType.JP2
                     for preview, band in it.zip_longest([], S2_L1C_BANDS, fillvalue=PREVIEW)},
                  **{'qi/MSK_{}_{}'.format(qi, band): MimeType.GML for qi, band in it.product(QI_LIST, S2_L1C_BANDS)},
                  **{band: MimeType.JP2 for band in S2_L1C_BANDS},
                  **{band: MimeType.JP2 for band in S2_L2A_BANDS},
                  **{'qi/{}_PVI'.format(source_id): MimeType.JP2 for source_id in SOURCE_ID_LIST},
                  **{'qi/{}_{}'.format(mask, res.lstrip('R')): MimeType.JP2 for mask, res in it.product(CLASS_MASKS,
                                                                                                        [R20m, R60m])}}

    # All files joined together
    AWS_FILES = {**PRODUCT_FILES,
                 **{filename.split('/')[-1]: data_format for filename, data_format in PRODUCT_FILES.items()},
                 **TILE_FILES,
                 **{filename.split('/')[-1]: data_format for filename, data_format in TILE_FILES.items()}}


class EsaSafeType(Enum):
    """ Enum constants class for ESA .SAFE type.

     Types are OLD_TYPE and COMPACT_TYPE
    """
    OLD_TYPE = 'old_type'
    COMPACT_TYPE = 'compact_type'
