import logging
import os
import numpy
import h5py

logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
logger.setLevel(logging.INFO)


def read_headers(h5_group: h5py.Group = None, h5_link: str = None) -> dict:
    if h5_group:
        return read_headers_from_h5group(h5_group=h5_group)
    h5file, h5path = h5_link.split("::")
    with h5py.File(h5file, "r") as file:
        return read_headers_from_h5group(h5_group=file[h5path])


def read_headers_from_h5group(h5_group: h5py.Group) -> dict:
    headers = {}
    for key in h5_group:
        value = h5_group[key][()]
        if isinstance(value, numpy.ndarray):
            value = numpy.array([deserialize_value(v) for v in value])
        else:
            value = deserialize_value(value)
        headers[key] = value
    return headers


def deserialize_value(value):
    if isinstance(value, bytes):
        value = value.decode("UTF-8")
    try:
        value = float(value)
    except (TypeError, ValueError):
        ...
    return value


class HS32:
    KEY_HS32_NB_PINS = "HS32Len"
    KEY_HS32_NAME = "HS32N"
    KEY_HS32_FACTOR = "HS32F"
    KEY_HS32_ZERO_VALUE = "HS32Z"
    KEY_HS32_COUNTERS = "HS32C"
    KEY_HS32_VALUES = "HS32V"
    KEY_EXPOSURE_TIME = "HSTime"
    KEY_MONITOR_0 = "HSI0"  # Monitor for beam intensity before the sample
    KEY_MONITOR_0_FACTOR = "HSI0Factor"
    KEY_MONITOR_1 = "HSI1"  # Monitor for beam intensity after the sample
    KEY_MONITOR_1_FACTOR = "HSI1Factor"
    KEY_SOT = "ShutterOpeningTime"
    KEY_SCT = "ShutterClosingTime"
    KEY_STARTEPOCH = "HMStartEpoch"
    KEY_STARTTIME = "HMStartTime"
    KEY_TITLEEXTENSION_TEMPLATE = "TitleExtensionTemplate"
    KEY_TITLEEXTENSION = "TitleExtension"
    KEY_PIXEL_SIZE_1 = "PSize_1"
    KEY_PIXEL_SIZE_2 = "PSize_2"
    KEY_BINNING_1 = "BSize_1"
    KEY_BINNING_2 = "BSize_2"
    KEY_WAVELENGTH = "WaveLength"
    KEY_SAMPLEDETECTOR_DISTANCE = "SampleDistance"
    KEY_NORMALIZATION_FACTOR = "NormalizationFactor"
    KEY_DETECTOR_MASK_FOLDER = "DetectorMaskFilePath"
    KEY_DETECTOR_MASK_FILE = "DetectorMaskFileName"
    KEY_BEAMSTOP_MASK_FOLDER = "MaskFilePath"
    KEY_BEAMSTOP_MASK_FILE = "MaskFileName"
    KEY_POLARIZATION_FACTOR = "polarization_factor"
    KEY_POLARIZATION_AXIS = "polarization_axis_offset"
    KEY_VARIANCE_FORMULA = "variance_formula"
    KEY_WINDOW_ROI_SIZE = "WindowRoiSize"
    KEY_DARK_FOLDER = "DarkFilePath"
    KEY_DARK_FILE = "DarkFileName"
    KEY_FLAT_FOLDER = "FlatfieldFilePath"
    KEY_FLAT_FILE = "FlatfieldFileName"
    KEY_WINDOW_FOLDER = "WindowFilePath"
    KEY_WINDOW_FILE = "WindowFileName"
    KEY_DUMMY = "Dummy"
    KEY_DELTA_DUMMY = "DDummy"
    KEY_CENTER_1 = "Center_1"
    KEY_CENTER_2 = "Center_2"
    KEY_NPT2_RAD = "npt2_rad"
    KEY_NPT2_AZIM = "npt2_azim"
    KEY_UNIT = "unit"
    KEY_TITLEEXTENSION = "TitleExtension"
    KEY_ALGORITHM_NORMALIZATION = "NormAlgorithm"

    @classmethod
    def get_HS32_number_pins(cls, headers: dict) -> int:
        """
        Get the number of HS32 pins, which will determine the shape of metadata arrays
        """
        return int(headers.get(cls.KEY_HS32_NB_PINS))

    @classmethod
    def get_HS32_names(cls, headers: dict) -> dict:
        nb_pins = cls.get_HS32_number_pins(headers=headers)
        names = {}
        for index_pin in range(nb_pins):
            names[index_pin] = headers.get(f"{cls.KEY_HS32_NAME}{(index_pin + 1):02}")
        return names

    @classmethod
    def get_headers_filename(
        cls, headers, folder_key: str, file_key: str, missing_ok: bool = False
    ):
        folder = headers.get(
            folder_key,
        )
        file_name = headers.get(
            file_key,
        )
        if folder is None or file_name is None:
            if not missing_ok:
                logger.warning(
                    f"Some keys are missing: {folder_key}={folder}, {file_key}={file_name}"
                )
            return

        filename = os.path.join(folder, file_name)
        if not os.path.exists(filename):
            logger.warning(f"{filename} from headers could not be found.")
            return
        return filename

    @classmethod
    def get_mask_window(cls, headers):
        return cls.get_headers_filename(
            headers=headers,
            folder_key=cls.KEY_WINDOW_FOLDER,
            file_key=cls.KEY_WINDOW_FILE,
        )

    @classmethod
    def get_mask_beamstop_filename(cls, headers):
        return cls.get_headers_filename(
            headers=headers,
            folder_key=cls.KEY_BEAMSTOP_MASK_FOLDER,
            file_key=cls.KEY_BEAMSTOP_MASK_FILE,
        )

    @classmethod
    def get_mask_gaps_filename(cls, headers):
        return cls.get_headers_filename(
            headers=headers,
            folder_key=cls.KEY_DETECTOR_MASK_FOLDER,
            file_key=cls.KEY_DETECTOR_MASK_FILE,
        )

    @classmethod
    def get_flat_filename(cls, headers):
        return cls.get_headers_filename(
            headers=headers,
            folder_key=cls.KEY_FLAT_FOLDER,
            file_key=cls.KEY_FLAT_FILE,
        )

    @classmethod
    def get_dark_filename(cls, headers, missing_ok: bool = False):
        return cls.get_headers_filename(
            headers=headers,
            folder_key=cls.KEY_DARK_FOLDER,
            file_key=cls.KEY_DARK_FILE,
            missing_ok=missing_ok,
        )
