import os
import io
from unittest import TestCase
from py_linq import Enumerable
from tests import _empty, _simple, _complex
from py_linq import exceptions


class IssueTests(TestCase):
    def setUp(self):
        self.empty = Enumerable(_empty)
        self.simple = Enumerable(_simple)
        self.complex = Enumerable(_complex)

    def test_issue19_1(self):
        foo = Enumerable([1])
        bar = Enumerable([1])
        self.assertEqual(foo.intersect(bar).count(), 1)

    def test_issue19_2(self):
        foo = Enumerable([1])
        bar = Enumerable([1]).distinct()
        self.assertEqual(foo.intersect(bar).count(), 1)

    def test_issue19_3(self):
        foo = Enumerable([1]).distinct()
        bar = Enumerable([1])
        self.assertEqual(foo.intersect(bar).count(), 1)

    def test_issue22(self):
        def my_iter():
            for i in range(10):
                yield i

        def low_iter():
            for i in range(5):
                yield i

        def high_iter():
            for k in range(5):
                yield k + 5

        data = my_iter()
        a = Enumerable(data)

        low = a.where(lambda x: x < 5)
        high = a.where(lambda x: x >= 5)

        self.assertListEqual([
            (0, 5),
            (1, 6),
            (2, 7),
            (3, 8),
            (4, 9)
        ], low.zip(high).to_list())

        self.assertListEqual([
            (0, 5),
            (1, 6),
            (2, 7),
            (3, 8),
            (4, 9)
        ], list(zip(low, high)))

    def test_generator_to_Enumerable(self):
        def first_3():
            for i in range(3):
                yield i
        p2 = Enumerable(first_3())
        self.assertListEqual([0, 1, 2], p2.to_list())

        p3 = Enumerable((i for i in first_3()))
        self.assertListEqual([0, 1, 2], p3.to_list())

    def test_issue22_join(self):
        class Val(object):
            def __init__(self, number, power):
                self.number = number
                self.power = power

            def __str__(self):
                return "VAL {0}: {1}".format(self.number, self.power)

        def powers_of_2():
            for i in range(2):
                yield Val(i, 2 ** i)

        def powers_of_10():
            for i in range(2):
                yield Val(i, 10 ** i)

        en2 = Enumerable(powers_of_2())
        en10 = Enumerable(powers_of_10())
        joined = en2.join(en10, lambda x: x.number, lambda y: y.number, lambda r: (r[0].power, r[1].power))
        truth = zip([2 ** i for i in range(2)], [10 ** y for y in range(2)])
        self.assertListEqual(list(truth), joined.to_list())

    def test_first_with_lambda(self):
        self.assertRaises(IndexError, self.empty.first, lambda x: x == 0)
        self.assertEqual(2, self.simple.first(lambda x: x == 2))
        self.assertDictEqual({'value': 2}, self.complex.first(lambda x: x['value'] == 2))

    def test_first_or_default_with_lambda(self):
        self.assertIsNone(self.empty.first_or_default(lambda x: x == 0))
        self.assertEqual(self.simple.first(lambda x: x == 2), self.simple.first_or_default(lambda x: x == 2))
        self.assertEqual(self.complex.first(lambda x: x['value'] == 2), self.complex.first_or_default(lambda x: x['value'] == 2))

    def test_last_with_lambda(self):
        self.assertRaises(IndexError, self.empty.last, lambda x: x == 0)
        self.assertEqual(2, self.simple.last(lambda x: x == 2))
        self.assertDictEqual({'value': 2}, self.complex.last(lambda x: x['value'] == 2))
        self.assertEqual(self.simple.first(lambda x: x == 2), self.simple.last(lambda x: x == 2))

    def test_last_or_default_with_lambda(self):
        self.assertIsNone(self.empty.last_or_default(lambda x: x == 0))
        self.assertEqual(self.simple.last(lambda x: x == 2), self.simple.last_or_default(lambda x: x == 2))
        self.assertEqual(self.complex.last(lambda x: x['value'] == 2), self.complex.last_or_default(lambda x: x['value'] == 2))
        self.assertEqual(self.simple.first_or_default(lambda x: x == 2), self.simple.last_or_default(lambda x: x == 2))

    def test_issue_34(self):
        class Obj(object):
            def __init__(self, n, v):
                self.n = n
                self.v = v

        foo = Enumerable([Obj('foo', 1), Obj('bar', 2)])
        self.assertTrue(foo.any(lambda x: x.n == 'foo'))
        self.assertTrue(foo.any(lambda x: x.n == 'foo'))
        filtered_foo = foo.where(lambda x: x.n == 'foo')
        self.assertIsNotNone(filtered_foo.first())
        self.assertEqual('foo', filtered_foo.first().n)
        self.assertTrue(filtered_foo.any(lambda x: x.v == 1))
        self.assertTrue(filtered_foo.any(lambda x: x.v == 1))

    def test_issue_35(self):
        a = Enumerable([9, 8, -7, 6, 5])
        b = a.order_by(lambda x: x)
        self.assertEqual(-7, b.first())
        self.assertEqual(-7, list(b)[0])
        self.assertEqual(-7, b.first())

    def test_issue_36(self):
        def append_to_list(lst, element):
            lst.append(element)

        filepath = os.path.join(os.getcwd(), "tests", "files", "test_file1.txt")
        result = []
        with io.open(filepath) as f:
            lines = Enumerable(f) \
                .skip(1) \
                .where(lambda l: not l.startswith('#')) \
                .aggregate(append_to_list, result)
        self.assertEqual(1, len(result))
        self.assertEqual("This line should be counted", result[0])
