from time import sleep

from slic.core.adjustable import Adjustable, PVAdjustable
from slic.core.device import Device, SimpleDevice
from slic.devices.general.motor import Motor
from slic.utils.hastyepics import get_pv as PV



class Attenuator(Device):

    def __init__(self, ID, energy_threshold=1500, name="Attenuator Aramis", process_time=1, **kwargs):
        super().__init__(ID, name=name, **kwargs)


        # self.motor_limits = motor_limits

        # self.motors = motors = LimitedMotors(ID, motor_limits)
        self.motor_status_pvs = [PVAdjustable(f"{ID}:MOTOR_{i}.DMOV") for i in [1,2,3,4,5,6]]

        # self.motors = LimitedMotors(ID, motor_limits)

        self.trans1st = Transmission(ID, process_time=process_time)
        self.trans3rd = Transmission(ID, process_time=process_time, third_order=True)
        self.energy = LimitedEnergy(ID, energy_threshold, process_time)
        self.foils = [Foil(ID, i+1) for i in range(6)]


    def get_current_transmission(self):
        return self.trans1st.get_current_value()

    def get_current_transmission_third_harmonic(self):
        return self.trans3rd.get_current_value()

    def set_transmission(self, value, energy=None):
        self.energy.set_target_value(energy)
        self.trans1st.set_target_value(value)

    def set_transmission_third_harmonic(self, value, energy=None):
        self.energy.set_target_value(energy)
        self.trans3rd.set_target_value(value)


    def set_foils_combination(self, combination: list, wait=False):
        assert len(combination) == 6, "Invalid combination entered"

        for value in combination:
            assert value in self.foils[0].values, "wrong value entered"


        for i, (foil,value) in enumerate(zip(self.foils,combination)):
            print(f"setting {repr(foil)} to position {value}")
            foil.set_target_value(value)

        if wait:
            self.foils[0].wait_for_motion()

            



class Transmission(PVAdjustable):

    def __init__(self, ID, third_order=False, check_motors=True, **kwargs):
        #self.motors = motors
        self.third_order = third_order
        self.motors = [Motor(f"{ID}:MOTOR_{i}") for i in [1,2,3,4,5,6]]

        self.check_motors = check_motors

        prefix = ID + ":"
        # Changed for new attenuator IOC
        pvname_setvalue = prefix + "UsrRec.TD"
        pvname_readback = prefix + ("UsrRec.TC3" if third_order else "UsrRec.TC1")
        super().__init__(pvname_setvalue, pvname_readback, **kwargs)

        self.pvnames.third_order_toggle = pvn = prefix + "UsrRec.HRM3"
        self.pvs.third_order_toggle = PV(pvn)


        pv_calculated = prefix + ("UsrRec.TR3" if third_order else "UsrRec.TR1")
        pv_current = prefix + ("UsrRec.TC3" if third_order else "UsrRec.TC1")

        self.calculated_trans = PVAdjustable(pv_calculated).get_current_value() 
        self.current_trans = PVAdjustable(pv_current).get_current_value()

    def set_target_value(self, *args, **kwargs):
        self.set_third_order_toggle()
        t = super().set_target_value(*args, **kwargs)
        self.wait_for_motion()
        return t

    def set_third_order_toggle(self):
        self.pvs.third_order_toggle.put(self.third_order)

    def wait_for_motion(self):
        sleep(1) # to be certain that motion has started
        while True:
            if not self.is_moving():
                break # motion is completed
            sleep(0.25)

    def is_moving(self):
        if self.check_motors:
            return any(m.is_moving() for m in self.motors)
        
        else:
            return (self.calculated_trans != self.current_trans)



class LimitedEnergy(PVAdjustable):

    def __init__(self, ID, threshold, wait_time):
        self.threshold = threshold
        self.wait_time = wait_time

        super().__init__(ID + ":UsrRec.ERY")

        prefix = ID + ":"
        self.pvnames.fel_energy = pvn = prefix + "ENY_CALC" # uses the new calculated energy from attenuator IOC
        self.pvs.fel_energy = PV(pvn)


    def set_target_value(self, value):
        threshold = self.threshold
        wait_time = self.wait_time

        while not value:
            value = self.pvs.fel_energy.get()
            if value is not None:
                if value >= threshold:
                    break

            print(f"Machine photon energy ({value} eV) is below {threshold} eV - waiting for the machine to recover...")
            value = None
            sleep(wait_time)

        super().set_target_value(value) # setting the energy is now instantanious, motors will still move afterwards.
        print(f"Set attenuator energy to {value} eV")


class Foil(PVAdjustable):
    
    def __init__(self, ID, number,check_motors=True):
        """ Foil with possible settings of 0,1,2,3 or 4.
        """

        assert 0 <= int(number) <= 6, "Invalid foil selected"
        super().__init__(ID + f":UsrRec.FI{number}")
        
        self.values = [0, 1, 2, 3, 4]

        prefix = ID + ":"

        self.motors = [Motor(f"{ID}:MOTOR_{i}") for i in [1,2,3,4,5,6]]
        self.check_motors = check_motors

        pv_calculated = prefix + "UsrRec.TR1"
        pv_current = prefix + "UsrRec.TC1"

        self.calculated_trans = PVAdjustable(pv_calculated).get_current_value() 
        self.current_trans = PVAdjustable(pv_current).get_current_value()

    def set_target_value(self, *args, **kwargs):
        t = super().set_target_value(*args, **kwargs).wait()
        self.wait_for_motion()
        return t

    def wait_for_motion(self):
        sleep(1) # to be certain that motion has started
        while True:
            if not self.is_moving():
                break # motion is completed
            sleep(0.25)

    def is_moving(self):
        if self.check_motors:
            return any(m.is_moving() for m in self.motors)
        
        else:
            return (self.calculated_trans != self.current_trans)
        

