# Copyright (c) 2017-2019 The University of Manchester
#
# This program is free software: you can redistribute it and/or modify
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.

import numpy
from pacman.exceptions import PacmanConfigurationException

""" A Key and Mask to be used for routing.
"""

__slots__ = [
# The routing key
"_base_key",

]

"""
:param int base_key: The routing key
:raise PacmanConfigurationException:
If key & mask != key i.e. the key is not valid for the given mask
"""
self._base_key = base_key

if base_key & mask != base_key:
raise PacmanConfigurationException(
"This routing info is invalid as the mask {} and key {} "
"together alters the key. This is deemed to be a error from "
"SpiNNaker's point of view and therefore please rectify and "

@property
def key(self):
""" The base key

:rtype: int
"""
return self._base_key

@property
def key_combo(self):
""" The key combined with the mask

:rtype: int
"""

@property

:rtype: int
"""

return False

def __ne__(self, other):
return not self.__eq__(other)

def __repr__(self):

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

def __hash__(self):
return self.__repr__().__hash__()

@property
def n_keys(self):
""" The total number of keys that can be generated given the mask

:rtype: int
"""
# converts mask into array of bit representation

# how many zeros are in the bit representation array

# number of keys available from this mask size
return 2 ** len(zeros)

[docs]    def get_keys(self, key_array=None, offset=0, n_keys=None):
""" Get the ordered list of keys that the combination allows

:param ~numpy.ndarray(int) key_array:
Optional array into which the returned keys will be placed
:param int offset:
Optional offset into the array at which to start placing keys
:param int n_keys:
Optional limit on the number of keys returned. If less than this
number of keys are available, only the keys available will be added
:return: A tuple of an array of keys and the number of keys added to
the array
:rtype: tuple(~numpy.ndarray(int), int)
"""
# Get the position of the zeros in the mask - assume 32-bits

# If there are no zeros, there is only one key in the range, so
# return that
if len(zeros) == 0:
if key_array is None:
key_array = numpy.zeros(1, dtype=">u4")
key_array[offset] = self._base_key
return key_array, 1

# We now know how many values there are - 2^len(zeros)
max_n_keys = 2 ** len(zeros)
if key_array is not None and len(key_array) < max_n_keys:
max_n_keys = len(key_array)
if n_keys is None or n_keys > max_n_keys:
n_keys = max_n_keys
if key_array is None:
key_array = numpy.zeros(n_keys, dtype=">u4")

# Create a list of 2^len(zeros) keys
unwrapped_key = numpy.unpackbits(
numpy.asarray([self._base_key], dtype=">u4").view(dtype="uint8"))

# for each key, create its key with the idea of a neuron ID being
# continuous and live at an offset position from the bottom of
# the key
for value in range(n_keys):
key = numpy.copy(unwrapped_key)
unwrapped_value = numpy.unpackbits(
numpy.asarray([value], dtype=">u4")
.view(dtype="uint8"))[-len(zeros):]
key[zeros] = unwrapped_value
key_array[value + offset] = \
numpy.packbits(key).view(dtype=">u4")[0].item()
return key_array, n_keys