from functools import lru_cache

from six import iteritems


class Server(object):

    def __init__(self, url, variables=None):
        self.url = url
        self.variables = variables and dict(variables) or {}

    @property
    def default_url(self):
        return self.get_url()

    @property
    def default_variables(self):
        defaults = {}
        for name, variable in iteritems(self.variables):
            defaults[name] = variable.default
        return defaults

    def get_url(self, **variables):
        if not variables:
            variables = self.default_variables
        return self.url.format(**variables)


class ServerVariable(object):

    def __init__(self, name, default, enum=None):
        self.name = name
        self.default = default
        self.enum = enum and list(enum) or []


class ServersGenerator(object):

    def __init__(self, dereferencer):
        self.dereferencer = dereferencer

    def generate(self, servers_spec):
        servers_deref = self.dereferencer.dereference(servers_spec)
        for server_spec in servers_deref:
            url = server_spec['url']
            variables_spec = server_spec.get('variables', {})

            variables = None
            if variables_spec:
                variables = self.variables_generator.generate(variables_spec)

            yield Server(url, variables=variables)

    @property
    @lru_cache()
    def variables_generator(self):
        return ServerVariablesGenerator(self.dereferencer)


class ServerVariablesGenerator(object):

    def __init__(self, dereferencer):
        self.dereferencer = dereferencer

    def generate(self, variables_spec):
        variables_deref = self.dereferencer.dereference(variables_spec)

        if not variables_deref:
            return [Server('/'), ]

        for variable_name, variable_spec in iteritems(variables_deref):
            default = variable_spec['default']
            enum = variable_spec.get('enum')

            variable = ServerVariable(variable_name, default, enum=enum)
            yield variable_name, variable