import os
import json
from StringIO import StringIO

from unittest import TestCase

from mock import Mock, MagicMock, call, patch

from yape.manager import (GenericAssetManager, ImageManager, FontManager,
    SpriteManager, JSONManager, Manager, JSONList)


class FakeRect(object):

    def __init__(self, **kwargs):
        self.x = kwargs.get('x', 0)
        self.y = kwargs.get('y', 0)
        self.width = kwargs.get('width', 320)
        self.height = kwargs.get('height', 320)

    def __setattr__(self, key, value):
        if all(map(
            lambda attr: hasattr(self, attr), ['x', 'y', 'width', 'height']
        )):
            if ((key == 'x' and value > self.x + self.width) or
                (key == 'y' and value > self.y + self.height)):
                raise ValueError()
        super(FakeRect, self).__setattr__(key, value)


class FakeImage(object):

    def __init__(self, image, **kwargs):
        self.rect = FakeRect(**kwargs)
        self.image = image

    def get_rect(self):
        return self.rect

    def subsurface(self, rect):
        self.rect = rect
        return self


class GenericAssetManagerTestCase(TestCase):

    def setUp(self):
        self.path = 'test_path'
        self.manager = GenericAssetManager(self.path)

    @patch('yape.manager.GenericAssetManager._load_asset')
    def test_get_cache_empty(self, mock_load_asset):
        value = self.manager.get('key')
        self.assertTrue(mock_load_asset.called)

    @patch('yape.manager.GenericAssetManager._load_asset')
    def test_get_cache_present(self, mock_load_asset):
        expected_value = JSONList(['value'])
        self.manager.cache[('key',)] = expected_value
        value = self.manager.get('key')
        self.assertEqual(value, expected_value)
        self.assertFalse(mock_load_asset.called)

    @patch('yape.manager.GenericAssetManager.load')
    def test_load_asset_failed(self, mock_load):
        expected_value = JSONList(['value'])
        mock_load.return_value = expected_value
        mock_load.side_effect = Exception
        value = self.manager.get('key')
        self.assertEqual(value, None)
        self.assertTrue(mock_load.called)

    @patch('yape.manager.GenericAssetManager.load')
    def test_load_asset_asset_none_not_stored(self, mock_load):
        expected_value = None
        mock_load.return_value = expected_value
        value = self.manager.get('key')
        self.assertEqual(value, expected_value)
        self.assertTrue(mock_load.called)

    @patch('yape.manager.GenericAssetManager.load')
    def test_load_asset_asset_stored(self, mock_load):
        expected_value = JSONList(['value'])
        mock_load.return_value = expected_value
        value = self.manager.get('key')
        self.assertEqual(value, expected_value)
        self.assertTrue(mock_load.called)
        self.assertEqual(self.manager.cache[('key',)], expected_value)


class ImageManagerTestCase(TestCase):

    def setUp(self):
        self.path = 'test_path'
        self.manager = ImageManager(self.path)

    def test_image_load(self):
        with patch('pygame.image.load') as mock_pygame_image_load:
            mock_pygame_image_load.return_value = MagicMock()
            filename = 'image1'
            image = self.manager.get(filename)
            expected_path = os.path.join(self.path, filename)
            self.assertEqual(
                mock_pygame_image_load.call_args, call(expected_path)
            )
            self.assertEqual(
                self.manager.cache[(filename,)], image
            )


class SpriteManagerTestCase(TestCase):

    def setUp(self):
        self.path = 'test_path'
        self.manager = SpriteManager(self.path)

    @patch('yape.manager.ImageManager.get')
    def test_sprite_load_failed_image(self, mock_image_get):
        mock_image_get.return_value = None
        sprite = self.manager.get('image1', 0, 0, 32, 32)
        self.assertEqual(sprite, None)
        self.assertEqual(mock_image_get.call_args, call('image1',))

    @patch('yape.manager.ImageManager.load')
    def test_spirte_load_caches_image(self, mock_image_load):
        mock_image = FakeImage(image=b'0')
        mock_image_load.return_value = mock_image
        sprite_1 = self.manager.get('image1', 0, 0, 32, 32)
        sprite_2 = self.manager.get('image1', 32, 32, 32, 32)
        sprite_3 = self.manager.get('image2', 0, 0, 32, 32)
        # The two unique images should be in the image cache
        # They should only be loaded once each
        expected_image_cache_keys = [
            ('image1',), ('image2',)
        ]
        expected_image_load_calls = [
            call('image1',), call('image2',),
        ]
        self.assertEqual(
            self.manager.image_manager.cache.keys(), expected_image_cache_keys
        )
        self.assertEqual(
            mock_image_load.call_args_list, expected_image_load_calls,
            'Only one load call should occur for each image'
        )

    @patch('yape.manager.ImageManager.load')
    def test_sprite_load_bad_rect(self, mock_image_load):
        mock_image = FakeImage(image=b'0', width=64, height=64)
        mock_image_load.return_value = mock_image
        sprite_1 = self.manager.get('image1', 64, 64, 32, 32)
        self.assertEqual(sprite_1.rect.x, 64)
        self.assertEqual(sprite_1.rect.y, 64)
        self.assertEqual(sprite_1.rect.width, 32)
        self.assertEqual(sprite_1.rect.height, 32)

    @patch('yape.manager.ImageManager.load')
    def test_sprite_load_valid(self, mock_image_load):
        mock_image = FakeImage(image=b'0')
        mock_image_load.return_value = mock_image
        sprite_1 = self.manager.get('image1', 64, 64, 32, 32)
        self.assertEqual(sprite_1.rect.x, 64)
        self.assertEqual(sprite_1.rect.y, 64)
        self.assertEqual(sprite_1.rect.width, 32)
        self.assertEqual(sprite_1.rect.height, 32)


class FontManagerTestCase(TestCase):

    def setUp(self):
        self.path = 'test_path'
        self.manager = FontManager(self.path)

    @patch('pygame.font.Font')
    def test_font_load(self, mock_pygame_font):
        mock_pygame_font.return_value = MagicMock()
        filename = 'font1'
        font = self.manager.get(filename, 12)
        expected_path = os.path.join(self.path, filename)
        self.assertEqual(
            mock_pygame_font.call_args, call(expected_path, 12)
        )
        self.assertEqual(
            self.manager.cache[(filename, 12)], font
        )


@patch('__builtin__.open')
class JSONManagerTestCase(TestCase):

    def setUp(self):
        self.path = 'test_path'
        self.manager = JSONManager(self.path)

    def test_load_filename_path(self, mock_open):
        mock_open.return_value = StringIO()
        self.manager.load(os.path.join('maps', 'map_1'))
        expected_path = os.path.join(self.path, 'maps', 'map_1')
        self.assertEqual(mock_open.call_args, call(expected_path))

    def test_load_no_file(self, mock_open):
        mock_open.side_effect = IOError
        json_data = self.manager.load(os.path.join('maps', 'map_1'))
        self.assertEqual(json_data, None)

    def test_load_empty_file(self, mock_open):
        mock_open.return_value = StringIO()
        json_data = self.manager.load(os.path.join('maps', 'map_1'))
        self.assertEqual(json_data, None)

    def test_load_invalid_json(self, mock_open):
        file_contents = '{"key": "value", "bad,syntax4"}'
        mock_open.return_value = StringIO(file_contents)
        json_data = self.manager.load(os.path.join('maps', 'map_1'))
        self.assertEqual(json_data, None)

    def test_json_with_dict(self, mock_open):
        file_data = {
            'key1': 'value1',
            'key2': 'value2',
        }
        file_contents = json.dumps(file_data)
        mock_open.return_value = StringIO(file_contents)
        json_data = self.manager.load(os.path.join('maps', 'map_1'))
        self.assertEqual(json_data, file_data)

    def test_json_with_list(self, mock_open):
        file_data = [
            {
                'key': 'value',
            },
            {
                'key': 'value',
            },
        ]
        file_contents = json.dumps(file_data)
        mock_open.return_value = StringIO(file_contents)
        json_data = self.manager.load(os.path.join('maps', 'map_1'))
        self.assertEqual(json_data, file_data)


class ManagerTestCase(TestCase):

    def setUp(self):
        self.path = 'test_path'
        self.manager = Manager(self.path)

    def test_paths(self):
        expected_path = self.path
        expected_image_path = os.path.join(self.path, 'images')
        expected_font_path = os.path.join(self.path, 'fonts')
        self.assertEqual(self.manager._json_manager.path, expected_path)
        self.assertEqual(self.manager._image_manager.path, expected_image_path)
        self.assertEqual(self.manager._sprite_manager.path, expected_image_path)
        self.assertEqual(self.manager._font_manager.path, expected_font_path)

    @patch('yape.manager.JSONManager.get')
    def test_get_json(self, mock_json_get):
        mock_json_get.return_value = {}
        json_data = self.manager.get_json('map', 'map_1')
        self.assertEqual(json_data, {})
        self.assertEqual(mock_json_get.call_args, call(os.path.join('map', 'map_1')))

    @patch('yape.manager.ImageManager.get')
    def test_get_image(self, mock_image_get):
        mock_image_get.return_value = ''
        image_data = self.manager.get_image('image1')
        self.assertEqual(image_data, '')
        self.assertEqual(mock_image_get.call_args, call('image1'))

    @patch('yape.manager.FontManager.get')
    def test_get_font(self, mock_font_get):
        mock_font_get.return_value = ''
        # fontsize defaults to 16 if not provided
        font_data = self.manager.get_font('font1')
        self.assertEqual(font_data, '')
        self.assertEqual(mock_font_get.call_args, call('font1', 16))
        # assure provided fontsize is passed
        font_data = self.manager.get_font('font1', 12)
        self.assertEqual(font_data, '')
        self.assertEqual(mock_font_get.call_args, call('font1', 12))

    @patch('yape.manager.SpriteManager.get')
    def test_get_sprite(self, mock_sprite_get):
        mock_sprite_get.return_value = ''
        sprite_data = self.manager.get_sprite('sprite1', 0, 0, 32, 32)
        self.assertEqual(sprite_data, '')
        self.assertEqual(
            mock_sprite_get.call_args, call('sprite1', 0, 0, 32, 32)
        )

