import bpy
from bpy_extras.object_utils import world_to_camera_view
import numpy as np
from util import poissonDiscSampling
import math
import random
from mathutils import Euler, Vector
import os
import glob
import sys

class ForegroundObjectPlacementRandomizer:
    """
    A randomizer class which randomly spawns virtual human objects.

    The foreground generation process of the blender scene involves selecting a subset from a pool of 3D human assets.
    These chosen 3D human assets are placed randomly within the region above the background objects.
    The placement positions of the foreground objects and their distances from each other are determined through
    Poisson distribution sampling within the specified spatial area.

    Attributes
    ----------
    __scene (bpy.types.Scene):  The blender scene data-block of current virtual environment.
    __camera (bpy.types.Camera): The blender camera data-block.
    __clip_start (float): Camera near clipping distance.
    __clip_end (float): Camera far clipping distance.
    num_foreground_object_in_scene_range (dict of str: int): The distribution of the number of retail items within the blender scene.
    __num_foreground_object_in_scene (int): The number of retail items within the blender scene.
    foreground_area (list of float): Spatial distribution area of foreground objects.
    __foreground_domain_size (numpy.ndarray): Spatial distribution area of foreground objects(convert foreground_area to ndarray).
    foreground_poisson_disk_sampling_radius (float): Foreground objects separation distance.
    asset_foreground_object_folder_path (str): The path to foreground object assets.
    __foreground_object_collection (bpy.types.Collection): The blender collection data-block of foreground objects.
    __n_particle (int): Number of generated particles of the poisson disks sampling.
    __particle_coordinates (numpy.ndarray): Coordinates of the poisson disks sampling.
    __particle_coordinates_can_see_in_view (list of list of float): Coordinates of the poisson disks sampling.

    Methods
    -------
    __error_check(): Check assigned background object assets folder path isn't empty.
    __load_object(): Load asset from other blendfile to the current blendfile.
    __posson_disc_sampling(): Using poisson disk sampling algorithm to generate the sampling.
    __import_foreground_object_asset(): Import a number of __n_particle foreground objects into current blender scene.
    __check_particle_in_cam_view(): Check if the particles from the Poisson disk sampling are within the camera's view.
    foreground_object_placement_randomize(): Generate foreground.

    """

    def __init__(self,
                 num_foreground_object_in_scene_range = {"min": 1 , "max": 5}, # Must <= 5
                 foreground_area = [9, 7, 4],
                 foreground_poisson_disk_sampling_radius = 1.5,
                 asset_foreground_object_folder_path = "C:/Users/user/Documents/project/PeopleSansPeople/Asset/Human/Procedural"
                 ):
        self.__scene = bpy.data.scenes["Scene"]
        self.__camera = bpy.data.objects['Camera']
        self.__clip_start = bpy.data.objects['Camera'].data.clip_start
        self.__clip_end = bpy.data.objects['Camera'].data.clip_end
        self.num_foreground_object_in_scene_range = num_foreground_object_in_scene_range
        self.__num_foreground_object_in_scene = None
        self.foreground_area = foreground_area
        self.__foreground_domain_size = np.array(self.foreground_area)
        self.foreground_poisson_disk_sampling_radius = foreground_poisson_disk_sampling_radius
        self.asset_foreground_object_folder_path = asset_foreground_object_folder_path
        self.__foreground_object_collection = bpy.data.collections["HumanCollection"]
        self.__n_particle = None
        self.__particle_coordinates = None # np.array
        self.__particle_coordinates_can_see_in_view = list()

    def __error_check(self,asset_path_list):
        """Check assigned background object assets folder path isn't empty.

        Args:
            asset_path_list (list of str): list of the path to background object assets.

        """
        num_asset_in_folder = len(asset_path_list)
        if num_asset_in_folder < 1:
            print(f'ERROR!!! can not find any foreground asset in {self.asset_foreground_object_folder_path}')
            input("Press Enter to continue...")
            sys.exit()

    def __load_object(self,filepath):
        """Load asset from other blendfile to the current blendfile.

        Args:
            filepath (str): The path to background object assets.

        References
        ----------
        https://studio.blender.org/training/scripting-for-artists/5eabe54d521eafd0953f6d45/
        https://docs.blender.org/api/current/bpy.types.BlendDataLibraries.html
        https://blender.stackexchange.com/questions/17876/import-object-without-bpy-ops-wm-link-append/33998#33998
        https://blender.stackexchange.com/questions/34540/how-to-link-append-a-data-block-using-the-python-api?noredirect=1&lq=1

        """
        # Append object from .blend file
        with bpy.data.libraries.load(filepath, link = False,assets_only = True) as (data_from, data_to):
            data_to.objects = data_from.objects
        # Link object to current scene
        for obj in data_to.objects:
            if obj is not None:
                self.__foreground_object_collection.objects.link(obj)

    def __posson_disc_sampling(self):
        """Generate the sampling with a spatially variable sampling radius."""
        # It seem like function poisson_disc_sampling sometimes will break (mtbf:2000-3000 cycle), when it break , return a empty list[]
        # add condition check len(self.__particle_coordinates) must >= 1
        while self.__n_particle == None or self.__n_particle == 0:
            self.__particle_coordinates = poissonDiscSampling.poisson_disc_sampling(radius = self.foreground_poisson_disk_sampling_radius,
                                                                            sample_domain_size = self.__foreground_domain_size,
                                                                            sample_rejection_threshold = 30)
            self.__n_particle = len(self.__particle_coordinates)

        print(f"nParticle Prev : {self.__n_particle}") # Show posson disc sampling caculated particle num
        loc_offset = np.array([self.__foreground_domain_size[0]/2,self.__foreground_domain_size[1]/2,-2])
        self.__particle_coordinates -= loc_offset

    def __import_foreground_object_asset(self):
        """Import a number of __n_particle foreground objects into current blender scene."""
        # Check n_particle must bigger than num_foreground_object_in_scene
        if self.__n_particle < self.__num_foreground_object_in_scene:
            print('Warning!!! nParticle:{} must bigger than fg_obj_in_scene_num:{}'.format(self.__n_particle,self.__num_foreground_object_in_scene))
            input("Press Enter to continue...")
            sys.exit()

        # Get foreground object asset path
        foreground_object_path_list = glob.glob(os.path.join(self.asset_foreground_object_folder_path, "*.blend"))
        self.__error_check(asset_path_list = foreground_object_path_list)
        num_fg_obj = len(foreground_object_path_list)
        print("num fg obj in folder: {}".format(num_fg_obj))

        # Shuffle foreground_object_path_list
        random.shuffle(foreground_object_path_list)

        # Check num_foreground_object_in_scene is bigger than num_fg_obj
        if self.__num_foreground_object_in_scene >= num_fg_obj:
            # Loop importforeground object
            num_loop = self.__num_foreground_object_in_scene // num_fg_obj
            num_remain = self.__num_foreground_object_in_scene % num_fg_obj

            for i in range(num_loop):
                for fg_obj_path in foreground_object_path_list:
                    self.__load_object(filepath = fg_obj_path)

            if num_remain != 0:
                for i in range(num_remain):
                    self.__load_object(filepath = foreground_object_path_list[i])
        else:
            # Randomly select n(n=num_foreground_object_in_scene) fg_obj from foreground_object_path_list, then import to scene
            foreground_object_path_list_selected = random.sample(foreground_object_path_list, self.__num_foreground_object_in_scene)
            for fg_obj_path in foreground_object_path_list_selected:
                self.__load_object(filepath = fg_obj_path)

    def __check_particle_in_cam_view(self):
        """Check if the particles from the Poisson disk sampling are within the camera's view.

        References
        ----------
        https://blender.stackexchange.com/questions/284884/what-does-world-to-camera-view-depend-on
        https://blender.stackexchange.com/questions/258000/how-to-update-world-transformation-matrices-without-calling-a-scene-update/258002#258002

        """
        # Update camera object matrix_world
        self.__camera.matrix_world = self.__camera.matrix_basis

        for coordinates in self.__particle_coordinates:
            # World space to ndc space
            vector_p = Vector(coordinates)
            co_ndc = world_to_camera_view(self.__scene, self.__camera, vector_p)
            # Check wether point is inside frustum
            if (0.0 < co_ndc.x < 1.0 and 0.0 < co_ndc.y < 1.0 and self.__clip_start < co_ndc.z < self.__clip_end):
                 self.__particle_coordinates_can_see_in_view.append(coordinates)

        # Update __particle_coordinates and __n_particle var value
        self.__particle_coordinates = np.array(self.__particle_coordinates_can_see_in_view)
        self.__n_particle = len(self.__particle_coordinates_can_see_in_view)

    def foreground_object_placement_randomize(self):
        """Generate foreground.

        References
        ----------
        [1]https://stackoverflow.com/questions/14262654/numpy-get-random-set-of-rows-from-2d-array

        """
        self.__num_foreground_object_in_scene = random.randint(self.num_foreground_object_in_scene_range["min"], self.num_foreground_object_in_scene_range["max"])
        # PoissonDiskSampling
        self.__posson_disc_sampling()
        # Select particles which can see in cam view
        self.__check_particle_in_cam_view()
        # Import background object asset
        self.__import_foreground_object_asset()
        # Randomly select n(n=num_foreground_object_in_scene) location from __particle_coordinates [1]
        selected_indices = np.random.choice(self.__particle_coordinates.shape[0],
                                            size = self.__num_foreground_object_in_scene,
                                            replace = False)
        fg_location = self.__particle_coordinates[selected_indices]
        print("fg_num: {} ".format(len(fg_location)))
        print("fg_location:\n {} ".format(fg_location))

        # Move all foregeound objects to fg_location
        fg_obj_list = []
        for fg_obj in self.__foreground_object_collection.objects:
            if fg_obj.type == "ARMATURE": # Select armature object only
                fg_obj_list.append(fg_obj)

        for i in range(self.__num_foreground_object_in_scene):
            obj_location = (fg_location[i][0],fg_location[i][1], fg_location[i][2])
            fg_obj_list[i].location = obj_location

        print("Particles in cam view num : {}".format(self.__n_particle)) # Show particle in cam view num
        print("Foreground Object Placement Randomize COMPLERED !!!")

if __name__ == '__main__':
    randomizer = ForegroundObjectPlacementRandomizer()
    randomizer.foreground_object_placement_randomize()


import math
from cmath import exp
import kwant

def hopping(sitei, sitej, phi, salt):
    xi, yi = sitei.pos
    xj, yj = sitej.pos
    return -exp(-0.5j * phi * (xi - xj) * (yi + yj))

def onsite(site, phi, salt):
    return 0.3 * kwant.digest.gauss(repr(site), salt) + 4

def test_qhe(W=16, L=8):
    def central_region(pos):
        x, y = pos
        return -L < x < L and abs(y) < W - 5.5 * math.exp(-x**2 / 5**2)

    lat = kwant.lattice.square(norbs=1)
    syst = kwant.Builder()

    syst[lat.shape(central_region, (0, 0))] = onsite
    syst[lat.neighbors()] = hopping

    lead = kwant.Builder(kwant.TranslationalSymmetry((-1, 0)))
    lead[(lat(0, y) for y in range(-W + 1, W))] = 4
    lead[lat.neighbors()] = hopping

    syst.attach_lead(lead)
    syst.attach_lead(lead.reversed())
    syst = syst.finalized()

    #### The following chunk of code can be uncommented to visualize the
    #### conductance plateaus.
    # from matplotlib import pyplot
    # import numpy
    # reciprocal_phis = numpy.linspace(0.1, 7, 200)
    # conductances = []
    # for phi in 1 / reciprocal_phis:
    #     smatrix = kwant.smatrix(syst, 1.0, [phi, ""])
    #     conductances.append(smatrix.transmission(1, 0))
    # pyplot.plot(reciprocal_phis, conductances)
    # pyplot.show()

    for r_phis, T_nominal, max_err in [((1.3, 2.1), 1, 1e-7),
                                       ((3.2, 3.7), 2, 1e-3),
                                       ((5.2, 5.5), 3, 1e-1)]:
        for r_phi in r_phis:
            params = dict(phi=1.0 / r_phi, salt="")
            pc = syst.precalculate(1.0, params=params, what='all')
            for result in [kwant.smatrix(pc, 1, params=params),
                           kwant.solvers.default.greens_function(pc, 1, params=params)]:
                assert abs(T_nominal - result.transmission(1, 0)) < max_err


from django.shortcuts import render, redirect
import csv
from django.http import HttpResponse

from django.template.loader import render_to_string
# from weasyprint import HTML
# Create your views here.

from .models import Members, Loans, Deposits
from django.db.models import Avg, Sum
from .forms import MemberForm

def home(request):
    total_dep = Deposits.objects.aggregate(mytotal=Sum('Amount_deposit'))
    total_loan = Loans.objects.aggregate(myloan=Sum('Amount_loan'))
    maximum_dep = Deposits.objects.aggregate(mymax=Avg('Amount_deposit'))
    member_list=Members.objects.all()
    total_members=member_list.count()

    context={'member_list':member_list,'total_members':total_members,'total_dep':total_dep,'maximum_dep':maximum_dep,'total_loan':total_loan}
    return render(request,'information_system/home.html',context)

def members(request,pk):

    members=Members.objects.get(id=pk)
    deposits=Deposits.objects.get(id=pk)
    deposit = members.deposits_set.all()
    deptotal=deposits.Amount_deposit
    # myFilter=MemberFilter(request., qs=deposit)
    # deposit=myFilter.qs

    dep_count=deposit.count()

    fname_by=members.FirstName
    lname_by=members.LastName
    dep_by=fname_by+lname_by

    dep_amount=deposits.Amount_deposit
    total_depos = Deposits.objects.aggregate(mytotal=Sum('Amount_deposit'))
    context={'members':members,'dep_amount':dep_amount,'dep_by':dep_by,'deposit':deposit,'total_depos':total_depos,'deptotal':deptotal}
    return render(request,'information_system/Members.html',context)

def export(request):
    response=HttpResponse(content_type='text/csv')
    writer=csv.writer(response)
    writer.writerow(['Account Number','First Name','Last Name','Date_start'])
    for member in Members.objects.all().values_list('AccountNumber','FirstName','LastName','Date_start'):
        writer.writerow(member)
    response['Content-Disposition'] = 'attachment; filename="Member_List.csv"'
    return response

def update(request, pk):
    member=Members.objects.get(id=pk)
    form=MemberForm(instance=member)
    if request.method == 'POST':
        form=MemberForm(request.Post, instance=member)
        if form.is_valid():
            form.save()
            return redirect('/')
    context = {'form':form}
    return render(request,'information_system/memberform.html',context)

def loan(request):
    loan_list=Loans.objects.all()
    total_loans=loan_list.count()
    context={'total_loans':total_loans,'loan_list':loan_list}
    return render(request,'information_system/loan.html',context)
def deposit(request):
    deposit_list=Deposits.objects.all()
    ##To get total deposit
    total_dep = Deposits.objects.aggregate(mytotal=Sum('Amount_deposit'))
    maximum_dep=Deposits.objects.aggregate(mymax=Avg('Amount_deposit'))

    context={'total_dep':total_dep,'maximum_dep':maximum_dep,'deposit_list':deposit_list}
    return render(request,'information_system/Deposit.html',context)


from ADMIN import *
from VIP_csv import *
from USER import *

if __name__ == '__main__':

    sign = 0
    user = 0

    # loop program, can exit with "end"
    while True:

        if sign == 0:
            print("Sign in as:")
            who = ""

            # not able to sign in as long as name is admin vip_user or name from DB
            # admin: able to create names and categories
            # vip user: able to create remove tasks or to add deadlines
            # users (names from NameDB added by admin): complete task
            # view_task, sort_task, filter_task, info - overall access

            while True:
                who = input()
                find = 0
                if path.exists("NameDB.txt"):
                    f = open("NameDB.txt", "r")
                    lines = f.readlines()
                    for line in lines:
                        if line.strip("\n") == who:
                            find = 1
                    f.close()

                if who == "admin" or who == "vip_user" or find == 1:
                    sign = 1
                    break
                elif who == "end":
                    sign = 0
                    break
                else:
                    print("Login types: [admin, vip_user, name_user]")

            if who == "end":
                break

            # giving privileges
            if who == "admin":
                user = 1
            if who == "vip_user":
                user = 2
            if find == 1:
                user = 3

            # displaying rights
            if user == 1:
                print("Admin Full Rights:")
                print(" add_name  \t\t-->Adds name to NameDB\n remove_name  \t-->Removes name from NameDB\n list_name  "
                      "\t-->Lists names from NameDB")
                print(" list_task  \t-->including:  Delete Task - 'D'\n\t\t\t\t\t\t\t   Sort Column by Task, Date, "
                      "Name and Category - 'S'")

            if user == 2:
                print("Vip_user Partial Rights:")
                print(" list_name")
                print(" add_task -->including: Add Task, Date, Name and Category - 'A'\n\t\t\t\t\t\tDelete Task - "
                      "'D'\n\t\t\t\t\t\tSort Column by Task, Date, Name and Category - 'S'")

            if user == 3:
                print(who, "Limited Rights:")
                print(" list_task  -->including:   Done Task - 'D'\n\t\t\t\t\t\t\tSort Column by Task, Date, "
                      "Name and Category - 'S'")

            print("Additional 'exit' to sign out")
            print("Additional 'end' to end session")

        # sign = 0 not sign in, 1 sign in
        if sign == 1:
            command = input()

            'full access'
            if command == "exit":
                sign = 0

            if command == "end":
                sign = 0
                break

            # astea 2 mai tarziu
            if command == "info":
                print("info mai tarziu")

            if command == "altceva":
                print("unknown command")

            # User 1
            if command == "add_name":
                if user == 1:
                    name = input()
                    if name == "admin" or name == "vip_user":
                        print("admin/vip_user key words")
                    else:
                        add_name(name)
                else:
                    print("No admin rights")

            if command == "remove_name":
                if user == 1:
                    name = input()
                    remove_name(name)
                else:
                    print("No admin rights")

            if command == "list_name":
                if user == 1 or user == 2:
                    list_name()
                else:
                    print("No rights")

            # User 2
            if command == "add_task":
                if user == 2:
                    add_task()
                else:
                    print("No admin rights")

            # User 3
            if command == "list_task":
                if user == 3 or user == 1:
                    list_task()
                else:
                    print("No admin rights")


import os
import pytest

from contextlib import contextmanager
from tempfile import TemporaryDirectory, NamedTemporaryFile
from unittest.mock import patch
from zipfile import ZipFile

from repo2docker.contentproviders import Hydroshare
from repo2docker.contentproviders.base import ContentProviderException

def test_content_id():
    with patch.object(Hydroshare, "urlopen") as fake_urlopen:
        fake_urlopen.return_value.url = (
            "https://www.hydroshare.org/resource/b8f6eae9d89241cf8b5904033460af61"
        )

        def read():
            return '{"dates": [{"type": "modified", "start_date": "2019-09-25T16:09:17.006152Z"}]}'

        fake_urlopen.return_value.read = read
        hydro = Hydroshare()

        hydro.detect("10.4211/hs.b8f6eae9d89241cf8b5904033460af61")
        assert hydro.content_id == "b8f6eae9d89241cf8b5904033460af61.v1569427757"

def test_detect_hydroshare():
    with patch.object(Hydroshare, "urlopen") as fake_urlopen:
        fake_urlopen.return_value.url = (
            "https://www.hydroshare.org/resource/b8f6eae9d89241cf8b5904033460af61"
        )

        def read():
            return '{"dates": [{"type": "modified", "start_date": "2019-09-25T16:09:17.006152Z"}]}'

        fake_urlopen.return_value.read = read
        # valid Hydroshare DOIs trigger this content provider
        expected = {
            "host": {
                "hostname": [
                    "https://www.hydroshare.org/resource/",
                    "http://www.hydroshare.org/resource/",
                ],
                "django_irods": "https://www.hydroshare.org/django_irods/download/bags/",
                "version": "https://www.hydroshare.org/hsapi/resource/{}/scimeta/elements",
            },
            "resource": "b8f6eae9d89241cf8b5904033460af61",
            "version": "1569427757",
        }
        assert (
            Hydroshare().detect(
                "https://www.hydroshare.org/resource/b8f6eae9d89241cf8b5904033460af61"
            )
            == expected
        )
        # assert a call to urlopen was called to fetch version
        assert fake_urlopen.call_count == 1
        assert (
            Hydroshare().detect("10.4211/hs.b8f6eae9d89241cf8b5904033460af61")
            == expected
        )
        # assert 2 more calls were made, one to resolve the DOI and another to fetch the version
        assert fake_urlopen.call_count == 3
        assert (
            Hydroshare().detect(
                "https://doi.org/10.4211/hs.b8f6eae9d89241cf8b5904033460af61"
            )
            == expected
        )
        # assert 2 more calls were made, one to resolve the DOI and another to fetch the version
        assert fake_urlopen.call_count == 5

    with patch.object(Hydroshare, "urlopen") as fake_urlopen:
        # Don't trigger the Hydroshare content provider
        assert Hydroshare().detect("/some/path/here") is None
        assert Hydroshare().detect("https://example.com/path/here") is None
        # don't handle DOIs that aren't from Hydroshare
        fake_urlopen.return_value.url = (
            "http://joss.theoj.org/papers/10.21105/joss.01277"
        )

        def read():
            return '{"dates": [{"type": "modified", "start_date": "2019-09-25T16:09:17.006152Z"}]}'

        fake_urlopen.return_value.read = read
        assert Hydroshare().detect("https://doi.org/10.21105/joss.01277") is None

@contextmanager
def hydroshare_archive(prefix="b8f6eae9d89241cf8b5904033460af61/data/contents"):
    with NamedTemporaryFile(suffix=".zip") as zfile:
        with ZipFile(zfile.name, mode="w") as zip:
            zip.writestr("{}/some-file.txt".format(prefix), "some content")
            zip.writestr("{}/some-other-file.txt".format(prefix), "some more content")

        yield zfile

class MockInfo:
    def __init__(self, content_type):
        self.content_type = content_type

    def get_content_type(self):
        return self.content_type

class MockResponse:
    def __init__(self, content_type, status_code):
        self.content_type = content_type
        self.status_code = status_code
        self.mock_info = MockInfo(self.content_type)

    def getcode(self):
        return self.status_code

    def info(self):
        return self.mock_info

def test_fetch_bag():
    # we "fetch" a local ZIP file to simulate a Hydroshare resource
    with hydroshare_archive() as hydro_path:
        with patch.object(
            Hydroshare,
            "urlopen",
            side_effect=[
                MockResponse("application/html", 200),
                MockResponse("application/zip", 200),
            ],
        ):
            with patch.object(
                Hydroshare, "_urlretrieve", side_effect=[(hydro_path, None)]
            ):
                hydro = Hydroshare()
                hydro.resource_id = "b8f6eae9d89241cf8b5904033460af61"
                spec = {
                    "host": {
                        "hostname": [
                            "https://www.hydroshare.org/resource/",
                            "http://www.hydroshare.org/resource/",
                        ],
                        "django_irods": "https://www.hydroshare.org/django_irods/download/bags/",
                    },
                    "resource": "123456789",
                }

                with TemporaryDirectory() as d:
                    output = []
                    for l in hydro.fetch(spec, d):
                        output.append(l)

                    unpacked_files = set(os.listdir(d))
                    expected = set(["some-other-file.txt", "some-file.txt"])
                    assert expected == unpacked_files

def test_fetch_bag_failure():
    with hydroshare_archive():
        with patch.object(
            Hydroshare, "urlopen", side_effect=[MockResponse("application/html", 500)]
        ):
            hydro = Hydroshare()
            spec = {
                "host": {
                    "hostname": [
                        "https://www.hydroshare.org/resource/",
                        "http://www.hydroshare.org/resource/",
                    ],
                    "django_irods": "https://www.hydroshare.org/django_irods/download/bags/",
                },
                "resource": "123456789",
            }
            with TemporaryDirectory() as d:
                with pytest.raises(
                    ContentProviderException,
                    match=r"Failed to download bag\. status code 500\.",
                ):
                    # loop for yield statements
                    for l in hydro.fetch(spec, d):
                        pass

def test_fetch_bag_timeout():
    with hydroshare_archive():
        with patch.object(
            Hydroshare, "urlopen", side_effect=[MockResponse("application/html", 200)]
        ):
            hydro = Hydroshare()
            spec = {
                "host": {
                    "hostname": [
                        "https://www.hydroshare.org/resource/",
                        "http://www.hydroshare.org/resource/",
                    ],
                    "django_irods": "https://www.hydroshare.org/django_irods/download/bags/",
                },
                "resource": "123456789",
            }
            with TemporaryDirectory() as d:
                with pytest.raises(
                    ContentProviderException,
                    match=r"Bag taking too long to prepare, exiting now, try again later\.",
                ):
                    # loop for yield statements
                    for l in hydro.fetch(spec, d, timeout=0):
                        pass


#!/usr/bin/python3
import sqlite3
import gspread
from oauth2client.service_account import ServiceAccountCredentials

scope = ['https://spreadsheets.google.com/feeds',
         'https://www.googleapis.com/auth/drive']

dbfile = 'TimeTrack4237.db'
dbconn =  sqlite3.connect(dbfile)
student_hours = None

with dbconn:

    dbcursor = dbconn.cursor()
    dbcursor.execute("SELECT name, SUM( ROUND( CAST( (JULIANDAY(checkout) - JULIANDAY(checkin)) * 24 AS REAL), 2)) \
                    FROM activity, students \
                   WHERE activity.id = students.id \
                     AND checkin IS NOT NULL \
                     AND checkout IS NOT NULL \
                   GROUP BY name \
                   ORDER BY name")

    student_hours = dbcursor.fetchall()

if student_hours is not None:

    credentials = ServiceAccountCredentials.from_json_keyfile_name('credentials/timetrack4237-12f97a6ef02f.json', scope)

    gc = gspread.authorize(credentials)

    workbook = gc.open("TimeTrack4237")

    workbook.sheet1.clear()

    workbook.values_update(
        'Sheet1!A1',
        params={'valueInputOption': 'RAW'},
        body={'values': student_hours}
    )


import unittest
import os

from conans.test.utils.test_files import temp_folder
from conans.util.files import save
from time import sleep

class SaveTestCase(unittest.TestCase):

    def setUp(self):
        folder = temp_folder()
        self.filepath = os.path.join(folder, "file.txt")

        # Save some content and keep timestamp
        self.content = "my content"
        save(self.filepath, self.content)
        self.timestamp = os.path.getmtime(self.filepath)
        sleep(1)  # precission is seconds, so we need to sleep

    def only_if_modified_true_test(self):
        save(self.filepath, self.content, only_if_modified=True)
        self.assertEqual(self.timestamp, os.path.getmtime(self.filepath))

    def only_if_modified_false_test(self):
        save(self.filepath, self.content, only_if_modified=False)
        self.assertNotEqual(self.timestamp, os.path.getmtime(self.filepath))

    def modified_only_true_test(self):
        save(self.filepath, "other content", only_if_modified=True)
        self.assertNotEqual(self.timestamp, os.path.getmtime(self.filepath))

    def modified_only_false_test(self):
        save(self.filepath, "other content", only_if_modified=False)
        self.assertNotEqual(self.timestamp, os.path.getmtime(self.filepath))


# 给你一个长度固定的整数数组 arr，请你将该数组中出现的每个零都复写一遍，并将其余的元素向右平移。

# 注意：请不要在超过该数组长度的位置写入元素。

# 要求：请对输入的数组 就地 进行上述修改，不要从函数返回任何东西。

class Solution(object):
    def duplicateZeros(self, arr):
        """
        :type arr: List[int]
        :rtype: None Do not return anything, modify arr in-place instead.
        """
        i = 0
        while(i < len(arr)):
            if arr[i] == 0:
                if i != len(arr) - 1:
                    for j in range(len(arr) - 1, i + 1, -1):
                        arr[j] = arr[j - 1]
                    arr[i + 1] = 0
                    i += 1
            i += 1
        return arr

arr = [1,0,2,3,0,4,5,0]

a = Solution()
print(a.duplicateZeros(arr))


"""Bridgy App Engine config.
"""
import logging

class StubsFilter(logging.Filter):
    """Suppress these INFO logs:
    Sandbox prevented access to file "/usr/local/Caskroom/google-cloud-sdk"
    If it is a static file, check that `application_readable: true` is set in your app.yaml
    """
    def filter(self, record):
        msg = record.getMessage()
        if (msg.startswith('Sandbox prevented access to file')
                or msg.startswith('If it is a static file, check that')):
            return 0
        return 1

logging.getLogger().addFilter(StubsFilter())


from tensorflow.keras.layers import Conv2D, Conv2DTranspose, concatenate, Dropout
from model.create_layers import create_conv_layers

class Decoder:
    def __init__(self, inputs, conv_layers, output_channels, dropout=0.3, name="Decoder"):
        self.inputs = inputs
        self.dropout = dropout
        self.name = name

        feature1, feature2, feature3, feature4 = conv_layers

        block1 = self._create_block(inputs=inputs, conv_output=feature4, filters=[512, 512], kernel_size=(3, 3),
                                    strides=(2, 2), dropout=0.3)

        block2 = self._create_block(inputs=block1, conv_output=feature3, filters=[256, 256], kernel_size=(3, 3),
                                    strides=(2, 2), dropout=0.3)

        block3 = self._create_block(inputs=block2, conv_output=feature2, filters=[128, 128], kernel_size=(3, 3),
                                    strides=(2, 2), dropout=0.3)

        block4 = self._create_block(inputs=block3, conv_output=feature1, filters=[64, 64], kernel_size=(3, 3),
                                    strides=(2, 2), dropout=0.3)

        self.outputs = Conv2D(filters=output_channels, kernel_size=(1, 1), activation='softmax')(block4)

    def _create_block(self, inputs, conv_output, filters, kernel_size, strides, dropout):
        conv_t = Conv2DTranspose(filters[0], kernel_size, strides=strides, padding='same')(inputs)
        output = concatenate([conv_t, conv_output])
        output = Dropout(dropout)(output)
        output = create_conv_layers(output, filters, kernel_size=(3, 3))

        return output

    def get_output(self):
        return self.outputs


#! /bin/python

# IMPORTANT do threadctl import first (before numpy imports)
from threadpoolctl import threadpool_limits

import os
import sys
import json

import luigi
import nifty.tools as nt

import cluster_tools.utils.volume_utils as vu
import cluster_tools.utils.function_utils as fu
from cluster_tools.cluster_tasks import SlurmTask, LocalTask, LSFTask

from elf.io.label_multiset_wrapper import LabelMultisetWrapper
from elf.label_multiset import create_multiset_from_labels, serialize_multiset

class CreateMultisetBase(luigi.Task):
    """ CreateMultiset base class
    """

    task_name = 'create_multiset'
    src_file = os.path.abspath(__file__)
    allow_retry = False

    # input and output volumes
    input_path = luigi.Parameter()
    input_key = luigi.Parameter()
    output_path = luigi.Parameter()
    output_key = luigi.Parameter()
    # dependency
    dependency = luigi.TaskParameter()

    def requires(self):
        return self.dependency

    @staticmethod
    def default_task_config():
        config = LocalTask.default_task_config()
        config.update({'compression': 'gzip'})
        return config

    def run_impl(self):
        # get the global config and init configs
        shebang, block_shape, roi_begin, roi_end = self.global_config_values()
        self.init(shebang)

        # get shape and make block config
        shape = vu.get_shape(self.input_path, self.input_key)

        # load the create_multiset config
        config = self.get_task_config()

        compression = config.get('compression', 'gzip')
        # require output dataset
        with vu.file_reader(self.output_path) as f:
            f.require_dataset(self.output_key, shape=shape, chunks=tuple(block_shape),
                              compression=compression, dtype='uint8')

        # update the config with input and output paths and keys
        # as well as block shape
        config.update({'input_path': self.input_path, 'input_key': self.input_key,
                       'output_path': self.output_path, 'output_key': self.output_key,
                       'block_shape': block_shape})
        block_list = vu.blocks_in_volume(shape, block_shape, roi_begin, roi_end)
        self._write_log('scheduling %i blocks to be processed' % len(block_list))
        n_jobs = min(len(block_list), self.max_jobs)

        # prime and run the jobs
        self.prepare_jobs(n_jobs, block_list, config)
        self.submit_jobs(n_jobs)

        # wait till jobs finish and check for job success
        self.wait_for_jobs()
        self.check_jobs(n_jobs)

class CreateMultisetLocal(CreateMultisetBase, LocalTask):
    """
    CreateMultiset on local machine
    """
    pass

class CreateMultisetSlurm(CreateMultisetBase, SlurmTask):
    """
    CreateMultiset on slurm cluster
    """
    pass

class CreateMultisetLSF(CreateMultisetBase, LSFTask):
    """
    CreateMultiset on lsf cluster
    """
    pass

#
# Implementation
#

@threadpool_limits.wrap(limits=1)  # restrict the numpy threadpool to 1 to avoid oversubscription
def _create_multiset_block(blocking, block_id, ds_in, ds_out):
    fu.log("start processing block %i" % block_id)
    block = blocking.getBlock(block_id)
    bb = vu.block_to_bb(block)

    labels = ds_in[bb]

    # we can't encode the paintra ignore label
    paintera_ignore_label = 18446744073709551615
    pignore_mask = labels == paintera_ignore_label
    if pignore_mask.sum() > 0:
        labels[pignore_mask] = 0

    if labels.sum() == 0:
        fu.log("block %i is empty" % block_id)
        fu.log_block_success(block_id)
        return

    # compute multiset from input labels
    multiset = create_multiset_from_labels(labels)
    ser = serialize_multiset(multiset)

    chunk_id = tuple(bs // ch for bs, ch in zip(block.begin, ds_out.chunks))
    ds_out.write_chunk(chunk_id, ser, True)
    fu.log_block_success(block_id)

def write_metadata(ds_out, max_id):
    attrs = ds_out.attrs
    attrs['maxId'] = max_id
    attrs['isLabelMultiset'] = True

@threadpool_limits.wrap(limits=1)  # restrict the numpy threadpool to 1 to avoid oversubscription
def create_multiset(job_id, config_path):
    fu.log("start processing job %i" % job_id)
    fu.log("reading config from %s" % config_path)
    with open(config_path, 'r') as f:
        config = json.load(f)

    # read the input cofig
    input_path = config['input_path']
    input_key = config['input_key']

    block_shape = list(config['block_shape'])
    block_list = config['block_list']

    # read the output config
    output_path = config['output_path']
    output_key = config['output_key']
    shape = list(vu.get_shape(output_path, output_key))

    # get the blocking
    blocking = nt.blocking([0, 0, 0], shape, block_shape)

    # submit blocks
    with vu.file_reader(input_path, 'r') as f_in, vu.file_reader(output_path) as f_out:
        ds_in = f_in[input_key]
        if ds_in.attrs.get('isLabelMultiset', False):
            ds_in = LabelMultisetWrapper(ds_in)
        ds_out = f_out[output_key]

        for block_id in block_list:
            _create_multiset_block(blocking, block_id, ds_in, ds_out)

        if job_id == 0:
            max_id = ds_in.attrs['maxId']
            write_metadata(ds_out, max_id)

    # log success
    fu.log_job_success(job_id)

if __name__ == '__main__':
    path = sys.argv[1]
    assert os.path.exists(path), path
    job_id = int(os.path.split(path)[1].split('.')[0].split('_')[-1])
    create_multiset(job_id, path)


# -*- coding: utf-8

from woo import utils,pack,export,qt
import gts,os

def Plane(v1,v2,v3,v4):
	pts = [ [Vector3(v1),Vector3(v2),Vector3(v3),Vector3(v4)] ]
	return pack.sweptPolylines2gtsSurface(pts,capStart=True,capEnd=True)

# Parameters
tc=0.001# collision time
en=0.3  # normal restitution coefficient
es=0.3  # tangential restitution coefficient
frictionAngle=radians(35)#
density=2700
kw=utils.getViscoelasticFromSpheresInteraction(10e3,tc,en,es)
params=utils.getViscoelasticFromSpheresInteraction(10e3,tc,en,es)
# facets material
facetMat=O.materials.append(ViscElMat(frictionAngle=frictionAngle,**params))
# default spheres material
dfltSpheresMat=O.materials.append(ViscElMat(density=density,frictionAngle=frictionAngle))

O.dt=.2*tc # time step

Rs=0.02 # mean particle radius
Rf=0.01 # dispersion (Rs±Rf*Rs)
nSpheres=1000# number of particles

# Create geometry
pln=Plane( (-.5, -.5, 0), (.5, -.5, -.05), (.5, .5, 0), (-.5, .5, -.05) );
plnIds=O.bodies.append(pack.gtsSurface2Facets(pln.faces(),material=facetMat,color=(0,1,0)))

fct=Plane( (-.25, -.25, .5), (.25, -.25, .5), (.25, .25, .5), (-.25, .25, .5) );
fctIds=O.bodies.append(pack.gtsSurface2Facets(fct.faces(),material=facetMat,color=(1,0,0),noBound=True))

# Create spheres
sp=pack.SpherePack();
sp.makeCloud(Vector3(-.5, -.5, 0),Vector3(.5, .5, .2), Rs, Rf, int(nSpheres), False)
spheres=O.bodies.append([utils.sphere(s[0],s[1],color=(0.929,0.412,0.412),material=dfltSpheresMat) for s in sp])
for id in spheres:
	s=O.bodies[id]
	p=utils.getViscoelasticFromSpheresInteraction(s.state['mass'],tc,en,es)
	s.mat['kn'],s.mat['cn'],s.mat['ks'],s.mat['cs']=p['kn'],p['cn'],p['ks'],p['cs']

# Create engines
O.engines=[
	ForceResetter(),
	InsertionSortCollider([Bo1_Sphere_Aabb(),Bo1_Facet_Aabb()]),
	InteractionLoop(
		[Ig2_Sphere_Sphere_ScGeom(), Ig2_Facet_Sphere_ScGeom()],
		[Ip2_ViscElMat_ViscElMat_ViscElPhys()],
		[Law2_ScGeom_ViscElPhys_Basic()],
	),
	GravityEngine(gravity=[0,0,-9.81]),
	NewtonIntegrator(damping=0),
	ResetRandomPosition(factoryFacets=fctIds,velocity=(0,0,-2),virtPeriod=0.01,ids=spheres,point=(0,0,-.5),normal=(0,0,1),maxAttempts=100),
]

#renderer = qt.Renderer()
#qt.View()
#O.saveTmp()
#O.run()


from os import path
import os

from .core import ZephyrBinaryRunner, get_env_or_bail

DEFAULT_PYOCD_GDB_PORT = 3333

class PyOcdBinaryRunner(ZephyrBinaryRunner):
    '''Runner front-end for pyocd-flashtool.'''

    def __init__(self, target, flashtool='pyocd-flashtool',
                 gdb=None, gdbserver='pyocd-gdbserver',
                 gdb_port=DEFAULT_PYOCD_GDB_PORT, tui=None,
                 bin_name=None, elf_name=None,
                 board_id=None, daparg=None, debug=False):
        super(PyOcdBinaryRunner, self).__init__(debug=debug)

        self.target_args = ['-t', target]
        self.flashtool = flashtool
        self.gdb_cmd = [gdb] if gdb is not None else None
        self.gdbserver = gdbserver
        self.gdb_port = gdb_port
        self.tui_args = [tui] if tui is not None else []
        self.bin_name = bin_name
        self.elf_name = elf_name

        board_args = []
        if board_id is not None:
            board_args = ['-b', board_id]
        self.board_args = board_args

        daparg_args = []
        if daparg is not None:
            daparg_args = ['-da', daparg]
        self.daparg_args = daparg_args

    def replaces_shell_script(shell_script, command):
        return (command in {'flash', 'debug', 'debugserver'} and
                shell_script == 'pyocd.sh')

    def port_args(self):
        return ['-p', str(self.gdb_port)]

    def create_from_env(command, debug):
        '''Create runner from environment.

        Required:

        - PYOCD_TARGET: target override

        Optional:

        - PYOCD_DAPARG: arguments to pass to pyocd tool, default is none
        - PYOCD_BOARD_ID: ID of board to flash, default is to prompt

        Required for 'flash':

        - O: build output directory
        - KERNEL_BIN_NAME: name of kernel binary

        Optional for 'flash':

        - PYOCD_FLASHTOOL: flash tool path, defaults to pyocd-flashtool

        Required for 'debug':

        - O: build output directory
        - KERNEL_ELF_NAME
        - GDB: gdb executable

        Optional for 'debug', 'debugserver':

        - TUI: one additional argument to GDB (e.g. -tui)
        - GDB_PORT: pyocd gdb port, defaults to 3333
        - PYOCD_GDBSERVER: gdb server executable, defaults to pyocd-gdbserver
        '''
        target = get_env_or_bail('PYOCD_TARGET')

        o = os.environ.get('O', None)
        bin_ = os.environ.get('KERNEL_BIN_NAME', None)
        elf = os.environ.get('KERNEL_ELF_NAME', None)
        bin_name = None
        elf_name = None
        if o is not None:
            if bin_ is not None:
                bin_name = path.join(o, bin_)
            if elf is not None:
                elf_name = path.join(o, elf)

        flashtool = os.environ.get('PYOCD_FLASHTOOL', 'pyocd-flashtool')
        board_id = os.environ.get('PYOCD_BOARD_ID', None)
        daparg = os.environ.get('PYOCD_DAPARG', None)
        gdb = os.environ.get('GDB', None)
        gdbserver = os.environ.get('PYOCD_GDBSERVER', 'pyocd-gdbserver')
        gdb_port = os.environ.get('GDB_PORT', DEFAULT_PYOCD_GDB_PORT)
        tui = os.environ.get('TUI', None)

        return PyOcdBinaryRunner(target, flashtool=flashtool, gdb=gdb,
                                 gdbserver=gdbserver, gdb_port=gdb_port,
                                 tui=tui, bin_name=bin_name, elf_name=elf_name,
                                 board_id=board_id, daparg=daparg, debug=debug)

    def run(self, command, **kwargs):
        if command not in {'flash', 'debug', 'debugserver'}:
            raise ValueError('{} is not supported'.format(command))

        if command == 'flash':
            self.flash(**kwargs)
        else:
            self.debug_debugserver(command, **kwargs)

    def flash(self, **kwargs):
        if self.bin_name is None:
            raise ValueError('Cannot flash; bin_name is missing')

        cmd = ([self.flashtool] +
               self.daparg_args +
               self.target_args +
               self.board_args +
               [self.bin_name])

        print('Flashing Target Device')
        self.check_call(cmd)

    def print_gdbserver_message(self):
        print('pyOCD GDB server running on port {}'.format(self.gdb_port))

    def debug_debugserver(self, command, **kwargs):
        server_cmd = ([self.gdbserver] +
                      self.daparg_args +
                      self.port_args() +
                      self.target_args +
                      self.board_args)

        if command == 'debugserver':
            self.print_gdbserver_message()
            self.check_call(server_cmd)
        else:
            if self.gdb_cmd is None:
                raise ValueError('Cannot debug; gdb is missing')
            if self.elf_name is None:
                raise ValueError('Cannot debug; elf is missing')
            client_cmd = (self.gdb_cmd +
                          self.tui_args +
                          [self.elf_name] +
                          ['-ex', 'target remote :{}'.format(self.gdb_port),
                           '-ex', 'load',
                           '-ex', 'monitor reset halt'])
            self.print_gdbserver_message()
            self.run_server_and_client(server_cmd, client_cmd)


from typing import List
import random

# 398. 随机数索引
# https://leetcode-cn.com/problems/random-pick-index/
# 蓄水池抽样

class Solution:

    def __init__(self, nums: List[int]):
        self.nums = nums

    def pick(self, target: int) -> int:
        ans = -1
        k = 1
        for i, each in enumerate(self.nums):
            if each == target:
                rand = random.randint(1, k)
                if rand == 1:
                    # print('hit')
                    ans = i
                k += 1
        return ans

nums = [1, 2, 3, 3, 3]
# Your Solution object will be instantiated and called as such:
obj = Solution(nums)
param_1 = obj.pick(3)
print(param_1)


# -*- coding: utf-8 -*-
"""
Created on Tue Jan 19 12:30:02 2021

@author: Nassim
"""

import os
from tkinter import *
from tkinter import filedialog, ttk
import numpy as np
import esat

def main():
    root = Tk()
    root.title("ESAT")
    root.configure(bg="lightsteelblue")
    root.geometry("800x400")

    main_folder = filedialog.askdirectory()
    welcome_label = Label(
        root, text="Welcome!", font=("Helvetica", 12), bg="lightsteelblue"
    ).grid(column=1, row=0)

    starts = []
    stops = []
    folders = [folder for folder in os.listdir(main_folder)]

    input_frame = Frame(root, bg="lightsteelblue")
    input_frame.grid(column=0, row=1)

    head_label = Label(input_frame, text="Filenames", bg="lightsteelblue")
    head_label.grid(column=0, row=2, columnspan=3)

    start_label = Label(input_frame, text="Time of first [h]", bg="lightsteelblue")
    start_label.grid(column=2, row=3)

    stop_label = Label(input_frame, text="Time of last [h]", bg="lightsteelblue")
    stop_label.grid(column=3, row=3)

    for index, folder in enumerate(folders):
        row = index + 5

        fold_label = Label(input_frame, text=folder, bg="lightsteelblue", width=5)
        fold_label.grid(column=1, row=row)

        start_entry = Entry(input_frame, borderwidth=3)
        start_entry.grid(column=2, row=row)
        starts.append(start_entry)

        stop_entry = Entry(input_frame, borderwidth=3)
        stop_entry.grid(column=3, row=row)
        stops.append(stop_entry)

    grow_entry = Entry(input_frame, borderwidth=3)
    grow_entry.grid(column=2, row=row + 2)
    grow_label = Label(input_frame, text="upper limit [%]", bg="lightsteelblue")
    grow_label.grid(column=1, row=row + 2)

    shrink_entry = Entry(input_frame, borderwidth=3)
    shrink_entry.grid(column=2, row=row + 3)
    shrink_label = Label(input_frame, text="lower limit [%]", bg="lightsteelblue")
    shrink_label.grid(column=1, row=row + 3)

    def init():

        import imp

        imp.reload(esat)
        print("reloaded esat")
        t_start = []
        t_end = []

        if len(folders) == 0:

            start = float(starts[0].get())
            stop = float(stops[0].get())
            t_start.append(start)
            t_end.append(stop)

        else:
            for i in range(len(folders)):
                t_start.append(float(starts[i].get()))
                t_end.append(float(stops[i].get()))

            times = [t_start, t_end]

        try:
            growth = float(grow_entry.get())
            shrink = float(shrink_entry.get())
            esat.run(
                main_folder, times, growth=growth, shrink=shrink
            )

        except ValueError:
            esat.run(main_folder, times)

    run_but = Button(input_frame, text="Run analysis", borderwidth=3, command=init)
    run_but.grid(column=3, row=row + 1)

    def restart():
        new_main = filedialog.askdirectory()
        if new_main == main_folder:
            pass
        else:
            for widget in input_frame.winfo_children():
                widget.destroy()
            main()

    def help_text():
        help_frame = Frame(root, height=800, width=400)
        help_frame.grid(column=0, row=0)
        help_label = Label(
            help_frame,
            text="""
            Welcome to this Embryonic Structure Analysis Tool, or ESAT for short.
            To help you with this application, here are some basic instructions:
            With the button \'Open files\', under the Files menu, you can browse
            through your computer to select the folder where the pictures you want
            to be analysed are saved.
            Please note, for the analysis to work properly it is essential that you
            select a folder which contains a (or multiple) folder(s) which contains
            the images to be analysed. For example, we have folder "Embryos" which
            contains 3 folders "embryo_1", "embryo_2" and ""embryo_3",
            each of these folders contains a certain amount of pictures.
            We now can select the "Embryos" main folder to run the analysis
            on all 3 embryos seperately.
            In the box \'Time of first\', fill in the number situated on
            the bottom right corner of the first picture. And in the entry box
            \'Time of last\', fill in the number situated on the bottom right corner
            of the last picture.
            Note: Please use points (.) to separate decimal numbers, not comma's (,).

            Furthermore the prefered maximum growth and collaps of the embryo are
            adjustable in the entry boxes below the weight entry box.
            The growth limits the percentual difference between 2 data points,
            e.g. the percentual growth between two data points will maximally be 'upper limit',
            The default value is 20%.

            The results are automatically exported in the same location as the main folder
            in the form of an excel table with 2 sheets,
            the first sheet gives a column of all measured surface aresas of all folders,
            the second contains the slopes of each folder.
            Last a folder with the name [selected_folder]_analysedis added to the directory,
            which will contain a folder per embryo holding all of the images with the detected circles drawn in them,
            and a folder containing the plots of all embryos.
            """,
            font=("helvetica", 9),
            bg="lightsteelblue",
        ).grid(column=1, row=0)

        back_button = Button(
            help_frame,
            text="Back to analysis",
            bg="lightsteelblue",
            command=help_frame.destroy,
        )
        back_button.grid(column=0, row=0)

    root.option_add("*tearOff", FALSE)
    menu = Menu(root)
    root.config(menu=menu)

    subfile = Menu(menu)
    menu.add_cascade(menu=subfile, label="Folders")
    subfile.add_command(label="open", command=restart)
    # subfile.add_command(label='close', command = restart_program)
    # optie: change file als blijkt dat cirkel niet goed is

    # sub-dropdown-menu
    subhelp = Menu(menu)
    menu.add_cascade(menu=subhelp, label="Help")
    subhelp.add_command(label="Help", command=help_text)
    # helpfunctie koppelen aan dit submenu

    root.mainloop()

def gridit(item, row, column):
    item.grid(column=column, row=row)

main()


import numpy as np
from timeit import default_timer as timer
import utils
from utils import *
from PyCuNN import *
from scipy.spatial.distance import euclidean as euc
import pickle

class rnn(object):
	def __init__(self, layers):
		super(rnn, self).__init__()
		self.layers = layers

		self.w1 = init_weights([self.layers[0],self.layers[1]])
		self.b1 = init_weights([1,self.layers[1]])

		self.w2 = init_weights([self.layers[1],self.layers[2]])
		self.b2 = init_weights([1,self.layers[2]])

		self.wr = init_weights([self.layers[1],self.layers[1]])
		self.br = init_weights([1,self.layers[1]])

		self.gw1 = zeros([self.layers[0],self.layers[1]])
		self.gw2 = zeros([self.layers[1],self.layers[2]])
		self.gwr = zeros([self.layers[1],self.layers[1]])
		self.gb1 = zeros([1,self.layers[1]])
		self.gb2 = zeros([1,self.layers[2]])
		self.gbr = zeros([1,self.layers[1]])
		self.gOutput = zeros([1,self.layers[2]])
		self.gInput = zeros([1,self.layers[2]])
		self.gRecurrent = zeros([1,self.layers[1]])
		self.delta = zeros([1,self.layers[1]])
		self.updates_tm1 = [self.gw2,self.gb2,self.gw1,self.gb1,self.gwr,self.gbr]

		self.output = zeros([1,self.layers[2]])
		self.outputs = []
		self.h = zeros([1,self.layers[1]])
		self.r = zeros([1,self.layers[1]])
		self.hs = [zeros([1,self.layers[1]])]
		self.inputs=[]
		self.lr = 0.01

	def forward(self,x):
		assert x.shape[1] == self.layers[0]
		fp(x,self.w1,self.b1,self.h)
		fp(self.hs[-1],self.wr,self.br,self.r)
		mmadd(self.h,self.r,self.h)
		mtanh(self.h,self.h)
		fp(self.h,self.w2,self.b2,self.output)
		mtanh(self.output,self.output)
		self.inputs.append(mcopy(x))
		self.hs.append(mcopy(self.h))
		self.outputs.append(mcopy(self.output))
		return self.output

	def bptt(self,t):
		for q in range(len(t)-2):
			assert t[-1].shape[1] == self.layers[2]
			mmsubtract(t[-1],self.outputs[-1],self.gOutput)
			msmult(self.gOutput,-1.,self.gOutput)

			bp(self.gOutput,self.w2,self.gw2,self.gb2,self.hs[-1],self.delta)

			mmadd(self.delta,self.gRecurrent,self.delta)

			mtanh_deriv(self.delta,self.hs[-1],self.delta)

			bp(self.delta,self.wr,self.gwr,self.gbr,self.hs[-2],self.gRecurrent)

			bp(self.delta,self.w1,self.gw1,self.gb1,self.inputs[-1],self.gInput)
			#print(np.argmax(asarray(self.inputs[-1])))

			self.hs.pop()
			self.inputs.pop()
			self.outputs.pop()
			t.pop()

	def updateWeights(self):
		mclip(self.gwr)
		mclip(self.gw2)
		mclip(self.gw1)

		update_weights(self.w2,self.gw2,self.lr)
		update_weights(self.b2,self.gb2,self.lr)
		update_weights(self.w1,self.gw1,self.lr)
		update_weights(self.b1,self.gb1,self.lr)
		update_weights(self.wr,self.gwr,self.lr)
		update_weights(self.br,self.gbr,self.lr)

		#print(asarray(self.wr))
		self.forget()

	def train(self,ds,epochs,lr=0.001,decay=0.99):
		#assert ds_x.shape[0] is ds_t.shape[0], "Size Mismatch: Ensure number of examples in input and target datasets is equal"
		self.lr = lr
		acc = 0
		w = 0
		time = 0.
		wps = 0.
		for epoch in range(epochs):
			start = timer()
			correct = 0
			count = 0
			for seq in range(len(ds)-1):
				x = ds[seq]
				targets = []
				st = timer()
				for t in range(len(x)-1):
					count += 1
					w += 1
					inval = encode(x[t])
					#print('inval',asarray(inval))
					tarval = encode(x[t+1])
					#print('tarval',asarray(tarval))
					self.forward(inval)
					#print('output',asarray(self.output))
					targets.append(mcopy(tarval))
					if most_similar(self.outputs[-1]) == x[t+1]:
						#print('correct')
						correct += 1
				#print(targets)
				acc = float(correct)/float(count)
				self.bptt(targets)
				#print('output',asarray(self.outputs[-1]))
				#print(asarray(self.outputs[-1]))
				#print('Outputs:',decode(self.outputs[-2]),decode(self.outputs[-1]),'Input',x[-2],'Target',decode(x[-1]))
				#print('gw2',self.gw2.asarray(),'gb2',self.gb2.asarray(),'iifog',cm.sum(self.hidden_layer.gi_IFOG,axis=1).sum(axis=0).asarray(),'hifog',self.hidden_layer.hm1_IFOG.asarray())
				self.updateWeights()
				time += timer()-st
				wps = float(w)/time
				#print('wps:',wps,"eta:",(float(utils.total)/wps)/60,'min')
				#if (seq % 100 == 0) and (self.lr > 0.005):
					#self.lr = self.lr * decay
			time = timer() - start
			sent = [ds[10][0]]
			for i in range(15):
				x = encode(sent[-1])
				y = self.forward(x)
				sent.append(most_similar(y))
			self.forget()
			print('Trained Epoch:',epoch+1,"With Accuracy:",acc, 'in', time, 'seconds', 'Learning Rate:',self.lr, 'wps',wps)
			print('Generated Sentence:',sent)

	def reset_grads(self):
		mzero(self.gw1)
		mzero(self.gw2)
		mzero(self.gwr)
		mzero(self.gb1)
		mzero(self.gb2)
		mzero(self.gbr)
		mzero(self.gOutput)
		mzero(self.gInput)
		mzero(self.gRecurrent)
		mzero(self.delta)
		self.updates_tm1 = [self.gw2,self.gb2,self.gw1,self.gb1,self.gwr,self.gbr]

	def reset_activations(self):
		mzero(self.output)
		self.outputs = []
		mzero(self.h)
		mzero(self.r)
		mzero(self.delta)
		mzero(self.gRecurrent)
		mzero(self.gInput)
		self.hs = [zeros([1,self.layers[1]])]
		self.inputs=[]

	def forget(self):
		self.reset_grads()
		self.reset_activations()

def test_rnn():
	ds = load_sentences_data('../data/ptb.train.short.txt',use_embeddings=True)

	net = rnn([500,1000,500])

	net.train(ds,150)


"""
Proximal Policy Optimization Algorithms (PPO):
    https://arxiv.org/pdf/1707.06347.pdf

Related Tricks(May not be useful):
    Mastering Complex Control in MOBA Games with Deep Reinforcement Learning (Dual Clip)
        https://arxiv.org/pdf/1912.09729.pdf
    A Closer Look at Deep Policy Gradients (Value clip, Reward normalizer)
        https://openreview.net/pdf?id=ryxdEkHtPS
    Revisiting Design Choices in Proximal Policy Optimization
        https://arxiv.org/pdf/2009.10897.pdf

Learning Complex Dexterous Manipulation with Deep Reinforcement Learning and Demonstrations (DAPG):
        https://arxiv.org/pdf/1709.10087.pdf
"""

from collections import defaultdict
from copy import deepcopy

import numpy as np
import torch
import torch.nn as nn
import torch.nn.functional as F
import math
from maniskill2_learn.env import build_replay
from maniskill2_learn.networks import build_actor_critic, build_model
from maniskill2_learn.utils.torch import build_optimizer
from maniskill2_learn.utils.data import DictArray, GDict, to_np, to_torch
from maniskill2_learn.utils.meta import get_logger, get_world_rank, get_world_size
from maniskill2_learn.utils.torch import BaseAgent, RunningMeanStdTorch, RunningSecondMomentumTorch, barrier, get_flat_grads, get_flat_params, set_flat_grads

from ..builder import MFRL

@MFRL.register_module()
class PPO(BaseAgent):
    def __init__(
        self,
        actor_cfg,
        critic_cfg,
        env_params,

        gamma=0.99,
        lmbda=0.95,
        max_kl=None,

        obs_norm=False,
        rew_norm=True,
        adv_norm=True,
        recompute_value=True,

        eps_clip=0.2,
        critic_coeff=0.5,
        entropy_coeff=0.0,
        num_epoch=10,
        critic_epoch=-1,
        actor_epoch=-1,
        num_mini_batch=-1,
        critic_warmup_epoch=0,
        batch_size=256,

        max_grad_norm=0.5,
        rms_grad_clip=None,

        dual_clip=None,
        critic_clip=False,

        shared_backbone=False,
        detach_actor_feature=True,
        debug_grad=False,

        demo_replay_cfg=None,
        dapg_lambda=0.1,
        dapg_damping=0.995,
        ignore_dones=True,
        visual_state_coeff=-1,
        visual_state_mlp_cfg=None,
        **kwargs
    ):
        super(PPO, self).__init__()
        assert dual_clip is None or dual_clip > 1.0, "Dual-clip PPO parameter should greater than 1.0."
        assert max_grad_norm is None or rms_grad_clip is None, "Only one gradient clip mode is allowed!"
        assert (
            (num_epoch > 0 and (actor_epoch < 0 and critic_epoch < 0)) or (num_epoch < 0 and (actor_epoch > 0 and critic_epoch > 0)),
            "We need only one set of the parameters num_epoch > 0, (actor_epoch > 0 and critic_epoch > 0).",
        )
        if not rew_norm:
            assert not critic_clip, "Value clip is available only when `reward_normalization` is True"

        actor_cfg = deepcopy(actor_cfg)
        critic_cfg = deepcopy(critic_cfg)

        actor_optim_cfg = actor_cfg.pop("optim_cfg", None)
        critic_optim_cfg = critic_cfg.pop("optim_cfg", None)
        obs_shape = env_params["obs_shape"]
        self.is_discrete = env_params["is_discrete"]

        self.gamma = gamma
        self.lmbda = lmbda

        self.adv_norm = adv_norm
        self.obs_rms = RunningMeanStdTorch(obs_shape, clip_max=10) if obs_norm else None
        self.rew_rms = RunningMeanStdTorch(1) if rew_norm else None

        self.critic_coeff = critic_coeff
        self.entropy_coeff = entropy_coeff
        self.eps_clip = eps_clip
        self.dual_clip = dual_clip
        self.critic_clip = critic_clip
        self.max_kl = max_kl
        self.recompute_value = recompute_value
        self.max_grad_norm = max_grad_norm
        self.rms_grad_clip = rms_grad_clip

        self.debug_grad = debug_grad

        self.num_mini_batch = num_mini_batch
        self.batch_size = batch_size  # The batch size for policy gradient

        self.critic_warmup_epoch = critic_warmup_epoch
        self.num_epoch = num_epoch
        self.critic_epoch = critic_epoch
        self.actor_epoch = actor_epoch

        # Use extra state to get better feature
        self.regress_visual_state = visual_state_coeff > 0 and visual_state_mlp_cfg is not None and "visual_state" in obs_shape
        self.visual_state_coeff = visual_state_coeff
        if self.regress_visual_state:
            assert shared_backbone, "Only Visuomotor policy supports extra state fitting"

        # For DAPG
        self.dapg_lambda = nn.Parameter(to_torch(dapg_lambda), requires_grad=False)
        self.dapg_damping = dapg_damping
        self.demo_replay = build_replay(demo_replay_cfg)
        if self.demo_replay is not None:
            for key in ['obs', 'actions']:
                assert key in self.demo_replay.memory, f"DAPG needs {key} in your demo!"

        # For done signal process.
        self.ignore_dones = ignore_dones

        # Build networks
        actor_cfg.update(env_params)
        critic_cfg.update(env_params)

        self.actor, self.critic = build_actor_critic(actor_cfg, critic_cfg, shared_backbone)

        if self.regress_visual_state:
            visual_state_mlp_cfg.mlp_spec += [obs_shape["visual_state"]]
            self.extra_fit = build_model(visual_state_mlp_cfg)

        if rms_grad_clip is not None:
            self.grad_rms = RunningSecondMomentumTorch(get_flat_params(self, trainable=True).shape, clip_max=rms_grad_clip)

        self.shared_backbone = shared_backbone
        self.detach_actor_feature = detach_actor_feature

        self.actor_optim = build_optimizer(self.actor, actor_optim_cfg)
        self.critic_optim = build_optimizer(self.critic, critic_optim_cfg)

    def compute_critic_loss(self, samples):
        # For update_actor_critic and update critic
        assert isinstance(samples, (dict, GDict))
        values = self.critic(
            samples["obs"], episode_dones=samples["episode_dones"], save_feature=True
        )
        feature = self.critic.values[0].backbone.pop_attr("saved_feature")
        visual_feature = self.critic.values[0].backbone.pop_attr("saved_visual_feature")
        if self.detach_actor_feature and feature is not None:
            feature = feature.detach()

        if self.critic_clip and isinstance(self.critic_clip, float):
            v_clip = samples["old_values"] + (values - samples["old_values"]).clamp(-self.critic_clip, self.critic_clip)
            vf1 = (samples["returns"] - values).pow(2)
            vf2 = (samples["returns"] - v_clip).pow(2)
            critic_loss = torch.max(vf1, vf2)
        else:
            critic_loss = (samples["returns"] - values).pow(2)

        critic_loss = critic_loss.mean() if samples["is_valid"] is None else critic_loss[samples["is_valid"]].mean()
        return critic_loss, feature, visual_feature

    def update_actor_critic(self, samples, demo_samples=None, with_critic=False):
        """
        Returns True if self.max_kl is not None and
        policy update causes large kl divergence between new policy and old policy,
        in which case we stop the policy update and throw away the current replay buffer
        """
        is_valid = samples["is_valid"]
        self.actor_optim.zero_grad()
        self.critic_optim.zero_grad()
        ret = {}
        critic_loss, actor_loss, demo_actor_loss, visual_state_loss, entropy_term = [0.0] * 5
        feature, visual_feature, critic_loss, policy_std = [None] * 4

        if with_critic:
            critic_mse, feature, visual_feature = self.compute_critic_loss(samples)
            critic_loss = critic_mse * self.critic_coeff
            ret["ppo/critic_err"] = critic_mse.item()
            # ret['ppo/critic_loss'] = critic_loss.item()

        # Run actor forward
        alls = self.actor(
            samples["obs"],
            episode_dones=samples["episode_dones"],
            mode="dist" if self.is_discrete else "dist_std",
            feature=feature,
            save_feature=feature is None,
            require_aux_loss=True, # auxiliary backbone self-supervision, e.g. aux_regress in VisuomotorTransformerFrame
        )
        if isinstance(alls, dict) and 'aux_loss' in alls.keys(): # auxiliary backbone self-supervision, e.g. aux_regress in VisuomotorTransformerFrame
            alls, backbone_aux_loss = alls['feat'], alls['aux_loss']
        else:
            backbone_aux_loss = None

        if not self.is_discrete:
            new_distributions, policy_std = alls
        else:
            new_distributions, policy_std = alls, None
        del alls

        if visual_feature is None:
            visual_feature = self.actor.backbone.pop_attr("saved_visual_feature")

        # Compute actor loss
        dist_entropy = new_distributions.entropy().mean()
        recent_log_p = new_distributions.log_prob(samples["actions"])
        log_ratio = recent_log_p - samples["old_log_p"]
        ratio = log_ratio.exp()
        # print("ratio", ratio[:20], flush=True)

        # Estimation of KL divergence = p (log p - log q) with method in Schulman blog: http://joschu.net/blog/kl-approx.html
        with torch.no_grad():
            approx_kl_div = (ratio - 1 - log_ratio).mean().item()
            clip_frac = (torch.abs(ratio - 1) > self.eps_clip).float().mean().item()
            if policy_std is not None:
                ret["ppo/policy_std"] = policy_std.mean().item()
            ret["ppo/entropy"] = dist_entropy.item()
            ret["ppo/mean_p_ratio"] = ratio.mean().item()
            ret["ppo/max_p_ratio"] = ratio.max().item()
            ret["ppo/log_p"] = recent_log_p.mean().item()
            ret["ppo/clip_frac"] = clip_frac
            ret["ppo/approx_kl"] = approx_kl_div

        sign = GDict(self.max_kl is not None and approx_kl_div > self.max_kl * 1.5).allreduce(op="BOR", wrapper=False)

        if sign:
            return True, ret

        if ratio.ndim == samples["advantages"].ndim - 1:
            ratio = ratio[..., None]

        surr1 = ratio * samples["advantages"]
        surr2 = ratio.clamp(1 - self.eps_clip, 1 + self.eps_clip) * samples["advantages"]
        surr = torch.min(surr1, surr2)
        if self.dual_clip:
            surr = torch.max(surr, self.dual_clip * samples["advantages"])
        actor_loss = -surr[is_valid].mean()
        entropy_term = -dist_entropy * self.entropy_coeff
        ret["ppo/actor_loss"] = actor_loss.item()
        ret["ppo/entropy_loss"] = entropy_term.item()

        # DAPG actor loss
        if demo_samples is not None:
            new_demo_distributions = self.actor(demo_samples["obs"], mode="dist")
            nll_loss_demo = -new_demo_distributions.log_prob(demo_samples["actions"]).mean()
            demo_actor_loss = nll_loss_demo * self.dapg_lambda
            with torch.no_grad():
                ret["dapg/demo_nll_loss"] = nll_loss_demo.item()
                ret["dapg/demo_actor_loss"] = demo_actor_loss.item()

        # State regression loss
        if self.regress_visual_state:
            assert feature is not None
            visual_state_mse = F.mse_loss(self.extra_fit(visual_feature), samples["obs/visual_state"], reduction="none")
            visual_state_mse = visual_state_mse[is_valid].mean()
            ret["ppo-extra/visual_state_mse"] = visual_state_mse
            visual_state_loss = visual_state_mse * self.visual_state_coeff
            ret["ppo-extra/visual_state_loss"] = visual_state_loss.item()

        # Backbone auxiliary supervision loss
        if backbone_aux_loss is not None:
            ret["ppo-extra/backbone_auxiliary_loss"] = backbone_aux_loss.item()

        loss = actor_loss + entropy_term + critic_loss + visual_state_loss + demo_actor_loss
        if backbone_aux_loss is not None:
            loss = loss + backbone_aux_loss
        loss.backward()

        net = self if with_critic else self.actor
        ret["grad/grad_norm"] = net.grad_norm
        if math.isnan(ret["grad/grad_norm"]):
            print("############ Debugging nan grad ############", flush=True)
            print("Dist mean", new_distributions.mean, flush=True)
            print("Dist std", new_distributions.stddev, flush=True)
            print("Samples[actions]", samples["actions"], flush=True)
            print("Recent_log_p", recent_log_p, flush=True)
            print("Samples[old_log_p]", samples["old_log_p"], flush=True)
            for k, v in ret.keys():
                print(k, v, flush=True)

        if self.shared_backbone:
            if getattr(self.actor.backbone, "visual_nn", None) is not None:
                ret["grad/visual_grad"] = self.actor.backbone.visual_nn.grad_norm

            if getattr(self.actor.backbone, "final_mlp", None) is not None:
                ret["grad/actor_mlp_grad"] = self.actor.backbone.final_mlp.grad_norm
            elif self.actor.final_mlp is not None:
                ret["grad/actor_mlp_grad"] = self.actor.final_mlp.grad_norm

            if with_critic:
                if getattr(self.critic.values[0].backbone, "final_mlp", None) is not None:
                    ret["grad/critic_mlp_grad"] = self.critic.values[0].backbone.final_mlp.grad_norm
                elif self.critic.values[0].final_mlp is not None:
                    ret["grad/critic_mlp_grad"] = self.critic.values[0].final_mlp.grad_norm

        if self.max_grad_norm is not None:
            nn.utils.clip_grad_norm_(net.parameters(), self.max_grad_norm)
        elif self.rms_grad_clip is not None:
            grads = get_flat_grads(self)
            grads = self.grad_rms.add(grads)
            set_flat_grads(self, grads)
        ret["grad/clipped_grad_norm"] = net.grad_norm

        self.actor_optim.step()
        if with_critic:
            self.critic_optim.step()

        return False, ret

    def update_critic(self, samples, demo_samples=None):
        self.critic_optim.zero_grad()
        critic_mse = self.compute_critic_loss(samples)[0]
        critic_loss = critic_mse * self.critic_coeff
        critic_loss.backward()

        ret = {}
        ret["grad/grad_norm"] = self.critic.grad_norm
        if self.max_grad_norm is not None:
            nn.utils.clip_grad_norm_(self.critic.parameters(), self.max_grad_norm)
        elif self.rms_grad_clip is not None:
            assert False
            grads = get_flat_grads(self)
            grads = self.grad_rms.add(grads)
            set_flat_grads(self, grads)

        ret["grad/clipped_grad_norm"] = self.critic.grad_norm
        ret["ppo/critic_loss"] = critic_loss.item()
        ret["ppo/critic_mse"] = critic_mse.item()
        self.critic_optim.step()
        return ret

    def update_parameters(self, memory, updates, with_v=False):
        world_size = get_world_size()
        logger = get_logger()
        ret = defaultdict(list)

        process_batch_size = self.batch_size if GDict(memory["obs"]).is_big else None
        if self.num_mini_batch < 0:
            max_samples = GDict(len(memory)).allreduce(op="MAX", device=self.device, wrapper=False) if world_size > 1 else len(memory)
            num_mini_batch = int((max_samples + self.batch_size - 1) // self.batch_size)
        else:
            num_mini_batch = self.num_mini_batch
        logger.info(f"Number of batches in one PPO epoch: {num_mini_batch}!")

        if len(memory) < memory.capacity:
            memory["episode_dones"][len(memory) :] = True

        # Do transformation for all valid samples
        memory["episode_dones"] = (memory["episode_dones"] + memory["is_truncated"]) > 1 - 0.1
        if self.has_obs_process:
            self.obs_rms.sync()
            obs = GDict({"obs": memory["obs"], "next_obs": memory["next_obs"]}).to_torch(device="cpu", wrapper=False)
            obs = GDict(self.process_obs(obs, batch_size=process_batch_size)).to_numpy(wrapper=False)
            memory.update(obs)

        with torch.no_grad():
            memory["old_distribution"], memory["old_log_p"] = self.get_dist_with_logp(
                obs=memory["obs"], actions=memory["actions"], batch_size=process_batch_size
            )
            ret["ppo/old_log_p"].append(memory["old_log_p"].mean().item())

        demo_memory = self.demo_replay
        if demo_memory is not None:
            with torch.no_grad():
                demo_memory = self.demo_replay.sample(min(len(self.demo_replay), len(memory)))
                if self.has_obs_process:
                    demo_memory = demo_memory.to_torch(device="cpu")
                    demo_memory = self.process_obs(demo_memory, batch_size=process_batch_size)
                    demo_memory = demo_memory.to_numpy()
                if self.ignore_dones:
                    demo_memory["dones"] = demo_memory["dones"] * 0

        def run_over_buffer(epoch_id, mode="v"):
            nonlocal memory, ret, demo_memory, logger
            assert mode in ["v", "pi", "v+pi"]

            if "v" in mode and (epoch_id == 0 or self.recompute_value):
                with self.critic.no_sync():
                    memory.update(
                        self.compute_gae(
                            obs=memory["obs"],
                            next_obs=memory["next_obs"],
                            rewards=memory["rewards"],
                            dones=memory["dones"],
                            episode_dones=memory["episode_dones"],
                            update_rms=True,
                            batch_size=process_batch_size,
                            ignore_dones=self.ignore_dones,
                        )
                    )

                if self.adv_norm:
                    # print(mean_adv, std_adv)
                    mean_adv = memory["advantages"].mean(0)
                    std_adv = memory["advantages"].std(0) + 1e-8
                    mean_adv, std_adv = GDict([mean_adv, std_adv]).allreduce(wrapper=False)
                    # print(mean_adv, std_adv)
                    # exit(0)
                    memory["advantages"] = (memory["advantages"] - mean_adv) / std_adv
                    ret["ppo/adv_mean"].append(mean_adv.item())
                    ret["ppo/adv_std"].append(std_adv.item())
                    ret["ppo/max_normed_adv"].append(np.abs(memory["advantages"]).max().item())

                ret["ppo/v_target"].append(memory["returns"].mean().item())
                ret["ppo/ori_returns"].append(memory["original_returns"].mean().item())

            def run_one_iter(samples, demo_samples):
                if "pi" in mode:
                    flag, infos = self.update_actor_critic(samples, demo_samples, with_critic=(mode == "v+pi"))
                    for key in infos:
                        ret[key].append(infos[key])
                elif mode == "v":
                    flag, infos = False, self.update_critic(samples, demo_samples)
                    for key in infos:
                        ret[key].append(infos[key])
                return flag

            for samples in memory.mini_batch_sampler(self.batch_size, drop_last=True, auto_restart=True, max_num_batches=num_mini_batch):
                samples = DictArray(samples).to_torch(device=self.device, non_blocking=True)
                demo_samples = None
                if demo_memory is not None:
                    indices = np.random.randint(0, high=len(demo_memory), size=self.batch_size)
                    demo_samples = demo_memory.slice(indices).to_torch(device=self.device, non_blocking=True)
                if run_one_iter(samples, demo_samples):
                    return True

            return False

        if self.critic_warmup_epoch > 0:
            logger.info("**Warming up critic at the beginning of training; this causes reported ETA to be slower than actual ETA**")
        for i in range(self.critic_warmup_epoch):
            run_over_buffer(i, "v")

        if self.num_epoch > 0:
            for i in range(self.num_epoch):
                num_actor_epoch = i + 1
                if run_over_buffer(i, "v+pi"):
                    break
        else:
            for i in range(self.critic_epoch):
                run_over_buffer(i, "v")
            for i in range(self.actor_epoch):
                num_actor_epoch = i + 1
                if run_over_buffer(i, "pi"):
                    break
        self.critic_warmup_epoch = 0
        ret = {key: np.mean(ret[key]) for key in ret}
        with torch.no_grad():
            ret["param/max_policy_abs"] = torch.max(torch.abs(get_flat_params(self.actor))).item()
            ret["param/policy_norm"] = torch.norm(get_flat_params(self.actor)).item()
            if isinstance(self.critic, nn.Module):
                ret["param/max_critic_abs"] = torch.max(torch.abs(get_flat_params(self.critic))).item()
                ret["param/critic_norm"] = torch.norm(get_flat_params(self.critic)).item()

        for key in ["old_distribution", "old_log_p", "old_values", "old_next_values", "original_returns", "returns", "advantages"]:
            if key in memory.memory:
                memory.memory.pop(key)

        ret["ppo/num_actor_epoch"] = num_actor_epoch
        if self.demo_replay is not None:
            # For DAPG
            ret["dapg/demo_lambda"] = self.dapg_lambda.item()
            self.dapg_lambda *= self.dapg_damping
        if with_v:
            # For PPG
            ret["vf"] = to_np(memory["original_returns"])
        # exit(0)
        return ret


from PyQt5.QtCore import QThread, QMutex, pyqtSignal
from binance.client import Client
import pyupbit
import pybithumb

import requests
from bs4 import BeautifulSoup

from debug import debuginfo

class binanceThread(QThread):
    binance_data = pyqtSignal(dict)
    def __init__(self):
        QThread.__init__(self)
        self.mutex = QMutex()
        self.binance = Client()
        self.binanceList = list()
        self.exchange_rate = float(1100)
        self.isRun = True

    def delSymbol(self, symbol):
        if symbol+"BTC" in self.binanceList:
            self.binanceList.remove(symbol+"BTC")

    def _start(self):
        self.isRun = True
        self.start()

    def stop(self):
        self.isRun = False

    def get_symbol_list(self):
        binanceList = list()
        try:
            for i in self.binance.get_all_tickers():
                symbol = i['symbol']
                if symbol[-3:] == 'BTC':
                    binanceList.append(symbol[:-3])
                if symbol == 'BTCUSDT':
                    binanceList.append(symbol[:-4])
        except Exception as e:
            debuginfo(e)
            pass
        return binanceList

    def save_list(self, list):
        for i in list:
            if i == 'BTC':
                self.binanceList.append('BTCUSDT')
            else:
                self.binanceList.append(i+'BTC')

    def get_dollor(self):
        try:
            res = requests.get('http://finance.naver.com/')
            text = res.text
            soup = BeautifulSoup(text, 'html.parser')
            td = soup.select_one(
                "#content > div.article2 > div.section1 > div.group1 > table > tbody > tr > td")
            exchange_rate = ''
            for i in td.text:
                if i == ',':
                    pass
                else:
                    exchange_rate += i
            self.exchange_rate = float(exchange_rate)
        except Exception as e:
            debuginfo(e)

    def get_prices(self):
        prices = dict()
        try:
            for i in self.binance.get_all_tickers():
                prices[i['symbol']] = i['price']
        except Exception as e:
            debuginfo(e)
            pass
        return prices

    def get_orderbooks(self):
        orderbooks = dict()
        try:
            for i in self.binance.get_orderbook_tickers():
                orderbooks[i['symbol']] = dict()
                orderbooks[i['symbol']]['bidPrice'] = i['bidPrice']
                orderbooks[i['symbol']]['bidQty'] = i['bidQty']
                orderbooks[i['symbol']]['askPrice'] = i['askPrice']
                orderbooks[i['symbol']]['askQty'] = i['askQty']
        except Exception as e:
            debuginfo(e)
            pass
        return orderbooks

    def calculate_krw(self, price, BTCUSDT, exchange_rate):
        return str(round(float(price) * BTCUSDT * exchange_rate, 2))

    def run(self):
        while self.isRun:
            self.mutex.lock()
            binanceDict = dict()
            self.get_dollor()
            prices = self.get_prices()
            orderbooks = self.get_orderbooks()

            try:
                BTCUSDT = float(prices['BTCUSDT'])
                binanceDict['BTC'] = dict()
                binanceDict['BTC']['price'] = str(round(BTCUSDT * self.exchange_rate, 2))
                binanceDict['BTC']['ask'] = str(
                    round(float(orderbooks['BTCUSDT']['askPrice']) * self.exchange_rate, 2)) + '/' + str(
                    round(float(orderbooks['BTCUSDT']['askQty']), 2))
                binanceDict['BTC']['bid'] = str(
                    round(float(orderbooks['BTCUSDT']['bidPrice']) * self.exchange_rate, 2)) + '/' + str(
                    round(float(orderbooks['BTCUSDT']['bidQty']), 2))

            except Exception as e:
                debuginfo(e)

            for i in self.binanceList:
                if i == 'BTCUSDT':
                    continue
                try:
                    symbol = i.replace('BTC', '')
                    binanceDict[symbol] = dict()
                    binanceDict[symbol]['price'] = self.calculate_krw(prices[i], BTCUSDT, self.exchange_rate)
                    binanceDict[symbol]['ask'] = self.calculate_krw(orderbooks[i]['askPrice'], BTCUSDT, self.exchange_rate) + '/' + str(round(float(orderbooks[i]['askQty']), 2))
                    binanceDict[symbol]['bid'] = self.calculate_krw(orderbooks[i]['bidPrice'], BTCUSDT, self.exchange_rate) + '/' + str(round(float(orderbooks[i]['bidQty']), 2))
                except Exception as e:
                    debuginfo(e)
                    pass

            self.binance_data.emit(binanceDict)
            self.mutex.unlock()

class upbitThread(QThread):
    upbit_data = pyqtSignal(dict)
    def __init__(self):
        QThread.__init__(self)
        self.mutex = QMutex()
        self.upbit = pyupbit
        self.upbitList = list()
        self.isRun = True

    def delSymbol(self, symbol):
        if "KRW-"+symbol in self.upbitList:
            self.upbitList.remove("KRW-"+symbol)

    def _start(self):
        self.isRun = True
        self.start()

    def stop(self):
        self.isRun = False

    def get_symbol_list(self):
        upbitList = list()
        try:
            for i in self.upbit.get_tickers(fiat="KRW"):
                upbitList.append(i.split('KRW-')[1])
        except Exception as e:
            debuginfo(e)
            pass
        return upbitList

    def save_list(self, list):
        for i in list:
            self.upbitList.append('KRW-'+i)

    def run(self):
        while self.isRun:
            self.mutex.lock()
            upbitDict = dict()
            prices = self.upbit.get_current_price(self.upbitList)
            orderbooks = self.upbit.get_orderbook(self.upbitList)
            if orderbooks and prices:
                for i in orderbooks:
                    try:
                        symbol = i['market'].split('-')[1]
                        orderbook = i['orderbook_units'][0]
                        ask = str(orderbook['ask_price']) + '/' + str(round(orderbook['ask_size'], 2))
                        bid = str(orderbook['bid_price']) + '/' + str(round(orderbook['bid_size'], 2))
                        upbitDict[symbol] = dict()
                        upbitDict[symbol]['price'] = str(round(prices[i['market']], 2))
                        upbitDict[symbol]['ask'] = ask
                        upbitDict[symbol]['bid'] = bid
                    except Exception as e:
                        debuginfo(e)
                self.upbit_data.emit(upbitDict)
            self.mutex.unlock()

class bithumbThread(QThread):
    bithumb_data = pyqtSignal(dict)
    def __init__(self):
        QThread.__init__(self)
        self.mutex = QMutex()
        self.bithumb = pybithumb.Bithumb
        self.bithumbList = list()
        self.isRun = True

    def delSymbol(self, symbol):
        if symbol in self.bithumbList:
            self.bithumbList.remove(symbol)

    def _start(self):
        self.isRun = True
        self.start()

    def stop(self):
        self.isRun = False

    def get_symbol_list(self):
        bithumbList = list()
        try:
            bithumbList = self.bithumb.get_tickers()
        except Exception as e:
            debuginfo(e)
            pass
        return bithumbList

    def save_list(self, list):
        self.bithumbList = list

    def run(self):
        while self.isRun:
            self.mutex.lock()
            bithumbDict = dict()

            prices = self.bithumb.get_current_price('ALL')
            orderbooks = self.bithumb.get_orderbook('ALL')
            if orderbooks and prices:
                orderbooks = orderbooks['data']
                for i in self.bithumbList:
                    try:
                        price = prices[i]['closing_price']
                        orderbook = orderbooks[i]
                        ask = orderbook['asks'][0]['price'] + '/' + str(round(float(orderbook['asks'][0]['quantity']), 2))
                        bid = orderbook['bids'][0]['price'] + '/' + str(round(float(orderbook['bids'][0]['quantity']), 2))
                        bithumbDict[i] = dict()
                        bithumbDict[i]['price'] = price
                        bithumbDict[i]['ask'] = ask
                        bithumbDict[i]['bid'] = bid
                    except Exception as e:
                        debuginfo(e)
                        pass
                self.bithumb_data.emit(bithumbDict)
            self.mutex.unlock()

if __name__ == "__main__":
    binance = binanceThread()
    upbit = upbitThread()
    bithumb = bithumbThread()

    binanceList = binance.get_symbol_list()
    upbitList = upbit.get_symbol_list()
    bithumbList = bithumb.get_symbol_list()

    binanceUpbitDuplicate = list()
    binanceBithumbDuplicate = list()
    upbitBithumbDuplicate = list()

    for i in binanceList:
        if i in upbitList:
            binanceUpbitDuplicate.append(i)
        if i in bithumbList:
            binanceBithumbDuplicate.append(i)

    for i in upbitList:
        if i in bithumbList:
            upbitBithumbDuplicate.append(i)

    newBinanceList = list(set(binanceUpbitDuplicate+binanceBithumbDuplicate))
    newUpbitList = list(set(binanceUpbitDuplicate+upbitBithumbDuplicate))
    newBithumbList = list(set(binanceBithumbDuplicate+upbitBithumbDuplicate))

    binance.save_list(newBinanceList)
    upbit.save_list(newUpbitList)
    bithumb.save_list(newBithumbList)


from typing import Dict
import os
import shutil

from hexlib.db import Table, PersistentState
import pickle

from tesseract import get_tesseract_langs
import sqlite3
from config import LOG_FOLDER, logger
from sist2 import SearchBackendType, Sist2SearchBackend

RUNNING_FRONTENDS: Dict[str, int] = {}

TESSERACT_LANGS = get_tesseract_langs()

DB_SCHEMA_VERSION = "5"

from pydantic import BaseModel

def _serialize(item):
    if isinstance(item, BaseModel):
        return pickle.dumps(item)
    if isinstance(item, bytes):
        raise Exception("FIXME: bytes in PickleTable")
    return item

def _deserialize(item):
    if isinstance(item, bytes):
        return pickle.loads(item)
    return item

class PickleTable(Table):

    def __getitem__(self, item):
        row = super().__getitem__(item)
        if row:
            return dict((k, _deserialize(v)) for k, v in row.items())
        return row

    def __setitem__(self, key, value):
        value = dict((k, _serialize(v)) for k, v in value.items())
        super().__setitem__(key, value)

    def __iter__(self):
        for row in super().__iter__():
            yield dict((k, _deserialize(v)) for k, v in row.items())

    def sql(self, where_clause, *params):
        for row in super().sql(where_clause, *params):
            yield dict((k, _deserialize(v)) for k, v in row.items())

def get_log_files_to_remove(db: PersistentState, job_name: str, n: int):
    if n < 0:
        return []

    counter = 0
    to_remove = []

    for row in db["task_done"].sql("WHERE has_logs=1 ORDER BY started DESC"):
        if row["name"].endswith(f"[{job_name}]"):
            counter += 1

            if counter > n:
                to_remove.append(row)

    return to_remove

def delete_log_file(db: PersistentState, task_id: str):
    db["task_done"][task_id] = {
        "has_logs": 0
    }

    try:
        os.remove(os.path.join(LOG_FOLDER, f"sist2-{task_id}.log"))
    except:
        pass

def migrate_v1_to_v2(db: PersistentState):
    shutil.copy(db.dbfile, db.dbfile + "-before-migrate-v2.bak")

    # Frontends
    db._table_factory = PickleTable
    frontends = [row["frontend"] for row in db["frontends"]]
    del db["frontends"]

    db._table_factory = Table
    for frontend in frontends:
        db["frontends"][frontend.name] = frontend
    list(db["frontends"])

    # Jobs
    db._table_factory = PickleTable
    jobs = [row["job"] for row in db["jobs"]]
    del db["jobs"]

    db._table_factory = Table
    for job in jobs:
        db["jobs"][job.name] = job
    list(db["jobs"])

    db["sist2_admin"]["info"] = {
        "version": "2"
    }

def create_default_search_backends(db: PersistentState):
    es_backend = Sist2SearchBackend.create_default(name="elasticsearch",
                                                   backend_type=SearchBackendType("elasticsearch"))
    db["search_backends"]["elasticsearch"] = es_backend
    sqlite_backend = Sist2SearchBackend.create_default(name="sqlite", backend_type=SearchBackendType("sqlite"))
    db["search_backends"]["sqlite"] = sqlite_backend

def migrate_v3_to_v4(db: PersistentState):
    shutil.copy(db.dbfile, db.dbfile + "-before-migrate-v4.bak")

    create_default_search_backends(db)

    try:
        conn = sqlite3.connect(db.dbfile)
        conn.execute("ALTER TABLE task_done ADD COLUMN has_logs INTEGER DEFAULT 1")
        conn.commit()
        conn.close()
    except Exception as e:
        logger.exception(e)

    db["sist2_admin"]["info"] = {
        "version": "4"
    }