#  Copyright 2012-2017, Intel Corporation, All Rights Reserved.
#
# This software is supplied under the terms of a license
# agreement or nondisclosure agreement with Intel Corp.
# and may not be copied or disclosed except in accordance
# with the terms of that agreement.
#
#  Author:  <NAME>

"""
Module containing the fully featured Params class, a positional only
ParamsPos class and the decorator class ParamsDrop which removes some
parameters.
"""

import shlex
import copy
import getopt
import re

import micp.common as micp_common

# available parameters type validators
XGEMM_VALIDATOR = 'gemm'
STREAM_VALIDATOR = 'stream'
SHOC_VALIDATOR = 'shoc'
LINPACK_VALIDATOR = 'linpack'
HPLINPACK_VALIDATOR = 'hplinpack'
HPCG_VALIDATOR = 'hpcg'
NO_VALIDATOR = 'none'

class MicpParamsHelpError(micp_common.MicpException):
    """request help message"""
    def micp_exit_code(self):
        return micp_common.E_PARSE

class UnknownParamError(micp_common.MicpException):
    """requested kernel parameters are incorrect"""
    def micp_exit_code(self):
        return micp_common.E_PARSE

class InvalidParamTypeError(micp_common.MicpException):
    """Will be raised to indicate a parameter doesn't have the right type"""
    def micp_exit_code(self):
        return micp_common.E_PARSE

class Params(object):
    """
    Stores the parameters for a particular run and will
    return the parameters in a variety of formats.
    """
    def __init__(self, params, paramNames, defaults={}, paramQuiet=[], paramValidatorName=NO_VALIDATOR):
        self._paramNames = list(paramNames)
        self._params = dict([(pn,None) for pn in paramNames])
        self._defaults = dict(defaults)
        self._paramQuiet = list(paramQuiet)


        if isinstance(params, str):
            try:
                params = shlex.split(params)
            except ValueError as err:
                message = ('ERROR: Unable to parse input parameters "{0}": {1}.')
                raise UnknownParamError(message.format(params, str(err)))

        is_short_option = re.compile('^-[a-zA-Z]$').match
        paramValidator = ParamTypeValidatorFactory(paramValidatorName)

        if not any([pp.startswith('--') or is_short_option(pp) for pp in params]):
            for pn, pp in zip(self._paramNames, params):
                self._params[pn] = paramValidator.validate_param(pn, pp)
        else:
            i = 0
            while i < (len(params)):
                if params[i].startswith('--'):
                    thisName = params[i][2:]
                    if thisName == 'help':
                        raise MicpParamsHelpError
                    if thisName not in self._paramNames:
                        raise UnknownParamError('Unknown kernel parameter: {0}'.format(thisName))
                    i += 1
                    if i < len(params) and not params[i].startswith('-'):
                        self._params[thisName] = paramValidator.validate_param(thisName, params[i])
                        i += 1
                    else:
                        self._params[thisName] = paramValidator.validate_param(thisName, '')
                elif is_short_option(params[i]):
                    thisFlag = params[i][1]
                    if thisFlag == 'h':
                        raise MicpParamsHelpError
                    thisName = [pn for pn in self._paramNames if pn.startswith(thisFlag)]
                    if len(thisName) == 0:
                        raise UnknownParamError('Unknown kernel flag: {0}'.format(thisFlag))
                    if len(thisName) > 1:
                        error = ('INTERNAL ERROR: Initializing with flags, but more than'
                                ' one parameter name starts with the same character {0}')
                        raise NameError(error.format(thisName))
                    thisName = thisName[0]
                    i += 1
                    if i < len(params) and not is_short_option(params[i]):
                        self._params[thisName] = paramValidator.validate_param(thisName, params[i])
                        i += 1
                    else:
                        self._params[thisName] = paramValidator.validate_param(thisName, '')
                else:
                    message = 'Malformed parameters, invalid token \'{0}\' found at position {1}'
                    raise UnknownParamError(message.format(params[i], i))


        for pp in paramNames:
            if self._params[pp] is None:
                try:
                    self._params[pp] = self._defaults[pp]
                except KeyError:
                    self._params[pp] = None

    def __str__(self):
        return ' '.join(['--' + pn + ' ' + self._params[pn]
                         for pn in self._paramNames
                         if self._params[pn] != None])

    def csv(self):
        return ', '.join([value_to_print(self._params[pn])
                          for pn in self._paramNames])

    def csv_header(self):
        return ', '.join(self._paramNames)

    def pos_list(self):
        return  [self._params[pn] if self._params[pn] != '' else '1'
                 for pn in self._paramNames
                 if self._params[pn] != None and pn not in self._paramQuiet]


    def pos_str(self):
        return ' '.join(self.pos_list())

    def value_list(self):
        return self.value_str().split()

    def value_str(self):
        return ' '.join(['--' + pn + ' ' + self._params[pn]
                         for pn in self._paramNames
                         if self._params[pn] != None and pn not in self._paramQuiet])

    def flag_list(self):
        return self.flag_str().split()

    def flag_str(self):
        return ' '.join(['-' + pn[0] + ' ' + self._params[pn]
                         for pn in self._paramNames
                         if self._params[pn] != None and pn not in self._paramQuiet])

    def num_param(self):
        return len(self.pos_list())

    def get_named(self, name):
        try:
            return self._params[name]
        except KeyError:
            raise NameError

    def set_named(self, name, value):
        if name in self._paramNames:
            self._params[name] = value
        else:
            raise NameError

class ParamsPos(object):
    """
    Class which implements the methods of Params that require only
    positional arguments.
    """

    def __init__(self, params):
        if type(params) == str:
            self._params = params.split()
        else:
            self._params = params

    def __str__(self):
        return self.pos_str()

    def csv(self):
        return ', '.join([value_to_print(param) for param in self.pos_list()])

    def csv_header(self):
        return ', '.join(self._paramNames)

    def pos_list(self):
        return list(self._params)

    def pos_str(self):
        return ' '.join(self._params)

    def num_param(self):
        return len(self.pos_list())

    def value_str(self):
        raise NotImplementedError

    def value_list(self):
        raise NotImplementedError

    def get_named(self, name):
        raise NotImplementedError

    def set_named(self, name, value):
        raise NotImplementedError

class ParamsGetopt(object):
    """
    Class which implements the methods of Params but allows for mixing
    of short form, long form and positional arguements as is done by
    POSIX getopt.
    """
    def __init__(self, params, paramNames, options, long_options=[], defaults={}):
        """
        INPUT PARAMETERS
        ----------------
        params:       A string or list of command line arguments.

        paramNames:   A list of tuples, the first element of each
                      tuple is the name of a parameter.  The other
                      tuple elements are any short form, long form, or
                      position index that are associated with that
                      parameter name.

        options:      Option string as is specified for getopt
                      e.g. 'hxy:z:'.  See getopt.gnu_getopt
                      documentation for details.

        long_options: Long option list as is specified for getopt
                      e.g. ['help', 'foobar'].  See getopt.gnu_getopt
                      documentation for details.

        defaults:     A dictionary mapping parameter names to default
                      values.

        PRIVATE ATTRIBUTES
        ------------------
        _optionMap:   maps an option (e.g. '--num_reps' or '-n' or 3)
                      to the parameter name
        _nameMap:     maps a parameter name to the long option, or if
                      no long option the short option or if no short
                      option the position index.
        _paramNames:  a list of parameter names
        _defaults:    maps from paramter name to default value

        """
        self._optionMap = {}
        self._nameMap = {}
        self._paramNames = []
        self._defaults = dict(defaults)
        if type(params) is str:
            params = shlex.split(params)
        try:
            # Use getopt to parse params
            self._opts, self._args = getopt.gnu_getopt(params, options, long_options)
        except getopt.GetoptError:
            if ' '.join(params).strip() in ('--help', '-h'):
                # Create help message
                message = ['    options: description, default']
                for pn in paramNames:
                    name = pn[0]
                    options = ' '.join([str(option) for option in pn[1:]])
                    if name in defaults:
                        message.append('    {0}: {1}, {2}'.format(options, name, defaults[name]))
                    else:
                        message.append('    {0}: {1}'.format(options, name))
                message = '\n'.join(message)
                raise MicpParamsHelpError(message)
            else:
                raise
        # parse paramNames, and create optionMap
        posIndex = 0
        for pn in paramNames:
            if type(pn) is str:
                self._optionMap[posIndex] = pn
                self._paramNames.append(pn)
                posIndex = posIndex + 1
            elif type(pn) in (list, tuple):
                name = pn[0]
                for option in pn[1:]:
                    self._optionMap[option] = name
                self._paramNames.append(name)
            else:
                raise TypeError('paramNames elements must be of type str, list or tuple')
        # Create inverse of optionMap: nameMap
        for option, name in self._optionMap.items():
            if name not in self._nameMap:
               self._nameMap[name] = option
            elif option.startswith('--') and (type(self._nameMap[name]) is int or not self._nameMap[name].startswith('--')):
               self._nameMap[name] = option
            elif type(self._nameMap[name]) is int:
                self._nameMap[name] = option
        # Remove defaults that params has specified
        defaultUsed = dict(defaults)
        for option, val in self._opts:
            name = self._optionMap[option]
            try:
                defaultUsed.pop(name)
            except KeyError:
                pass
        for ii in range(len(self._args)):
            try:
                defaultUsed.pop(self._optionMap[ii])
            except KeyError:
                pass
        # Substitute in default values
        if defaultUsed:
            argsEnum = zip(range(len(self._args)), self._args)
            for name, value in defaultUsed.items():
                option = self._nameMap[name]
                if type(option) is int:
                    argsEnum.append((option, value))
                else:
                    self._opts.append((option, value))
            if argsEnum:
                argsEnum.sort()
                position, self._args = zip(*argsEnum)
                if position != tuple(range(len(position))):
                    raise RuntimeError('Default positional parameters not given in sequence')
        # Check to make sure that each variable name is specified only once
        found = [False for ii in range(self.num_param())]
        for option, val in self._opts:
            name = self._optionMap[option]
            index = self._paramNames.index(name)
            if found[index] == True:
                raise RuntimeError('Parameter named {0} is specified twice: {1}'.format(name, self.__str__()))
            found[index] = True

    def __str__(self):
        """
        Returns a string of the command line arguements.
        """
        result = ['{0} {1}'.format(option, val)
                  for option, val in self._opts
                  if val is not None]
        result.extend(self._args)
        return ' '.join(result)

    def csv(self):
        """
        Returns a comma separated list of the parameter values in the
        order of the parameter names.
        """
        result = ['' for ii in range(len(self._paramNames))]
        options, vals = [list(x) for x in zip(*self._opts)]
        options.extend(range(len(self._args)))
        vals.extend(self._args)
        for option, val in zip(options, vals):
            name = self._optionMap[option]
            result[self._paramNames.index(name)] = val
        return ', '.join(result)

    def csv_header(self):
        """
        Returns a comma separated list of the parameter names.
        """
        return ', '.join(self._paramNames)

    def num_param(self):
        """
        Return the total number of parameters allowed (not the number
        set).
        """
        return len(self._paramNames)

    def get_named(self, name):
        """
        Return the value of a parameter given its parameter name.
        """
        for option, val in self._opts:
            if self._optionMap[option] == name:
                return val
        for index, arg in zip(range(len(self._args)), self._args):
            if self._optionMap[index] == name:
                return arg
        return None

    def set_named(self, name, value):
        """
        Set the value of a parameter given its parameter name.
        """
        optsEnum = zip(*self._opts)
        optsEnum.append(range(len(self._opts)))
        optsEnum = zip(*optsEnum)
        for option, oldVal, ii in optsEnum:
            if self._optionMap[option] == name:
                self._opts[ii] = (option, value)
                return
        for ii in range(len(self._args)):
            if self._optionMap[ii] == name:
                self._args[ii] = value
                return
        option = self._nameMap[name]
        if type(option) is int:
            self._args.insert(option, value)
        else:
            self._opts.append((option, value))

class ParamsDrop(object):
    """
    Decorator class for the Params and ParamsPos classes which will
    drop the last parameters.
    """
    def __init__(self, params, numDrop, paramMax):
        self._params = copy.deepcopy(params)
        if numDrop < 1:
            raise ValueError('numDrop must be greater than 0')
        self._numDrop = numDrop
        if type(params) != ParamsPos:
            self._params._paramQuiet = params._paramNames[paramMax:paramMax + numDrop]

    def __str__(self):
        return self._params.__str__()

    def csv(self):
        return ', '.join([value_to_print(param) for param in self.pos_list()])

    def csv_header(self):
        return ', '.join(self._params._paramNames[:-self._numDrop])

    def pos_list(self):
        return self._params.pos_list()

    def pos_str(self):
        return ' '.join(self._params.pos_str().split())

    def value_list(self):
        return self._params.value_list()

    def value_str(self):
        return self._params.value_str()

    def flag_list(self):
        return self._params.flag_list()

    def flag_str(self):
        return self._params.flag_str()

    def num_param(self):
        return len(self.pos_list())

    def get_named(self, name):
        return self._params.get_named(name)

    def set_named(self, name, value):
        self._params.set_named(name, value)



def value_to_print(value):
    if value is None:
        return 'False'
    elif value == '':
        return 'True'
    else:
        return value

class ParamTypeValidator(object):
    """Abstract class, concrete classes should derive and populate
    the dictionary _validators properly. This dictionary maps the
    name of the parameters to a function that can be used to validate
    the correct type of given parameter. e.g.

        self._validators = {'number_of_iterations' : is_integer}

    Where is_integer is a function that takes a single parameter (the
    value) and raises a ValueError exception if value is not an integer.
    """

    def __init__(self):
        """class cannot be instantiated"""
        raise NotImplementedError("Abstract Class")

    def validate_param(self, param, value):
        """receives the name of the parameter to validate and the value that
        is intended to be used, on success validate_param() will return the
        same value otherwise it will raise an InvalidParamTypeError
        exception."""

        if param not in self._validators.keys():
            message = ('INTERNAL ERROR: _validator dictionary was not'
                       ' populated correctly, missing key: "{0}"')
            raise RuntimeError(message.format(param))

        param_type_validator = self._validators[param]
        try:
            param_type_validator(value)
        except ValueError as err:
            message = 'Invalid type for {0}\'s parameter "{1}", {2}'
            raise InvalidParamTypeError(message.format(self.name, param, str(err)))

        # this point will only be reached if the type validator succeeds
        return value


class NoParamTypeValidator(ParamTypeValidator):
    """do not perform any check on the parameters type"""
    def __init__(self):
        """override base class constructor,
        nothing to be done in this case"""
        pass

    def validate_param(self, param, value):
        """override validate_param() behavior to """
        return value


class GEMMParamTypeValidator(ParamTypeValidator):
    """Parameter type validator class for SGEMM and DGEMM kernels"""
    def __init__(self):
        self.name = 'XGEMM'
        valid_mmodes = 'NN|NT|TN|TT|0|1|2|3'
        self._validators = {
            'i_num_rep' : micp_common.unsigned_int,
            'n_num_thread' : micp_common.signed_int,
            'm_mode' : lambda param: micp_common.custom_type(param, valid_mmodes),
            # M, N and K accept also -1 which means default value
            'M_size' : micp_common.unsigned_int,
            'N_size' : micp_common.unsigned_int,
            'K_size' : micp_common.unsigned_int}


class LinpackParamTypeValidator(ParamTypeValidator):
    """Parameter type validator class for the linpack kernel"""
    def __init__(self):
        self.name = 'linpack'
        self._validators = {
            'omp_num_threads' : micp_common.signed_int,
            'matrix_size' : micp_common.unsigned_int,
            'num_rep' : micp_common.unsigned_int,
            'lead_dim': micp_common.unsigned_int}


class StreamParamTypeValidator(ParamTypeValidator):
    """Parameter type validator class for the STREAM kernel"""
    def __init__(self):
        self.name = 'STREAM'
        self._validators = {'omp_num_threads' : micp_common.unsigned_int}


class SHOCParamTypeValidator(ParamTypeValidator):
    """Parameter type validator class for shoc_download and shoc_readback"""
    def __init__(self):
        self.name = 'SHOC'
        self._validators = {
            'target' : micp_common.unsigned_int,
            'passes': micp_common.unsigned_int,
            'nopinned' : micp_common.no_args}


class HPLinpackParamTypeValidator(ParamTypeValidator):
    """Parameter type validator class for the hplinpack kernel"""
    def __init__(self):
        self.name = 'hplinpack'
        self._validators = {
            'problem_size' : micp_common.unsigned_int,
            'block_size' : micp_common.unsigned_int,
            'hpl_numthreads': micp_common.unsigned_int}


class HPCGParamTypeValidator(ParamTypeValidator):
    """Parameter type validator class for the hplinpack kernel"""
    def __init__(self):
        self.name = 'hpcg'
        self._validators = {
            'problem_size' : micp_common.unsigned_int,
            'time' : micp_common.unsigned_int,
            'omp_num_threads': micp_common.unsigned_int}


def ParamTypeValidatorFactory(name):
    """Factory to instantiate ParamTypeValidator objects"""
    if name == NO_VALIDATOR:
        return NoParamTypeValidator()

    if name == XGEMM_VALIDATOR:
        return GEMMParamTypeValidator()

    if name == SHOC_VALIDATOR:
        return SHOCParamTypeValidator()

    if name == LINPACK_VALIDATOR:
        return LinpackParamTypeValidator()

    if name == STREAM_VALIDATOR:
        return StreamParamTypeValidator()

    if name == HPLINPACK_VALIDATOR:
        return HPLinpackParamTypeValidator()

    if name == HPCG_VALIDATOR:
        return HPCGParamTypeValidator()

    raise ValueError('INTERNAL ERROR: unknown validator "{0}"'.format(name))

#  Copyright 2012-2017, Intel Corporation, All Rights Reserved.
#
# This software is supplied under the terms of a license
# agreement or nondisclosure agreement with Intel Corp.
# and may not be copied or disclosed except in accordance
# with the terms of that agreement.
#
#  Author:  <NAME>

"""
Module containing the fully featured Params class, a positional only
ParamsPos class and the decorator class ParamsDrop which removes some
parameters.
"""

import shlex
import copy
import getopt
import re

import micp.common as micp_common

# available parameters type validators
XGEMM_VALIDATOR = 'gemm'
STREAM_VALIDATOR = 'stream'
SHOC_VALIDATOR = 'shoc'
LINPACK_VALIDATOR = 'linpack'
HPLINPACK_VALIDATOR = 'hplinpack'
HPCG_VALIDATOR = 'hpcg'
NO_VALIDATOR = 'none'

class MicpParamsHelpError(micp_common.MicpException):
    """request help message"""
    def micp_exit_code(self):
        return micp_common.E_PARSE

class UnknownParamError(micp_common.MicpException):
    """requested kernel parameters are incorrect"""
    def micp_exit_code(self):
        return micp_common.E_PARSE

class InvalidParamTypeError(micp_common.MicpException):
    """Will be raised to indicate a parameter doesn't have the right type"""
    def micp_exit_code(self):
        return micp_common.E_PARSE

class Params(object):
    """
    Stores the parameters for a particular run and will
    return the parameters in a variety of formats.
    """
    def __init__(self, params, paramNames, defaults={}, paramQuiet=[], paramValidatorName=NO_VALIDATOR):
        self._paramNames = list(paramNames)
        self._params = dict([(pn,None) for pn in paramNames])
        self._defaults = dict(defaults)
        self._paramQuiet = list(paramQuiet)


        if isinstance(params, str):
            try:
                params = shlex.split(params)
            except ValueError as err:
                message = ('ERROR: Unable to parse input parameters "{0}": {1}.')
                raise UnknownParamError(message.format(params, str(err)))

        is_short_option = re.compile('^-[a-zA-Z]$').match
        paramValidator = ParamTypeValidatorFactory(paramValidatorName)

        if not any([pp.startswith('--') or is_short_option(pp) for pp in params]):
            for pn, pp in zip(self._paramNames, params):
                self._params[pn] = paramValidator.validate_param(pn, pp)
        else:
            i = 0
            while i < (len(params)):
                if params[i].startswith('--'):
                    thisName = params[i][2:]
                    if thisName == 'help':
                        raise MicpParamsHelpError
                    if thisName not in self._paramNames:
                        raise UnknownParamError('Unknown kernel parameter: {0}'.format(thisName))
                    i += 1
                    if i < len(params) and not params[i].startswith('-'):
                        self._params[thisName] = paramValidator.validate_param(thisName, params[i])
                        i += 1
                    else:
                        self._params[thisName] = paramValidator.validate_param(thisName, '')
                elif is_short_option(params[i]):
                    thisFlag = params[i][1]
                    if thisFlag == 'h':
                        raise MicpParamsHelpError
                    thisName = [pn for pn in self._paramNames if pn.startswith(thisFlag)]
                    if len(thisName) == 0:
                        raise UnknownParamError('Unknown kernel flag: {0}'.format(thisFlag))
                    if len(thisName) > 1:
                        error = ('INTERNAL ERROR: Initializing with flags, but more than'
                                ' one parameter name starts with the same character {0}')
                        raise NameError(error.format(thisName))
                    thisName = thisName[0]
                    i += 1
                    if i < len(params) and not is_short_option(params[i]):
                        self._params[thisName] = paramValidator.validate_param(thisName, params[i])
                        i += 1
                    else:
                        self._params[thisName] = paramValidator.validate_param(thisName, '')
                else:
                    message = 'Malformed parameters, invalid token \'{0}\' found at position {1}'
                    raise UnknownParamError(message.format(params[i], i))


        for pp in paramNames:
            if self._params[pp] is None:
                try:
                    self._params[pp] = self._defaults[pp]
                except KeyError:
                    self._params[pp] = None

    def __str__(self):
        return ' '.join(['--' + pn + ' ' + self._params[pn]
                         for pn in self._paramNames
                         if self._params[pn] != None])

    def csv(self):
        return ', '.join([value_to_print(self._params[pn])
                          for pn in self._paramNames])

    def csv_header(self):
        return ', '.join(self._paramNames)

    def pos_list(self):
        return  [self._params[pn] if self._params[pn] != '' else '1'
                 for pn in self._paramNames
                 if self._params[pn] != None and pn not in self._paramQuiet]


    def pos_str(self):
        return ' '.join(self.pos_list())

    def value_list(self):
        return self.value_str().split()

    def value_str(self):
        return ' '.join(['--' + pn + ' ' + self._params[pn]
                         for pn in self._paramNames
                         if self._params[pn] != None and pn not in self._paramQuiet])

    def flag_list(self):
        return self.flag_str().split()

    def flag_str(self):
        return ' '.join(['-' + pn[0] + ' ' + self._params[pn]
                         for pn in self._paramNames
                         if self._params[pn] != None and pn not in self._paramQuiet])

    def num_param(self):
        return len(self.pos_list())

    def get_named(self, name):
        try:
            return self._params[name]
        except KeyError:
            raise NameError

    def set_named(self, name, value):
        if name in self._paramNames:
            self._params[name] = value
        else:
            raise NameError

class ParamsPos(object):
    """
    Class which implements the methods of Params that require only
    positional arguments.
    """

    def __init__(self, params):
        if type(params) == str:
            self._params = params.split()
        else:
            self._params = params

    def __str__(self):
        return self.pos_str()

    def csv(self):
        return ', '.join([value_to_print(param) for param in self.pos_list()])

    def csv_header(self):
        return ', '.join(self._paramNames)

    def pos_list(self):
        return list(self._params)

    def pos_str(self):
        return ' '.join(self._params)

    def num_param(self):
        return len(self.pos_list())

    def value_str(self):
        raise NotImplementedError

    def value_list(self):
        raise NotImplementedError

    def get_named(self, name):
        raise NotImplementedError

    def set_named(self, name, value):
        raise NotImplementedError

class ParamsGetopt(object):
    """
    Class which implements the methods of Params but allows for mixing
    of short form, long form and positional arguements as is done by
    POSIX getopt.
    """
    def __init__(self, params, paramNames, options, long_options=[], defaults={}):
        """
        INPUT PARAMETERS
        ----------------
        params:       A string or list of command line arguments.

        paramNames:   A list of tuples, the first element of each
                      tuple is the name of a parameter.  The other
                      tuple elements are any short form, long form, or
                      position index that are associated with that
                      parameter name.

        options:      Option string as is specified for getopt
                      e.g. 'hxy:z:'.  See getopt.gnu_getopt
                      documentation for details.

        long_options: Long option list as is specified for getopt
                      e.g. ['help', 'foobar'].  See getopt.gnu_getopt
                      documentation for details.

        defaults:     A dictionary mapping parameter names to default
                      values.

        PRIVATE ATTRIBUTES
        ------------------
        _optionMap:   maps an option (e.g. '--num_reps' or '-n' or 3)
                      to the parameter name
        _nameMap:     maps a parameter name to the long option, or if
                      no long option the short option or if no short
                      option the position index.
        _paramNames:  a list of parameter names
        _defaults:    maps from paramter name to default value

        """
        self._optionMap = {}
        self._nameMap = {}
        self._paramNames = []
        self._defaults = dict(defaults)
        if type(params) is str:
            params = shlex.split(params)
        try:
            # Use getopt to parse params
            self._opts, self._args = getopt.gnu_getopt(params, options, long_options)
        except getopt.GetoptError:
            if ' '.join(params).strip() in ('--help', '-h'):
                # Create help message
                message = ['    options: description, default']
                for pn in paramNames:
                    name = pn[0]
                    options = ' '.join([str(option) for option in pn[1:]])
                    if name in defaults:
                        message.append('    {0}: {1}, {2}'.format(options, name, defaults[name]))
                    else:
                        message.append('    {0}: {1}'.format(options, name))
                message = '\n'.join(message)
                raise MicpParamsHelpError(message)
            else:
                raise
        # parse paramNames, and create optionMap
        posIndex = 0
        for pn in paramNames:
            if type(pn) is str:
                self._optionMap[posIndex] = pn
                self._paramNames.append(pn)
                posIndex = posIndex + 1
            elif type(pn) in (list, tuple):
                name = pn[0]
                for option in pn[1:]:
                    self._optionMap[option] = name
                self._paramNames.append(name)
            else:
                raise TypeError('paramNames elements must be of type str, list or tuple')
        # Create inverse of optionMap: nameMap
        for option, name in self._optionMap.items():
            if name not in self._nameMap:
               self._nameMap[name] = option
            elif option.startswith('--') and (type(self._nameMap[name]) is int or not self._nameMap[name].startswith('--')):
               self._nameMap[name] = option
            elif type(self._nameMap[name]) is int:
                self._nameMap[name] = option
        # Remove defaults that params has specified
        defaultUsed = dict(defaults)
        for option, val in self._opts:
            name = self._optionMap[option]
            try:
                defaultUsed.pop(name)
            except KeyError:
                pass
        for ii in range(len(self._args)):
            try:
                defaultUsed.pop(self._optionMap[ii])
            except KeyError:
                pass
        # Substitute in default values
        if defaultUsed:
            argsEnum = zip(range(len(self._args)), self._args)
            for name, value in defaultUsed.items():
                option = self._nameMap[name]
                if type(option) is int:
                    argsEnum.append((option, value))
                else:
                    self._opts.append((option, value))
            if argsEnum:
                argsEnum.sort()
                position, self._args = zip(*argsEnum)
                if position != tuple(range(len(position))):
                    raise RuntimeError('Default positional parameters not given in sequence')
        # Check to make sure that each variable name is specified only once
        found = [False for ii in range(self.num_param())]
        for option, val in self._opts:
            name = self._optionMap[option]
            index = self._paramNames.index(name)
            if found[index] == True:
                raise RuntimeError('Parameter named {0} is specified twice: {1}'.format(name, self.__str__()))
            found[index] = True

    def __str__(self):
        """
        Returns a string of the command line arguements.
        """
        result = ['{0} {1}'.format(option, val)
                  for option, val in self._opts
                  if val is not None]
        result.extend(self._args)
        return ' '.join(result)

    def csv(self):
        """
        Returns a comma separated list of the parameter values in the
        order of the parameter names.
        """
        result = ['' for ii in range(len(self._paramNames))]
        options, vals = [list(x) for x in zip(*self._opts)]
        options.extend(range(len(self._args)))
        vals.extend(self._args)
        for option, val in zip(options, vals):
            name = self._optionMap[option]
            result[self._paramNames.index(name)] = val
        return ', '.join(result)

    def csv_header(self):
        """
        Returns a comma separated list of the parameter names.
        """
        return ', '.join(self._paramNames)

    def num_param(self):
        """
        Return the total number of parameters allowed (not the number
        set).
        """
        return len(self._paramNames)

    def get_named(self, name):
        """
        Return the value of a parameter given its parameter name.
        """
        for option, val in self._opts:
            if self._optionMap[option] == name:
                return val
        for index, arg in zip(range(len(self._args)), self._args):
            if self._optionMap[index] == name:
                return arg
        return None

    def set_named(self, name, value):
        """
        Set the value of a parameter given its parameter name.
        """
        optsEnum = zip(*self._opts)
        optsEnum.append(range(len(self._opts)))
        optsEnum = zip(*optsEnum)
        for option, oldVal, ii in optsEnum:
            if self._optionMap[option] == name:
                self._opts[ii] = (option, value)
                return
        for ii in range(len(self._args)):
            if self._optionMap[ii] == name:
                self._args[ii] = value
                return
        option = self._nameMap[name]
        if type(option) is int:
            self._args.insert(option, value)
        else:
            self._opts.append((option, value))

class ParamsDrop(object):
    """
    Decorator class for the Params and ParamsPos classes which will
    drop the last parameters.
    """
    def __init__(self, params, numDrop, paramMax):
        self._params = copy.deepcopy(params)
        if numDrop < 1:
            raise ValueError('numDrop must be greater than 0')
        self._numDrop = numDrop
        if type(params) != ParamsPos:
            self._params._paramQuiet = params._paramNames[paramMax:paramMax + numDrop]

    def __str__(self):
        return self._params.__str__()

    def csv(self):
        return ', '.join([value_to_print(param) for param in self.pos_list()])

    def csv_header(self):
        return ', '.join(self._params._paramNames[:-self._numDrop])

    def pos_list(self):
        return self._params.pos_list()

    def pos_str(self):
        return ' '.join(self._params.pos_str().split())

    def value_list(self):
        return self._params.value_list()

    def value_str(self):
        return self._params.value_str()

    def flag_list(self):
        return self._params.flag_list()

    def flag_str(self):
        return self._params.flag_str()

    def num_param(self):
        return len(self.pos_list())

    def get_named(self, name):
        return self._params.get_named(name)

    def set_named(self, name, value):
        self._params.set_named(name, value)



def value_to_print(value):
    if value is None:
        return 'False'
    elif value == '':
        return 'True'
    else:
        return value

class ParamTypeValidator(object):
    """Abstract class, concrete classes should derive and populate
    the dictionary _validators properly. This dictionary maps the
    name of the parameters to a function that can be used to validate
    the correct type of given parameter. e.g.

        self._validators = {'number_of_iterations' : is_integer}

    Where is_integer is a function that takes a single parameter (the
    value) and raises a ValueError exception if value is not an integer.
    """

    def __init__(self):
        """class cannot be instantiated"""
        raise NotImplementedError("Abstract Class")

    def validate_param(self, param, value):
        """receives the name of the parameter to validate and the value that
        is intended to be used, on success validate_param() will return the
        same value otherwise it will raise an InvalidParamTypeError
        exception."""

        if param not in self._validators.keys():
            message = ('INTERNAL ERROR: _validator dictionary was not'
                       ' populated correctly, missing key: "{0}"')
            raise RuntimeError(message.format(param))

        param_type_validator = self._validators[param]
        try:
            param_type_validator(value)
        except ValueError as err:
            message = 'Invalid type for {0}\'s parameter "{1}", {2}'
            raise InvalidParamTypeError(message.format(self.name, param, str(err)))

        # this point will only be reached if the type validator succeeds
        return value


class NoParamTypeValidator(ParamTypeValidator):
    """do not perform any check on the parameters type"""
    def __init__(self):
        """override base class constructor,
        nothing to be done in this case"""
        pass

    def validate_param(self, param, value):
        """override validate_param() behavior to """
        return value


class GEMMParamTypeValidator(ParamTypeValidator):
    """Parameter type validator class for SGEMM and DGEMM kernels"""
    def __init__(self):
        self.name = 'XGEMM'
        valid_mmodes = 'NN|NT|TN|TT|0|1|2|3'
        self._validators = {
            'i_num_rep' : micp_common.unsigned_int,
            'n_num_thread' : micp_common.signed_int,
            'm_mode' : lambda param: micp_common.custom_type(param, valid_mmodes),
            # M, N and K accept also -1 which means default value
            'M_size' : micp_common.unsigned_int,
            'N_size' : micp_common.unsigned_int,
            'K_size' : micp_common.unsigned_int}


class LinpackParamTypeValidator(ParamTypeValidator):
    """Parameter type validator class for the linpack kernel"""
    def __init__(self):
        self.name = 'linpack'
        self._validators = {
            'omp_num_threads' : micp_common.signed_int,
            'matrix_size' : micp_common.unsigned_int,
            'num_rep' : micp_common.unsigned_int,
            'lead_dim': micp_common.unsigned_int}


class StreamParamTypeValidator(ParamTypeValidator):
    """Parameter type validator class for the STREAM kernel"""
    def __init__(self):
        self.name = 'STREAM'
        self._validators = {'omp_num_threads' : micp_common.unsigned_int}


class SHOCParamTypeValidator(ParamTypeValidator):
    """Parameter type validator class for shoc_download and shoc_readback"""
    def __init__(self):
        self.name = 'SHOC'
        self._validators = {
            'target' : micp_common.unsigned_int,
            'passes': micp_common.unsigned_int,
            'nopinned' : micp_common.no_args}


class HPLinpackParamTypeValidator(ParamTypeValidator):
    """Parameter type validator class for the hplinpack kernel"""
    def __init__(self):
        self.name = 'hplinpack'
        self._validators = {
            'problem_size' : micp_common.unsigned_int,
            'block_size' : micp_common.unsigned_int,
            'hpl_numthreads': micp_common.unsigned_int}


class HPCGParamTypeValidator(ParamTypeValidator):
    """Parameter type validator class for the hplinpack kernel"""
    def __init__(self):
        self.name = 'hpcg'
        self._validators = {
            'problem_size' : micp_common.unsigned_int,
            'time' : micp_common.unsigned_int,
            'omp_num_threads': micp_common.unsigned_int}


def ParamTypeValidatorFactory(name):
    """Factory to instantiate ParamTypeValidator objects"""
    if name == NO_VALIDATOR:
        return NoParamTypeValidator()

    if name == XGEMM_VALIDATOR:
        return GEMMParamTypeValidator()

    if name == SHOC_VALIDATOR:
        return SHOCParamTypeValidator()

    if name == LINPACK_VALIDATOR:
        return LinpackParamTypeValidator()

    if name == STREAM_VALIDATOR:
        return StreamParamTypeValidator()

    if name == HPLINPACK_VALIDATOR:
        return HPLinpackParamTypeValidator()

    if name == HPCG_VALIDATOR:
        return HPCGParamTypeValidator()

    raise ValueError('INTERNAL ERROR: unknown validator "{0}"'.format(name))

#  Copyright 2012-2017, Intel Corporation, All Rights Reserved. # # This software is supplied under the terms of a license # agreement or nondisclosure agreement with Intel Corp. # and may not be copied or disclosed except in accordance # with the terms of that agreement. # #  Author:  <NAME> Module containing the fully featured Params class, a positional only ParamsPos class and the decorator class ParamsDrop which removes some parameters. # available parameters type validators request help message requested kernel parameters are incorrect Will be raised to indicate a parameter doesn't have the right type Stores the parameters for a particular run and will     return the parameters in a variety of formats. Class which implements the methods of Params that require only     positional arguments. Class which implements the methods of Params but allows for mixing     of short form, long form and positional arguements as is done by     POSIX getopt. INPUT PARAMETERS         ----------------         params:       A string or list of command line arguments.          paramNames:   A list of tuples, the first element of each                       tuple is the name of a parameter.  The other                       tuple elements are any short form, long form, or                       position index that are associated with that                       parameter name.          options:      Option string as is specified for getopt                       e.g. 'hxy:z:'.  See getopt.gnu_getopt                       documentation for details.          long_options: Long option list as is specified for getopt                       e.g. ['help', 'foobar'].  See getopt.gnu_getopt                       documentation for details.          defaults:     A dictionary mapping parameter names to default                       values.          PRIVATE ATTRIBUTES         ------------------         _optionMap:   maps an option (e.g. '--num_reps' or '-n' or 3)                       to the parameter name         _nameMap:     maps a parameter name to the long option, or if                       no long option the short option or if no short                       option the position index.         _paramNames:  a list of parameter names         _defaults:    maps from paramter name to default value # Use getopt to parse params # Create help message # parse paramNames, and create optionMap # Create inverse of optionMap: nameMap # Remove defaults that params has specified # Substitute in default values # Check to make sure that each variable name is specified only once Returns a string of the command line arguements. Returns a comma separated list of the parameter values in the         order of the parameter names. Returns a comma separated list of the parameter names. Return the total number of parameters allowed (not the number         set). Return the value of a parameter given its parameter name. Set the value of a parameter given its parameter name. Decorator class for the Params and ParamsPos classes which will     drop the last parameters. Abstract class, concrete classes should derive and populate     the dictionary _validators properly. This dictionary maps the     name of the parameters to a function that can be used to validate     the correct type of given parameter. e.g.          self._validators = {'number_of_iterations' : is_integer}      Where is_integer is a function that takes a single parameter (the     value) and raises a ValueError exception if value is not an integer. class cannot be instantiated receives the name of the parameter to validate and the value that         is intended to be used, on success validate_param() will return the         same value otherwise it will raise an InvalidParamTypeError         exception. # this point will only be reached if the type validator succeeds do not perform any check on the parameters type override base class constructor,         nothing to be done in this case override validate_param() behavior to Parameter type validator class for SGEMM and DGEMM kernels # M, N and K accept also -1 which means default value Parameter type validator class for the linpack kernel Parameter type validator class for the STREAM kernel Parameter type validator class for shoc_download and shoc_readback Parameter type validator class for the hplinpack kernel Parameter type validator class for the hplinpack kernel Factory to instantiate ParamTypeValidator objects