# Copyright (c) 2012 ARM Limited
# All rights reserved.
#
# The license below extends only to copyright in the software and shall
# not be construed as granting a license to any other intellectual
# property including but not limited to intellectual property relating
# to a hardware implementation of the functionality of the software
# licensed hereunder.  You may use the software subject to the license
# terms below provided that you ensure that this notice is replicated
# unmodified and in its entirety in all distributions of the software,
# modified or unmodified, in source code or in binary form.
#
# Copyright (c) 2004-2006 The Regents of The University of Michigan
# Copyright (c) 2010-2011 Advanced Micro Devices, Inc.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are
# met: redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer;
# redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in the
# documentation and/or other materials provided with the distribution;
# neither the name of the copyright holders nor the names of its
# contributors may be used to endorse or promote products derived from
# this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#
# Authors: Steve Reinhardt
#          Nathan Binkert
#          Gabe Black
#          Andreas Hansson

#####################################################################
#
# Parameter description classes
#
# The _params dictionary in each class maps parameter names to either
# a Param or a VectorParam object.  These objects contain the
# parameter description string, the parameter type, and the default
# value (if any).  The convert() method on these objects is used to
# force whatever value is assigned to the parameter to the appropriate
# type.
#
# Note that the default values are loaded into the class's attribute
# space when the parameter dictionary is initialized (in
# MetaSimObject._new_param()); after that point they aren't used.
#
#####################################################################

import copy
import datetime
import re
import sys
import time
import math

import proxy
import ticks
from util import *

def isSimObject(*args, **kwargs):
    return SimObject.isSimObject(*args, **kwargs)

def isSimObjectSequence(*args, **kwargs):
    return SimObject.isSimObjectSequence(*args, **kwargs)

def isSimObjectClass(*args, **kwargs):
    return SimObject.isSimObjectClass(*args, **kwargs)

allParams = {}

class MetaParamValue(type):
    def __new__(mcls, name, bases, dct):
        cls = super(MetaParamValue, mcls).__new__(mcls, name, bases, dct)
        assert name not in allParams
        allParams[name] = cls
        return cls


# Dummy base class to identify types that are legitimate for SimObject
# parameters.
class ParamValue(object):
    __metaclass__ = MetaParamValue


    # Generate the code needed as a prerequisite for declaring a C++
    # object of this type.  Typically generates one or more #include
    # statements.  Used when declaring parameters of this type.
    @classmethod
    def cxx_predecls(cls, code):
        pass

    # Generate the code needed as a prerequisite for including a
    # reference to a C++ object of this type in a SWIG .i file.
    # Typically generates one or more %import or %include statements.
    @classmethod
    def swig_predecls(cls, code):
        pass

    # default for printing to .ini file is regular string conversion.
    # will be overridden in some cases
    def ini_str(self):
        return str(self)

    # allows us to blithely call unproxy() on things without checking
    # if they're really proxies or not
    def unproxy(self, base):
        return self

# Regular parameter description.
class ParamDesc(object):
    def __init__(self, ptype_str, ptype, *args, **kwargs):
        self.ptype_str = ptype_str
        # remember ptype only if it is provided
        if ptype != None:
            self.ptype = ptype

        if args:
            if len(args) == 1:
                self.desc = args[0]
            elif len(args) == 2:
                self.default = args[0]
                self.desc = args[1]
            else:
                raise TypeError, 'too many arguments'

        if kwargs.has_key('desc'):
            assert(not hasattr(self, 'desc'))
            self.desc = kwargs['desc']
            del kwargs['desc']

        if kwargs.has_key('default'):
            assert(not hasattr(self, 'default'))
            self.default = kwargs['default']
            del kwargs['default']

        if kwargs:
            raise TypeError, 'extra unknown kwargs %s' % kwargs

        if not hasattr(self, 'desc'):
            raise TypeError, 'desc attribute missing'

    def __getattr__(self, attr):
        if attr == 'ptype':
            ptype = SimObject.allClasses[self.ptype_str]
            assert isSimObjectClass(ptype)
            self.ptype = ptype
            return ptype

        raise AttributeError, "'%s' object has no attribute '%s'" % \
              (type(self).__name__, attr)

    def convert(self, value):
        if isinstance(value, proxy.BaseProxy):
            value.set_param_desc(self)
            return value
        if not hasattr(self, 'ptype') and isNullPointer(value):
            # deferred evaluation of SimObject; continue to defer if
            # we're just assigning a null pointer
            return value
        if isinstance(value, self.ptype):
            return value
        if isNullPointer(value) and isSimObjectClass(self.ptype):
            return value
        return self.ptype(value)

    def cxx_predecls(self, code):
        code('#include <cstddef>')
        self.ptype.cxx_predecls(code)

    def swig_predecls(self, code):
        self.ptype.swig_predecls(code)

    def cxx_decl(self, code):
        code('${{self.ptype.cxx_type}} ${{self.name}};')

# Vector-valued parameter description.  Just like ParamDesc, except
# that the value is a vector (list) of the specified type instead of a
# single value.

class VectorParamValue(list):
    __metaclass__ = MetaParamValue
    def __setattr__(self, attr, value):
        raise AttributeError, \
              "Not allowed to set %s on '%s'" % (attr, type(self).__name__)

    def ini_str(self):
        return ' '.join([v.ini_str() for v in self])

    def getValue(self):
        return [ v.getValue() for v in self ]

    def unproxy(self, base):
        if len(self) == 1 and isinstance(self[0], proxy.AllProxy):
            return self[0].unproxy(base)
        else:
             return [v.unproxy(base) for v in self]

class SimObjectVector(VectorParamValue):
    # support clone operation
    def __call__(self, **kwargs):
        return SimObjectVector([v(**kwargs) for v in self])

    def clear_parent(self, old_parent):
        for v in self:
            v.clear_parent(old_parent)

    def set_parent(self, parent, name):
        if len(self) == 1:
            self[0].set_parent(parent, name)
        else:
            width = int(math.ceil(math.log(len(self))/math.log(10)))
            for i,v in enumerate(self):
                v.set_parent(parent, "%s%0*d" % (name, width, i))

    def has_parent(self):
        return reduce(lambda x,y: x and y, [v.has_parent() for v in self])

    # return 'cpu0 cpu1' etc. for print_ini()
    def get_name(self):
        return ' '.join([v._name for v in self])

    # By iterating through the constituent members of the vector here
    # we can nicely handle iterating over all a SimObject's children
    # without having to provide lots of special functions on
    # SimObjectVector directly.
    def descendants(self):
        for v in self:
            for obj in v.descendants():
                yield obj

    def get_config_as_dict(self):
        a = []
        for v in self:
            a.append(v.get_config_as_dict())
        return a

class VectorParamDesc(ParamDesc):
    # Convert assigned value to appropriate type.  If the RHS is not a
    # list or tuple, it generates a single-element list.
    def convert(self, value):
        if isinstance(value, (list, tuple)):
            # list: coerce each element into new list
            tmp_list = [ ParamDesc.convert(self, v) for v in value ]
        else:
            # singleton: coerce to a single-element list
            tmp_list = [ ParamDesc.convert(self, value) ]

        if isSimObjectSequence(tmp_list):
            return SimObjectVector(tmp_list)
        else:
            return VectorParamValue(tmp_list)

    def swig_module_name(self):
        return "%s_vector" % self.ptype_str

    def swig_predecls(self, code):
        code('%import "${{self.swig_module_name()}}.i"')

    def swig_decl(self, code):
        code('%module(package="m5.internal") ${{self.swig_module_name()}}')
        code('%{')
        self.ptype.cxx_predecls(code)
        code('%}')
        code()
        # Make sure the SWIGPY_SLICE_ARG is defined through this inclusion
        code('%include "std_container.i"')
        code()
        self.ptype.swig_predecls(code)
        code()
        code('%include "std_vector.i"')
        code()

        ptype = self.ptype_str
        cxx_type = self.ptype.cxx_type

        code('''\
%typemap(in) std::vector< $cxx_type >::value_type {
    if (SWIG_ConvertPtr($$input, (void **)&$$1, $$1_descriptor, 0) == -1) {
        if (SWIG_ConvertPtr($$input, (void **)&$$1,
                            $$descriptor($cxx_type), 0) == -1) {
            return NULL;
        }
    }
}

%typemap(in) std::vector< $cxx_type >::value_type * {
    if (SWIG_ConvertPtr($$input, (void **)&$$1, $$1_descriptor, 0) == -1) {
        if (SWIG_ConvertPtr($$input, (void **)&$$1,
                            $$descriptor($cxx_type *), 0) == -1) {
            return NULL;
        }
    }
}
''')

        code('%template(vector_$ptype) std::vector< $cxx_type >;')

    def cxx_predecls(self, code):
        code('#include <vector>')
        self.ptype.cxx_predecls(code)

    def cxx_decl(self, code):
        code('std::vector< ${{self.ptype.cxx_type}} > ${{self.name}};')

class ParamFactory(object):
    def __init__(self, param_desc_class, ptype_str = None):
        self.param_desc_class = param_desc_class
        self.ptype_str = ptype_str

    def __getattr__(self, attr):
        if self.ptype_str:
            attr = self.ptype_str + '.' + attr
        return ParamFactory(self.param_desc_class, attr)

    # E.g., Param.Int(5, "number of widgets")
    def __call__(self, *args, **kwargs):
        ptype = None
        try:
            ptype = allParams[self.ptype_str]
        except KeyError:
            # if name isn't defined yet, assume it's a SimObject, and
            # try to resolve it later
            pass
        return self.param_desc_class(self.ptype_str, ptype, *args, **kwargs)

Param = ParamFactory(ParamDesc)
VectorParam = ParamFactory(VectorParamDesc)

#####################################################################
#
# Parameter Types
#
# Though native Python types could be used to specify parameter types
# (the 'ptype' field of the Param and VectorParam classes), it's more
# flexible to define our own set of types.  This gives us more control
# over how Python expressions are converted to values (via the
# __init__() constructor) and how these values are printed out (via
# the __str__() conversion method).
#
#####################################################################

# String-valued parameter.  Just mixin the ParamValue class with the
# built-in str class.
class String(ParamValue,str):
    cxx_type = 'std::string'

    @classmethod
    def cxx_predecls(self, code):
        code('#include <string>')

    @classmethod
    def swig_predecls(cls, code):
        code('%include "std_string.i"')

    def getValue(self):
        return self

# superclass for "numeric" parameter values, to emulate math
# operations in a type-safe way.  e.g., a Latency times an int returns
# a new Latency object.
class NumericParamValue(ParamValue):
    def __str__(self):
        return str(self.value)

    def __float__(self):
        return float(self.value)

    def __long__(self):
        return long(self.value)

    def __int__(self):
        return int(self.value)

    # hook for bounds checking
    def _check(self):
        return

    def __mul__(self, other):
        newobj = self.__class__(self)
        newobj.value *= other
        newobj._check()
        return newobj

    __rmul__ = __mul__

    def __div__(self, other):
        newobj = self.__class__(self)
        newobj.value /= other
        newobj._check()
        return newobj

    def __sub__(self, other):
        newobj = self.__class__(self)
        newobj.value -= other
        newobj._check()
        return newobj

# Metaclass for bounds-checked integer parameters.  See CheckedInt.
class CheckedIntType(MetaParamValue):
    def __init__(cls, name, bases, dict):
        super(CheckedIntType, cls).__init__(name, bases, dict)

        # CheckedInt is an abstract base class, so we actually don't
        # want to do any processing on it... the rest of this code is
        # just for classes that derive from CheckedInt.
        if name == 'CheckedInt':
            return

        if not (hasattr(cls, 'min') and hasattr(cls, 'max')):
            if not (hasattr(cls, 'size') and hasattr(cls, 'unsigned')):
                panic("CheckedInt subclass %s must define either\n" \
                      "    'min' and 'max' or 'size' and 'unsigned'\n",
                      name);
            if cls.unsigned:
                cls.min = 0
                cls.max = 2 ** cls.size - 1
            else:
                cls.min = -(2 ** (cls.size - 1))
                cls.max = (2 ** (cls.size - 1)) - 1

# Abstract superclass for bounds-checked integer parameters.  This
# class is subclassed to generate parameter classes with specific
# bounds.  Initialization of the min and max bounds is done in the
# metaclass CheckedIntType.__init__.
class CheckedInt(NumericParamValue):
    __metaclass__ = CheckedIntType

    def _check(self):
        if not self.min <= self.value <= self.max:
            raise TypeError, 'Integer param out of bounds %d < %d < %d' % \
                  (self.min, self.value, self.max)

    def __init__(self, value):
        if isinstance(value, str):
            self.value = convert.toInteger(value)
        elif isinstance(value, (int, long, float, NumericParamValue)):
            self.value = long(value)
        else:
            raise TypeError, "Can't convert object of type %s to CheckedInt" \
                  % type(value).__name__
        self._check()

    @classmethod
    def cxx_predecls(cls, code):
        # most derived types require this, so we just do it here once
        code('#include "base/types.hh"')

    @classmethod
    def swig_predecls(cls, code):
        # most derived types require this, so we just do it here once
        code('%import "stdint.i"')
        code('%import "base/types.hh"')

    def getValue(self):
        return long(self.value)

class Int(CheckedInt):      cxx_type = 'int';      size = 32; unsigned = False
class Unsigned(CheckedInt): cxx_type = 'unsigned'; size = 32; unsigned = True

class Int8(CheckedInt):     cxx_type =   'int8_t'; size =  8; unsigned = False
class UInt8(CheckedInt):    cxx_type =  'uint8_t'; size =  8; unsigned = True
class Int16(CheckedInt):    cxx_type =  'int16_t'; size = 16; unsigned = False
class UInt16(CheckedInt):   cxx_type = 'uint16_t'; size = 16; unsigned = True
class Int32(CheckedInt):    cxx_type =  'int32_t'; size = 32; unsigned = False
class UInt32(CheckedInt):   cxx_type = 'uint32_t'; size = 32; unsigned = True
class Int64(CheckedInt):    cxx_type =  'int64_t'; size = 64; unsigned = False
class UInt64(CheckedInt):   cxx_type = 'uint64_t'; size = 64; unsigned = True

class Counter(CheckedInt):  cxx_type = 'Counter';  size = 64; unsigned = True
class Tick(CheckedInt):     cxx_type = 'Tick';     size = 64; unsigned = True
class TcpPort(CheckedInt):  cxx_type = 'uint16_t'; size = 16; unsigned = True
class UdpPort(CheckedInt):  cxx_type = 'uint16_t'; size = 16; unsigned = True

class Percent(CheckedInt):  cxx_type = 'int'; min = 0; max = 100

class Cycles(CheckedInt):
    cxx_type = 'Cycles'
    size = 64
    unsigned = True

    def getValue(self):
        from m5.internal.core import Cycles
        return Cycles(self.value)

class Float(ParamValue, float):
    cxx_type = 'double'

    def __init__(self, value):
        if isinstance(value, (int, long, float, NumericParamValue, Float)):
            self.value = float(value)
        else:
            raise TypeError, "Can't convert object of type %s to Float" \
                  % type(value).__name__

    def getValue(self):
        return float(self.value)

class MemorySize(CheckedInt):
    cxx_type = 'uint64_t'
    size = 64
    unsigned = True
    def __init__(self, value):
        if isinstance(value, MemorySize):
            self.value = value.value
        else:
            self.value = convert.toMemorySize(value)
        self._check()

class MemorySize32(CheckedInt):
    cxx_type = 'uint32_t'
    size = 32
    unsigned = True
    def __init__(self, value):
        if isinstance(value, MemorySize):
            self.value = value.value
        else:
            self.value = convert.toMemorySize(value)
        self._check()

class Addr(CheckedInt):
    cxx_type = 'Addr'
    size = 64
    unsigned = True
    def __init__(self, value):
        if isinstance(value, Addr):
            self.value = value.value
        else:
            try:
                self.value = convert.toMemorySize(value)
            except TypeError:
                self.value = long(value)
        self._check()
    def __add__(self, other):
        if isinstance(other, Addr):
            return self.value + other.value
        else:
            return self.value + other

class AddrRange(ParamValue):
    cxx_type = 'Range<Addr>'

    def __init__(self, *args, **kwargs):
        def handle_kwargs(self, kwargs):
            if 'end' in kwargs:
                self.end = Addr(kwargs.pop('end'))
            elif 'size' in kwargs:
                self.end = self.start + Addr(kwargs.pop('size')) - 1
            else:
                raise TypeError, "Either end or size must be specified"

        if len(args) == 0:
            self.start = Addr(kwargs.pop('start'))
            handle_kwargs(self, kwargs)

        elif len(args) == 1:
            if kwargs:
                self.start = Addr(args[0])
                handle_kwargs(self, kwargs)
            elif isinstance(args[0], (list, tuple)):
                self.start = Addr(args[0][0])
                self.end = Addr(args[0][1])
            else:
                self.start = Addr(0)
                self.end = Addr(args[0]) - 1

        elif len(args) == 2:
            self.start = Addr(args[0])
            self.end = Addr(args[1])
        else:
            raise TypeError, "Too many arguments specified"

        if kwargs:
            raise TypeError, "Too many keywords: %s" % kwargs.keys()

    def __str__(self):
        return '%s:%s' % (self.start, self.end)

    def size(self):
        return long(self.end) - long(self.start) + 1

    @classmethod
    def cxx_predecls(cls, code):
        Addr.cxx_predecls(code)
        code('#include "base/range.hh"')

    @classmethod
    def swig_predecls(cls, code):
        Addr.swig_predecls(code)
        code('%import "python/swig/range.i"')

    def getValue(self):
        from m5.internal.range import AddrRange

        value = AddrRange()
        value.start = long(self.start)
        value.end = long(self.end)
        return value

# Boolean parameter type.  Python doesn't let you subclass bool, since
# it doesn't want to let you create multiple instances of True and
# False.  Thus this is a little more complicated than String.
class Bool(ParamValue):
    cxx_type = 'bool'
    def __init__(self, value):
        try:
            self.value = convert.toBool(value)
        except TypeError:
            self.value = bool(value)

    def getValue(self):
        return bool(self.value)

    def __str__(self):
        return str(self.value)

    # implement truth value testing for Bool parameters so that these params
    # evaluate correctly during the python configuration phase
    def __nonzero__(self):
        return bool(self.value)

    def ini_str(self):
        if self.value:
            return 'true'
        return 'false'

def IncEthernetAddr(addr, val = 1):
    bytes = map(lambda x: int(x, 16), addr.split(':'))
    bytes[5] += val
    for i in (5, 4, 3, 2, 1):
        val,rem = divmod(bytes[i], 256)
        bytes[i] = rem
        if val == 0:
            break
        bytes[i - 1] += val
    assert(bytes[0] <= 255)
    return ':'.join(map(lambda x: '%02x' % x, bytes))

_NextEthernetAddr = "00:90:00:00:00:01"
def NextEthernetAddr():
    global _NextEthernetAddr

    value = _NextEthernetAddr
    _NextEthernetAddr = IncEthernetAddr(_NextEthernetAddr, 1)
    return value

class EthernetAddr(ParamValue):
    cxx_type = 'Net::EthAddr'

    @classmethod
    def cxx_predecls(cls, code):
        code('#include "base/inet.hh"')

    @classmethod
    def swig_predecls(cls, code):
        code('%include "python/swig/inet.i"')

    def __init__(self, value):
        if value == NextEthernetAddr:
            self.value = value
            return

        if not isinstance(value, str):
            raise TypeError, "expected an ethernet address and didn't get one"

        bytes = value.split(':')
        if len(bytes) != 6:
            raise TypeError, 'invalid ethernet address %s' % value

        for byte in bytes:
            if not 0 <= int(byte) <= 0xff:
                raise TypeError, 'invalid ethernet address %s' % value

        self.value = value

    def unproxy(self, base):
        if self.value == NextEthernetAddr:
            return EthernetAddr(self.value())
        return self

    def getValue(self):
        from m5.internal.params import EthAddr
        return EthAddr(self.value)

    def ini_str(self):
        return self.value

# When initializing an IpAddress, pass in an existing IpAddress, a string of
# the form "a.b.c.d", or an integer representing an IP.
class IpAddress(ParamValue):
    cxx_type = 'Net::IpAddress'

    @classmethod
    def cxx_predecls(cls, code):
        code('#include "base/inet.hh"')

    @classmethod
    def swig_predecls(cls, code):
        code('%include "python/swig/inet.i"')

    def __init__(self, value):
        if isinstance(value, IpAddress):
            self.ip = value.ip
        else:
            try:
                self.ip = convert.toIpAddress(value)
            except TypeError:
                self.ip = long(value)
        self.verifyIp()

    def __str__(self):
        tup = [(self.ip >> i)  & 0xff for i in (24, 16, 8, 0)]
        return '%d.%d.%d.%d' % tuple(tup)

    def __eq__(self, other):
        if isinstance(other, IpAddress):
            return self.ip == other.ip
        elif isinstance(other, str):
            try:
                return self.ip == convert.toIpAddress(other)
            except:
                return False
        else:
            return self.ip == other

    def __ne__(self, other):
        return not (self == other)

    def verifyIp(self):
        if self.ip < 0 or self.ip >= (1 << 32):
            raise TypeError, "invalid ip address %#08x" % self.ip

    def getValue(self):
        from m5.internal.params import IpAddress
        return IpAddress(self.ip)

# When initializing an IpNetmask, pass in an existing IpNetmask, a string of
# the form "a.b.c.d/n" or "a.b.c.d/e.f.g.h", or an ip and netmask as
# positional or keyword arguments.
class IpNetmask(IpAddress):
    cxx_type = 'Net::IpNetmask'

    @classmethod
    def cxx_predecls(cls, code):
        code('#include "base/inet.hh"')

    @classmethod
    def swig_predecls(cls, code):
        code('%include "python/swig/inet.i"')

    def __init__(self, *args, **kwargs):
        def handle_kwarg(self, kwargs, key, elseVal = None):
            if key in kwargs:
                setattr(self, key, kwargs.pop(key))
            elif elseVal:
                setattr(self, key, elseVal)
            else:
                raise TypeError, "No value set for %s" % key

        if len(args) == 0:
            handle_kwarg(self, kwargs, 'ip')
            handle_kwarg(self, kwargs, 'netmask')

        elif len(args) == 1:
            if kwargs:
                if not 'ip' in kwargs and not 'netmask' in kwargs:
                    raise TypeError, "Invalid arguments"
                handle_kwarg(self, kwargs, 'ip', args[0])
                handle_kwarg(self, kwargs, 'netmask', args[0])
            elif isinstance(args[0], IpNetmask):
                self.ip = args[0].ip
                self.netmask = args[0].netmask
            else:
                (self.ip, self.netmask) = convert.toIpNetmask(args[0])

        elif len(args) == 2:
            self.ip = args[0]
            self.netmask = args[1]
        else:
            raise TypeError, "Too many arguments specified"

        if kwargs:
            raise TypeError, "Too many keywords: %s" % kwargs.keys()

        self.verify()

    def __str__(self):
        return "%s/%d" % (super(IpNetmask, self).__str__(), self.netmask)

    def __eq__(self, other):
        if isinstance(other, IpNetmask):
            return self.ip == other.ip and self.netmask == other.netmask
        elif isinstance(other, str):
            try:
                return (self.ip, self.netmask) == convert.toIpNetmask(other)
            except:
                return False
        else:
            return False

    def verify(self):
        self.verifyIp()
        if self.netmask < 0 or self.netmask > 32:
            raise TypeError, "invalid netmask %d" % netmask

    def getValue(self):
        from m5.internal.params import IpNetmask
        return IpNetmask(self.ip, self.netmask)

# When initializing an IpWithPort, pass in an existing IpWithPort, a string of
# the form "a.b.c.d:p", or an ip and port as positional or keyword arguments.
class IpWithPort(IpAddress):
    cxx_type = 'Net::IpWithPort'

    @classmethod
    def cxx_predecls(cls, code):
        code('#include "base/inet.hh"')

    @classmethod
    def swig_predecls(cls, code):
        code('%include "python/swig/inet.i"')

    def __init__(self, *args, **kwargs):
        def handle_kwarg(self, kwargs, key, elseVal = None):
            if key in kwargs:
                setattr(self, key, kwargs.pop(key))
            elif elseVal:
                setattr(self, key, elseVal)
            else:
                raise TypeError, "No value set for %s" % key

        if len(args) == 0:
            handle_kwarg(self, kwargs, 'ip')
            handle_kwarg(self, kwargs, 'port')

        elif len(args) == 1:
            if kwargs:
                if not 'ip' in kwargs and not 'port' in kwargs:
                    raise TypeError, "Invalid arguments"
                handle_kwarg(self, kwargs, 'ip', args[0])
                handle_kwarg(self, kwargs, 'port', args[0])
            elif isinstance(args[0], IpWithPort):
                self.ip = args[0].ip
                self.port = args[0].port
            else:
                (self.ip, self.port) = convert.toIpWithPort(args[0])

        elif len(args) == 2:
            self.ip = args[0]
            self.port = args[1]
        else:
            raise TypeError, "Too many arguments specified"

        if kwargs:
            raise TypeError, "Too many keywords: %s" % kwargs.keys()

        self.verify()

    def __str__(self):
        return "%s:%d" % (super(IpWithPort, self).__str__(), self.port)

    def __eq__(self, other):
        if isinstance(other, IpWithPort):
            return self.ip == other.ip and self.port == other.port
        elif isinstance(other, str):
            try:
                return (self.ip, self.port) == convert.toIpWithPort(other)
            except:
                return False
        else:
            return False

    def verify(self):
        self.verifyIp()
        if self.port < 0 or self.port > 0xffff:
            raise TypeError, "invalid port %d" % self.port

    def getValue(self):
        from m5.internal.params import IpWithPort
        return IpWithPort(self.ip, self.port)

time_formats = [ "%a %b %d %H:%M:%S %Z %Y",
                 "%a %b %d %H:%M:%S %Z %Y",
                 "%Y/%m/%d %H:%M:%S",
                 "%Y/%m/%d %H:%M",
                 "%Y/%m/%d",
                 "%m/%d/%Y %H:%M:%S",
                 "%m/%d/%Y %H:%M",
                 "%m/%d/%Y",
                 "%m/%d/%y %H:%M:%S",
                 "%m/%d/%y %H:%M",
                 "%m/%d/%y"]


def parse_time(value):
    from time import gmtime, strptime, struct_time, time
    from datetime import datetime, date

    if isinstance(value, struct_time):
        return value

    if isinstance(value, (int, long)):
        return gmtime(value)

    if isinstance(value, (datetime, date)):
        return value.timetuple()

    if isinstance(value, str):
        if value in ('Now', 'Today'):
            return time.gmtime(time.time())

        for format in time_formats:
            try:
                return strptime(value, format)
            except ValueError:
                pass

    raise ValueError, "Could not parse '%s' as a time" % value

class Time(ParamValue):
    cxx_type = 'tm'

    @classmethod
    def cxx_predecls(cls, code):
        code('#include <time.h>')

    @classmethod
    def swig_predecls(cls, code):
        code('%include "python/swig/time.i"')

    def __init__(self, value):
        self.value = parse_time(value)

    def getValue(self):
        from m5.internal.params import tm

        c_time = tm()
        py_time = self.value

        # UNIX is years since 1900
        c_time.tm_year = py_time.tm_year - 1900;

        # Python starts at 1, UNIX starts at 0
        c_time.tm_mon =  py_time.tm_mon - 1;
        c_time.tm_mday = py_time.tm_mday;
        c_time.tm_hour = py_time.tm_hour;
        c_time.tm_min = py_time.tm_min;
        c_time.tm_sec = py_time.tm_sec;

        # Python has 0 as Monday, UNIX is 0 as sunday
        c_time.tm_wday = py_time.tm_wday + 1
        if c_time.tm_wday > 6:
            c_time.tm_wday -= 7;

        # Python starts at 1, Unix starts at 0
        c_time.tm_yday = py_time.tm_yday - 1;

        return c_time

    def __str__(self):
        return time.asctime(self.value)

    def ini_str(self):
        return str(self)

    def get_config_as_dict(self):
        return str(self)

# Enumerated types are a little more complex.  The user specifies the
# type as Enum(foo) where foo is either a list or dictionary of
# alternatives (typically strings, but not necessarily so).  (In the
# long run, the integer value of the parameter will be the list index
# or the corresponding dictionary value.  For now, since we only check
# that the alternative is valid and then spit it into a .ini file,
# there's not much point in using the dictionary.)

# What Enum() must do is generate a new type encapsulating the
# provided list/dictionary so that specific values of the parameter
# can be instances of that type.  We define two hidden internal
# classes (_ListEnum and _DictEnum) to serve as base classes, then
# derive the new type from the appropriate base class on the fly.

allEnums = {}
# Metaclass for Enum types
class MetaEnum(MetaParamValue):
    def __new__(mcls, name, bases, dict):
        assert name not in allEnums

        cls = super(MetaEnum, mcls).__new__(mcls, name, bases, dict)
        allEnums[name] = cls
        return cls

    def __init__(cls, name, bases, init_dict):
        if init_dict.has_key('map'):
            if not isinstance(cls.map, dict):
                raise TypeError, "Enum-derived class attribute 'map' " \
                      "must be of type dict"
            # build list of value strings from map
            cls.vals = cls.map.keys()
            cls.vals.sort()
        elif init_dict.has_key('vals'):
            if not isinstance(cls.vals, list):
                raise TypeError, "Enum-derived class attribute 'vals' " \
                      "must be of type list"
            # build string->value map from vals sequence
            cls.map = {}
            for idx,val in enumerate(cls.vals):
                cls.map[val] = idx
        else:
            raise TypeError, "Enum-derived class must define "\
                  "attribute 'map' or 'vals'"

        cls.cxx_type = 'Enums::%s' % name

        super(MetaEnum, cls).__init__(name, bases, init_dict)

    # Generate C++ class declaration for this enum type.
    # Note that we wrap the enum in a class/struct to act as a namespace,
    # so that the enum strings can be brief w/o worrying about collisions.
    def cxx_decl(cls, code):
        name = cls.__name__
        code('''\
#ifndef __ENUM__${name}__
#define __ENUM__${name}__

namespace Enums {
    enum $name {
''')
        code.indent(2)
        for val in cls.vals:
            code('$val = ${{cls.map[val]}},')
        code('Num_$name = ${{len(cls.vals)}}')
        code.dedent(2)
        code('''\
    };
extern const char *${name}Strings[Num_${name}];
}

#endif // __ENUM__${name}__
''')

    def cxx_def(cls, code):
        name = cls.__name__
        code('''\
#include "enums/$name.hh"
namespace Enums {
    const char *${name}Strings[Num_${name}] =
    {
''')
        code.indent(2)
        for val in cls.vals:
            code('"$val",')
        code.dedent(2)
        code('''
    };
} // namespace Enums
''')

    def swig_decl(cls, code):
        name = cls.__name__
        code('''\
%module(package="m5.internal") enum_$name

%{
#include "enums/$name.hh"
%}

%include "enums/$name.hh"
''')


# Base class for enum types.
class Enum(ParamValue):
    __metaclass__ = MetaEnum
    vals = []

    def __init__(self, value):
        if value not in self.map:
            raise TypeError, "Enum param got bad value '%s' (not in %s)" \
                  % (value, self.vals)
        self.value = value

    @classmethod
    def cxx_predecls(cls, code):
        code('#include "enums/$0.hh"', cls.__name__)

    @classmethod
    def swig_predecls(cls, code):
        code('%import "python/m5/internal/enum_$0.i"', cls.__name__)

    def getValue(self):
        return int(self.map[self.value])

    def __str__(self):
        return self.value

# how big does a rounding error need to be before we warn about it?
frequency_tolerance = 0.001  # 0.1%

class TickParamValue(NumericParamValue):
    cxx_type = 'Tick'

    @classmethod
    def cxx_predecls(cls, code):
        code('#include "base/types.hh"')

    @classmethod
    def swig_predecls(cls, code):
        code('%import "stdint.i"')
        code('%import "base/types.hh"')

    def getValue(self):
        return long(self.value)

class Latency(TickParamValue):
    def __init__(self, value):
        if isinstance(value, (Latency, Clock)):
            self.ticks = value.ticks
            self.value = value.value
        elif isinstance(value, Frequency):
            self.ticks = value.ticks
            self.value = 1.0 / value.value
        elif value.endswith('t'):
            self.ticks = True
            self.value = int(value[:-1])
        else:
            self.ticks = False
            self.value = convert.toLatency(value)

    def __getattr__(self, attr):
        if attr in ('latency', 'period'):
            return self
        if attr == 'frequency':
            return Frequency(self)
        raise AttributeError, "Latency object has no attribute '%s'" % attr

    def getValue(self):
        if self.ticks or self.value == 0:
            value = self.value
        else:
            value = ticks.fromSeconds(self.value)
        return long(value)

    # convert latency to ticks
    def ini_str(self):
        return '%d' % self.getValue()

class Frequency(TickParamValue):
    def __init__(self, value):
        if isinstance(value, (Latency, Clock)):
            if value.value == 0:
                self.value = 0
            else:
                self.value = 1.0 / value.value
            self.ticks = value.ticks
        elif isinstance(value, Frequency):
            self.value = value.value
            self.ticks = value.ticks
        else:
            self.ticks = False
            self.value = convert.toFrequency(value)

    def __getattr__(self, attr):
        if attr == 'frequency':
            return self
        if attr in ('latency', 'period'):
            return Latency(self)
        raise AttributeError, "Frequency object has no attribute '%s'" % attr

    # convert latency to ticks
    def getValue(self):
        if self.ticks or self.value == 0:
            value = self.value
        else:
            value = ticks.fromSeconds(1.0 / self.value)
        return long(value)

    def ini_str(self):
        return '%d' % self.getValue()

# A generic frequency and/or Latency value.  Value is stored as a latency,
# but to avoid ambiguity this object does not support numeric ops (* or /).
# An explicit conversion to a Latency or Frequency must be made first.
class Clock(ParamValue):
    cxx_type = 'Tick'

    @classmethod
    def cxx_predecls(cls, code):
        code('#include "base/types.hh"')

    @classmethod
    def swig_predecls(cls, code):
        code('%import "stdint.i"')
        code('%import "base/types.hh"')

    def __init__(self, value):
        if isinstance(value, (Latency, Clock)):
            self.ticks = value.ticks
            self.value = value.value
        elif isinstance(value, Frequency):
            self.ticks = value.ticks
            self.value = 1.0 / value.value
        elif value.endswith('t'):
            self.ticks = True
            self.value = int(value[:-1])
        else:
            self.ticks = False
            self.value = convert.anyToLatency(value)

    def __getattr__(self, attr):
        if attr == 'frequency':
            return Frequency(self)
        if attr in ('latency', 'period'):
            return Latency(self)
        raise AttributeError, "Frequency object has no attribute '%s'" % attr

    def getValue(self):
        return self.period.getValue()

    def ini_str(self):
        return self.period.ini_str()

class NetworkBandwidth(float,ParamValue):
    cxx_type = 'float'
    def __new__(cls, value):
        # convert to bits per second
        val = convert.toNetworkBandwidth(value)
        return super(cls, NetworkBandwidth).__new__(cls, val)

    def __str__(self):
        return str(self.val)

    def getValue(self):
        # convert to seconds per byte
        value = 8.0 / float(self)
        # convert to ticks per byte
        value = ticks.fromSeconds(value)
        return float(value)

    def ini_str(self):
        return '%f' % self.getValue()

class MemoryBandwidth(float,ParamValue):
    cxx_type = 'float'
    def __new__(cls, value):
        # convert to bytes per second
        val = convert.toMemoryBandwidth(value)
        return super(cls, MemoryBandwidth).__new__(cls, val)

    def __str__(self):
        return str(self.val)

    def getValue(self):
        # convert to seconds per byte
        value = float(self)
        if value:
            value = 1.0 / float(self)
        # convert to ticks per byte
        value = ticks.fromSeconds(value)
        return float(value)

    def ini_str(self):
        return '%f' % self.getValue()

#
# "Constants"... handy aliases for various values.
#

# Special class for NULL pointers.  Note the special check in
# make_param_value() above that lets these be assigned where a
# SimObject is required.
# only one copy of a particular node
class NullSimObject(object):
    __metaclass__ = Singleton

    def __call__(cls):
        return cls

    def _instantiate(self, parent = None, path = ''):
        pass

    def ini_str(self):
        return 'Null'

    def unproxy(self, base):
        return self

    def set_path(self, parent, name):
        pass

    def __str__(self):
        return 'Null'

    def getValue(self):
        return None

# The only instance you'll ever need...
NULL = NullSimObject()

def isNullPointer(value):
    return isinstance(value, NullSimObject)

# Some memory range specifications use this as a default upper bound.
MaxAddr = Addr.max
MaxTick = Tick.max
AllMemory = AddrRange(0, MaxAddr)


#####################################################################
#
# Port objects
#
# Ports are used to interconnect objects in the memory system.
#
#####################################################################

# Port reference: encapsulates a reference to a particular port on a
# particular SimObject.
class PortRef(object):
    def __init__(self, simobj, name, role):
        assert(isSimObject(simobj) or isSimObjectClass(simobj))
        self.simobj = simobj
        self.name = name
        self.role = role
        self.peer = None   # not associated with another port yet
        self.ccConnected = False # C++ port connection done?
        self.index = -1  # always -1 for non-vector ports

    def __str__(self):
        return '%s.%s' % (self.simobj, self.name)

    def __len__(self):
        # Return the number of connected ports, i.e. 0 is we have no
        # peer and 1 if we do.
        return int(self.peer != None)

    # for config.ini, print peer's name (not ours)
    def ini_str(self):
        return str(self.peer)

    # for config.json
    def get_config_as_dict(self):
        return {'role' : self.role, 'peer' : str(self.peer)}

    def __getattr__(self, attr):
        if attr == 'peerObj':
            # shorthand for proxies
            return self.peer.simobj
        raise AttributeError, "'%s' object has no attribute '%s'" % \
              (self.__class__.__name__, attr)

    # Full connection is symmetric (both ways).  Called via
    # SimObject.__setattr__ as a result of a port assignment, e.g.,
    # "obj1.portA = obj2.portB", or via VectorPortElementRef.__setitem__,
    # e.g., "obj1.portA[3] = obj2.portB".
    def connect(self, other):
        if isinstance(other, VectorPortRef):
            # reference to plain VectorPort is implicit append
            other = other._get_next()
        if self.peer and not proxy.isproxy(self.peer):
            fatal("Port %s is already connected to %s, cannot connect %s\n",
                  self, self.peer, other);
        self.peer = other
        if proxy.isproxy(other):
            other.set_param_desc(PortParamDesc())
        elif isinstance(other, PortRef):
            if other.peer is not self:
                other.connect(self)
        else:
            raise TypeError, \
                  "assigning non-port reference '%s' to port '%s'" \
                  % (other, self)

    def clone(self, simobj, memo):
        if memo.has_key(self):
            return memo[self]
        newRef = copy.copy(self)
        memo[self] = newRef
        newRef.simobj = simobj
        assert(isSimObject(newRef.simobj))
        if self.peer and not proxy.isproxy(self.peer):
            peerObj = self.peer.simobj(_memo=memo)
            newRef.peer = self.peer.clone(peerObj, memo)
            assert(not isinstance(newRef.peer, VectorPortRef))
        return newRef

    def unproxy(self, simobj):
        assert(simobj is self.simobj)
        if proxy.isproxy(self.peer):
            try:
                realPeer = self.peer.unproxy(self.simobj)
            except:
                print "Error in unproxying port '%s' of %s" % \
                      (self.name, self.simobj.path())
                raise
            self.connect(realPeer)

    # Call C++ to create corresponding port connection between C++ objects
    def ccConnect(self):
        from m5.internal.pyobject import connectPorts

        if self.role == 'SLAVE':
            # do nothing and let the master take care of it
            return

        if self.ccConnected: # already done this
            return
        peer = self.peer
        if not self.peer: # nothing to connect to
            return

        # check that we connect a master to a slave
        if self.role == peer.role:
            raise TypeError, \
                "cannot connect '%s' and '%s' due to identical role '%s'" \
                % (peer, self, self.role)

        try:
            # self is always the master and peer the slave
            connectPorts(self.simobj.getCCObject(), self.name, self.index,
                         peer.simobj.getCCObject(), peer.name, peer.index)
        except:
            print "Error connecting port %s.%s to %s.%s" % \
                  (self.simobj.path(), self.name,
                   peer.simobj.path(), peer.name)
            raise
        self.ccConnected = True
        peer.ccConnected = True

# A reference to an individual element of a VectorPort... much like a
# PortRef, but has an index.
class VectorPortElementRef(PortRef):
    def __init__(self, simobj, name, role, index):
        PortRef.__init__(self, simobj, name, role)
        self.index = index

    def __str__(self):
        return '%s.%s[%d]' % (self.simobj, self.name, self.index)

# A reference to a complete vector-valued port (not just a single element).
# Can be indexed to retrieve individual VectorPortElementRef instances.
class VectorPortRef(object):
    def __init__(self, simobj, name, role):
        assert(isSimObject(simobj) or isSimObjectClass(simobj))
        self.simobj = simobj
        self.name = name
        self.role = role
        self.elements = []

    def __str__(self):
        return '%s.%s[:]' % (self.simobj, self.name)

    def __len__(self):
        # Return the number of connected peers, corresponding the the
        # length of the elements.
        return len(self.elements)

    # for config.ini, print peer's name (not ours)
    def ini_str(self):
        return ' '.join([el.ini_str() for el in self.elements])

    # for config.json
    def get_config_as_dict(self):
        return {'role' : self.role,
                'peer' : [el.ini_str() for el in self.elements]}

    def __getitem__(self, key):
        if not isinstance(key, int):
            raise TypeError, "VectorPort index must be integer"
        if key >= len(self.elements):
            # need to extend list
            ext = [VectorPortElementRef(self.simobj, self.name, self.role, i)
                   for i in range(len(self.elements), key+1)]
            self.elements.extend(ext)
        return self.elements[key]

    def _get_next(self):
        return self[len(self.elements)]

    def __setitem__(self, key, value):
        if not isinstance(key, int):
            raise TypeError, "VectorPort index must be integer"
        self[key].connect(value)

    def connect(self, other):
        if isinstance(other, (list, tuple)):
            # Assign list of port refs to vector port.
            # For now, append them... not sure if that's the right semantics
            # or if it should replace the current vector.
            for ref in other:
                self._get_next().connect(ref)
        else:
            # scalar assignment to plain VectorPort is implicit append
            self._get_next().connect(other)

    def clone(self, simobj, memo):
        if memo.has_key(self):
            return memo[self]
        newRef = copy.copy(self)
        memo[self] = newRef
        newRef.simobj = simobj
        assert(isSimObject(newRef.simobj))
        newRef.elements = [el.clone(simobj, memo) for el in self.elements]
        return newRef

    def unproxy(self, simobj):
        [el.unproxy(simobj) for el in self.elements]

    def ccConnect(self):
        [el.ccConnect() for el in self.elements]

# Port description object.  Like a ParamDesc object, this represents a
# logical port in the SimObject class, not a particular port on a
# SimObject instance.  The latter are represented by PortRef objects.
class Port(object):
    # Generate a PortRef for this port on the given SimObject with the
    # given name
    def makeRef(self, simobj):
        return PortRef(simobj, self.name, self.role)

    # Connect an instance of this port (on the given SimObject with
    # the given name) with the port described by the supplied PortRef
    def connect(self, simobj, ref):
        self.makeRef(simobj).connect(ref)

    # No need for any pre-declarations at the moment as we merely rely
    # on an unsigned int.
    def cxx_predecls(self, code):
        pass

    # Declare an unsigned int with the same name as the port, that
    # will eventually hold the number of connected ports (and thus the
    # number of elements for a VectorPort).
    def cxx_decl(self, code):
        code('unsigned int port_${{self.name}}_connection_count;')

class MasterPort(Port):
    # MasterPort("description")
    def __init__(self, *args):
        if len(args) == 1:
            self.desc = args[0]
            self.role = 'MASTER'
        else:
            raise TypeError, 'wrong number of arguments'

class SlavePort(Port):
    # SlavePort("description")
    def __init__(self, *args):
        if len(args) == 1:
            self.desc = args[0]
            self.role = 'SLAVE'
        else:
            raise TypeError, 'wrong number of arguments'

# VectorPort description object.  Like Port, but represents a vector
# of connections (e.g., as on a Bus).
class VectorPort(Port):
    def __init__(self, *args):
        self.isVec = True

    def makeRef(self, simobj):
        return VectorPortRef(simobj, self.name, self.role)

class VectorMasterPort(VectorPort):
    # VectorMasterPort("description")
    def __init__(self, *args):
        if len(args) == 1:
            self.desc = args[0]
            self.role = 'MASTER'
            VectorPort.__init__(self, *args)
        else:
            raise TypeError, 'wrong number of arguments'

class VectorSlavePort(VectorPort):
    # VectorSlavePort("description")
    def __init__(self, *args):
        if len(args) == 1:
            self.desc = args[0]
            self.role = 'SLAVE'
            VectorPort.__init__(self, *args)
        else:
            raise TypeError, 'wrong number of arguments'

# 'Fake' ParamDesc for Port references to assign to the _pdesc slot of
# proxy objects (via set_param_desc()) so that proxy error messages
# make sense.
class PortParamDesc(object):
    __metaclass__ = Singleton

    ptype_str = 'Port'
    ptype = Port

baseEnums = allEnums.copy()
baseParams = allParams.copy()

def clear():
    global allEnums, allParams

    allEnums = baseEnums.copy()
    allParams = baseParams.copy()

__all__ = ['Param', 'VectorParam',
           'Enum', 'Bool', 'String', 'Float',
           'Int', 'Unsigned', 'Int8', 'UInt8', 'Int16', 'UInt16',
           'Int32', 'UInt32', 'Int64', 'UInt64',
           'Counter', 'Addr', 'Tick', 'Percent',
           'TcpPort', 'UdpPort', 'EthernetAddr',
           'IpAddress', 'IpNetmask', 'IpWithPort',
           'MemorySize', 'MemorySize32',
           'Latency', 'Frequency', 'Clock',
           'NetworkBandwidth', 'MemoryBandwidth',
           'AddrRange',
           'MaxAddr', 'MaxTick', 'AllMemory',
           'Time',
           'NextEthernetAddr', 'NULL',
           'MasterPort', 'SlavePort',
           'VectorMasterPort', 'VectorSlavePort']

import SimObject
