from math import ceil

import config
from kivy.animation import Animation
from kivy.core.window import Window
from kivy.factory import Factory
from kivy.properties import (ObjectProperty, BooleanProperty)

class HoverBehavior(object):
    """Hover behavior.
    :Events:
        `on_enter`
            Fired when mouse enter the bbox of the widget.
        `on_leave`
            Fired when the mouse exit the widget
    """

    hovered = BooleanProperty(False)
    border_point = ObjectProperty(None)
    '''Contains the last relevant point received by the Hoverable. This can
    be used in `on_enter` or `on_leave` in order to know where was dispatched the event.
    '''

    def __init__(self, **kwargs):
        self.register_event_type('on_enter')
        self.register_event_type('on_leave')
        Window.bind(mouse_pos=self.on_mouse_pos)
        super(HoverBehavior, self).__init__(**kwargs)

    def on_mouse_pos(self, *args):
        if not self.get_root_window():
            return  # do proceed if I'm not displayed <=> If have no parent
        pos = args[1]
        # Next line to_widget allow to compensate for relative layout
        inside = self.collide_point(*self.to_widget(*pos))
        if self.hovered == inside:
            # We have already done what was needed
            return
        self.border_point = pos
        self.hovered = inside
        if inside:
            self.dispatch('on_enter')
        else:
            self.dispatch('on_leave')

    def on_enter(self):
        pass

    def on_leave(self):
        pass

Factory.register('HoverBehavior', HoverBehavior)

def subregion(px, py, r_x, r_y):  # Поиск нужного тайла среди выбранных
    rx = int(r_x)
    ry = int(r_y)
    foo = px - py
    bar = px + py
    # print(f'{px - py:.3f}, {px + py:.3f} | {px, py} | {foo, bar}')
    if foo < 0 and bar > 1:  # Top
        return rx, ry
    elif foo < 0 and bar < 1:  # Left
        if r_y > 0:
            if py > 0.5:
                return rx - 1, ry + 1
            return rx - 1, ry
        else:
            return None
    elif foo > 0 and bar > 1:  # Right
        if r_y > 0:
            if py > 0.5:
                return rx, ry + 1
            return rx, ry
        else:
            return None
    elif foo > 0 and bar < 1:  # Bottom
        if r_y < 0 or py == 0:
            return rx, ry
        return rx, ry + 1

def world_to_tile(pos):  # TODO: добавить правильный зум
    TILE_WIDTH = config.TILE_WIDTH
    TILE_HEIGHT = config.TILE_HEIGHT
    mw = config.MW
    mh = config.MH
    x, y = pos
    y -= 10 * config.SCALING  # height of a tile
    r_x = x / (TILE_WIDTH * config.SCALING)
    r_y = mh - (y / (TILE_HEIGHT * config.SCALING)) * 2
    if r_x >= 0 and r_y >= -1:
        MouseMapX = x % (TILE_WIDTH * config.SCALING)
        MouseMapY = y % (TILE_HEIGHT * config.SCALING)
        map_x = MouseMapX / (TILE_WIDTH * config.SCALING)
        map_y = MouseMapY / (TILE_HEIGHT * config.SCALING)
        result = subregion(map_x, map_y, r_x, r_y)
        if result is not None:
            if -1 < result[0] < mw and -1 < result[1] < mh:
                return result

def tile_to_world(pos):
    column_index, row_index = pos
    if row_index % 2 == 0:
        x = column_index * (config.TILE_WIDTH * config.SCALING)
        y = (config.MH - row_index - 1) * ((config.TILE_HEIGHT * config.SCALING) / 2)
    else:
        x = column_index * (config.TILE_WIDTH * config.SCALING) + ((config.TILE_WIDTH * config.SCALING) / 2)
        y = (config.MH - row_index - 1) * ((config.TILE_HEIGHT * config.SCALING) / 2)
    return x, y

def tile_to_world_adv(pos):
    column_index, row_index = pos
    if row_index % 2 == 0:
        x = column_index * (config.TILE_WIDTH * config.SCALING)
        y = (config.MH - row_index - 1) * ((config.TILE_HEIGHT * config.SCALING) / 2)
    else:
        x = column_index * (config.TILE_WIDTH * config.SCALING) + ((config.TILE_WIDTH * config.SCALING) / 2)
        y = (config.MH - row_index - 1) * ((config.TILE_HEIGHT * config.SCALING) / 2)
    return x, y + config.TILE_Z

def set_screen(name_screen, sm):
    sm.current = name_screen

def change_view(obj, root, quick=False):  # Передвижение камеры к городам
    if quick:
        root.pos = -(obj.pos[0] - Window.width / 2 + obj.width / 2), -(obj.pos[1] - Window.height / 2 + obj.height / 2)
    else:
        anim = Animation(
            pos=(-(obj.pos[0] - Window.width / 2 + obj.width / 2), -(obj.pos[1] - Window.height / 2 + obj.height / 2)),
            duration=.5)
        anim.start(root)

def change_current_city(city):
    config.current_city = city

def to_tile_center(pos):
    return pos[0] + config.TILE_WIDTH / 2, pos[1] + config.TILE_HEIGHT / 2

def get_moves(pos, frad):  # Список доступных ходов
    moves_list = []
    scrx, scry = pos
    irad = ceil(frad)
    linx, liny = screen_to_linear(scrx, scry)
    for dx in range(-irad, irad + 1):
        for dy in range(-irad, irad + 1):
            # print(f'{dx, dy} | {dx * dx}, {dy * dy}, {frad * frad}')
            if dx * dx + dy * dy <= frad * frad:
                move = linear_to_screen(linx + dx, liny + dy)
                if move != pos:
                    moves_list.append(move)
                # print((linx + dx, liny + dy))
    return moves_list

def screen_to_linear(scrx, scry):
    linx = scrx - scry // 2
    liny = -scrx - (scry + 1) // 2
    return linx, liny

def linear_to_screen(linx, liny):
    rotx = (linx - liny) // 2
    roty = (-linx - liny)
    return rotx, roty

def bring_to_front():
    for widget in config.map_gui_list:
        parent = widget.parent
        if parent is not None:
            parent.remove_widget(widget)
            parent.add_widget(widget)

def widget_to_front(widget):
    parent = widget.parent
    parent.remove_widget(widget)
    parent.add_widget(widget)

def tile_to_world_center(pos):
    column_index, row_index = pos
    if row_index % 2 == 0:
        x = column_index * (config.TILE_WIDTH * config.SCALING)
        y = (config.MH - row_index - 1) * ((config.TILE_HEIGHT * config.SCALING) / 2)
    else:
        x = column_index * (config.TILE_WIDTH * config.SCALING) + ((config.TILE_WIDTH * config.SCALING) / 2)
        y = (config.MH - row_index - 1) * ((config.TILE_HEIGHT * config.SCALING) / 2)
    return x + 5, y + 7.5

def generate_move_map(x, y, moves):
    rm = 1  # 'условный массив'
    if rm < moves:
        # rm = moves
        _y = -1
        for _x in range(-1, 1):
            _x += 1
            print(_x)


import numpy
import pandas as pd
import pickle
from sklearn.preprocessing import MinMaxScaler

def predict_data(data):
    timestamp = pd.DataFrame(list(data.values('timestamp')))
    # data = pd.DataFrame(list(data.values('temperature', 'humidity',
    #                                      'light_intensity', 'ph', 'soil_moisture',
    #                                      'soil_temperature', 'water_temperature', 'flow_intensity')))
    data = pd.DataFrame(list(data.values('temperature')))
    data = data.astype('float32')

    timestamp.set_index('timestamp', inplace=True)
    scaler = MinMaxScaler(feature_range=(0, 1))
    data = scaler.fit_transform(data)

    look_back = 7
    X_data, Y_data = create_dataset(data, look_back)
    X_new = numpy.reshape(X_data, (X_data.shape[0], 1, X_data.shape[1]))

    model = load_model()

    predictions = model.predict(X_new)
    # dates = data.index[-len(predictions):]

    # temperature_predictions = predictions[:, 0]
    # humidity_predictions = predictions[:, 1]

    temperature_predictions = scaler.inverse_transform(data)
    # humidity_predictions = scaler.inverse_transform(humidity_predictions.reshape(-1, 1)).flatten()

    result = {
        'temperature': temperature_predictions,
        # 'humidity': humidity_predictions,
    }

    return result

def load_model():
    with open('ai_models/time-series-prediction-model.pkl', 'rb') as file:
        model = pickle.load(file)
    return model

def create_dataset(dataset, look_back=1):
    dataX, dataY = [], []
    for i in range(len(dataset)-look_back-1):
        a = dataset[i:(i+look_back), 0]
        dataX.append(a)
        dataY.append(dataset[i + look_back, 0])
    return numpy.array(dataX), numpy.array(dataY)


import pandas as pd
import numpy as np
from sklearn.preprocessing import LabelEncoder
import random
from sklearn.ensemble import RandomForestRegressor
from sklearn.ensemble import GradientBoostingClassifier
from sklearn.model_selection import GridSearchCV, train_test_split
from sklearn.metrics import r2_score
from sklearn.preprocessing import LabelEncoder
from sklearn import preprocessing

##------read csv file (train and test)
train = pd.read_csv("/Users/song/Downloads/trainnew.csv")
test = pd.read_csv("/Users/song/Downloads/test.csv")

#train.head(10)
#test.head(10)

##------divide features into categorical and numerical
##------convert categorical variables into numeric by using labelEncoder
le = preprocessing.LabelEncoder()
cat_vars1=train['penalty']
cat_vars2=test['penalty']
train['penalty'] = le.fit_transform(cat_vars1.tolist())
test['penalty'] = le.fit_transform(cat_vars2.tolist())

##------feature engineering
'''
X = np.array(X[['penalty','l1_ratio','alpha','max_iter','random_state', 'n_jobs',
                   'n_samples','n_features','n_classes','n_clusters_per_class',
                   'n_informative','flip_y','scale']])
Y = np.array(train['time'])
X_train, X_test, y_train, y_test = train_test_split(X, Y, test_size=0.3,random_state=0)
#find importance
feat_labels = train.columns[1:]
forest = RandomForestRegressor(n_estimators = 10000, random_state = 0, n_jobs=-1)
forest.fit(X_train, y_train)
importances = forest.feature_importances_
indices = np.argsort(importances)[::-1]
for f in range(X_train.shape[1]):
    print("%2d) %-*s %f" % (f + 1, 30, feat_labels[f], importances[indices[f]]))
'''
##multiply some related features
train['other']=train['max_iter']*train['n_samples']*train['n_features']
test['other']=test['max_iter']*test['n_samples']*test['n_features']
##square features which impotances are high
train['scale']=np.square(train['scale'])
test['scale']=np.square(test['scale'])
#train['max_iter']=np.square(train['max_iter'])
#test['max_iter']=np.square(test['max_iter'])
#train['n_features']=np.square(train['n_features'])
#test['n_features']=np.square(test['n_features'])
train['n_classes']=np.square(train['n_classes'])
test['n_classes']=np.square(test['n_classes'])
##replace the nagtive value with positive one
##the number of n_jobs also can be given as exponent of 2
train['n_jobs']=train['n_jobs'].replace([-1],[16])
test['n_jobs']=test['n_jobs'].replace([-1],[16])

##------model training
X = train[['other','n_jobs','n_classes',
           'n_clusters_per_class','n_informative','flip_y','scale','penalty']]

Y = train['time']

Z = test[['other', 'n_jobs','n_classes','n_clusters_per_class',
                   'n_informative','flip_y','scale','penalty']]

##------adjust parameters
'''
from sklearn.ensemble import RandomForestRegressor
from sklearn import metrics
for i in range(0,50):
    X_train, X_test, y_train, y_test = train_test_split(X, Y, test_size=0.25, random_state=i)
    random.seed(1000)
    rf = RandomForestRegressor(max_depth=10,n_estimators=200)
    rf.fit(X_train, y_train)
    m=rf.predict(X_test)
    n=y_test
    print(i,metrics.mean_squared_error(n,m))

for i in range(2,70):
    X_train, X_test, y_train, y_test = train_test_split(X, Y, test_size=0.25, random_state=7)
    random.seed(1000)
    rf = RandomForestRegressor(max_depth=i,n_estimators=500)
    rf.fit(X_train, y_train)
    m=rf.predict(X_test)
    n=y_test
    print(i,metrics.mean_squared_error(n,m))

p=[100,200,300,400,500,600]
for i in p:
    X_train, X_test, y_train, y_test = train_test_split(X, Y, test_size=0.25, random_state=7)
    random.seed(1000)
    rf = RandomForestRegressor(max_depth=19,n_estimators=i)
    rf.fit(X_train, y_train)
    m=rf.predict(X_test)
    n=y_test
    print(metrics.mean_squared_error(n,m))

p=[0.2,0.25,0.3,0.35,0.4]
for i in p:
    X_train, X_test, y_train, y_test = train_test_split(X, Y, test_size=i, random_state=7)
    random.seed(1000)
    rf = RandomForestRegressor(max_depth=19,n_estimators=400)
    rf.fit(X_train, y_train)
    m=rf.predict(X_test)
    n=y_test
    print(metrics.mean_squared_error(n,m))
'''

'''
from sklearn.model_selection import GridSearchCV, train_test_split
from sklearn.ensemble import RandomForestRegressor
X_train, X_test, y_train, y_test = train_test_split(X, Y, test_size=0.25,random_state=1)

depths = [1,10,20,30,40,50]
errors1 = []

for depth in depths:
    forest = RandomForestRegressor(max_depth=depth)
    forest.fit(X_train, y_train)

    z1 = forest.predict(X_test)
    error1 = np.sqrt(np.sum((y_test - z1) * (y_test - z1)) / (1.0 * len(y_test)))
    errors1.append(error1)
print(errors1)
'''

X_train, X_test, y_train, y_test = train_test_split(X, Y, test_size=0.25, random_state=1)
random.seed(1000)
rf = RandomForestRegressor(max_depth=11,n_estimators=600)
rf.fit(X_train, y_train)

m=rf.predict(X_test)
n=y_test
#metrics.mean_squared_error(n,m)

##------prediction
final_status = rf.predict(Z)
final_status=pd.DataFrame(final_status, columns=['time'])
final_status.index.name='id'
final_status.to_csv('test.csv')


from django.urls import path
from . import views

urlpatterns = [
    path('login/', views.loginPage, name='login'),
    path('signup/', views.signupPage, name='signup'),
    path('logout/', views.logoutPage, name='logout'),
    path('profile/<str:pk>', views.userProfile, name='user-profile'),
    path('', views.home, name='home'),
    path('patient/<str:pk>/', views.patient, name='patient'),
    path('create-patient/', views.createPatient, name='create-patient'),
    path('update-patient/<str:pk>/', views.updatePatient, name='update-patient'),
    path('delete-patient/<str:pk>/', views.deletePatient, name='delete-patient'),
    path('update-user/', views.updateUser, name='update-user'),
]


#! /usr/bin/env python

# General Dependancies
import rospy
from geometry_msgs.msg import TransformStamped, Pose, PoseArray, Quaternion
import tf.transformations as tft
import tf2_ros
import math
from std_srvs.srv import Trigger, TriggerResponse
from radiation_msgs.msg import DoseRate

class gammaSensor():
    def __init__(self):
        # Get parameters
        self.node_name = rospy.get_name()
        self.sensor_frame = rospy.get_param(self.node_name +"/sensor_frame", "gamma_sensor")
        rospy.set_param(self.node_name + "/sensor_frame", self.sensor_frame)
        self.sources_topic = rospy.get_param("~sources_topic", "gamma_sources_server/poses")
        self.source_intensities = rospy.get_param("gamma_sources_server/intensity")
        self.rate = rospy.Rate(1.0) # Hz
        self.global_frame = "map"

        self.tfBuffer = tf2_ros.Buffer()
        self.listener = tf2_ros.TransformListener(self.tfBuffer)

        self.source_poses = PoseArray()
        self.intensity_msg = DoseRate()

        rospy.Subscriber(self.sources_topic, PoseArray, self.posescb)  # Get positions of gamma sources

        s = rospy.Service(self.node_name + "/update_frame", Trigger, self.service_handler) # Method to trigger a change of TF frame on-the-fly

        self.intensity_publisher = rospy.Publisher(self.node_name + "/intensity", DoseRate, queue_size=1, latch=True)

        # Single TF call
        self.transform = TransformStamped()
        self.calcTF()

        rospy.wait_for_message(self.sources_topic, PoseArray, timeout=5)

        while not rospy.is_shutdown():
            # Calculate TF from sensor frame to world frame
            self.calcTF()
            # Calculate distance to each source
            dists = self.getDistances()
            # Convert distance to intensity
            total_intensity = self.dist2flux(dists)
            # Publish overall intensity
            self.publish(total_intensity)
            self.rate.sleep()

    def service_handler(self, request):
        self.sensor_frame = rospy.get_param("~sensor_frame")
        return TriggerResponse(success = True, message = self.sensor_frame)

    def posescb(self, arraymsg):
        # Update poses of sources in global frame (if moved)
        self.source_poses = arraymsg
        self.global_frame = arraymsg.header.frame_id

    def calcTF(self):
        try:
            # Target Frame, Source Frame, Time, Timeout Duration
            self.transform = self.tfBuffer.lookup_transform(self.sensor_frame, self.global_frame, rospy.Time())
        except (tf2_ros.LookupException, tf2_ros.ConnectivityException, tf2_ros.ExtrapolationException):
            self.rate.sleep()

    def getDistances(self):
        dists = []
        for pose in self.source_poses.poses:
            v = [self.transform.transform.translation.x + pose.position.x, self.transform.transform.translation.y + pose.position.y, self.transform.transform.translation.z + pose.position.z]
            dist = tft.vector_norm(v)
            dists.append(dist)
        return dists

    def dist2flux(self, distances):
        R = 1E-2
        normalisation = 2/( 1- ( 1 / math.sqrt(1 + R**2) ) )
        # Double check intensities
        self.source_intensities = rospy.get_param("gamma_sources_server/intensity")

        I_Total = 0

        for i, D in enumerate(distances):
            I_D = 0.5 * self.source_intensities[i] * ( 1- ( D / math.sqrt(D**2 + R**2) ) ) * normalisation
            I_Total += I_D

        return I_Total

    def publish(self, intensity_value):
        self.intensity_msg.header.stamp = rospy.Time.now()
        self.intensity_msg.header.frame_id = self.sensor_frame
        self.intensity_msg.radiation_type = 4
        self.intensity_msg.units = 0
        self.intensity_msg.integration_time = 1.0
        self.intensity_msg.rate = intensity_value
        self.intensity_publisher.publish(self.intensity_msg)

if __name__ == "__main__":
    try:
        rospy.init_node("gamma_sensor", anonymous=True)
        rospy.loginfo("Starting Gamma Sensor")
        s = gammaSensor()
        rospy.spin()
    except rospy.ROSInterruptException: pass


#!/usr/bin/python
# -*- coding: utf-8 -*-
"""Tools and plugins for Veusz, providing Misura Thermal Analysis functionality"""
from misura.canon.logger import get_module_logging
logging = get_module_logging(__name__)
import veusz.plugins as plugins
import numpy
import scipy
import veusz.document as document
import numpy as np
from scipy import interpolate
from scipy.optimize import fmin

class SimFlex(object):

    """Simulation processing."""

    def __init__(self, layers, L0, minT, maxT, N=1000):
        """`layers`: a list containing layers description dictionaries,
                        ordered from the uppermost to the bottom layer
                `L0`: initial length of the composite
                `minT`: simulation starting temperature
                `maxT`: simulation ending temperature
                `N`: number of points in the simulated curve
        """
        self.layers = layers
        self.L0 = L0
        self.minT = minT
        self.maxT = maxT
        self.N = N
        step = int((maxT - minT) / N) + 1
        self.vT = np.arange(minT, maxT, step)
        """Temperatures array"""
        # Middle position:
        hs = [mat['h'] for mat in layers]
        hm = np.sum(hs) / len(hs)  # mean position

        # layer positions
        hpos = [0] + [hs[i - 1] + hs[i] / 2. for i in range(1, len(layers))]

        self.hpos = np.array(hpos) - hm
        """Layer positions array, relative to the middle height of the composite"""

    def equilibrium_length(self, T):
        """Calculates the equilibrium length of the composite at a certain temperature.
        It is the average of all layers ideal length (uncoupled length),
        weighted by each layer's elastic factor Eh"""
        n = 0
        d = 0
        for i, mat in enumerate(self.layers):
            # Ideal equilibrium length for mat layer
            mL = self.L0 * (1 + mat['fL'](T) / 100)
            mat['mL'] = mL
            # Weight that length by the elastic factor (E*h) of the layer
            n += mL * mat['Eh']
            # Add layer's Eh to the denominator of the average
            d += mat['Eh']
        return n / d

    def elastic_energy(self, inp, L):  # r
        """Calculates the elastic energy of the composite given a curvature radius and equilibrium length.
        `inp`: input paremeters list, [ curvature radius ]
        `L`  : equilibrium length of the composite
        The displacement of each layer towards its uncoupled length is calculated,
        and the elastic energies associated to all these displacements is summed up to the
        returning result of the function.
         """
        r = inp[0]
        if abs(r) < 1:
            r = 1. * np.sign(r)
        # Calculate equilibrium length
        U = 0
        ar = abs(r)
        sgn = np.sign(r)
        for i, mat in enumerate(self.layers):
            # Length on curvature
            LR = (ar - sgn * self.hpos[i]) * L / ar
            # Displacement from equilibrium length
            dL = LR - mat['mL']
            # Add to energy
            u = mat['Eh'] * (dL**2)
            # print 'dL',mat['name'],dL,u
            U += u * np.sign(dL)
        # print r,abs(U)
        return abs(U)

    def simulation(self):
        """Perform flexure simulation. Returns:
        vT, flex, length
        vT: temperature array
        flex: flexure values array, as a function of vT
        length: composite equilibrium length array, as a function of vT"""
        curv = [100]
        length = []
        for T in self.vT:
            L = self.equilibrium_length(T)
            length.append(L)
            # Explore positive and negative curvature energies
            ePos = self.elastic_energy([10000.], L)
            eNeg = self.elastic_energy([-10000.], L)
            # Choose a starting curvature with the most favorable sign
            r0 = abs(curv[-1])
            if r0 < 1:
                r0 = 100
            r0 = r0 * np.sign(eNeg - ePos)
            # Minimize energy
            xopt, fopt, iter, funcalls, warnflag = fmin(self.elastic_energy, [r0], args=(L,),
                                                        disp=True, full_output=True)
            logging.debug(T, xopt, fopt)
            curv.append(xopt[0])
        curv.pop(0)
        length = np.array(length)
        curv = np.array(curv)
        # Calculate vertical displacement of the middle point (flexure)
        flex = (abs(curv) - np.sqrt(curv**2 - (length / 2)**2)) * np.sign(curv)
        logging.debug(self.vT, flex, length)
        return self.vT, flex, length

class SimFlexPlugin(plugins.DatasetPlugin):

    """Simlate a flexure curve starting from up to three dilatation curves"""
    # tuple of strings to build position on menu
    menu = ('Misura', 'Flex simulation')
    # internal name for reusing plugin later
    name = 'SimFlex'
    # string which appears in status bar
    description_short = 'Simulate a flexure curve from two dilatation curves'

    # string goes in dialog box
    description_full = ('Simulate a flexure curve from two dilatation curves')

    def __init__(self, ds0='', T0='', E0=200, h0=2,
                 ds1='', T1='', E1=300, h1=1,
                 ds2='', T2='', E2=400, h2=5,
                 L0=80, num=100, ds_out='simflex'):
        """Define input fields for plugin."""
        self.fields = [
            plugins.FieldDataset(
                'ds0', 'Top layer (L0) dilatation curve', default=ds0),
            plugins.FieldDataset(
                'T0', 'Top layer (L0) temperature curve', default=T0),
            plugins.FieldFloat('E0', 'L0 Elasticity Modulus', default=E0),
            plugins.FieldFloat('h0', 'L0 Thickness', default=h0),

            plugins.FieldDataset(
                'ds1', 'Second layer (L1) dilatation curve', default=ds1),
            plugins.FieldDataset(
                'T1', 'Second layer (L1) temperature curve', default=T1),
            plugins.FieldFloat('E1', 'L1 Elasticity Modulus', default=E1),
            plugins.FieldFloat('h1', 'L1 Thickness', default=h1),

            plugins.FieldDataset(
                'ds2', 'Third layer (L2) dilatation curve', default=ds2),
            plugins.FieldDataset(
                'T2', 'Third layer (L2) temperature curve', default=T2),
            plugins.FieldFloat('E2', 'L2 Elasticity Modulus', default=E2),
            plugins.FieldFloat('h2', 'L2 Thickness', default=h2),

            plugins.FieldFloat(
                'L0', 'Sample Starting length (rods inter-axis)', default=L0),
            plugins.FieldInt(
                'num', 'Number of flexure points to generate ', default=num),
            plugins.FieldDataset(
                'ds_out', 'Output dataset name', default=ds_out),
        ]

    def getDatasets(self, fields):
        """Returns single output dataset (self.ds_out).
        This method should return a list of Dataset objects, which can include
        Dataset1D, Dataset2D and DatasetText
        """
        # raise DatasetPluginException if there are errors
        if fields['ds_out'] == '':
            raise plugins.DatasetPluginException('Invalid output dataset name')
        if fields['ds_out'] in [fields['ds0'], fields['ds1'], fields['ds2']]:
            raise plugins.DatasetPluginException(
                'Input and output datasets cannot be the same.')
        # make a new dataset with name in fields['ds_out']
        self.ds_out = plugins.Dataset1D(fields['ds_out'])
        # Automatic temperature output dataset
        self.ds_T = plugins.Dataset1D(fields['ds_out'] + '_T')
        # Automatic equilibrium length output dataset
        self.ds_len = plugins.Dataset1D(fields['ds_out'] + '_L')
        # return list of datasets
        return [self.ds_out, self.ds_T, self.ds_len]

    def updateDatasets(self, fields, helper):
        """Do flex simulation.
        This function should *update* the dataset(s) returned by getDatasets
        """
        # TODO: se le informazioni sui layer potrebbero essere caricate direttamente dai file di test.
        # get the input datasets - helper provides methods for getting other
        # datasets from Veusz
        ds0 = helper.getDataset(fields['ds0'])
        x0 = numpy.array(ds0.data)
        ds1 = helper.getDataset(fields['ds1'])
        x1 = numpy.array(ds1.data)
        dT0 = helper.getDataset(fields['T0'])
        T0 = numpy.array(dT0.data)
        dT1 = helper.getDataset(fields['T1'])
        T1 = numpy.array(dT1.data)
        NL = 2
        # Optional third layer
        if fields['ds2'] != '':
            ds2 = helper.getDataset(fields['ds2'])
            x2 = numpy.array(ds2.data)
            dT2 = helper.getDataset(fields['T2'])
            T2 = numpy.array(dT2.data)
            NL = 3
        # Check dimensions of the input datasets
        if x0.ndim * x1.ndim * T0.ndim * T1.ndim != 1 or (NL == 3 and x2.ndim * T2.ndim != 1):
            raise DatasetPluginException(
                "simflex only accepts 1 dimension arrays.")
        # Build interpolating functions for dilatations
        fL0 = interpolate.interp1d(T0, x0)
        fL1 = interpolate.interp1d(T1, x1)
        fL2 = lambda a: 0
        if NL == 3:
            fL2 = interpolate.interp1d(T2, x2)
        # Temperature boundaries
        maxT = max(max(T0), max(T1))
        minT = min(min(T0), min(T1))
        # Setup the layers
        lay0 = {'E': fields['E0'],
                'h': fields['h0'],
                'fL': fL0,
                'name': 'top'
                }
        lay0['Eh'] = lay0['E'] * lay0['h']
        lay1 = {'E': fields['E1'],
                'h': fields['h1'],
                'fL': fL1,
                'name': 'second'
                }
        lay1['Eh'] = lay1['E'] * lay1['h']
        layers = [lay0, lay1]
        lay2 = {'E': fields['E2'],
                'h': fields['h2'],
                'fL': fL2,
                'name': 'third'
                }
        lay2['Eh'] = lay2['E'] * lay2['h']
        if NL == 3:
            layers.append(lay2)
        # Do the simulation
        sim = SimFlex(layers, fields['L0'], minT, maxT, fields['num'])
        vT, disp, length = sim.simulation()
        # Record output data
        self.ds_out.update(data=disp)
        self.ds_T.update(data=vT)
        self.ds_len.update(data=length)
        return [self.ds_out, self.ds_T, self.ds_len]

plugins.datasetpluginregistry.append(SimFlexPlugin)


import requests
import json
import time
from selenium import webdriver
from selenium.webdriver.common.keys import Keys

URL = "https://www.amazon.com/s?k="

options = webdriver.FirefoxOptions()
driver = webdriver.Firefox()

search_word = str(input("Enter your search word:\n"))

driver.get(URL)
query = driver.find_element_by_xpath('//*[@id="twotabsearchtextbox"]')
query.send_keys(search_word, Keys.ENTER)
time.sleep(3)

class Product:
    def __init__(self, name, price, prev_price, link):
        self.name = name
        self.price = price
        self.prev_price = prev_price
        self.link = link

    def serialize(self):
        return {
            "name": self.name,
            "price": self.price,
            "prev_price": self.prev_price,
            "link": self.link
        }

    def jsonFormat(self, json_):
        self.name = json_["name"]
        self.price = json_["price"]
        self.prev_price = json_["prev_price"]
        self.link = json_["link"]

products = []

def convert_price_toNumber(price):
    price = price.split("$")[1]
    try:
        price = price.split("\n")[0] + "." + price.split("\n")[1]
    except:
        Exception()
    try:
        price = price.split(",")[0] + price.split(",")[1]
    except:
        Exception()
    return float(price)

page = 1
while True:
    if page != 0:
        try:
            driver.get(driver.current_url+ "&page=" + str(page))
        except:
            break
    for i in driver.find_elements_by_xpath('//*[@id="search"]/div[1]/div[2]/div/span[4]/div[1]'):
        # /html/body/div[1]/div[1]/div[1]/div[2]/div/span[4]/div[1]
        counter = 0
        for query in i.find_elements_by_xpath('//div/div/div[2]/div[2]/div/div[2]/div[1]/div/div[1]/div/div/a'):
            should_add = True
            name = ""
            price = ""
            prev_price = ""
            link = ""
            try:
                name = i.find_elements_by_tag_name('h2')[counter].text
                price = convert_price_toNumber(query.find_element_by_class_name('a-price').text)
                link = i.find_elements_by_xpath('//h2/a')[counter].get_attribute("href")
                try:
                    prev_price = convert_price_toNumber(query.find_element_by_class_name('a-text-price').text)
                except:
                    Exception()
                    prev_price = price
            except:
                print("exception")
                should_add = False
            product = Product(name, price, prev_price, link)
            if should_add:
                products.append(product)
            counter = counter + 1
    page = page +1
    if page == 11:
        break
    print(str(page) + " " + str(driver.current_url))

biggest_discount = 0.0
lowest_price = 0.0
cheapest_product = Product("", "", "", "")
best_deal_product = Product("", "", "", "")
search_word = search_word.split(" ")

run = 0

for product in products:
    not_right = False
    for word in search_word:
        if word.lower() not in product.name.lower():
            not_right = True
    if not not_right:
        if run == 0:
            lowest_price = product.price
            cheapest_product = product
            run = 1
        elif product.price < lowest_price:
            lowest_price = product.price
            cheapest_product = product
        discount = product.prev_price - product.price
        if discount > biggest_discount:
            biggest_discount = discount
            best_deal_product = product

with open('products.json', 'w') as json_file:
    data = {}
    data["Products"] = []
    for prod in products:
        data["Products"].append(prod.serialize())
    json.dump(data, json_file, sort_keys=True, indent=4)

print(json.dumps(cheapest_product.serialize(), indent=4, sort_keys=True))
print(json.dumps(best_deal_product.serialize(), indent=4, sort_keys=True))

driver = webdriver.Firefox()
driver.get(best_deal_product.link)
driver.find_element_by_tag_name('body').send_keys(Keys.COMMAND + 't')


from itertools import combinations
from collections import defaultdict

import numpy as np
import networkx as nx

from ..factors.discrete import TabularCPD
from ..base import DAG

class DynamicBayesianNetwork(DAG):
    def __init__(self, ebunch=None):
        """
        Base class for Dynamic Bayesian Network

        This is a time variant model of the static Bayesian model, where each
        time-slice has some static nodes and is then replicated over a certain
        time period.

        The nodes can be any hashable python objects.

        Parameters
        ----------
        ebunch: Data to initialize graph.  If data=None (default) an empty
              graph is created.  The data can be an edge list, or any NetworkX
              graph object

        Examples
        --------
        Create an empty Dynamic Bayesian Network with no nodes and no edges:
        >>> from pgmpy.models import DynamicBayesianNetwork as DBN
        >>> dbn = DBN()

        Adding nodes and edges inside the dynamic bayesian network. A single
        node can be added using the method below. For adding edges we need to
        specify the time slice since edges can be across different time slices.

        For example for a network as [image](http://s8.postimg.org/aaybw4x2t/Blank_Flowchart_New_Page_1.png),
        we will need to add all the edges in the 2-TBN as:

        >>> dbn.add_edges_from([(('D', 0), ('G', 0)), (('I', 0), ('G', 0)),
        ...                     (('G', 0), ('L', 0)), (('D', 0), ('D', 1)),
        ...                     (('I', 0), ('I', 1)), (('G', 0), ('G', 1)),
        ...                     (('G', 0), ('L', 1)), (('L', 0), ('L', 1))])

        We can query the edges and nodes in the network as:
        >>> dbn.nodes()
        ['G', 'D', 'I', 'L']
        >>> dbn.edges()
        [(('D', 1), ('G', 1)), (('I', 0), ('G', 0)), (('I', 0), ('I', 1)),
         (('I', 1), ('G', 1)), (('G', 0), ('L', 0)), (('G', 0), ('G', 1)),
         (('G', 0), ('L', 1)), (('D', 0), ('G', 0)), (('D', 0), ('D', 1)),
         (('L', 0), ('L', 1)), (('G', 1), ('L', 1))]

        If any variable is not present in the network while adding an edge,
        pgmpy will automatically add that variable to the network.

        But for adding nodes to the model we don't need to specify the time
        slice as it is common in all the time slices. And therefore pgmpy
        automatically replicated it all the time slices. For example, for
        adding a new variable `S` in the above network we can simply do:
        >>> dbn.add_node('S')
        >>> dbn.nodes()
        ['S', 'G', 'D', 'I', 'L']

        Public Methods
        --------------
        add_node
        add_nodes_from
        add_edges
        add_edges_from
        add_cpds
        initialize_initial_state
        inter_slice
        intra_slice
        copy
        """
        super(DynamicBayesianNetwork, self).__init__()
        if ebunch:
            self.add_edges_from(ebunch)
        self.cpds = []
        self.cardinalities = defaultdict(int)

    def add_node(self, node, **attr):
        """
        Adds a single node to the Network

        Parameters
        ----------
        node: node
            A node can be any hashable Python object.

        Examples
        --------
        >>> from pgmpy.models import DynamicBayesianNetwork as DBN
        >>> dbn = DBN()
        >>> dbn.add_node('A')
        ['A']
        """
        super(DynamicBayesianNetwork, self).add_node((node, 0), **attr)

    def add_nodes_from(self, nodes, **attr):
        """
        Add multiple nodes to the Network.

        Parameters
        ----------
        nodes: iterable container
            A container of nodes (list, dict, set, etc.).

        Examples
        --------
        >>> from pgmpy.models import DynamicBayesianNetwork as DBN
        >>> dbn = DBN()
        >>> dbn.add_nodes_from(['A', 'B', 'C'])
        """
        for node in nodes:
            self.add_node(node)

    def _nodes(self):
        """
        Returns the list of nodes present in the network

        Examples
        --------
        >>> from pgmpy.models import DynamicBayesianNetwork as DBN
        >>> dbn = DBN()
        >>> dbn.add_nodes_from(['A', 'B', 'C'])
        >>> sorted(dbn._nodes())
        ['B', 'A', 'C']
        """
        return list(
            set(
                [
                    node
                    for node, timeslice in super(DynamicBayesianNetwork, self).nodes()
                ]
            )
        )

    def add_edge(self, start, end, **kwargs):
        """
        Add an edge between two nodes.

        The nodes will be automatically added if they are not present in the network.

        Parameters
        ----------
        start: tuple
               Both the start and end nodes should specify the time slice as
               (node_name, time_slice). Here, node_name can be any hashable
               python object while the time_slice is an integer value,
               which denotes the time slice that the node belongs to.

        end: tuple
               Both the start and end nodes should specify the time slice as
               (node_name, time_slice). Here, node_name can be any hashable
               python object while the time_slice is an integer value,
               which denotes the time slice that the node belongs to.

        Examples
        --------
        >>> from pgmpy.models import DynamicBayesianNetwork as DBN
        >>> model = DBN()
        >>> model.add_nodes_from(['D', 'I'])
        >>> model.add_edge(('D',0), ('I',0))
        >>> sorted(model.edges())
        [(('D', 0), ('I', 0)), (('D', 1), ('I', 1))]
        """
        try:
            if len(start) != 2 or len(end) != 2:
                raise ValueError("Nodes must be of type (node, time_slice).")
            elif not isinstance(start[1], int) or not isinstance(end[1], int):
                raise ValueError("Nodes must be of type (node, time_slice).")
            elif start[1] == end[1]:
                start = (start[0], 0)
                end = (end[0], 0)
            elif start[1] == end[1] - 1:
                start = (start[0], 0)
                end = (end[0], 1)
            elif start[1] > end[1]:
                raise NotImplementedError(
                    "Edges in backward direction are not allowed."
                )
            elif start[1] != end[1]:
                raise ValueError(
                    "Edges over multiple time slices is not currently supported"
                )
        except TypeError:
            raise ValueError("Nodes must be of type (node, time_slice).")

        if start == end:
            raise ValueError("Self Loops are not allowed")
        elif (
            start in super(DynamicBayesianNetwork, self).nodes()
            and end in super(DynamicBayesianNetwork, self).nodes()
            and nx.has_path(self, end, start)
        ):
            raise ValueError(
                f"Loops are not allowed. Adding the edge from ({str(start)} --> {str(end)}) forms a loop."
            )

        super(DynamicBayesianNetwork, self).add_edge(start, end, **kwargs)

        if start[1] == end[1]:
            super(DynamicBayesianNetwork, self).add_edge(
                (start[0], 1 - start[1]), (end[0], 1 - end[1])
            )
        else:
            super(DynamicBayesianNetwork, self).add_node((end[0], 1 - end[1]))

    def add_edges_from(self, ebunch, **kwargs):
        """
        Add all the edges in ebunch.

        If nodes referred in the ebunch are not already present, they
        will be automatically added. Node names can be any hashable python object.

        Parameters
        ----------
        ebunch : list, array-like
                List of edges to add. Each edge must be of the form of
                ((start, time_slice), (end, time_slice)).

        Examples
        --------
        >>> from pgmpy.models import DynamicBayesianNetwork as DBN
        >>> dbn = DBN()
        >>> dbn.add_edges_from([(('D', 0), ('G', 0)), (('I', 0), ('G', 0))])
        >>> dbn.nodes()
        ['G', 'I', 'D']
        >>> dbn.edges()
        [(('D', 1), ('G', 1)),
         (('I', 1), ('G', 1)),
         (('D', 0), ('G', 0)),
         (('I', 0), ('G', 0))]
        """
        for edge in ebunch:
            self.add_edge(edge[0], edge[1])

    def get_intra_edges(self, time_slice=0):
        """
        Returns the intra slice edges present in the 2-TBN.

        Parameters
        ----------
        time_slice: int (whole number)
                The time slice for which to get intra edges. The timeslice
                should be a positive value or zero.

        Examples
        --------
        >>> from pgmpy.models import DynamicBayesianNetwork as DBN
        >>> dbn = DBN()
        >>> dbn.add_nodes_from(['D', 'G', 'I', 'S', 'L'])
        >>> dbn.add_edges_from([(('D', 0), ('G', 0)), (('I', 0), ('G', 0)),
        ...                     (('G', 0), ('L', 0)), (('D', 0), ('D', 1)),
        ...                     (('I', 0), ('I', 1)), (('G', 0), ('G', 1)),
        ...                     (('G', 0), ('L', 1)), (('L', 0), ('L', 1))])
        >>> dbn.get_intra_edges()
        [(('D', 0), ('G', 0)), (('G', 0), ('L', 0)), (('I', 0), ('G', 0))]
        """
        if not isinstance(time_slice, int) or time_slice < 0:
            raise ValueError(
                "The timeslice should be a positive value greater than or equal to zero"
            )

        return [
            tuple((x[0], time_slice) for x in edge)
            for edge in self.edges()
            if edge[0][1] == edge[1][1] == 0
        ]

    def get_inter_edges(self):
        """
        Returns the inter-slice edges present in the 2-TBN.

        Examples
        --------
        >>> from pgmpy.models import DynamicBayesianNetwork as DBN
        >>> dbn = DBN()
        >>> dbn.add_edges_from([(('D', 0), ('G', 0)), (('I', 0), ('G', 0)),
        ...                     (('G', 0), ('L', 0)), (('D', 0), ('D', 1)),
        ...                     (('I', 0), ('I', 1)), (('G', 0), ('G', 1)),
        ...                     (('G', 0), ('L', 1)), (('L', 0), ('L', 1))])
        >>> dbn.get_inter_edges()
        [(('D', 0), ('D', 1)),
         (('G', 0), ('G', 1)),
         (('G', 0), ('L', 1)),
         (('I', 0), ('I', 1)),
         (('L', 0), ('L', 1))]
        """
        return [edge for edge in self.edges() if edge[0][1] != edge[1][1]]

    def get_interface_nodes(self, time_slice=0):
        """
        Returns the nodes in the first timeslice whose children are present in the first timeslice.

        Parameters
        ----------
        time_slice:int
                The timeslice should be a positive value greater than or equal to zero

        Examples
        --------
        >>> from pgmpy.models import DynamicBayesianNetwork as DBN
        >>> dbn = DBN()
        >>> dbn.add_nodes_from(['D', 'G', 'I', 'S', 'L'])
        >>> dbn.add_edges_from([(('D',0),('G',0)),(('I',0),('G',0)),(('G',0),('L',0)),(('D',0),('D',1))])
        >>> dbn.get_interface_nodes()
        [('D', 0)]
        """
        if not isinstance(time_slice, int) or time_slice < 0:
            raise ValueError(
                "The timeslice should be a positive value greater than or equal to zero"
            )

        return [(edge[0][0], time_slice) for edge in self.get_inter_edges()]

    def get_slice_nodes(self, time_slice=0):
        """
        Returns the nodes present in a particular timeslice

        Parameters
        ----------
        time_slice:int
                The timeslice should be a positive value greater than or equal to zero

        Examples
        --------
        >>> from pgmpy.models import DynamicBayesianNetwork as DBN
        >>> dbn = DBN()
        >>> dbn.add_nodes_from(['D', 'G', 'I', 'S', 'L'])
        >>> dbn.add_edges_from([(('D', 0),('G', 0)),(('I', 0),('G', 0)),(('G', 0),('L', 0)),(('D', 0),('D', 1))])
        >>> dbn.get_slice_nodes()
        """
        if not isinstance(time_slice, int) or time_slice < 0:
            raise ValueError(
                "The timeslice should be a positive value greater than or equal to zero"
            )

        return [(node, time_slice) for node in self._nodes()]

    def add_cpds(self, *cpds):
        """
        This method adds the cpds to the dynamic bayesian network.
        Note that while adding variables and the evidence in cpd,
        they have to be of the following form
        (node_name, time_slice)
        Here, node_name is the node that is inserted
        while the time_slice is an integer value, which denotes
        the index of the time_slice that the node belongs to.

        Parameters
        ----------
        cpds : list, set, tuple (array-like)
            List of CPDs which are to be associated with the model. Each CPD
            should be an instance of `TabularCPD`.

        Examples
        --------
        >>> from pgmpy.models import DynamicBayesianNetwork as DBN
        >>> from pgmpy.factors.discrete import TabularCPD
        >>> dbn = DBN()
        >>> dbn.add_edges_from([(('D', 0),('G', 0)),(('I', 0),('G', 0)),(('D', 0),('D', 1)),(('I', 0),('I', 1))])
        >>> grade_cpd = TabularCPD(('G', 0), 3, [[0.3, 0.05, 0.9, 0.5],
        ...                                      [0.4, 0.25, 0.8, 0.03],
        ...                                      [0.3, 0.7, 0.02, 0.2]],
        ...                        evidence=[('I', 0),('D', 0)],
        ...                        evidence_card=[2, 2])
        >>> d_i_cpd = TabularCPD(('D',1), 2, [[0.6, 0.3],
        ...                                   [0.4, 0.7]],
        ...                      evidence=[('D',0)],
        ...                      evidence_card=[2])
        >>> diff_cpd = TabularCPD(('D', 0), 2, [[0.6, 0.4]])
        >>> intel_cpd = TabularCPD(('I', 0), 2, [[0.7, 0.3]])
        >>> i_i_cpd = TabularCPD(('I', 1), 2, [[0.5, 0.4],
        ...                                    [0.5, 0.6]],
        ...                      evidence=[('I', 0)],
        ...                      evidence_card=[2])
        >>> dbn.add_cpds(grade_cpd, d_i_cpd, diff_cpd, intel_cpd, i_i_cpd)
        >>> dbn.get_cpds()
        [<TabularCPD representing P(('G', 0):3 | ('I', 0):2, ('D', 0):2) at 0x7ff7f27b0cf8>,
         <TabularCPD representing P(('D', 1):2 | ('D', 0):2) at 0x7ff810b9c2e8>,
         <TabularCPD representing P(('D', 0):2) at 0x7ff7f27e6f98>,
         <TabularCPD representing P(('I', 0):2) at 0x7ff7f27e6ba8>,
         <TabularCPD representing P(('I', 1):2 | ('I', 0):2) at 0x7ff7f27e6668>]
        """
        for cpd in cpds:
            if not isinstance(cpd, TabularCPD):
                raise ValueError("cpd should be an instance of TabularCPD")

            if set(cpd.variables) - set(cpd.variables).intersection(
                set(super(DynamicBayesianNetwork, self).nodes())
            ):
                raise ValueError("CPD defined on variable not in the model", cpd)

        self.cpds.extend(cpds)

    def get_cpds(self, node=None, time_slice=0):
        """
        Returns the CPDs that have been associated with the network.

        Parameters
        ----------
        node: tuple (node_name, time_slice)
            The node should be in the following form (node_name, time_slice).
            Here, node_name is the node that is inserted while the time_slice is
            an integer value, which denotes the index of the time_slice that the
            node belongs to.

        time_slice: int
            The time_slice should be a positive integer greater than or equal to zero.

        Examples
        --------
        >>> from pgmpy.models import DynamicBayesianNetwork as DBN
        >>> from pgmpy.factors.discrete import TabularCPD
        >>> dbn = DBN()
        >>> dbn.add_edges_from([(('D',0),('G',0)),(('I',0),('G',0)),(('D',0),('D',1)),(('I',0),('I',1))])
        >>> grade_cpd =  TabularCPD(('G',0), 3, [[0.3,0.05,0.9,0.5],
        ...                                      [0.4,0.25,0.8,0.03],
        ...                                      [0.3,0.7,0.02,0.2]], [('I', 0),('D', 0)],[2,2])
        >>> dbn.add_cpds(grade_cpd)
        >>> dbn.get_cpds()
        """
        # TODO: fix bugs in this
        if node:
            if node not in super(DynamicBayesianNetwork, self).nodes():
                raise ValueError("Node not present in the model.")
            else:
                for cpd in self.cpds:
                    if cpd.variable == node:
                        return cpd
        else:
            return_cpds = []
            for var in self.get_slice_nodes(time_slice=time_slice):
                cpd = self.get_cpds(node=var)
                if cpd:
                    return_cpds.append(cpd)
            return return_cpds

    def remove_cpds(self, *cpds):
        """
        Removes the cpds that are provided in the argument.

        Parameters
        ----------
        *cpds : list, set, tuple (array-like)
            List of CPDs which are to be associated with the model. Each CPD
            should be an instance of `TabularCPD`.

        Examples
        --------
        >>> from pgmpy.models import DynamicBayesianNetwork as DBN
        >>> from pgmpy.factors.discrete import TabularCPD
        >>> dbn = DBN()
        >>> dbn.add_edges_from([(('D',0),('G',0)),(('I',0),('G',0)),(('D',0),('D',1)),(('I',0),('I',1))])
        >>> grade_cpd =  TabularCPD(('G',0), 3, [[0.3,0.05,0.9,0.5],
        ...                                      [0.4,0.25,0.8,0.03],
        ...                                      [0.3,0.7,0.02,0.2]], [('I', 0),('D', 0)],[2,2])
        >>> dbn.add_cpds(grade_cpd)
        >>> dbn.get_cpds()
        [<TabularCPD representing P(('G', 0):3 | ('I', 0):2, ('D', 0):2) at 0x3348ab0>]
        >>> dbn.remove_cpds(grade_cpd)
        >>> dbn.get_cpds()
        []
        """
        for cpd in cpds:
            if isinstance(cpd, (tuple, list)):
                cpd = self.get_cpds(cpd)
            self.cpds.remove(cpd)

    def check_model(self):
        """
        Check the model for various errors. This method checks for the following
        errors.

        * Checks if the sum of the probabilities in each associated CPD for each
            state is equal to 1 (tol=0.01).
        * Checks if the CPDs associated with nodes are consistent with their parents.

        Returns
        -------
        boolean: True if everything seems to be order. Otherwise raises error
            according to the problem.
        """
        for node in super(DynamicBayesianNetwork, self).nodes():
            cpd = self.get_cpds(node=node)
            if isinstance(cpd, TabularCPD):
                evidence = cpd.variables[:0:-1]
                evidence_card = cpd.cardinality[:0:-1]
                parents = self.get_parents(node)
                if set(evidence) != set(parents if parents else []):
                    raise ValueError(
                        f"CPD associated with {node} doesn't have proper parents associated with it."
                    )
                if not np.allclose(
                    cpd.to_factor()
                    .marginalize([node], inplace=False)
                    .values.flatten("C"),
                    np.ones(np.product(evidence_card)),
                    atol=0.01,
                ):
                    raise ValueError(
                        f"Sum of probabilities of states for node {node} is not equal to 1"
                    )
        return True

    def initialize_initial_state(self):
        """
        This method will automatically re-adjust the cpds and the edges added to the bayesian network.
        If an edge that is added as an intra time slice edge in the 0th timeslice, this method will
        automatically add it in the 1st timeslice. It will also add the cpds. However, to call this
        method, one needs to add cpds as well as the edges in the bayesian network of the whole
        skeleton including the 0th and the 1st timeslice,.

        Examples
        --------
        >>> from pgmpy.models import DynamicBayesianNetwork as DBN
        >>> from pgmpy.factors.discrete import TabularCPD
        >>> student = DBN()
        >>> student.add_nodes_from(['D', 'G', 'I', 'S', 'L'])
        >>> student.add_edges_from([(('D', 0),('G', 0)),(('I', 0),('G', 0)),(('D', 0),('D', 1)),(('I', 0),('I', 1))])
        >>> grade_cpd = TabularCPD(('G', 0), 3, [[0.3, 0.05, 0.9, 0.5],
        ...                                      [0.4, 0.25, 0.08, 0.3],
        ...                                      [0.3, 0.7, 0.02, 0.2]],
        ...                        evidence=[('I', 0),('D', 0)],
        ...                        evidence_card=[2, 2])
        >>> d_i_cpd = TabularCPD(('D', 1), 2, [[0.6, 0.3],
        ...                                    [0.4, 0.7]],
        ...                      evidence=[('D', 0)],
        ...                      evidence_card=[2])
        >>> diff_cpd = TabularCPD(('D', 0), 2, [[0.6, 0.4]])
        >>> intel_cpd = TabularCPD(('I',0), 2, [[0.7, 0.3]])
        >>> i_i_cpd = TabularCPD(('I', 1), 2, [[0.5, 0.4],
        ...                                    [0.5, 0.6]],
        ...                      evidence=[('I', 0)],
        ...                      evidence_card=[2])
        >>> student.add_cpds(grade_cpd, d_i_cpd, diff_cpd, intel_cpd, i_i_cpd)
        >>> student.initialize_initial_state()
        """
        for cpd in self.cpds:
            temp_var = (cpd.variable[0], 1 - cpd.variable[1])
            parents = self.get_parents(temp_var)
            if not any(x.variable == temp_var for x in self.cpds):
                if all(x[1] == parents[0][1] for x in parents):
                    if parents:
                        evidence_card = cpd.cardinality[:0:-1]
                        new_cpd = TabularCPD(
                            temp_var,
                            cpd.variable_card,
                            cpd.values.reshape(
                                cpd.variable_card, np.prod(evidence_card)
                            ),
                            parents,
                            evidence_card,
                        )
                    else:
                        if cpd.get_evidence():
                            initial_cpd = cpd.marginalize(
                                cpd.get_evidence(), inplace=False
                            )
                            new_cpd = TabularCPD(
                                temp_var,
                                cpd.variable_card,
                                np.reshape(initial_cpd.values, (2, -1)),
                            )
                        else:
                            new_cpd = TabularCPD(
                                temp_var,
                                cpd.variable_card,
                                np.reshape(cpd.values, (2, -1)),
                            )
                    self.add_cpds(new_cpd)
            self.check_model()

    def moralize(self):
        """
        Removes all the immoralities in the Network and creates a moral
        graph (UndirectedGraph).

        A v-structure X->Z<-Y is an immorality if there is no directed edge
        between X and Y.

        Examples
        --------
        >>> from pgmpy.models import DynamicBayesianNetwork as DBN
        >>> dbn = DBN([(('D',0), ('G',0)), (('I',0), ('G',0))])
        >>> moral_graph = dbn.moralize()
        >>> moral_graph.edges()
        EdgeView([(('G', 0), ('I', 0)),
                  (('G', 0), ('D', 0)),
                  (('D', 1), ('I', 1)),
                  (('D', 1), ('G', 1)),
                  (('I', 0), ('D', 0)),
                  (('G', 1), ('I', 1))])
        """
        moral_graph = self.to_undirected()

        for node in super(DynamicBayesianNetwork, self).nodes():
            moral_graph.add_edges_from(combinations(self.get_parents(node), 2))

        return moral_graph

    def copy(self):
        """
        Returns a copy of the dynamic bayesian network.

        Returns
        -------
        DynamicBayesianNetwork: copy of the dynamic bayesian network

        Examples
        --------
        >>> from pgmpy.models import DynamicBayesianNetwork as DBN
        >>> from pgmpy.factors.discrete import TabularCPD
        >>> dbn = DBN()
        >>> dbn.add_edges_from([(('D',0),('G',0)),(('I',0),('G',0)),(('D',0),('D',1)),(('I',0),('I',1))])
        >>> grade_cpd =  TabularCPD(('G',0), 3, [[0.3, 0.05, 0.9,  0.5 ],
        ...                                      [0.4, 0.25, 0.8,  0.03],
        ...                                      [0.3,  0.7, 0.02, 0.2 ]],
        ...                         [('I', 0), ('D', 0)],[2,2])
        >>> dbn.add_cpds(grade_cpd)
        >>> dbn_copy = dbn.copy()
        >>> dbn_copy.nodes()
        ['Z', 'G', 'I', 'D']
        >>> dbn_copy.edges()
        [(('I', 1), ('G', 1)),
        (('I', 0), ('I', 1)),
        (('I', 0), ('G', 0)),
        (('D', 1), ('G', 1)),
        (('D', 0), ('G', 0)),
        (('D', 0), ('D', 1))]
        >>> dbn_copy.get_cpds()
        [<TabularCPD representing P(('G', 0):3 | ('I', 0):2, ('D', 0):2) at 0x7f13961a3320>]
        """
        dbn = DynamicBayesianNetwork()
        dbn.add_nodes_from(self._nodes())
        dbn.add_edges_from(self.edges())
        cpd_copy = [cpd.copy() for cpd in self.get_cpds()]
        dbn.add_cpds(*cpd_copy)
        return dbn


import os, sys
from file import File
from pywinauto.application import Application

class MediaPlayer(object):

	def __init__(self, outputFileLocation):
		self.outputFileLocation = outputFileLocation
		self.playList = []
		self.app = Application()

	#adds a media file to the current playlist
	def addMedia(self, aFile):

		#make sure that aFile contains a valid file location
		try:
			if os.path.getsize(aFile.location) > 0:
				self.playList.append(aFile)
		except:
			pass

	#removes a file from the current playlist
	def removeMedia(self, aFile):
		self.playList.remove(aFile)

	#outputs the playlist to a file
	def toM3U(self):
		out = open(self.outputFileLocation, 'w')
		out.write("#EXTM3U\n\n")
		for mediaFile in self.playList:
			titleStr = "#EXTINF:-1, " + mediaFile.name + "\n"
			locStr = mediaFile.location + "\n\n"
			out.write(titleStr)
			out.write(locStr)

	#plays the current playlist
	def play(self):
		self.toM3U()
		connectionString = r"C:\Program Files\Windows Media Player\wmplayer.exe"
		connectionString += r" /play /fullscreen" + self.outputFileLocation
		self.app.start(connectionString)

	def stop(self):
		pass

if __name__ == '__main__':
	player = MediaPlayer( os.getcwd() + r"\selfTest.m3u")
	aFile = File()
	aFile.location = r"C:\WINDOWS\Media\tada.wav"
	aFile.name = "tada"
	player.addMedia(aFile)
	aFile = File()
	aFile.location = r"C:\WINDOWS\Media\chimes.wav"
	aFile.name = "chimes"
	player.addMedia(aFile)
	player.play()

"""
app["Windows Media Player"].TypeKeys("^u")

"""

topics/02 - scripting/windows automation/MediaPlayer.py


from manapy.ddm import readmesh
from manapy.ddm import Domain

import mumps

import numpy as np

import os

from triangle import dist, orthocenter

from manapy.comms                import all_to_all
from manapy.comms.pyccel_comm    import define_halosend
from manapy.ast.pyccel_functions import convert_solution

from mpi4py import MPI
COMM = MPI.COMM_WORLD
SIZE = COMM.Get_size()
RANK = COMM.Get_rank()

# ... get the mesh directory
'''try:
    MESH_DIR = os.environ['MESH_DIR']

except:
    BASE_DIR = os.path.dirname(os.path.realpath(__file__))
    BASE_DIR = os.path.join(BASE_DIR , '..')
    MESH_DIR = os.path.join(BASE_DIR, 'mesh')
'''
filename = "../mesh/rectangle.msh"
#filename = os.path.join(MESH_DIR, filename)

dim = 2
readmesh(filename, dim=dim, periodic=[0,0,0])

#Create the informations about cells, faces and nodes
domain = Domain(dim=dim)

cells   = domain.cells
nbcells = domain.nbcells
faces   = domain.faces
nodes   = domain.nodes
halos   = domain.halos

def Assembly(cellid:'int[:,:]', centerf:'float[:]', haloext:'int[:,:]',halofid:'int[:]',
                loctoglob:'int[:]', mesuref:'float[:]', namef:'int[:]', innerfaces:'int[:]',
                boundaryfaces:'int[:]', halofaces:'int[:]', globalsize):

    sizeM  = 4*len(innerfaces) + len(boundaryfaces) + 2 * len(halofaces)

    center = np.zeros( (nbcells, 2), dtype = np.float64 )
    b      = np.zeros(  globalsize, dtype = np.float64 )
    row    = np.zeros(    sizeM, dtype = np.int32   )
    col    = np.zeros(    sizeM, dtype = np.int32   )
    data   = np.zeros(    sizeM, dtype = np.float64   )

    for i in range(nbcells):
        center[i, :] = orthocenter([ nodes.vertex[ cells.nodeid[i][k] ] for k in range(3) ])

    #orthocenter = np.zeros(domain.nbcells)
    #center_ghost = np.zeros(domain.nbfaces)
    centerx = np.zeros(nbcells, dtype = np.float64)
    centery = np.zeros(nbcells, dtype = np.float64)

    centerx[:] = center[:, 0]
    centery[:] = center[:, 1]

    #array to send
    halotosendx  = np.zeros(len(domain.halos.halosint))
    halotosendy  = np.zeros(len(domain.halos.halosint))

    #array to receive
    center_halox  = np.zeros(domain.nbhalos)
    center_haloy  = np.zeros(domain.nbhalos)

    #prepare halotosend array
    define_halosend(centerx, halotosendx, domain.halos.indsend)
    define_halosend(centery, halotosendy, domain.halos.indsend)

    #send information to neighbor processors received on orthocenter_halo array
    all_to_all(halotosendx, domain.nbhalos, domain.halos.scount, domain.halos.rcount,
               center_halox, domain.halos.comm_ptr)
    all_to_all(halotosendy, domain.nbhalos, domain.halos.scount, domain.halos.rcount,
               center_haloy, domain.halos.comm_ptr)

    cmpt = 0
    for face in innerfaces:

        c_left = cellid[face][0]
        c_leftglob  = loctoglob[c_left]

        c_right = cellid[face][1]
        c_rightglob  = loctoglob[c_right]

        mesure = mesuref[face]

        row[cmpt] = c_leftglob
        col[cmpt] = c_leftglob
        data[cmpt] = - (mesure/ dist(center[c_left], center[c_right]))
        cmpt = cmpt + 1

        row[cmpt] = c_rightglob
        col[cmpt] = c_rightglob
        data[cmpt] = -(mesure/dist(center[c_left], center[c_right]))
        cmpt = cmpt + 1

        row[cmpt] = c_leftglob
        col[cmpt] = c_rightglob
        data[cmpt] = (mesure/dist(center[c_left], center[c_right]))
        cmpt = cmpt + 1

        row[cmpt] = c_rightglob
        col[cmpt] = c_leftglob
        data[cmpt] = (mesure/dist(center[c_left], center[c_right]))
        cmpt = cmpt + 1

    for face in boundaryfaces:
        c_left = cellid[face][0]
        c_leftglob  = loctoglob[c_left]

        #K = loctoglob[cellid[face][0]]
        mesure = mesuref[face]

        row[cmpt] = c_leftglob
        col[cmpt] = c_leftglob
        data[cmpt] = -(mesure/dist(center[c_left], centerf[face]))
        cmpt = cmpt + 1

        if namef[face] == 1:
            b[c_leftglob] = -20*(mesure/dist(center[c_left], centerf[face]))

    for face in halofaces:
        mesure = mesuref[face]

        c_left = cellid[face][0]
        c_leftglob  = loctoglob[c_left]

        #parameters[0] = param4[i]; parameters[1] = param2[i]

        c_rightglob = haloext[halofid[face]][0]
        c_right     = halofid[face]

        row[cmpt] = c_leftglob
        col[cmpt] = c_leftglob

        p =  - mesure / dist(center[c_left], [center_halox[c_right], center_haloy[c_right]] )
        data[cmpt] = p
        cmpt = cmpt + 1

        row[cmpt] = c_leftglob
        col[cmpt] = c_rightglob
        data[cmpt] = - p
        cmpt = cmpt + 1

    return row, col, data, b

globalsize = COMM.allreduce(nbcells, op=MPI.SUM)

row, col, data, b = Assembly(faces.cellid, faces.center, halos.halosext, faces.halofid, cells.loctoglob,
                                 faces.mesure, faces.name, domain.innerfaces, domain.boundaryfaces,
                                 domain.halofaces, globalsize)

rhs0_glob = COMM.reduce(b, op=MPI.SUM, root=0)

#if RANK == 0:
#    print(rhs0_glob)

ctx = mumps.DMumpsContext(comm=COMM)
ctx.set_shape(nbcells)
ctx.set_silent()

ctx.set_distributed_assembled_rows_cols(row+1, col+1)
ctx.set_distributed_assembled_values(data)
ctx.set_icntl(18,3)

if COMM.Get_rank() == 0:
    ctx.id.n = globalsize

#Analyse
ctx.run(job=1)
#Factorization Phase
ctx.run(job=2)

#u3 = np.zeros(nbcells)
#Allocation size of rhs
if RANK == 0:
    u = rhs0_glob.copy()
    ctx.set_rhs(u)

#Solution Phase
ctx.run(job=3)

#Destroy
ctx.destroy()
#if RANK == 0:
#    print(u3)
#for i in range(len(row)):
#    print(data[i], row[i], col[i])

sendcounts1 = np.array(COMM.gather(nbcells, root=0))
x1converted = np.zeros(globalsize)
u_loc = np.zeros(nbcells)

if RANK == 0:
    #Convert solution for scattering
    convert_solution(u, x1converted, domain.cells.tc, globalsize)
COMM.Scatterv([x1converted, sendcounts1, MPI.DOUBLE], u_loc, root = 0)

domain.save_on_cell(miter=2, value = u_loc )


# Python
import cv2
print(cv2.__version__)
flip=2  # change camera play direction
dispW = 640
dispH = 480

camSet='nvarguscamerasrc !  video/x-raw(memory:NVMM), width=3264, height=2464, format=NV12, framerate=21/1 ! nvvidconv flip-method='+str(flip)+' ! video/x-raw, width='+str(dispW)+', height='+str(dispH)+', format=BGRx ! videoconvert ! video/x-raw, format=BGR ! appsink'
cam= cv2.VideoCapture(camSet)
dispW = int(cam.get(cv2.CAP_PROP_FRAME_WIDTH)) # screen width
dispH = int(cam.get(cv2.CAP_PROP_FRAME_HEIGHT)) # screen height
print(f'window size = {dispW}Width x {dispH}Height')

BW = int(.18 * dispW) # rectangle width
BH = int(.14 * dispH) # rectangle height
posX = 10 # rectangle X-position start point
posY = 270 # rectangle Y-position start point
dx = 2 # each x move step
dy = 2 # each y move step

while True:
    ret, frame = cam.read()

    # step1: copy the initial color pixel points in ROI area
    # remind: in coordination, row=y, column=x
    roi_init = frame[posY:posY+BH, posX:posX+BW].copy()
    print('roi_init= ',roi_init)

    # step2: change the screen copy to gray
    roi_gray = cv2.cvtColor(frame,cv2.COLOR_BGR2GRAY)
    roi_gray = cv2.cvtColor(roi_gray,cv2.COLOR_GRAY2BGR) # from gray picture to set the ROI in original frame
    frame = cv2.rectangle(roi_gray, (posX, posY),(posX+BW, posY+BH),(0,255,0),4)

    # step3: ROI area replace with the initial colored pixel points
    frame[posY:posY+BH, posX:posX+BW] = roi_init

    cv2.imshow('nanoCam', frame)
    cv2.moveWindow('nanoCam',0,0)
    posX += dx
    posY += dy
    if posX <= 0: # left boundary
        dx = dx*(-1) # change direction
    if posX + BW >= dispW: # right boundary
        dx = dx*(-1)
    if posY <= 0:  # upper boundary
        dy = dy*(-1)
    if posY + BH >= dispH: # bottom boundary
        dy = dy*(-1)

    if cv2.waitKey(5)==ord('q'):
        break

cam.release()
cv2.destroyAllWindows()


#import sys
#import os
import re
#import time
#import threading

try:
    from Xlib import X, XK, display#, error
#    from Xlib.ext import record
#    from Xlib.protocol import rq
    from Xlib.protocol.event import KeyPress
    from Xlib.protocol.event import KeyRelease
except ImportError:
    print('This script requires the python-xlib library')

#######################################################################
########################START CLASS DEF################################
#######################################################################

def lookup_keysym(keysym):
    for name in dir(XK):
        if name.startswith("XK_") and getattr(XK, name) == keysym:
            return name.lstrip("XK_")
    return "[%d]" % keysym

class HookManager(object):
    """This is the main class. Instantiate it, and you can hand it KeyDown and KeyUp (functions in your own code) which execute to parse the pyxhookkeyevent class that is returned.
    This simply takes these two values for now:
    KeyDown = The function to execute when a key is pressed, if it returns anything. It hands the function an argument that is the pyxhookkeyevent class.
    KeyUp = The function to execute when a key is released, if it returns anything. It hands the function an argument that is the pyxhookkeyevent class.
    """

    def __init__(self):

        # Give these some initial values
        self.ison = {"shift":False, "caps":False}

        # Compile our regex statements.
        self.isshift = re.compile('^Shift')
        self.iscaps = re.compile('^Caps_Lock')
        self.shiftablechar = re.compile('^[a-z0-9]$|^minus$|^equal$|^bracketleft$|^bracketright$|^semicolon$|^backslash$|^apostrophe$|^comma$|^period$|^slash$|^grave$')
        self.logrelease = re.compile('.*')
        self.isspace = re.compile('^space$')

        # Assign default function actions (do nothing).
        self.KeyDown = lambda x: True
        self.KeyUp = lambda x: True

        # Hook to our display.
        self.local_dpy = display.Display()

        self.is_running = True

    def cancel(self):
         self.local_dpy.flush()

    def printevent(self, event):

        if event.Key == 'Escape':
            self.cancel()

        print(event)

    def propagate(self,event):

        window = self.local_dpy.get_input_focus()._data['focus']

        # If is KeyPress event
        if event.type == X.KeyPress:
            # Get event class
            event_class = KeyPress
        # If is KeyRelease event
        elif event.type == X.KeyRelease:
            # Get event class
            event_class = KeyRelease
        else:
            return

        # Create event object
        new_event = event_class(
            detail=event.detail,
            time=event.time,
            root=event.root,
            window=window,
            child=X.NONE,
            root_x=event.root_x,
            root_y=event.root_y,
            event_x=event.event_x,
            event_y=event.event_y,
            state=event.state,
            same_screen=event.same_screen,
        )

        # Send event
        self.local_dpy.send_event(window, new_event, propagate=True)

        # Flush
        self.local_dpy.flush()

    def processevents(self,event):

        # If is KeyPress event
        if event.type == X.KeyPress:
            # Get event object
            hookevent = self.keypressevent(event)

            # Call event handler
            self.KeyDown(hookevent)

        # If is KeyRelease event
        elif event.type == X.KeyRelease:
            # Get event object
            hookevent = self.keyreleaseevent(event)

            # Call event handler
            self.KeyUp(hookevent)

        # If is not KeyPress or KeyRelease event
        else:
            # Ignore
            return

       # Return
        return hookevent

    def keypressevent(self, event):
        matchto = lookup_keysym(self.local_dpy.keycode_to_keysym(event.detail, 0))

        if self.shiftablechar.match(lookup_keysym(self.local_dpy.keycode_to_keysym(event.detail, 0))): ## This is a character that can be typed.
            if self.ison["shift"] == False:
                keysym = self.local_dpy.keycode_to_keysym(event.detail, 0)
                return self.makekeyhookevent(keysym, event,True)
            else:
                keysym = self.local_dpy.keycode_to_keysym(event.detail, 1)
                return self.makekeyhookevent(keysym, event,True)
        else: ## Not a typable character.
            keysym = self.local_dpy.keycode_to_keysym(event.detail, 0)
            if self.isshift.match(matchto):
                self.ison["shift"] = self.ison["shift"] + 1
            elif self.iscaps.match(matchto):
                if self.ison["caps"] == False:
                    self.ison["shift"] = self.ison["shift"] + 1
                    self.ison["caps"] = True
                if self.ison["caps"] == True:
                    self.ison["shift"] = self.ison["shift"] - 1
                    self.ison["caps"] = False
            return self.makekeyhookevent(keysym, event,False)

    def keyreleaseevent(self, event):
        if self.shiftablechar.match(lookup_keysym(self.local_dpy.keycode_to_keysym(event.detail, 0))):
            printable=True
            if self.ison["shift"] == False:
                keysym = self.local_dpy.keycode_to_keysym(event.detail, 0)
            else:
                keysym = self.local_dpy.keycode_to_keysym(event.detail, 1)
        else:
            printable=False
            keysym = self.local_dpy.keycode_to_keysym(event.detail, 0)
        matchto = lookup_keysym(keysym)
        if self.isshift.match(matchto):
            self.ison["shift"] = self.ison["shift"] - 1
        return self.makekeyhookevent(keysym, event,printable)

    def asciivalue(self, keysym):
        asciinum = XK.string_to_keysym(lookup_keysym(keysym))
        if asciinum < 256:
            return asciinum
        else:
            return 0

    def makekeyhookevent(self, keysym, event,printable):
        storewm = self.xwindowinfo()
        if event.type == X.KeyPress:
            MessageName = "key down"
        elif event.type == X.KeyRelease:
            MessageName = "key up"
        return pyxhookkeyevent(storewm["handle"], storewm["name"], storewm["class"], lookup_keysym(keysym), self.asciivalue(keysym), printable, event.detail, MessageName)

    def xwindowinfo(self):
        try:
            windowvar = self.local_dpy.get_input_focus().focus
            wmname = windowvar.get_wm_name()
            wmclass = windowvar.get_wm_class()
            wmhandle = str(windowvar)[20:30]
        except:
            ## This is to keep things running smoothly. It almost never happens, but still...
          return {"name":None, "class":None, "handle":None}
        if (wmname == None) and (wmclass == None):
            try:
                windowvar = windowvar.query_tree().parent
                wmname = windowvar.get_wm_name()
                wmclass = windowvar.get_wm_class()
                wmhandle = str(windowvar)[20:30]
            except:
                ## This is to keep things running smoothly. It almost never happens, but still...
                return {"name":None, "class":None, "handle":None}
        if wmclass == None:
            return {"name":wmname, "class":wmclass, "handle":wmhandle}
        else:
            return {"name":wmname, "class":wmclass[0], "handle":wmhandle}

class pyxhookkeyevent:
    """This is the class that is returned with each key event.
    It simply creates the variables below in the class.

    Window = The handle of the window.
    WindowName = The name of the window.
    WindowProcName = The backend process for the window.
    Key = The key pressed, shifted to the correct caps value.
    Ascii = An ascii representation of the key. It returns 0 if the ascii value is not between 31 and 256.
    Printable = A flag saying whether the key pressed is supposed to "type" something out (as opposed to, say, cursor keys).
    ScanCode = Please don't use this. It differs for pretty much every type of keyboard. X11 abstracts this information anyway.
    MessageName = "key down", "key up".
    """

    def __init__(self, Window, WindowName, WindowProcName, Key, Ascii, Printable, ScanCode, MessageName):
        self.Window = Window
        self.WindowName = WindowName
        self.WindowProcName = WindowProcName
        self.Key = Key
        self.Ascii = Ascii
        self.ScanCode = ScanCode
        self.Printable = Printable
        self.MessageName = MessageName

    def __str__(self):
        return "Window Handle: " + str(self.Window) + "\nWindow Name: " + str(self.WindowName) + "\nWindow's Process Name: " + str(self.WindowProcName) + "\nKey Pressed: " + str(self.Key) + "\nAscii Value: " + str(self.Ascii) + "\nScanCode: " + str(self.ScanCode) + "\nMessageName: " + str(self.MessageName) + "\n"

#######################################################################
#########################END CLASS DEF#################################
#######################################################################


# this program breaks down into a csv where phosphosite orthologs could be lost in the PhosphositeOrthology program
# PhosphositePlus is being used to verify that my orthologs are correct but PSP does not have everything which is the
# reason for using dbPAF
# we want to make sure that the UniprotIDs contained in BOTH PSP and dbPAF end up in the orthologs list (ID_in_dbPAF)
# possible reasons why the candidates may not make it through are:

# dbPAF-OMA conversion table does not contain uniprotID -- see about checking both rev & unrev ID (ID_in_dbPAF_OMA_conversion_table)
# **** issues with having both reviewed and unreviewed ids in conversion table
# OMA program does not actually identify ortholog that PSP does -- in this case no fix (in_OMA_orthologs)
# or my alignment does not work well to identify phos orthologs (in_griffin_phos_orthologs)

import pandas as pd
from sqlalchemy import create_engine
import os

# Don't use excel file, interprets genes as dates
dbPAF_df = pd.read_table('../TOTAL.elm', dtype=object)
psp_df = pd.read_table('Phosphorylation_site_dataset.tab', dtype=object)

psp_df = psp_df.loc[psp_df['ORGANISM'].isin(['human', 'mouse', 'rat', 'fruit fly'])]  # filter out animals not in this list

comparison_df = psp_df[['SITE_GRP_ID', 'ORGANISM', 'ACC_ID', 'PROTEIN', 'MOD_RSD']].copy()  # copy only necessary columns
comparison_df.rename(columns={'SITE_GRP_ID': 'PSP_SITE_GRP_ID', 'ACC_ID': 'Uniprot_ACC_ID'}, inplace=True)  # give cols more specific names

comparison_df['Position'] = comparison_df.MOD_RSD.str[1:-2]  # 'S23-p'  --> '23'    # Position is also str in dbPAF_df
comparison_df['Type'] = comparison_df.MOD_RSD.str[0]  # 'S23-p'  --> 'S'

# check if the UniprotIDs in PhosphositePlus are also in dbPAF
comparison_df['ID_in_dbPAF'] = comparison_df['Uniprot_ACC_ID'].isin(dbPAF_df['Uniprot'])

# check if UniprotID, site, and amino acid type from PSP are also in dbPAF
comparison_df['ID_and_site_in_dbPAF'] = comparison_df['Uniprot_ACC_ID'].isin(dbPAF_df['Uniprot']) \
                                        & comparison_df['Position'].isin(dbPAF_df['Position']) \
                                        & comparison_df['Type'].isin(dbPAF_df['Type'])

# check if the UniprotIDs from PSP are in the dbPAF to OMA conversion table
oma_uniprot_df = pd.read_table('oma-uniprot_clean.txt', dtype=object)
comparison_df['ID_in_OMA-dbPAF_conversion_table'] = comparison_df['Uniprot_ACC_ID'].isin(oma_uniprot_df['uniprot'])

# check if UniprotID in OMA orthologs file
engine = create_engine('sqlite:///../phosphosite_orthology.db')  # create db in outside directory
table_iter = engine.execute("SELECT name FROM sqlite_master WHERE type='table';")

ortholog_species_pairs = ['CAEEL-HUMAN_UniprotIDs', 'DROME-HUMAN_UniprotIDs', 'MOUSE-HUMAN_UniprotIDs',
                          'RATNO-HUMAN_UniprotIDs', 'SCHPO-HUMAN_UniprotIDs', 'YEAST-HUMAN_UniprotIDs']

oma_ortholog_df = pd.DataFrame()
for i in ortholog_species_pairs:
    temp_oma_orth_df = pd.read_sql_table(i, engine)
    temp_oma_orth_df = temp_oma_orth_df.rename(columns={i[:5]: 'species'})  # rename organism to facilitate appending
    oma_ortholog_df = oma_ortholog_df.append(temp_oma_orth_df)

oma_ortholog_df = oma_ortholog_df.drop(['oma_group', 'orthology_type'], axis=1)

comparison_df['ID_in_OMA_orthologs'] = comparison_df['Uniprot_ACC_ID'].isin(oma_ortholog_df['HUMAN']) \
                                        | comparison_df['Uniprot_ACC_ID'].isin(oma_ortholog_df['species'])

# check in resulting griffin orthologs, after phosphosite alignment matching
phos_ortholog_df = pd.DataFrame()
for csv in os.listdir('aligned_phos_ortholog_pairs'):
    temp_phos_ortholog_df = pd.read_csv('aligned_phos_ortholog_pairs/' + csv)
    temp_phos_ortholog_df = temp_phos_ortholog_df.rename(columns={csv[:5]: 'species'})
    phos_ortholog_df = phos_ortholog_df.append(temp_phos_ortholog_df)  # df of ortholog animal-human uniprot ID pairs

# darn, this doesn's account for the phosphosite, only the ID?????
# use | for bitwise or rather than boolean 'or' for pandas, checks all the comparison_df id's in both human and animal
#  columns of phos_orthology_df
comparison_df['ID_in_griffin_phos_orthologs'] = comparison_df['Uniprot_ACC_ID'].isin(phos_ortholog_df['HUMAN']) | \
                                             comparison_df['Uniprot_ACC_ID'].isin(phos_ortholog_df['species'])

# print(dbPAF_df.head())
print('\n')
print(comparison_df.head())
print('\n')
#print(phos_ortholog_df.head())

 # TRY TO FINISH THIS COLUMN TOO:
#phos_ortholog_df[0] = phos_ortholog_df[0] + phos_ortholog_df[2] + phos_ortholog_df[8]
#phos_ortholog_df[1] = phos_ortholog_df[1] + phos_ortholog_df[3] + phos_ortholog_df[9]

#phos_ortholog_df.to_csv('is_this_work.csv', index=False)
#comparison_df['ID_and_site_in_griffin_phos_orthologs'] = comparison_df['Uniprot_ACC_ID'].isin(phos_ortholog_df[0]) &\
#                                             comparison_df['Position'].isin(phos_ortholog_df[2]) &\
#                                             comparison_df['Type'].isin(phos_ortholog_df[8]) | \
#                                             comparison_df['Uniprot_ACC_ID'].isin(phos_ortholog_df[1]) & \
#                                             comparison_df['Position'].isin(phos_ortholog_df[3]) & \
#                                             comparison_df['Type'].isin(phos_ortholog_df[9])

comparison_df.to_csv('db_comparison_results.csv', index=False)


import lz
from lz import *

fpath = work_path + 'reid/work.8.1'
fpath = osp.abspath(fpath)

def clean_empty():
    paths = glob.glob(fpath)
    for path in paths:
        size = os.stat(path).st_size
        # print(size)
        if size < 67:
            print(path, size)
            # shell(f'trash {path}')

clean_empty()
os.chdir((fpath))
paths = glob.glob('*')
assert len(paths) > 0, paths
# random.shuffle(paths)
res_dict = {}
cfg_dict = {}
cfgs = []
for path in paths:
    # name = args.logs_dir.split('/')[-1]
    name = path.split('/')[-1]
    print(name)
    # if 'only' in name: continue
    # if 'duke' not in name: continue
    # if 'cfisher' not in name or 'cu'  in name: continue
    # if 'tuning' not in name: continue
    # if not 'cu01' in name and not 'cuhk01' in name: continue

    assert osp.exists(path + '/conf.pkl'), path
    # if not osp.exists(path + '/conf.pkl'):
    #     continue
    args = pickle_load(path + '/conf.pkl')
    # if osp.exists(args.logs_dir + '/eval/res.json'):
    # res_path = args.logs_dir + '/eval/res.json'
    # else:
    args.logs_dir = fpath + '/' + osp.basename(osp.abspath(args.logs_dir))
    res_path = args.logs_dir + '/res.json'

    if not osp.exists(res_path):
        res, _ = shell(f'tail {osp.dirname(res_path)+"/log.txt"} -n 1')
        try:
            import json

            json.loads(res)
        except:
            continue
    else:
        # print(args.logs_dir)
        res = json_load(res_path)
    # print(args, res)
    res_dict[name] = res
    cfg_dict[name] = args
    cfgs.append(args)

df_acc = pd.DataFrame(res_dict).T
df_acc *= 100

def is_all_same(lst):
    lst = [lsti if not isinstance(lsti, np.ndarray) else lsti.tolist() for lsti in lst]
    res = [lsti == lst[0] for lsti in lst]
    # try:
    return np.asarray(res).all()
    # except Exception as e:
    #     print(e)

df = pd.DataFrame(cfgs)
if len(cfgs) == 1:
    raise ValueError
res = []
for j in range(df.shape[1]):
    if not is_all_same(df.iloc[:, j].tolist()):
        res.append(j)
res = [df.columns[r] for r in res]
df_cfg = df[res]

print(df_cfg.head())
df_cfg.index = df_cfg.logs_dir.str.replace(fpath, '').str.replace(osp.abspath(work_path + 'reid/work'), '').str.strip(
    '/')
print(df_cfg.head())
print(df_acc.head())
df_final = pd.concat((  df_cfg, df_acc), axis=1)
df_final = df_final[['top-1', 'top-5', 'top-10', 'mAP'] + list(df_final.keys())]
df_final.to_excel(work_path + 't.xlsx')
exit(0)

def f1(x):
    return r'%.2f' % x

t = df_acc[['top-1',
            'mAP',
            # 'top-5', 'top-10',
            ]]
print(t.to_latex(formatters=[f1, ] * 3))
# print(df[['top-1.rk',
#           # 'mAP.rk',
#           'top-5.rk','top-10.rk',
#           ]].to_latex(formatters=[f1, ] * 3))
# print(t)
# print(df[['top-1.rk',
#           # 'mAP.rk',
#           'top-5.rk','top-10.rk',
#           ]])


from django.shortcuts import render, redirect
from django.http import HttpResponse, HttpResponseRedirect
import datetime

def list_movies(request):
    # find out city of the client
    if 'city' in request.COOKIES:
        cityname = request.COOKIES['city']
        return render(request, 'list_movies.html', {'city': cityname})
    else:
        return redirect("/demo/selectcity")

def select_city(request):
    if request.method == "GET":
        return render(request,'select_city.html')
    else:
        cityname = request.POST['city']
        response = HttpResponseRedirect("/demo/movies")
        response.set_cookie('city',cityname,
                expires = datetime.datetime.now() + datetime.timedelta(days=7))
        return response


"""
File used to visualize various aspects of nab datasets.

It was used to determine the time delatas between the observations in
time-series.

Notes:
realAWSCloudwatch/ec2_cpu_utilization_fe7f93.csv -> ok
realAWSCloudwatch/rds_cpu_utilization_e47b3b.csv -> ok
realAWSCloudwatch/grok_asg_anomaly.csv -> ok
realAWSCloudwatch/iio_us-east-1_i-a2eb1cd9_NetworkIn.csv -> ok

realKnownCause/machine_temperature_system_failure.csv -> file corretto -> ok
realAWSCloudwatch/elb_request_count_8c0756.csv -> linear interpolation -> ok

realKnownCause/ambient_temperature_system_failure.csv ->
    10 delta diversi -> necessaria correzione stagionale con STL -> X

"""

import glob
import json
import os
import re
from collections import defaultdict
from pprint import pprint  # noqa

import matplotlib.pyplot as plt
import pandas as pd
from ddm_project.settings import datasets_dir
from pandas.plotting import register_matplotlib_converters

register_matplotlib_converters()

def get_key(path):
    """Get key for `labels` dict."""
    return re.search(r"[^" + os.sep + "]+" + os.sep + r"[^" + os.sep + "]+\.csv", path).group(0)

root_dir = os.path.join(datasets_dir, "nab_dataset", "data")
label_dir = os.path.join(datasets_dir, "nab_dataset", "labels")
label_file = "combined_labels.json"

label_path = label_dir + os.sep + label_file
with open(label_path) as f:
    labels = json.load(f)

data_paths = glob.glob(root_dir + os.sep
                       + "realAWSCloudwatch" + os.sep + "*.csv")
data_paths += glob.glob(root_dir + os.sep
                        + "realKnownCause" + os.sep + "*.csv")

pattern = re.compile(os.sep + r'([\d\w_-]*)\.')
data_filenames = [re.search(pattern, p).group(1) for p in data_paths]

# ===========================
_fname = data_paths[15]
key = get_key(_fname)
anomalies = labels[key]
df = pd.read_csv(_fname, parse_dates=['timestamp'], index_col='timestamp')
values = df.loc[[pd.to_datetime(a) for a in anomalies]]
plt.plot(df.index.to_pydatetime(), df['value'])
plt.scatter(anomalies, values, marker="x", color="orange", s=80, zorder=10)

plt.title(key)
plt.show()
# raise SystemExit
# ===========================

plot = True
if plot:
    fig, axes = plt.subplots(4, 6)

all_deltas = {}
equally_spaced = []
non_equally_spaced = []
for i, path in enumerate(data_paths):
    name = get_key(path)  # path.split('/', 5)[-1]
    # if name.split("/")[1].startswith("old_"):
    #     continue
    df = pd.read_csv(path, parse_dates=['timestamp'], index_col='timestamp')
    deltas = defaultdict(lambda: 0)
    first_delta = df.index[1] - df.index[0]
    print(path)
    print("first delta:", first_delta)

    for j in range(1, len(df)):
        delta = df.index[j] - df.index[j - 1]
        deltas[delta] += 1
        if delta != first_delta:
            print("{}: {}".format(j, delta))
    all_deltas[name] = deltas
    if len(deltas) == 1:
        equally_spaced.append(name)
    else:
        non_equally_spaced.append(name)
    print("")

    max_count = 0
    max_delta = None
    total_counts = 0
    for delta, count in deltas.items():
        if count > max_count:
            max_count = count
            max_delta = delta
        total_counts += count

    # total = sum([count for _, count in deltas.items()])
    assert total_counts == len(df) - 1,\
        "Expected {} to be {}.".format(total_counts, len(df) - 1)
    if plot:
        ax = axes[i // 6, i % 6]
        # fig, ax = plt.subplots()
        ax.plot(df.index.to_pydatetime(), df['value'])
        anomalies = labels[name]
        values = df.loc[[pd.to_datetime(a) for a in anomalies]]
        ax.scatter(anomalies, values, marker="x",
                   color="orange", s=80, zorder=10)
        if len(deltas) != 1:
            ax.set_facecolor((235 / 255, 64 / 255, 52 / 255, 0.1))
            y_min, y_max = ax.get_ylim()
            print("min max:", y_min, y_max)
            for j in range(1, len(df)):
                delta = df.index[j] - df.index[j - 1]
                if delta != max_delta:
                    # print(delta)
                    # print(df.index[j - 1], df.index[j])
                    # print(df.index[j - 1:j + 1])
                    # raise SystemExit
                    ax.fill_between(
                        df.index[j - 1:j + 1],
                        y_min, y_max,
                        color='r', alpha=0.5)
            ax.set_ylim(y_min, y_max)

        ax.set_title(name.replace(os.sep, ' \n '), fontsize=7)
        ax.xaxis.set_ticklabels([])
        ax.xaxis.set_visible(False)
        ax.tick_params(axis='both', which='major', labelsize=6)
        ax.tick_params(axis='both', which='minor', labelsize=6)
        ax.yaxis.get_offset_text().set_fontsize(6)

if plot:
    # plt.show()
    fig = plt.gcf()
    fig.set_size_inches(18.5, 10.5)
    fig.set_tight_layout(True)
    plt.show()
    # plt.savefig("nab_dataset_plots.svg", transparent=False, dpi=100,
    #             frameon=False, bbox_inches='tight', pad_inches=0)

print("")
print("=" * 80)
print("")
for name, delta in all_deltas.items():
    print(name)
    for k, v in delta.items():
        print("{}: {}".format(k, v))

print("")
print("=" * 80)
print("")

for name, delta in all_deltas.items():
    print("{}: {}".format(name, len(delta)))
# '2014-04-11 00:00:00'
# ax.plot([datetime.datetime('2014-04-10 07:15:00.000000'),
#   datetime.datetime('2014-04-11 16:45:00.000000')], [50, 50], color='orange')

# ax.plot([datetime.datetime(year=2014, month=4, day=10, hour=7, minute=15),
#   datetime.datetime(year=2014, month=4, day=11, hour=16, minute=45)],
#   [50, 50], color='orange')
# ax.scatter([datetime.datetime(year=2014, month=4, day=11)], [60],
#   color='red')

# plt.show()


from PySide6 import QtWidgets
from data.data_manager import DataManager
from data.chess_structs import ChessGame, ChessPlayer
from gui.edit.wid_edit_player import PlayerEditDialog
from helpers.gui import ShowModalException, SpawnModal

class PlayersWidget(QtWidgets.QWidget):
    ''' Widgets that shows players '''

    def __init__(self, db_manager: DataManager, parent: QtWidgets.QWidget):
        super().__init__(parent)
        self.db_manager = db_manager

        self.main_layout = QtWidgets.QHBoxLayout(self)
        self.setLayout(self.main_layout)

        # Datasheet
        self.table_widget = QtWidgets.QTableWidget(self)
        self.table_widget.setSizeAdjustPolicy(QtWidgets.QAbstractScrollArea.AdjustToContents)
        # self.table_widget.horizontalHeader().setSectionResizeMode(QtWidgets.QHeaderView.Stretch)
        self.table_widget.setSelectionBehavior(QtWidgets.QAbstractItemView.SelectionBehavior.SelectRows)
        self.table_widget.setSelectionMode(QtWidgets.QAbstractItemView.SelectionMode.SingleSelection)
        self.table_widget.setEditTriggers(QtWidgets.QAbstractItemView.NoEditTriggers)

        headers = list(ChessPlayer.getHeaders())
        self.table_widget.setColumnCount(len(headers))
        self.table_widget.setHorizontalHeaderLabels(headers)

        self.__RefreshTableData()
        self.main_layout.addWidget(self.table_widget)

        # Buttons
        self.buttons_widget = self.__CreateButtonsWidget()
        self.main_layout.addWidget(self.buttons_widget)

    def __CreateButtonsWidget(self):
        buttons_widget = QtWidgets.QWidget(self)
        buttons_layout = QtWidgets.QVBoxLayout(buttons_widget)
        buttons_widget.setLayout(buttons_layout)

        # Top
        new_btn = QtWidgets.QPushButton("New (N)", buttons_widget)
        delete_btn = QtWidgets.QPushButton("Delete (D)", buttons_widget)
        edit_btn = QtWidgets.QPushButton("Edit (E)", buttons_widget)

        new_btn.clicked.connect(self.__OnNewPlayer)
        delete_btn.clicked.connect(self.__OnDeletePlayer)
        edit_btn.clicked.connect(self.__OnEditPlayer)

        # Bottom
        showgames_btn = QtWidgets.QPushButton("Show games (S)", buttons_widget)

        showgames_btn.clicked.connect(self.__OnShowPlayerGame)

        # Add all to widget
        buttons_layout.addWidget(new_btn)
        buttons_layout.addWidget(delete_btn)
        buttons_layout.addWidget(edit_btn)
        buttons_layout.addStretch()
        buttons_layout.addWidget(showgames_btn)

        return buttons_widget

    def __GetSelectedPlayerID(self):
        rows = self.table_widget.selectionModel().selectedRows()
        if len(rows):
            return int(rows[0].data())
        else:
            return None

    def __GetSelectedPlayerData(self):
        id = self.__GetSelectedPlayerID()
        if id:
            return next((p for p in self.player_data if p.id == id), None)

    def __OnNewPlayer(self):
        dialog = PlayerEditDialog(self.db_manager, self)
        if dialog.exec_():
            self.__RefreshTableData()

    def __OnDeletePlayer(self):
        id = self.__GetSelectedPlayerID()
        try:
            result = SpawnModal(
                "I have for you imporant question",
                "Are you sure?",
                QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.Cancel
            )
            if result == QtWidgets.QMessageBox.Yes:
                self.db_manager.GetDatabaseAPI().DeletePlayer(id)
                self.__RefreshTableData()
        except Exception as e:
            ShowModalException()

    def __OnEditPlayer(self):
        player = self.__GetSelectedPlayerData()
        if not player:
            SpawnModal('Error', 'Cannot find currently selected player.', QtWidgets.QMessageBox.Ok)
            return

        dialog = PlayerEditDialog(self.db_manager, self, player)
        if dialog.exec_():
            self.__RefreshTableData()

    def __OnShowPlayerGame(self):
        print("!!!")

    def RefreshData(self):
        self.__RefreshTableData()

    def __RefreshTableData(self):
        self.player_data = list(self.db_manager.GetDatabaseAPI().GetPlayers())
        self.table_widget.setRowCount(len(self.player_data))
        for i, player in enumerate(self.player_data):
            for j, data in enumerate(player.getData()):
                new_item = QtWidgets.QTableWidgetItem(str(data))
                self.table_widget.setItem(i, j, new_item)

        # self.table_widget.setSizeAdjustPolicy(QtWidgets.QAbstractScrollArea.AdjustToContents)
        # self.table_widget.resizeColumnsToContents()
        # self.table_widget.horizontalHeader().setStretchLastSection(True)