# Variables

# v1 - Define a variable named x, and make it equal to the number 3
x = 3

# v2 - Define a variable named s, and make it a string that says I am a string
s = "I am a string"

# v3 - Define a variable named b, and make it equal to the boolean false value
b = False

# Functions and Logic


# f1 - Define a function called do_something
# f2 - do_something should take two parameters, x1 and x2
# f3 - do_something should have a docstring that says "This does something"
# f4 - do_something should return the result of x1 times x2
def do_something(x1, x2):
    """
     This does something
    """
    return x1*x2


# f5 - Define a function called keyword_fn, it should take a keyword
# argument called keyword, with a default value of None
# f6 - If keyword is None, then keyword_fn should return the string 'No'.
# Otherwise, it should return the keyword argument plus 2
def keyword_fn(keyword=None):
    if keyword is None:
        return 'No'
    else:
        return keyword + 2

# f7 - Define a function called return_many, it should take three parameters
# f8 - return_many should add '2' to each of the parameters, and return the
# three parameters as a tuple
def return_many(a, b, c):
    return a+2, b+2, c+2
    pass

# f9 - Call return_many with the parameters 1, 2, and 3, and assign the
# returned values to the variables r1, r2, and r3
r1, r2, r3 = return_many(1, 2, 3)


# Modules

# m1 - Import the library called some_library
import some_library

# m2 - Call the i_am_a_teapot function inside of some_library, store its return
# value in a variable called teapot
teapot = some_library.i_am_a_teapot()

# m3 - Define a function called gonna_call_stuff, and have it take a single parameter
# m4 - From the gonna_call_stuff function, call the multiply_by_2 function in the
# some_library library with the first parameter equal to the first parameter of
# the gonna_call_stuff function, and return the value returned by multiply_by_2
def gonna_call_stuff(a):
    return some_library.multiply_by_2(a)


# Tuples

# t1 - Define a variable t1, and make it equal to an empty tuple
t1 = ()

# t2 - Define a variable t2, and make it equal to a tuple containing the value False
t2 = (False,)

# t3 - Define a variable t3, and make it equal to a tuple containing 32000 elements
t3 = (0,) * 32000

# t4 - Define a variable t4, and make it equal to a tuple containing the values
# 'foo', 1, and False in it (in that order).
t4 = ('foo', 1, False)

# t5 - Define a variable t5, and make it equal to the 0th element of t4
t5 = t4[0]

# t6 - Define a function called measure_tuple, that takes a single parameter
# t7 - The first parameter of measure_tuple is a tuple. The function should return
# the number of elements present in the tuple


def measure_tuple(a):
    return len(a)

# t8 - Define a function called sum_tuple, that takes a single parameter
# t9 - In sum_tuple, the parameter is a tuple. If there are 5 elements in the tuple,
# return the sum of the elements in the tuple. Otherwise, return None.
def sum_tuple(a):
    if len(a) == 5:
        return sum(a)
    else:
        return None


# Lists

# l1 - Define a variable l1, and make it equal to an empty list
l1 = []


# l2 - Define a variable l2, and make it equal to a list containing the value False
l2 = [False]


# l3 - Define a variable l3, and make it equal to a list containing 32000 elements
l3 = [0] * 32000


# l4 - Define a variable l4, and make it equal to a list containing the values
# 'foo', 1, and False in it (in that order).
l4 = ['foo', 1, False]

# l5 - Define a variable l5, and make it equal to the 0th element of l4
l5 = l4[0]

# l6 - Define a function called measure_list, that takes a single parameter
# l7 - measure_list should return the number of elements present in the list


def measure_list(a):
    return len(a)

# l8 - Define a function called sum_list, that takes a single parameter
# l9 - In sum_list, the parameter is a list. If there are 5 elements in the list,
# return the sum of the elements in the list. Otherwise, return None.


def sum_list(a):
    if len(a) == 5:
        return sum(a)
    else:
        return None


# l10 - Define a function called wopit that takes a single parameter, and returns None.
# The parameter is a list. Add the first element of the list to the end of the list.
# Do nothing if the list is empty.
def wopit(a):
    if len(a) > 0:
        a.append(a[0])
    return None

# l11 - Define a function called bopit that takes a single parameter, and returns None.
# The parameter is a list. Remove an item from the end of the list. Do nothing if the
# list is empty.


def bopit(a):
    if len(a) > 0:
        a.pop()
    return None


# l12 - Define a function called mopit that takes a single parameter, and returns None.
# The parameter is a list. Remove an item from the beginning of the list.
# Do nothing if the list is empty.
def mopit(a):
    if len(a) > 0:
        a.pop(0)
    return None

# l13 - Define a function called zopit that takes a single parameter. The parameter is a list.
# Return True if there is an element in the list that is equal to the string item, and the
# element position in the list is greater than 100. Otherwise, return False.
def zopit(a):
    for i in range(101, len(a)):
        if a[i] == 'item':
            return True
    return False

# Dictionaries

# d1 - Define a variable d1, and make it equal to an empty dictionary
d1 = {}

# d2 - Define a variable d2, ane make it a dictionary with the following key/value pairs:
# key: 'k1', value: 'item'; key: 'k2', value: a tuple with the elements 1 and 2.
d2 = {'k1': 'item', 'k2': (1, 2)}

# d3 - Define a variable d3 that is equal to the value stored in the dictionary with key k2
d3 = d2['k2']

# d4 - Define a function called superd that takes a single parameter. The parameter
# is a dictionary. Add 10000 elements to the dictionary, with the keys numbers 1 - 10000.
# The values associated with each key is the key as a string value.
def superd(a):
    for i in range(1, 10001):
        a[i] = str(i)


# Classes and objects
# c1 - Define a class called MyClass
# c2 - Add a class variable to MyClass called clsvar, equal to the value 3
# c3 - Define a constructor method for MyClass, taking a single parameter.
# The parameter must be set as an instance variable (also called an 'attribute')
# called 'instvar'. If the parameter is equal to the string 'Hi', then the 'instvar'
# attribute must be set to 'Hello'
# c4 - In MyClass, define a method called 'add5', which takes a single parameter.
# The method must add the number 5 to the parameter, and add the result to the
# instance parameter 'instvar'. If instvar is greater than 100, the method must return True.
# Return False otherwise.
# c5 - In MyClass, define a property method called prop which must always return the string 'hi'.
# c6 - In MyClass, when the prop property is set, it must set an instance variable called '_prop'
# to the value it was set to
# c7 - In MyClass, define two functions, a (taking a single parameter) and b (taking no parameters).
# When b is called, it must return the value that was passed to the a function. If the a function was
# never called, b must return None.

class MyClass:
    clsvar = 3

    def __init__(self, instvar):
        if instvar == 'Hi':
            self.instvar = 'Hello'
        else:
            self.instvar = instvar
        self._value = None  # add this line to initialize the new instance variable

    def add5(self, a):
        self.instvar += a + 5
        if self.instvar > 100:
            return True
        else:
            return False

    @property
    def prop(self):
        return 'hi'

    @prop.setter
    def prop(self, value):
        self._prop = value

    def a(self, a):
        self._value = a
    def b(self):
        return self._value

# c8 - Create an instance of MyClass, with an initial argument of 'Hi' and assign it
# to a variable called mine. Create another instance of MyClass and assign it to a
# variable called mine2. The value of the instvar attribute on mine2 must be the same
# string that was passed to the mine instance.
mine = MyClass('Hi')
mine2 = MyClass(mine.instvar)

# State Machine


# sm1 - Define a class called `StateMachine`
# sm2 - The constructor for `StateMachine` will receive a single argument, which is
# a string indicating which state to start in. A readonly property of the `StateMachine`
# class called `state` must be defined, which will return the string representing the current state.
# sm3 - Define a method of `StateMachine` called `reset`. If this method is called, the current
# state must be set to the 'init' state.
# sm4 - Define a method of `StateMachine` called `process`. It must take a single parameter
# (referred to below as `sensed`), which can be True or False. The `process` method will be called over
# and over again, and must follow these rules:
# If in the 'init' state, the function must return the value of the `sensed` parameter. If the `sensed`
# parameter is True, the state must be set to 'running'. If in the 'running' state, the function must return
# 'True'. If the function has been called 20 times in the 'running' state, the current state must be set to 'slowing'.
# If in the 'slowing' state, the function must return 'True'. If the function has been called 10 times in the
# 'slowing' state, the current state must be set to 'init'.


class StateMachine:
    def __init__(self, state):
        self._state = state
        self.normal_runs = 0
        self.slow_runs = 0

    @property
    def state(self):
        return self._state

    def reset(self):
        self._state = 'init'
        self.normal_runs = 0
        self.slow_runs = 0

    def process(self, sensed):
        if self._state == 'init':
            if sensed is True:
                self._state = 'running'
            return sensed
        elif self._state == 'running':
            self.normal_runs += 1
            if self.normal_runs == 20:
                self._state = 'slowing'
                self.normal_runs = 0
            return True
        elif self._state == 'slowing':
            self.slow_runs += 1
            if self.slow_runs == 10:
                self._state = 'init'
                self.slow_runs = 0
            return True
