
class Iterator:

    def __init__(self, text: str):
        self.text = text.upper()
        self.index = 0

    def __iter__(self):
        return self
    
    def __next__(self):
        try:
            result = self.text[self.index]
        except IndexError:
            raise StopIteration
        self.index += 1
        return result
    
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~        

class SequenceIterator:

    def __init__(self, data):
        self.data = data
        self.start_index = 0

    def is_even(self, value):
        return value%2 == 0

    def __iter__(self):
        return self
    
    def __next__(self):

        if self.start_index >= len(self.data):
            raise StopIteration

        value = self.data[self.start_index]
        
        if self.is_even(len(self.data)) and self.start_index == len(self.data) - 2:
            self.start_index = 1
        elif self.is_even(len(self.data)) == False and self.start_index == len(self.data) - 1:
            self.start_index = 1
        else:
            self.start_index += 2

        return value

# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~        

class Stack:

    def __init__(self):
        self.items = []

    def push(self, item):
        self.items.append(item)

    def pop(self):
        if len(self.items) == 0:
            print("Empty Stack")
        else:
            return self.items.pop() # Последний элемент

    # возвращает последний элемент, но не удаляет его
    def peek(self):
        if len(self.items) == 0:
            print("Empty Stack")
        else:
            return self.items[-1]

    def is_empty(self):
        return len(self.items) == 0

    def size(self):
        return len(self.items)

    def __iter__(self):
        return StackIterator(self)


class StackIterator:

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

    def __iter__(self):
        return self

    def __next__(self):
        if self.stack.is_empty():
            raise StopIteration
        return self.stack.pop()


def test_3():
    stack = Stack()
    stack.push(100)
    stack.push(True)
    stack.push('hello')
    stack.push('world')
# Используем итератор для обхода стека
    for item in stack:
        print(item)


# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~        

class FibonacciIterator:

    def __init__(self, n):
        self.n = n
        self.index = 0
        self.first = 0
        self.second = 0

    def __iter__(self):
        return self

    def __next__(self):
        if self.index == self.n:
            raise StopIteration

        if self.index == 1:
            result = 1    
        else:
            result = self.first + self.second

        self.index += 1
        self.first = self.second
        self.second = result

        return result

def test_4():
    fibonacci_iter = FibonacciIterator(7)

    for number in fibonacci_iter:
        print(number)

# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~        

class Book:
    def __init__(self, title, pages):
        self.title = title
        self.pages = pages


class Library:
    def __init__(self):
        self.books = []

    def add_book(self, book):
        self.books.append(book)

    def __iter__(self):
        return LibraryIterator(self)


class LibraryIterator:
    def __init__(self, library):
        self.library = library
        self.book_number = len(self.library.books)
        self.book = 0
        self.page = 0
        self.next_raise = False

    def __iter__(self):
        return self

    def __next__(self):

        if self.next_raise:
            raise StopIteration

        if self.book == self.book_number - 1 and self.page == len(self.library.books[self.book].pages) - 1:
            self.next_raise = True

        page = self.library.books[self.book].pages[self.page]

        if self.page == len(self.library.books[self.book].pages) - 1:
            self.page = 0
            self.book += 1
        else:
            self.page += 1

        return page
        
def test_5():
    book1 = Book("Book 1", ["Page 1", "Page 2", "Page 3", "Page 4"])
    book2 = Book("Book 2", ["Page A", "Page B", "Page C"])
    book3 = Book("Book 3", ["Chapter 1", "Chapter 2"])

    library = Library()
    library_iterator = LibraryIterator(library)

    library.add_book(book1)
    library.add_book(book2)
    library.add_book(book3)

    # library_iterator.test()

    # Используем вложенный итератор для обхода страниц в библиотеке
    for page in library:
        print(page)

# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~        

class InfinityIterator:

    def __init__(self, start=0):
        self.value = start

    def __iter__(self):
        return self

    def __next__(self):
        foo = self.value
        self.value += 10
        return foo

def test_6():
    for i in InfinityIterator(7):
        print(i)


# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~        




def test_2():
    container = SequenceIterator([1, 5, 4, 6, 43, True, 'hello'])
    # container = SequenceIterator([1, 5, 4, 6, 43, True])
    for i in container:
        print(i)

def test_1():
    phrase = Iterator('Qwerty')
    it_1 = iter(phrase)
    it_2 = iter(phrase)
    for i in it_1:
        print(i)

    for i in it_1:
        print(i)

    for i in it_2:
        print(i)

def main():
    # test_1()
    # test_2()
    # test_3()
    # test_4()
    # test_5()
    # test_6()
    pass

if __name__ == '__main__':
    main()
