"""We'll begin by creating a few functions or procedure for functional programming
Here's a simple example"""

def add(num1: int, num2: int) -> int: #over here we've declared a function with type hints (variable types)
    total = num1 + num2
    return total #the return keyword is key in functions

def subtract(num1: int, num2: int) -> int: #this is a subtract function similar to the add one above
    difference = num1 - num2
    return difference

def main(): #this one is a procedure so won't return anything
    num1 = int(input("Enter a number: ")) #here we take the first number as a user input (covered in episode 2)
    num2 = int(input("Enter another number: "))
    total = add(num1, num2)
    difference = subtract(num1, num2)
    #above we just called the 2 functions we defined above with the numbers, now we can output the results
    print("The total of the 2 numbers is:",total)
    print("The difference between the 2 numbers is:",difference)

#now we'll use the main statement and call our procedure
if __name__ == "__main__":
    main() #now we run it

"""So in this example, we have the add and subtract functions
that perform basic arithmetic operations and then the main procedure
calls these functions after taking user inputs and presents the results in
an orderly and user friendly manner. It's an easy model to follow and straightforward for simple tasks.
However, in a real world situation, it isn't always ideal and that's where OOP comes in."""

# Now in OOP we'll use classes to build the calculator and define it in code

class calculator():
    def __init__(self): #this is the constructor method, it's needed in all classes
        #we'll use this to 'save' variables
        self.num1 = 0 #this is a public attribute (can be accessed anywhere)
        self.__num2 = 0 #the 2 underscores transform it into a private attribute (can only be accessed within this class)
        #now we've set up variables as attributes

    def __add(self): #here we've setup a private method
        return self.num1 + self.__num2
    
    def __subtract(self):
        return self.num1 - self.__num2
    
    def calculate(self, num1, num2):
        self.num1 = num1
        self.__num2 = num2
        total = self.__add()
        difference = self.__subtract()
        #above here, we've called the 2 private methods wee defined before and set the attributes to the passed in variables
        return total, difference

if __name__ == "__main__":
    thisCalculator = calculator() #this is where we instantiate the class
    num1 = int(input("Enter a number: "))
    num2 = int(input("Enter another number: "))
    total, difference = thisCalculator.calculate(num1, num2) #the method is then called here
    print("The total of the 2 numbers is:",total)
    print("The difference between the 2 numbers is:",difference)

"""In this OOP example, we defined a Calculator class with methods add and subtract. 
We then created an instance of the Calculator class and call its method. 
This modular approach is great for more complex projects where you want to encapsulate data and behavior.
It also allows for methods that can't be accessed anywhere else, minimising unintended side effects"""
