import os
from glob import glob
from pathlib import Path
from configparser import ConfigParser
from frappy.errors import ConfigError
from frappy.config import Param


class Rack:
    configbase = Path('/home/l_samenv/.config/frappy_instruments')

    def __init__(self, modfactory):
        self.modfactory = modfactory
        instpath = self.configbase / os.environ['Instrument']
        sections = {}
        self.config = {}
        files = glob(str(instpath / '*.ini'))
        # TODO: why may we accept several rack configs?
        for filename in files:
            parser = ConfigParser()
            parser.optionxform = str
            parser.read([filename])
            for section in parser.sections():
                prev = sections.get(section)
                if prev:
                    raise ConfigError(f'duplicate {section} section in {filename} and {prev}')
                sections[section] = filename
                self.config.update(parser.items(section))
        if 'config' not in sections:
            raise ConfigError(f'no rack found in {instpath}')
        self.props = {}  # dict (<property>, <method>) of value
        self.mods = {}  # dict (<property>, <method>) of list of <cfg>
        self.ccu_uri = {}

    def set_props(self, mod, **kwds):
        for prop, method in kwds.items():
            value = self.props.get((prop, method))
            if value is None:
                # add mod to the list of cfgs to be fixed
                self.mods.setdefault((prop, method), []).append(mod)
            else:
                # set prop in current module
                if not mod.get(prop):  # do not override given and not empty property
                    mod[prop] = Param(value)

    def fix_props(self, method, **kwds):
        for prop, value in kwds.items():
            if (prop, method) in self.props:
                raise ConfigError(f'duplicate call to {method}()')
            self.props[prop, method] = value
            # set property in modules to be fixed
            for mod in self.mods.get((prop, method), ()):
                mod[prop] = Param(value)

    def lakeshore(self, ls_uri=None, io='ls_io', dev='ls', model='336', **kwds):
        Mod = self.modfactory
        self.fix_props('lakeshore', io=io, device=dev)
        self.ls_model = self.config.get('ls_model', model)
        self.ls_dev = dev
        ls_uri = ls_uri or self.config.get('ls_uri')
        Mod(io, cls=f'frappy_psi.lakeshore.IO{self.ls_model}',
            description='comm. to lakeshore in cc rack', uri=ls_uri)
        self.dev = Mod(dev, cls=f'frappy_psi.lakeshore.Device{self.ls_model}',
                       description='lakeshore in cc rack', io=io, curve_handling=True)

    def sensor(self, name, channel, calcurve, **kwds):
        Mod = self.modfactory
        kwds.setdefault('cls', f'frappy_psi.lakeshore.Sensor{self.ls_model}')
        kwds.setdefault('description', f'T sensor {name}')
        mod = Mod(name, channel=channel, calcurve=calcurve,
                  device=self.ls_dev, **kwds)
        self.set_props(mod, io='lakeshore', dev='lakeshore')

    def loop(self, name, channel, calcurve, output_module, **kwds):
        Mod = self.modfactory
        kwds.setdefault('cls', f'frappy_psi.lakeshore.Loop{self.ls_model}')
        kwds.setdefault('description', f'T loop {name}')
        Mod(name, channel=channel, calcurve=calcurve, output_module=output_module,
            device=self.ls_dev, **kwds)
        self.fix_props(f'heater({output_module})', description=f'heater for {name}')

    def heater(self, name, output_no, max_heater, resistance, **kwds):
        Mod = self.modfactory
        if output_no == 1:
            kwds.setdefault('cls', f'frappy_psi.lakeshore.MainOutput{self.ls_model}')
        elif output_no == 2:
            kwds.setdefault('cls', f'frappy_psi.lakeshore.SecondaryOutput{self.ls_model}')
        else:
            return
        kwds.setdefault('description', '')
        mod = Mod(name, max_heater=max_heater, resistance=resistance, **kwds)
        self.set_props(mod, io='lakeshore', device='lakeshore', description=f'heater({name})')

    def ccu(self, name=None, ccu_uri=None, ccu_io='ccu_io', args_for_io=None, **kwds):
        if args_for_io is None:
            args_for_io, kwds = kwds, {}
        prev_uri = self.ccu_uri.get(ccu_io)
        ccu_uri = ccu_uri or self.config.get('ccu_uri')
        if prev_uri:
            if prev_uri == ccu_uri:
                return kwds  # already configured
            raise ConfigError(f'rack.{name or "ccu"}: ccu_uri {prev_uri} does not match {ccu_uri}')
        self.ccu_uri[ccu_io] = ccu_uri
        self.modfactory(ccu_io, 'frappy_psi.ccu4.IO', 'comm. to CCU4', uri=ccu_uri, **args_for_io)
        return kwds

    def he(self, name='He_lev', ccu_io='ccu_io', **kwds):
        self.ccu('he', ccu_io=ccu_io, args_for_io={}, **kwds)
        self.modfactory(name, cls='frappy_psi.ccu4.HeLevel',
                        description='the He Level', io=ccu_io, **kwds)

    def n2(self, name='N2_lev', valve='N2_valve', upper='N2_upper', lower='N2_lower', ccu_io='ccu_io', **kwds):
        self.ccu('n2', ccu_io=ccu_io, args_for_io={}, **kwds)
        Mod = self.modfactory
        Mod(name, cls='frappy_psi.ccu4.N2Level',
            description='the N2 Level', io=ccu_io,
            valve=valve, upper=upper, lower=lower)
        Mod(valve, cls='frappy_psi.ccu4.N2FillValve',
            description='LN2 fill valve', io=ccu_io)
        Mod(upper, cls='frappy_psi.ccu4.N2TempSensor',
            description='upper LN2 sensor')
        Mod(lower, cls='frappy_psi.ccu4.N2TempSensor',
            description='lower LN2 sensor')

    def flow(self, hepump_uri=None, hepump_type=None, hepump_io='hepump_io',
                 hepump='hepump', hepump_mot='hepump_mot', hepump_valve='hepump_valve',
                 flow_sensor='flow_sensor', pump_pressure='pump_pressure', nv='nv',
                 ccu_io='ccu_io', **kwds):
        """creates needle valve and pump access if available"""
        kwds = self.ccu('flow', ccu_io=ccu_io, args_for_io={}, **kwds)
        Mod = self.modfactory
        hepump_type = hepump_type or self.config.get('hepump_type', 'no')
        Mod(nv, 'frappy_psi.ccu4.NeedleValveFlow', 'flow from flow sensor or pump pressure',
            flow_sensor=flow_sensor, pressure=pump_pressure, io=ccu_io, pump_type=hepump_type, **kwds)
        Mod(pump_pressure, 'frappy_psi.ccu4.Pressure', 'He pump pressure', io=ccu_io)
        if hepump_type == 'no':
            print('no pump, no flow meter - using flow from pressure alone')
            return
        hepump_uri = hepump_uri or self.config['hepump_uri']
        Mod(hepump_io, 'frappy.io.BytesIO', 'He pump connection', uri=hepump_uri)
        Mod(hepump, 'frappy_psi.hepump.HePump', 'He pump', pump_type=hepump_type,
            valvemotor=hepump_mot, valve=hepump_valve)
        Mod(hepump_mot, 'frappy_psi.hepump.Motor', 'He pump valve motor', io=hepump_io, maxcurrent=2.8)
        Mod(hepump_valve, 'frappy_psi.butterflyvalve.Valve', 'He pump valve', motor=hepump_mot)
        Mod(flow_sensor, 'frappy_psi.sensirion.FlowSensor', 'Flow Sensor', io=hepump_io, nsamples=160)

