<gh_stars>0
"""
Threads.

Various examples exhibiting Thread use.
"""
from threading import Thread
from threading import active_count
from threading import current_thread
import logging
import os
import random
import time

from calculator.calculator import add
from calculator.calculator import subtract

# Environment
ENV = os.environ.get("ENV") or "development"
LOG_LEVEL = os.environ.get("LOG_LEVEL") or "info"

# Configuration
DELAY = os.environ.get("DELAY") or "2"

# Logging
loggers = {"debug": logging.DEBUG, "info": logging.INFO, "error": logging.ERROR}
logging.basicConfig(level=loggers[LOG_LEVEL], format="%(asctime)-15s %(levelname)-8s %(message)s")
logger = logging.getLogger("threads")

logger.debug("Configuration...")
logger.debug("Environment => {}".format(ENV))
logger.debug("LOG LEVEL => {}".format(LOG_LEVEL))
logger.debug("DELAY => {} seconds".format(DELAY))

# Set Current Thread Name
current_thread().setName("neutral")

# Active Thread Count
logger.info("Active THREADS => {}".format(active_count()))
time.sleep(1)

logger.info("Executing Method 1 => Functions as Target [{}]".format(current_thread().getName()))

# Threading
# Method 1: Setting Function as the Target
for i in range(0, 10):

    # Generate Inputs
    a = random.randint(0, 100)
    b = random.randint(0, 50)

    # Start Thread / Independent Task
    t1 = Thread(target=add, name="function", args=(a, b))
    t1.start()

    # Start Thread / Independent Task
    t2 = Thread(target=subtract, name="function", args=(a, b))
    t2.start()

# Active Thread Count
logger.info("Active THREADS => {}".format(active_count()))
time.sleep(1)

logger.info("Executing Method 2 => Without Extending Thread Class [{}]".format(current_thread().getName()))


# Threading
# Method 2: Without Extending Thread Class
class Calculator:
    """Collection of Calculator Functions."""

    def add(self, a: int, b: int) -> int:
        """Add two numbers."""
        delay = random.randint(0, int(DELAY))

        logger.debug("{} [{}] [ STARTED ]".format(current_thread().getName(), current_thread().ident))

        # Simulate Concurrency
        time.sleep(delay)

        logger.info("Adding => {} + {} [{}]".format(a, b, current_thread().getName()))

        logger.debug("{} [{}] [ ENDED ]".format(current_thread().getName(), current_thread().ident))

        return a + b

    def subtract(self, a: int, b: int) -> int:
        """Subtract two numbers."""
        delay = random.randint(0, int(DELAY))

        # Simulate Concurrency
        time.sleep(delay)

        logger.info("Subtracting => {} + {} [{}]".format(a, b, current_thread().getName()))
        return a - b


# Instantiate
calc = Calculator()

# Threading
# Method 2: Without Extending Thread Class
for i in range(0, 10):

    # Generate Inputs
    a = random.randint(0, 100)
    b = random.randint(0, 50)

    # Start Thread / Independent Task
    t1 = Thread(target=calc.add, name="not-extending", args=(a, b))
    t1.start()

    # Start Thread / Independent Task
    t2 = Thread(target=calc.subtract, name="not-extending", args=(a, b))
    t2.start()

# Active Thread Count
logger.info("Active THREADS => {}".format(active_count()))
time.sleep(1)

logger.info("Executing Method 3 =>  Extending Thread Class [{}]".format(current_thread().getName()))


# Method 3: Extending Thread Class
# Had to group functionality in the only-available "run" function
# May make things easier to code (async/await)
class CalculatorThread(Thread):
    """Thread by Extending Thread Class."""

    def run(self):
        """Override run function."""
        a = random.randint(0, 100)
        b = random.randint(0, 50)

        delay = random.randint(0, int(DELAY))

        logger.debug("{} [{}] [ STARTED ]".format(current_thread().getName(), current_thread().ident))

        # Simulate Concurrency
        time.sleep(delay)

        logger.info("Adding => {} + {} [{}]".format(a, b, current_thread().getName()))
        logger.info("Subtracting => {} + {} [{}]".format(a, b, current_thread().getName()))

        logger.debug("{} [{}] [ ENDED ]".format(current_thread().getName(), current_thread().ident))


# Method 3: Extending Thread Class
for i in range(0, 10):

    # Start Thread / Independent Task
    t1 = CalculatorThread(name="extending")
    t1.start()

# Active Thread Count
logger.info("Active THREADS => {}".format(active_count()))
