# System modules
import time
from threading import Thread

# Custom modules
from utils.logger import log


def func1(sleep=0.05, times=10):
    log.info(f"func1 started")
    for i in range(times):
        log.info(f"func1 iteration {i}")
        time.sleep(sleep)
    return f"func1 result"


def func2(sleep=0.03, times=20):
    log.warning(f"func2 started")
    for i in range(times):
        log.warning(f"func2 iteration {i}")
        time.sleep(sleep)
    return f"func2 result"


def func3(sleep=0.01, times=20):
    log.debug(f"func3 started")
    for i in range(times):
        log.debug(f"func3 iteration {i}")
        time.sleep(sleep)
    return f"func3 result"


class TestClass:
    def __init__(self):
        self.name = "default name"

    def mymethod(self, x):
        func1()
        return f"Done {x}"

    @property
    def myname(self):
        self._myname = self.name
        func1()
        return self._myname

    @myname.setter
    def myname(self, n):
        self._myname = n
        func2()
        self.name = self._myname


##################################################
# Threading
##################################################


class Parallel(Thread):
    def __init__(self, group=None, target=None, name=None,
                 args=(), kwargs={}, Verbose=None):
        Thread.__init__(self, group, target, name, args, kwargs)
        self._return = None

    def run(self):
        # print(type(self._target))
        if self._target is not None:
            self._return = self._target(*self._args, **self._kwargs)

    def join(self, *args):
        Thread.join(self, *args)
        return self._return

# # Set
# paral = [
#     Parallel(target=setattr, args=(aaa, "myname", "a") ),
#     Parallel(target=type(bbb).myname.fset, args=(bbb, "b") )
# ]

# pstart = [ p.start() for p in paral ]
# pjoin = [p.join() for p in paral ]
# print(pjoin)

# # Get
# p1 = Parallel(target=getattr, args=(aaa, "myname") )
# p2 = Parallel(target=type(bbb).myname.fget, args=(bbb,) )

# pstart = [ p.start() for p in [p1, p2] ]
# pjoin = [p.join() for p in [p1, p2]]
# print(pjoin)


##################################################
# Ray
##################################################

# import ray
# ray.init()

# # Start parallel
# rets = [ray.remote(func1).remote(0.7,10),
#         ray.remote(func2).remote(sleep=1,times=10)]

# # Start not in parallel
# f2_not_in_parallel = func3(0.4,15)
# log.info(f"Not in parallel: {f2_not_in_parallel}")

# # Get parallel
# f1, f2 = ray.get(rets)
# log.info(f"In parallel: {f1} {f2}")

# ray.shutdown()

##################################################
# Multiprocessing
##################################################

# from multiprocessing.pool import ThreadPool

# pool = ThreadPool(processes=3)
# async_result = []
# async_result.append(pool.apply_async(func1, (0.7, 10)))
# async_result.append(pool.apply_async(func2, (1.0, 10)))
# return_val = [ a.get() for a in async_result ]

# print(return_val)

##################################################
# Multiprocessing
##################################################

# from multiprocessing import Process

# p1 = Process(target=func1, args=(0.7, 10))
# p2 = Process(target=func2, args=(1.0, 10))
# p1.start()
# p2.start()

# p1.join()
# func3(0.4, 15)
# p2.join()


##################################################
# Threading
##################################################

# import threading

# to_do=[]
# to_do.append(threading.Thread(target=func1, args=(0.7, 10)))
# to_do.append(threading.Thread(target=func2, kwargs={"sleep":1, "times":10}))

# for threads in to_do:
#     threads.start()

# for threads in to_do:
#     threads.join()

# print(threads)

##################################################
# Threading
##################################################

# from threading import Thread

# class ThreadWithReturnValue(Thread):
#     def __init__(self, group=None, target=None, name=None,
#                  args=(), kwargs={}, Verbose=None):
#         Thread.__init__(self, group, target, name, args, kwargs)
#         self._return = None
#     def run(self):
#         print(type(self._target))
#         if self._target is not None:
#             self._return = self._target(*self._args, **self._kwargs)
#     def join(self, *args):
#         Thread.join(self, *args)
#         return self._return

# p1 = ThreadWithReturnValue(target=func1, args=(0.7, 10))
# p2 = ThreadWithReturnValue(target=func2, args=(0.4,), kwargs={"times":10})

# p1.start()
# p2.start()

# pres = [p.join() for p in [p1, p2]]
# print(pres)


##################################################
# Ray
##################################################

# import ray
# ray.init()

# aaa = TestClass()
# bbb = TestClass()

# # Start parallel
# rets = [ray.remote(type(aaa).myname.fset).remote(aaa, "A"),
#         ray.remote(type(bbb).myname.fset).remote(bbb, "B")]

# # Start not in parallel
# f2_not_in_parallel = func3(0.4,15)
# log.debug(f"Not in parallel: {f2_not_in_parallel}")

# # Join parallel
# f1, f2 = ray.get(rets)
# log.info(f"In parallel: {f1} {f2}")


# # Start parallel
# rets = [ray.remote(type(aaa).myname.fget).remote(aaa),
#         ray.remote(type(bbb).myname.fget).remote(bbb)]

# # Join parallel
# f1, f2 = ray.get(rets)
# log.info(f"In parallel: {f1} {f2}")


# ray.shutdown()
