import datetime
import glob
import os
import sys
import time
import random
import traceback
import torchvision.models as models
from PIL import Image
from torchsummary import summary
import matplotlib.pyplot as plt
import matplotlib.image as mpimg
import cv2
import keyboard
import torch
from torch import nn, optim
from keyboard import press, release
from DeadExp import DeadExp as DE, DeadExp
from input_handler import InputHandler
from men_handler import MemHandler
from models.cnn import alexnet
from video_handler import VideoHandler
from window_loader import WindowLoader
import numpy as np
from numpy import log as ln
import skimage.color
import skimage.io

def rgb2gray(rgb):
    return skimage.color.rgb2gray(rgb)

def save_frame(frame):
    plt.imsave('test.png', frame, cmap=plt.cm.gray)

class ProjectTouhou:

    def __init__(self, load=False):
        self.device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')

        self.window_handler = WindowLoader()
        self.frame_handler = self.window_handler.get_window()
        self.video_handler = VideoHandler(640, 480, 4)
        self.input_handler = InputHandler()
        self.model = models.resnext101_32x8d(pretrained=True)
        self.available_moves = (['shift', 'up', 'z'], ['shift', 'down', 'z'], ['shift', 'left', 'z'], ['shift', 'right', 'z'], ['z'])
        self.model.fc = nn.Linear(self.model.fc.in_features, len(self.available_moves))
        if load:
            self.model.load_state_dict(torch.load('model'))
            self.model.load_state_dict(torch.load('model'))
        self.model.cuda()
        self.optimizer = torch.optim.Adam(self.model.parameters(), lr=0.0001)

        #summary(self.model, (3, 460, 640))
        self.mem_handler = MemHandler()
        self.frame = self.get_frame()
        self.frames = []
        self.frames.append(rgb2gray(self.frame))
        self.frames.append(rgb2gray(self.frame))
        self.frames.append(rgb2gray(self.frame))
        #print(self.frames)
        #print(len(self.frames))
        #print(len(self.frames[0]))
        #print(len(self.frames[0][0]))

        if 'run' not in os.listdir():
            os.mkdir('run')

        self.path = 'run/exp' + str(len(glob.glob('run/exp*'))) + '/'
        self.death_path = 'run/exp' + str(len(glob.glob('run/exp*'))) + '/death/'
        os.mkdir('run/exp' + str(len(glob.glob('run/exp*'))))
        os.mkdir(self.path + 'death')
        self.save_model()

        self.actions = []
        self.hp = 10
        self.mem_handler = MemHandler()
        self.success = 0
        self.power = 0
        self.dg = 0
        self.move_sequels = []

    def get_frame(self):
        return cv2.cvtColor(next(self.frame_handler).reshape(480, 640, 4), cv2.COLOR_BGRA2BGR)

    def save_death_frame(self):
        plt.imsave(
            self.death_path + 'death@' + str(datetime.datetime.now()).replace(' ', '-').replace('-', '_').replace(':','_') + '.png',
            self.frame, cmap=plt.cm.gray)

    def loss(self, output, target):
        return torch.mean(torch.abs(output - target))

    def carrot(self, action):
        _, tmp = torch.max(action, 0)
        ret = [0] * len(action)
        ret[tmp] = 1
        print('🥕', action)
        return torch.tensor(ret, device=self.device)

    def stick(self, action):
        _, tmp = torch.max(action, 0)
        ret = [float(x) + (float(action[tmp] / (len(action) - 1))) for x in action]
        ret[tmp] = 0
        print('🏒', action)
        return torch.tensor(ret, device=self.device)

    def soso(self, action):
        print('😶', action)
        return None

    def get_incentive(self):
        _, tmp = torch.max(self.actions[-1], 0)
        hp, bm, dg, score, power = self.mem_handler.get_score()

        if hp < self.hp:
            self.hp = hp
            self.save_death_frame()
            return -10

        if bm == 0 and tmp == 5:
            return -10

        if hp > 255:
            if self.hp != 255:
                self.save_death_frame()
            self.hp = 255
            self.save_model()
            return -20
        self.hp = hp
        if dg > self.dg:
            self.dg = dg
            return 10
        self.dg = dg

    def backward(self, reward):
        #print(reward)
        discount = 1
        if reward < 0:
            print(self.actions)
        else:
            return
        for i in reversed(self.actions):
            if reward < 0:
                if reward == -20:
                    self.input_handler.clear()
                    self.start = time.time()
                    time.sleep(max(1 / 3 - (time.time() - self.start), 0))
                    self.input_handler.set(['z'])
                    self.start = time.time()
                    time.sleep(max(1 / 3 - (time.time() - self.start), 0))
                loss = self.loss(i, self.stick(i))
                loss.backward()
            #elif reward > 0:
                #pass
                #loss = self.loss(i, self.carrot(i))
            #else:
                #loss = self.loss(i, self.soso(i))
            #loss.backward()
        self.optimizer.step()
        self.optimizer.zero_grad()
        self.actions = []

    def save_model(self):
        torch.save(self.model.state_dict(),
                   self.path + 'model@' + str(datetime.datetime.now()).replace(' ', '-').replace('-', '_').replace(':',
                                                                                '_'))

    def GameOn(self):
        self.frame = self.get_frame()
        self.start = time.time()
        while (True):
            if keyboard.is_pressed('/'):  # if key 'q' is pressed
                print('You Pressed the Key! :3')
                torch.save(self.model.state_dict(),'model')
                break  # finishing the loop\
            self.frame = self.get_frame()
            if len(self.actions) != 0:
                inc = self.get_incentive()
                #print(inc)
                if inc is not None:
                    self.backward(inc)
                    #self.actions = []
                if inc == -20:
                    continue

            self.frames.pop(0)
            self.frames.append(rgb2gray(self.frame) / 255)
            action = self.model(torch.Tensor([self.frames]).cuda()/255)
            self.actions.append(action[0])
            if(len(self.actions)>3):
                self.actions.pop(0)
            #print(action)
            #print(action[0])
            conf, move = torch.max(action, 1)
            self.input_handler.set(self.available_moves[move[0]])
            if move[0] == 5:
                time.sleep(1 / 5)
                self.input_handler.clear()
                time.sleep(1 / 5)
            print(self.available_moves[move[0]],conf)
            # self.save_death_frame()
            time.sleep(max(1 / 10 - (time.time() - self.start), 0))
            print(1 / (time.time() - self.start))

            self.start = time.time()

        # self.save_frame(self.rgb2gray(frame))///
        torch.save(self.model.state_dict(), self.path + 'last')
        self.video_handler.close()
        self.input_handler.close()
        # self.window_handler.close()

if __name__ == "__main__":
    tmp = ProjectTouhou(load=True)
    #tmp = ProjectTouhou()
    tmp.GameOn()


import skimage.io as io
import skimage.transform as skt
import numpy as np
from PIL import Image
from src.models.class_patcher import patcher
from src.utils.imgproc import *

class patcher(patcher):
    def __init__(self, body='./body/body_mira.png', **options):
        super().__init__('ミラ', body=body, pantie_position=[1268, 10], **options)
        self.mask = io.imread('./mask/mask_mira.png')

    def convert(self, image):
        pantie = np.array(image)
        arrx = np.zeros(25)
        arry = np.zeros(25)
        arry[:10] -= np.sin(np.linspace(0, 1, 10)) * 30
        pantie_ = affine_transform_by_arr(pantie, arrx, arry)

        patch = np.copy(pantie[-190:, 546:, :])
        pantie[-100:, 546:, :] = 0
        patch = skt.resize(patch[::-1, ::-1, :], (patch.shape[0] + 25, patch.shape[1]), anti_aliasing=True, mode='reflect')
        [pr, pc, d] = patch.shape
        pantie_[125:125 + pr, 3:3+pc, :] = patch
        pantie = pantie_[:340]
        pantie = np.uint8(resize(pantie, [0.9, 1.25]) * 255)
        pantie = np.bitwise_and(pantie, self.mask)
        return Image.fromarray(pantie)


from __future__ import (absolute_import, division, print_function, unicode_literals)

from math import floor, radians
# from os import system
from os.path import dirname, join
from struct import unpack
from sys import argv, exit

from numpy import array, empty, float64, fromfile

from .functional import kwargs_only
from .timezone_names import timezone_names

# later functions should be automatically compiled once on installation:
# try:
#     import compiled_numba_funcs
# except ImportError:
#     precompilation = None
#     try:
#         import numba
#     except ImportError:
#         numba = None
#
#     if numba is not None:
#         from .helpers_numba import coord2int, distance_to_polygon_exact, distance_to_polygon, inside_polygon, \
#             all_the_same
#     else:
#         from .helpers import coord2int, distance_to_polygon_exact, inside_polygon, all_the_same, distance_to_polygon
#
#
# try:
#     import numba
#
#     print('using numba version:', numba.__version__)
#
#     print('compiling the helpers ahead of time...')
#     # FIXME target architecture is wrong. because of old Numba version?
#     # TODO in this environment numba could not be available
#     # precompile functions by running the helpers_numba.py script
#     system("python3 " + join(dirname(__file__), 'helpers_numba.py'))
#     try:
#         from compiled_helpers import coord2int, distance_to_polygon_exact, distance_to_polygon, inside_polygon, \
#             all_the_same
#
#         print('... worked!')
#
#     except ImportError:
#         from .helpers_numba import coord2int, distance_to_polygon_exact, distance_to_polygon, inside_polygon, \
#             all_the_same
#
#     print('... did not work!')
#
# except ImportError:
#     numba = None
#     from .helpers import coord2int, distance_to_polygon_exact, inside_polygon, all_the_same, distance_to_polygon

try:
    import numba
    from .helpers_numba import coord2int, int2coord, distance_to_polygon_exact, distance_to_polygon, inside_polygon, \
        all_the_same
except ImportError:
    numba = None
    from .helpers import coord2int, int2coord, distance_to_polygon_exact, distance_to_polygon, inside_polygon, \
        all_the_same

# those two helper functions cannot be outsourced to helpers.py because they create lists (not supported by numba)
def convert2coords(polygon_data):
    # return a tuple of coordinate lists
    return [[int2coord(x) for x in polygon_data[0]], [int2coord(y) for y in polygon_data[1]]]

def convert2coord_pairs(polygon_data):
    # return a list of coordinate tuples (x,y)
    coodinate_list = []
    i = 0
    for x in polygon_data[0]:
        coodinate_list.append((int2coord(x), int2coord(polygon_data[1][i])))
        i += 1
    return coodinate_list

class TimezoneFinder:
    """
    This class lets you quickly find the timezone of a point on earth.
    It keeps the binary files open in reading mode to enable fast consequent access.
    currently per half degree of latitude and per degree of longitude a set of candidate polygons are stored
        this gives a SHORTCUT to which of the 27k+ polygons should be tested
        (tests evaluated this to be the fastest setup when being used with numba)
    """

    def __init__(self):

        # open all the files in binary reading mode
        # for more info on what is stored in which .bin file, please read the comments in file_converter.py
        self.poly_zone_ids = open(join(dirname(__file__), 'poly_zone_ids.bin'), 'rb')
        self.poly_coord_amount = open(join(dirname(__file__), 'poly_coord_amount.bin'), 'rb')
        self.poly_adr2data = open(join(dirname(__file__), 'poly_adr2data.bin'), 'rb')
        self.poly_data = open(join(dirname(__file__), 'poly_data.bin'), 'rb')
        self.poly_max_values = open(join(dirname(__file__), 'poly_max_values.bin'), 'rb')
        self.poly_nr2zone_id = open(join(dirname(__file__), 'poly_nr2zone_id.bin'), 'rb')

        self.hole_poly_ids = open(join(dirname(__file__), 'hole_poly_ids.bin'), 'rb')
        self.hole_coord_amount = open(join(dirname(__file__), 'hole_coord_amount.bin'), 'rb')
        self.hole_adr2data = open(join(dirname(__file__), 'hole_adr2data.bin'), 'rb')
        self.hole_data = open(join(dirname(__file__), 'hole_data.bin'), 'rb')

        self.shortcuts_entry_amount = open(join(dirname(__file__), 'shortcuts_entry_amount.bin'), 'rb')
        self.shortcuts_adr2data = open(join(dirname(__file__), 'shortcuts_adr2data.bin'), 'rb')
        self.shortcuts_data = open(join(dirname(__file__), 'shortcuts_data.bin'), 'rb')
        self.shortcuts_unique_id = open(join(dirname(__file__), 'shortcuts_unique_id.bin'), 'rb')

        # store for which polygons (how many) holes exits and the id of the first of those holes
        # since there are very few (+-22) it is feasible to keep them in the memory
        self.hole_registry = {}
        i = 0
        # read the polygon ids for all the holes
        for block in iter(lambda: self.hole_poly_ids.read(2), b''):
            poly_id = unpack(b'<H', block)[0]
            try:
                amount_of_holes, hole_id = self.hole_registry[poly_id]
                self.hole_registry.update({
                    poly_id: (amount_of_holes + 1, hole_id)
                })
            except KeyError:
                self.hole_registry.update({
                    poly_id: (1, i)
                })
            i += 1

    def __del__(self):
        self.poly_zone_ids.close()
        self.poly_coord_amount.close()
        self.poly_adr2data.close()
        self.poly_data.close()
        self.poly_max_values.close()
        self.poly_nr2zone_id.close()
        self.hole_poly_ids.close()
        self.hole_coord_amount.close()
        self.hole_adr2data.close()
        self.hole_data.close()
        self.shortcuts_entry_amount.close()
        self.shortcuts_adr2data.close()
        self.shortcuts_data.close()
        self.shortcuts_unique_id.close()

    @staticmethod
    def using_numba():
        return numba is not None

    # TODO enable
    #  @staticmethod
    # def using_precompiled_funcs():
    #     return (precompilation is not None)

    def id_of(self, line=0):
        self.poly_zone_ids.seek(2 * line)
        return unpack(b'<H', self.poly_zone_ids.read(2))[0]

    def ids_of(self, iterable):
        id_array = empty(shape=len(iterable), dtype='<i1')

        i = 0
        for line_nr in iterable:
            self.poly_zone_ids.seek((2 * line_nr))
            id_array[i] = unpack(b'<H', self.poly_zone_ids.read(2))[0]
            i += 1

        return id_array

    def polygons_of_shortcut(self, x=0, y=0):
        # get the address of the first entry in this shortcut
        # offset: 180 * number of shortcuts per lat degree * 2bytes = entries per column of x shortcuts
        # shortcuts are stored: (0,0) (0,1) (0,2)... (1,0)...
        self.shortcuts_entry_amount.seek(720 * x + 2 * y)
        nr_of_entries = unpack(b'<H', self.shortcuts_entry_amount.read(2))[0]

        self.shortcuts_adr2data.seek(1440 * x + 4 * y)
        self.shortcuts_data.seek(unpack(b'<I', self.shortcuts_adr2data.read(4))[0])
        return fromfile(self.shortcuts_data, dtype='<u2', count=nr_of_entries)

    def coords_of(self, line=0):
        # how many coordinates are stored in this polygon
        self.poly_coord_amount.seek(2 * line)
        nr_of_values = unpack(b'<H', self.poly_coord_amount.read(2))[0]

        self.poly_adr2data.seek(4 * line)
        self.poly_data.seek(unpack(b'<I', self.poly_adr2data.read(4))[0])

        return array([fromfile(self.poly_data, dtype='<i4', count=nr_of_values),
                      fromfile(self.poly_data, dtype='<i4', count=nr_of_values)])

    def _holes_of_line(self, line=0):
        try:
            amount_of_holes, hole_id = self.hole_registry[line]

            for i in range(amount_of_holes):
                self.hole_coord_amount.seek(2 * hole_id)
                nr_of_values = unpack(b'<H', self.hole_coord_amount.read(2))[0]

                self.hole_adr2data.seek(4 * hole_id)
                self.hole_data.seek(unpack(b'<I', self.hole_adr2data.read(4))[0])

                yield array([fromfile(self.hole_data, dtype='<i4', count=nr_of_values),
                             fromfile(self.hole_data, dtype='<i4', count=nr_of_values)])
                hole_id += 1

        except KeyError:
            return

    def get_polygon(self, polygon_nr, coords_as_pairs=False):
        list_of_converted_polygons = []

        if coords_as_pairs:
            conversion_method = convert2coord_pairs
        else:
            conversion_method = convert2coords

        list_of_converted_polygons.append(conversion_method(self.coords_of(line=polygon_nr)))
        for hole in self._holes_of_line(polygon_nr):
            list_of_converted_polygons.append(conversion_method(hole))

        return list_of_converted_polygons

    def get_geometry(self, tz_name='', tz_id=0, use_id=False, coords_as_pairs=False):
        '''
        :param tz_name: one of the names in timezone_names.py
        :param tz_id: the id of the timezone (=index in timezone_names.py)
        :param use_id: determines whether id or name should be used
        :param coords_as_pairs: determines the structure of the polygon representation
        :return: a data structure representing the multipolygon of this timezone
        output format: [ [polygon1, hole1, hole2...], [polygon1, ...], ...]
         and each polygon and hole is itself formated like: ([longitudes], [latitudes])
         or [(lng1,lat1), (lng2,lat2),...] if ``coords_as_pairs=True``.

        TODO optimize, store additional info in binaries to compile this faster, maybe sort polygons after id
        '''

        if use_id:
            zone_id = tz_id
        else:
            try:
                zone_id = timezone_names.index(tz_name)
            except ValueError:
                raise ValueError("The timezone '", tz_name, "' does not exist.")

        self.poly_nr2zone_id.seek(2 * zone_id)
        # read poly_nr of the first polygon of that zone
        first_polygon_nr = unpack(b'<H', self.poly_nr2zone_id.read(2))[0]
        # read poly_nr of the first polygon of the next zone
        last_polygon_nr = unpack(b'<H', self.poly_nr2zone_id.read(2))[0]
        print(first_polygon_nr, last_polygon_nr, range(first_polygon_nr, last_polygon_nr))
        poly_nrs = range(first_polygon_nr, last_polygon_nr)
        return [self.get_polygon(poly_nr, coords_as_pairs) for poly_nr in poly_nrs]

    def id_list(self, polygon_id_list, nr_of_polygons):
        """
        :param polygon_id_list:
        :param nr_of_polygons: length of polygon_id_list
        :return: (list of zone_ids, boolean: do all entries belong to the same zone)
        """
        zone_id_list = empty([nr_of_polygons], dtype='<u2', )
        pointer_local = 0

        first_id = self.id_of(polygon_id_list[0])
        equal = True
        for polygon_id in polygon_id_list:
            zone_id = self.id_of(polygon_id)
            if zone_id != first_id:
                equal = False
            zone_id_list[pointer_local] = zone_id
            pointer_local += 1

        return zone_id_list, equal

    def compile_id_list(self, polygon_id_list, nr_of_polygons):
        """
        sorts the polygons_id list from least to most occurrences of the zone ids (->speed up)
        4.8% of all shortcuts include polygons from more than one zone
        but only for about 0.4% sorting would be beneficial (zones have different frequencies)
        in most of those cases there are only two types of zones (= entries in counted_zones) and one of them
         has only one entry.
        As a consequence the effort of sorting only really makes sense for closest_timezone_at(), because only in
         that use case the polygon lists are quite long (multiple shortcuts are being checked simultaneously).
        :param polygon_id_list:
        :param nr_of_polygons: length of polygon_id_list
        :return: sorted list of polygon_ids, sorted list of zone_ids, boolean: do all entries belong to the same zone
        """

        def all_equal(iterable):
            x = None
            for x in iterable:
                # first_val = x
                break
            for y in iterable:
                if x != y:
                    return False
            return True

        zone_id_list = empty([nr_of_polygons], dtype='<u2', )
        pointer_local = 0
        counted_zones = {}
        for polygon_id in polygon_id_list:
            zone_id = self.id_of(polygon_id)
            zone_id_list[pointer_local] = zone_id
            pointer_local += 1
            try:
                counted_zones[zone_id] += 1
            except KeyError:
                counted_zones[zone_id] = 1
        # print(counted_zones)

        if len(counted_zones) == 1:
            # there is only one zone. no sorting needed.
            return polygon_id_list, zone_id_list, True

        if all_equal(counted_zones.values()):
            # all the zones have the same amount of polygons. no sorting needed.
            return polygon_id_list, zone_id_list, False

        counted_zones_sorted = sorted(counted_zones.items(), key=lambda zone: zone[1])
        sorted_polygon_id_list = empty([nr_of_polygons], dtype='<u2')
        sorted_zone_id_list = empty([nr_of_polygons], dtype='<u2')

        pointer_output = 0
        for zone_id, amount in counted_zones_sorted:
            # write all polygons from this zone in the new list
            pointer_local = 0
            detected_polygons = 0
            while detected_polygons < amount:
                if zone_id_list[pointer_local] == zone_id:
                    # the polygon at the pointer has the wanted zone_id
                    detected_polygons += 1
                    sorted_polygon_id_list[pointer_output] = polygon_id_list[pointer_local]
                    sorted_zone_id_list[pointer_output] = zone_id
                    pointer_output += 1

                pointer_local += 1

        return sorted_polygon_id_list, sorted_zone_id_list, False

    @kwargs_only
    def closest_timezone_at(self, lng, lat, delta_degree=1, exact_computation=False, return_distances=False,
                            force_evaluation=False):
        """
        This function searches for the closest polygon in the surrounding shortcuts.
        Make sure that the point does not lie within a polygon (for that case the algorithm is simply wrong!)
        Note that the algorithm won't find the closest polygon when it's on the 'other end of earth'
        (it can't search beyond the 180 deg lng border yet)
        This checks all the polygons within [delta_degree] degree lng and lat/
        Keep in mind that x degrees lat are not the same distance apart than x degree lng!
        This is also the reason why there could still be a closer polygon even though you got a result already.
        In order to make sure to get the closest polygon, you should increase the search radius
        until you get a result and then increase it once more (and take that result).
        This should only make a difference in really rare cases however.
        :param lng: longitude of the point in degree
        :param lat: latitude in degree
        :param delta_degree: the 'search radius' in degree
        :param exact_computation: when enabled the distance to every polygon edge is computed (way more complicated),
        instead of only evaluating the distances to all the vertices (=default).
        This only makes a real difference when polygons are very close.
        :param return_distances: when enabled the output looks like this:
        ( 'tz_name_of_the_closest_polygon',[ distances to all polygons in km], [tz_names of all polygons])
        :param force_evaluation:
        :return: the timezone name of the closest found polygon, the list of distances or None
        """

        def exact_routine(polygon_nr):
            coords = self.coords_of(polygon_nr)
            nr_points = len(coords[0])
            empty_array = empty([2, nr_points], dtype=float64)
            return distance_to_polygon_exact(lng, lat, nr_points, coords, empty_array)

        def normal_routine(polygon_nr):
            coords = self.coords_of(polygon_nr)
            nr_points = len(coords[0])
            return distance_to_polygon(lng, lat, nr_points, coords)

        if lng > 180.0 or lng < -180.0 or lat > 90.0 or lat < -90.0:
            raise ValueError('The coordinates are out ouf bounds: (', lng, ',', lat, ')')

        # transform point X into cartesian coordinates
        current_closest_id = None
        central_x_shortcut = int(floor((lng + 180)))
        central_y_shortcut = int(floor((90 - lat) * 2))

        lng = radians(lng)
        lat = radians(lat)

        possible_polygons = []

        # there are 2 shortcuts per 1 degree lat, so to cover 1 degree two shortcuts (rows) have to be checked
        # the highest shortcut is 0
        top = max(central_y_shortcut - 2 * delta_degree, 0)
        # the lowest shortcut is 359 (= 2 shortcuts per 1 degree lat)
        bottom = min(central_y_shortcut + 2 * delta_degree, 359)

        # the most left shortcut is 0
        left = max(central_x_shortcut - delta_degree, 0)
        # the most right shortcut is 359 (= 1 shortcuts per 1 degree lng)
        right = min(central_x_shortcut + delta_degree, 359)

        # select all the polygons from the surrounding shortcuts
        for x in range(left, right + 1, 1):
            for y in range(top, bottom + 1, 1):
                for p in self.polygons_of_shortcut(x, y):
                    if p not in possible_polygons:
                        possible_polygons.append(p)

        polygons_in_list = len(possible_polygons)

        if polygons_in_list == 0:
            return None

        # initialize the list of ids
        # this list is sorted (see documentation of compile_id_list() )
        possible_polygons, ids, zones_are_equal = self.compile_id_list(possible_polygons, polygons_in_list)

        # if all the polygons in this shortcut belong to the same zone return it
        if zones_are_equal:
            if not (return_distances or force_evaluation):
                return timezone_names[ids[0]]

        if exact_computation:
            routine = exact_routine
        else:
            routine = normal_routine

        # the maximum possible distance is half the perimeter of earth pi * 12743km = 40,054.xxx km
        min_distance = 40100

        distances = empty(polygons_in_list, dtype=float64)
        # [None for i in range(polygons_in_list)]
        pointer = 0

        if force_evaluation:
            for polygon_nr in possible_polygons:
                distance = routine(polygon_nr)
                distances[pointer] = distance
                if distance < min_distance:
                    min_distance = distance
                    current_closest_id = ids[pointer]
                pointer += 1

        else:
            # stores which polygons have been checked yet
            already_checked = [False] * polygons_in_list  # initialize array with False
            while pointer < polygons_in_list:

                # only check a polygon when its id is not the closest a the moment and it has not been checked already!
                if already_checked[pointer] or ids[pointer] == current_closest_id:
                    # go to the next polygon
                    pointer += 1

                else:
                    # this polygon has to be checked
                    distance = routine(possible_polygons[pointer])
                    distances[pointer] = distance
                    already_checked[pointer] = True
                    if distance < min_distance:
                        min_distance = distance
                        current_closest_id = ids[pointer]
                        # list of polygons has to be searched again, because closest zone has changed
                        # set pointer to the beginning of the list
                        # having a sorted list of polygon is beneficial here (less common zones come first)
                        pointer = 1

        if return_distances:
            return timezone_names[current_closest_id], distances, [timezone_names[x] for x in ids]
        return timezone_names[current_closest_id]

    @kwargs_only
    def timezone_at(self, lng=0.0, lat=0.0):
        """
        this function looks up in which polygons the point could be included in
        to speed things up there are shortcuts being used (stored in a binary file)
        especially for large polygons it is expensive to check if a point is really included,
        so certain simplifications are made and even when you get a hit the point might actually
        not be inside the polygon (for example when there is only one timezone nearby)
        if you want to make sure a point is really inside a timezone use 'certain_timezone_at'
        :param lng: longitude of the point in degree (-180 to 180)
        :param lat: latitude in degree (90 to -90)
        :return: the timezone name of a matching polygon or None
        """
        if lng > 180.0 or lng < -180.0 or lat > 90.0 or lat < -90.0:
            raise ValueError('The coordinates are out ouf bounds: ( %f, %f, )' % (lng, lat))

        # x = longitude  y = latitude  both converted to 8byte int
        x = coord2int(lng)
        y = coord2int(lat)

        shortcut_id_x = int(floor((lng + 180)))
        shortcut_id_y = int(floor((90 - lat) * 2))
        self.shortcuts_unique_id.seek((720 * shortcut_id_x + 2 * shortcut_id_y))
        try:
            return timezone_names[unpack(b'<H', self.shortcuts_unique_id.read(2))[0]]
        except IndexError:
            possible_polygons = self.polygons_of_shortcut(shortcut_id_x, shortcut_id_y)
            nr_possible_polygons = len(possible_polygons)
            if nr_possible_polygons == 0:
                return None
            if nr_possible_polygons == 1:
                # there is only one polygon in that area. return its timezone name without further checks
                return timezone_names[self.id_of(possible_polygons[0])]

            # create a list of all the timezone ids of all possible polygons
            ids, only_one_zone = self.id_list(possible_polygons, nr_possible_polygons)
            if only_one_zone:
                # all of the polygons belong to the same zone. return its name.
                return timezone_names[ids[0]]

            # otherwise check until the point is included in one of the possible polygons
            for i in range(nr_possible_polygons):
                polygon_nr = possible_polygons[i]

                # get the boundaries of the polygon = (lng_max, lng_min, lat_max, lat_min)
                self.poly_max_values.seek(16 * polygon_nr)
                boundaries = fromfile(self.poly_max_values, dtype='<i4', count=4)
                # only run the expensive algorithm if the point is withing the boundaries
                if not (x > boundaries[0] or x < boundaries[1] or y > boundaries[2] or y < boundaries[3]):

                    outside_all_holes = True
                    # when the point is within a hole of the polygon, this timezone doesn't need to be checked
                    for hole_coordinates in self._holes_of_line(polygon_nr):
                        if inside_polygon(x, y, hole_coordinates):
                            outside_all_holes = False
                            break

                    if outside_all_holes:
                        if inside_polygon(x, y, self.coords_of(line=polygon_nr)):
                            # the point is included in this polygon. return its timezone name without further checks
                            return timezone_names[ids[i]]

                # when after the current polygon only polygons from the same zone remain, return the name of that zone
                same_element = all_the_same(pointer=i + 1, length=nr_possible_polygons, id_list=ids)
                if same_element != -1:
                    return timezone_names[same_element]

            # this statement is actually never reached, because the timezone name of the last polygon is always returned
            # if no other polygon has been matched beforehand.
            return None

    @kwargs_only
    def certain_timezone_at(self, lng=0.0, lat=0.0):
        """
        this function looks up in which polygon the point certainly is included
        this is much slower than 'timezone_at'!
        :param lng: longitude of the point in degree
        :param lat: latitude in degree
        :return: the timezone name of the polygon the point is included in or None
        """

        if lng > 180.0 or lng < -180.0 or lat > 90.0 or lat < -90.0:
            raise ValueError('The coordinates are out ouf bounds: (', lng, ',', lat, ')')

        shortcut_id_x = int(floor((lng + 180)))
        shortcut_id_y = int(floor((90 - lat) * 2))
        possible_polygons = self.polygons_of_shortcut(shortcut_id_x, shortcut_id_y)

        # x = longitude  y = latitude  both converted to 8byte int
        x = coord2int(lng)
        y = coord2int(lat)

        # check if the point is actually included in one of the polygons
        for polygon_nr in possible_polygons:

            # get boundaries
            self.poly_max_values.seek(16 * polygon_nr)
            boundaries = fromfile(self.poly_max_values, dtype='<i4', count=4)
            if not (x > boundaries[0] or x < boundaries[1] or y > boundaries[2] or y < boundaries[3]):

                outside_all_holes = True
                # when the point is within a hole of the polygon this timezone doesn't need to be checked
                for hole_coordinates in self._holes_of_line(polygon_nr):
                    if inside_polygon(x, y, hole_coordinates):
                        outside_all_holes = False
                        break

                if outside_all_holes:
                    if inside_polygon(x, y, self.coords_of(line=polygon_nr)):
                        return timezone_names[self.id_of(polygon_nr)]

        # no polygon has been matched
        return None

if __name__ == '__main__':
    arguments = argv
    nr_arguments = len(arguments)
    if nr_arguments < 3:
        print('Error: not enough arguments given\ncommand should look like: python timezonefinderL.py lng lat [-v]')
        exit(1)

    tf = TimezoneFinder()
    longitude = float(arguments[1])
    latitude = float(arguments[2])
    tz = tf.timezone_at(lng=longitude, lat=latitude)

    if nr_arguments > 3 and arguments[3] == '-v':
        # verbose mode:
        print('Looking for TZ at lat=', latitude, ' lon=', longitude)
        print('Timezone=', tz)
    else:
        print(tz)


#! /usr/bin/env python
"""Run a YOLO_v2 style detection model on test images."""
import argparse
import colorsys
import imghdr
import os
import random
import cv2
import numpy as np
from keras import backend as K
from keras.models import load_model
from PIL import Image, ImageDraw, ImageFont

from yad2k.models.keras_yolo import yolo_eval, yolo_head

parser = argparse.ArgumentParser(
    description='Run a YOLO_v2 style detection model on test images..')
parser.add_argument(
    '--model_path',
    help='path to h5 model file containing body of a YOLO_v2 model',
    default = 'model_data/yolo.h5')
parser.add_argument(
    '-a',
    '--anchors_path',
    help='path to anchors file, defaults to yolo_anchors.txt',
    default='model_data/yolo_anchors.txt')
parser.add_argument(
    '-c',
    '--classes_path',
    help='path to classes file, defaults to coco_classes.txt',
    default='model_data/coco_classes.txt')
parser.add_argument(
    '-t',
    '--test_path',
    help='path to directory of test images, defaults to images/',
    default='images')
parser.add_argument(
    '-o',
    '--output_path',
    help='path to output test images, defaults to images/out',
    default='images/out')
parser.add_argument(
    '-s',
    '--score_threshold',
    type=float,
    help='threshold for bounding box scores, default .3',
    default=.3)
parser.add_argument(
    '-iou',
    '--iou_threshold',
    type=float,
    help='threshold for non max suppression IOU, default .5',
    default=.5)

def _main(args):
    model_path = os.path.expanduser(args.model_path)
    assert model_path.endswith('.h5'), 'Keras model must be a .h5 file.'
    anchors_path = os.path.expanduser(args.anchors_path)
    classes_path = os.path.expanduser(args.classes_path)
    test_path = os.path.expanduser(args.test_path)
    output_path = os.path.expanduser(args.output_path)

    if not os.path.exists(output_path):
        print('Creating output path {}'.format(output_path))
        os.mkdir(output_path)

    sess = K.get_session()  # TODO: Remove dependence on Tensorflow session.

    with open(classes_path) as f:
        class_names = f.readlines()
    class_names = [c.strip() for c in class_names]

    with open(anchors_path) as f:
        anchors = f.readline()
        anchors = [float(x) for x in anchors.split(',')]
        anchors = np.array(anchors).reshape(-1, 2)

    yolo_model = load_model(model_path)

    # Verify model, anchors, and classes are compatible
    num_classes = len(class_names)
    num_anchors = len(anchors)
    # TODO: Assumes dim ordering is channel last
    model_output_channels = yolo_model.layers[-1].output_shape[-1]
    assert model_output_channels == num_anchors * (num_classes + 5), \
        'Mismatch between model and given anchor and class sizes. ' \
        'Specify matching anchors and classes with --anchors_path and ' \
        '--classes_path flags.'
    print('{} model, anchors, and classes loaded.'.format(model_path))

    # Check if model is fully convolutional, assuming channel last order.
    model_image_size = yolo_model.layers[0].input_shape[1:3]
    is_fixed_size = model_image_size != (None, None)

    # Generate colors for drawing bounding boxes.
    hsv_tuples = [(x / len(class_names), 1., 1.)
                  for x in range(len(class_names))]
    colors = list(map(lambda x: colorsys.hsv_to_rgb(*x), hsv_tuples))
    colors = list(
        map(lambda x: (int(x[0] * 255), int(x[1] * 255), int(x[2] * 255)),
            colors))
    random.seed(10101)  # Fixed seed for consistent colors across runs.
    random.shuffle(colors)  # Shuffle colors to decorrelate adjacent classes.
    random.seed(None)  # Reset seed to default.

    # Generate output tensor targets for filtered bounding boxes.
    # TODO: Wrap these backend operations with Keras layers.
    yolo_outputs = yolo_head(yolo_model.output, anchors, len(class_names))
    input_image_shape = K.placeholder(shape=(2, ))
    boxes, scores, classes = yolo_eval(
        yolo_outputs,
        input_image_shape,
        score_threshold=args.score_threshold,
        iou_threshold=args.iou_threshold)

    #video url
    videoUrl = "C:\\Users\\Jason\\Scripts\\YOLO\\YAD2K-master\\images\\20200102   Surf Cam Surf spot Twin Lion beach in Toucheng Yilan County Taiwan ROC.mp4"#"https://thbcctv01.thb.gov.tw/T1A-9K+700"
    cap = cv2.VideoCapture(videoUrl)

    while(True):
        try:
            ret, frame = cap.read()
            rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2BGRA)
        except:
            cap = cv2.VideoCapture(videoUrl)
            import time
            time.sleep(0.5)
            continue

        if cv2.waitKey(1) & 0xFF == ord('q'):
            out = cv2.imwrite('capture.jpg', frame)
            break
        frame = cv2.resize(frame, tuple(
            reversed(model_image_size)), Image.BICUBIC)
        image_data = np.array(frame, dtype='float32')
        image_data /= 255.
        image_data = np.expand_dims(image_data, 0)  # Add batch dimension.
        out_boxes, out_scores, out_classes = sess.run(
            [boxes, scores, classes],
            feed_dict={
                yolo_model.input: image_data,
                input_image_shape: [frame.shape[1], frame.shape[0]],
                K.learning_phase(): 0
            })
        # print('Found {} boxes for {}'.format(len(out_boxes), image_file))

        font = ImageFont.truetype(
            font='font/FiraMono-Medium.otf',
            size=np.floor(3e-2 * frame.shape[1] + 0.5).astype('int32'))
        thickness = (frame.shape[0] + frame.shape[1]) // 300

        img = Image.fromarray(cv2.cvtColor(frame, cv2.COLOR_BGR2RGB))
        for i, c in reversed(list(enumerate(out_classes))):
            predicted_class = class_names[c]
            box = out_boxes[i]
            score = out_scores[i]

            label = '{} {:.2f}'.format(predicted_class, score)

            draw = ImageDraw.Draw(img)
            label_size = draw.textsize(label, font)

            top, left, bottom, right = box
            top = max(0, np.floor(top + 0.5).astype('int32'))
            left = max(0, np.floor(left + 0.5).astype('int32'))
            bottom = min(frame.shape[1], np.floor(bottom + 0.5).astype('int32'))
            right = min(frame.shape[0], np.floor(right + 0.5).astype('int32'))
            print(label, (left, top), (right, bottom))

            if top - label_size[1] >= 0:
                text_origin = np.array([left, top - label_size[1]])
            else:
                text_origin = np.array([left, top + 1])

            # My kingdom for a good redistributable image drawing library.
            for i in range(thickness):
                draw.rectangle(
                    [left + i, top + i ,
                        right - i, bottom - i],
                    outline=colors[c])
            draw.rectangle(
                [tuple(text_origin), tuple(text_origin + label_size)],
                fill=colors[c])
            draw.text(text_origin, label, fill=(0, 0, 0), font=font)
            del draw
        cv_img = cv2.cvtColor(np.asarray(img), cv2.COLOR_RGB2BGR)
        resized = cv2.resize(cv_img, (640,480), interpolation = cv2.INTER_AREA)
        cv2.imshow('frame', resized)
        # image.save(os.path.join(output_path, image_file), quality=90)
    sess.close()
    cap.release()
    cv2.destroyAllWindows()

if __name__ == '__main__':
    _main(parser.parse_args())


class Doctor():
    ID = 0
    """Simulates a doctor. Sees patients from an erack,can handle maxPts at a time"""
    def __init__(self, ED, pickup_rate, eval_rate, dispo_rate, maxPts):
        self.ED = ED
        self.erack = ED.erack
        self.pickup_rate = pickup_rate # willingness to pick up a free patient (0-1)
        self.eval_rate = eval_rate # speed of initial eval (minutes)
        self.dispo_rate = dispo_rate # speed of disposition after eval (minutes)
        self.IDnum = Doctor.ID
        Doctor.ID += 1
        self.NewPts = queue.Queue(1) # signed up, to be seen, currently capped at 1, but will be on priority - can bump for acute pt
        self.ActivePts = [] # list - everyone in it is waiting on something
        self.DispoPts = [] # currently can handle a lot of signout or dispo-ed patients
        self.currentPt = None # for the sake of simulation, only doing one H&P/action at a time, w/o interruption
        if self.ED.get_verbose():
            print("Doctor", self.IDnum, "created.")

    def get_time(self):
        return self.ED.get_time()

    def get_ID(self):
        return self.IDnum

    def get_patient_totals(self):
        return len(self.ActivePts) + len(self.DispoPts) + self.NewPts.qsize()

    def get_patient_from_rack(self):
        """if there is a patient to get from rack, get them and add to NewPts"""
        if not self.erack.empty():
            if np.random.uniform(0,1) <= self.pickup_rate:
                newpatient = self.erack.get()
                self.NewPts.put(newpatient)
                if self.ED.get_verbose():
                    print("Doctor", self.IDnum, ": Signed up for Patient", newpatient.get_ID(), "at", self.get_time())
                newpatient.set_doc(self)
                return newpatient
        return None

    def get_patient_from_new(self):
        """returns patient from newPts queue, none if empty"""
        if self.NewPts.empty():
            return None
        else:
            newpt = self.NewPts.get()
            self.ActivePts.append(newpt)
            return newpt

    def active_patient_need(self):
        """if there is an active patient with stuff to do, return them, else none - dispo comes first"""
        for patient in self.ActivePts:
            if patient.get_state() == "evaluated":
                if patient.has_needs():
                    pass
                else:
                    return patient
            else:
                ### evaluated == already evaled, NTD for MD

                return patient
        return None

    def get_active_patients(self):
        return self.ActivePts

    def get_next_patient_action(self):
        """get the next patient task and set as the current patient if so
        1. Any active patients need next step?
        2. If NTD, get a new patient
        3. else pass
        """
        if self.currentPt is not None:
            # sanity check
            return

        self.currentPt = self.active_patient_need() # if next patient is an existing pt, select them
        if self.currentPt is None:
            if not self.NewPts.full():
                self.get_patient_from_rack()  ## claim a patient from the erack, only an issue if allowing batches
            else:
                self.currentPt = self.get_patient_from_new()
        pass

    def eval_treat_patient(self):
        """update status of current patient - see pt, eval pt, dispo pt..."""
        # states = unassigned, assigned, evaluated, treated, dispositioned
        to_do = self.currentPt.get_state()
        if to_do == "assigned":
            ## initial eval
            if self.ED.get_verbose():
                print("Doctor", self.IDnum, ": Evaluating patient", self.currentPt.get_ID() ,"at", self.get_time())
            self.currentPt.MDupdate()
            return

        elif to_do == "evaluating":
            probability = np.random.uniform(0,1)
            evals_hour = 60 / self.eval_rate
            if probability <= (evals_hour / 60):
                if self.ED.get_verbose():
                    print("Doctor", self.IDnum, ": finished evaluating patient", self.currentPt.get_ID() ,"at", self.get_time())
                self.currentPt.MDupdate()
                self.currentPt = None
                self.lastActionTimer = 0
                return
            else:
                return
        elif to_do == "treated":
            ## dispo pt
            probability = np.random.uniform(0,1)
            dispos_hour = 60 / self.dispo_rate
            if probability <= (dispos_hour / 60):
                if self.ED.get_verbose():
                    print("Doctor", self.IDnum, ": dispositioning patient", self.currentPt.get_ID() ,"at", self.get_time())
                self.currentPt.MDupdate()
                self.DispoPts.append(self.currentPt)
                self.ActivePts.remove(self.currentPt)
                self.currentPt = None
        else:
            self.currentPt.MDupdate()
            self.currentPt = None

    def update(self):
        """General behavior - if not currently working on a patient:
        1. Check if anyone in the active list can be dispositioned # dispo takes priority
        2. Check if there is a new patient to see
        3. Otherwise, pass
        """
        if self.currentPt is None: # not actively working on a patient -- use not None as == is overloaded for comp
            self.lastActionTimer = 0 # reset
            self.currentPt == self.get_next_patient_action()

        else:
            self.eval_treat_patient()

        if self.ED.get_verbose():
            print("Doctor", self.IDnum, ":", end=" ")
            print("Current patient:", end=" ")
            if self.currentPt is not None:
                print(self.currentPt.get_ID(), ":", self.currentPt.get_state(), end=" ")
            print()
            print("    Active Patients: ", end=" ")
            for pt in self.ActivePts:
                if pt.get_state() != "evaluated":
                    print(pt.get_ID(), pt.get_state(), end="  ")
                else:
                    print(pt.get_ID(), "evaluated|needs:", pt.get_needs(), end="  ")
            print()
            print("    Dispositioned Patients: ", end=" ")
            for pt in self.DispoPts:
                print(pt.get_ID(), pt.get_state(), end=" ")
            print()

        return

import scipy as sp
import numpy as np
import queue

from ED import ED
from Patient import Patient


import numpy as np
import time, random

import torch
from torch.multiprocessing import SimpleQueue, Process, Lock, Queue

from utils.learning_rate import LinearAutoSchedule as LinearSchedule

from utils.bot import Bot
from utils.botproxy import BotProxy

from utils import policy

from agent.simulation import simulation_launch

from threading import Thread

# multi process worker
def agent_launch(bot_id, cfg, task_factory, encoder, Actor, Critic, stop_q, callback = None, goal_encoder = None):
    agent = Zer0Bot(bot_id, cfg, task_factory, encoder, Actor, Critic, goal_encoder)

    loss_gate, mcts, signal = zip(*[(
        Queue(), SimpleQueue(), SimpleQueue()
        ) for i in range(cfg['n_simulations'])])

    sims = [ Thread(#Process(#
        target=simulation_launch,
        args=(cfg, agent.bot, bot_id, i, task_factory, loss, seed, sig, )
        ) for i, (loss, seed, sig) in enumerate(zip(loss_gate, mcts, signal)) ]

    for sim in sims:
        sim.start()

    while stop_q.empty():
        scores = agent.train(loss_gate, mcts, signal)
        if None == callback:
            continue
        scores = callback(agent, scores)
        if scores is None:
            continue
        stop_q.put(scores)

    print("AGENT OVER")
    for seed, sim in zip(mcts, sims):
        seed.put(None)
        sim.join()

    for qs in [loss_gate, mcts, signal]:
        for q in qs:
            while not q.empty():
                q.get()

class Zer0Bot:
    def __init__(self, bot_id, cfg, task_factory, encoder, Actor, Critic, goal_encoder):
        self.cfg = cfg
        self.bot = Bot(
                cfg,
                bot_id,
                encoder,
                goal_encoder,
                Actor,
                Critic,
                task_factory.state_size,
                task_factory.action_size,
                task_factory.wrap_action,
                task_factory.wrap_value)
        self.bot.share_memory() # !! must be done from main process !!

        self.iter = 0
        self.freezed = 0

        self.counter = 1
        self.tau = LinearSchedule(cfg['tau_replay_counter'], cfg['tau_base'], cfg['tau_final'])

        self.lock = Lock()
        self.bot = BotProxy(self.lock, cfg, self.bot, cfg['device'])

    def act(self, goal, state, history): # TODO rename to exploit
        return self.bot.exploit(goal, state, history)

    def train(self, loss, mcts, signal):
        self._encoder_freeze_schedule()
        #  seed = [random.randint(0, self.cfg['mcts_random_cap'])] * self.cfg['mcts_rounds']
        for c in mcts: # maybe we want to * n_episodes ...
            c.put([random.randint(0, self.cfg['mcts_random_cap'])] * self.cfg['mcts_rounds'])

        while all(c.empty() for c in signal):
            self._train_worker(loss)

        scores = []
        for s in signal:
            scores += s.get()
        return scores

    def _train_worker(self, loss):
        time.sleep(.1)
        status = self._update_policy(self.tau.value(), loss)
        if not status:
            return
        self.counter += 1

    def _update_policy(self, tau, loss_gate):
        if any(c.empty() for c in loss_gate):
            return False

        states, grads, actions = zip(*map(
            lambda i: self._get_grads(i, loss_gate[i].get()), range(self.cfg['n_simulations'])))

        if self.cfg["attention_enabled"]:
            # ok we will scatter additional info which is hard to be weighted w/o additional info
            gran = min(map(len, grads))
            states = np.vstack([s[:gran] for s in states])
            actions = np.vstack([a[:gran] for a in actions])
            grads = torch.cat([g[:gran] for g in grads])
        else:
            states = np.vstack(states)
            actions = np.vstack(actions)
            grads = torch.cat(grads)

        # in case of PPO it is safe to move full force
        tau = 1. if not self.cfg['ddpg'] else tau * (0 == self.counter % self.cfg['actor_update_delay'])
        self.bot.learn_actor(states, grads, actions, tau)
        return True

    def _get_grads(self, i, s_f_a_p_td):
        # this is ok to call, as we just using content which is known at creation time ( immutable )
        s, w, a = self._qa_function(i, *s_f_a_p_td)
        return s, w, a

    def _qa_function(self, objective_id, goals, states, history, actions, probs, td_targets):
        qa, dist = self.bot.q_explore(objective_id, goals, states, history)

        loss = self._qa_error(qa, td_targets)
        if self.cfg['normalize_advantages']:
            loss = policy.normalize(loss)

        probs = np.vstack(probs)
        grads = policy.policy_loss(
                torch.tensor(probs),
                dist.log_prob(torch.tensor(actions)),
                loss,
                self.cfg['ppo_eps'], self.cfg['dbgout_ratio'])

        return states, grads, actions

    def _qa_error(self, qa, td_targets):
        if not self.cfg['advantages_enabled']:
            return qa

        td_error = torch.tensor(td_targets).to(qa.device) - qa
# in case of ddpg ~ we calc advantage bit differently ~ check _eval + what is feeded here,
# turned table basically to favor of perf, basically we calculating grads w.r.t. other action
        if self.cfg['ddpg']:
            td_error = -td_error

        if not self.cfg['advantages_boost']:
            return td_error

        for i, e in enumerate(td_error):
            td_error[i] = e if abs(e) > 1e-5 else qa[i]
        return td_error

    def _encoder_freeze_schedule(self):
        if not self.cfg['freeze_delta']:
            return
        self.iter += (0 == self.freezed)
        if self.iter % self.cfg['freeze_delta']:
            return
        if not self.freezed:
            self.bot.freeze_encoders()
        self.freezed += 1
        if self.freezed <= self.cfg['freeze_count']:
            return
        self.freezed = 0
        self.bot.unfreeze_encoders()


import os
import sys
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
#from tensorflow.tensorflow.python.keras.models import load_model
from music import Music
#from tensorflow.tensorflow.python import keras
from keras.models import load_model
from time import sleep
#from .. tensorflow.tensorflow.python.keras.preprocessing.image import img_to_array
#from .. tensorflow.tensorflow.python.keras.preprocessing import image
import cv2
import numpy as np
import time

face_classifier = cv2.CascadeClassifier(r'C:\Users\Aachal\Desktop\emotions_detection\Emotion_Detection_CNN\haarcascade_frontalface_default.xml')
classifier =load_model(r'C:\Users\Aachal\Desktop\emotions_detection\Emotion_Detection_CNN\model.h5')

emotion_labels = ['Angry','Disgust','Fear','Happy','Neutral', 'Sad', 'Surprise']

cap = cv2.VideoCapture(0)

while True:
    _, frame = cap.read()
    labels = []
    gray = cv2.cvtColor(frame,cv2.COLOR_BGR2GRAY)
    faces = face_classifier.detectMultiScale(gray)

    for (x,y,w,h) in faces:
        cv2.rectangle(frame,(x,y),(x+w,y+h),(0,255,255),2)
        roi_gray = gray[y:y+h,x:x+w]
        roi_gray = cv2.resize(roi_gray,(48,48),interpolation=cv2.INTER_AREA)

        if np.sum([roi_gray])!=0:
            roi = roi_gray.astype('float')/255.0
            roi = img_to_array(roi)
            roi = np.expand_dims(roi,axis=0)

            prediction = classifier.predict(roi)[0]
            label=emotion_labels[prediction.argmax()]
            label_position = (x,y)
            cv2.putText(frame,label,label_position,cv2.FONT_HERSHEY_SIMPLEX,1,(0,255,0),2)
            temp = Music()
            temp.recommendations(label)
            time.sleep(20)

        else:
            cv2.putText(frame,'No Faces',(30,80),cv2.FONT_HERSHEY_SIMPLEX,1,(0,255,0),2)
    cv2.imshow('Emotion Detector',frame)
    if cv2.waitKey(1) & 0xFF == ord('q'):
        break

cap.release()
cv2.destroyAllWindows()


import json
import math
import os
import numpy as np
from functools import partial
import time
from pathlib import Path
import ffmpeg

from choirless_lib import mqtt_status, create_signed_url, create_cos_client

helper = lambda x: {'tag': f"{x['compositor']}-{x['row_num']}"}
@mqtt_status(helper)
def main(args):

    args['endpoint'] = args.get('endpoint', args.get('ENDPOINT'))
    cos = create_cos_client(args)

    if not cos:
        raise ValueError(f"could not create COS instance")

    notification = args.get('notification', {})
    definition_key = args.get('definition_key', notification.get('object_name', ''))

    # infer choir, song, and definition id from filename
    choir_id, song_id, def_id = Path(definition_key).stem.split('+', 3)

    definition_bucket = args['definition_bucket']
    src_bucket = args['converted_bucket']
    dst_bucket = args['final_parts_bucket']

    # the compositor to run (audio / video)
    compositor = args['compositor']

    # the row number we are processing
    row_num = int(args['row_num'])
    rows_hash = args['rows_hash']

    # run id used to group all our files together
    run_id = args['run_id']

    print(f"We are the child {compositor} process, run id: {run_id} row: {row_num}")

    # Download the definition file for this job
    definition_object = cos.get_object(
        Bucket=definition_bucket,
        Key=definition_key,
    )
    definition = json.load(definition_object['Body'])

    output_spec = definition['output']
    input_specs = definition['inputs']

    # Calculate number of rows
    rows = set()
    for spec in input_specs:
        x, y = spec.get('position', [-1,-1])
        rows.add(y)
    rows = sorted(rows)
    num_rows = len(rows)

    # The output key
    output_key = f"{choir_id}+{song_id}+{def_id}+{run_id}+{row_num}@{rows_hash}.nut"

    # Create partial functions to get signed urls for input / output
    geo = args['geo']
    host = args['endpoint']
    cos_hmac_keys = args['__bx_creds']['cloud-object-storage']['cos_hmac_keys']
    cos_api_key = cos_hmac_keys['access_key_id']
    cos_api_secret = cos_hmac_keys['secret_access_key']

    get_input_url = partial(create_signed_url,
                            host,
                            'GET',
                            cos_api_key,
                            cos_api_secret,
                            geo,
                            src_bucket)

    get_output_url = partial(create_signed_url,
                             host,
                             'PUT',
                             cos_api_key,
                             cos_api_secret,
                             geo,
                             dst_bucket)

    # Calculate the max row length, needed for volume compensation
    # on uneven rows
    max_row_len = 0
    for r in rows:
        l = len(tuple(specs_for_row(input_specs, r)))
        if l > max_row_len:
            max_row_len = l

    # Get the row input specs
    row_input_specs = tuple(specs_for_row(input_specs, row_num))

    # Calculate bounding boxes and padding
    top, bottom = calc_bounding_box(row_input_specs)
    margin = 10

    total_output_width, total_output_height = output_spec['size']
    output_width = total_output_width
    output_height = bottom - top + margin

    # by default rows are at top
    row_y = 0

    # Main combination process
    audio_inputs = []
    video_inputs = []
    coords = []
    streams_and_filename = []

    for spec in row_input_specs:
        # Get the part spec and input
        part_id = spec['part_id']
        part_key = f"{choir_id}+{song_id}+{part_id}.nut"
        part_url = get_input_url(part_key)

        # process the spec
        video, audio = process_spec(part_url, spec)

        audio_inputs.append(audio)
        # Get co-ords for video
        if video is not None:
            video_inputs.append(video)
            x, _ = spec['position']
            coords.append((x, row_y))

    # Combine the audio parts if there are any
    if len(audio_inputs) > 0:
        if len(audio_inputs) == 1:
            audio_pipeline = audio_inputs[0]
        else:
            audio_pipeline = ffmpeg.filter(audio_inputs, 'amix', inputs=len(audio_inputs))

        # Adjust the volume in proportion to total number of parts
        volume = len(row_input_specs) / float(max_row_len)
        if volume != 1.0:
            audio_pipeline = audio_pipeline.filter('volume',
                                                   volume=volume)

        streams_and_filename.append(audio_pipeline)

    # Combine the video parts if there are any
    if len(video_inputs) > 0:
        if len(video_inputs) == 1:
            x, y = coords[0]
            video_pipeline = video_inputs[0]
            video_pipeline = video_pipeline.filter('pad',
                                                   output_width,
                                                   output_height,
                                                   x,
                                                   y)
        else:
            layout = '|'.join([ f"{x}_{row_y}" for x, row_y in coords ])
            video_pipeline = ffmpeg.filter(video_inputs,
                                           'xstack',
                                           inputs=len(video_inputs),
                                           fill='black',
                                           layout=layout)
            video_pipeline = video_pipeline.filter('pad',
                                                   output_width,
                                                   output_height)
        streams_and_filename.append(video_pipeline)

    if len(streams_and_filename) == 0:
        return {'error': 'no parts to process'}

    kwargs = {}
    if 'duration' in args:
        kwargs['t'] = int(args['duration'])

    output_url = get_output_url(output_key)
    streams_and_filename.append(output_url)

    pipeline = ffmpeg.output(*streams_and_filename,
                             format='nut',
                             pix_fmt='yuv420p',
                             acodec='pcm_s16le',
                             vcodec='mpeg2video',
                             method='PUT',
                             r=25,
                             seekable=0,
                             qscale=1,
                             qmin=1,
                             **kwargs
    )

    cmd = pipeline.compile()
    print("ffmpeg command to run: ", cmd)
    t1 = time.time()
    pipeline.run()
    t2 = time.time()

    ret = {"status": "ok",
           "definition_key": definition_key,
           "dst_key": output_key,
           "render_time": int(t2-t1),
           "row_num": row_num,
           "run_id": run_id,
           "rows_hash": rows_hash,
           }

    return ret

def specs_for_row(specs, row):
    for spec in specs:
        x, y = spec.get('position', [-1, -1])
        if y == row:
            yield spec

def calc_bounding_box(specs):
    top = np.inf
    bottom = -np.inf
    for spec in specs:
        if not 'position' in spec:
            continue
        x, y = spec['position']
        width, height = spec['size']
        # round height down to next even number as that is what the scaler will so
        height = height // 2 * 2
        if y < top:
            top = y
        if (y + height) > bottom:
            bottom = y + height

    return top, bottom

def process_spec(part_url, spec):
    # main stream input
    stream = ffmpeg.input(part_url,
                          seekable=0,
                          r=25,
                          thread_queue_size=64)

    # Calc the offset in seconds
    offset = spec.get('offset', 0)
    offset = float(offset) / 1000

    # Get the part spec and input
    # video
    if 'position' in spec:
        width, height = spec['size']

        video = stream.video
        if offset > 0:
            video = video.filter('trim',
                             start=offset)
        video = video.filter('setpts', 'PTS-STARTPTS')
        video = video.filter('scale', width, height,
                             force_original_aspect_ratio='decrease',
                             force_divisible_by=2)

    else:
        video = None

    # audio
    audio = stream.audio
    if offset > 0:
        audio = audio.filter('atrim',
                             start=offset)
    audio = audio.filter('asetpts', 'PTS-STARTPTS')
    pan = float(spec.get('pan', 0))
    volume = float(spec.get('volume', 1))
    audio = audio.filter('volume',
                         volume=volume)
    audio = audio.filter('stereotools',
                         mpan=pan)

    return video, audio


import atexit
import carna
import carna.base
import carna.egl
import carna.presets
import carna.helpers
import numpy as np
import warnings

version    = carna.version
py_version = carna.py_version

# Create the OpenGL context when module is loaded
ctx = carna.egl.Context.create()

# Release the OpenGL context when module is unloaded
@atexit.register
def shutdown():
    ctx.free()

class SpatialWrapper:
    def __init__(self, spatial):
        self._ = spatial

    def translate(self, *args):
        self._.local_transform = self._.local_transform @ carna.base.math.translation4f(*args)
        return self

    def rotate(self, axis, amount, units='rad'):
        assert units in ('rad', 'deg')
        axis = axis / np.linalg.norm(axis)
        if units == 'deg': amount = carna.base.math.deg2rad(amount)
        self._.local_transform = self._.local_transform @ carna.base.math.rotation4f(axis, amount)
        return self

    def scale(self, *args):
        self._.local_transform = self._.local_transform @ carna.base.math.scaling4f(*args)
        return self

    @property
    def position(self):
        return self._.local_transform[:3, -1]

    def look(self, direction, up):
        direction = np.asarray(direction) / np.linalg.norm(direction)
        left = np.cross(direction, up)
        left = left / np.linalg.norm(left)
        up   = np.cross(left, direction)
        mat  = np.eye(4)
        mat[:3, 0] = -left
        mat[:3, 1] =  up
        mat[:3, 2] =  direction
        mat[:3, 3] =  self.position
        self._.local_transform = mat
        return self

    def look_at(self, point, up):
        direction = self.position - point
        return self.look(direction, up)

class VolumeWrapper(SpatialWrapper):
    def __init__(self, volume, data_shape, dimensions=None, spacing=None):
        assert (dimensions is None) != (spacing is None)
        super().__init__(volume)
        if dimensions is None: dimensions = spacing * (np.array(data_shape) - 1)[None, :]
        self.dimensions = dimensions
        self.data_shape = data_shape

    def map_normalized_coordinates(self, coordinates):
        """Maps normalized coordinates to volume coordinates.
        """
        coordinates = np.asarray(coordinates)
        assert coordinates.ndim == 2 and coordinates.shape[1] == 3
        return (coordinates - 0.5) * self.dimensions

    def map_voxel_coordinates(self, coordinates):
        """Maps voxel coordinates to volume coordinates.
        """
        coordinates = np.asarray(coordinates)
        assert coordinates.ndim == 2 and coordinates.shape[1] == 3
        coordinates = coordinates / np.subtract(self.data_shape, 1)[None, :]
        return self.map_normalized_coordinates(coordinates)

def deduce_volume_format(dtype, dtype_fallback='float16'):
    dtype = str(np.dtype(dtype))
    if dtype in ('float32', 'float64'):
        warnings.warn(f'Unsupported data type: {dtype} (will be treated as {dtype_fallback})')
        dtype = dtype_fallback
    return {
        'uint8'  : 'VolumeGrid_UInt8Intensity',
        'uint16' : 'VolumeGrid_UInt16Intensity',
        'float16': 'VolumeGrid_UInt16Intensity',
    }[dtype]

def preprocess_mask_data(data):
    dtype = str(np.dtype(data.dtype))
    if dtype == 'bool': return 'VolumeGrid_UInt8Intensity', data
    if (dtype.startswith('int') or dtype.startswith('uint')):
        if data.max() < (1 << 8):
            return 'VolumeGrid_UInt8Intensity', (data / data.max()).clip(0, 1)
        if data.max() < (1 << 16):
            return 'VolumeGrid_UInt16Intensity', (data / data.max()).clip(0, 1)
    if dtype.startswith('float'):
        return 'VolumeGrid_UInt16Intensity', (data / data.max()).clip(0, 1)
    else:
        raise ValueError(f'Unsupported mask format')

class SingleFrameContext:

    GEOMETRY_TYPE_OPAQUE = 0
    GEOMETRY_TYPE_VOLUME = 1
    GEOMETRY_TYPE_PLANE  = 2
    GEOMETRY_TYPE_MASK   = 3

    def __init__(self, shape, near, far, fov=None, ortho=None,
            max_segment_bytesize=carna.helpers.VolumeGridHelperBase.DEFAULT_MAX_SEGMENT_BYTESIZE):
        assert (fov is None) != (ortho is None), f'Either "fov" or "ortho" musst be supplied (fov={fov}, ortho={ortho})'
        assert ortho is None or (isinstance(ortho, tuple) and len(ortho) == 4), str(ortho)
        self.result = None
        self.shape  = shape
        self.near   = near
        self.far    = far
        self.fov    = fov
        self.ortho  = ortho
        self.max_segment_bytesize = max_segment_bytesize

    def __enter__(self):
        self. result       = None
        self._camera       = carna.base.Camera.create()
        self._renderer     = carna.base.FrameRenderer.create(ctx, self.shape[1], self.shape[0])
        self._helper       = carna.helpers.FrameRendererHelper(self._renderer)
        self._extra_stages = {}
        self._points       = None
        self._root         = carna.base.Node.create()
        self._stages       = dict()
        self._grids        = []
        self._meshes       = []
        self._materials    = []
        if self.fov is not None:
            self._camera.projection = carna.base.math.frustum4f(carna.base.math.deg2rad(self.fov), self.shape[0] / self.shape[1], self.near, self.far)
        else:
            self._camera.projection = carna.base.math.ortho4f(*self.ortho, self.near, self.far)
        self._root.attach_child(self._camera)
        return self

    def _get_stage(self, stage_type, *create_args):
        if stage_type not in self._stages:
            stage = stage_type.create(*create_args)
            self._stages[stage_type] = stage
            if stage_type is not carna.presets.MaskRenderingStage:
                self._helper.add_stage(stage)
        return self._stages[stage_type]

    def _render(self):
        surface = carna.base.Surface.create(ctx, self._renderer.width, self._renderer.height)
        surface.begin()
        self._renderer.render(self._camera)
        return surface.end()

    def _append_extra_render_stages(self, current_render_time):
        assert current_render_time in ('before', 'after')
        for stage, scheduled_render_time in self._extra_stages.items():
            if scheduled_render_time == current_render_time:
                self._renderer.append_stage(stage)

    def __exit__(self, ex_type, ex_value, ex_traceback):
        self._append_extra_render_stages('before')
        self._helper.commit(False)
        self._append_extra_render_stages('after')
        self.result = self._render()
        for material in self._materials: material.release()
        for mesh in self._meshes: mesh.release()
        for grid in self._grids: grid.free()
        if self._points is not None: self._points.free()
        self._renderer.free()
        self._root.free()

    @property
    def camera(self):
        return SpatialWrapper(self._camera)

    def _get_parent(self, parent):
        if isinstance(parent, str) and parent == 'root': return self._root
        elif isinstance(parent, SpatialWrapper): return parent._
        else: raise ValueError('parent must be either "root" or SpatialWrapper instance')

    def volume(self, data, dimensions=None, spacing=None, normals=False, fmt_hint=None, parent='root'):
        assert data.ndim == 3
        assert (dimensions is None) != (spacing is None)
        if dimensions is not None: size_hint = carna.helpers.Dimensions(dimensions)
        if spacing    is not None: size_hint = carna.helpers.Spacing   (spacing)
        grid_helper_type = deduce_volume_format(data.dtype if fmt_hint is None else fmt_hint)
        if normals: grid_helper_type += '_Int8Normal'
        grid = getattr(carna.helpers, grid_helper_type).create(data.shape, self.max_segment_bytesize)
        grid.load_data(data)
        volume = grid.create_node(SingleFrameContext.GEOMETRY_TYPE_VOLUME, size_hint)
        pivot = carna.base.Node.create()
        pivot.attach_child(volume)
        self._get_parent(parent).attach_child(pivot)
        self._grids.append(grid)
        return VolumeWrapper(pivot, data.shape, dimensions, spacing)

    def masks(self, flavor='default', **kwargs):
        """Configures the mask rendering stage.
        """
        assert flavor in ('regions', 'regions-on-top', 'borders-on-top', 'borders-in-background')
        mr = self._get_stage(carna.presets.MaskRenderingStage, SingleFrameContext.GEOMETRY_TYPE_MASK)
        for key, val in kwargs.items():
            setattr(mr, key, val)
        if flavor == 'default' and mr not in self._extra_stages: flavor = 'regions'
        if flavor == 'regions':
            render_time       = 'before'
            mr.render_borders =  False
        if flavor == 'regions-on-top':
            render_time       = 'after'
            mr.render_borders =  False
        if flavor == 'borders-on-top':
            render_time       = 'after'
            mr.render_borders =  True
        if flavor == 'borders-in-background':
            render_time       = 'before'
            mr.render_borders =  True
        self._extra_stages[mr] = render_time
        return mr

    def mask(self, data, *args, dimensions=None, spacing=None, parent='root', **kwargs):
        mr = self.masks(*args, **kwargs) ## require mask rendering stage
        assert (dimensions is None) != (spacing is None)
        if dimensions is not None: size_hint = carna.helpers.Dimensions(dimensions)
        if spacing    is not None: size_hint = carna.helpers.Spacing   (spacing)
        grid_helper_type, data = preprocess_mask_data(data)
        grid = getattr(carna.helpers, grid_helper_type).create(data.shape, self.max_segment_bytesize)
        grid.intensities_role = mr.mask_role
        grid.load_data(data)
        volume = grid.create_node(SingleFrameContext.GEOMETRY_TYPE_MASK, size_hint)
        pivot = carna.base.Node.create()
        pivot.attach_child(volume)
        self._get_parent(parent).attach_child(pivot)
        self._grids.append(grid)
        return VolumeWrapper(pivot, data.shape, dimensions, spacing)

    def plane(self, *args, parent='root'):
        self.planes() ## require cutting planes rendering stage
        plane = carna.base.Geometry.create(SingleFrameContext.GEOMETRY_TYPE_PLANE)
        plane.local_transform = carna.base.math.plane4f(*args)
        self._get_parent(parent).attach_child(plane)
        return SpatialWrapper(plane)

    def planes(self):
        return self._get_stage(carna.presets.CuttingPlanesStage, SingleFrameContext.GEOMETRY_TYPE_VOLUME, SingleFrameContext.GEOMETRY_TYPE_PLANE)

    def opaque(self):
        return self._get_stage(carna.presets.OpaqueRenderingStage, SingleFrameContext.GEOMETRY_TYPE_OPAQUE)

    def occluded(self):
        return self._get_stage(carna.presets.OccludedRenderingStage)

    def mip(self, layers=[(0, 1, (1,1,1,1))], **kwargs):
        mip = self._get_stage(carna.presets.MIPStage, SingleFrameContext.GEOMETRY_TYPE_VOLUME)
        mip.clear_layers()
        for layer in layers:
            mip.append_layer(carna.presets.MIPLayer.create(*layer))
        for key, val in kwargs.items():
            setattr(mip, key, val)
        return mip

    def dvr(self, translucency=0, color_map=[(0, 1, (1,1,1,0), (1,1,1,1))], **kwargs):
        dvr = self._get_stage(carna.presets.DVRStage, SingleFrameContext.GEOMETRY_TYPE_VOLUME)
        dvr.translucency = translucency
        dvr.clear_color_map()
        for color_map_entry in color_map:
            dvr.write_color_map(*color_map_entry)
        for key, val in kwargs.items():
            setattr(dvr, key, val)
        return dvr

    def dots(self, data, color, size, parent='root'):
        opaque = self.opaque()
        if self._points is None: self._points = carna.helpers.PointMarkerHelper.create(SingleFrameContext.GEOMETRY_TYPE_OPAQUE)
        dots = []
        for location in data:
            dot = self._points.create_point_marker(size, color)
            dot.local_transform = carna.base.math.translation4f(*location)
            self._get_parent(parent).attach_child(dot)
            dots.append(SpatialWrapper(dot))
        return dots

    def material(self, color, shader='solid'):
        material = carna.base.Material.create(shader)
        material.set_parameter4f('color', color)
        self._materials.append(material)
        return material

    def box(self, width, height, depth):
        box = carna.base.create_box(width, height, depth)
        self._meshes.append(box)
        return box

    def ball(self, radius, degree=3):
        ball = carna.base.create_ball(radius, degree)
        self._meshes.append(ball)
        return ball

    def mesh(self, mesh, material, parent='root'):
        self.opaque() ## require opaque rendering stage
        geom = carna.base.Geometry.create(SingleFrameContext.GEOMETRY_TYPE_OPAQUE)
        geom.put_feature(carna.presets.OpaqueRenderingStage.ROLE_DEFAULT_MESH, mesh)
        geom.put_feature(carna.presets.OpaqueRenderingStage.ROLE_DEFAULT_MATERIAL, material)
        self._get_parent(parent).attach_child(geom)
        return SpatialWrapper(geom)

    def meshes(self, mesh, material, locations, parent='root'):
        geoms = []
        for loc in locations:
            geom = self.mesh(mesh, material, parent=parent).translate(*loc)
            geoms.append(geom)
        return geoms