import pygame
import cv2
import os
import time
import opensimplex
import sys

strLocalPath = os.path.dirname(sys.modules[__name__].__file__)
if strLocalPath == "": strLocalPath = './'
sys.path.append(strLocalPath+"/../alex_pytools/")
import misctools

def bgr2rgb(col):
    b = col[0]
    col[0] = col[2]
    col[2] = b
    return col

def clamp( x, lowerlimit = 0, upperlimit = 1 ):
    if x < lowerlimit: return lowerlimit
    if x > upperlimit: return upperlimit
    return x

def clamp_list(a,lowerlimit = 0, upperlimit = 1 ):
    ret = list(a)
    for i in range(len(ret)):
        ret[i] = clamp(ret[i],lowerlimit = lowerlimit, upperlimit=upperlimit)
    if isinstance(a, tuple):
        ret = tuple(ret)
    return ret

def warp(x,screensize):
    if x < 0:
        x += screensize
    if x >= screensize:
        x -= screensize
    return x

pygame.init()

# Define some colors
BLACK = ( 0, 0, 0,100)
WHITE = ( 255, 255, 255)
GREEN = ( 0, 255, 0)
RED = ( 255, 0, 0)

#set windows position
os.environ['SDL_VIDEO_WINDOW_POS'] = "%d,%d" % (4,40)

screenw = 1200
screenh = 800

if 1:
    # full screen on my ms tab4
    screenw = 2736//2
    screenh = 1824//2
    os.environ['SDL_VIDEO_WINDOW_POS'] = "%d,%d" % (0,0)

bUseModel = 1  # force the trace to draw something
if bUseModel:
    #~ model = cv2.imread(misctools.getPathData() + "landscape-bw.jpg", cv2.IMREAD_GRAYSCALE)
    model = cv2.imread(misctools.getPathData() + "logo_nike.jpg", cv2.IMREAD_GRAYSCALE)
    model = cv2.imread(misctools.getPathData() + "lion1.jpg", cv2.IMREAD_GRAYSCALE)
    if model.shape[1] > 1300:
        model = cv2.resize(model,None,fx=0.5,fy=0.5)
    while model.shape[1] < 500:
        model = cv2.resize(model,None,fx=2,fy=2)
    screenh,screenw = model.shape[:2]

bUseModelRGB = 0
if bUseModelRGB:
    #~ model = cv2.imread(misctools.getPathData() + "landscape-bw.jpg", cv2.IMREAD_GRAYSCALE)
    model = cv2.imread(misctools.getPathData() + "landscape.jpg")
    screenh,screenw = model.shape[:2]

size = (screenw, screenh)
screen = pygame.display.set_mode(size)
pygame.display.set_caption("My First Game")

# The clock will be used to control how fast the screen updates
clock = pygame.time.Clock()

bContinue = True

nCptImageFps = 0
nCptImageTotal = 0
timeBegin = time.time()

t = 0
dt = 1/60.
x = screenw // 2
y = screenh // 2

x2 = screenw // 2 + 100
y2 = screenh // 2

x3 = screenw // 2 - 100
y3 = screenh // 2

osx = opensimplex.OpenSimplex()
screen.fill(WHITE)

while bContinue:
    # --- Main event loop
    for event in pygame.event.get(): # User did something
        if event.type == pygame.QUIT: # If user clicked close
              bContinue = False

        if event.type == pygame.KEYDOWN:
            if event.key == pygame.K_ESCAPE:
                bContinue = False

    #logic
    dx = osx.noise2d(t,0)*0.2
    dy = osx.noise2d(t,1)*0.2
    #~ print("t: %5.2f, dx: %f, dy: %f" % (t,dx, dy) )

    if bUseModel:
        grey = model[int(clamp(y,0,screenh-1)),int(clamp(x,0,screenw-1))]

        dx *= 20
        dy *= 20
        # slowdown if dark
        dx /= (256-grey)*0.1
        dy /= (256-grey)*0.1

    x += dx
    y += dy

    if bUseModelRGB:
        dx2 = osx.noise2d(t,10)*0.2
        dy2 = osx.noise2d(t,20)*0.2
        dx3 = osx.noise2d(t,30)*0.2
        dy3 = osx.noise2d(t,40)*0.2

        x2 += dx2
        y2 += dy2
        x3 += dx3
        y3 += dy3

    if bUseModel or bUseModelRGB:
        # warp
        if x < 0:
            x += screenw
        if x >= screenw:
            x -= screenw
        if y < 0:
            y += screenh
        if y >= screenh:
            y -= screenh

        if bUseModelRGB:
            x2 = warp(x2,screenw)
            y2 = warp(y2,screenh)
            x3 = warp(x3,screenw)
            y3 = warp(y3,screenh)

    #rendering
    #~ screen.fill(WHITE)

    #~ pygame.draw.rect(screen, RED, [55, 200, 100, 70],0)
    #~ pygame.draw.line(screen, GREEN, [0, 0], [100, 100], 5)
    #~ pygame.draw.ellipse(screen, BLACK, [20,20,250,100], 2)

    xr = int(x)
    yr = int(y)
    while xr < 0 or xr >= screenw:
        #~ print("begin: %s" % xr)
        if xr < 0:
            xr = abs(xr)
        if xr >= screenw:
            xr = 2*(screenw-1) - xr
        #~ print(xr)

    while yr < 0 or yr >= screenh:
        if yr < 0:
            yr = abs(yr)
        if yr >= screenh:
            yr = 2*(screenh-1) - yr

    new_color = screen.get_at((xr-0,yr-0))
    inc = -1
    new_color = (new_color[0]+inc,new_color[1]+inc,new_color[2]+inc)
    new_color = clamp_list(new_color,0,255)
    #~ print(new_color)

    if bUseModelRGB:
        new_color = model[yr,xr]
        new_color = bgr2rgb(new_color)
        if 1:
            # 3 pixels per composantes
            xr2 = int(x2)
            yr2 = int(y2)
            xr3 = int(x3)
            yr3 = int(y3)

            col = screen.get_at((xr,yr))
            col_model = model[yr,xr]
            #~ print(col)
            col = (col_model[2],col[1],col[2])
            screen.set_at( (xr,yr), col )

            col = screen.get_at((xr2,yr2))
            col_model = model[yr2,xr2]
            #~ print(col)
            col = (col[0],col_model[1],col[2])
            screen.set_at( (xr2,yr2), col )

            col = screen.get_at((xr3,yr3))
            col_model = model[yr3,xr3]
            #~ print(col)
            col = (col[0],col[1],col_model[0])
            screen.set_at( (xr3,yr3), col )

    else:
        if bUseModel:
            screen.set_at( (xr,yr), new_color )
        else:
            pygame.draw.circle(screen, new_color, [xr,yr], 1)

    # --- Go ahead and update the screen with what we've drawn.
    if (nCptImageTotal % 200) == 0:
        pygame.display.flip()

    if 1:
        if (nCptImageTotal % (500*1000)) == 0:
            name = "/images_generated/" + misctools.getFilenameFromTime() + ".png"
            print("t:%7.2fs, saving image..." % t)
            pygame.image.save(screen, name)

        if 0:
            if (nCptImageTotal % (50*1000)) == 0:
                #estompe tout l'ecran
                for j in range(screenh):
                    for i in range(screenw):
                        col = screen.get_at([i,j])
                        for k in range(3):
                            if col[k] < 255:
                                col[k] = col[k]+1
                        screen.set_at([i,j],col)

    # --- Limit to 60 frames per second
    clock.tick(6000)
    t += dt

    nCptImageTotal += 1

    # fps counting
    nCptImageFps += 1
    if nCptImageFps > 10000 or time.time() - timeBegin > 5:
        duration = time.time() - timeBegin
        print("INF: %5.1ffps" % ( nCptImageFps / duration) )
        nCptImageFps = 0
        timeBegin = time.time()

#Once we have exited the main program loop we can stop the game engine:
pygame.quit()


# coding: utf-8

import datetime as dt

today = dt.date.today()
day = today

while day.day != 13 or dt.datetime.isoweekday(day) != 5:
	day += dt.timedelta(days=1)

print("Next friday the 13th will be ", day, " (", "in ", day - today, ")", sep="")


"""This file tests internal details of AndroidPlatform. These are not part of the public API,
and should not be accessed or relied upon by user code.
"""

import calendar
from contextlib import contextmanager
import imp
from importlib import import_module, metadata, reload, resources
import importlib.util
from importlib.util import cache_from_source, MAGIC_NUMBER
import marshal
import os
from os.path import dirname, exists, join, realpath, splitext
import pkgutil
import platform
import re
from shutil import rmtree
import subprocess
import sys
from traceback import format_exc
import types
import unittest

from .test_utils import API_LEVEL, FilterWarningsCase

# Flags from PEP 3149.
ABI_FLAGS = ""

REQUIREMENTS = ["chaquopy-libcxx", "murmurhash", "Pygments"]

if API_LEVEL:
    from android.os import Build
    from com.chaquo.python.android import AndroidPlatform
    from java.android import importer

    context = __loader__.finder.context  # noqa: F821
    APP_ZIP = "app"
    REQS_COMMON_ZIP = "requirements-common"
    multi_abi = len([name for name in context.getAssets().list("chaquopy")
                     if name.startswith("requirements")]) > 2
    ABI = AndroidPlatform.ABI
    REQS_ABI_ZIP = f"requirements-{ABI}" if multi_abi else REQS_COMMON_ZIP

    # REQS_COMMON_ZIP and REQS_ABI_ZIP are now both extracted into the same directory, but we
    # maintain the distinction in the tests in case that changes again in the future.
    def asset_path(zip_name, *paths):
        return join(realpath(context.getFilesDir().toString()), "chaquopy/AssetFinder",
                    zip_name.partition("-")[0], *paths)

    LIBCXX_FILENAME = asset_path(REQS_ABI_ZIP, "chaquopy/lib/libc++_shared.so")

def setUpModule():
    if API_LEVEL is None:
        raise unittest.SkipTest("Not running on Android")

class AndroidTestCase(FilterWarningsCase):
    def assertPredicate(self, f, *args):
        self.check_predicate(self.assertTrue, f, *args)

    def assertNotPredicate(self, f, *args):
        self.check_predicate(self.assertFalse, f, *args)

    def check_predicate(self, assertion, f, *args):
        assertion(f(*args), f"{f.__name__}{args!r}")

class TestAndroidPlatform(AndroidTestCase):

    # 64-bit should be preferred on devices which support it. We use Build.SUPPORTED_ABIS to
    # detect support because Build.CPU_ABI always returns the active ABI of the app, which can
    # be 32-bit even on a 64-bit device (https://stackoverflow.com/a/53158339).
    #
    # This test will only pass on a 64-bit device if the 64-bit ABI was included in abiFilters.
    @unittest.skipUnless(API_LEVEL and API_LEVEL >= 21, "Requires Build.SUPPORTED_ABIS")
    def test_abi(self):
        python_bits = platform.architecture()[0]
        self.assertEqual(python_bits,
                         "64bit" if set(Build.SUPPORTED_ABIS) & set(["arm64-v8a", "x86_64"])
                         else "32bit")

    def test_files(self):
        chaquopy_dir = join(str(context.getFilesDir()), "chaquopy")
        self.assertCountEqual(["AssetFinder", "bootstrap-native", "bootstrap.imy",
                               "cacert.pem", "stdlib-common.imy", "ticket.txt"],
                              os.listdir(chaquopy_dir))
        bn_dir = f"{chaquopy_dir}/bootstrap-native"
        self.assertCountEqual([ABI], os.listdir(bn_dir))

        for subdir, entries in [
            (ABI, ["java", "_csv.so", "_ctypes.so", "_datetime.so", "_hashlib.so", "_json.so",
                   "_random.so", "_struct.so", "binascii.so",  "math.so", "mmap.so", "zlib.so"]),
            (f"{ABI}/java", ["chaquopy.so", "chaquopy_android.so"]),
        ]:
            with self.subTest(subdir=subdir):
                # Create a stray file which should be removed on the next startup.
                pid_txt = f"{os.getpid()}.txt"
                with open(f"{bn_dir}/{subdir}/{pid_txt}", "w"):
                    pass
                self.assertCountEqual(entries + [pid_txt], os.listdir(f"{bn_dir}/{subdir}"))

class TestAndroidImport(AndroidTestCase):

    def test_init(self):
        self.check_py("murmurhash", REQS_COMMON_ZIP, "murmurhash/__init__.py", "get_include",
                      is_package=True)
        self.check_py("android1", APP_ZIP, "android1/__init__.py", "x",
                      source_head="# This package is used by test_android.", is_package=True)

    def test_py(self):
        self.check_py("murmurhash.about", REQS_COMMON_ZIP, "murmurhash/about.py", "__summary__")
        self.check_py("android1.mod1", APP_ZIP, "android1/mod1.py",
                      "x", source_head='x = "android1.mod1"')

    def check_py(self, mod_name, zip_name, zip_path, existing_attr, **kwargs):
        filename = asset_path(zip_name, zip_path)
        # build.gradle has pyc { src false }, so APP_ZIP will generate __pycache__ directories.
        cache_filename = cache_from_source(filename) if zip_name == APP_ZIP else None
        mod = self.check_module(mod_name, filename, cache_filename, **kwargs)
        self.assertNotPredicate(exists, filename)
        if cache_filename is None:
            self.assertNotPredicate(exists, cache_from_source(filename))

        new_attr = "check_py_attr"
        self.assertFalse(hasattr(mod, new_attr))
        setattr(mod, new_attr, 1)
        delattr(mod, existing_attr)
        reload(mod)  # Should reuse existing module object.
        self.assertEqual(1, getattr(mod, new_attr))
        self.assertTrue(hasattr(mod, existing_attr))

        if cache_filename:
            # A valid .pyc should not be written again.
            with self.assertNotModifies(cache_filename):
                mod = self.clean_reload(mod)
            self.assertFalse(hasattr(mod, new_attr))

            # And if the header matches, the code in the .pyc should be used, whatever it is.
            header = self.read_pyc_header(cache_filename)
            with open(cache_filename, "wb") as pyc_file:
                pyc_file.write(header)
                code = compile(f"{new_attr} = 2", "<test>", "exec")
                marshal.dump(code, pyc_file)
            mod = self.clean_reload(mod)
            self.assertEqual(2, getattr(mod, new_attr))
            self.assertFalse(hasattr(mod, existing_attr))

            # A .pyc with mismatching header timestamp should be written again.
            new_header = header[0:8] + b"\x00\x01\x02\x03" + header[12:]
            self.assertNotEqual(new_header, header)
            self.write_pyc_header(cache_filename, new_header)
            with self.assertModifies(cache_filename):
                self.clean_reload(mod)
            self.assertEqual(header, self.read_pyc_header(cache_filename))

    def read_pyc_header(self, filename):
        with open(filename, "rb") as pyc_file:
            return pyc_file.read(16)

    def write_pyc_header(self, filename, header):
        with open(filename, "r+b") as pyc_file:
            pyc_file.seek(0)
            pyc_file.write(header)

    def test_so(self):
        filename = asset_path(REQS_ABI_ZIP, "murmurhash/mrmr.so")
        mod = self.check_module("murmurhash.mrmr", filename, filename)
        self.check_extract_if_changed(mod, filename)

    def test_non_package_data(self):
        for dir_name, dir_description in [("", "root"), ("non_package_data", "directory"),
                                          ("non_package_data/subdir", "subdirectory")]:
            with self.subTest(dir_name=dir_name):
                extracted_dir = asset_path(APP_ZIP, dir_name)
                self.assertCountEqual(
                    ["non_package_data.txt"] + (["test.pth"] if not dir_name else []),
                    [entry.name for entry in os.scandir(extracted_dir) if entry.is_file()])
                with open(join(extracted_dir, "non_package_data.txt")) as f:
                    self.assertPredicate(str.startswith, f.read(),
                                         f"# Text file in {dir_description}")

        # Package directories shouldn't be extracted on startup, but on first import. This
        # package is never imported, so it should never be extracted at all.
        self.assertNotPredicate(exists, asset_path(APP_ZIP, "never_imported"))

    def test_package_data(self):
        # App ZIP
        pkg = "android1"
        self.check_data(APP_ZIP, pkg, "__init__.py", b"# This package is")
        self.check_data(APP_ZIP, pkg, "b.so", b"bravo")
        self.check_data(APP_ZIP, pkg, "a.txt", b"alpha")
        self.check_data(APP_ZIP, pkg, "subdir/c.txt", b"charlie")

        # Requirements ZIP
        self.check_data(REQS_COMMON_ZIP, "murmurhash", "about.pyc", MAGIC_NUMBER)
        self.check_data(REQS_ABI_ZIP, "murmurhash", "mrmr.so", b"\x7fELF")
        self.check_data(REQS_COMMON_ZIP, "murmurhash", "mrmr.pxd", b"from libc.stdint")

        import murmurhash.about
        loader = murmurhash.about.__loader__
        zip_name = REQS_COMMON_ZIP
        with self.assertRaisesRegex(ValueError,
                                    r"AssetFinder\('{}'\) can't access '/invalid.py'"
                                    .format(asset_path(zip_name, "murmurhash"))):
            loader.get_data("/invalid.py")
        with self.assertRaisesRegex(FileNotFoundError, "invalid.py"):
            loader.get_data(asset_path(zip_name, "invalid.py"))

    def check_data(self, zip_name, package, filename, start):
        # Extraction is triggered only when a top-level package is imported.
        self.assertNotIn(".", package)

        cache_filename = asset_path(zip_name, package, filename)
        if exists(cache_filename):
            os.remove(cache_filename)

        mod = import_module(package)
        data = pkgutil.get_data(package, filename)
        self.assertTrue(data.startswith(start))

        if splitext(filename)[1] in [".py", ".pyc", ".so"]:
            # Importable files are not extracted.
            self.assertNotPredicate(exists, cache_filename)
        else:
            self.check_extract_if_changed(mod, cache_filename)
            with open(cache_filename, "rb") as cache_file:
                self.assertEqual(data, cache_file.read())

    def check_extract_if_changed(self, mod, cache_filename):
        # A missing file should be extracted.
        if exists(cache_filename):
            os.remove(cache_filename)
        mod = self.clean_reload(mod)
        self.assertPredicate(exists, cache_filename)

        # An unchanged file should not be extracted again.
        with self.assertNotModifies(cache_filename):
            mod = self.clean_reload(mod)

        # A file with mismatching mtime should be extracted again.
        original_mtime = os.stat(cache_filename).st_mtime
        os.utime(cache_filename, None)
        with self.assertModifies(cache_filename):
            self.clean_reload(mod)
        self.assertEqual(original_mtime, os.stat(cache_filename).st_mtime)

    def clean_reload(self, mod):
        sys.modules.pop(mod.__name__, None)
        submod_names = [name for name in sys.modules if name.startswith(mod.__name__ + ".")]
        for name in submod_names:
            sys.modules.pop(name)

        new_mod = import_module(mod.__name__)
        self.assertIsNot(new_mod, mod)
        return new_mod

    def check_module(self, mod_name, filename, cache_filename, *, is_package=False,
                     source_head=None):
        if cache_filename and exists(cache_filename):
            os.remove(cache_filename)
        mod = import_module(mod_name)
        mod = self.clean_reload(mod)
        if cache_filename:
            self.assertPredicate(exists, cache_filename)

        # Module attributes
        self.assertEqual(mod_name, mod.__name__)
        self.assertEqual(filename, mod.__file__)
        self.assertEqual(realpath(mod.__file__), mod.__file__)
        self.assertEqual(filename.endswith(".so"), exists(mod.__file__))
        if is_package:
            self.assertEqual([dirname(filename)], mod.__path__)
            self.assertEqual(realpath(mod.__path__[0]), mod.__path__[0])
            self.assertEqual(mod_name, mod.__package__)
        else:
            self.assertFalse(hasattr(mod, "__path__"))
            self.assertEqual(mod_name.rpartition(".")[0], mod.__package__)
        loader = mod.__loader__
        self.assertIsInstance(loader, importer.AssetLoader)
        spec = mod.__spec__
        self.assertEqual(mod_name, spec.name)
        self.assertIs(loader, spec.loader)
        # spec.origin and the extract_so symlink workaround are covered by pyzmq/test.py.

        # Loader methods (get_data is tested elsewhere)
        self.assertEqual(is_package, loader.is_package(mod_name))
        self.assertIsInstance(loader.get_code(mod_name),
                              types.CodeType if filename.endswith(".py") else type(None))

        source = loader.get_source(mod_name)
        if source_head:
            self.assertTrue(source.startswith(source_head), repr(source))
        else:
            self.assertIsNone(source)

        self.assertEqual(re.sub(r"\.pyc$", ".py", loader.get_filename(mod_name)),
                         mod.__file__)

        return mod

    # Verify that the traceback builder can get source code from the loader in all contexts.
    # (The "package1" test files are also used in test_import.py.)
    def test_exception(self):
        test_frame = (fr'  File "{asset_path(APP_ZIP)}/chaquopy/test/test_android.py", '
                      fr'line \d+, in test_exception\n'
                      fr'    .+?\n')  # Source code line from this file.
        import_frame = r'  File "import.pxi", line \d+, in java.chaquopy.import_override\n'

        # Compilation
        try:
            from package1 import syntax_error  # noqa
        except SyntaxError:
            self.assertRegex(
                format_exc(),
                test_frame + import_frame +
                fr'  File "{asset_path(APP_ZIP)}/package1/syntax_error.py", line 1\n'
                fr'    one two\n'
                fr'        \^\n'
                fr'SyntaxError: invalid syntax\n$')
        else:
            self.fail()

        # Module execution
        try:
            from package1 import recursive_import_error  # noqa
        except ImportError:
            self.assertRegex(
                format_exc(),
                test_frame + import_frame +
                fr'  File "{asset_path(APP_ZIP)}/package1/recursive_import_error.py", '
                fr'line 1, in <module>\n'
                fr'    from os import nonexistent\n'
                fr"ImportError: cannot import name 'nonexistent' from 'os'")
        else:
            self.fail()

        # Module execution (recursive import)
        try:
            from package1 import recursive_other_error  # noqa
        except ValueError:
            self.assertRegex(
                format_exc(),
                test_frame + import_frame +
                fr'  File "{asset_path(APP_ZIP)}/package1/recursive_other_error.py", '
                fr'line 1, in <module>\n'
                fr'    from . import other_error  # noqa: F401\n' +
                import_frame +
                fr'  File "{asset_path(APP_ZIP)}/package1/other_error.py", '
                fr'line 1, in <module>\n'
                fr'    int\("hello"\)\n'
                fr"ValueError: invalid literal for int\(\) with base 10: 'hello'\n$")
        else:
            self.fail()

        # After import complete.
        # Frames from pre-compiled requirements should have no source code.
        try:
            import murmurhash
            murmurhash_file = murmurhash.__file__
            del murmurhash.__file__
            murmurhash.get_include()
        except NameError:
            self.assertRegex(
                format_exc(),
                test_frame +
                fr'  File "{asset_path(REQS_COMMON_ZIP)}/murmurhash/__init__.py", '
                fr'line 5, in get_include\n'
                fr"NameError: name '__file__' is not defined\n$")
        else:
            self.fail()
        finally:
            murmurhash.__file__ = murmurhash_file

        # Frames from pre-compiled stdlib should have filenames starting with "stdlib/", and no
        # source code.
        try:
            import json
            json.loads("hello")
        except json.JSONDecodeError:
            self.assertRegex(
                format_exc(),
                test_frame +
                r'  File "stdlib/json/__init__.py", line \d+, in loads\n'
                r'  File "stdlib/json/decoder.py", line \d+, in decode\n'
                r'  File "stdlib/json/decoder.py", line \d+, in raw_decode\n'
                r'json.decoder.JSONDecodeError: Expecting value: line 1 column 1 \(char 0\)\n$')
        else:
            self.fail()

    def test_imp(self):
        with self.assertRaisesRegex(ImportError, "No module named 'nonexistent'"):
            imp.find_module("nonexistent")

        # See comment about torchvision below.
        from murmurhash import mrmr
        os.remove(mrmr.__file__)

        # If any of the below modules already exist, they will be reloaded. This may have
        # side-effects, e.g. if we'd included sys, then sys.executable would be reset and
        # test_sys below would fail.
        for mod_name, expected_type in [
                ("email", imp.PKG_DIRECTORY),                   # stdlib
                ("argparse", imp.PY_COMPILED),                  #
                ("select", imp.C_EXTENSION),                    #
                ("errno", imp.C_BUILTIN),                       #
                ("murmurhash", imp.PKG_DIRECTORY),              # requirements
                ("murmurhash.about", imp.PY_COMPILED),          #
                ("murmurhash.mrmr", imp.C_EXTENSION),           #
                ("chaquopy.utils", imp.PKG_DIRECTORY),          # app (already loaded)
                ("imp_test", imp.PY_SOURCE)]:                   #     (not already loaded)
            with self.subTest(mod_name=mod_name):
                path = None
                prefix = ""
                words = mod_name.split(".")
                for i, word in enumerate(words):
                    prefix += word
                    with self.subTest(prefix=prefix):
                        file, pathname, description = imp.find_module(word, path)
                        suffix, mode, actual_type = description

                        if actual_type in [imp.C_BUILTIN, imp.PKG_DIRECTORY]:
                            self.assertIsNone(file)
                            self.assertEqual("", suffix)
                            self.assertEqual("", mode)
                        else:
                            data = file.read()
                            self.assertEqual(0, len(data))
                            if actual_type == imp.PY_SOURCE:
                                self.assertEqual("r", mode)
                                self.assertIsInstance(data, str)
                            else:
                                self.assertEqual("rb", mode)
                                self.assertIsInstance(data, bytes)
                            self.assertPredicate(str.endswith, pathname, suffix)

                        # See comment about torchvision in find_module_override.
                        if actual_type == imp.C_EXTENSION:
                            self.assertPredicate(exists, pathname)

                        mod = imp.load_module(prefix, file, pathname, description)
                        self.assertEqual(prefix, mod.__name__)
                        self.assertEqual(actual_type == imp.PKG_DIRECTORY,
                                         hasattr(mod, "__path__"))
                        self.assertIsNotNone(mod.__spec__)
                        self.assertEqual(mod.__name__, mod.__spec__.name)

                        if actual_type == imp.C_BUILTIN:
                            self.assertIsNone(pathname)
                        elif actual_type == imp.PKG_DIRECTORY:
                            self.assertEqual(pathname, dirname(mod.__file__))
                        else:
                            self.assertEqual(re.sub(r"\.pyc$", ".py", pathname),
                                             re.sub(r"\.pyc$", ".py", mod.__file__))

                        if i < len(words) - 1:
                            self.assertEqual(imp.PKG_DIRECTORY, actual_type)
                            prefix += "."
                            path = mod.__path__
                        else:
                            self.assertEqual(expected_type, actual_type)

    # This trick was used by Electron Cash to load modules under a different name. The Electron
    # Cash Android app no longer needs it, but there may be other software which does.
    def test_imp_rename(self):
        # Clean start to allow test to be run more than once.
        for name in list(sys.modules):
            if name.startswith("imp_rename"):
                del sys.modules[name]

        # Renames in stdlib are not currently supported.
        with self.assertRaisesRegex(ImportError, "ChaquopyZipImporter does not support "
                                    "loading module 'json' under a different name 'jason'"):
            imp.load_module("jason", *imp.find_module("json"))

        def check_top_level(real_name, load_name, id):
            mod_renamed = imp.load_module(load_name, *imp.find_module(real_name))
            self.assertEqual(load_name, mod_renamed.__name__)
            self.assertEqual(id, mod_renamed.ID)
            self.assertIs(mod_renamed, import_module(load_name))

            mod_original = import_module(real_name)
            self.assertEqual(real_name, mod_original.__name__)
            self.assertIsNot(mod_renamed, mod_original)
            self.assertEqual(mod_renamed.ID, mod_original.ID)
            self.assertEqual(mod_renamed.__file__, mod_original.__file__)

        check_top_level("imp_rename_one", "imp_rename_1", "1")  # Module
        check_top_level("imp_rename_two", "imp_rename_2", "2")  # Package

        import imp_rename_two  # Original
        import imp_rename_2    # Renamed
        path = [asset_path(APP_ZIP, "imp_rename_two")]
        self.assertEqual(path, imp_rename_two.__path__)
        self.assertEqual(path, imp_rename_2.__path__)

        # Non-renamed sub-modules
        from imp_rename_2 import mod_one, pkg_two
        for mod, name, id in [(mod_one, "mod_one", "21"), (pkg_two, "pkg_two", "22")]:
            self.assertFalse(hasattr(imp_rename_two, name), name)
            mod_attr = getattr(imp_rename_2, name)
            self.assertIs(mod_attr, mod)
            self.assertEqual("imp_rename_2." + name, mod.__name__)
            self.assertEqual(id, mod.ID)
        self.assertEqual([asset_path(APP_ZIP, "imp_rename_two/pkg_two")], pkg_two.__path__)

        # Renamed sub-modules
        mod_3 = imp.load_module("imp_rename_2.mod_3",
                                *imp.find_module("mod_three", imp_rename_two.__path__))
        self.assertEqual("imp_rename_2.mod_3", mod_3.__name__)
        self.assertEqual("23", mod_3.ID)
        self.assertIs(sys.modules["imp_rename_2.mod_3"], mod_3)

        # The standard load_module implementation doesn't add a sub-module as an attribute of
        # its package. Despite this, it can still be imported under its new name using `from
        # ... import`. This seems to contradict the documentation of __import__, but it's not
        # important enough to investigate just now.
        self.assertFalse(hasattr(imp_rename_2, "mod_3"))

    # Make sure the standard library importer implements the new loader API
    # (https://stackoverflow.com/questions/63574951).
    def test_zipimport(self):
        for mod_name in ["zipfile",  # Imported during bootstrap
                         "wave"]:    # Imported after bootstrap
            with self.subTest(mod_name=mod_name):
                old_mod = import_module(mod_name)
                spec = importlib.util.find_spec(mod_name)
                new_mod = importlib.util.module_from_spec(spec)
                spec.loader.exec_module(new_mod)

                self.assertIsNot(new_mod, old_mod)
                for attr_name in ["__name__", "__file__"]:
                    with self.subTest(attr_name=attr_name):
                        self.assertEqual(getattr(new_mod, attr_name),
                                         getattr(old_mod, attr_name))

    # See src/test/python/test.pth.
    def test_pth(self):
        import pth_generated
        self.assertFalse(hasattr(pth_generated, "__file__"))
        self.assertEqual([asset_path(APP_ZIP, "pth_generated")], pth_generated.__path__)
        for entry in sys.path:
            self.assertNotIn("nonexistent", entry)

    def test_iter_modules(self):
        def check_iter_modules(mod, expected):
            mod_infos = list(pkgutil.iter_modules(mod.__path__))
            self.assertCountEqual(expected, [(mi.name, mi.ispkg) for mi in mod_infos])
            finders = [pkgutil.get_importer(p) for p in mod.__path__]
            for mi in mod_infos:
                self.assertIn(mi.module_finder, finders, mi)

        import murmurhash.tests
        check_iter_modules(murmurhash, [("about", False),   # Pure-Python module
                                        ("mrmr", False),    # Native module
                                        ("tests", True)])   # Package
        check_iter_modules(murmurhash.tests, [("test_import", False)])

        self.assertCountEqual([("murmurhash.about", False), ("murmurhash.mrmr", False),
                               ("murmurhash.tests", True),
                               ("murmurhash.tests.test_import", False)],
                              [(mi.name, mi.ispkg) for mi in
                               pkgutil.walk_packages(murmurhash.__path__, "murmurhash.")])

    def test_pr_distributions(self):
        import pkg_resources as pr
        self.assertCountEqual(REQUIREMENTS, [dist.project_name for dist in pr.working_set])
        self.assertEqual("0.28.0", pr.get_distribution("murmurhash").version)

    def test_pr_resources(self):
        import pkg_resources as pr

        # App ZIP
        pkg = "android1"
        names = ["subdir", "__init__.py", "a.txt", "b.so", "mod1.py"]
        self.assertCountEqual(names, pr.resource_listdir(pkg, ""))
        for name in names:
            with self.subTest(name=name):
                self.assertTrue(pr.resource_exists(pkg, name))
                self.assertEqual(pr.resource_isdir(pkg, name),
                                 name == "subdir")
        self.assertFalse(pr.resource_exists(pkg, "nonexistent"))
        self.assertFalse(pr.resource_isdir(pkg, "nonexistent"))

        self.assertCountEqual(["c.txt"], pr.resource_listdir(pkg, "subdir"))
        self.assertTrue(pr.resource_exists(pkg, "subdir/c.txt"))
        self.assertFalse(pr.resource_isdir(pkg, "subdir/c.txt"))
        self.assertFalse(pr.resource_exists(pkg, "subdir/nonexistent.txt"))

        self.check_pr_resource(APP_ZIP, pkg, "__init__.py", b"# This package is")
        self.check_pr_resource(APP_ZIP, pkg, "a.txt", b"alpha\n")
        self.check_pr_resource(APP_ZIP, pkg, "b.so", b"bravo\n")
        self.check_pr_resource(APP_ZIP, pkg, "subdir/c.txt", b"charlie\n")

        # Requirements ZIP
        self.reset_package("murmurhash")
        self.assertCountEqual(["include", "tests", "__init__.pxd", "__init__.pyc", "about.pyc",
                               "mrmr.pxd", "mrmr.pyx", "mrmr.so"],
                              pr.resource_listdir("murmurhash", ""))
        self.assertCountEqual(["MurmurHash2.h", "MurmurHash3.h"],
                              pr.resource_listdir("murmurhash", "include/murmurhash"))

        self.check_pr_resource(REQS_COMMON_ZIP, "murmurhash", "__init__.pyc", MAGIC_NUMBER)
        self.check_pr_resource(REQS_COMMON_ZIP, "murmurhash", "mrmr.pxd", b"from libc.stdint")
        self.check_pr_resource(REQS_ABI_ZIP, "murmurhash", "mrmr.so", b"\x7fELF")

    def check_pr_resource(self, zip_name, package, filename, start):
        import pkg_resources as pr
        with self.subTest(package=package, filename=filename):
            data = pr.resource_string(package, filename)
            self.assertPredicate(data.startswith, start)

            abs_filename = pr.resource_filename(package, filename)
            self.assertEqual(asset_path(zip_name, package.replace(".", "/"), filename),
                             abs_filename)
            if splitext(filename)[1] in [".py", ".pyc", ".so"]:
                # Importable files are not extracted.
                self.assertNotPredicate(exists, abs_filename)
            else:
                with open(abs_filename, "rb") as f:
                    self.assertEqual(data, f.read())

    def reset_package(self, package_name):
        package = import_module(package_name)
        for entry in package.__path__:
            rmtree(entry)
        self.clean_reload(package)

    def test_spec_from_file_location(self):
        # This is the recommended way to load a module from a known filename
        # (https://docs.python.org/3.8/library/importlib.html#importing-a-source-file-directly).
        def import_from_filename(name, location):
            spec = importlib.util.spec_from_file_location(name, location)
            module = importlib.util.module_from_spec(spec)
            spec.loader.exec_module(module)
            return module

        for name, zip_name, zip_path, attr in [
                ("module1", APP_ZIP, "module1.py", "test_relative"),
                ("module1_renamed", APP_ZIP, "module1.py", "test_relative"),
                ("android1", APP_ZIP, "android1/__init__.py", "x"),
                ("murmurhash", REQS_COMMON_ZIP, "murmurhash/__init__.py", "get_include"),
                ("murmurhash.about", REQS_COMMON_ZIP, "murmurhash/about.py", "__license__")]:
            with self.subTest(name=name):
                module = import_from_filename(name, asset_path(zip_name, zip_path))
                self.assertEqual(name, module.__name__)
                self.assertTrue(hasattr(module, attr))

        bad_path = asset_path(APP_ZIP, "nonexistent.py")
        with self.assertRaisesRegex(FileNotFoundError, bad_path):
            import_from_filename("nonexistent", bad_path)

    # Unlike pkg_resources, importlib.resources cannot access subdirectories within packages.
    def test_importlib_resources(self):
        # App ZIP
        pkg = "android1"
        names = ["subdir", "__init__.py", "a.txt", "b.so", "mod1.py"]
        self.assertCountEqual(names, resources.contents(pkg))
        for name in names:
            with self.subTest(name=name):
                self.assertEqual(resources.is_resource(pkg, name),
                                 name != "subdir")

        self.check_ir_resource(APP_ZIP, pkg, "__init__.py", b"# This package is")
        self.check_ir_resource(APP_ZIP, pkg, "a.txt", b"alpha\n")
        self.check_ir_resource(APP_ZIP, pkg, "b.so", b"bravo\n")

        self.assertFalse(resources.is_resource(pkg, "invalid.py"))
        with self.assertRaisesRegex(FileNotFoundError, "invalid.py"):
            resources.read_binary(pkg, "invalid.py")
        with self.assertRaisesRegex(FileNotFoundError, "invalid.py"):
            with resources.path(pkg, "invalid.py"):
                pass

        # Requirements ZIP
        self.reset_package("murmurhash")
        self.assertCountEqual(["include", "tests", "__init__.pxd", "__init__.pyc", "about.pyc",
                               "mrmr.pxd", "mrmr.pyx", "mrmr.so"],
                              resources.contents("murmurhash"))

        self.check_ir_resource(REQS_COMMON_ZIP, "murmurhash", "__init__.pyc", MAGIC_NUMBER)
        self.check_ir_resource(REQS_COMMON_ZIP, "murmurhash", "mrmr.pxd", b"from libc.stdint")
        self.check_ir_resource(REQS_ABI_ZIP, "murmurhash", "mrmr.so", b"\x7fELF")

    def check_ir_resource(self, zip_name, package, filename, start):
        with self.subTest(package=package, filename=filename):
            data = resources.read_binary(package, filename)
            self.assertPredicate(data.startswith, start)

            with resources.path(package, filename) as abs_path:
                if splitext(filename)[1] in [".py", ".pyc", ".so"]:
                    # Importable files are not extracted.
                    self.assertEqual(join(str(context.getCacheDir()), "chaquopy/tmp"),
                                     dirname(abs_path))
                else:
                    self.assertEqual(asset_path(zip_name, package.replace(".", "/"), filename),
                                     str(abs_path))
                with open(abs_path, "rb") as f:
                    self.assertEqual(data, f.read())

    def test_importlib_metadata(self):
        dists = list(metadata.distributions())
        self.assertCountEqual(REQUIREMENTS, [d.metadata["Name"] for d in dists])
        for dist in dists:
            dist_info = str(dist._path)
            self.assertPredicate(str.startswith, dist_info, asset_path(REQS_COMMON_ZIP))
            self.assertPredicate(str.endswith, dist_info, ".dist-info")

            # .dist-info directories shouldn't be extracted.
            self.assertNotPredicate(exists, dist_info)

        dist = metadata.distribution("murmurhash")
        self.assertEqual("0.28.0", dist.version)
        self.assertEqual(dist.version, dist.metadata["Version"])
        self.assertIsNone(dist.files)
        self.assertEqual("Matthew Honnibal", dist.metadata["Author"])
        self.assertEqual(["chaquopy-libcxx (>=7000)"], dist.requires)

    @contextmanager
    def assertModifies(self, filename):
        TEST_MTIME = calendar.timegm((2020, 1, 2, 3, 4, 5))
        os.utime(filename, (TEST_MTIME, TEST_MTIME))
        self.assertEqual(TEST_MTIME, os.stat(filename).st_mtime)
        yield
        self.assertNotEqual(TEST_MTIME, os.stat(filename).st_mtime)

    @contextmanager
    def assertNotModifies(self, filename):
        before_stat = os.stat(filename)
        os.chmod(filename, before_stat.st_mode & ~0o222)
        try:
            yield
            after_stat = os.stat(filename)
            self.assertEqual(before_stat.st_mtime, after_stat.st_mtime)
            self.assertEqual(before_stat.st_ino, after_stat.st_ino)
        finally:
            os.chmod(filename, before_stat.st_mode)

# On Android, getDeclaredMethods and getDeclaredFields fail when the member's type refers to a
# class that cannot be loaded. Test the partial workaround in Reflector.
class TestAndroidReflect(AndroidTestCase):

    MEMBERS = ["tcFieldPublic", "tcFieldProtected", "tcMethodPublic", "tcMethodProtected",
               "iFieldPublic", "iFieldProtected", "iMethodPublic", "iMethodProtected",
               "finalize"]

    def test_android_reflect(self):
        from com.chaquo.python.demo import TestAndroidReflect as TAR

        if API_LEVEL >= 26:
            # TextClassifier is in the platform, so all members should be visible.
            self.assertMembers(TAR, self.MEMBERS)
        elif API_LEVEL >= 21:
            # Overridden methods should be visible, plus public methods that don't involve
            # TextClassifier.
            self.assertMembers(TAR, ["iMethodPublic", "finalize"])
        else:
            # Only overridden methods should be visible.
            self.assertMembers(TAR, ["finalize"])

    def assertMembers(self, cls, names):
        for name in names:
            with self.subTest(name=name):
                self.assertTrue(self.declares_member(cls, name))

        for name in self.MEMBERS:
            if name not in names:
                with self.subTest(name=name):
                    self.assertFalse(self.declares_member(cls, name))

    def declares_member(self, cls, name):
        hasattr(cls, name)  # Adds member to __dict__ if it exists.
        return name in cls.__dict__

class TestAndroidStdlib(AndroidTestCase):

    def test_ctypes(self):
        import ctypes
        from ctypes.util import find_library

        def assertHasSymbol(dll, name):
            self.assertIsNotNone(getattr(dll, name))
        def assertNotHasSymbol(dll, name):
            with self.assertRaises(AttributeError):
                getattr(dll, name)

        # Library extraction caused by CDLL.
        from murmurhash import mrmr
        os.remove(mrmr.__file__)
        ctypes.CDLL(mrmr.__file__)
        self.assertPredicate(exists, mrmr.__file__)

        # Library extraction caused by find_library.
        os.remove(LIBCXX_FILENAME)
        find_library_result = find_library("c++_shared")
        self.assertIsInstance(find_library_result, str)

        # This test covers non-Python libraries: for Python modules, see pyzmq/test.py.
        if (platform.architecture()[0] == "64bit") and (API_LEVEL < 23):
            self.assertNotIn("/", find_library_result)
        else:
            self.assertEqual(LIBCXX_FILENAME, find_library_result)

        # Whether find_library returned an absolute filename or not, the file should have been
        # extracted and the return value should be accepted by CDLL.
        self.assertPredicate(exists, LIBCXX_FILENAME)
        libcxx = ctypes.CDLL(find_library_result)
        assertHasSymbol(libcxx, "_ZSt9terminatev")  # std::terminate()
        assertNotHasSymbol(libcxx, "nonexistent")

        # System libraries.
        self.assertIsNotNone(find_library("c"))
        self.assertIsNotNone(find_library("log"))
        self.assertIsNone(find_library("nonexistent"))

        libc = ctypes.CDLL(find_library("c"))
        liblog = ctypes.CDLL(find_library("log"))
        assertHasSymbol(libc, "printf")
        assertHasSymbol(liblog, "__android_log_write")
        assertNotHasSymbol(libc, "__android_log_write")

        # Global search (https://bugs.python.org/issue34592): only works on newer API levels.
        if API_LEVEL >= 21:
            main = ctypes.CDLL(None)
            assertHasSymbol(main, "printf")
            assertHasSymbol(main, "__android_log_write")
            assertNotHasSymbol(main, "nonexistent")

        assertHasSymbol(ctypes.pythonapi, "PyObject_Str")

    def test_datetime(self):
        import datetime
        # This is the interface to the native _datetime module, which is required by NumPy. The
        # attribute will only exist if _datetime was available when datetime was first
        # imported.
        self.assertTrue(datetime.datetime_CAPI)

    def test_json(self):
        from json import encoder, scanner
        # These attributes will be None if the native _json module was unavailable when json
        # was first imported, which would significantly reduce the module's performance.
        self.assertTrue(encoder.c_make_encoder)
        self.assertTrue(scanner.c_make_scanner)

    def test_lib2to3(self):
        # Requires grammar files to be available in stdlib zip.
        from lib2to3 import pygram  # noqa: F401

    def test_hashlib(self):
        import hashlib
        INPUT = b"The quick brown fox jumps over the lazy dog"
        TESTS = [
            # OpenSSL and built-in, OpenSSL preferred.
            ("sha1", "2fd4e1c67a2d28fced849ee1bb76e7391b93eb12"),

            # OpenSSL and built-in, built-in preferred.
            ("sha3_512", ("01dedd5de4ef14642445ba5f5b97c15e47b9ad931326e4b0727cd94cefc44fff23f"
                          "07bf543139939b49128caf436dc1bdee54fcb24023a08d9403f9b4bf0d450")),
            ("blake2b", ("a8add4bdddfd93e4877d2746e62817b116364a1fa7bc148d95090bc7333b3673f8240"
                         "1cf7aa2e4cb1ecd90296e3f14cb5413f8ed77be73045b13914cdcd6a918")),

            # OpenSSL only.
            ("ripemd160", "37f332f68db77bd9d7edd4969571ad671cf9dd3b"),
        ]
        for name, expected in TESTS:
            with self.subTest(algorithm=name):
                # With initial data
                self.assertEqual(expected, hashlib.new(name, INPUT).hexdigest())
                # Without initial data
                h = hashlib.new(name)
                h.update(INPUT)
                self.assertEqual(expected, h.hexdigest())

                if name in hashlib.algorithms_guaranteed:
                    # With initial data
                    self.assertEqual(expected, getattr(hashlib, name)(INPUT).hexdigest())
                    # Without initial data
                    h = getattr(hashlib, name)()
                    h.update(INPUT)
                    self.assertEqual(expected, h.hexdigest())
                else:
                    self.assertFalse(hasattr(hashlib, name))

    def test_locale(self):
        import locale
        self.assertEqual("UTF-8", locale.getlocale()[1])
        self.assertEqual("UTF-8", locale.getdefaultlocale()[1])
        self.assertEqual("UTF-8", locale.getpreferredencoding())
        self.assertEqual("utf-8", sys.getdefaultencoding())
        self.assertEqual("utf-8", sys.getfilesystemencoding())

    def test_multiprocessing(self):
        from multiprocessing.dummy import Pool
        import random
        import time

        def square_slowly(x):
            time.sleep(random.uniform(0.1, 0.2))
            return x ** 2

        with Pool(8) as pool:
            start = time.time()
            self.assertEqual([0, 1, 4, 9, 16, 25, 36, 49],
                             pool.map(square_slowly, range(8), chunksize=1))
            duration = time.time() - start
            self.assertGreater(duration, 0.1)
            self.assertLess(duration, 0.25)

        from multiprocessing import get_context, synchronize
        ctx = get_context()
        for name in ["Barrier", "BoundedSemaphore", "Condition", "Event", "Lock", "RLock",
                     "Semaphore"]:
            cls = getattr(synchronize, name)
            with self.assertRaisesRegex(OSError, "This platform lacks a functioning sem_open"):
                if name == "Barrier":
                    cls(1, ctx=ctx)
                else:
                    cls(ctx=ctx)

    def test_os(self):
        self.assertEqual("posix", os.name)
        self.assertTrue(os.access(os.environ["HOME"], os.R_OK | os.W_OK | os.X_OK))

        self.assertTrue(os.get_exec_path())
        for name in os.get_exec_path():
            with self.subTest(name=name):
                self.assertTrue(os.access(name, os.X_OK))

    def test_pickle(self):
        import pickle
        # This attribute will only exist if the native _pickle module was available when
        # pickle was first imported.
        self.assertTrue(pickle.PickleBuffer)

    def test_platform(self):
        # Requires sys.executable to exist.
        import platform
        p = platform.platform()
        self.assertRegex(p, r"^Linux")

    def test_select(self):
        import select
        self.assertFalse(hasattr(select, "kevent"))
        self.assertFalse(hasattr(select, "kqueue"))

        import selectors
        self.assertIs(selectors.DefaultSelector, selectors.EpollSelector)

    def test_signal(self):
        import enum
        import signal

        # These functions may be unavailable if _signal was compiled incorrectly.
        for name in ["pthread_sigmask", "sigpending", "sigwait", "valid_signals"]:
            with self.subTest(name=name):
                self.assertTrue(hasattr(signal, name))

        vs = signal.valid_signals()
        self.assertIsInstance(vs, set)
        vs = list(vs)

        self.assertEqual(1, vs[0])
        self.assertIs(signal.SIGHUP, vs[0])
        self.assertIsInstance(vs[0], signal.Signals)
        self.assertIsInstance(vs[0], enum.IntEnum)
        self.assertEqual("<Signals.SIGHUP: 1>", repr(vs[0]))

        self.assertEqual(signal.NSIG - 1, vs[-1])
        self.assertEqual(signal.NSIG - 1, len(vs))

    def test_sqlite(self):
        import sqlite3
        conn = sqlite3.connect(":memory:")
        conn.execute("create table test (a text, b text)")
        conn.execute("insert into test values ('alpha', 'one'), ('bravo', 'two')")
        cur = conn.execute("select b from test where a = 'bravo'")
        self.assertEqual([("two",)], cur.fetchall())

    def test_ssl(self):
        from urllib.request import urlopen
        resp = urlopen("https://chaquo.com/chaquopy/")
        self.assertEqual(200, resp.getcode())
        self.assertRegex(resp.info()["Content-type"], r"^text/html")

    def test_subprocess(self):
        # An executable on the PATH.
        subprocess.run(["ls", os.environ["HOME"]])

        # A nonexistent executable.
        for name in ["nonexistent",                 # PATH search
                     "/system/bin/nonexistent"]:    # Absolute filename
            with self.subTest(name=name), self.assertRaises(FileNotFoundError):
                subprocess.run([name])

    def test_sys(self):
        self.assertEqual(ABI_FLAGS, sys.abiflags)
        self.assertEqual([""], sys.argv)
        self.assertTrue(exists(sys.executable), sys.executable)
        self.assertEqual("siphash24", sys.hash_info.algorithm)

        chaquopy_dir = f"{context.getFilesDir()}/chaquopy"
        self.assertEqual(
            [join(realpath(chaquopy_dir), path) for path in
             ["AssetFinder/app", "AssetFinder/requirements", f"AssetFinder/stdlib-{ABI}"]] +
            [join(chaquopy_dir, path) for path in
             ["stdlib-common.imy", "bootstrap.imy", f"bootstrap-native/{ABI}"]],
            sys.path)
        for p in sys.path:
            self.assertTrue(exists(p), p)

        self.assertRegex(sys.platform, r"^linux")
        self.assertRegex(sys.version,  # Make sure we don't have any "-dirty" caption.
                         r"^{}.{}.{} \(default, ".format(*sys.version_info[:3]))

    def test_sysconfig(self):
        import distutils.sysconfig
        import sysconfig
        ldlibrary = "libpython{}.{}{}.so".format(*sys.version_info[:2], ABI_FLAGS)
        self.assertEqual(ldlibrary, sysconfig.get_config_vars()["LDLIBRARY"])
        self.assertEqual(ldlibrary, distutils.sysconfig.get_config_vars()["LDLIBRARY"])

    def test_tempfile(self):
        import tempfile
        expected_dir = join(str(context.getCacheDir()), "chaquopy/tmp")
        self.assertEqual(expected_dir, tempfile.gettempdir())
        with tempfile.NamedTemporaryFile() as f:
            self.assertEqual(expected_dir, dirname(f.name))

    def test_time(self):
        import time
        t = time.gmtime(1582917965)
        self.assertEqual("Fri, 28 Feb 2020 19:26:05",
                         time.strftime("%a, %d %b %Y %H:%M:%S", t))

class TestAndroidStreams(AndroidTestCase):

    maxDiff = None

    def setUp(self):
        from android.util import Log
        Log.i(*self.get_marker())
        self.expected_log = []

    def expect_log(self, level, stream_name, lines):
        for line in lines:
            self.expected_log.append(f"{level}/python.{stream_name}: {line}")

    def tearDown(self):
        actual_log = None
        marker = "I/{}: {}".format(*self.get_marker())
        for line in subprocess.run(["logcat", "-d", "-v", "tag"], capture_output=True,
                                   errors="backslashreplace").stdout.splitlines():
            if line == marker:
                actual_log = []
            elif actual_log is not None and "/python.std" in line:
                actual_log.append(line)
        self.assertEqual(self.expected_log, actual_log)

    def get_marker(self):
        cls_name, test_name = self.id().split(".")[-2:]
        return cls_name, test_name

    def test_output(self):
        for stream_name, level in [("stdout", "I"), ("stderr", "W")]:
            with self.subTest(stream=stream_name):
                stream = getattr(sys, stream_name)
                self.check_output_properties(stream)

                def write(s, lines=None):
                    self.assertEqual(len(s), stream.write(s))
                    if lines is None:
                        lines = [s]
                    self.expect_log(level, stream_name, lines)

                write("", [])
                write("a")
                write("Hello")
                write("Hello world")
                write(" ")
                write("  ")

                # The maximum line length is 4000.
                write("foobar" * 700, [("foobar" * 666) + "foob",
                                       "ar" + ("foobar" * 33)])

                # TODO #5730: max line length should be measured in bytes.
                # These are the "full-width" digits 0-9, which encode in UTF-8 as 3 bytes each.
                # write("\uff10\uff11\uff12\uff13\uff14\uff15\uff16\uff17\uff18\uff19" * 150,
                #       ["TODO"])

                # Non-ASCII text
                write("ol\u00e9")  # Spanish
                write("\u4e2d\u6587")  # Chinese

                # Non-BMP emoji
                write("\U0001f600", [r"\xed\xa0\xbd\xed\xb8\x80" if API_LEVEL < 23
                                     else "\U0001f600"])

                # Null characters are logged using "modified UTF-8".
                write("\u0000", [r"\xc0\x80"])
                write("a\u0000", [r"a\xc0\x80"])
                write("\u0000b", [r"\xc0\x80b"])
                write("a\u0000b", [r"a\xc0\x80b"])

                # Multi-line messages. Avoid identical consecutive lines, as they may activate
                # "chatty" filtering and break the tests. Empty lines are dropped by logcat, so
                # they should be logged as a single space.
                write("\n", [" "])
                write("\na", [" ", "a"])
                write("\na\n", [" ", "a"])
                write("b\n", ["b"])
                write("c\n\n", ["c", " "])
                write("d\ne", ["d", "e"])
                write("f\n\ng", ["f", " ", "g"])

                for obj in [b"", b"hello", None, 42]:
                    with self.subTest(obj=obj):
                        with self.assertRaisesRegex(TypeError, fr"write\(\) argument must be "
                                                    fr"str, not {type(obj).__name__}"):
                            stream.write(obj)

    def test_output_bytes(self):
        for stream_name, level in [("stdout", "I"), ("stderr", "W")]:
            with self.subTest(stream=stream_name):
                stream = getattr(sys, stream_name).buffer
                self.check_output_properties(stream)

                def write(b, lines=None):
                    self.assertEqual(len(b), stream.write(b))
                    if lines is None:
                        lines = [b.decode()]
                    self.expect_log(level, stream_name, lines)

                write(b"", [])
                write(b"a")
                write(b"Hello")
                write(b"Hello world")
                write(b" ")
                write(b"  ")

                # Non-ASCII text
                write(b"ol\xc3\xa9")  # Spanish
                write(b"\xe4\xb8\xad\xe6\x96\x87")  # Chinese

                # Non-BMP emoji
                write(b"\xf0\x9f\x98\x80", [r"\xed\xa0\xbd\xed\xb8\x80" if API_LEVEL < 23
                                            else "\U0001f600"])

                # Invalid UTF-8
                write(b"\xff", [r"\xff"])
                write(b"a\xff", [r"a\xff"])
                write(b"\xffb", [r"\xffb"])
                write(b"a\xffb", [r"a\xffb"])

                # Null characters are logged using "modified UTF-8".
                write(b"\x00", [r"\xc0\x80"])
                write(b"a\x00", [r"a\xc0\x80"])
                write(b"\x00b", [r"\xc0\x80b"])
                write(b"a\x00b", [r"a\xc0\x80b"])

                for obj in ["", "hello"]:
                    with self.subTest(obj=obj):
                        with self.assertRaisesRegex(TypeError, r"decoding str is not supported"):
                            stream.write(obj)

                for obj in [None, 42]:
                    with self.subTest(obj=obj):
                        with self.assertRaisesRegex(TypeError, fr"decoding to str: need a bytes-"
                                                    fr"like object, {type(obj).__name__} found"):
                            stream.write(obj)

    def check_output_properties(self, stream):
        self.assertTrue(stream.writable())
        self.assertFalse(stream.readable())
        self.assertEqual("UTF-8", stream.encoding)
        self.assertEqual("backslashreplace", stream.errors)

    def test_input(self):
        self.assertTrue(sys.stdin.readable())
        self.assertFalse(sys.stdin.writable())
        self.assertEqual("", sys.stdin.read())
        self.assertEqual("", sys.stdin.read(42))
        self.assertEqual("", sys.stdin.readline())
        self.assertEqual("", sys.stdin.readline(42))


import uuid
from datetime import datetime

from models_manager import Field, Model

from settings import USERS_DB_NAME, DEFAULT_TENANT
from utils.utils import random_string

class MailMessages(Model):
    SCOPE = [
        {'name': 'MailMessage.Read', 'scope': None, 'scopeType': None},
        {'name': 'MailMessage.Delete', 'scope': None, 'scopeType': None},
        {'name': 'MailMessage.Update', 'scope': None, 'scopeType': None},
        {'name': 'MailMessage.Create', 'scope': None, 'scopeType': None},
    ]
    database = USERS_DB_NAME
    identity = 'mail_message_id'

    mail_message_id = Field(default=uuid.uuid4, json='id', category=str)
    state = Field(default=0, json='state', category=int)
    text_body = Field(default=random_string, json='textBody', category=str)
    html_body = Field(default=random_string, json='htmlBody', category=str)
    to_name = Field(default=random_string, json='toName', category=str)
    to_address = Field(default=random_string, json='toAddress', category=str)
    tenant_id = Field(default=DEFAULT_TENANT['id'], json='tenantId', category=str, is_related=True)
    subject = Field(default=random_string, json='subject', category=str)
    created = Field(default=datetime.now, category=str)
    modified = Field(default=datetime.now, category=str)

    def __str__(self):
        return f'<MailMessage {self.mail_message_id}>'


#!/usr/bin/env python
# -*- coding: utf-8 -*-

from lib.classes.weatherYahoo import WeatherYahoo

class Core(object):
	_answer = []
	_sqlo = None
	_services = {
		'tiempo': WeatherYahoo,
		}

	def __init__(self):
		pass

	def instanceServices(self, service, args=[]):
		service = self._services[service]
		obj = service()
		if len(args) > 0 :
			obj._args = args
		return obj

	def getAnswer(self):
		return self._answer

	def makeAnswer(self,response):
		response = response.lower()
		array_response = response.split('+')
		if 'tiempo' in array_response:
			indexCommand,wordCommand=array_response.index('tiempo'),'tiempo'

		objServices = self.instanceServices(wordCommand,array_response[indexCommand+1:])
		objServices.execute()
		self._answer = objServices.getResult()
		return True


import names # pip install names
import random
import csv

n_connections = 500

csvReader = csv.reader(open('sp500.csv'), delimiter=',', quotechar='"')
companies = [row[1] for row in csvReader]

with open('occupations.txt') as f:
    occupations = f.read().splitlines()

def generate_fake_profile():
    first_name = names.get_first_name()
    last_name = names.get_last_name()
    email = first_name.lower() + last_name.lower() + '@fakemail.com'
    company = random.choice(companies)
    position = random.choice(occupations)
    connectedOn = '{0}/{1}/{2}'.format(random.randint(1,12), random.randint(1,28), random.randint(2003,2016))
    row = '{0},{1},{2},"{3}",{4},{5},\n'.format(first_name, last_name, email, company, position, connectedOn)
    return row

def generate_fake_connections_csv(n_connections):
    f = open('Connections.csv','w')
    f.write('FirstName,LastName,EmailAddress,Company,Position,ConnectedOn,Tags\n')
    for _ in range(n_connections):
        row = generate_fake_profile()
        f.write(row)
    f.close()

generate_fake_connections_csv(n_connections)


#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Created on Mon Jan 25 21:59:10 2021

@author: lin.yang
"""

import os
current_path = os.getcwd()+'/'
import sys
sys.path.append(current_path)
import numpy as np
import math
import matplotlib.pyplot as plt
import myInput
import datetime
import multiprocessing as mp

class vertex_class(object):
    def __init__(self,nx,ny,ng,cores,interval,P0,R):
        # V_matrix init value; runnning time and error for the algorithm
        self.running_time = 0
        self.running_coreTime = 0
        self.errors = 0
        self.errors_per_site = 0

        # initial condition data
        self.nx,self.ny = nx, ny
        self.ng = ng
        self.R = R  # results of analysis model
        # convert individual grains map into one grain map
        self.P = np.zeros((3,nx,ny)) # matrix to store IC and normal vector results
        self.C = np.zeros((2,nx,ny)) # curvature result matrix
        for i in range(0,np.shape(P0)[2]):
            self.P[0,:,:] += P0[:,:,i]*(i+1)
            self.C[0,:,:] += P0[:,:,i]*(i+1)

        # data for multiprocessing
        self.cores = cores

        # data for accuracy
        self.interval = interval #Number of timesteps

    #%% Function
    def get_P(self):
        # Outout the result matrix, first level is microstructure,
        # last two layers are normal vectors
        return self.P

    def get_C(self):
        # Get curvature matrix
        return self.C

    def get_errors(self):
        ge_gbsites = self.get_gb_list()
        for gbSite in ge_gbsites :
            [gei,gej] = gbSite
            ge_dx,ge_dy = myInput.get_grad(self.P,gei,gej)
            self.errors += math.acos(round(abs(-ge_dx*self.R[gei,gej,0]+ge_dy*self.R[gei,gej,1]),5))
            # self.errors += math.acos(round(abs(ge_dx*self.R[gei,gej,0]-ge_dy*self.R[gei,gej,1]),5))
        self.errors_per_site = self.errors/len(ge_gbsites)

    def get_curvature_errors(self):
        gce_gbsites = self.get_gb_list()
        for gceSite in gce_gbsites:
            [gcei, gcej] = gceSite
            self.errors += abs(self.R[gcei, gcej, 2] - self.C[1, gcei, gcej])

        self.errors_per_site = self.errors/len(gce_gbsites)

    def get_2d_plot(self,init,algo):
        m = 35
        n = 2
        plt.close()
        plt.subplots_adjust(wspace=0.2,right=1.8)
        fig1 = plt.figure(1)
        fig_page = self.interval
        plt.title(f'{algo}-{init} \n half interval = '+str(fig_page))
        if fig_page < 10:
            String = '000'+str(fig_page)
        elif fig_page < 100:
            String = '00'+str(fig_page)
        elif fig_page < 1000:
            String = '0'+str(fig_page)
        elif fig_page < 10000:
            String = str(fig_page)
        plt.imshow(self.P[0,:,:], cmap='nipy_spectral', interpolation='nearest')

        g2p_gbsites = self.get_gb_list()
        for gbSite in g2p_gbsites:
            [g2pi,g2pj] = gbSite
            if m != 0:
                if self.P[0,g2pi+m,g2pj+n]==self.P[0,g2pi,g2pj]:
                    anchor = np.array([g2pi+m,g2pj+n])
                elif self.P[0,g2pi-m,g2pj+n]==self.P[0,g2pi,g2pj]:
                    anchor = np.array([g2pi-m,g2pj+n])
                elif self.P[0,g2pi+m,g2pj-n]==self.P[0,g2pi,g2pj]:
                    anchor = np.array([g2pi+m,g2pj-n])
                else:
                    anchor = np.array([g2pi-m,g2pj-n])
                m=0
            if np.dot( (anchor - np.array([g2pi,g2pj])), (np.array([self.P[1,g2pi,g2pj],self.P[2,g2pi,g2pj]])) ) >= 0:
                g2p_prof = -1
            else:
                g2p_prof = 1

            g2p_dx,g2p_dy = myInput.get_grad(self.P,g2pi,g2pj)
            plt.arrow(g2pj,g2pi,-g2p_prof*10*g2p_dx,g2p_prof*10*g2p_dy,width=0.1,lw=0.1,alpha=0.8,color='navy')

        plt.xticks([])
        plt.yticks([])
        plt.savefig(f'VT_{init}_{algo}_{String}.png',dpi=1000,bbox_inches='tight')

    def get_gb_list(self,grainID=1):
        ggn_gbsites = []
        for i in range(0,self.nx):
            for j in range(0,self.ny):
                ip,im,jp,jm = myInput.periodic_bc(self.nx,self.ny,i,j)
                if ( ((self.P[0,ip,j]-self.P[0,i,j])!=0) or ((self.P[0,im,j]-self.P[0,i,j])!=0) or ((self.P[0,i,jp]-self.P[0,i,j])!=0) or ((self.P[0,i,jm]-self.P[0,i,j])!=0) ): #\
                        # and self.P[0,i,j]==grainID:
                    ggn_gbsites.append([i,j])
        return ggn_gbsites

    def find_circle(self,A,B,C):
        a = A[1]-B[1]
        b = A[0]-B[0]
        c = A[1]-C[1]
        d = A[0]-C[0]
        e = ((A[1]**2-B[1]**2)-(B[0]**2-A[0]**2))/2.0
        f = ((A[1]**2-C[1]**2)-(C[0]**2-A[0]**2))/2.0
        x0 = -(d*e-b*f)/(b*c-a*d)
        y0 = -(a*f-c*e)/(b*c-a*d)
        r = math.sqrt((A[1]-x0)**2+(A[0]-y0)**2)
        return x0,y0,r

    def find_fittingCircle(self,array):
        K_mat = []
        Y_mat = []

        for point in array[1:]:
            K_mat.append([point[0],point[1],1])
            Y_mat.append([point[0]**2+point[1]**2])

        K_mat = np.mat(K_mat)
        Y_mat = np.mat(Y_mat)
        K_mat_tra = K_mat.T
        X_mat = (K_mat_tra*K_mat).I * K_mat_tra * Y_mat
        fit_x0 = X_mat[0]/2
        fit_y0 = X_mat[1]/2
        fit_R = math.sqrt(X_mat[2]+fit_x0**2+fit_y0**2)

        return fit_y0, fit_x0, fit_R

    def check_collinear(self,A,B,C):
        a = A[1]-B[1]
        b = A[0]-B[0]
        c = A[1]-C[1]
        d = A[0]-C[0]
        if a*d==b*c:
            return True
        else:
            return False

    # loop from any value in one list
    def starting_with(self,arr, start_index):
         # use xrange instead of range in python 3
         for i in range(start_index, len(arr)):
            yield arr[i]
         for i in range(start_index):
            yield arr[i]

    # find the boundary
    def find_connect(self,A):
        ip,im,jp,jm = myInput.periodic_bc(self.nx,self.ny,A[0],A[1])
        output = []

        leftListC = [ [A[0],jm],[im,jm],[im,A[1]],[im,jp],[A[0],jp],[ip,jp],[ip,A[1]],[ip,jm] ]
        leftListA = [ [A[0],jm],[ip,jm],[ip,A[1]],[ip,jp],[A[0],jp],[im,jp],[im,A[1]],[im,jm] ]

        if self.P[0,A[0],jm] != self.P[0,A[0],A[1]]:
            for ti in self.starting_with(leftListC, leftListC.index([A[0],jm]) ):
                if self.P[0,ti[0],ti[1]] == self.P[0,A[0],A[1]]:
                    output.append(ti)
                    break
            for ti in self.starting_with(leftListA, leftListA.index([A[0],jm]) ):
                if self.P[0,ti[0],ti[1]] == self.P[0,A[0],A[1]]:
                    output.append(ti)
                    break

        elif self.P[0,A[0],jp] != self.P[0,A[0],A[1]]:
            for ti in self.starting_with(leftListC, leftListC.index([A[0],jp]) ):
                if self.P[0,ti[0],ti[1]] == self.P[0,A[0],A[1]]:
                    output.append(ti)
                    break
            for ti in self.starting_with(leftListA, leftListA.index([A[0],jp]) ):
                if self.P[0,ti[0],ti[1]] == self.P[0,A[0],A[1]]:
                    output.append(ti)
                    break

        elif self.P[0,im,A[1]] != self.P[0,A[0],A[1]]:
            for ti in self.starting_with(leftListC, leftListC.index([im,A[1]]) ):
                if self.P[0,ti[0],ti[1]] == self.P[0,A[0],A[1]]:
                    output.append(ti)
                    break
            for ti in self.starting_with(leftListA, leftListA.index([im,A[1]]) ):
                if self.P[0,ti[0],ti[1]] == self.P[0,A[0],A[1]]:
                    output.append(ti)
                    break

        elif self.P[0,ip,A[1]] != self.P[0,A[0],A[1]]:
            for ti in self.starting_with(leftListC, leftListC.index([ip,A[1]]) ):
                if self.P[0,ti[0],ti[1]] == self.P[0,A[0],A[1]]:
                    output.append(ti)
                    break
            for ti in self.starting_with(leftListA, leftListA.index([ip,A[1]]) ):
                if self.P[0,ti[0],ti[1]] == self.P[0,A[0],A[1]]:
                    output.append(ti)
                    break
        return output

    def find_connect_single(self,A,B,clockwise):
        ip,im,jp,jm = myInput.periodic_bc(self.nx,self.ny,A[0],A[1])
        output = []

        theListC = [ [A[0],jm],[im,jm],[im,A[1]],[im,jp],[A[0],jp],[ip,jp],[ip,A[1]],[ip,jm] ]
        theListA = [ [A[0],jm],[ip,jm],[ip,A[1]],[ip,jp],[A[0],jp],[im,jp],[im,A[1]],[im,jm] ]

        if clockwise == 1:
            for ci in self.starting_with(theListC, theListC.index(B)+1 ):
                if self.P[0,ci[0],ci[1]] == self.P[0,A[0],A[1]]:
                    output.append(ci)
                    break

        elif clockwise == 0:
            for ci in self.starting_with(theListA, theListA.index(B)+1 ):
                if self.P[0,ci[0],ci[1]] == self.P[0,A[0],A[1]]:
                    output.append(ci)
                    break

        if len(output) != 1:
            print("connect_single != 1 !!")

        return output

    #%% Core base
    def vertex_curvature_core(self,core_input):
        core_stime = datetime.datetime.now()
        li,lj,lk=np.shape(core_input)
        fval = np.zeros((self.nx,self.ny,1))

        for core_a in core_input:
            for core_b in core_a:
                i = core_b[0]
                j = core_b[1]

                stored_boun = [] # store all the boundary by sequence

                ip,im,jp,jm = myInput.periodic_bc(self.nx,self.ny,i,j)
                if ( ((self.P[0,ip,j]-self.P[0,i,j])!=0) or ((self.P[0,im,j]-self.P[0,i,j])!=0) or ((self.P[0,i,jp]-self.P[0,i,j])!=0) or ((self.P[0,i,jm]-self.P[0,i,j])!=0) ):
                    stored_boun.append([i,j])
                    stored_boun.append([i,j])
                    direc = self.find_connect([i,j])

                    if direc == []:
                        print("exception1")
                        return -1

                    # remove the exception case: don't care when 3 neighbor, only site with 1 neighbor are important
                    if direc[0] == direc[1]:
                        inc_i = i-direc[0][0]
                        inc_j = j-direc[0][1]
                        vec_len = math.sqrt(inc_i**2+inc_j**2)
                        H = 1.0
                        fval[i,j,0] = 2  # max curvature in the IC, r=0.5 voxel, so k=1/r=2

                    direc_a = direc[0]
                    direc_b = direc[1]
                    stored_boun.append(direc_a)
                    stored_boun.append(direc_b)
                    #remove the exception case when we only need 1 interval
                    if self.interval == 1:
                        pass
                    else:
                        for k in range(2,self.interval+1):
                            # for clockwise direction
                            next_point = self.find_connect_single(direc_a,stored_boun[-4],1)
                            direc_a = next_point[0]
                            stored_boun.append(direc_a)

                            next_point = self.find_connect_single(direc_b,stored_boun[-4],0)
                            direc_b = next_point[0]
                            stored_boun.append(direc_b)

                        if len(stored_boun) != (self.interval+1)*2:
                            print("stored_boun cannot get right length " + str((self.interval+1)*2-len(stored_boun)) )

                    # two boundary case: line and circle
                    if self.check_collinear(stored_boun[0],stored_boun[-1],stored_boun[-2]):
                        inc_i = stored_boun[-2][0]-stored_boun[-1][0]
                        inc_j = stored_boun[-2][1]-stored_boun[-1][1]
                        vec_len = math.sqrt(inc_i**2+inc_j**2)
                        H = 1.0
                        fval[i,j,0] = 0
                    else:
                        x0,y0,radius = self.find_circle(stored_boun[-1],stored_boun[0],stored_boun[-2])

                        fval[i,j,0] = 1/radius
        core_etime = datetime.datetime.now()
        print("my core time is " + str((core_etime - core_stime).total_seconds()))
        return (fval,(core_etime - core_stime).total_seconds())

    def vertex_normal_vector_core(self,core_input):
        core_stime = datetime.datetime.now()
        li,lj,lk=np.shape(core_input)
        fval = np.zeros((self.nx,self.ny,2))

        for core_a in core_input:
            for core_b in core_a:
                i = core_b[0]
                j = core_b[1]

                stored_boun = [] # store all the boundary by sequence

                ip,im,jp,jm = myInput.periodic_bc(self.nx,self.ny,i,j)
                if ( ((self.P[0,ip,j]-self.P[0,i,j])!=0) or ((self.P[0,im,j]-self.P[0,i,j])!=0) or ((self.P[0,i,jp]-self.P[0,i,j])!=0) or ((self.P[0,i,jm]-self.P[0,i,j])!=0) ):
                    stored_boun.append([i,j])
                    stored_boun.append([i,j])
                    direc = self.find_connect([i,j])

                    if direc == []:
                        print("exception1")
                        return -1

                    # remove the exception case: don't care when 3 neighbor, only site with 1 neighbor are important
                    if direc[0] == direc[1]:
                        inc_i = i-direc[0][0]
                        inc_j = j-direc[0][1]
                        vec_len = math.sqrt(inc_i**2+inc_j**2)
                        H = 1.0
                        fval[i,j,0] = -H*inc_j/vec_len
                        fval[i,j,1] = H*inc_i/vec_len

                    direc_a = direc[0]
                    direc_b = direc[1]
                    stored_boun.append(direc_a)
                    stored_boun.append(direc_b)
                    #remove the exception case when we only need 1 interval
                    if self.interval == 1:
                        pass
                    else:
                        for k in range(2,self.interval+1):
                            # for clockwise direction
                            next_point = self.find_connect_single(direc_a,stored_boun[-4],1)
                            direc_a = next_point[0]
                            stored_boun.append(direc_a)

                            next_point = self.find_connect_single(direc_b,stored_boun[-4],0)
                            direc_b = next_point[0]
                            stored_boun.append(direc_b)

                        if len(stored_boun) != (self.interval+1)*2:
                            print("stored_boun cannot get right length " + str((self.interval+1)*2-len(stored_boun)) )

                    # two boundary case: line and circle
                    if self.check_collinear(stored_boun[0],stored_boun[-1],stored_boun[-2]):
                        inc_i = stored_boun[-2][0]-stored_boun[-1][0]
                        inc_j = stored_boun[-2][1]-stored_boun[-1][1]
                        vec_len = math.sqrt(inc_i**2+inc_j**2)
                        H = 1.0
                        fval[i,j,0] = -H*inc_j/vec_len
                        fval[i,j,1] = H*inc_i/vec_len
                    else:
                        x0,y0,radius = self.find_circle(stored_boun[-1],stored_boun[0],stored_boun[-2])

                        fval[i,j,0] = -(y0-stored_boun[0][0])/radius#(stored_boun[0][1]-x0)/radius
                        fval[i,j,1] = (stored_boun[0][1]-x0)/radius#-(y0-stored_boun[0][0])/radius
        core_etime = datetime.datetime.now()
        print("my core time is " + str((core_etime - core_stime).total_seconds()))
        return (fval,(core_etime - core_stime).total_seconds())

    def vertex_main(self, purpose="inclination"):
        # calculate time
            starttime = datetime.datetime.now()

            pool = mp.Pool(processes=self.cores)
            main_lc, main_wc = myInput.split_cores(self.cores)

            all_sites = np.array([[x,y] for x in range(self.nx) for y in range(self.ny) ]).reshape(self.nx,self.ny,2)
            multi_input = myInput.split_IC(all_sites, self.cores,2, 0,1)

            res_list=[]
            if purpose == "inclination":
                for ki in range(main_wc):
                    for kj in range(main_lc):
                        print(f'the processor [{ki},{kj}] start...')
                        res_one = pool.apply_async(func = self.vertex_normal_vector_core, args = (multi_input[ki][kj], ), callback=self.res_back )
                        res_list.append(res_one)
            elif purpose == "curvature":
                for ki in range(main_wc):
                    for kj in range(main_lc):
                        print(f'the processor [{ki},{kj}] start...')
                        res_one = pool.apply_async(func = self.vertex_curvature_core, args = (multi_input[ki][kj], ), callback=self.res_back )
                        res_list.append(res_one)

            pool.close()
            pool.join()
            print("core done!")
            # print(res_list[0].get())

            # calculate time
            endtime = datetime.datetime.now()

            self.running_time = (endtime - starttime).total_seconds()
            if purpose == "inclination":
                self.get_errors()
            elif purpose == "curvature":
                self.get_curvature_errors()

    def res_back(self,back_result):
        res_stime = datetime.datetime.now()
        (fval,core_time) = back_result
        if core_time > self.running_coreTime:
            self.running_coreTime = core_time

        print("res_back start...")
        if fval.shape[2] == 1:
            self.C[1,:,:] += fval[:,:,0]
        elif fval.shape[2] == 2:
            self.P[1,:,:] += fval[:,:,0]
            self.P[2,:,:] += fval[:,:,1]
        res_etime = datetime.datetime.now()
        print("my res time is " + str((res_etime - res_stime).total_seconds()))

#%%
if __name__ == '__main__':

    # VT_errors =np.zeros(10)
    # VT_runningTime = np.zeros(10)
    # VT2_errors =np.zeros(10)
    # VT2_runningTime = np.zeros(10)
    # ctx = mp.get_context('fork')
    interval = range(11,12)
    nx, ny = 200, 200
    ng = 2
    cores = [4]

    # P0,R=myInput.init2IC(nx, ny, ng, "PolyIC.init")
    P0,R=myInput.Circle_IC(nx,ny, 5)
    # P0,R=myInput.Voronoi_IC(nx,ny,ng)
    # P0,R=myInput.Complex2G_IC(nx,ny)
    # P0,R=myInput.Abnormal_IC(nx,ny)
    # P0[:,:,:]=myInput.SmallestGrain_IC(100,100)
    for ci in cores:
        for inti in interval:
            test1 = vertex_class(nx,ny,ng,ci,inti,P0,R)
            test1.vertex_main("curvature")
            # P = test1.get_P()
            C_vt = test1.get_C()

            #%% figure
            # test1.get_2d_plot('Poly', 'Vertex')

            #%% error

            print('loop_times = ' + str(test1.interval))
            print('running_time = %.2f' % test1.running_time)
            print('running_core time = %.2f' % test1.running_coreTime)
            print('total_errors = %.2f' % test1.errors)
            print('per_errors = %.3f' % test1.errors_per_site)
            print()

            # VT_errors[inti-1] = test1.errors_per_site
            # VT_runningTime[inti-1] = test1.running_coreTime


## initial imports ------------------------------

import os, sys
assert sys.version_info.major >= 3
sys.path.append( os.environ['BK_LCTR__PROJECT_PATH'] )
from book_locator_app import settings_app  # requires above path to be set

## rest of imports ------------------------------

import json, logging, pprint
import requests
from book_locator_app.lib.locator import LocateData
from book_locator_app.lib.normalizer import Item

## rest of setup --------------------------------

## setup logging
logging.basicConfig(
    filename=settings_app.INDEXER_LOG_PATH,
    level=logging.DEBUG,
    format='[%(asctime)s] %(levelname)s [%(module)s-%(funcName)s()::%(lineno)d] %(message)s',
    datefmt='%d/%b/%Y %H:%M:%S'
    )
log = logging.getLogger( 'book_locator_indexer' )
log.info( '\n\nstarting index_2021.py...' )

## Indexer --------------------------------------

class Indexer():

    def __init__( self ):
        log.debug( 'starting Indexer.__init__()' )
        self.raw_groups = self.prepare_groups()
        self.spreadsheet_group_json_urls = self.prepare_spreadsheet_urls( self.raw_groups )
        # self.rock_general_floor_a_data = {}
        # self.rock_general_floor_b_data = {}
        # self.rock_general_floor_2_data = {}
        # self.rock_general_floor_3_data = {}
        # self.rock_general_floor_4_data = {}
        # self.sci_floor_11_data = {}
        # self.sci_floor_12_data = {}
        # self.sci_floor_13_data = {}
        # self.chinese_data = {}
        # self.japanese_data = {}
        # self.korean_data = {}

    ## run on __init__() ------------------------

    def prepare_groups( self ):
        """ Populates Indexer.groups on instantiation.
            Called by __init__() """
        log.debug( 'starting prepare_groups()' )
        raw_groups = [
            {
                'location_code': 'rock',
                'spreadsheet_id': settings_app.ROCK_GENERAL_SPREADSHEET_ID,
                'worksheet_info': [
                    {'id': settings_app.ROCK_GENERAL_FLOOR_A_WORKSHEET_ID, 'label': 'rock_general_floor_a' },
                    {'id': settings_app.ROCK_GENERAL_FLOOR_B_WORKSHEET_ID, 'label': 'rock_general_floor_b' },
                    {'id': settings_app.ROCK_GENERAL_FLOOR_2_WORKSHEET_ID, 'label': 'rock_general_floor_2' },
                    {'id': settings_app.ROCK_GENERAL_FLOOR_3_WORKSHEET_ID, 'label': 'rock_general_floor_3' },
                    {'id': settings_app.ROCK_GENERAL_FLOOR_4_WORKSHEET_ID, 'label': 'rock_general_floor_4' }
                ]
            },
            {
                'location_code': 'sci',
                'spreadsheet_id': settings_app.SCI_SPREADSHEET_ID,
                'worksheet_info': [
                    {'id': settings_app.SCI_FLOOR_11_WORKSHEET_ID, 'label': 'sci_floor_11' },
                    {'id': settings_app.SCI_FLOOR_12_WORKSHEET_ID, 'label': 'sci_floor_12' },
                    {'id': settings_app.SCI_FLOOR_13_WORKSHEET_ID, 'label': 'sci_floor_13' },
                ]
            },
            {
                'location_code': 'rock-chinese',
                'spreadsheet_id': settings_app.ROCK_CJK_SPREADSHEET_ID,
                'worksheet_info': [
                    {'id': settings_app.CHINESE_WORKSHEET_ID, 'label': 'rock_chinese' },
                ]
            },
            {
                'location_code': 'rock-japanese',
                'spreadsheet_id': settings_app.ROCK_CJK_SPREADSHEET_ID,
                'worksheet_info': [
                    {'id': settings_app.JAPANESE_WORKSHEET_ID, 'label': 'rock_japanese' },
                ]
            },
            {
                'location_code': 'rock-korean',
                'spreadsheet_id': settings_app.ROCK_CJK_SPREADSHEET_ID,
                'worksheet_info': [
                    {'id': settings_app.KOREAN_WORKSHEET_ID, 'label': 'rock_korean' },
                ]
            },
        ]
        return raw_groups

    def prepare_spreadsheet_urls( self, raw_groups ):
        """ Populates Indexer.spreadsheet_urls on instantiation.
            Creates a list of spreadsheet-url info, with each element of the list having the structure...
            {
                'group_json_urls': [
                    {'worksheet_label': 'rock_general_floor_a', 'worksheet_url': 'https://worksheet-url'},
                    {'worksheet_label': 'rock_general_floor_b', 'worksheet_url': 'https://worksheet-url'},
                    {'worksheet_label': 'rock_general_floor_2',  'worksheet_url': 'https://worksheet-url'},
                    etc.
                    ],
                'location_code': 'rock'
            },
            Called by __init__() """
        log.debug( 'starting prepare_spreadsheet_urls()' )
        assert type( raw_groups ) == list
        log.debug( f'raw_groups, ``{pprint.pformat(raw_groups)}``' )
        spreadsheet_group_json_urls = []
        for group in raw_groups:
            assert type(group) == dict
            worksheet_info_lst = []
            spreadsheet_id = group['spreadsheet_id']
            # location_code = group['location_code']
            for worksheet_data_dct in group['worksheet_info']:
                worksheet_id = worksheet_data_dct['id']
                worksheet_url = f'https://docs.google.com/spreadsheets/d/{spreadsheet_id}/gviz/tq?tqx=out:json&gid={worksheet_id}'
                worksheet_info_dct = {
                    'worksheet_url': worksheet_url,
                    'worksheet_label': worksheet_data_dct['label'],
                    # 'spreadsheet_location_code': location_code
                    }
                worksheet_info_lst.append( worksheet_info_dct )
            dct = {
                'location_code': group['location_code'],
                'group_json_urls': worksheet_info_lst
            }
            spreadsheet_group_json_urls.append( dct )
        return spreadsheet_group_json_urls

    ## managers ---------------------------------

    def process_worksheet_urls( self ):
        """ Loops through worksheet urls.
            Called by main() """
        log.debug( 'starting process_worksheet_urls()' )
        assert type( self.spreadsheet_group_json_urls ) == list
        log.debug( f'self.spreadsheet_group_json_urls, ``{pprint.pformat(self.spreadsheet_group_json_urls)}``' )
        ## process spreadsheets
        for spreadsheet_info in self.spreadsheet_group_json_urls:
            assert type( spreadsheet_info ) == dict
            log.debug( f'spreadsheet_info, ``{pprint.pformat(spreadsheet_info)}``' )
            locate_index = {}       # holder for all spreadsheet dict-data
            range_start_list = []   # holder for all spreadsheet normalized-callnumber-list data
            location_code = spreadsheet_info['location_code']
            ## process worksheets
            for worksheet_info in spreadsheet_info['group_json_urls']:
                assert type( worksheet_info ) == dict
                log.debug( f'worksheet_info, ``{worksheet_info}``' )
                ( label, url ) = ( worksheet_info['worksheet_label'], worksheet_info['worksheet_url'] )
                # self.process_worksheet_url( url, label )
                self.process_worksheet_url( url, label, locate_index, range_start_list )
                # break  # TEMP
            log.debug( f'locate_index, ``{pprint.pformat(locate_index)}``' )
            log.debug( f'range_start_list (not-normalized), ``{pprint.pformat(range_start_list)}``' )
            self.save_data( location_code, locate_index, range_start_list )
            # break  # TEMP
        return

    # def process_worksheet_url( self, url, label ):
    def process_worksheet_url( self, url, label, locate_index, range_start_list ):
        """ Manages processing of single worksheet.
            Called by process_worksheet_urls() """
        log.debug( 'starting process_worksheet_url()' )
        log.debug( f'label, ``{label}``; url, ``{url}``' )
        assert type( label ) == str
        assert type( url ) == str
        assert type( locate_index ) == dict
        assert type( range_start_list ) == list
        raw_data_dct = self.query_spreadsheet( url, label )
        row_list = self.create_row_data( raw_data_dct, label )
        # self.index_worksheet_data( row_list, label )  # hand off to previous code
        self.index_worksheet_data( row_list, label, locate_index, range_start_list )  # hand off to previous code
        return

    def save_data( self, location_code, locate_index, range_start_list ):
        """ Writes indexed data to disk, for use by webapp.
            Called by process_worksheet_urls() """
        assert type( locate_index ) == dict
        assert type( range_start_list ) == list
        ## save index-dict to disk...
        ld = LocateData( location_code, meta=True )
        ld.dump( locate_index )
        log.debug( 'index-dict data saved' )
        ## save normalized-callnumber-list to disk (used for 'bisect', to get key for index-dict lookup )
        filtered_range_start_list = [ x for x in range_start_list if x ]  # removes None elements before sorting
        filtered_range_start_list.sort()
        ld = LocateData(location_code, index=True)
        ld.dump( filtered_range_start_list )
        log.debug( 'index-normalized-callnumber-list data saved' )
        return

    ## meat -------------------------------------

    def query_spreadsheet( self, worksheet_url, label ):
        """ Queries spreadsheet.
            Called by process_worksheet_url() """
        ( jsn_dct, err ) = ( None, None )
        try:
            log.debug( f'querying worksheet, ``{label}``' )
            log.debug( f'querying worksheet_url, ``{worksheet_url}``' )
            assert type( worksheet_url ) == str
            assert type( label ) == str
            r = requests.get( worksheet_url )
            content = r.content.decode( 'utf-8' )
            if label == 'sci_floor_11':
                log.debug( f'r.content, ``{r.content}``' )
            start_string = 'Query.setResponse('
            start_position = content.find( start_string ) + len( start_string )
            end_position = len( ');' )
            jsn_str = content[start_position: -end_position]
            jsn_dct = json.loads( jsn_str )
            # log.debug( f'jsn_dct, ``{pprint.pformat( jsn_dct )}``' )
            assert type( jsn_dct ) == dict
            return jsn_dct
        except:
            err = f'Problem querying spreaadsheet, ``{label}``'
            log.exception( err )
        return ( jsn_dct, err )

    def create_row_data( self, raw_data_dct, label ):
        """ Converts self.all_raw_json_data list-data into row-data for each spreadsheet.
            Called by process_worksheet_url() """
        log.debug( 'starting create_row_data()' )
        log.debug( f'creating row-data from raw-data from worksheet, ``{label}``' )
        # if 'rock_general' in label:
        #     log.debug( f'TEMP- raw_data_dct, ``{pprint.pformat(raw_data_dct)}``' )
        assert type( raw_data_dct ) == dict
        assert type( label ) == str
        log.debug( f'raw_data_dct.keys(), ``{pprint.pformat(raw_data_dct.keys())}``' )
        log.debug( f'raw_data_dct["table"], ``{raw_data_dct["table"]}``' )
        table = raw_data_dct['table']
        assert type( table ) == dict
        log.debug( f'table-keys, ``{pprint.pformat(table.keys())}``' )
        second_row_table_identifier = table['rows'][2]
        log.debug( f'processing table containing, ``{second_row_table_identifier}``' )
        row_list = []
        for row in table['rows']:
            cells = row['c']
            first_cell_data = cells[0]['v']
            # log.debug( f'first_cell_data, ``{first_cell_data}``' )
            if first_cell_data == 'aisle':  # this skips 2 header-rows
                continue
            row_dct = {
                'aisle': '', 'begin': '', 'end': '', 'floor': '', 'location_code': '' }
            for ( i, cell ) in enumerate( cells ):
                if i == 0:
                    assert type( cell['v'] ) == str, type( cell['v'] )
                    row_dct['aisle'] = cell['v'].strip()
                elif i == 1:
                    assert type( cell['v'] ) == str, type( cell['v'] )
                    row_dct['begin'] = cell['v'].strip()
                elif i == 2:
                    assert type( cell['v'] ) == str, type( cell['v'] )
                    row_dct['end'] = cell['v'].strip()
                elif i == 3:
                    # log.debug( f'cell.keys(), ``{cell.keys()}``' )
                    if 'f' in cell.keys():
                        assert type(cell['f']) == str, type(cell['f'])
                        row_dct['floor'] = cell['f']
                    elif 'v' in cell.keys():
                        assert type(cell['v']) == str, type(cell['v'])
                        row_dct['floor'] = cell['v']
                elif i == 4:
                    assert type( cell['v'] ) == str, type( cell['v'] )
                    row_dct['location_code'] = cell['v'].strip()
                elif i > 4:
                    break
            row_list.append( row_dct )
        log.debug( f'row_list data for worksheet ``{label}``: ``{pprint.pformat(row_list)}``' )
        assert type( row_list ) == list
        return row_list
        ## end def create_row_data()

    # def index_worksheet_data( self, row_list, label ):
    def index_worksheet_data( self, row_list, label, locate_index_reference, range_start_list_reference ):
        """ For each row in the worksheet's data...
            - makes a copy of the row-data
            - gets the 'begin' element
            - gets the normalized-begin-element
            - appends the normalized-begin-element to a list (no sorting yet)
            - adds the normalized-begin-element to the copy of the row-data
            - creates an index entry with the key of normalized-begin, and the value of of the updated row-data
            Called by process_worksheet_url() """
        log.debug( 'starting index_worksheet_data()' )
        log.debug( f'would be handling processing of worksheet, ``{label}``' )
        assert type( row_list ) == list
        assert type( label ) == str
        assert type( locate_index_reference ) == dict
        log.debug( f'initial locate_index_reference, ``{pprint.pformat(locate_index_reference)}``' )
        log.debug( f'initial range_start_list_reference, ``{range_start_list_reference}``' )
        assert type( range_start_list_reference) == list
        for row in row_list:
            assert type( row ) == dict
            aisle_meta = row.copy()
            begin = self.get_begin( row )
            if begin is None:
                log.warning("No begin range")
                continue
            normalized_range_start = self.build_item( row['location_code'], begin )
            range_start_list_reference.append( normalized_range_start )

            aisle_meta['normalized_start'] = normalized_range_start
            locate_index_reference[normalized_range_start] = aisle_meta
        return

    def get_begin( self, row ):
        """ Returns None rather than an empty string.
            Called by index_worksheet_data() """
        assert type( row ) == dict
        val = row.get( 'begin', None )
        if val is None:
            return_val = None
        elif val.strip() == '':
            return_val = None
        else:
            return_val = val
        log.debug( f'return_val, `{return_val}`' )
        return return_val

    def build_item( self, location, begin_callnumber ):
        """ Returns normalized-callnumber
            Called by index_worksheet_data() """
        log.debug( 'starting build_item()' )
        log.debug( f'location, ``{location}; begin_callnumber, ``{begin_callnumber}``' )
        assert type( location ) == str
        assert type( begin_callnumber ) == str
        item = Item( begin_callnumber, location )
        normalized_callnumber = None
        try:
            normalized_callnumber = item.normalize()
            log.debug( 'callnumber normalized' )
        except:
            log.exception( 'could not normalize' )
        if normalized_callnumber is None:
            log.warning( 'normalized_callnumber is None' )
        log.debug( f'returning normalized_callnumber, ``{normalized_callnumber}``' )
        return normalized_callnumber

    ## end class Indexer()

## runner ---------------------------------------

def main():
    log.debug( 'starting `def main()`' )
    indexer = Indexer()
    indexer.process_worksheet_urls()

if __name__ == "__main__":
    log.debug( 'starting `if __name__...`' )
    main()