from __future__ import division, print_function
import numpy as np
import pandas as pd
import copy
import math
import random
import matplotlib.pyplot as plt
from highwayEnv.Vars_and_Methods import methods, variables
from highwayEnv.environment import roundabout
from highwayEnv.environment import archive

"""
    MAIN VEHICLE CONTROL FOR ALL CARS (WE COULD MAKE A COPY OF THIS TO SEPARATE CONTROL OF AGENTS AND OTHER CARS):
"""

class Vehicle(object):
    """
        A moving vehicle on a road, and its dynamics.

        The vehicle is represented by a dynamical system: a modified bicycle model.
        It's state is propagated depending on its steering and acceleration actions.
    """
    COLLISIONS_ENABLED = True
    """ Enable collision detection between vehicles """

    LENGTH = 5.0
    """ Vehicle length [m] """
    WIDTH = 2.0
    """ Vehicle width [m] """
    DEFAULT_VELOCITIES = [23, 25]
    """ Range for random initial velocities [m/s] """
    MAX_VELOCITY = variables.MAX_VELOCITY
    """ Maximum reachable velocity [m/s] """

    def __init__(self, road, position, heading=0, velocity=0):
        self.road = road
        self.position = np.array(position).astype('float')
        self.heading = heading
        self.velocity = velocity
        self.lane_index = self.road.network.get_closest_lane_index(self.position) if self.road else np.nan
        self.lane = self.road.network.get_lane(self.lane_index) if self.road else None
        self.action = {'steering': 0, 'acceleration': 0}
        self.log = []

    @classmethod
    def make_on_lane(cls, road, lane_index, longitudinal, velocity=0):
        """
            Create a vehicle on a given lane at a longitudinal position.

        :param road: the road where the vehicle is driving
        :param lane_index: index of the lane where the vehicle is located
        :param longitudinal: longitudinal position along the lane
        :param velocity: initial velocity in [m/s]
        :return: A vehicle with at the specified position
        """

        lane = road.network.get_lane(lane_index)
        if velocity is None:
            velocity = lane.speed_limit
        return cls(road, lane.position(longitudinal, 0), lane.heading_at(longitudinal), velocity)

    """ @classmethod
    def create_random(cls, road, velocity=None, spacing=1): """
    """
            Create a random vehicle on the road.

            The lane and /or velocity are chosen randomly, while longitudinal position is chosen behind the last
            vehicle in the road with density based on the number of lanes.

        :param road: the road where the vehicle is driving
        :param velocity: initial velocity in [m/s]. If None, will be chosen randomly
        :param spacing: ratio of spacing to the front vehicle, 1 being the default
        :return: A vehicle with random position and/or velocity
        """
    """ if velocity is None:
            velocity = road.np_random.uniform(Vehicle.DEFAULT_VELOCITIES[0], Vehicle.DEFAULT_VELOCITIES[1])
        default_spacing = 1.5*velocity
        _from = road.np_random.choice(list(road.network.graph.keys()))
        _to = road.np_random.choice(list(road.network.graph[_from].keys()))
        _id = road.np_random.choice(len(road.network.graph[_from][_to]))
        offset = spacing * default_spacing * np.exp(-5 / 30 * len(road.network.graph[_from][_to]))
        x0 = np.max([v.position[0] for v in road.vehicles]) if len(road.vehicles) else 3*offset
        x0 += offset * road.np_random.uniform(0.9, 1.1)
        v = cls(road, road.network.get_lane((_from, _to, _id)).position(x0, 0), 0, velocity)
        return v """

    def act(self, action=None):
        """
            Store an action to be repeated.

        :param action: the input action
        """
        if action:
            self.action = action

    def step(self, dt):
        """
            Propagate the vehicle state given its actions.

            Integrate a modified bicycle model with a 1st-order response on the steering wheel dynamics.
            If the vehicle is crashed, the actions are overridden with erratic steering and braking until complete stop.
            The vehicle's current lane is updated.

        :param dt: timestep of integration of the model [s]
        """
        self.action['steering'] = float(self.action['steering'])
        self.action['acceleration'] = float(self.action['acceleration'])
        if self.velocity > self.MAX_VELOCITY:
            self.action['acceleration'] = min(self.action['acceleration'], 1.0 * (self.MAX_VELOCITY - self.velocity))
        elif self.velocity < -self.MAX_VELOCITY:
            self.action['acceleration'] = max(self.action['acceleration'], 1.0 * (self.MAX_VELOCITY - self.velocity))

        v = self.velocity * np.array([np.cos(self.heading), np.sin(self.heading)])
        self.position += v * dt
        self.heading += self.velocity * np.tan(self.action['steering']) / self.LENGTH * dt
        self.velocity += self.action['acceleration'] * dt

        if self.road:
            self.lane_index = self.road.network.get_closest_lane_index(self.position)
            self.lane = self.road.network.get_lane(self.lane_index)

    def lane_distance_to(self, vehicle):
        """
            Compute the signed distance to another vehicle along current lane.

        :param vehicle: the other vehicle
        :return: the distance to the other vehicle [m]
        """
        if not vehicle:
            return np.nan
        return self.lane.local_coordinates(vehicle.position)[0] - self.lane.local_coordinates(self.position)[0]

    @property
    def direction(self):
        return np.array([np.cos(self.heading), np.sin(self.heading)])

    def front_distance_to(self, other):
        return self.direction.dot(other.position - self.position)

    def distance_to(self, other):
        euclidian = math.sqrt(math.pow(other.position[0] - self.position[0], 2) + math.pow(other.position[1] - self.position[1], 2))
        return euclidian

    def direction_to(self, other):
        return other.position - self.position

    def angle_to(self, other):
        if (np.cos(self.heading) * (other.position - self.position)[1]
                - np.sin(self.heading) * (other.position - self.position)[0] <= 0):
            angle = math.acos(self.direction.dot(other.position - self.position) / np.linalg.norm((other.position - self.position)))
            if (0 <= angle <= (math.pi / 2)):
                return angle
        return None

    def to_dict(self, origin_vehicle=None):
        d = {
            'presence': 1,
            'x': self.position[0],
            'y': self.position[1],
            'vx': self.velocity * self.direction[0],
            'vy': self.velocity * self.direction[1],
            'cos_h': self.direction[0],
            'sin_h': self.direction[1]
        }
        if origin_vehicle:
            origin_dict = origin_vehicle.to_dict()
            for key in ['x', 'y', 'vx', 'vy']:
                d[key] -= origin_dict[key]
        return d

    def dump(self):
        """
            Update the internal log of the vehicle, containing:
                - its kinematics;
                - some metrics relative to its neighbour vehicles.
        """
        data = {
            'x': self.position[0],
            'y': self.position[1],
            'psi': self.heading,
            'vx': self.velocity * np.cos(self.heading),
            'vy': self.velocity * np.sin(self.heading),
            'v': self.velocity,
            'acceleration': self.action['acceleration'],
            'steering': self.action['steering']}

        if self.road:
            for lane_index in self.road.network.side_lanes(self.lane_index):
                lane_coords = self.road.network.get_lane(lane_index).local_coordinates(self.position)
                data.update({
                    'dy_lane_{}'.format(lane_index): lane_coords[1],
                    'psi_lane_{}'.format(lane_index): self.road.network.get_lane(lane_index).heading_at(lane_coords[0])
                })
            front_vehicle, rear_vehicle = self.road.neighbour_vehicles(self)
            if front_vehicle:
                data.update({
                    'front_v': front_vehicle.velocity,
                    'front_distance': self.lane_distance_to(front_vehicle)
                })
            if rear_vehicle:
                data.update({
                    'rear_v': rear_vehicle.velocity,
                    'rear_distance': rear_vehicle.lane_distance_to(self)
                })

        self.log.append(data)

    def get_log(self):
        """
            Cast the internal log as a DataFrame.

        :return: the DataFrame of the Vehicle's log.
        """
        return pd.DataFrame(self.log)

    def __str__(self):
        return "{} #{}: {}".format(self.__class__.__name__, id(self) % 1000, self.position)

    def __repr__(self):
        return self.__str__()
"""
----------------------------------------OUR AGENTS CONTROL: ---------------------------------------------------------------
"""

class AgentVehicle(Vehicle):
    """
        A vehicle piloted by two low-level controller, allowing high-level actions
        such as cruise control and lane changes.

        - The longitudinal controller is a velocity controller;
        - The lateral controller is a heading controller cascaded with a lateral position controller.

        A controlled vehicle with a specified discrete range of allowed target velocities.
    """

    TAU_A = 0.6  # [s]
    TAU_DS = 0.2  # [s]
    PURSUIT_TAU = 1.5 * TAU_DS  # [s]
    KP_A = 1 / TAU_A
    KP_HEADING = 1 / TAU_DS
    KP_LATERAL = 1 / 0.5  # [1/s]
    MAX_STEERING_ANGLE = np.pi / 3  # [rad]
    MAX_ACC = 4 # [m/s2]

    DELTA_VELOCITY = variables.MAX_VELOCITY  # [m/s]

    SPEED_COUNT = 3  # []
    SPEED_MIN = 20  # [m/s]
    SPEED_MAX = 30  # [m/s]
    WEIGHT_RISK = None
    WEIGHT_TIME = None

    # Variables for learning
    risk_threshold = 0.0
    waiting_time = variables.MAX_WAITING_TIME
    max_waiting_time = variables.MAX_WAITING_TIME
    crashed = False
    stopped = False
    standing = False
    solution_selected = -1
    archive_updated = False
    exchange_done = False
    stats_updated = False
    hypervolume = 0.0
    non_dominated_time = []
    non_dominated_risk = []
    archive_index = -1

    def __init__(self,
                 road,
                 position,
                 heading=0,
                 velocity=0,
                 target_lane_index=None,
                 target_velocity=None,
                 route=None,
                 id=None,
                 timer=None,
                 waiting_time=20.0,
                 crashed=False):
        super(AgentVehicle, self).__init__(road, position, heading, velocity)
        self.target_lane_index = target_lane_index or self.lane_index
        self.target_velocity = target_velocity or self.velocity
        self.route = route
        #self.velocity_index = self.speed_to_index(self.target_velocity)
        #self.target_velocity = self.index_to_speed(self.velocity_index)
        self.id = id
        self.timer = timer or (np.sum(self.position) * np.pi) % 1
        self.risk_threshold = random.randint(50, 100) / 100

    @classmethod
    def create_from(cls, vehicle):
        """
            Create a new vehicle from an existing one.
            The vehicle dynamics and target dynamics are copied, other properties are default.

        :param vehicle: a vehicle
        :return: a new vehicle at the same dynamical state
        """
        v = cls(vehicle.road, vehicle.position, heading=vehicle.heading, velocity=vehicle.velocity,
                target_lane_index=vehicle.target_lane_index, target_velocity=vehicle.target_velocity,
                route=vehicle.route)
        return v

    def plan_route_to(self, destination):
        """
            Plan a route to a destination in the road network

        :param destination: a node in the road network
        """
        path = self.road.network.shortest_path(self.lane_index[1], destination)
        if path:
            self.route = [self.lane_index] + [(path[i], path[i + 1], None) for i in range(len(path) - 1)]
        else:
            self.route = [self.lane_index]
        return self

    def act(self, action=None):
        """
            Perform a high-level action to change the desired lane or velocity.

            - If a high-level action is provided, update the target velocity and lane;
            - then, perform longitudinal and lateral control.

        :param action: a high-level action
        """
        self.follow_road()
        if action == "FASTER":
            self.target_velocity = self.DELTA_VELOCITY
            #self.velocity_index = self.speed_to_index(self.velocity) + 1
        elif action == "SLOWER":
            self.target_velocity -= self.DELTA_VELOCITY
            #self.velocity_index = self.speed_to_index(self.velocity) - 1
        elif action == "STOP":
            self.velocity = 0
            self.target_velocity = 0

        action = {'steering': self.steering_control(self.target_lane_index),
                  'acceleration': self.velocity_control(self.target_velocity)}
        super(AgentVehicle, self).act(action)

    def follow_road(self):
        """
           At the end of a lane, automatically switch to a next one.
        """
        if self.road.network.get_lane(self.target_lane_index).after_end(self.position):
            self.target_lane_index = self.road.network.next_lane(self.target_lane_index,
                                                                 route=self.route,
                                                                 position=self.position,
                                                                 np_random=self.road.np_random)

    def steering_control(self, target_lane_index):
        """
            Steer the vehicle to follow the center of an given lane.

        1. Lateral position is controlled by a proportional controller yielding a lateral velocity command
        2. Lateral velocity command is converted to a heading reference
        3. Heading is controlled by a proportional controller yielding a heading rate command
        4. Heading rate command is converted to a steering angle

        :param target_lane_index: index of the lane to follow
        :return: a steering wheel angle command [rad]
        """
        target_lane = self.road.network.get_lane(target_lane_index)
        lane_coords = target_lane.local_coordinates(self.position)
        lane_next_coords = lane_coords[0] + self.velocity * self.PURSUIT_TAU
        lane_future_heading = target_lane.heading_at(lane_next_coords)

        # Lateral position control
        lateral_velocity_command = - self.KP_LATERAL * lane_coords[1]
        # Lateral velocity to heading
        heading_command = np.arcsin(np.clip(lateral_velocity_command/methods.not_zero(self.velocity), -1, 1))
        heading_ref = lane_future_heading + np.clip(heading_command, -np.pi/4, np.pi/4)
        # Heading control
        heading_rate_command = self.KP_HEADING * methods.wrap_to_pi(heading_ref - self.heading)
        # Heading rate to steering angle
        steering_angle = np.arctan(self.LENGTH / methods.not_zero(self.velocity) * heading_rate_command)
        steering_angle = np.clip(steering_angle, -self.MAX_STEERING_ANGLE, self.MAX_STEERING_ANGLE)
        return steering_angle

    def velocity_control(self, target_velocity):
        """
            Control the velocity of the vehicle.

            Using a simple proportional controller with an upper limit.

        :param target_velocity: the desired velocity
        :return: an acceleration command [m/s2]
        """
        acc = self.KP_A * (target_velocity - self.velocity)
        if acc > self.MAX_ACC:
            acc = self.MAX_ACC
        return acc

    def predict_trajectory_constant_velocity(self, times):
        """
            Predict the future positions of the vehicle along its planned route, under constant velocity
        :param times: timesteps of prediction
        :return: positions, headings
        """
        coordinates = self.lane.local_coordinates(self.position)
        route = self.route or [self.lane_index]
        return zip(*[self.road.network.position_heading_along_route(route, coordinates[0] + self.velocity * t, 0)
                     for t in times])

    @classmethod
    def index_to_speed(cls, index):
        """
            Convert an index among allowed speeds to its corresponding speed
        :param index: the speed index []
        :return: the corresponding speed [m/s]
        """
        if cls.SPEED_COUNT > 1:
            return cls.SPEED_MIN + index * (cls.SPEED_MAX - cls.SPEED_MIN) / (cls.SPEED_COUNT - 1)
        else:
            return cls.SPEED_MIN

    @classmethod
    def speed_to_index(cls, speed):
        """
            Find the index of the closest speed allowed to a given speed.
        :param speed: an input speed [m/s]
        :return: the index of the closest speed allowed []
        """
        x = (speed - cls.SPEED_MIN) / (cls.SPEED_MAX - cls.SPEED_MIN)
        return np.int(np.clip(np.round(x * (cls.SPEED_COUNT - 1)), 0, cls.SPEED_COUNT - 1))

    def speed_index(self):
        """
            The index of current velocity
        """
        return self.speed_to_index(self.velocity)

    def predict_trajectory(self, actions, action_duration, trajectory_timestep, dt):
        """
            Predict the future trajectory of the vehicle given a sequence of actions.

        :param actions: a sequence of future actions.
        :param action_duration: the duration of each action.
        :param trajectory_timestep: the duration between each save of the vehicle state.
        :param dt: the timestep of the simulation
        :return: the sequence of future states
        """
        states = []
        v = copy.deepcopy(self)
        t = 0
        for action in actions:
            v.act(action)  # High-level decision
            for _ in range(int(action_duration / dt)):
                t += 1
                v.act()  # Low-level control action
                v.step(dt)
                if (t % int(trajectory_timestep / dt)) == 0:
                    states.append(copy.deepcopy(v))
        return states

    def projected_position(self):
        """ Get position of ego_vehicle projected into round about."""
        if self.lane_index == ("nxr", "sen", 0):
            position_of_interest = [variables.center_north[0] + self.position[0],
                                   variables.center_north[1] + math.sqrt((variables.raradius + 4)**2 - self.position[0]**2)]
        elif self.lane_index == ("nes", "ne", 0):
            position_of_interest = [variables.center_south[0] + self.position[0],
                                   variables.center_south[1] - math.sqrt((variables.raradius + 4)**2 - self.position[0]**2)]
        else:
            return None
        return position_of_interest

    def distances_to_relevant_vehicles(self, other_vehicles, position_of_interest):
        """ Filter other_vehicles based on whether they are in the relevant field of fiew of the agent and if
            so calculate their distance to the projected_position of the agent in the round about."""
        vehicle_distance_list = []
        for vehicle in other_vehicles:
            if self.angle_to(vehicle) != None and self.distance_to(vehicle) <= variables.com_radius:
                distance = np.linalg.norm(position_of_interest - vehicle.position)
                alpha = 2 * np.arcsin(distance / (2 * (variables.raradius + 4)))
                vehicle_distance_list.append((vehicle, alpha * (variables.raradius + 4)))
                vehicle_distance_list.sort(key=lambda t: t[1])
        return vehicle_distance_list

    def distances_between_relevant_vehicles(self, position_of_interest, vehicle_distance_list):
        """ Calculate the distances between the other vehicles from the distances to the agent."""
        relevant_distances = []
        for i in range(0, len(vehicle_distance_list)):
                if i == 0:
                    distance = np.linalg.norm(vehicle_distance_list[i][0].position - position_of_interest)
                    alpha = 2 * np.arcsin(distance / (2 * (variables.raradius + 4)))
                    length_between = alpha * (variables.raradius + 4)
                    relevant_distances.append(length_between)
                else:
                    distance = np.linalg.norm(vehicle_distance_list[i][0].position - vehicle_distance_list[i-1][0].position)
                    alpha = 2 * np.arcsin(distance / (2 * (variables.raradius + 4)))
                    length_between = alpha * (variables.raradius+4)
                    relevant_distances.append(length_between)
        return relevant_distances

    def waiting_time_and_risk(self, other_vehicles):
        """ Calculate waiting time and a risk assuming the agent enters the roundabout right after every new vehicle."""
        position_of_interest = self.projected_position()
        if position_of_interest is not None:
            vehicle_distance_list = self.distances_to_relevant_vehicles(other_vehicles, position_of_interest)
            relevant_distances = self.distances_between_relevant_vehicles(position_of_interest, vehicle_distance_list)
            # calculate the risk and the time based on s = t*v,
            # simplifying for s by just using distances from other cars to ego vehicle.
            risk=[]
            waiting_time=[]
            for i in range(0, len(relevant_distances)):
                risk.append(3 / relevant_distances[i])
                if i == 0:
                    waiting_time.append(0 + self.timer)
                else:
                    waiting_time.append((vehicle_distance_list[i-1][1]) / vehicle_distance_list[i-1][0].velocity + self.timer)
            return waiting_time, risk
        else:
            return None, None

    def mutate_threshold(self, sigma, mutation_probability):
        """Change the agent risk threshold by mutation."""
        if  random.random() <= mutation_probability:
            mutated_threshold = self.risk_threshold - random.gauss(0, sigma)
            if mutated_threshold < variables.lower_risk_bound:
                mutated_threshold = variables.lower_risk_bound
            elif mutated_threshold > variables.upper_risk_bound:
                mutated_threshold = variables.upper_risk_bound
            self.risk_threshold = mutated_threshold

    def get_PO_solutions(self, time, risk):
        """Identify the pareto optimal solutions for the agent out of all possible ones.
        """
        dominated_risk=[]
        dominated_time=[]
        for i in range(0,len(time)):
            for j in range(0, len(time)):
                if (time[i]<=time[j] and risk[i]<risk[j]) or (time[i]<time[j] and risk[i]<=risk[j]):
                    if time[j] not in dominated_time:
                        dominated_risk.append(risk[j])
                        dominated_time.append(time[j])

        non_dominated_risk=self.diff_list(risk, dominated_risk)
        non_dominated_time=self.diff_list(time, dominated_time)

        return non_dominated_time, dominated_time, non_dominated_risk, dominated_risk

    def diff_list(self, li1, li2):
        """Get the difference between two lists."""
        li_dif = [i for i in li1 + li2 if i not in li1 or i not in li2]
        return li_dif

    def visualize (self, non_dominated_time, dominated_time, non_dominated_risk, dominated_risk, roundabout):
        """Create a plot of the different options for the agent based on risk and time."""
        fig, ax = plt.subplots()
        fig.subplots_adjust(bottom=0.4)
        hyperv = roundabout.archive.hypervolume(non_dominated_risk, non_dominated_time)
        plt.title("Risk/Waiting time decision options for Agent " + self.id)
        plt.xlabel("Waiting time \n \n Selected solution: " + str(self.solution_selected + 1)
                    + "\n threshold: " + str(self.risk_threshold)
                    + "\n hypervolume: " + str(hyperv))
        plt.ylabel("Risk")
        #plt.ylim(0,2)
        plt.xlim(-0.3, 10)
        plt.axhline(y=self.risk_threshold)
        ax.scatter(non_dominated_time, non_dominated_risk, c='g')
        ax.scatter(dominated_time, dominated_risk, c='b')
        plt.show()

    def select_solution(self, non_dominated_time, non_dominated_risk):
        """Pick solution for agent out of the non-dominated possible options."""
        for index in range(len(non_dominated_risk)):
            if non_dominated_risk[index] <= self.risk_threshold:
                self.solution_selected = index
                break
        if self.solution_selected != -1:
            self.waiting_time = non_dominated_time[self.solution_selected]

    def first_arrival(self, roundabout):
        """Routine when the agent approaches a roundabout in the training phase."""
        self.act(variables.ACTIONS[3])
        self.stopped = True
        self.standing = True
        self.crashed = False
        self.archive_updated = False
        self.timer = 0
        self.waiting_time = variables.MAX_WAITING_TIME
        self.solution_selected = -1
        time, risk = self.waiting_time_and_risk(roundabout.other_vehicles)
        non_dominated_time, dominated_time, non_dominated_risk, dominated_risk = self.get_PO_solutions(time, risk)
        self.non_dominated_risk = non_dominated_risk
        self.non_dominated_time = non_dominated_time
        self.hypervolume = roundabout.archive.hypervolume(non_dominated_risk, non_dominated_time)
        roundabout.archive.search_archive(self, roundabout.risk_tol, roundabout.threshold_tol, roundabout.hv_tol)
        self.select_solution(non_dominated_time, non_dominated_risk)
        # self.visualize(non_dominated_time, dominated_time, non_dominated_risk, dominated_risk)

    def first_arrival_test(self, roundabout):
        """Routine when the agent is already waiting at the roundabout entrance in the test phase."""
        self.act(variables.ACTIONS[3])
        self.stopped = True
        self.standing = True
        self.crashed = False
        self.stats_updated = False
        self.timer = 0
        self.waiting_time = variables.MAX_WAITING_TIME
        self.solution_selected = -1
        time, risk = self.waiting_time_and_risk(roundabout.other_vehicles)
        non_dominated_time, dominated_time, non_dominated_risk, dominated_risk = self.get_PO_solutions(time, risk)
        self.non_dominated_risk = non_dominated_risk
        self.non_dominated_time = non_dominated_time
        self.hypervolume = roundabout.archive.hypervolume(non_dominated_risk, non_dominated_time)
        roundabout.archive.search_test_archive(self, roundabout.risk_tol, roundabout.hv_tol, roundabout.distances_to_solutions)
        self.select_solution(non_dominated_time, non_dominated_risk)

    def still_standing(self, roundabout):
        """Routine when the agent is already waiting at the roundabout entrance in the training phase."""
        self.act(variables.ACTIONS[0])

        if self.timer >= self.max_waiting_time and self.solution_selected == -1:
            self.mutate_threshold(roundabout.sigma, 1.)
            self.first_arrival(roundabout)

        elif self.timer >= self.waiting_time and self.solution_selected != -1:
            self.act(variables.ACTIONS[1])
            self.standing = False

        elif self.timer >= max(self.non_dominated_time[-1], self.non_dominated_time[0] + 1) and self.solution_selected == -1:
            self.waiting_time = max(self.non_dominated_time[-1], self.non_dominated_time[0] + 1) - self.non_dominated_time[0] + 5
            roundabout.archive.update_archive(self, roundabout.risk_tol, roundabout.threshold_tol, roundabout.hv_tol, self.archive_index,
                                        self.hypervolume, self.non_dominated_risk, 'waiting')
            self.archive_updated = False
            time, risk = self.waiting_time_and_risk(roundabout.other_vehicles)
            non_dominated_time, dominated_time, non_dominated_risk, dominated_risk = self.get_PO_solutions(time,risk)
            self.hypervolume = roundabout.archive.hypervolume(non_dominated_risk, non_dominated_time)
            self.non_dominated_risk = non_dominated_risk
            self.non_dominated_time = non_dominated_time
            roundabout.archive.search_archive(self, roundabout.risk_tol, roundabout.threshold_tol, roundabout.hv_tol)
            self.select_solution(non_dominated_time, non_dominated_risk)

    def still_standing_test(self, roundabout):
        """Routine when the agent is already waiting at the roundabout entrance in the test phase."""
        self.act(variables.ACTIONS[0])
        if self.timer >= self.waiting_time and self.solution_selected != -1:
            self.act(variables.ACTIONS[1])
            self.standing = False

        elif self.timer >= max(self.non_dominated_time[-1], self.non_dominated_time[0] + 1) and self.solution_selected == -1:
            time, risk = self.waiting_time_and_risk(roundabout.other_vehicles)
            non_dominated_time, dominated_time, non_dominated_risk, dominated_risk = self.get_PO_solutions(time,risk)
            self.hypervolume = roundabout.archive.hypervolume(non_dominated_risk, non_dominated_time)
            self.non_dominated_time = non_dominated_time
            self.non_dominated_risk = non_dominated_risk
            roundabout.archive.search_test_archive(self, roundabout.risk_tol, roundabout.hv_tol, roundabout.distances_to_solutions)
            self.select_solution(non_dominated_time, non_dominated_risk)

    def roundabout_entrance(self, roundabout):
        """Select routines for agent based on whether it just arrived at the roundabout or is already standing there for the training phase."""
        if (self.position[1] < -168 and self.stopped == False) or (self.position[1] > -44 and self.stopped == False):
            self.first_arrival(roundabout)
        elif (self.position[1] < -168 or self.position[1] > -44) and self.stopped == True and self.standing == True:
            self.still_standing(roundabout)

    def roundabout_entrance_test(self, roundabout):
        """Select routines for agent based on whether it just arrived at the roundabout or is already standing there for the test phase."""
        if (self.position[1] < -168 and self.stopped == False) or (self.position[1] > -44 and self.stopped == False):
            self.first_arrival_test(roundabout)
        elif (self.position[1] < -168 or self.position[1] > -44) and self.stopped == True and self.standing == True:
            self.still_standing_test(roundabout)

    def step(self, dt):
        """
            Step the simulation.

            Increases a timer used for decision policies, and step the vehicle dynamics.

        :param dt: timestep
        """
        self.timer += dt
        super(AgentVehicle, self).step(dt)

    def check_collision(self, other):
        """
            Check for collision with another vehicle.

        :param other: the other vehicle
        """

        if not self.COLLISIONS_ENABLED or not other.COLLISIONS_ENABLED or self.crashed or other is self:
            return

        # Fast spherical pre-check
        if np.linalg.norm(other.position - self.position) > self.LENGTH:
            return

        # Accurate rectangular check
        if methods.rotated_rectangles_intersect((self.position, 0.9*self.LENGTH, 0.9*self.WIDTH, self.heading),
                                              (other.position, 0.9*other.LENGTH, 0.9*other.WIDTH, other.heading)) and self.crashed == False:
            """ self.velocity = min(self.velocity, other.velocity)"""
            self.crashed = True
            #print(self.crashCounter)

"""
--------------------------------------------VEHICLES FOR THE ROUNDABOUT: ---------------------------------------------------------------------------------------------
"""
class simpleVehicle(Vehicle):
    """
        A vehicle using both a longitudinal and a lateral decision policies.

        - Longitudinal: the IDM model computes an acceleration given the preceding vehicle's distance and velocity.

        """
    TAU_A = 0.6  # [s]
    TAU_DS = 0.2  # [s]
    PURSUIT_TAU = 1.5*TAU_DS  # [s]
    KP_A = 1 / TAU_A
    KP_HEADING = 1 / TAU_DS
    KP_LATERAL = 1 / 0.5  # [1/s]
    MAX_STEERING_ANGLE = np.pi / 3  # [rad]

    DELTA_VELOCITY = 5  # [m/s]

    # Longitudinal policy parameters
    ACC_MAX = 6.0  # [m/s2]
    COMFORT_ACC_MAX = 3.0  # [m/s2]
    COMFORT_ACC_MIN = -5.0  # [m/s2]
    DISTANCE_WANTED = 1.0  # [m]
    TIME_WANTED = 0.0  # [s]
    DELTA = 4.0  # []

    def __init__(self,
                 road,
                 position,
                 heading=0,
                 velocity=0,
                 target_lane_index=None,
                 target_velocity=None,
                 route=None,
                 enable_lane_change=False,
                 timer=None):
        super(simpleVehicle, self).__init__(road, position, heading, velocity)
        self.target_lane_index = target_lane_index or self.lane_index
        self.target_velocity = target_velocity or self.velocity
        self.route = route
        self.enable_lane_change = enable_lane_change
        self.timer = timer or (np.sum(self.position)*np.pi) % 1

    def act(self, action=None):
        """
            Perform a high-level action to change the desired lane or velocity.

            - If a high-level action is provided, update the target velocity and lane;
            - then, perform longitudinal and lateral control.

        :param action: a high-level action
        """

        if action == "FASTER":
            self.target_velocity += self.DELTA_VELOCITY
        elif action == "SLOWER":
            self.target_velocity -= self.DELTA_VELOCITY
        action = {}
        action = {'steering': self.steering_control(self.target_lane_index),
                  'acceleration': self.velocity_control(self.target_velocity)}
        super(simpleVehicle, self).act(action)

    def steering_control(self, target_lane_index):
        """
            Steer the vehicle to follow the center of an given lane.

        1. Lateral position is controlled by a proportional controller yielding a lateral velocity command
        2. Lateral velocity command is converted to a heading reference
        3. Heading is controlled by a proportional controller yielding a heading rate command
        4. Heading rate command is converted to a steering angle

        :param target_lane_index: index of the lane to follow
        :return: a steering wheel angle command [rad]
        """
        target_lane = self.road.network.get_lane(target_lane_index)
        lane_coords = target_lane.local_coordinates(self.position)
        lane_next_coords = lane_coords[0] + self.velocity * self.PURSUIT_TAU
        lane_future_heading = target_lane.heading_at(lane_next_coords)

        # Lateral position control
        lateral_velocity_command = - self.KP_LATERAL * lane_coords[1]
        # Lateral velocity to heading
        heading_command = np.arcsin(np.clip(lateral_velocity_command/methods.not_zero(self.velocity), -1, 1))
        heading_ref = lane_future_heading + np.clip(heading_command, -np.pi/4, np.pi/4)
        # Heading control
        heading_rate_command = self.KP_HEADING * methods.wrap_to_pi(heading_ref - self.heading)
        # Heading rate to steering angle
        steering_angle = np.arctan(self.LENGTH / methods.not_zero(self.velocity) * heading_rate_command)
        steering_angle = np.clip(steering_angle, -self.MAX_STEERING_ANGLE, self.MAX_STEERING_ANGLE)
        return steering_angle

    def velocity_control(self, target_velocity):
        """
            Control the velocity of the vehicle.

            Using a simple proportional controller.

        :param target_velocity: the desired velocity
        :return: an acceleration command [m/s2]
        """
        return self.KP_A * (target_velocity - self.velocity)

    def setDistanceWanted (self, distance):
        self.DISTANCE_WANTED = distance

    def randomize_behavior(self):
        pass

    def step(self, dt):
        """
            Step the simulation.

            Increases a timer used for decision policies, and step the vehicle dynamics.

        :param dt: timestep
        """
        self.timer += dt
        super(simpleVehicle, self).step(dt)

    def acceleration(self, ego_vehicle, front_vehicle=None, rear_vehicle=None):
        """
            Compute an acceleration command with the Intelligent Driver Model.

            The acceleration is chosen so as to:
            - reach a target velocity;
            - maintain a minimum safety distance (and safety time) w.r.t the front vehicle.

        :param ego_vehicle: the vehicle whose desired acceleration is to be computed. It does not have to be an
                            IDM vehicle, which is why this method is a class method. This allows an IDM vehicle to
                            reason about other vehicles behaviors even though they may not IDMs.
        :param front_vehicle: the vehicle preceding the ego-vehicle
        :param rear_vehicle: the vehicle following the ego-vehicle
        :return: the acceleration command for the ego-vehicle [m/s2]
        """
        if not ego_vehicle:
            return 0
        acceleration = self.COMFORT_ACC_MAX * (
                1 - np.power(max(ego_vehicle.velocity, 0) / methods.not_zero(ego_vehicle.target_velocity), self.DELTA))

        if front_vehicle:
            if type(front_vehicle) is type(self):
                d = ego_vehicle.lane_distance_to(front_vehicle)
                #Avoid going backwards:
                if (acceleration<0):
                    acceleration -= self.COMFORT_ACC_MAX * \
                        np.power(self.desired_gap(ego_vehicle, front_vehicle) / methods.not_zero(d), 2)
        return acceleration

    def desired_gap(self, ego_vehicle, front_vehicle=None):
        """
            Compute the desired distance between a vehicle and its leading vehicle.

        :param ego_vehicle: the vehicle being controlled
        :param front_vehicle: its leading vehicle
        :return: the desired distance between the two [m]
        """
        d0 = self.DISTANCE_WANTED + ego_vehicle.LENGTH / 2 + front_vehicle.LENGTH / 2
        tau = self.TIME_WANTED
        ab = -self.COMFORT_ACC_MAX * self.COMFORT_ACC_MIN
        dv = ego_vehicle.velocity - front_vehicle.velocity
        d_star = d0 + ego_vehicle.velocity * tau + ego_vehicle.velocity * dv / (2 * np.sqrt(ab))
        return d_star

    def maximum_velocity(self, front_vehicle=None):
        """
            Compute the maximum allowed velocity to avoid Inevitable Collision States.

            Assume the front vehicle is going to brake at full deceleration and that
            it will be noticed after a given delay, and compute the maximum velocity
            which allows the ego-vehicle to brake enough to avoid the collision.

        :param front_vehicle: the preceding vehicle
        :return: the maximum allowed velocity, and suggested acceleration
        """
        if not front_vehicle:
            return self.target_velocity
        d0 = self.DISTANCE_WANTED
        a0 = self.COMFORT_ACC_MIN
        a1 = self.COMFORT_ACC_MIN
        tau = self.TIME_WANTED
        d = max(self.lane_distance_to(front_vehicle) - self.LENGTH / 2 - front_vehicle.LENGTH / 2 - d0, 0)
        v1_0 = front_vehicle.velocity
        delta = 4 * (a0 * a1 * tau) ** 2 + 8 * a0 * (a1 ** 2) * d + 4 * a0 * a1 * v1_0 ** 2
        v_max = -a0 * tau + np.sqrt(delta) / (2 * a1)

        # Velocity control
        self.target_velocity = min(self.maximum_velocity(front_vehicle), self.target_velocity)
        acceleration = self.velocity_control(self.target_velocity)

        return v_max, acceleration

"""
    --------------The next classes are not used, they define certain behaviors of cars. --------------------------------
"""
class LinearVehicle(simpleVehicle):
    """
        A Vehicle whose longitudinal and lateral controllers are linear with respect to parameters
    """
    ACCELERATION_PARAMETERS = [0.3, 0.14, 0.8]
    STEERING_PARAMETERS = [AgentVehicle.KP_HEADING, AgentVehicle.KP_HEADING * AgentVehicle.KP_LATERAL]

    ACCELERATION_RANGE = np.array([0.5*np.array(ACCELERATION_PARAMETERS), 1.5*np.array(ACCELERATION_PARAMETERS)])
    STEERING_RANGE = np.array([np.array(STEERING_PARAMETERS) - np.array([0.07, 1.5]),
                               np.array(STEERING_PARAMETERS) + np.array([0.07, 1.5])])

    TIME_WANTED = 2.0

    def __init__(self, road, position,
                 heading=0,
                 velocity=0,
                 target_lane_index=None,
                 target_velocity=None,
                 route=None,
                 enable_lane_change=True,
                 timer=None):
        super(LinearVehicle, self).__init__(road,
                                            position,
                                            heading,
                                            velocity,
                                            target_lane_index,
                                            target_velocity,
                                            route,
                                            enable_lane_change,
                                            timer)

    def randomize_behavior(self):
        ua = self.road.np_random.uniform(size=np.shape(self.ACCELERATION_PARAMETERS))
        self.ACCELERATION_PARAMETERS = self.ACCELERATION_RANGE[0] + ua*(self.ACCELERATION_RANGE[1] -
                                                                        self.ACCELERATION_RANGE[0])
        ub = self.road.np_random.uniform(size=np.shape(self.STEERING_PARAMETERS))
        self.STEERING_PARAMETERS = self.STEERING_RANGE[0] + ub*(self.STEERING_RANGE[1] - self.STEERING_RANGE[0])

    def acceleration(self, ego_vehicle, front_vehicle=None, rear_vehicle=None):
        """
            Compute an acceleration command with a Linear Model.

            The acceleration is chosen so as to:
            - reach a target velocity;
            - reach the velocity of the leading (resp following) vehicle, if it is lower (resp higher) than ego's;
            - maintain a minimum safety distance w.r.t the leading vehicle.

        :param ego_vehicle: the vehicle whose desired acceleration is to be computed. It does not have to be an
                            Linear vehicle, which is why this method is a class method. This allows a Linear vehicle to
                            reason about other vehicles behaviors even though they may not Linear.
        :param front_vehicle: the vehicle preceding the ego-vehicle
        :param rear_vehicle: the vehicle following the ego-vehicle
        :return: the acceleration command for the ego-vehicle [m/s2]
        """
        return np.dot(self.ACCELERATION_PARAMETERS, self.acceleration_features(ego_vehicle, front_vehicle, rear_vehicle))

    def acceleration_features(self, ego_vehicle, front_vehicle=None, rear_vehicle=None):
        vt, dv, dp = 0, 0, 0
        if ego_vehicle:
            vt = ego_vehicle.target_velocity - ego_vehicle.velocity
            d_safe = self.DISTANCE_WANTED + np.max(ego_vehicle.velocity, 0) * self.TIME_WANTED + ego_vehicle.LENGTH
            if front_vehicle:
                d = ego_vehicle.lane_distance_to(front_vehicle)
                dv = min(front_vehicle.velocity - ego_vehicle.velocity, 0)
                dp = min(d - d_safe, 0)
        return np.array([vt, dv, dp])

    def steering_control(self, target_lane_index):
        """
            Linear controller with respect to parameters.
            Overrides the non-linear controller ControlledVehicle.steering_control()
        :param target_lane_index: index of the lane to follow
        :return: a steering wheel angle command [rad]
        """
        steering_angle = np.dot(np.array(self.STEERING_PARAMETERS), self.steering_features(target_lane_index))
        steering_angle = np.clip(steering_angle, -self.MAX_STEERING_ANGLE, self.MAX_STEERING_ANGLE)
        return steering_angle

    def steering_features(self, target_lane_index):
        """
            A collection of features used to follow a lane
        :param target_lane_index: index of the lane to follow
        :return: a array of features
        """
        lane = self.road.network.get_lane(target_lane_index)
        lane_coords = lane.local_coordinates(self.position)
        lane_next_coords = lane_coords[0] + self.velocity * self.PURSUIT_TAU
        lane_future_heading = lane.heading_at(lane_next_coords)
        features = np.array([methods.wrap_to_pi(lane_future_heading - self.heading) *
                             self.LENGTH / methods.not_zero(self.velocity),
                             -lane_coords[1] * self.LENGTH / (methods.not_zero(self.velocity) ** 2)])
        return features

class AggressiveVehicle(LinearVehicle):
    LANE_CHANGE_MIN_ACC_GAIN = 1.0  # [m/s2]
    MERGE_ACC_GAIN = 0.8
    MERGE_VEL_RATIO = 0.75
    MERGE_TARGET_VEL = 30
    ACCELERATION_PARAMETERS = [MERGE_ACC_GAIN / ((1 - MERGE_VEL_RATIO) * MERGE_TARGET_VEL),
                               MERGE_ACC_GAIN / (MERGE_VEL_RATIO * MERGE_TARGET_VEL),
                               0.5]

class DefensiveVehicle(LinearVehicle):
    LANE_CHANGE_MIN_ACC_GAIN = 1.0  # [m/s2]
    MERGE_ACC_GAIN = 1.2
    MERGE_VEL_RATIO = 0.75
    MERGE_TARGET_VEL = 30
    ACCELERATION_PARAMETERS = [MERGE_ACC_GAIN / ((1 - MERGE_VEL_RATIO) * MERGE_TARGET_VEL),
                               MERGE_ACC_GAIN / (MERGE_VEL_RATIO * MERGE_TARGET_VEL),
                               2.0]

"""
                OBSTACLE CLASS, IN CASE WE WANT TO PUT OBSTACLES IN THE ROAD (NOT NEEDED)
"""
class Obstacle(Vehicle):
    """
        A motionless obstacle at a given position.
    """

    def __init__(self, road, position, heading=0):
        super(Obstacle, self).__init__(road, position, velocity=0, heading=heading)
        self.target_velocity = 0
        self.LENGTH = self.WIDTH


import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import statsmodels.formula.api as smf

num_obs = 20
laliga_df = pd.read_csv("playerstats.txt", delimiter=',')
minutes_model = pd.DataFrame()
minutes_model = minutes_model.assign(minutes=laliga_df['Min'][0:num_obs])
minutes_model = minutes_model.assign(age=laliga_df['Age'][0:num_obs])

# Make an age squared column so we can fit polynomial model.
minutes_model = minutes_model.assign(age_squared=np.power(laliga_df['Age'][0:num_obs], 2))

# plotting the data
fig, ax = plt.subplots(num=1)
ax.plot(minutes_model['age'], minutes_model['minutes'], linestyle='none', marker='.', markersize=10, color='blue')
ax.set_ylabel('Minutes played')
ax.set_xlabel('Age')
ax.spines['top'].set_visible(False)
ax.spines['right'].set_visible(False)
plt.xlim((15, 40))
plt.ylim((0, 3000))
plt.show()

# fitting the model
model_fit = smf.ols(formula='minutes  ~ age   ', data=minutes_model).fit()
print(model_fit.summary())
b = model_fit.params

# comparing the fit
# First plot the data as previously
fig, ax = plt.subplots(num=1)
ax.plot(minutes_model['age'], minutes_model['minutes'], linestyle='none', marker='.', markersize=10, color='blue')
ax.set_ylabel('Minutes played')
ax.set_xlabel('Age')
ax.spines['top'].set_visible(False)
ax.spines['right'].set_visible(False)
plt.xlim((15, 40))
plt.ylim((0, 3000))

# Now create the line through the data
x = np.arange(40, step=1)
y = np.mean(minutes_model['minutes']) * np.ones(40)
ax.plot(x, y, color='black')

# Show distances to line for each point
for i, a in enumerate(minutes_model['age']):
    ax.plot([a, a], [minutes_model['minutes'][i], np.mean(minutes_model['minutes'])], color='red')
plt.show()

# fitting quadratic model
# First fit the model
model_fit = smf.ols(formula='minutes  ~ age + age_squared  ', data=minutes_model).fit()
print(model_fit.summary())
b = model_fit.params

# Compare the fit
fig, ax = plt.subplots(num=1)
ax.plot(minutes_model['age'], minutes_model['minutes'], linestyle='none', marker='.', markersize=10, color='blue')
ax.set_ylabel('Minutes played')
ax.set_xlabel('Age')
ax.spines['top'].set_visible(False)
ax.spines['right'].set_visible(False)
plt.xlim((15, 40))
plt.ylim((0, 3000))
x = np.arange(40, step=1)
y = b[0] + b[1] * x + b[2] * x * x
ax.plot(x, y, color='black')

for i, a in enumerate(minutes_model['age']):
    ax.plot([a, a], [minutes_model['minutes'][i], b[0] + b[1] * a + b[2] * a * a], color='red')
plt.show()


from fastapi.responses import JSONResponse
import gridfs
from motor.motor_asyncio import AsyncIOMotorClient, AsyncIOMotorGridFSBucket
from model import Location, Array2D, KeyIndices
from dotenv import load_dotenv
import os
import pandas as pd
import pickle
from scipy.spatial import KDTree
from scipy.stats import norm
from tensorflow import keras
from keras.models import load_model
from datetime import datetime
import numpy as np
import requests
from gridfs import GridFS
from bson import ObjectId
import logging
logging.basicConfig(level=logging.INFO)

load_dotenv()
MONGO_URL=os.getenv("MONGO_URL")
GOOGLE_API_KEY = os.getenv("GOOGLE_API_KEY")
import motor.motor_asyncio
client = motor.motor_asyncio.AsyncIOMotorClient(MONGO_URL)
database = client.smartparking
collection = database.segments
collection_ratios = database.test_data
collection_keys = database.key_dict
collection_ratios2 = database.test_data2

async def fetch_all_segments():
    segments = []
    cursor = collection.find({})
    async for document in cursor:
        segments.append(Location(**document))
    return segments

async def fetch_test_data():
    ratios = []
    cursor = collection_ratios.find({})
    async for document in cursor:
        ratios.append(Array2D(**document))

    return ratios

async def fetch_key_dict():
    keys = {}
    cursor = collection_keys.find({})
    async for document in cursor:
        element = KeyIndices(**document)
        keys[element.segment_id] = element.index

    return keys

async def read_ratios():
    loaded_data=pd.read_pickle("../backend/239_ratioMatrix_merged.pickle")
    return loaded_data

async def download_stream():
    my_db = AsyncIOMotorClient().test
    fs = AsyncIOMotorGridFSBucket(my_db)
    # get _id of file to read.
    file_id = await fs.upload_from_stream("test_file",
                                          b"data I want to store!")
    grid_out = await fs.open_download_stream(file_id)
    contents = await grid_out.read()

def find_place_id(api_key,coord_list_dict):
    # logging.info("coord_list_dict len:************", len(coord_list_dict))
    place_id_dict = {}
    seg_address_dict = {}
    base_url = 'https://maps.googleapis.com/maps/api/geocode/json'

    for segment_id, coord in coord_list_dict.items():
        params = {
            'latlng': f'{coord[0]},{coord[1]}',
            'key': api_key
        }

        response = requests.get(base_url, params=params)
        data = response.json()

        first_result = data['results'][0]
        place_id = first_result.get('place_id')
        address = first_result.get('formatted_address')

        place_id_dict[place_id] = segment_id
        seg_address_dict[segment_id] = address

    return place_id_dict, seg_address_dict

async def find_closest_ten(lat,lng):
    segments = []
    cursor = collection.find({})
    async for document in cursor:
        segments.append(Location(**document))

    # Coordinates of the 239 places in a list of tuples [(lat, lon), ...]
    places_coordinates = []
    for seg in segments:
        places_coordinates.append((seg.start_loc_lat, seg.start_loc_long))
        places_coordinates.append((seg.end_loc_lat, seg.end_loc_long))

    # Build a KD-Tree for starting coordinates
    places_kd_tree = KDTree(places_coordinates)

    # Function to find the closest places to a given destination
    k = 20

    distances, indices = places_kd_tree.query([(lat,lng)], k=k)
    closest_places = [(places_coordinates[i], distance) for i, distance in zip(indices[0], distances[0])]
    selected_segments_dict , seg_cap_dict, error_dist_dict, matrix_index_dict = await find_segment_id_from_coordinates(closest_places)

    return {"selected_seg_dict":selected_segments_dict , "seg_cap_dict":seg_cap_dict, "error_dist_dict":error_dist_dict, "matrix_index_dict":matrix_index_dict}

def calculate_distances(origins, destination, mode, api_key):

    API_KEY = api_key

    base_url = 'https://maps.googleapis.com/maps/api/distancematrix/json'

    params = {
        'origins': '|'.join(origins),
        'destinations': destination,
        'key': API_KEY,
        'mode': mode
    }

    response = requests.get(base_url, params=params)
    data = response.json()

    return data

def format_date(current_time):
    date, time = current_time.split(' ')
    year, month, day = date.split('/')

    date_string = year + "-" + month + "-" + '5' + " " + time
    date_format = "%Y-%m-%d %H:%M:%S"

    parsed_date = datetime.strptime(date_string, date_format)
    return parsed_date

def string_sep_time(car_duration):
    dur_list = car_duration.split(' ')
    len_list = len(dur_list)
    total_min = 0
    for i in range(0,len_list,2):
        if(dur_list[i+1] == "days"):
            total_min += int(dur_list[i]) * 24 * 60
        elif(dur_list[i+1] == "hours"):
            total_min += int(dur_list[i]) * 60
        else:
            total_min += int(dur_list[i])

    return total_min

def string_sep_distance(distance):
    string_dist, unit_dist = distance.split(' ')
    walking_dist = -1
    if(unit_dist == "km"):
        if(',' in string_dist):
            walking_dist1, walking_dist2 = string_dist.split(',')
            walking_dist = walking_dist1 + walking_dist2
            walking_dist = float(walking_dist) * 1000
        else:
            walking_dist = float(string_dist) * 1000
    else:
        walking_dist = float(string_dist)
    return walking_dist

#give 30 inputs to the model and get 6 next
async def make_prediction(segment_id_dict, seg_cap_dict, error_dist_dict, matrix_index_dict, current_time, dest_lat, dest_lng, current_loc, selected_option):

    # #print(current_loc) # in the format of [-37.8388305, 144.9418181]

    #todo: bu input olaraK GELMELİ
    current_time = "2019/07/05 14:17:00"

    parsed_date = format_date(current_time)
    #print(parsed_date) # in the format of 2019-02-05 12:25:22
    time_intervals = pd.date_range("2019-01-01 00:00", "2019-12-31 23:55", freq='5T')  #time_intervals = time_intervals[8000:10000]

    # --------------------------------------Load model ---------------------------------------------#
    model = load_model("../modelfiles/LSTM_sixstep_allsegments.keras")

    #---------------------------Find segment_ids of closest 10 places ------------------------------#
    #segment_id_dict, segment_id_list = await find_segment_id_from_coordinates(closest_places)
    segment_id_list = list(segment_id_dict.keys())

    ratios_all = await read_ratios()

    # --------------------- Find the index of the interval in which the date resides--------------- #
    interval_index = time_intervals.searchsorted(parsed_date) + 1

    #------------------------- Reshape input to give as prediction input ---------------------------#
    model_input = ratios_all[:,interval_index - 30:interval_index ].reshape((1,239,30))

    #----------------------------------------Make prediction--------------------------------------- #
    prediction_next_6 = model.predict(model_input)

    # --------------------------Calculate walking meters from segments to destination---------------#
    API_KEY = GOOGLE_API_KEY

    place_id_dict, seg_address_dict = find_place_id(API_KEY,segment_id_dict)

    origins = []
    # for key,cor in segment_id_dict.items():
    #     origins.append(str(cor[0]) + ',' + str(cor[1]))

    for place_id,segment_id in place_id_dict.items():
        origins.append("place_id:" + str(place_id))

    origins2 = []
    for segment_id, address in seg_address_dict.items():
        origins2.append(address)

    walking_metadata = calculate_distances(origins2, str(dest_lat) + "," + str(dest_lng), 'walking', API_KEY)
    car_travel_metadata = calculate_distances(origins2, str(current_loc[0]) + "," + str(current_loc[1]), 'driving', API_KEY)

    #-------------------------Calculate car travel time if origin is supplied ----------------------#
    segment_metadata = {}
    index_row = 0
    rows = walking_metadata['rows']
    rows_car = car_travel_metadata['rows']

    for segment_id, coord in seg_address_dict.items():
        distance = rows[index_row]['elements'][0]['distance']['text']
        duration = rows[index_row]['elements'][0]['duration']['text']

        car_distance = rows_car[index_row]['elements'][0]['distance']['text']
        car_duration = rows_car[index_row]['elements'][0]['duration']['text']

        walking_dist = string_sep_distance(distance)
        walking_travel_time_min = string_sep_time(duration)

        car_distance_float = string_sep_distance(car_distance)
        car_travel_time_min = string_sep_time(car_duration)

        segment_metadata[segment_id] = {
            'walking_distance': walking_dist,
            'walking_duration': walking_travel_time_min,
            'car_distance': car_distance_float,
            'car_duration': car_travel_time_min,
            'seg_total_dur': walking_travel_time_min + car_travel_time_min
        }
        index_row += 1

    prediction_next_6_dict = get_pred_for_10_seg(matrix_index_dict,prediction_next_6[0].tolist())

    # what we need
    #prediction_next_6 # -> next 6 value prediction for all segments
    segment_id_list # -> segments ids in a list format
    seg_cap_dict # -> seg_id: capacity_list [12]
    matrix_index_dict # -> seg_id: matrix_index [10]
    error_dist_dict # -> seg_id: error_dist_list [6]
    prediction_next_6_dict # -> predicted values for our 10 segments
    #segment_metadata[segment_id] # ->         segment_metadata[segment_id] = {
                                                #     'walking_distance': walking_dist,
                                                #     'walking_duration': walking_travel_time_min,
                                                #     'car_distance': "" ,
                                                #     'car_duration': "",
                                                # }

    #use naive model results if the total travel time is more than 30
    naive_model_data = ratios_all[:, interval_index - 2016 : interval_index]
    #print(naive_model_data)
    naive_model_10seg_dict = get_pred_for_10_seg(matrix_index_dict, naive_model_data)

    travel_time_list = []
    walking_distance_list = []
    likelihood_list = []
    pred_ratio_acc_time={}
    curr_month = parsed_date.month
    for seg_id in segment_id_list:
        segment = segment_metadata[seg_id]
        total_travel_time= segment['seg_total_dur'] # in minutes
        walking_distance_in_m = segment['walking_distance'] # in meters
        travel_time_list.append(total_travel_time)

        walking_distance_list.append(walking_distance_in_m)

        index_5t = int(total_travel_time // 5)
        if( index_5t >= 6): #use naive
            pred = naive_model_10seg_dict[seg_id][index_5t]
            error_dist = error_dist_dict[seg_id][6]
        else:
            error_dist = error_dist_dict[seg_id][index_5t]
            pred = prediction_next_6_dict[seg_id][index_5t]

        mean = error_dist[0]
        standard_dev = error_dist[1]
        pred_ratio_acc_time[seg_id] = pred

        capacity = seg_cap_dict[seg_id][curr_month-1]
        value = (capacity * (1 - pred - mean) - 1) / (capacity * standard_dev )
        likelihood_list.append(norm.cdf(value))

    travel_time_list = np.array(travel_time_list)
    likelihood_list = np.array(likelihood_list)
    walking_distance_list = np.array(walking_distance_list)

    travel_normalized = normalize_lists(travel_time_list)
    walking_normalized = normalize_lists(walking_distance_list)
    likelihood_normalized = 1 - normalize_lists(likelihood_list)

    likelihood_normalized_coeff = 0.30
    if(selected_option == 'Travel'):
        travel_normalized_coeff = 0.45
        walking_normalized_coeff = 0.25
    elif(selected_option == 'Walking'):
        travel_normalized_coeff = 0.25
        walking_normalized_coeff = 0.45
    else:
        travel_normalized_coeff = 0.35
        walking_normalized_coeff = 0.35

    result_list = travel_normalized * travel_normalized_coeff + walking_normalized * walking_normalized_coeff + likelihood_normalized * likelihood_normalized_coeff

    #likelihood -> 30
    #preference -> 45
    #otherrrrrr -> 25
    #default -> 30, 35, 35

    tmp_ind = 0
    dtype = [('segment_id', int), ('coordinates', tuple), ('result', float), ('capacity', int), ('predicted_ratio', float),('segment_metadata',dict), ('segment_address', tuple)]
    result_np_array = np.empty(0, dtype=dtype)
    for seg_id, coord in segment_id_dict.items():
        our_str = seg_address_dict[seg_id]
        elem = (seg_id , coord, result_list[tmp_ind], seg_cap_dict[seg_id][curr_month-1], pred_ratio_acc_time[seg_id],segment_metadata[seg_id], (our_str))
        result_np_array = np.append(result_np_array, np.array(elem, dtype=dtype))
        tmp_ind+= 1
    sorted_np = np.sort(result_np_array, order='result')

    return sorted_np.tolist()

#coor_list is a list of tuples [(lat1,long1), (lat2,long2),...]

def normalize_lists(list_np):
    min_travel = min(list_np)
    max_travel = max(list_np)

    normalized = ( list_np - min_travel ) / (max_travel - min_travel)
    return normalized

def get_pred_for_10_seg(matrix_index_dict,prediction_next_6):
    #print(len(prediction_next_6))
    prediction_dict = {}
    for seg_id, index in matrix_index_dict.items():
        prediction_dict[seg_id] = prediction_next_6[index]

    return prediction_dict

async def find_capacity_and_error(segment_id_list: list):
    seg_cap_dict = {}
    error_dist_dict = {}
    matrix_index_dict = {}
    for seg in segment_id_list:
        query = {"segment_id": seg}
        matching_documents = collection.find(query)
        new_elem = await matching_documents.to_list(length=1)
        seg_cap_dict[seg] = new_elem[0]['capacity']
        error_dist_dict[seg] = [new_elem[0]['error_dist1'] , new_elem[0]['error_dist2'], new_elem[0]['error_dist3'], new_elem[0]['error_dist4'], new_elem[0]['error_dist5'], new_elem[0]['error_dist6']]
        matrix_index_dict[seg] = new_elem[0]['matrix_index']

    return seg_cap_dict, error_dist_dict, matrix_index_dict

async def find_segment_id_from_coordinates(coor_list: list):
    selected_segments_dict = {}
    selected_segments = []

    seg_cap_dict = {}
    error_dist_dict = {}
    matrix_index_dict = {}

    len_list = len(coor_list)
    len_last = 0
    for i in range(len_list):
        coor = coor_list[i][0]
        query_s = {"start_loc_lat": coor[0], "start_loc_long": coor[1]}
        query_e = {"end_loc_lat": coor[0], "end_loc_long": coor[1]}
        matching_documents_s = collection.find(query_s)
        matching_documents_e = collection.find(query_e)
        new_elem_s = await matching_documents_s.to_list(length=1)
        new_elem_e = await matching_documents_e.to_list(length=1)

        if( new_elem_s != []):
            our_elem = new_elem_s
        elif( new_elem_e != []):
            our_elem = new_elem_e

        if( our_elem[0]['segment_id'] not in selected_segments_dict.keys() ):
            seg = int(our_elem[0]['segment_id'])
            selected_segments.append(seg)
            selected_segments_dict[seg] = coor
            seg_cap_dict[seg] = our_elem[0]['capacity']
            error_dist_dict[seg] = [our_elem[0]['error_dist1'] , our_elem[0]['error_dist2'], our_elem[0]['error_dist3'], our_elem[0]['error_dist4'], our_elem[0]['error_dist5'], our_elem[0]['error_dist6'],our_elem[0]['naive_error']]
            matrix_index_dict[seg] = our_elem[0]['matrix_index']
            len_last += 1

        if(len_last == 10):
            break

    return selected_segments_dict , seg_cap_dict, error_dist_dict, matrix_index_dict


# -*- coding: utf-8 -*-
"""
Created on Sat Dec  5 13:02:13 2020

@author: tonian
"""

import pandas as pd
import numpy as np
from matplotlib import pyplot as plt
from linearstatistics import linestatistics as lstats

pckfile = '308.txt'
pckdata = pd.read_csv(pckfile, sep="\s", header=None)
show_slopebreaks = 'true'

pckdata.columns = ["dist", "time"]

x = np.array(pckdata.dist)
y = np.array(pckdata.time)

nPts = len(x); #number of Points in each time series

#Plotting
plt.plot(x,y, 'b-o', label='1st Arrival Pcks')
#plt.title("Displacment for PS Point %i" %(in_ID))
plt.ylabel('Time (ms)',fontsize=15)
plt.xlabel('Distance (m)',fontsize=15)

# Define range of "kinks" to test for a slope break in best fit line
firstkink = 3; #allowing the kinks to start after the first 3 data Points (remember python indices start at 1 so the first kink is at #4); this is the location of the kink as it relates to the extracted 56 data Pointss
lastkink = nPts-3;#:nPts; #leaving 4 data points for the last kink
kinks = np.arange(firstkink, lastkink+1);
nKinks = len(kinks);

#------------CREATING ARRAYS TO STORE INFORMATION----------------------#
b = np.zeros((nKinks,4)); #stores best-fitting parameters from 2-line fit for all positions
yfitk = np.zeros((nPts,1)); #empty array for all kinked lines
yfitminslopeuncertaintyk = np.zeros(nKinks); #empty array for y values of minslopeuncertaintyk (shows optimum kink point)
minslopeuncertaintyk = np.zeros(2); #creating empty array to store max Rsquared values for kinked lines
kinkpos = np.zeros(1); #creating empty array to store corresponding time values for max Rsquared values
yfitkinkline = np.zeros((1,nPts)); #empty array for saving y fit values caluclated with kink location (basically plots kink line)
bminslopeuncertaintyk = np.zeros(4); #b values that correspond to the min slopeuncertaintyk value #b(1) intercept of line 1 for nth position
#b(2)#slope of line 1, b(3)intercept of second line added past kink Points, b(4) slope of second line added past kink Points
A = np.zeros((nPts,4))
D = []

#--------Best Fit Two Lines With Kink------------------------
bk = np.zeros((1,4)); # make bk array to store best-fit coefficients for each kink time
statistics_k1 = np.zeros((nKinks,5)) # make statistics array to for each kink time for 1st slope
statistics_k2 = np.zeros((nKinks,5)) # make statistics array to for each kink time for 2nd slope

#-----------Begin finding slope breaks------------------------------------
nk = -1; # make counter for kinks
for ik in range(firstkink,lastkink+1):
    nk = nk+1;
    D = np.zeros(nPts);
    D[ik:nPts+1] = 1;  # for Pointss with D = 1 second line is added; letting the first 3 points = 0

    # build A array , for which y = A * b and b = A\y
    A[:,0] = np.ones(nPts)
    A[:,1] = x
    A[:,2] = D
    A[:,3] = np.multiply(D,x)

    bk = np.linalg.lstsq(A,y)[0]; #calculating b array using the linear algebra - least squares from numpy (has to be least squares becasue the A matrix is not a perfect square)
    #input('Press ENTER to continue');

    #store b (array of 4) into the bk row for this kink time
    b[nk,:] = bk; #using the corresponding b array to calculate the yfit value for each Points

    #compute the yfit best-fitting line with kink through the data
    yfitk = bk[0] + bk[1]*x + bk[2]*D + bk[3]*D*x

    if show_slopebreaks == 'true':
        # -------Plot kinks------
        #random colors for fun
        rc = np.random.rand(1)
        bc = np.random.rand(1)
        gc = np.random.rand(1)
        color = (rc[0], gc[0], bc[0])
        plt.plot(x,yfitk, c=color, linewidth=1,)
    elif show_slopebreaks == 'false':
        pass

    # Compute line statistics for 1st slope, function returns [m, b, m_uncertainty, b_uncertainty,  R2]
    statistics_k1[nk,:] = lstats(x[0:ik],y[0:ik])

    # Compute slope uncertainty for 2nd slope, function returns [m, b, m_uncertainty, b_uncertainty,  R2]
    statistics_k2[nk,:] = lstats(x[ik-1:nPts],y[ik-1:nPts]) # want points to cross over the "kink point"

#-------------end of for loop for for finkding slope breaks---------------------

#Finding minimum combination of slope R2 for all kink lines
sq_combo = np.square(statistics_k1[:,2]) + np.square(statistics_k2[:,2])
[mincombo_min,mincombo_index] = [np.amin(sq_combo),np.where(sq_combo == sq_combo.min())];
#np.where(a == a.min())

#Storing minimum slope uncertainties
minslopeuncertaintyk[0] = statistics_k1[mincombo_index,2];
minslopeuncertaintyk[1] = statistics_k2[mincombo_index,2];

bminslopeuncertaintyk[0:4] = b[mincombo_index,0:4]; #finding
#corresponding b values for each minslopeuncertaintyk; this is done before finding
#the kink times because the b array is 48+/-2 (based on site) cells long

posminslopeuncertaintyk = mincombo_index[0]+2; #adding the 3 points
#that were excluded from the first kink, to find positions on whole
#array

kinkpos = x[posminslopeuncertaintyk];#finding
#corresponding time location for min slopeuncertaintyk values

#----------RECALULATING Y VALUES FOR BEST FITTED KINK LINE----------#

#Recreating the D vector to calculate the yfit for the minslopeuncertaintyk
Dminslopeuncertaintyk = np.zeros(nPts);
Dminslopeuncertaintyk[posminslopeuncertaintyk[0]:nPts] = 1;

yfitminslopeuncertaintyk = ((bminslopeuncertaintyk[0]) + (bminslopeuncertaintyk[1])*x + (bminslopeuncertaintyk[2])*Dminslopeuncertaintyk + (bminslopeuncertaintyk[3])*Dminslopeuncertaintyk*x);
yfitkinkline = yfitminslopeuncertaintyk; #saving yfit line with kink for future plotting

#---------Plotting final Slope Break
#plt.plot(x,yfitminslopeuncertaintyk, 'y-')
#plt.title("Displacment for PS Point %i" %(in_ID))

x_direct = x[0:posminslopeuncertaintyk[0]+1]
y_direct = y[0:posminslopeuncertaintyk[0]+1]

x_refract = x[posminslopeuncertaintyk[0]:nPts]
y_refract = y[posminslopeuncertaintyk[0]:nPts]

#------- Computing final slope statistics and labeling direct and refreacted waves
# Compute line statistics for 1st slope, function returns [m, b, m_uncertainty, b_uncertainty,  R2], for final slope break
statistics_s1 = lstats(x_direct,y_direct)

# Compute slope uncertainty for 2nd slope, function returns [m, b, m_uncertainty, b_uncertainty,  R2]
statistics_s2 = lstats(x_refract,y_refract) # want points to cross over the "kink point"

if np.abs(statistics_s1[0]) < np.abs(statistics_s2[0]) :
         print('\n>>> !ERROR! You may have an hidden layer problem, your direct wave has a much faster velocity (higher absolute slope) OR you did not start picking from time (0)!')

if statistics_s1[0] and statistics_s2[0] < 0: #if slopes neg they are reverse shot
    rotn = -180
    vanchor = 'baseline' #'top', 'bottom', 'center', 'baseline', 'center_baseline'
    hanchor = 'left' #'center', 'right', 'left'

elif statistics_s1[0] and statistics_s2[0] > 0:
    rotn = 0
    vanchor = 'baseline'
    hanchor = 'center'

plt.title('SEG2 First Arrivals Pcks from: %s'%(pckfile),fontweight ="bold")

label_s1 = 'Direct Wave'
label_s2 = 'Refracted Wave'
color1 = (0.1, 0.7, 0.1)
color2 = (0.9, 0.1, 0.1)
m_dir = statistics_s1[0]
m_refract = statistics_s2[0]
b_dir = statistics_s1[1]
b_refract = statistics_s2[1]

#Adding trendline for slope 1
z1 = np.polyfit(x_direct, y_direct, 1)
p1 = np.poly1d(z1)
plt.plot(x_direct,p1(x_direct),c = color1,linewidth=3, label=label_s1)

#Adding trendline for slope 2
z2 = np.polyfit(x_refract,y_refract, 1)
p2 = np.poly1d(z2)
plt.plot(x_refract,p2(x_refract),c = color2,linewidth=3,label=label_s2)

plt.axvline(x= kinkpos,c=(1,.2,1), color = 'r')

plt.legend(fontsize=15)

# =============================================================================
#         #Just to move eqns up and down
#         delta_ydirect = y_direct[3] - y_direct[0]
#         delta_xdirect = x_direct[3] - x_direct[0]
#         delta_yrefract = y_refract[3] - y_refract[0]
#         delta_xrefract = x_refract[3] - x_refract[0]
# =============================================================================

# Tranformations & Locations to plot eqn text
T1_direct = plt.gca().transData.transform_point((x_direct[0], y_direct[0]))
T2_direct = plt.gca().transData.transform_point((x_direct[-1], y_direct[-1]))
dy_direct = (T2_direct[1] - T1_direct[1])
dx_direct = (T2_direct[0] - T1_direct[0])
angle_direct = np.degrees(np.arctan2(dy_direct, dx_direct))
txt1 = np.array((np.median(x_direct), np.median(y_direct)))

T1_refract = plt.gca().transData.transform_point((x_refract[0], y_refract[0]))
T2_refract = plt.gca().transData.transform_point((x_refract[-1], y_refract[-1]))
dy_refract = (T2_refract[1] - T1_refract[1])
dx_refract = (T2_refract[0] - T1_refract[0])
angle_refract = np.degrees(np.arctan2(dy_refract, dx_refract))
txt2 = np.array((np.median(x_refract), np.median(y_refract)))

   # print(delta_ydirect, delta_xrefract, delta_ydirect, delta_xdirect)

#Plot text
plt.text(txt1[0], txt1[1], 'y = %0.3fx + %0.3f'%(m_dir,b_dir), fontsize=16, ha=hanchor, va=vanchor,
               rotation=angle_direct+rotn)

plt.text(txt2[0], txt2[1], 'y = %0.3fx + %0.3f'%(m_refract,b_refract), fontsize=16, ha=hanchor, va=vanchor,
               rotation=angle_refract+rotn)


import time
from typing import Iterable

from ..graph import Model, ModelStatus
from .interface import AbstractExecutionEngine
from .listener import DefaultListener

_execution_engine = None
_default_listener = None

__all__ = ['get_execution_engine', 'get_and_register_default_listener',
           'list_models', 'submit_models', 'wait_models', 'query_available_resources',
           'set_execution_engine', 'is_stopped_exec', 'budget_exhausted']

def set_execution_engine(engine: AbstractExecutionEngine) -> None:
    global _execution_engine
    if _execution_engine is None:
        _execution_engine = engine
    else:
        raise RuntimeError('Execution engine is already set.')

def get_execution_engine() -> AbstractExecutionEngine:
    global _execution_engine
    assert _execution_engine is not None, 'You need to set execution engine, before using it.'
    return _execution_engine

def get_and_register_default_listener(engine: AbstractExecutionEngine) -> DefaultListener:
    global _default_listener
    if _default_listener is None:
        _default_listener = DefaultListener()
        engine.register_graph_listener(_default_listener)
    return _default_listener

def submit_models(*models: Model) -> None:
    engine = get_execution_engine()
    get_and_register_default_listener(engine)
    engine.submit_models(*models)

def list_models(*models: Model) -> Iterable[Model]:
    engine = get_execution_engine()
    get_and_register_default_listener(engine)
    return engine.list_models()

def wait_models(*models: Model) -> None:
    get_and_register_default_listener(get_execution_engine())
    while True:
        time.sleep(1)
        left_models = [g for g in models if not g.status in (ModelStatus.Trained, ModelStatus.Failed)]
        if not left_models:
            break

def query_available_resources() -> int:
    engine = get_execution_engine()
    resources = engine.query_available_resource()
    return resources if isinstance(resources, int) else len(resources)

def is_stopped_exec(model: Model) -> bool:
    return model.status in (ModelStatus.Trained, ModelStatus.Failed)

def budget_exhausted() -> bool:
    engine = get_execution_engine()
    return engine.budget_exhausted()


import datetime
import csv

class Conversion():
    def __init__(self, input_file, output_file, merge_time):
        self.headers = ['time']
        self.rows = []

        log_file = open(input_file)
        time_cache = ''
        self.datas = {}
        for line in log_file:
            time_last, _, data = line.split("  | ")

            # print(time_last, type(time_last), data, type(data))

            if len(data)<5 : continue

            if time_cache == '' : time_cache = datetime.datetime.strptime(time_last,"%Y-%m-%d %H:%M:%S")

            # 计算时间差
            time_last_format = datetime.datetime.strptime(time_last,"%Y-%m-%d %H:%M:%S")
            if (time_last_format - time_cache).seconds > merge_time:
                self.datas = {**self.datas, **{'time':str(time_cache)}}
                self.put_row_into_rows()
                time_cache = time_last_format

            self.datas = {**self.datas, **eval(data)}

        self.datas = {**self.datas, **{'time':str(time_cache)}}
        self.put_row_into_rows()

        with open(output_file,'w', newline='')as f:
            f_csv = csv.writer(f)
            f_csv.writerow(self.headers)
            f_csv.writerows(self.rows)

    def put_row_into_rows(self):
        for key in self.datas:
            if key not in self.headers : self.headers.append(key)
        row = [''] * len(self.headers)
        for key in self.headers:
            if key in self.datas.keys(): row[self.headers.index(key)] = self.datas[key]
        self.rows.append(row)
        self.datas = {}

if __name__ == '__main__':
    import argparse

    parser = argparse.ArgumentParser(prog='log2csv')
    parser.add_argument('--log','-l', metavar='L', required=True, type=str, help='The HISTORY path')
    parser.add_argument('--csv','-c', metavar='O', type=str, help='Output CSV file path. defaule is \x1B[3mLOG_FILENAME.TXT\x1B[23m.csv')
    parser.add_argument('--merge','-m', metavar='M', type=int, default=0, help='Data with an interval of t seconds will be considered the same time. Defalut=0 Each row will be recorded separately')
    args = parser.parse_args()

    if args.csv == None : args.csv = args.log + '.csv'

    Conversion(args.log, args.csv, args.merge)


import DumpManager
import json

def jsonWriter(dump):

    with open("update.json", "a") as f:
        json.dump(dump,f, indent=3, separators=(',', ': '))
        print("update.json created")
        f.close()

def NewDump(dump):

    output=[]

    for check in dump:
        if check[1] == "You got a 200":
            productId = check[0]['productId']
            image = check[0]['image']

            if len(output) == 0:
                output.append({
                "productId": productId,
                "images": [image]
                })

            repeated = False

            for line in output:

                if line["productId"] == productId:
                    repeated = True
                    if len(line['images']) < 3:
                        line['images'].append(image)

            if repeated is False:
                output.append({
                "productId": productId,
                "images": [image]
                })

    return output


import torch
import torch.nn as nn

from models import LayerNorm2d

class SimpleGate(nn.Module):
  def forward(self, x):
    x1, x2 = x.chunk(2, dim=1)
    return x1 * x2

class NAFBlock(nn.Module):
  def __init__(self, c, DW_Expand=2, FFN_Expand=2, drop_out_rate=0.):
    super().__init__()
    dw_channel = c * DW_Expand
    self.conv1 = nn.Conv2d(in_channels=c, out_channels=dw_channel, kernel_size=1, padding=0, stride=1, groups=1, bias=True)
    self.conv2 = nn.Conv2d(in_channels=dw_channel, out_channels=dw_channel, kernel_size=3, padding=1, stride=1, groups=dw_channel,
                            bias=True)
    self.conv3 = nn.Conv2d(in_channels=dw_channel // 2, out_channels=c, kernel_size=1, padding=0, stride=1, groups=1, bias=True)

    # Simplified Channel Attention
    self.sca = nn.Sequential(
      nn.AdaptiveAvgPool2d(1),
      nn.Conv2d(in_channels=dw_channel // 2, out_channels=dw_channel // 2, kernel_size=1, padding=0, stride=1,
                groups=1, bias=True),
    )

    self.sg = SimpleGate()

    ffn_channel = FFN_Expand * c
    self.conv4 = nn.Conv2d(in_channels=c, out_channels=ffn_channel, kernel_size=1, padding=0, stride=1, groups=1, bias=True)
    self.conv5 = nn.Conv2d(in_channels=ffn_channel // 2, out_channels=c, kernel_size=1, padding=0, stride=1, groups=1, bias=True)

    self.norm1 = LayerNorm2d(c)
    self.norm2 = LayerNorm2d(c)

    self.dropout = nn.Dropout(drop_out_rate) if drop_out_rate > 0. else nn.Identity()

    self.beta = nn.Parameter(torch.zeros((1, c, 1, 1)), requires_grad=True)
    self.gamma = nn.Parameter(torch.zeros((1, c, 1, 1)), requires_grad=True)

  def forward(self, inp):
    x = self.norm1(inp)

    x = self.conv1(x)
    x = self.conv2(x)
    x = self.sg(x)
    x = x * self.sca(x)
    x = self.conv3(x)

    x = self.dropout(x)

    y = inp + x * self.beta

    x = self.conv4(self.norm2(y))
    x = self.sg(x)
    x = self.conv5(x)

    x = self.dropout(x)

    return y + x * self.gamma

class UNetLayer(nn.Module):
  def __init__(self, encoder, down, up, decoder, bottom=None):
    super(UNetLayer, self).__init__()
    self.encoder = encoder
    self.down = down
    self.up = up
    self.decoder = decoder
    self.bottom = (lambda x: bottom(x)) if bottom else (lambda x: x)
  def forward(self, x):
    x1 = self.encoder(x)
    x = self.up(self.bottom(self.down(x1)))
    return self.decoder(x + x1)

# align = 16 or 2^len(enc_blk_nums)
class NAFNet(nn.Module):

  def __init__(self, img_channel=3, width=16, middle_blk_num=1, enc_blk_nums=[], dec_blk_nums=[]):
    super().__init__()

    self.intro = nn.Conv2d(in_channels=img_channel, out_channels=width, kernel_size=3, padding=1, stride=1, groups=1,
                          bias=True)
    self.ending = nn.Conv2d(in_channels=width, out_channels=img_channel, kernel_size=3, padding=1, stride=1, groups=1,
                          bias=True)

    self.layers = nn.ModuleList()

    chan = width << len(enc_blk_nums)
    m = nn.Sequential(
        *(NAFBlock(chan) for _ in range(middle_blk_num))
      )
    self.layers.append(m)
    for nEnc, nDec in zip(enc_blk_nums[::-1], dec_blk_nums):
      chan = chan >> 1
      m = UNetLayer(
        nn.Sequential(
          *(NAFBlock(chan) for _ in range(nEnc))
        ),
        nn.Conv2d(chan, chan << 1, 2, 2),
        nn.Sequential(
          nn.Conv2d(chan << 1, chan << 2, 1, bias=False),
          nn.PixelShuffle(2)
        ),
        nn.Sequential(
          *(NAFBlock(chan) for _ in range(nDec))
        ),
        m
      )
      self.layers.insert(0, m)

  def forward(self, inp):
    x = self.intro(inp)
    x = self.layers[0](x)
    x = self.ending(x)
    return x + inp


from name_to_job import *
from job_to_salary import *
from keras.models import load_model
import tensorflow.contrib.eager as tfe
from tensorflow.python.eager.context import context, EAGER_MODE, GRAPH_MODE
def switch_to(mode):
    ctx = context()._eager_context
    ctx.mode = mode
    ctx.is_eager = mode == EAGER_MODE

def main():
    last_name = input('Enter your family name: ')
    print('Family name:  ' + last_name)
    first_name = input('Enter your given name: ')
    print('Given name:  ' + first_name)
    input_tensor, target_tensor, inp_lang, targ_lang, max_length_inp, max_length_targ = load_sequence_data()
    # Creating training and validation sets using an 80-20 split
    input_tensor_train, input_tensor_val, target_tensor_train, target_tensor_val = train_test_split(input_tensor, target_tensor, test_size=0.2)
    print(len(input_tensor_train))
    print(len(target_tensor_train))
    print(len(input_tensor_val))
    print(len(target_tensor_val))

    BUFFER_SIZE = len(input_tensor_train)
    BATCH_SIZE = 64
    N_BATCH = BUFFER_SIZE//BATCH_SIZE
    embedding_dim = 256
    units = 1024
    vocab_inp_size = len(inp_lang.word2idx)
    vocab_tar_size = len(targ_lang.word2idx)

    dataset = tf.data.Dataset.from_tensor_slices((input_tensor_train, target_tensor_train)).shuffle(BUFFER_SIZE)
    dataset = dataset.batch(BATCH_SIZE, drop_remainder=True)
    encoder = Encoder(vocab_inp_size, embedding_dim, units, BATCH_SIZE)
    decoder = Decoder(vocab_tar_size, embedding_dim, units, BATCH_SIZE)
    optimizer = tf.train.AdamOptimizer()
    # checkpoint_dir = './training_checkpoints'
    checkpoint_dir = './save_model'
    checkpoint = tf.train.Checkpoint(optimizer=optimizer,
                                     encoder=encoder,
                                     decoder=decoder)
    checkpoint.restore(tf.train.latest_checkpoint(checkpoint_dir))
    job = translate(last_name, first_name, encoder, decoder, inp_lang, targ_lang, max_length_inp, max_length_targ)
    switch_to(GRAPH_MODE)
    print('predicted job: ', job[:-6])
    salary_model = load_model('save_model/model_job_to_salary.h5')
    X1 = np.array([job[:-6]])
    word_to_vec_map = load_glove_file()
    X1_train_embedding = np.array([sentence_to_avg(x, word_to_vec_map) for x in X1])
    print('Predicted salary: ', salary_model.predict(X1_train_embedding)[0])

if __name__ == '__main__':
    main()


"""
URL configuration for djangoali project.

The `urlpatterns` list routes URLs to views. For more information please see:
    https://docs.djangoproject.com/en/4.2/topics/http/urls/
Examples:
Function views
    1. Add an import:  from my_app import views
    2. Add a URL to urlpatterns:  path('', views.home, name='home')
Class-based views
    1. Add an import:  from other_app.views import Home
    2. Add a URL to urlpatterns:  path('', Home.as_view(), name='home')
Including another URLconf
    1. Import the include() function: from django.urls import include, path
    2. Add a URL to urlpatterns:  path('blog/', include('blog.urls'))
"""
from django.contrib import admin
from django.urls import path,include
from smallali.views import index_page, about_page, under_about, done
from smallali import views
import mimetypes

admin.site.site_header = "Моя админка"
admin.site.index_title = 'Моя супер-пупер админка'

urlpatterns = [
    path('admin/', admin.site.urls),
    path('', index_page),
    path('done', done),
    path('movies/<str:category>/<str:ingredient>', about_page, name = "about-name"),
    path('movies/<str:category>/', under_about, name = 'under-about-name'),
    path("horoscope/<int:znak>/", views.forallznakov_number),
    path("horoscope/<str:znak>/", views.forallznakov, name = "name-number"),
    path('horoscope/meri/lao', views.lao),
    path('__debug__/', include('debug_toolbar.urls')),
]


from flask import Flask, render_template, request, redirect, session

app = Flask(__name__)
app.secret_key = 'secret_key'

@app.route("/")
def index():
    session.clear()
    return render_template("index.html")

@app.route("/destroy_session")
def destroy_session():
    session.clear()
    return redirect("/")

@app.route("/get_results", methods=["post"])
def get_results():
    session['name'] = request.form['name']
    session['location'] = request.form['location']
    session['language'] = request.form['language']
    session['checkbox'] = request.form.getlist('checkbox')
    session['radio_btn'] = request.form.getlist('radio_btn')
    session['comments'] = request.form['comments']
    print(request.form)
    return redirect("/results")

@app.route("/results")
def results():
    name = session['name']
    location = session['location']
    language = session['language']
    checkbox = session['checkbox']
    radio_btn = session['radio_btn']
    comments = session['comments']
    return render_template("results.html", name=name, location=location, language=language, comments=comments, checkbox=checkbox, radio_btn=radio_btn)

if __name__ == "__main__":
    app.run(debug=True)


import torch
from torch import nn
import numpy as np
import bilateral_slice

def conv(in_channels, out_channels, kernel, stride=1, norm=False, bias=True, relu=True,
         weights_init=None, bias_init=None):
    layers = [nn.Conv2d(in_channels, out_channels, kernel, stride=stride, padding=(kernel - 1) // 2, bias=bias)]
    assert not (bias_init is not None and not bias)
    if weights_init is not None:
        if isinstance(weights_init, np.ndarray):
            weights_init = torch.from_numpy(weights_init)
        layers[0].weight.data = weights_init
    if bias_init is not None:
        if isinstance(bias_init, np.ndarray):
            bias_init = torch.from_numpy(bias_init)
        layers[0].bias.data = bias_init
    if norm:
        layers.append(nn.InstanceNorm2d(out_channels, affine=True))
    if relu:
        layers.append(nn.LeakyReLU(inplace=True))
    return nn.Sequential(*layers)

class ConvBlock(nn.Module):
    def __init__(self, in_channels, out_channels, kernel, stride=1, norm=False, bias=True, relu=True,
        weights_init=None, bias_init=None):
        super().__init__()
        # conv
        self.conv = nn.Conv2d(in_channels, out_channels, kernel, stride=stride, padding=(kernel - 1) // 2, bias=bias)
        assert not (bias_init is not None and not bias)
        if weights_init is not None:
            if isinstance(weights_init, np.ndarray):
                weights_init = torch.from_numpy(weights_init)
            self.conv.weight.data = weights_init
        if bias_init is not None:
            if isinstance(bias_init, np.ndarray):
                bias_init = torch.from_numpy(bias_init)
            self.conv.bias.data = bias_init
        # norm & relu
        self.norm = None
        self.relu = None
        if norm:
            # self.norm = nn.InstanceNorm2d(out_channels//2, affine=True)
            self.norm = nn.InstanceNorm2d(out_channels, affine=True)
        if relu:
            self.relu = nn.LeakyReLU(inplace=True)

    def forward(self, x):
        out = self.conv(x)
        # Half Instance Norm (HIN)
        if self.norm:
            # out_1, out_2 = torch.chunk(out, 2, dim=1)
            # out = torch.cat([self.norm(out_1), out_2], dim=1)
            out = self.norm(out)
        if self.relu:
            out = self.relu(out)
        return out

def fc(in_features, out_features, norm=False, relu=True):
    layers = [nn.Linear(in_features, out_features)]
    if norm:
        layers.append(nn.BatchNorm1d(out_features))
    if relu:
        layers.append(nn.ReLU(inplace=True))
    return nn.Sequential(*layers)

class BilateralSliceFunction(torch.autograd.Function):
    @staticmethod
    def forward(ctx, bilateral_grid, guide, input, has_offset):
        ctx.save_for_backward(bilateral_grid, guide, input)
        ctx.has_offset = has_offset
        return bilateral_slice.forward(bilateral_grid, guide, input, has_offset)

    @staticmethod
    def backward(ctx, grad):
        bilateral_grid, guide, input = ctx.saved_variables
        d_grid, d_guide, d_input = bilateral_slice.backward(grad,
                                                            bilateral_grid,
                                                            guide,
                                                            input,
                                                            ctx.has_offset)
        return d_grid, d_guide, d_input, None

class BilateralSlice(torch.nn.Module):
    def __init__(self, has_offset):
        super().__init__()
        self.has_offset = has_offset

    def forward(self, bilateral_grid, guide, input):
        return BilateralSliceFunction(bilateral_grid, guide, input, self.has_offset)


"""Revocation utilities."""

import re
from typing import Sequence

from ..core.profile import Profile

REVOCATION_EVENT_PREFIX = "acapy::REVOCATION::"
EVENT_LISTENER_PATTERN = re.compile(f"^{REVOCATION_EVENT_PREFIX}(.*)?$")
REVOCATION_REG_INIT_EVENT = "REGISTRY_INIT"
REVOCATION_REG_ENDORSED_EVENT = "REGISTRY_ENDORSED"
REVOCATION_ENTRY_ENDORSED_EVENT = "ENTRY_ENDORSED"
REVOCATION_ENTRY_EVENT = "SEND_ENTRY"
REVOCATION_PUBLISHED_EVENT = "published"
REVOCATION_CLEAR_PENDING_EVENT = "clear-pending"

async def notify_revocation_reg_init_event(
    profile: Profile,
    issuer_rev_id: str,
    create_pending_rev_reg: bool = False,
    endorser_connection_id: str = None,
):
    """Send notification for a revocation registry init event."""
    meta_data = {
        "context": {
            "issuer_rev_id": issuer_rev_id,
        },
        "processing": {"create_pending_rev_reg": create_pending_rev_reg},
    }
    if endorser_connection_id:
        meta_data["endorser"] = {"connection_id": endorser_connection_id}
    topic = f"{REVOCATION_EVENT_PREFIX}{REVOCATION_REG_INIT_EVENT}::{issuer_rev_id}"
    await profile.notify(topic, meta_data)

async def notify_revocation_entry_event(
    profile: Profile, issuer_rev_id: str, meta_data: dict
):
    """Send notification for a revocation registry entry event."""
    topic = f"{REVOCATION_EVENT_PREFIX}{REVOCATION_ENTRY_EVENT}::{issuer_rev_id}"
    await profile.notify(topic, meta_data)

async def notify_revocation_reg_endorsed_event(
    profile: Profile, rev_reg_id: str, meta_data: dict
):
    """Send notification for a revocation registry endorsement event."""
    topic = f"{REVOCATION_EVENT_PREFIX}{REVOCATION_REG_ENDORSED_EVENT}::{rev_reg_id}"
    await profile.notify(topic, meta_data)

async def notify_revocation_entry_endorsed_event(
    profile: Profile, rev_reg_id: str, meta_data: dict
):
    """Send notification for a revocation registry entry endorsement event."""
    topic = f"{REVOCATION_EVENT_PREFIX}{REVOCATION_ENTRY_ENDORSED_EVENT}::{rev_reg_id}"
    await profile.notify(topic, meta_data)

async def notify_revocation_published_event(
    profile: Profile,
    rev_reg_id: str,
    crids: Sequence[str],
):
    """Send notification of credential revoked as issuer."""
    topic = f"{REVOCATION_EVENT_PREFIX}{REVOCATION_PUBLISHED_EVENT}::{rev_reg_id}"
    await profile.notify(topic, {"rev_reg_id": rev_reg_id, "crids": crids})

async def notify_pending_cleared_event(
    profile: Profile,
    rev_reg_id: str,
):
    """Send notification of credential revoked as issuer."""
    topic = f"{REVOCATION_EVENT_PREFIX}{REVOCATION_CLEAR_PENDING_EVENT}::{rev_reg_id}"
    await profile.notify(topic, {"rev_reg_id": rev_reg_id})