"""
Wrapper around ConfigParser to manage testcases configuration.

:author: rsalveti@linux.vnet.ibm.com (Ricardo Salveti de Araujo)
"""

import re
import string
import types
from ConfigParser import ConfigParser
from StringIO import StringIO
from os import path

from autotest.client.shared import utils

__all__ = ['config_loader']


class config_loader:

    """
    Base class of the configuration parser
    """

    def __init__(self, cfg, tmpdir='/tmp', raise_errors=False):
        """
        Instantiate ConfigParser and provide the file like object that we'll
        use to read configuration data from.
        :param cfg: Where we'll get configuration data. It can be either:
                * A URL containing the file
                * A valid file path inside the filesystem
                * A string containing configuration data
        :param tmpdir: Where we'll dump the temporary conf files.
        :param raise_errors: Whether config value absences will raise
                ValueError exceptions.
        """
        # Base Parser
        self.parser = ConfigParser()
        # Raise errors when lacking values
        self.raise_errors = raise_errors
        # File is already a file like object
        if hasattr(cfg, 'read'):
            self.cfg = cfg
            self.parser.readfp(self.cfg)
        elif isinstance(cfg, types.StringTypes):
            # Config file is a URL. Download it to a temp dir
            if cfg.startswith('http') or cfg.startswith('ftp'):
                self.cfg = path.join(tmpdir, path.basename(cfg))
                utils.urlretrieve(cfg, self.cfg)
                self.parser.read(self.cfg)
            # Config is a valid filesystem path to a file.
            elif path.exists(path.abspath(cfg)):
                if path.isfile(cfg):
                    self.cfg = path.abspath(cfg)
                    self.parser.read(self.cfg)
                else:
                    e_msg = 'Invalid config file path: %s' % cfg
                    raise IOError(e_msg)
            # Config file is just a string, convert it to a python file like
            # object using StringIO
            else:
                self.cfg = StringIO(cfg)
                self.parser.readfp(self.cfg)

    def get(self, section, option, default=None):
        """
        Get the value of a option.

        Section of the config file and the option name.
        You can pass a default value if the option doesn't exist.

        :param section: Configuration file section.
        :param option: Option we're looking after.
        :default: In case the option is not available and raise_errors is set
                to False, return the default.
        """
        if not self.parser.has_option(section, option):
            if self.raise_errors:
                raise ValueError('No value for option %s. Please check your '
                                 'config file "%s".' % (option, self.cfg))
            else:
                return default

        return self.parser.get(section, option)

    def set(self, section, option, value):
        """
        Set an option.

        This change is not persistent unless saved with 'save()'.
        """
        if not self.parser.has_section(section):
            self.parser.add_section(section)
        return self.parser.set(section, option, value)

    def remove(self, section, option):
        """
        Remove an option.
        """
        if self.parser.has_section(section):
            self.parser.remove_option(section, option)

    def save(self):
        """
        Save the configuration file with all modifications
        """
        if not self.cfg:
            return
        fileobj = file(self.cfg, 'w')
        try:
            self.parser.write(fileobj)
        finally:
            fileobj.close()

    def check(self, section):
        """
        Check if the config file has valid values
        """
        if not self.parser.has_section(section):
            return False, "Section not found: %s" % (section)

        options = self.parser.items(section)
        for i in range(options.__len__()):
            param = options[i][0]
            aux = string.split(param, '.')

            if aux.__len__ < 2:
                return False, "Invalid parameter syntax at %s" % (param)

            if not self.check_parameter(aux[0], options[i][1]):
                return False, "Invalid value at %s" % (param)

        return True, None

    def check_parameter(self, param_type, parameter):
        """
        Check if a option has a valid value
        """
        if parameter == '' or parameter is None:
            return False
        elif param_type == "ip" and self.__isipaddress(parameter):
            return True
        elif param_type == "int" and self.__isint(parameter):
            return True
        elif param_type == "float" and self.__isfloat(parameter):
            return True
        elif param_type == "str" and self.__isstr(parameter):
            return True

        return False

    def __isipaddress(self, parameter):
        """
        Verify if the ip address is valid

        :param ip String: IP Address
        :return: True if a valid IP Address or False
        """
        octet1 = "([1-9][0-9]{,1}|1[0-9]{2}|2[0-4][0-9]|25[0-5])"
        octet = "([0-9]{1,2}|1[0-9]{2}|2[0-4][0-9]|25[0-5])"
        pattern = "^" + octet1 + "\.(" + octet + "\.){2}" + octet + "$"
        if re.match(pattern, parameter) is None:
            return False
        else:
            return True

    def __isint(self, parameter):
        try:
            int(parameter)
        except Exception, e_stack:
            return False
        return True

    def __isfloat(self, parameter):
        try:
            float(parameter)
        except Exception, e_stack:
            return False
        return True

    def __isstr(self, parameter):
        try:
            str(parameter)
        except Exception, e_stack:
            return False
        return True
