import time
import glob
import io
import os
import numpy
import numpy.typing
import functools
import pathlib
import threading
import cython
import datetime

from typing import Any, Optional, TypeVar, Type, cast
# from scoping import scoping as s


def test(
	_id: int,
	T: float,
	a: numpy.ndarray[Any, numpy.dtype[numpy.int32]],
) -> None:
	with cython.nogil:
		# if True:
		started_at = datetime.datetime.now()
		print('started')

		def elapsed() -> float:
			return (datetime.datetime.now() - started_at).total_seconds()

		# a = 0
		while elapsed() < T:
			# a += 1
			for k in range(1024 * 1024):
				a[_id] += 1

	print(['done', started_at, elapsed(), a[_id]])


M = TypeVar('M', bound=Type[Any])


def build(content: str, module: M) -> M:
	import pathlib
	import tempfile
	import hashlib
	import Cython.Build.Inline

	sha256sum = hashlib.sha256(content.encode('utf-8')).digest().hex()

	output_dir = (pathlib.Path('.') / 'tmp' / 'cython' / sha256sum).absolute()

	if not output_dir.exists() or True:
		os.makedirs(str(output_dir), exist_ok=True)

		source_path = output_dir / ('_%s.pyx' % sha256sum)
		if not source_path.exists():
			with io.open(str(source_path), 'w') as f:
				f.write(content)

		t1 = Cython.Build.Inline._get_build_extension()
		t1.extensions = Cython.Build.cythonize(str(source_path))
		t1.build_temp = str(pathlib.Path('/'))
		t1.build_lib = str(output_dir)
		# t2 = Cython.Build.Inline.Extension(
		#    name=sha256sum,
		# )
		t1.run()

		return cast(M, Cython.Build.Inline.load_dynamic('_%s' % sha256sum, glob.glob(str(output_dir / ('_%s*.so' % sha256sum)))[0]))

	raise NotImplementedError


def mypyc_build(file_path: pathlib.Path) -> Any:
	import pathlib
	import tempfile
	import hashlib
	import mypyc.build
	import Cython.Build.Inline

	assert isinstance(file_path, pathlib.Path)

	# sha256sum = hashlib.sha256(content.encode('utf-8')).digest().hex()

	# output_dir = (pathlib.Path('.') / 'tmp' / 'cython' / sha256sum).absolute()
	output_dir = pathlib.Path('.') / 'tmp' / 'mypyc'
	sha256sum = file_path.stem
	lib_pattern = file_path.parent / ('%s.cpython*.so' % sha256sum)
	lib_dir = pathlib.Path('.')

	def lib_path_glob(path: str | pathlib.Path) -> Optional[pathlib.Path]:
		res: list[str] = glob.glob(str(path))

		if len(res) == 0:
			return None
		else:
			return pathlib.Path(res[0])

	need_build: bool = False

	lib_path: Optional[pathlib.Path] = None

	lib_path = lib_path_glob(lib_pattern)

	if not lib_path is None:
		t2 = file_path.stat()
		t3 = lib_path.stat()
		if t3.st_mtime < t2.st_mtime:
			need_build = True

		del t2
		del t3
	else:
		need_build = True

	if need_build:
		for o in [
			output_dir,
			output_dir / 'build' / file_path.parent,
		]:
			os.makedirs(str(o), exist_ok=True)
		# source_path = output_dir / ('_%s.py' % sha256sum)
		source_path = file_path
		# with io.open(str(source_path), 'w') as f:
		#    f.write(content)

		t1 = Cython.Build.Inline._get_build_extension()
		t1.extensions = mypyc.build.mypycify([str(source_path)], target_dir=str(output_dir / 'build'))
		t1.build_temp = str(output_dir)
		t1.build_lib = str(lib_dir)
		# t2 = Cython.Build.Inline.Extension(
		#    name=sha256sum,
		# )
		t1.run()

		lib_path = lib_path_glob(lib_pattern)

	return Cython.Build.Inline.load_dynamic(
		#'_%s' % sha256sum,
		# t1.extensions[0].name,
		file_path.stem,
		str(lib_path),
	)

	raise NotImplementedError


class Source:
	@staticmethod
	def test2(_a: numpy.ndarray[Any, numpy.dtype[numpy.int64]], _id: numpy.dtype[numpy.int32] | int, T: float = 16) -> int:
		raise NotImplementedError


source = build(
	r"""
cimport cython

@cython.boundscheck(False)
@cython.wraparound(False)
def test4(int[:] a, int[:] b):
    cdef int N = a.shape[0]
    assert N == b.shape[0]

    with cython.nogil:
        for i in range(N):
            a[i] += b[i]
    return N

import datetime

def elapsed(started_at: datetime.datetime):
    res = (datetime.datetime.now() - started_at).total_seconds()

    return res

@cython.boundscheck(False)  # Deactivate bounds checking
@cython.wraparound(False)   # Deactivate negative indexing.
def has_time(started_at: datetime.datetime, T: float):
    t1 = elapsed(started_at)

    res = t1 < T

    return res

@cython.boundscheck(False)
@cython.wraparound(False)
def test2(long long [:] _a,  int _id, double T=16) -> int:
    started_at = datetime.datetime.now()

    print('started')

    cdef int C = 1;

    cdef int cond;

    with cython.nogil:
    #if True:
        #a = 0
        while True:

            with cython.gil:
                cond = has_time(started_at, T)
                #cond = 0

                if cond != 1:
                    break

            #a += 1
            for k in range(1024 * 1024 * 1024):
                _a[_id] += C

    print(['done', started_at, elapsed(started_at), _a[_id]])

    return _a[_id]

""",
	Source,
)


def test_cython(N: int = 4, T: int = 16) -> None:
	# a = [0] * N
	a = numpy.zeros((N,), dtype=numpy.int64)

	t = [
		threading.Thread(
			target=functools.partial(
				source.test2,
				a,
				k,
				T,
			)
		)
		for k in range(N)
	]

	for o in t:
		o.start()
	for o in t:
		o.join()

	# cython_module['test2'](a, 0)


def test_mypyc(N: int = 4, W: int = 35) -> None:
	cython2 = mypyc_build((pathlib.Path(__file__).parent / 'cython2.py').relative_to(pathlib.Path.cwd()))

	# from .cython2 import fib

	# a = [0] * N
	t = [
		threading.Thread(
			target=functools.partial(
				cython2.fib,
				W,
			)
		)
		for k in range(N)
	]

	for o in t:
		o.start()
	for o in t:
		o.join()
