"""Test the media accessors.

If run directly from the command line, also tests the accessors and the names
of all the media by getting just about everything in a naive brute-force way.
This, of course, takes a lot of time to run.
"""
from __future__ import print_function

import pytest

import os
import re

from pokedex.db import tables
from pokedex.util import media

path_re = re.compile('^[-a-z0-9./]*$')

def test_totodile(session, media_root):
    """Totodile's female sprite -- same as male"""
    totodile = session.query(tables.PokemonSpecies).filter_by(identifier=u'totodile').one()
    accessor = media.PokemonSpeciesMedia(media_root, totodile)
    assert accessor.sprite() == accessor.sprite(female=True)

def test_chimecho(session, media_root):
    """Chimecho's Platinum female backsprite -- diffeent from male"""
    chimecho = session.query(tables.PokemonSpecies).filter_by(identifier=u'chimecho').one()
    accessor = media.PokemonSpeciesMedia(media_root, chimecho)
    male = accessor.sprite('platinum', back=True, frame=2)
    female = accessor.sprite('platinum', back=True, female=True, frame=2)
    assert male != female

def test_venonat(session, media_root):
    """Venonat's shiny Yellow sprite -- same as non-shiny"""
    venonat = session.query(tables.PokemonSpecies).filter_by(identifier=u'venonat').one()
    accessor = media.PokemonSpeciesMedia(media_root, venonat)
    assert accessor.sprite('yellow') == accessor.sprite('yellow', shiny=True)

@pytest.mark.xfail
def test_arceus_icon(session, media_root):
    """Arceus normal-form icon -- same as base icon"""
    arceus = session.query(tables.PokemonSpecies).filter_by(identifier=u'arceus').one()
    accessor = media.PokemonSpeciesMedia(media_root, arceus)
    normal_arceus = [f for f in arceus.forms if f.form_identifier == 'normal'][0]
    normal_accessor = media.PokemonFormMedia(media_root, normal_arceus)
    assert accessor.icon() == normal_accessor.icon()

def test_strict_castform(session, media_root):
    """Castform rainy form overworld with strict -- unavailable"""
    with pytest.raises(ValueError):
        castform = session.query(tables.PokemonSpecies).filter_by(identifier=u'castform').first()
        rainy_castform = [f for f in castform.forms if f.form_identifier == 'rainy'][0]
        rainy_castform = media.PokemonFormMedia(media_root, rainy_castform)
        rainy_castform.overworld('up', strict=True)

def test_strict_exeggcute(session, media_root):
    """Exeggcutes's female backsprite, with strict -- unavailable"""
    with pytest.raises(ValueError):
        exeggcute = session.query(tables.PokemonSpecies).filter_by(identifier=u'exeggcute').one()
        accessor = media.PokemonSpeciesMedia(media_root, exeggcute)
        accessor.sprite(female=True, strict=True)



def get_all_filenames(media_root):
    all_filenames = set()

    for dirpath, dirnames, filenames in os.walk(media_root):
        dirnames[:] = [dirname for dirname in dirnames if dirname != '.git']
        for filename in filenames:
            path = os.path.join(dirpath, filename)
            assert path_re.match(path), path
            all_filenames.add(path)

    return all_filenames

def hit(filenames, method, *args, **kwargs):
    """
    Run the given accessor method with args & kwargs; if found remove the
    result path from filenames and return True, else return False.
    """
    try:
        medium = method(*args, **kwargs)
        assert medium.exists
    except ValueError:
        return False
    except:
        print('Error while processing', method, args, kwargs)
        raise
    try:
        filenames.remove(medium.path)
    except KeyError:
        pass
    return True

@pytest.mark.slow
@pytest.mark.xfail
def test_get_everything(session, media_root):
    """
    For every the accessor method, loop over the Cartesian products of all
    possible values for its arguments.
    Make sure we get every file in the repo, and that we get a file whenever
    we should.

    Well, there are exceptions of course.
    """

    versions = list(session.query(tables.Version).all())
    versions.append('red-green')

    # We don't have any graphics for Colosseum or XD
    versions.remove(session.query(tables.Version).filter_by(identifier=u'colosseum').one())
    versions.remove(session.query(tables.Version).filter_by(identifier=u'xd').one())

    black = session.query(tables.Version).filter_by(identifier=u'black').one()

    filenames = get_all_filenames(media_root)

    # Some small stuff first

    for damage_class in session.query(tables.MoveDamageClass).all():
        assert hit(filenames, media.DamageClassMedia(media_root, damage_class).icon)

    for habitat in session.query(tables.PokemonHabitat).all():
        assert hit(filenames, media.HabitatMedia(media_root, habitat).icon)

    for shape in session.query(tables.PokemonShape).all():
        assert hit(filenames, media.ShapeMedia(media_root, shape).icon)

    for item_pocket in session.query(tables.ItemPocket).all():
        assert hit(filenames, media.ItemPocketMedia(media_root, item_pocket).icon)
        assert hit(filenames, media.ItemPocketMedia(media_root, item_pocket).icon, selected=True)

    for contest_type in session.query(tables.ContestType).all():
        assert hit(filenames, media.ContestTypeMedia(media_root, contest_type).icon)

    for elemental_type in session.query(tables.Type).all():
        assert hit(filenames, media.TypeMedia(media_root, elemental_type).icon)

    # Items
    versions_for_items = [
        None,
        session.query(tables.Version).filter_by(identifier='emerald').one(),
    ]

    for item in session.query(tables.Item).all():
        accessor = media.ItemMedia(media_root, item)
        assert hit(filenames, accessor.berry_image) or not item.berry
        for rotation in (0, 90, 180, 270):
            assert hit(filenames, accessor.underground, rotation=rotation) or (
                    not item.appears_underground or rotation)
        for version in versions_for_items:
            success = hit(filenames, accessor.sprite, version=version)
            if version is None:
                assert success

    for color in 'red green blue pale prism'.split():
        for big in (True, False):
            accessor = media.UndergroundSphereMedia(media_root, color=color, big=big)
            assert hit(filenames, accessor.underground)

    for rock_type in 'i ii o o-big s t z'.split():
        accessor = media.UndergroundRockMedia(media_root, rock_type)
        for rotation in (0, 90, 180, 270):
            success = hit(filenames, accessor.underground, rotation=rotation)
            assert success or rotation

    # Pokemon!
    accessors = []

    accessors.append(media.UnknownPokemonMedia(media_root))
    accessors.append(media.EggMedia(media_root))
    manaphy = session.query(tables.PokemonSpecies).filter_by(identifier=u'manaphy').one()
    accessors.append(media.EggMedia(media_root, manaphy))
    accessors.append(media.SubstituteMedia(media_root))

    for form in session.query(tables.PokemonForm).all():
        accessors.append(media.PokemonFormMedia(media_root, form))

    for pokemon in session.query(tables.PokemonSpecies).all():
        accessors.append(media.PokemonSpeciesMedia(media_root, pokemon))

    for accessor in accessors:
        assert hit(filenames, accessor.footprint) or not accessor.is_proper
        assert hit(filenames, accessor.trozei) or not accessor.is_proper or (
                accessor.introduced_in > 3)
        assert hit(filenames, accessor.cry) or not accessor.is_proper
        assert hit(filenames, accessor.cropped_sprite) or not accessor.is_proper
        for female in (True, False):
            assert hit(filenames, accessor.icon, female=female) or not accessor.is_proper
            assert hit(filenames, accessor.sugimori, female=female) or (
                    not accessor.is_proper or int(accessor.species_id) >= 647)
            for shiny in (True, False):
                for frame in (1, 2):
                    for direction in 'up down left right'.split():
                        assert hit(filenames, accessor.overworld,
                                direction=direction,
                                shiny=shiny,
                                female=female,
                                frame=frame,
                            ) or not accessor.is_proper or (
                                    accessor.introduced_in > 4)
                    for version in versions:
                        for animated in (True, False):
                            for back in (True, False):
                                for color in (None, 'gray', 'gbc'):
                                    success = hit(filenames,
                                            accessor.sprite,
                                            version,
                                            animated=animated,
                                            back=back,
                                            color=color,
                                            shiny=shiny,
                                            female=female,
                                            frame=frame,
                                        )
                                    if (version == black and not animated
                                        and not back and not color and not
                                        shiny and not female and
                                        frame == 1):
                                        # All pokemon are in Black
                                        assert success or not accessor.is_proper
                                    if (str(accessor.species_id) == '1'
                                        and not animated and not color and
                                        frame == 1):
                                        # Bulbasaur is in all versions
                                        assert success

    # Remove exceptions
    exceptions = [os.path.join(media_root, dirname) for dirname in
            'chrome fonts ribbons'.split()]
    exceptions.append(os.path.join(media_root, 'items', 'hm-'))
    exceptions = tuple(exceptions)

    unaccessed_filenames = set(filenames)
    for filename in filenames:
        if filename.startswith(exceptions):
            unaccessed_filenames.remove(filename)
        if filename.endswith('-beta.png'):
            unaccessed_filenames.remove(filename)

    if unaccessed_filenames:
        print('Unaccessed files:')
        for filename in unaccessed_filenames:
            print(filename)

    assert unaccessed_filenames == set()

    return (not filenames)
