#!/usr/bin/env python
# -*- coding: utf-8 -*-

#
# Partially based on AboutMethods in the Ruby Koans
#

from runner.koan import *


def my_global_function(a, b):
    return a + b


class AboutMethods(Koan):
    def test_calling_an_global_function(self):
        self.assertEqual(5, my_global_function(2, 3))

    # NOTE: Wrong number of arguments is not a SYNTAX error, but a
    # runtime error.
    def test_calling_functions_with_wrong_number_of_arguments(self):
        try:
            my_global_function()
        except Exception as exception:
            self.assertEqual("TypeError", type(exception).__name__)
            self.assertMatch(
                r'my_global_function\(\) takes exactly 2 arguments \(0 given\)',
                exception[0])

        try:
            my_global_function(1, 2, 3)
        except Exception as e:

            # Note, watch out for parenthesis. They need slashes in front!
            self.assertMatch("my_global_function", e[0])

    # ------------------------------------------------------------------

    def pointless_method(self, a, b):
        sum = a + b

    def test_which_does_not_return_anything(self):
        self.assertEqual(None, self.pointless_method(1, 2))
        # Notice that methods accessed from class scope do not require
        # you to pass the first "self" argument?

    # ------------------------------------------------------------------

    def method_with_defaults(self, a, b='default_value'):
        return [a, b]

    def test_calling_with_default_values(self):
        self.assertEqual([1, "default_value"], self.method_with_defaults(1))
        self.assertEqual([1,2], self.method_with_defaults(1, 2))

    # ------------------------------------------------------------------

    def method_with_var_args(self, *args):
        return args

    def test_calling_with_variable_arguments(self):
        self.assertEqual((), self.method_with_var_args())
        self.assertEqual(('one', ), self.method_with_var_args('one'))
        self.assertEqual(('one', 'two'), self.method_with_var_args('one', 'two'))

    # ------------------------------------------------------------------

    def function_with_the_same_name(self, a, b):
        return a + b

    def test_functions_without_self_arg_are_global_functions(self):
        def function_with_the_same_name(a, b):
            return a * b

        self.assertEqual(12, function_with_the_same_name(3, 4))

    def test_calling_methods_in_same_class_with_explicit_receiver(self):
        def function_with_the_same_name(a, b):
            return a * b

        self.assertEqual(7, self.function_with_the_same_name(3, 4))

    # ------------------------------------------------------------------

    def another_method_with_the_same_name(self):
        return 10

    link_to_overlapped_method = another_method_with_the_same_name

    def another_method_with_the_same_name(self):
        return 42

    def test_that_old_methods_are_hidden_by_redefinitions(self):
        self.assertEqual(42, self.another_method_with_the_same_name())

    def test_that_overlapped_method_is_still_there(self):
        self.assertEqual(10, self.link_to_overlapped_method())

    # ------------------------------------------------------------------

    def empty_method(self):
        pass

    def test_methods_that_do_nothing_need_to_use_pass_as_a_filler(self):
        self.assertEqual(None, self.empty_method())

    def test_pass_does_nothing_at_all(self):
        "You"
        "shall"
        "not"
        pass
        self.assertEqual(True, "Still got to this line" != None)

    # ------------------------------------------------------------------

    def one_line_method(self):
        return 'Madagascar'

    def test_no_indentation_required_for_one_line_statement_bodies(self):
        self.assertEqual('Madagascar', self.one_line_method())

    # ------------------------------------------------------------------

    def method_with_documentation(self):
        "A string placed at the beginning of a function is used for documentation"
        return "ok"

    def test_the_documentation_can_be_viewed_with_the_doc_method(self):
        self.assertMatch("A string", self.method_with_documentation.__doc__)

    # ------------------------------------------------------------------

    class Dog(object):
        def name(self):
            return "Fido"

        def _tail(self):
            # Prefixing a method with an underscore implies private scope
            return "wagging"

        def __password(self):
            return 'password'  # Genius!

    def test_calling_methods_in_other_objects(self):
        rover = self.Dog()
        self.assertEqual("Fido", rover.name())

    def test_private_access_is_implied_but_not_enforced(self):
        rover = self.Dog()

        # This is a little rude, but legal
        self.assertEqual("wagging", rover._tail())

    def test_double_underscore_attribute_prefixes_cause_name_mangling(self):
        """Attributes names that start with a double underscore get
        mangled when an instance is created."""
        rover = self.Dog()
        try:
            #This may not be possible...
            password = rover.__password()
        except Exception as ex:
            self.assertEqual("AttributeError", type(ex).__name__)

        # But this still is!
        self.assertEqual("password", rover._Dog__password())

        # Name mangling exists to avoid name clash issues when subclassing.
        # It is not for providing effective access protection
