import uuid
import json
import copy


# A Requirement is a rule that determines whether a proposed match is
# acceptable to the seeker. This is an abstract class.
class Requirement():
    def is_match_ok(self, player, match):
        return NotImplemented

    @staticmethod
    def from_dict(d):
        out = globals()[d['rclass']]()
        out.__dict__ = d['props']
        return out

    def to_dict(self):
        return {'rclass': self.__class__.__name__,
                'props': copy.deepcopy(self.__dict__)}


class NumSets(Requirement):
    def __init__(self, min_sets=None, max_sets=None):
        self.min_sets = int(min_sets) if min_sets is not None else None
        self.max_sets = int(max_sets) if max_sets is not None else None

    def is_match_ok(self, player, match):
        host = match.get_host()
        if host.sets_owned is None:
            return False
        if self.min_sets and len(host.sets_owned) < self.min_sets:
            return False
        if self.max_sets and len(host.sets_owned) > self.max_sets:
            return False
        return True


class BaseOnly(NumSets):
    def __init__(self):
        super.__init__(1, 1)


class AllCards(NumSets):
    def __init__(self):
        super.__init__(15, 15)


class NumPlayers(Requirement):

    def __init__(self, min_players=None, max_players=None):
        self.min_players = int(min_players) if min_players is not None else None
        self.max_players = int(max_players) if max_players is not None else None

    def is_match_ok(self, player, match):
        return (int(self.min_players) <= len(match.seeks)
                <= int(self.max_players))


class HostName(Requirement):

    def __init__(self, hostname=None):
        self.hostname = hostname

    def is_match_ok(self, player, match):
        return match.hostname == self.hostname


class RatingSystem(Requirement):

    def __init__(self, rating_system=None):
        self.rating_system = rating_system

    def is_match_ok(self, player, match):
        return match.rating_system == self.rating_system


class RelativeRating(Requirement):

    def __init__(self, rating_system=None, pts_lower=None, pts_higher=None):
        self.rating_system = rating_system
        self.pts_lower = float(pts_lower) if pts_lower is not None else None
        self.pts_higher = float(pts_higher) if pts_higher is not None else None

    def is_match_ok(self, player, match):
        assert self.rating_system in \
            ('pro', 'casual', 'unrated', 'isotropish'),\
            'Unknown rating system: ' + self.rating_system

        def r(p):
            if self.rating_system == 'pro':
                return p.rating.goko_pro_rating
            elif self.rating_system == 'casual':
                return p.rating.goko_casual_rating
            elif self.rating_system == 'isotropish':
                return p.rating.isotropish_rating
            elif self.rating_system == 'unrated':
                return p.rating.goko_casual_rating

        opps = set([s.player for s in match.seeks]) - set([player])
        for o in opps:
            if r(o) is None:
                return False
            if self.pts_lower and (r(o) < r(player) - self.pts_lower):
                return False
            if self.pts_higher and (r(o) > r(player) + self.pts_higher):
                return False
        return True


class AbsoluteRating(Requirement):

    def __init__(self, rating_system=None, min_pts=None, max_pts=None):
        self.rating_system = rating_system
        self.min_pts = float(min_pts) if min_pts is not None else None
        self.max_pts = float(max_pts) if max_pts is not None else None

    def is_match_ok(self, player, match):
        assert self.rating_system in \
            ('pro', 'casual', 'unrated', 'isotropish'),\
            'Unknown rating system: ' + self.rating_system

        def r(p):
            if self.rating_system == 'pro':
                return p.rating.goko_pro_rating
            elif self.rating_system == 'casual':
                return p.rating.goko_casual_rating
            elif self.rating_system == 'unrated':
                return p.rating.goko_casual_rating
            elif self.rating_system == 'isotropish':
                return p.rating.isotropish_rating

        opps = set([s.player for s in match.seeks]) - set([player])
        for o in opps:
            if r(o) is None:
                return False
            if r(o) and self.min_pts and (r(o) < self.min_pts):
                return False
            if r(o) and self.max_pts and (r(o) > self.max_pts):
                return False
        return True


class VPCounter(Requirement):

    def __init__(self, vpcounter=None):
        self.vpcounter = vpcounter

    def is_match_ok(self, player, match):
        if self.vpcounter is None:
            return True
        else:
            for s in match.seeks:
                for r in s.requirements:
                    if r.__class__.__name__ == 'VPCounter':
                        # NOTE: Requests with an empty vpcounter field
                        #       shouldn't be allowed, but the client is
                        #       generating them anyway.
                        if 'vpcounter' in dir(r):
                            if (r.vpcounter != self.vpcounter):
                                return False
        return True
