"""Method for sharing global sensor and port configuration values between
modules.
"""
import serial
portdefaults = {
    "port_baud": 9600,
    "port_stopbits": serial.STOPBITS_ONE,
    "port_parity": serial.PARITY_NONE,
    "port_timeout":  0.01,
    "virtual": False,
    "delimiter": r"\s",
    "encoding": "UTF-8"
    }
"""dict: default parameters passed to the :class:`serial.Serial` constructor for
communicating with the serial port.
"""

def _get_parser(config):
    """Returns a :class:`~configparser.ConfigParser` instance for the specified
    file.

    Args:
        config (ConfigParser): instance from which to extract the sensor list
          and port information. `str` is also allowed, in which case it
          should be the path to the config file to load.
    """
    try:
        from configparser import ConfigParser
    except ImportError: # pragma: no cover
        #Renaming of modules to lower case in python 3.
        from ConfigParser import ConfigParser

    if isinstance(config, str):
        parser = ConfigParser()
        parser.readfp(open(config))    
    else: # pragma: no cover
        parser = config               

    return parser
        
_sensors = {}
"""dict: keys are sensor names, values are :class:`liveserial.config.Sensor`
instances.
"""
_sensors_parsed = False
"""bool: when True, we have already scanned the config file for sensor
settings.
"""
def _load_sensors(config):
    """Loads all the sensors from the specified config file.

    Args:
        config (ConfigParser): instance from which to extract the sensor list.
          `str` is also allowed, in which case it
          should be the path to the config file to load.
    """
    global _sensors, _sensors_parsed
    if not _sensors_parsed:
        parser = _get_parser(config)
        if parser is not None:
            #Now that we have the thread, we can add configuration for each of the
            #sensors in the config file.
            from fnmatch import fnmatch
            for section in parser.sections():
                if fnmatch(section, "sensor.*"):
                    name = section[len("sensor."):]
                    _sensors[name] = Sensor(None,name,**dict(parser.items(section)))
                
        _sensors_parsed = True
    
def sensors(config, sensor=None, port=None, monitor=None):
    """Returns the list of :class:`liveserial.config.Sensor` instances that have
    the specified port.

    Args:
        config (ConfigParser): instance from which to extract the sensor list
          and port information. `str` is also allowed, in which case it
          should be the path to the config file to load.
        port (str): name of the port to configure for.
    """
    _load_sensors(config)
    global _sensors
    if port is not None:
        result = {}
        for sensor_n, instance in _sensors.items():
            if instance.port == port:
                instance.monitor = monitor
                if monitor is not None:
                    instance.port = monitor.port
                result[sensor_n] = instance
    elif sensor is not None:
        if sensor in _sensors:
            result = _sensors[sensor]
        else:
            result = None

    return result
                    
_ports = {}
"""dict: keys are port names, values are updated parameter dictionaries that can
be passed to the :class:`liveserial.monitor.ComMonitorThread` constructor.
"""
_ports_parsed = False
"""bool: when True, we have already examined the config file for port settings.
"""
def _load_ports(config):
    """Loads the configured port information from the specified configuraton.
    """
    global _ports, _ports_parsed
    if not _ports_parsed:
        parser = _get_parser(config)
        if parser is None: # pragma: no cover
            _ports_parsed = True
            return
        
        from fnmatch import fnmatch
        for section in parser.sections():
            if fnmatch(section, "port.*"):
                name = section[len("port."):]
            else: # pragma: no cover
                continue

            params = portdefaults.copy()
            for option, value in params.items():
                #Override the value using the config value unless it doesn't
                #exist.
                if parser.has_option(section, option):
                    params[option] = parser.get(section, option)

            #Python's bool is interesting because bool('0') => True. So, we test
            #explicitly here for the option value the user set.
            import re
            if not isinstance(params["virtual"], bool):
                if re.match(r"\b\d+\b", params["virtual"]):
                    params["virtual"] = bool(int(params["virtual"]))
                elif re.match(r"[a-z]", params["virtual"][0], re.I):
                    params["virtual"] = params["virtual"][0].lower() == 't'

            _ports[name] = params
            
        _ports_parsed = True

def ports(config, port):
    """Returns the port configuration dictionary for the specified port name.

    Args:
        config (ConfigParser): instance from which to extract the port
          information. `str` is also allowed, in which case it
          should be the path to the config file to load.
        port (str): name of the port to return configuration for.
    """
    _load_ports(config)
    if port in _ports:
        return _ports[port]
    else: # pragma: no cover
        return portdefaults.copy()

_script = {}
"""dict: keys are command-line arguments usually accepted by the script when it
is run. Values are configured option values from the config file.
"""
_script_parsed = False
"""bool: when True, the script options have been parsed already.
"""
def script(config):
    """Returns the config options configured globally for the script.

    Args:
        config (ConfigParser): instance from which to extract the port
          information. `str` is also allowed, in which case it
          should be the path to the config file to load.
    """
    global _script, _script_parsed
    if not _script_parsed:
        parser = _get_parser(config)
        if parser is not None:
            for section in parser.sections():
                if section == "global":
                    _script = dict(parser.items("global"))

            #We also need to handle the types, since all the options are just
            #strings by default.
            from liveserial.livemon import script_options
            for name in _script:
                optname = "-{}".format(name)
                if optname in script_options and "type" in script_options[optname]:
                    caster = script_options[optname]["type"]
                    _script[name] = caster(_script[name])
        _script_parsed = True
                
    return _script
    
def _config_split(value, delim, cast=None):
    """Splits the specified value using `delim` and optionally casting the
    resulting items.

    Args:
        value (str): config option to split.
        delim (str): string to split the option value on.
        cast (function): to apply to each item after the split operation.
    """
    if value is None:
        return
    
    if delim is None: # pragma: no cover
        vals = value.split()
    else:
        vals = value.split(delim)
        
    if cast is not None: