import pytest

from hilda.exceptions import CreatingObjectiveCSymbolError
from hilda.objective_c_symbol import ObjectiveCSymbol


@pytest.mark.parametrize('literal, result', [
    ('@{@"one": @1}', '{\n    one = 1;\n}'),
    ('@{}', '{\n}'),
])
def test_create_objective_c_symbol(hilda_client, literal: str, result: str):
    """
    :param hilda.hilda_client.HildaClient hilda_client: Hilda client.
    :param literal: ObjectiveC object literal.
    :param result: Object po.
    """
    assert hilda_client.evaluate_expression(literal).objc_symbol.po() == result


def test_fail_create_objective_c_symbol(hilda_client):
    """
    :param hilda.hilda_client.HildaClient hilda_client: Hilda client.
    """
    with pytest.raises(CreatingObjectiveCSymbolError):
        hilda_client.symbol(1).objc_symbol


def test_access_missing_attribute(hilda_client):
    """
    :param hilda.hilda_client.HildaClient hilda_client: Hilda client.
    """
    single_key_dictionary = hilda_client.evaluate_expression('@{@"one": @1}').objc_symbol
    with pytest.raises(AttributeError):
        assert single_key_dictionary.attribute_that_doesnt_exist


def test_get_objective_c_ivar(hilda_client):
    """
    :param hilda.hilda_client.HildaClient hilda_client: Hilda client.
    """
    single_key_dictionary = hilda_client.evaluate_expression('@{@"one": @1}').objc_symbol
    assert single_key_dictionary._key.po() == 'one'


def test_change_objective_c_ivar(hilda_client):
    """
    :param hilda.hilda_client.HildaClient hilda_client: Hilda client.
    """
    single_key_dictionary = hilda_client.evaluate_expression('@{@"one": @1}').objc_symbol
    single_key_dictionary._key = hilda_client.ns('two')
    assert single_key_dictionary.description.po() == '{\n    two = 1;\n}'
    assert single_key_dictionary._key.po() == 'two'


def test_call_objective_c_property(hilda_client):
    """
    :param hilda.hilda_client.HildaClient hilda_client: Hilda client.
    """
    single_key_dictionary = hilda_client.evaluate_expression('@{@"one": @1}').objc_symbol
    assert single_key_dictionary.description.po() == '{\n    one = 1;\n}'


def test_call_objective_c_method_by_method_name(hilda_client):
    """
    :param hilda.hilda_client.HildaClient hilda_client: Hilda client.
    """
    dictionary1 = hilda_client.evaluate_expression('@{@"one": @1}').objc_symbol
    dictionary2 = hilda_client.evaluate_expression('@{@"one": @2}').objc_symbol
    assert dictionary1.isEqualToDictionary_(dictionary1)
    assert not dictionary1.isEqualToDictionary_(dictionary2)


def test_call_objective_c_method_by_objc_call(hilda_client):
    """
    :param hilda.hilda_client.HildaClient hilda_client: Hilda client.
    """
    dictionary1 = hilda_client.evaluate_expression('@{@"one": @1}').objc_symbol
    dictionary2 = hilda_client.evaluate_expression('@{@"one": @2}').objc_symbol
    assert dictionary1.objc_call('isEqualToDictionary:', dictionary1)
    assert not dictionary1.objc_call('isEqualToDictionary:', dictionary2)


def test_call_objective_c_method_returns_native_symbol(hilda_client):
    """
    :param hilda.hilda_client.HildaClient hilda_client: Hilda client.
    """
    string1 = hilda_client.ns('asdasd').objc_symbol
    assert not isinstance(string1.UTF8String(), ObjectiveCSymbol)


@pytest.mark.parametrize('selector', [
    'alloc',  # Test super class methods.
    'init',  # Test super instance methods.
    'dictionaryWithObjects_forKeys_',  # Test class methods.
    'objectForKey_',  # Test instance methods.
    'allKeys',  # Test instance properties.
])
def test_symbol_dir(hilda_client, selector: str):
    """
    :param hilda.hilda_client.HildaClient hilda_client: Hilda client.
    :param selector: Superclass selector to test.
    """
    dict_dir = dir(hilda_client.ns({1: 2}).objc_symbol)
    assert dict_dir.count(selector) == 1


@pytest.mark.parametrize('sub_str', [
    '@interface NSObject',
    '+ alloc;',
    'Class isa = 0x',
    '@property (readonly,copy) NSString * description;',
])
def test_symbol_without_super_str(hilda_client, sub_str: str):
    """
    :param hilda.hilda_client.HildaClient hilda_client: Hilda client.
    :param sub_str: Substring that should appear in the str.
    """
    assert str(hilda_client.objc_get_class('NSObject').new().objc_symbol).count(sub_str) == 1


@pytest.mark.parametrize('sub_str', [
    '@interface',  # Class declaration.
    'NSMutableDictionary',  # Class type.
    'Class isa = 0x',  # Ivar.
    '@property (readonly,copy) NSString * description;',  # Property.
    '+ automaticallyNotifiesObserversForKey:(id);',  # Class method.
    '- removeAllObjects;',  # Instance method.
])
def test_symbol_str(hilda_client, sub_str: str):
    """
    :param hilda.hilda_client.HildaClient hilda_client: Hilda client.
    :param sub_str: Substring that should appear in the str.
    """
    assert hilda_client.ns({1: 2, 3: 4}).objc_symbol._to_str().count(sub_str) == 1


def test_symbol_str_not_recursive(hilda_client):
    """
    :param hilda.hilda_client.HildaClient hilda_client: Hilda client.
    """
    assert '+ alloc;' not in hilda_client.ns({1: 2, 3: 4}).objc_symbol._to_str(False)


def test_symbol_str_recursive(hilda_client):
    """
    :param hilda.hilda_client.HildaClient hilda_client: Hilda client.
    """
    assert '+ alloc;' in hilda_client.ns({1: 2, 3: 4}).objc_symbol._to_str(True)


@pytest.mark.skip('Not fully supported')
def test_set_implementation(hilda_client):
    pid = hilda_client.symbols.getpid()

    hilda_client.objc_get_class('NSJSONSerialization').methods.get('isValidJSONObject:').set_implementation(
        hilda_client.symbols.getpid)
    assert hilda_client.objc_get_class('NSJSONSerialization').isValidJSONObject_() == pid
