''' General helper functions for tests '''
from contextlib import contextmanager
from mock import patch, MagicMock
import io


@contextmanager
def patch_open():
    '''Patch open() to allow mocking both open() itself and the file that is
    yielded.

    Yields the mock for "open" and "file", respectively.'''
    mock_open = MagicMock(spec=open)
    mock_file = MagicMock(spec=io.FileIO)

    @contextmanager
    def stub_open(*args, **kwargs):
        mock_open(*args, **kwargs)
        yield mock_file

    with patch('builtins.open', stub_open):
        yield mock_open, mock_file


@contextmanager
def mock_open(filename, contents=None):
    ''' Slightly simpler mock of open to return contents for filename '''
    def mock_file(name, mode='r', buffering=-1):  # Python 2 signature.
        if name == filename:
            if 'b' in mode:
                return io.BytesIO(contents)
            return io.StringIO(contents)
        else:
            return open(name, mode, buffering)

    with patch('builtins.open', mock_file):
        yield


class FakeRelation(object):
    '''
    A fake relation class. Lets tests specify simple relation data
    for a default relation + unit (foo:0, foo/0, set in setUp()), eg:

        rel = {
            'private-address': 'foo',
            'password': 'passwd',
        }
        relation = FakeRelation(rel)
        self.relation_get.side_effect = relation.get
        passwd = self.relation_get('password')

    or more complex relations meant to be addressed by explicit relation id
    + unit id combos:

        rel = {
            'mysql:0': {
                'mysql/0': {
                    'private-address': 'foo',
                    'password': 'passwd',
                }
            }
        }
        relation = FakeRelation(rel)
        self.relation_get.side_affect = relation.get
        passwd = self.relation_get('password', rid='mysql:0', unit='mysql/0')

    set_relation_context can be used to simulate being in a relation hook
    context. This allows omitting a relation id or unit when calling relation
    helpers as the related unit is present.

    To set the context:

        relation = FakeRelation(rel)
        relation.set_relation_context('mysql-svc2/0', 'shared-db:12')

    To clear it:

        relation.clear_relation_context()
    '''
    def __init__(self, relation_data):
        self.relation_data = relation_data
        self.remote_unit = None
        self.current_relation_id = None

    def set_relation_context(self, remote_unit, relation_id):
        self.remote_unit = remote_unit
        self.current_relation_id = relation_id

    def clear_relation_context(self):
        self.remote_unit = None
        self.current_relation_id = None

    def get(self, attribute=None, unit=None, rid=None):
        if not rid or rid == 'foo:0':
            if self.current_relation_id:
                if not unit:
                    unit = self.remote_unit
                udata = self.relation_data[self.current_relation_id][unit]
                if attribute:
                    return udata[attribute]
                else:
                    return udata[unit]
            if attribute is None:
                return self.relation_data
            elif attribute in self.relation_data:
                return self.relation_data[attribute]
            return None
        else:
            if rid not in self.relation_data:
                return None
            try:
                relation = self.relation_data[rid][unit]
            except KeyError:
                return None
            if attribute and attribute in relation:
                return relation[attribute]
            return relation

    def relation_id(self):
        return self.current_relation_id

    def relation_ids(self, reltype=None):
        rids = self.relation_data.keys()
        if reltype:
            return [r for r in rids if r.split(':')[0] == reltype]
        return rids

    def related_units(self, relid=None):
        try:
            return self.relation_data[relid].keys()
        except KeyError:
            return []

    def relation_units(self, relation_id):
        if relation_id not in self.relation_data:
            return None
        return self.relation_data[relation_id].keys()
