# This Python file uses the following encoding: utf-8
# Etienne Dumur <etienne.dumur@gmail.com>, september 2020
# Jiaqi Cai <jiaqic@mit.edu>, Oct., 2025
import os
import pandas as pd
import numpy as np
from datetime import date
from typing import Optional
from qcodes.instrument.base import Instrument

class BlueFors(Instrument):
    """
    This is the QCoDeS python driver to extract the temperature and pressure
    from a BlueFors fridge
    """

    def __init__(self, name                      : str,
                       folder_path               : str,
                       channel_vacuum_can        : int,
                       channel_pumping_line      : int,
                       channel_compressor_outlet : int,
                       channel_compressor_inlet  : int,
                       channel_mixture_tank      : int,
                       channel_venting_line      : int,
                       channel_50k_plate         : int,
                       channel_4k_plate          : int,
                       channel_still             : int,
                       channel_mixing_chamber    : int,
                       channel_magnet            : Optional[int] = None,
                       channel_fse               : Optional[int] = None,
                       **kwargs) -> None:
        """
        QCoDeS driver for BlueFors fridges.
        ! This driver get parameters from the fridge log files.
        ! It does not interact with the fridge electronics.

        Args:
        name: Name of the instrument.
        folder_path: Valid path toward the BlueFors log folder.
        channel_vacuum_can: channel of the vacuum can
        channel_pumping_line: channel of the pumping line.
        channel_compressor_outlet: channel of the compressor outlet.
        channel_compressor_inlet: channel of the compressor inlet.
        channel_mixture_tank: channel of the mixture tank.
        channel_venting_line: channel of the venting line.
        channel_50k_plate: channel of the 50k plate.
        channel_4k_plate: channel of the 4k plate.
        channel_still: channel of the still.
        channel_mixing_chamber: channel of the mixing chamber.
        channel_magnet: channel of the magnet (optional).
        channel_fse: channel of the FSE (optional).
        """

        super().__init__(name = name, **kwargs)

        self.folder_path = os.path.abspath(folder_path)

        self.add_parameter(name       = 'pressure_vacuum_can',
                           unit       = 'mBar',
                           get_parser = float,
                           get_cmd    = lambda: self.get_pressure(channel_vacuum_can),
                           docstring  = 'Pressure of the vacuum can',
                           )

        self.add_parameter(name       = 'pressure_pumping_line',
                           unit       = 'mBar',
                           get_parser = float,
                           get_cmd    = lambda: self.get_pressure(channel_pumping_line),
                           docstring  = 'Pressure of the pumping line',
                           )

        self.add_parameter(name       = 'pressure_compressor_outlet',
                           unit       = 'mBar',
                           get_parser = float,
                           get_cmd    = lambda: self.get_pressure(channel_compressor_outlet),
                           docstring  = 'Pressure of the compressor outlet',
                           )

        self.add_parameter(name       = 'pressure_compressor_inlet',
                           unit       = 'mBar',
                           get_parser = float,
                           get_cmd    = lambda: self.get_pressure(channel_compressor_inlet),
                           docstring  = 'Pressure of the compressor inlet',
                           )

        self.add_parameter(name       = 'pressure_mixture_tank',
                           unit       = 'mBar',
                           get_parser = float,
                           get_cmd    = lambda: self.get_pressure(channel_mixture_tank),
                           docstring  = 'Pressure of the mixture tank',
                           )

        self.add_parameter(name       = 'pressure_venting_line',
                           unit       = 'mBar',
                           get_parser = float,
                           get_cmd    = lambda: self.get_pressure(channel_venting_line),
                           docstring  = 'Pressure of the venting line',
                           )

        self.add_parameter(name       = 'temperature_50k_plate',
                           unit       = 'K',
                           get_parser = float,
                           get_cmd    = lambda: self.get_temperature(channel_50k_plate),
                           docstring  = 'Temperature of the 50K plate',
                           )

        self.add_parameter(name       = 'temperature_4k_plate',
                           unit       = 'K',
                           get_parser = float,
                           get_cmd    = lambda: self.get_temperature(channel_4k_plate),
                           docstring  = 'Temperature of the 4K plate',
                           )

        if channel_magnet is not None:
            self.add_parameter(name       = 'temperature_magnet',
                               unit       = 'K',
                               get_parser = float,
                               get_cmd    = lambda: self.get_temperature(channel_magnet),
                               docstring  = 'Temperature of the magnet',
                               )

        if channel_fse is not None:
            self.add_parameter(name       = 'temperature_fse',
                               unit       = 'K',
                               get_parser = float,
                               get_cmd    = lambda: self.get_temperature(channel_fse),
                               docstring  = 'Temperature of the FSE',
                               )

        self.add_parameter(name       = 'temperature_still',
                           unit       = 'K',
                           get_parser = float,
                           get_cmd    = lambda: self.get_temperature(channel_still),
                           docstring  = 'Temperature of the still',
                           )

        self.add_parameter(name       = 'temperature_mixing_chamber',
                           unit       = 'K',
                           get_parser = float,
                           get_cmd    = lambda: self.get_temperature(channel_mixing_chamber),
                           docstring  = 'Temperature of the mixing chamber',
                           )

        self.connect_message()


    def get_temperature(self, channel: int) -> float:
        """
        Return the last registered temperature of the current day for the
        channel.

        Args:
            channel (int): Channel from which the temperature is extracted.

        Returns:
            temperature (float): Temperature of the channel in Kelvin.
        """

        folder_name = date.today().strftime("%y-%m-%d")
        file_path = os.path.join(self.folder_path, folder_name, 'CH'+str(channel)+' T '+folder_name+'.log')

        try:
            df = pd.read_csv(file_path,
                                delimiter = ',',
                                names     = ['date', 'time', 'y'],
                                header    = None)

            try:
                # There is a space before the day for old BlueFors Control Sofware versions
                date_time = pd.to_datetime(
                    df["date"] + "-" + df["time"], format=" %d-%m-%y-%H:%M:%S"
                )
            except Exception:
                # There is no space before the day with BlueFors Control Software v2.2
                date_time = pd.to_datetime(
                    df["date"] + "-" + df["time"], format="%d-%m-%y-%H:%M:%S"
                )
            df["date_time"] = date_time
            df.set_index("date_time", inplace=True)
            df.sort_index(inplace=True)
            return df.iloc[-1]["y"]
        except (PermissionError, OSError) as err:
            self.log.warning('Cannot access log file: {}. Returning np.nan instead of the temperature value.'.format(err))
            return np.nan
        except IndexError as err:
            self.log.warning('Cannot parse log file: {}. Returning np.nan instead of the temperature value.'.format(err))
            return np.nan


    def get_pressure(self, channel: int) -> float:
        """
        Return the last registered pressure of the current day for the
        channel.

        Args:
            channel (int): Channel from which the pressure is extracted.

        Returns:
            pressure (float): Pressure of the channel in mBar.
        """

        folder_name = date.today().strftime("%y-%m-%d")
        file_path = os.path.join(self.folder_path, folder_name, 'maxigauge '+folder_name+'.log')

        try:
            df = pd.read_csv(file_path,
                            delimiter=',',
                            names=['date', 'time',
                                    'ch1_name', 'ch1_void1', 'ch1_status', 'ch1_pressure', 'ch1_void2', 'ch1_void3',
                                    'ch2_name', 'ch2_void1', 'ch2_status', 'ch2_pressure', 'ch2_void2', 'ch2_void3',
                                    'ch3_name', 'ch3_void1', 'ch3_status', 'ch3_pressure', 'ch3_void2', 'ch3_void3',
                                    'ch4_name', 'ch4_void1', 'ch4_status', 'ch4_pressure', 'ch4_void2', 'ch4_void3',
                                    'ch5_name', 'ch5_void1', 'ch5_status', 'ch5_pressure', 'ch5_void2', 'ch5_void3',
                                    'ch6_name', 'ch6_void1', 'ch6_status', 'ch6_pressure', 'ch6_void2', 'ch6_void3',
                                    'void'],
                            header=None)

            df["date_time"] = pd.to_datetime(df['date']+'-'+df['time'], format='%d-%m-%y-%H:%M:%S')
            df.set_index("date_time", inplace=True)
            df.sort_index(inplace=True)

            return df.iloc[-1]['ch'+str(channel)+'_pressure']
        except (PermissionError, OSError) as err:
            self.log.warning('Cannot access log file: {}. Returning np.nan instead of the pressure value.'.format(err))
            return np.nan
        except IndexError as err:
            self.log.warning('Cannot parse log file: {}. Returning np.nan instead of the pressure value.'.format(err))
            return np.nan
