# Path: luma/core/sprite_system.py
# class spritesheet(object):
#     """
#     A sprite sheet is a series of images (usually animation frames) combined
#     into a larger image. A dictionary is usually spread into the object
#     constructor parameters with the following top-level attributes:
# 
#     :param image: A path to a sprite map image.
#     :type image: str
# 
#     :param frames: A dictionary of settings that defines how to extract
#         individual frames from the supplied image, as follows
# 
#         - ``width`` & ``height`` are required and specify the dimensions of
#           the frames
#         - ``regX`` & ``regY`` indicate the registration point or "origin" of
#           the frames
#         - ``count`` allows you to specify the total number of frames in the
#           spritesheet; if omitted, this will be calculated based on the
#           dimensions of the source images and the frames. Frames will be
#           assigned indexes based on their position in the source images
#           (left to right, top to bottom).
#     :type frames: dict
# 
#     :param animations: A dictionary of key/value pairs where the key is the
#         name of of the animation sequence, and the value are settings that
#         defines an animation sequence as follows:
# 
#         - ``frames`` is a list of frame to show in sequence. Usually this
#           comprises of frame numbers, but can refer to other animation
#           sequences (which are handled much like a subroutine call).
#         - ``speed`` determines how quickly the animation frames are cycled
#           through compared to the how often the animation sequence yields.
#         - ``next`` is optional, but if supplied, determines what happens when
#           the animation sequence is exhausted. Typically this can be used to
#           self-reference, so that it forms an infinite loop, but can hand off
#           to any other animation sequence.
#     :type animations: dict
# 
#     Loosely based on https://www.createjs.com/docs/easeljs/classes/SpriteSheet.html
#     """
#     def __init__(self, image, frames, animations):
#         with open(image, 'rb') as fp:
#             self.image = Image.open(fp)
#             self.image.load()
#         self.frames = dict_wrapper(frames)
#         self.animations = dict_wrapper(animations)
#         # Reframe the sprite map in terms of the registration point (if set)
#         regX = self.frames.regX if hasattr(self.frames, "regX") else 0
#         regY = self.frames.regY if hasattr(self.frames, "regY") else 0
#         self.image = self.image.crop((regX, regY, self.image.width - regX, self.image.height - regY))
#         self.width, self.height = self.image.size
# 
#         assert(self.width % self.frames.width == 0)
#         assert(self.height % self.frames.height == 0)
# 
#         self.frames.size = (self.frames.width, self.frames.height)
#         if not hasattr(self.frames, 'count'):
#             self.frames.count = (self.width * self.height) // (self.frames.width * self.frames.height)
# 
#         self.cache = {}
# 
#     def __getitem__(self, frame_index):
#         """
#         Returns (and caches) the frame for the given index.
#         :param frame_index: The index of the frame.
#         :type frame_index: int
#         :returns: A Pillow image cropped from the main image corresponding to
#             the given frame index.
#         :raises TypeError: if the ``frame_index`` is not numeric
#         :raises IndexError: if the ``frame_index`` is less than zero or more
#             than the largest frame.
#         """
# 
#         if not isinstance(frame_index, int):
#             raise TypeError("frame index must be numeric")
# 
#         if frame_index < 0 or frame_index > self.frames.count:
#             raise IndexError("frame index out of range")
# 
#         cached_frame = self.cache.get(frame_index)
#         if cached_frame is None:
#             offset = frame_index * self.frames.width
#             left = offset % self.width
#             top = (offset // self.width) * self.frames.height
#             right = left + self.frames.width
#             bottom = top + self.frames.height
# 
#             bounds = [left, top, right, bottom]
#             cached_frame = self.image.crop(bounds)
#             self.cache[frame_index] = cached_frame
# 
#         return cached_frame
# 
#     def __len__(self):
#         """
#         The number of frames in the sprite sheet
#         """
#         return self.frames.count
# 
#     def animate(self, seq_name):
#         """
#         Returns a generator which "executes" an animation sequence for the given
#         ``seq_name``, inasmuch as the next frame for the given animation is
#         yielded when requested.
# 
#         :param seq_name: The name of a previously defined animation sequence.
#         :type seq_name: str
#         :returns: A generator that yields all frames from the animation
#             sequence.
#         :raises AttributeError: If the ``seq_name`` is unknown.
#         """
#         while True:
#             index = 0
#             anim = getattr(self.animations, seq_name)
#             speed = anim.speed if hasattr(anim, "speed") else 1
#             num_frames = len(anim.frames)
#             while index < num_frames:
#                 frame = anim.frames[int(index)]
#                 index += speed
# 
#                 if isinstance(frame, int):
#                     yield self[frame]
#                 else:
#                     for subseq_frame in self.animate(frame):
#                         yield subseq_frame
# 
#             if not hasattr(anim, "next"):
#                 break
# 
#             seq_name = anim.next


import pytest
import PIL
from luma.core.sprite_system import spritesheet

def test_len():
    sheet = spritesheet(**data)
    assert len(sheet) == sheet.frames.count


def test_caching():
    sheet = spritesheet(**data)
    assert sheet.cache == {}
    frame = sheet[17]
    assert list(sheet.cache.keys()) == [17]
    assert sheet.cache[17] == frame


def test_get():
    w = 64
    h = 67
    sheet = spritesheet(**data)
    frame = sheet[16]
    expected = sheet.image.crop((w * 6, h * 1, w * 7, h * 2))
    assert frame == expected


def test_get_string():
    sheet = spritesheet(**data)
    with pytest.raises(TypeError):
        sheet['banana']


def test_get_outofrange():

# Path: luma/core/sprite_system.py
# class spritesheet(object):
#     """
#     A sprite sheet is a series of images (usually animation frames) combined
#     into a larger image. A dictionary is usually spread into the object
#     constructor parameters with the following top-level attributes:
# 
#     :param image: A path to a sprite map image.
#     :type image: str
# 
#     :param frames: A dictionary of settings that defines how to extract
#         individual frames from the supplied image, as follows
# 
#         - ``width`` & ``height`` are required and specify the dimensions of
#           the frames
#         - ``regX`` & ``regY`` indicate the registration point or "origin" of
#           the frames
#         - ``count`` allows you to specify the total number of frames in the
#           spritesheet; if omitted, this will be calculated based on the
#           dimensions of the source images and the frames. Frames will be
#           assigned indexes based on their position in the source images
#           (left to right, top to bottom).
#     :type frames: dict
# 
#     :param animations: A dictionary of key/value pairs where the key is the
#         name of of the animation sequence, and the value are settings that
#         defines an animation sequence as follows:
# 
#         - ``frames`` is a list of frame to show in sequence. Usually this
#           comprises of frame numbers, but can refer to other animation
#           sequences (which are handled much like a subroutine call).
#         - ``speed`` determines how quickly the animation frames are cycled
#           through compared to the how often the animation sequence yields.
#         - ``next`` is optional, but if supplied, determines what happens when
#           the animation sequence is exhausted. Typically this can be used to
#           self-reference, so that it forms an infinite loop, but can hand off
#           to any other animation sequence.
#     :type animations: dict
# 
#     Loosely based on https://www.createjs.com/docs/easeljs/classes/SpriteSheet.html
#     """
#     def __init__(self, image, frames, animations):
#         with open(image, 'rb') as fp:
#             self.image = Image.open(fp)
#             self.image.load()
#         self.frames = dict_wrapper(frames)
#         self.animations = dict_wrapper(animations)
#         # Reframe the sprite map in terms of the registration point (if set)
#         regX = self.frames.regX if hasattr(self.frames, "regX") else 0
#         regY = self.frames.regY if hasattr(self.frames, "regY") else 0
#         self.image = self.image.crop((regX, regY, self.image.width - regX, self.image.height - regY))
#         self.width, self.height = self.image.size
# 
#         assert(self.width % self.frames.width == 0)
#         assert(self.height % self.frames.height == 0)
# 
#         self.frames.size = (self.frames.width, self.frames.height)
#         if not hasattr(self.frames, 'count'):
#             self.frames.count = (self.width * self.height) // (self.frames.width * self.frames.height)
# 
#         self.cache = {}
# 
#     def __getitem__(self, frame_index):
#         """
#         Returns (and caches) the frame for the given index.
#         :param frame_index: The index of the frame.
#         :type frame_index: int
#         :returns: A Pillow image cropped from the main image corresponding to
#             the given frame index.
#         :raises TypeError: if the ``frame_index`` is not numeric
#         :raises IndexError: if the ``frame_index`` is less than zero or more
#             than the largest frame.
#         """
# 
#         if not isinstance(frame_index, int):
#             raise TypeError("frame index must be numeric")
# 
#         if frame_index < 0 or frame_index > self.frames.count:
#             raise IndexError("frame index out of range")
# 
#         cached_frame = self.cache.get(frame_index)
#         if cached_frame is None:
#             offset = frame_index * self.frames.width
#             left = offset % self.width
#             top = (offset // self.width) * self.frames.height
#             right = left + self.frames.width
#             bottom = top + self.frames.height
# 
#             bounds = [left, top, right, bottom]
#             cached_frame = self.image.crop(bounds)
#             self.cache[frame_index] = cached_frame
# 
#         return cached_frame
# 
#     def __len__(self):
#         """
#         The number of frames in the sprite sheet
#         """
#         return self.frames.count
# 
#     def animate(self, seq_name):
#         """
#         Returns a generator which "executes" an animation sequence for the given
#         ``seq_name``, inasmuch as the next frame for the given animation is
#         yielded when requested.
# 
#         :param seq_name: The name of a previously defined animation sequence.
#         :type seq_name: str
#         :returns: A generator that yields all frames from the animation
#             sequence.
#         :raises AttributeError: If the ``seq_name`` is unknown.
#         """
#         while True:
#             index = 0
#             anim = getattr(self.animations, seq_name)
#             speed = anim.speed if hasattr(anim, "speed") else 1
#             num_frames = len(anim.frames)
#             while index < num_frames:
#                 frame = anim.frames[int(index)]
#                 index += speed
# 
#                 if isinstance(frame, int):
#                     yield self[frame]
#                 else:
#                     for subseq_frame in self.animate(frame):
#                         yield subseq_frame
# 
#             if not hasattr(anim, "next"):
#                 break
# 
#             seq_name = anim.next


# Path: tests/test_spritesheet.py
import pytest
import PIL
from luma.core.sprite_system import spritesheet


def test_len():
    sheet = spritesheet(**data)
    assert len(sheet) == sheet.frames.count


def test_caching():
    sheet = spritesheet(**data)
    assert sheet.cache == {}
    frame = sheet[17]
    assert list(sheet.cache.keys()) == [17]
    assert sheet.cache[17] == frame


def test_get():
    w = 64
    h = 67
    sheet = spritesheet(**data)
    frame = sheet[16]
    expected = sheet.image.crop((w * 6, h * 1, w * 7, h * 2))
    assert frame == expected


def test_get_string():
    sheet = spritesheet(**data)
    with pytest.raises(TypeError):
        sheet['banana']


def test_get_outofrange():

    sheet = spritesheet(**data)