#
# Copyright 2008-2024 Universidad Complutense de Madrid
#
# This file is part of Numina
#
# SPDX-License-Identifier: GPL-3.0-or-later
# License-Filename: LICENSE.txt
#

import warnings

import numina.util.convert as conv
from .keydef import FITSKeyExtractor


class DataModel:
    """Model of the Data being processed

    Parameters
    ==========
    name : str
        Name of the DataModel
    mappings: dict, optional
        A dictionary of str keys and values convertibles to FITSKeyExtractor

    """

    db_info_keys = [
        "instrument",
        "object",
        "observation_date",
        "uuid",
        "type",
        "mode",
        "exptime",
        "darktime",
        "insconf",
        "blckuuid",
        "quality_control",
    ]
    db_info_keys_extra = []
    query_attrs = {}
    meta_dinfo_headers = [
        "instrument",
        "object",
        "observation_date",
        "uuid",
        "type",
        "mode",
        "exptime",
        "darktime",
        "insconf",
        "blckuuid",
        "quality_control",
        "block_uuid",  # Alias
        "insconf_uuid",  # Alias
        "imgid",
    ]

    def __init__(self, name="UNKNOWN", mappings=None):

        self.name = name
        values = self.default_mappings()
        more = {} if mappings is None else mappings
        values.update(more)

        self.extractor_map = dict()
        self.extractor_map["fits"] = FITSKeyExtractor(values)
        self.extractor_map["json"] = None
        self.extractor = self.extractor_map["fits"]

    def default_mappings(self):
        return {
            "instrument": "INSTRUME",
            "object": "OBJECT",
            "observation_date": ("DATE-OBS", 0, conv.convert_date),
            "uuid": "uuid",
            "type": "numtype",
            "mode": "obsmode",
            "exptime": self.get_exptime,
            "darktime": self.get_darktime,
            "quality_control": ("NUMRQC", 0, conv.convert_qc),
            "insmode": ("INSMODE", "undefined"),
            "imgid": self.get_imgid,
            "insconf": "INSCONF",
            "blckuuid": lambda x: "1",
            "insconf_uuid": "insconf",  # Alias
            "block_uuid": "blckuuid",  # Alias
        }

    def get_data(self, img):
        """Obtain the primary data from the image"""
        return img["primary"].data

    def get_header(self, img):
        """Obtain the primary header from the image"""
        return img["primary"].header

    def get_variance(self, img):
        """Obtain the variance of the primary data from the image"""
        return img["variance"].data

    def get_imgid(self, img):
        """Obtain a unique identifier of the image.

        Parameters
        ----------
        img : astropy.io.fits.HDUList

        Returns
        -------
        str:
             Identification of the image

        """
        return get_imgid(img, prefix=False)

    def get_darktime(self, img):
        """Obtain DARKTIME"""
        header = self.get_header(img)
        if "DARKTIME" in header.keys():
            return header["DARKTIME"]
        else:
            return self.get_exptime(img)

    def get_exptime(self, img):
        """Obtain EXPTIME"""
        header = self.get_header(img)
        if "EXPTIME" in header.keys():
            etime = header["EXPTIME"]

        elif "EXPOSED" in header.keys():
            etime = header["EXPOSED"]
        else:
            etime = 1.0
        return etime

    def do_sky_correction(self, img):
        """Perform sky correction"""
        return True

    def gather_info(self, dframe):
        """Obtain a summary of information about the image."""
        with dframe.open() as hdulist:
            info = self.gather_info_hdu(hdulist)
        return info

    def gather_info_dframe(self, img):
        """Obtain a summary of information about the image."""
        return self.gather_info(img)

    def gather_info_hdu(self, hdulist):
        """Obtain a summary of information about the image."""
        values = {}
        values["n_ext"] = len(hdulist)
        extnames = [hdu.header.get("extname", "") for hdu in hdulist[1:]]
        values["name_ext"] = ["PRIMARY"] + extnames

        fits_extractor = self.extractor_map["fits"]
        for key in self.meta_dinfo_headers:
            values[key] = fits_extractor.extract(key, hdulist)

        return values

    def gather_info_oresult(self, ob):
        return [self.gather_info_dframe(f) for f in ob.images]

    def get_quality_control(self, img):
        """Obtain quality control flag from the image."""
        fits_extractor = self.extractor_map["fits"]
        return fits_extractor.extract("quality_control", img)


def get_imgid(img, prefix=True):
    """Obtain a unique identifier of the image.

    Parameters
    ----------
    img : astropy.io.fits.HDUList

    Returns
    -------
    str:
         Identification of the image

    """
    hdr = img[0].header
    try:
        return get_imgid_header(hdr, prefix=prefix)
    except ValueError:
        warnings.warn("no method to identify image", RuntimeWarning)
        value = repr(img)
        pre = "py:{}"
        base = "{}"
        if prefix:
            return pre.format(value)
        else:
            return base.format(value)


def get_imgid_header(hdr, prefix=True):
    """Obtain a unique identifier of the image.

    Parameters
    ----------
    hdr : astropy.io.fits.Header

    Returns
    -------
    str:
         Identification of the image

    """
    base = "{}"
    if "UUID" in hdr:
        pre = "uuid:{}"
        value = hdr["UUID"]
    elif "EMIRUUID" in hdr:  # EMIRISM
        pre = "uuid:{}"
        value = hdr["EMIRUUID"]
    elif "DATE-OBS" in hdr:
        pre = "dateobs:{}"
        value = hdr["DATE-OBS"]
    elif "TSUTC1" in hdr:  # EMIRISM
        pre = "tsutc:{:16.5f}"
        value = hdr["TSUTC1"]
    elif "checksum" in hdr:
        pre = "checksum:{}"
        value = hdr["checksum"]
    elif "filename" in hdr:
        pre = "file:{}"
        value = hdr["filename"]
    else:
        raise ValueError("no method to identity image")
    if prefix:
        return pre.format(value)
    else:
        return base.format(value)
