# 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 functools
import struct
from collections import defaultdict, OrderedDict
from spinnman.model import IOBuffer
from .abstract_multi_connection_process import AbstractMultiConnectionProcess

_ENCODING = "ascii"
_ONE_WORD = struct.Struct("<I")
_FIRST_IOBUF = struct.Struct("<I8xI")

""" A process for reading IOBUF memory (mostly log messages) from a\
SpiNNaker core.
"""
__slots__ = [
"_iobuf",
"_iobuf_view",

def __init__(self, connection_selector):
"""
:param connection_selector:
:type connection_selector:
AbstractMultiConnectionProcessConnectionSelector
"""
super().__init__(connection_selector)

# A dictionary of (x, y, p) -> iobuf address

# A dictionary of (x, y, p) -> OrderedDict(n) -> bytearray
self._iobuf = defaultdict(OrderedDict)

# A dictionary of (x, y, p) -> OrderedDict(n) -> memoryview
self._iobuf_view = defaultdict(OrderedDict)

# A list of extra reads that need to be done as a result of the first
# read = list of (x, y, p, n, base_address, size, offset)

# A list of next reads that need to be done as a result of the first

def _request_iobuf_address(self, iobuf_size, x, y, p):
self._send_request(
iobuf_size, x, y, p))

self, iobuf_size, x, y, p, response):
first_read_size = min((iobuf_size + 16, UDP_MESSAGE_MAX_SIZE))

def _request_iobuf_region_tail(self, extra_region):
(x, y, p, n, base_address, size, offset) = extra_region
self._send_request(
functools.partial(self._handle_extra_iobuf_response,
x, y, p, n, offset))

def _handle_extra_iobuf_response(
self, x, y, p, n, offset, response):
view = self._iobuf_view[x, y, p][n]
view[offset:offset + response.length] = response.data[
response.offset:response.offset + response.length]

def _request_iobuf_region(self, region):
self._send_request(
functools.partial(self._handle_first_iobuf_response,

def _handle_first_iobuf_response(self, x, y, p, n, base_address,
# pylint: disable=too-many-arguments

response.data, response.offset)

# Create a buffer for the data
view = memoryview(data)
self._iobuf[x, y, p][n] = data
self._iobuf_view[x, y, p][n] = view

# Put the data from this packet into the buffer
packet_bytes = response.length - 16
if packet_bytes > 0:
offset = response.offset + 16
view[0:packet_bytes] = response.data[
offset:(offset + packet_bytes)]

# While more reads need to be done to read the data
# Read the next bit of memory making up the buffer

# If there is another IOBuf buffer, read this next

"""
:param int iobuf_size:
:param ~spinn_machine.CoreSubsets core_subsets:
:rtype: iterable(IOBuffer)
"""
# Get the iobuf address for each core
for core_subset in core_subsets:
x = core_subset.x
y = core_subset.y
for p in core_subset.processor_ids:
self._finish()
self.check_for_error()

# Run rounds of the process until reading is complete
# Process the extra iobuf reads needed

# Process the next iobuf reads needed

# Finish this round
self._finish()
self.check_for_error()

for core_subset in core_subsets:
x = core_subset.x
y = core_subset.y
for p in core_subset.processor_ids:
iobuf = ""
for item in self._iobuf[x, y, p].values():
iobuf += item.decode(_ENCODING)
yield IOBuffer(x, y, p, iobuf)