import seaborn as sns
import pandas as pd
import matplotlib.pyplot as plt
import requests
import io
import math
from scipy import stats
import numpy as np
import researchpy as rp
import statsmodels.api as sm
import pprint

# -----------------------------------------------------------------------------
# ---------------------------- EXTRACCIÓN DE DATOS ----------------------------
# -----------------------------------------------------------------------------
string_csv = requests.get("https://gitlab.com/drvicsana/ciencia-datos/-/raw/master/datasets/fifa/players_20.csv").content
string_stream = io.StringIO(string_csv.decode("utf-8"))
player_df = pd.read_csv(string_stream)

# -----------------------------------------------------------------------------
# ---------------------------- EXPLORACIÓN DE DATOS ---------------------------
# -----------------------------------------------------------------------------

# numero de filas y columnas del df
pprint.pprint(player_df.shape)

# descripcion de los datos
pprint.pprint(player_df.describe())

# encabezado de los datos
pprint.pprint(player_df.head(5))

# descripcion categorica
print(player_df.describe(include=["object"]))

# -----------------------------------------------------------------------------
# ---------------------------- FILTRADO DE DATOS ------------------------------
# -----------------------------------------------------------------------------

# filtrado para equipos españoles
spanish_teams = ["Real Madrid", "FC Barcelona", "Atlético Madrid", "Valencia CF", "Villarreal CF", "Real Valladolid CF",
                 "Athletic Club de Bilbao", "Cádiz CF", "Granada CF", "Real Betis", "RCD Espanyol", "Real Sociedad", "Deportivo Alavés", "Elche CF", "Sevilla FC",
                 "CA Osasuna", "SD Eibar", "RC Celta", "Levante UD", "SD Huesca" ]

# posiciones posibles de cada jugador
defender_positions = [ "SW", "RWB", "LWB", "RB", "LB", "CB" ]
midfield_positions = [ "DM", "RW", "LW", "LM", "RM", "CM", "AM" ]
forward_positions = [ "CF", "RF", "LF", "ST" ]
goalkeeper_positions = ["GK"]

# crear nuevo df con jugadores españoles
spanish_players_df = player_df[ player_df.club.isin(spanish_teams) ]

# descripcion de los datos
pprint.pprint(spanish_players_df.describe())

# encabezado de los datos
pprint.pprint(spanish_players_df.head(5))

# descripcion categorica
print(spanish_players_df.describe(include=["object"]))

# -----------------------------------------------------------------------------
# ----------------------- CREACIÓN DE NUEVOS DATOS (COLUMNAS) -----------------
# -----------------------------------------------------------------------------

########### Funciones para definir la posición principal de un jugador ###########

# mete las posiciones de un jugador en una lista
def from_string_position_to_list(positions_string):
    return positions_string.replace(" ", "").split(",")

# dice si pertenece a la lista prospective
def is_position(positions_string, prospective_positions):
  list_positions = from_string_position_to_list(positions_string)
  found = False
  for position in list_positions :
    if position in prospective_positions :
      found = True
  return found

# decide a que posición pertenece
def determine_main_position(positions_string):
  count_defender = 0
  count_midfield = 0
  count_forward = 0
  count_goalkeeper = 0
  list_positions = from_string_position_to_list(positions_string)
  for position in list_positions :
    if position in defender_positions :
      count_defender = count_defender + 1
    elif position in midfield_positions :
      count_midfield = count_midfield + 1
    elif position in forward_positions :
      count_forward = count_forward + 1
    elif position in goalkeeper_positions :
      count_goalkeeper = count_goalkeeper + 1

  return max( [ (count_defender, "DF"), (count_midfield, "MF"), (count_forward, "FW"), (count_goalkeeper, "GK") ] )[1]

# funciones lambda que dice si un jugador es o no de una posición (esto testea todas las posiciones en las que juega el jugador indicado).
is_defender = lambda s : is_position(s, defender_positions)
is_midfielder = lambda s : is_position(s, midfield_positions)
is_forward = lambda s : is_position(s, forward_positions)
is_goalkeeper = lambda s: is_position(s, goalkeeper_positions)

# crea la columna "main_position" en la cual muestra la posición del player
spanish_players_df = spanish_players_df.assign( main_position = spanish_players_df.apply( lambda row: determine_main_position(row.player_positions), axis=1 ) )

# * determina si en algun caso el jugador es defensa * (utiliza la lista con todas las posiciones en las que juega el jugador).
spanish_players_df = spanish_players_df.assign( is_defender = spanish_players_df.apply( lambda row : is_position(row.player_positions, defender_positions), axis=1 ))

# determina si el jugador tiene como posicion principal defensa
spanish_players_df = spanish_players_df.assign( is_defender = spanish_players_df.apply( lambda row : True if ("DF" == row.main_position) else False, axis=1 ))
# determina si el jugador tiene como posicion principal delantero
spanish_players_df = spanish_players_df.assign( is_forward = spanish_players_df.apply( lambda row : True if ("FW" == row.main_position) else False, axis=1 ))

########### Definir de que continente es cada jugador en una nueva columna ###########

# definir cada continente
country_to_continent = { "Argentina": "America", "Slovenia": "Europe", "Belgium":"Europe", "Germany": "Europe", "Croatia":"Europe",
                        "Spain": "Europe", "Uruguay":"America", "France":"Europe", "Brazil":"America", "Costa Rica":"America",
                        "Netherlands":"Europe", "Wales": "Europe", "Colombia":"America", "Chile":"America", "Serbia":"Europe",
                        "Portugal":"Europe", "Ghana":"Africa", "Montenegro":"Europe", "Mexico":"America", "Denmark":"Europe",
                        "Central African Rep.":"Africa", "Czech Republic":"Europe", "Algeria":"Africa", "Israel":"Asia",
                        "England":"Europe", "Slovakia":"Europe", "Dominican Republic":"America", "Cameroon":"Africa",
                        "China PR":"Asia", "Japan":"Asia", "Russia":"Asia", "Norway":"Europe", "Turkey":"Asia",
                        "FYR Macedonia":"Europe", "Senegal":"Africa", "Morocco":"Africa", "Italy":"Europe",
                        "Ukraine":"Europe", "Korea Republic":"Asia", "Nigeria":"Africa", "Sweden":"Europe", "Venezuela":"America",
                        "Bosnia Herzegovina":"Europe", "Ivory Coast":"Africa", "Romania":"Europe", "Equatorial Guinea":"Africa",
                        "Ecuador":"America", "Albania":"Europe", "Mauritania":"Africa", "Benin":"Africa", "Switzerland":"Europe",
                        "DR Congo":"Africa"}

spanish_players_df.apply( lambda row: row.age*2, axis=1 )

# crea la columna "continent" en la cual muestra el continente del player
spanish_players_df = spanish_players_df.assign( continent = spanish_players_df.apply(lambda row: country_to_continent[row.nationality], axis=1) )

# -----------------------------------------------------------------------------
# ---------------- ESTADISTICA DESCRIPTIVA (DATOS CATEGÓRICOS) ----------------
# -----------------------------------------------------------------------------

# frecuencia absoluta por posición principal
pprint.pprint(spanish_players_df.main_position.value_counts())

# frecuencia relativa por posicion principal
pprint.pprint(spanish_players_df.main_position.value_counts(normalize=True))

# gráfico de queso de jugadores por posición
count_data = spanish_players_df.main_position.value_counts()
plt.figure(figsize=(10, 10))
fig = plt.pie( count_data, labels = count_data.index, autopct="%.2f", textprops={"fontsize":14} )

# gráfico de queso de jugadores por continente
count_data = spanish_players_df.continent.value_counts()
plt.figure(figsize=(10,10))
fig = plt.pie( count_data, labels = count_data.index, autopct="%.2f", textprops={"fontsize":14} )

# gráfico de barras donde se muestran la frecuencia de cada posición
count_data = spanish_players_df.main_position.value_counts(normalize=True)
axes = sns.barplot( x=count_data.index, y=count_data )
axes.set_xlabel("Position")
axes.set_ylabel("Percentage")
axes.set_title("Player positions")

# gráfico de barras donde se muestran la frecuencia del continente
count_data = spanish_players_df.continent.value_counts(normalize=True)
axes = sns.barplot( x=count_data.index, y=count_data )
axes.set_xlabel("Continent")
axes.set_ylabel("Percentage")
axes.set_title("Player continent")

#### TABLA DE CONTINGENCIAS ####

# tabla de contingencia del pie que usan los jugadores por posición principal
pd.crosstab( spanish_players_df.preferred_foot, spanish_players_df.main_position )

# lo mismo pero normalizado
pd.crosstab( spanish_players_df.preferred_foot, spanish_players_df.main_position, normalize=True )

# frecuencias condicionada a la preferencia del pie
pd.crosstab( spanish_players_df.preferred_foot, spanish_players_df.main_position, normalize = "index" )

# ahora las frecuencias condicionadas a la posición que juegan
pd.crosstab( spanish_players_df.preferred_foot, spanish_players_df.main_position, normalize = "columns" )

# ahora las frecuencias condicionadas al continente al que pertenecen
pd.crosstab( spanish_players_df.preferred_foot, spanish_players_df.continent, normalize = "columns" )

# -----------------------------------------------------------------------------
# ---------------- ESTADISTICA DESCRIPTIVA (DATOS NUMÉRICOS) ------------------
# -----------------------------------------------------------------------------

#### HISTOGRAMAS ####

# histograma del overall del conjunto de datos, bins: numero de cubetas en el histograma
sns.histplot(data=spanish_players_df, x="overall", bins=30)

# con probabilidad (la suma de las alturas de las cubetas será 1)
sns.histplot(data=spanish_players_df, x="overall", bins=30, stat="probability")

# con densidad (el área del histograma será 1)
sns.histplot(data=spanish_players_df, x="overall", bins=30, stat="density")

# comparación de distintos histogramas
# - x: datos a analizar (variable aleatoria) ,
# - hue: crea un histograma para cada clase de esta columna (diferencia entre histogramas).
sns.histplot(data=spanish_players_df, x="overall", bins=30, hue="preferred_foot", stat="density", common_norm=False)

# histograma por edad (densidades)
sns.histplot(data=spanish_players_df, x="age", bins=30, stat="density")

# histograma por salarios (densidades)
sns.histplot(data=spanish_players_df, x="wage_eur", bins=40, stat="density")

# comparación de histogramas con variable aleatoria edad diferenciando por dependencia de pie dominante
sns.histplot(data=spanish_players_df, x="age", bins=30, hue="preferred_foot", stat="density", common_norm=False)

#### BOX WHISKERS ####

# Caja Box Whiskers con info de los jugadores
sns.boxplot( x=spanish_players_df.overall )

# Dos cajas Box Whiskers en el mismo plot
sns.boxplot( x=spanish_players_df.preferred_foot, y=spanish_players_df.overall )

# -----------------------------------------------------------------------------
# ----------------------- INFERENCIA SOBRE PROPORCIONES -----------------------
# -----------------------------------------------------------------------------

# cogemos una muestra
small_sample = player_df.sample(n=100)

# calculamos la proporcion en la muestra (contamos los jugadores zurdos)
p = small_sample[ small_sample.preferred_foot == "Left" ].shape[0]/100
print(p)

#### INTERVALO DE CONFIANZA ####

# Genera 10 datos aleatorios procedentes de una normal con media 20 y desviación típica 2
random_data = stats.norm.rvs(loc=20, scale=2, size= 10)
print(random_data)
# Obtiene el valor de la función de densidad para el punto 21.3 en una normal con media 20 y desviación típica 2
print( stats.norm.pdf( 21.3, loc=20, scale=2 ) )
# Obtiene la probabilidad de observar valores menores o iguales a cero en una distribución normal con media 0 y desviación típica 1
print( stats.norm.cdf( 0 ,loc=0, scale=1 ) )
# Obtiene el percentil 97.5 (mayor o igual que el 97.5% de las posibles observaciones) para una normal con media 0 y desviación típica 1
print( stats.norm.ppf( 0.975, loc = 0, scale = 1 ) )
# Obtiene los extremos del intervalo centrado en la media que contiene el 95% de las observaciones en una normal con media 0 y desviación típica 1
print( stats.norm.interval( 0.95, loc=0, scale=1 ) )
# construimos el intervalo de confianza
lower_bound = 0
upper_bound = 0
Zalpha = stats.norm.ppf( 0.975, loc=0, scale=1 )
deviation = Zalpha * math.sqrt( p*(1-p)/100 )
interval = ( p - deviation, p + deviation )
print(interval)

# parametro poblacional de los jugadores de FIFA zurdos
true_p = player_df[ player_df.preferred_foot == "Left" ].shape[0]/player_df.shape[0]
print(true_p)

# intervalo de confianza de 95% con 100 muestras
small_sample = player_df.sample(n=100)
p = small_sample[ small_sample.preferred_foot == "Left" ].shape[0]/100
Zalpha = stats.norm.ppf( 0.975, loc=0, scale=1 )
deviation = Zalpha * math.sqrt( p*(1-p)/100 )
interval = ( p - deviation, p + deviation )
print("Intervalo de 95% (100 muestras) -> ",interval)
print("Tamaño -> ",((p + deviation)-(p - deviation)))

# intervalo de confianza de 95% con 1000 muestras
sample = player_df.sample(n=1000)
p1 = sample[ sample.preferred_foot == "Left" ].shape[0]/1000
Zalpha = stats.norm.ppf( 0.975, loc=0, scale=1 )
deviation_1 = Zalpha * math.sqrt( p1*(1-p1)/100 )
interval = ( p1 - deviation_1, p1 + deviation_1 )
print("Intervalo de 95% (1000 muestras) -> ",interval)
print("Tamaño -> ",((p1 + deviation_1)-(p1 - deviation_1)))

# se hacen 100 muestras en las que se testea si el intervalo de confianza contiene o no el valor, se representa una linea roja
# donde, si cruza con el intervalo es que ha acertado, tiene un % de acierto del 95%.
number_success = 0
intervals = []
n_rep = 100
for i in range(n_rep):
  n = 100

  small_sample = player_df.sample(n=n)

  p = small_sample[ small_sample.preferred_foot == "Left" ].shape[0]/n

  lower_bound = 0
  upper_bound = 0
  Zalpha = stats.norm.ppf( 0.975, loc=0, scale=1 )
  deviation = Zalpha * math.sqrt( p*(1-p)/n )
  interval = ( p - deviation, p + deviation )

  intervals.append(interval)

  if true_p >= interval[0] and true_p <= interval[1]:
    number_success = number_success + 1

print(number_success/n_rep)

plt.figure(figsize=(30,10))
plt.errorbar( x=list(range(1, n_rep+1)), y=[ (x[1]+x[0])/2 for x in intervals ], yerr=[ (x[1]-x[0])/2 for x in intervals ], fmt='o' )
plt.hlines( xmin=1, xmax=100, y=true_p, colors="red" )

#### PRUEBAS DE NORMALIDAD ####

sample = player_df.sample(n=100)

# prueba de normalidad de asimetria
#   H0:  El coeficiente de asimetría (CA) de la población sigue una asimetria propia de una población normal (CA = 0)
#   H1:  El coeficiente de asimetría (CA) de la población sigue una asimetría diferente de una población normal (CA != 0)
# si pvalue<=0.05 -> H1
# si pvalue>0.05 -> H0
print( stats.skewtest( sample.overall ) )

# prueba de normalidad de curtosis
#   H0:  El coeficiente de curtosis (CC) de la población sigue una curtosis propia de una población normal (CC=0)
#   H1:  El coeficiente de curtosis (CC) de la población tiene una curtosis diferente a la de una población normal (CC!=0)
# si pvalue<=0.05 -> H1
# si pvalue>0.05 -> H0
print( stats.kurtosistest( sample.overall ) )

# prueba de Kolmogorov-Smirnov
#   H0:  La función de distribución acumulada se comporta como la función de distribución acumulada de una distribución normal.
#   H1: La función de distribución acumulada difiere de la función de distribución acumulada de una distribución normal.
# si pvalue<=0.05 -> H1
# si pvalue>0.05 -> H0
stats.kstest( (sample.overall-sample.overall.mean())/sample.overall.std(), 'norm'  )

# prueba de Shapiro-Wilk
#   H0:  La variable aleatoria estudiada procede de una población normal
#   H1: La variable aleatoria estudiada no procede de una población normal
# si pvalue<=0.05 -> H1
# si pvalue>0.05 -> H0
stats.shapiro( sample.overall )

#### PAPEL PROBABILISTICO ####

# gráfico probabilistico (papel probabilistico) de overall con respecto a una normal
plot = sm.ProbPlot( sample.overall, dist="norm" )
plot.probplot(line="r")

#### PRUEBAS DE NORMALIDAD Y MUESTRAS GRANDES ####

# vamos a ver que ocurre con las pruebas en las que no pudimos
# descartar normalidad cuando la muestra es proporcionada es grande
new_sample1 = player_df.sample(n=1000)
new_sample2 = player_df.sample(n=2000)
new_sample3 = player_df.sample(n=4000)

print( stats.shapiro( new_sample1.overall ) )
print( stats.shapiro( new_sample2.overall ) )
print( stats.shapiro( new_sample3.overall ) )

print(stats.kstest( (new_sample1.overall-new_sample1.overall.mean())/new_sample1.overall.std(), 'norm'  ))
print(stats.kstest( (new_sample2.overall-new_sample2.overall.mean())/new_sample2.overall.std(), 'norm'  ))
print(stats.kstest( (new_sample3.overall-new_sample3.overall.mean())/new_sample3.overall.std(), 'norm'  ))

# analisis de toda la población
# (kde -> kernel density estimate: estimiacion de la funcion de densidad no parametrica)
axes = sns.kdeplot( (player_df.overall - player_df.overall.mean() )/(player_df.overall.std()), common_norm=False  )
# representación de una normal para comparar con el kde
sns.lineplot( y=[ stats.norm.pdf(x) for x in np.arange(-3, 3, 0.01) ], x= [x for x in np.arange(-3, 3, 0.01)] )
# histograma
sns.histplot(player_df.overall, bins=25)
# gráfico de probabilidad normal
plot = sm.ProbPlot( player_df.overall, dist="norm" )
plot.probplot(line="r")

# -----------------------------------------------------------------------------
# ------------ INFERENCIA SOBRE LA MEDIA DE POBLACIONES NORMALES --------------
# -----------------------------------------------------------------------------

#### INTERVALO DE CONFIANZA CON T DE STUDENT ####

# genera 10 datos aleatorios procedentes de una t de Student con 4 grados de libertad
# rvs -> random variates of given type
random_data = stats.t.rvs( 4, size= 10)
print(random_data)

# obtiene el valor de la función de densidad para el punto 1 en una t de Student con 4 grados de libertad
print( stats.t.pdf( 1, 4 ) )

# obtiene la probabilidad de observar valores menores o iguales a cero en una distribución t de student con 4 grados de libertad
print( stats.t.cdf( 0, 4 ) )

# obtiene el percentil 97.5 (mayor o igual que el 97.5% de las posibles observaciones) para una t de student con 4 grados de libertad
print( stats.t.ppf( 0.975, 4 ) )

# obtiene los extremos del intervalo centrado en 0 que contiene el 95% de las observaciones en una t de student con 4 grados de libertad
print( stats.t.interval( 0.95, 4 ) )

# intervalo de confianza del 95%
lower_bound = 0
upper_bound = 0
x = sample.overall.mean()
n = sample.shape[0]
t = stats.t.ppf(0.975, n-1 )
s = sample.overall.std()
lower_bound = x - t * s/math.sqrt(n)
upper_bound = x + t * s/math.sqrt(n)
print(lower_bound, upper_bound)
print("media muestral -> ",x)

# analisis de normalidad para el peso en kg de los jugadores
sample = player_df.sample(n=100)
print(" alpha -> 0.05 \n")
print( stats.skewtest( sample.weight_kg ) , "\n -> H0:  El coeficiente de asimetría (CA) de la población sigue una asimetria propia de una población normal (CA = 0)\n" )
print( stats.kurtosistest( sample.weight_kg ) , "\n -> H0:  El coeficiente de curtosis (CC) de la población sigue una curtosis propia de una población normal (CC=0)\n")
print( stats.kstest( (sample.weight_kg-sample.weight_kg.mean())/sample.weight_kg.std(), 'norm'  ) , "\n -> H0:  La función de distribución acumulada se comporta como la función de distribución acumulada de una distribución normal.\n")
print( stats.shapiro( sample.weight_kg ) , "\n -> H0:  La variable aleatoria estudiada procede de una población normal\n")
plot = sm.ProbPlot( sample.weight_kg, dist="norm" )
plot.probplot(line="r")
print("Despues de hacer varios tipos de pruebas de normalidad y todas diciendo que la distribución sigue una normal, concluimos que sigue una normal.\n")
# intervalo de confianza para la media de la variable weight_kg
lower_bound = 0
upper_bound = 0
x = sample.weight_kg.mean()
n = sample.shape[0]
t = stats.t.ppf(0.975, n-1 )
s = sample.weight_kg.std()
lower_bound = x - t * s/math.sqrt(n)
upper_bound = x + t * s/math.sqrt(n)
print("(lower_bound: ",lower_bound,", upper_bound: ", upper_bound,")")
print("media muestral -> ",x)

# -----------------------------------------------------------------------------
# ----------- INFERENCIA SOBRE LA VARIANZA EN POBLACIONES NORMALES ------------
# -----------------------------------------------------------------------------

#### INTERVALO DE CONFIANZA DE CHI-CUADRADO ####

# genera 10 datos aleatorios procedentes de una Chi cuadrado con 4 grados de libertad
random_data = stats.chi2.rvs( 4, size= 10)
print(random_data)

# obtiene el valor de la función de densidad para el punto 2 en una chi cuadrado con 4 grados de libertad
print( stats.chi2.pdf( 2, 4 ) )

# obtiene la probabilidad de observar valores menores o iguales a 1 en una distribución chi cuadrado con 4 grados de libertad
print( stats.chi2.cdf( 1, 4 ) )

# obtiene el percentil 97.5 (mayor o igual que el 97.5% de las posibles observaciones) para una chi cuadrado 4 grados de libertad
print( stats.chi2.ppf( 0.975, 4 ) )

# obtiene los extremos del intervalo que contiene el 95% de las observaciones en una chi cuadrado con 4 grados de libertad
print( stats.chi2.interval( 0.95, 4 ) )

# intervalo de confianza 95% para la varianza poblacional de overall
lower_bound = 0
upper_bound = 0
n = sample.shape[0]
s2 = sample.overall.std()
chi1 = stats.chi2.ppf(1-0.05/2, n - 1 )
chi2 = stats.chi2.ppf(0.05/2, n - 1 )
lower_bound = (n-1)*s2/chi1
upper_bound = (n-1)*s2/chi2
print(lower_bound, upper_bound)
print("varianza muestral -> ",s2)

# intervaol de confianza 99% para la varianza poblacional de weight_kg
lower_bound = 0
upper_bound = 0
n = sample.shape[0]
s2 = sample.weight_kg.std()
chi1 = stats.chi2.ppf(1-0.01/2, n - 1 )
chi2 = stats.chi2.ppf(0.01/2, n - 1 )
lower_bound = (n-1)*s2/chi1
upper_bound = (n-1)*s2/chi2
print(lower_bound, upper_bound)
print("varianza muestral -> ",s2)

# -----------------------------------------------------------------------------
# ---------------------- EXPLORACION DE DATOS (TAREA) -------------------------
# -----------------------------------------------------------------------------

# analizar normalidad y sacar los intervalos de confianza de media y varianza de 3 muestras del conjunto

sample = spanish_players_df.sample(n=100)

# histogramas
plt.figure(figsize=(20,5))
plt.subplot(1,3,1)
sns.histplot(data=spanish_players_df, x="potential", bins=30)
plt.title('Potential')
plt.subplot(1,3,2)
sns.histplot(data=spanish_players_df, x="international_reputation", bins=30)
plt.title('International Reputation')
plt.subplot(1,3,3)
sns.histplot(data=spanish_players_df, x="power_stamina", bins=30)
plt.title('Power Stamina')

# Gráficos probabilisticas
plt.figure()
sm.ProbPlot( sample.potential, dist="norm" ).probplot(line="r")
plt.title('Potential')
sm.ProbPlot( sample.international_reputation, dist="norm" ).probplot(line="r")
plt.title('International Reputation')
sm.ProbPlot( sample.power_stamina, dist="norm" ).probplot(line="r")
plt.title('Power Stamina')

# Pruebas de normalidad
print(" alpha -> 0.05 \n")

print("       Potential:\n")
print( stats.skewtest( sample.potential ) , "\n    -> H0:  El coeficiente de asimetría (CA) de la población sigue una asimetria propia de una población normal (CA = 0)" )
print( stats.kurtosistest( sample.potential ) , "\n    -> H0:  El coeficiente de curtosis (CC) de la población sigue una curtosis propia de una población normal (CC=0)")
print( stats.kstest( (sample.potential-sample.potential.mean())/sample.potential.std(), 'norm'  ) , "\n    -> H0:  La función de distribución acumulada se comporta como la función de distribución acumulada de una distribución normal.")
print( stats.shapiro( sample.potential ) , "\n     -> H0:  La variable aleatoria estudiada procede de una población normal")
print("**Despues de hacer varios tipos de pruebas de normalidad y todas diciendo que la distribución sigue una normal, concluimos que sigue una normal.**\n")

print("\n\n       International Reputation:\n")
print( stats.skewtest( sample.international_reputation ) , "\n    -> H1:  El coeficiente de asimetría (CA) de la población sigue una asimetría diferente de una población normal (CA != 0)" )
print( stats.kurtosistest( sample.international_reputation ) , "\n    -> H1:  El coeficiente de curtosis (CC) de la población tiene una curtosis diferente a la de una población normal (CC!=0)")
print( stats.kstest( (sample.international_reputation-sample.international_reputation.mean())/sample.international_reputation.std(), 'norm'  ) , "\n    -> H1:  La función de distribución acumulada difiere de la función de distribución acumulada de una distribución normal.")
print( stats.shapiro( sample.international_reputation ) , "\n     -> H1:  La variable aleatoria estudiada no procede de una población normal")
print("**Despues de hacer varios tipos de pruebas de normalidad y todas diciendo que la distribución NO sigue una normal, concluimos que NO sigue una normal.**\n")

print("\n\n       Power Stamina:\n")
print( stats.skewtest( sample.power_stamina ) , "\n    -> H1:  El coeficiente de asimetría (CA) de la población sigue una asimetría diferente de una población normal (CA != 0)" )
print( stats.kurtosistest( sample.power_stamina ) , "\n    -> H0:  El coeficiente de curtosis (CC) de la población sigue una curtosis propia de una población normal (CC=0)")
print( stats.kstest( (sample.power_stamina-sample.power_stamina.mean())/sample.power_stamina.std(), 'norm'  ) , "\n    -> H0:  La función de distribución acumulada se comporta como la función de distribución acumulada de una distribución normal.")
print( stats.shapiro( sample.power_stamina ) , "\n     -> H1:  La variable aleatoria estudiada no procede de una población normal")
print("**Despues de hacer varios tipos de pruebas de normalidad, aunque el de asimetría y la de shapiro dicen que la distribución no sigue una normal, observando el gráfico probabilistico y el histograma podemos concluir que sigue una normal.**\n")

# Intervalos de confianza

# Media
print("\n       Media:")
print("Potential")
lower_bound = 0
upper_bound = 0
x = sample.potential.mean()
n = sample.shape[0]
t = stats.t.ppf(0.975, n-1 )
s = sample.potential.std()
lower_bound = x - t * s/math.sqrt(n)
upper_bound = x + t * s/math.sqrt(n)
print("   Hay un 95% de probabilidad de que las muestras tengan una media de entre: (",lower_bound,", ", upper_bound,").")

print("Power Stamina")
lower_bound_1 = 0
upper_bound_1 = 0
x_1 = sample.power_stamina.mean()
n_1 = sample.shape[0]
t_1 = stats.t.ppf(0.975, n_1-1 )
s_1 = sample.power_stamina.std()
lower_bound_1 = x_1 - t_1 * s_1/math.sqrt(n_1)
upper_bound_1 = x_1 + t_1 * s_1/math.sqrt(n_1)
print("   Hay un 95% de probabilidad de que las muestras tengan una media de entre: (",lower_bound_1,", ", upper_bound_1,").")

# Varianza
print("\n       Varianza:")
print("Potential")
lower_bound_2 = 0
upper_bound_2 = 0
n_2 = sample.shape[0]
s2_2 = sample.potential.std()
chi1_2 = stats.chi2.ppf(1-0.05/2, n_2 - 1 )
chi2_2 = stats.chi2.ppf(0.05/2, n_2 - 1 )
lower_bound_2 = (n_2-1)*s2_2/chi1_2
upper_bound_2 = (n_2-1)*s2_2/chi2_2
print("   Hay un 95% de probabilidad de que las muestras tengan una varianza de entre: (",lower_bound_2,", ", upper_bound_2,").")

print("Power Stamina")
lower_bound_3 = 0
upper_bound_3 = 0
n_3 = sample.shape[0]
s2_3 = sample.power_stamina.std()
chi1_3 = stats.chi2.ppf(1-0.05/2, n_3 - 1 )
chi2_3 = stats.chi2.ppf(0.05/2, n_3 - 1 )
lower_bound_3 = (n_3-1)*s2_3/chi1_3
upper_bound_3 = (n_3-1)*s2_3/chi2_3
print("   Hay un 95% de probabilidad de que las muestras tengan una varianza de entre: (",lower_bound_3,", ", upper_bound_3,").")


from __future__ import absolute_import
from __future__ import division
from __future__ import print_function

from absl import flags

import tensorflow as tf
from tensorflow_examples.models.pix2pix import data_download
from tensorflow_examples.models.pix2pix import pix2pix

FLAGS = flags.FLAGS

class Pix2PixTest(tf.test.TestCase):

  def test_one_step_with_function(self):
    epochs = 1
    batch_size = 1
    enable_function = True

    input_image = tf.random.uniform((256, 256, 3))
    target_image = tf.random.uniform((256, 256, 3))

    train_dataset = tf.data.Dataset.from_tensors(
        (input_image, target_image)).map(pix2pix.random_jitter).batch(
            batch_size)
    checkpoint_pr = pix2pix.get_checkpoint_prefix()

    pix2pix_obj = pix2pix.Pix2pix(epochs, enable_function)
    pix2pix_obj.train(train_dataset, checkpoint_pr)

  def test_one_step_without_function(self):
    epochs = 1
    batch_size = 1
    enable_function = False

    input_image = tf.random.uniform((256, 256, 3))
    target_image = tf.random.uniform((256, 256, 3))

    train_dataset = tf.data.Dataset.from_tensors(
        (input_image, target_image)).map(pix2pix.random_jitter).batch(
            batch_size)

    pix2pix_obj = pix2pix.Pix2pix(epochs, enable_function)

    checkpoint_pr = pix2pix.get_checkpoint_prefix()
    pix2pix_obj.train(train_dataset, checkpoint_pr)

class Pix2PixBenchmark(tf.test.Benchmark):

  def __init__(self, output_dir=None, **kwargs):
    self.output_dir = output_dir

  def benchmark_with_function(self):
    path = data_download.main("datasets")
    kwargs = {"epochs": 6, "enable_function": True, "path": path,
              "buffer_size": 400, "batch_size": 1}
    self._run_and_report_benchmark(**kwargs)

  def benchmark_without_function(self):
    path = data_download.main("datasets")
    kwargs = {"epochs": 6, "enable_function": False, "path": path,
              "buffer_size": 400, "batch_size": 1}
    self._run_and_report_benchmark(**kwargs)

  def _run_and_report_benchmark(self, **kwargs):
    time_list = pix2pix.main(**kwargs)
    # 1st epoch is the warmup epoch hence skipping it for calculating time.
    self.report_benchmark(wall_time=tf.reduce_mean(time_list[1:]))

if __name__ == "__main__":
  tf.test.main()


###
### <INCOMPLETE WORK>
###
### Iteration #2: attempt to separate ../face-req-v1/  logic into two parts and use threads
###  - Face Recognition handled in ./img_processing.py
###  - Communication with MQTT and WebSocket as in ../face-req-v1/
###

import logging
import time

import aiohttp
from aiohttp import web
import paho.mqtt.client as mqtt

import imutils
import pickle
import time
import cv2
import numpy as np
import io

from img_processing import ImgProcessing

# Debug
isVerbose = True
firstInstance = True

# MQTT
mqttServer = "192.168.1.41"
mqttPort = 1883
mqttTopic = "teddyCtrl"
mqttOrder = "clientOrder"
mqttClientOrder = None
mqttData = None

def on_publish(client, userdata, result):
    if(isVerbose): print("MQTT's publish ACK: client={}, result={}".format(client, result))
    pass

def get_mqttData():
    return mqttData

def set_mqttData(msg_data):
    mqttData = msg_data

# Face Recognition
frameWidth = 0
depth = None
xPos = None

encodingsP = "encodings.pickle"  #Determine faces from encodings.pickle file model created from train_model.py

def get_frameWidth():
    return frameWidth

def set_frameWidth(ww):
    frameWidth = ww

def get_xPos():
    return xPos

def set_xPos(position):
    xPos = position

def get_depth():
    return depth

def set_depth(d):
    depth = d

#
# Handle Websocket request and process image for face recognition
#
async def websocket_handler(request):
    #frame_count = 0
    #recognition_count = 0

    ws = web.WebSocketResponse()
    await ws.prepare(request)

    async for msg in ws:
        if(isVerbose): print("---------------     New WebSocket Message:     ---------------")
        if msg.type == aiohttp.WSMsgType.TEXT:
            if msg.data == 'close':
                await ws.close()
        if msg.type == aiohttp.WSMsgType.BINARY:

            # Process Image using ImgProcessing instance
            faceRecognitionProcessing.start(msg.data)

            # mqtt
            currentTime = time.time()
            #clientMQTT.connect(mqttServer, mqttPort)

            mqttClientOrder = clientMQTT.subscribe(mqttOrder)
            print("MQTT Client Order: {}".format(mqttClientOrder))
            print("MQTT to be published: {}".format(faceRecognitionProcessing.get_stringJSON_data()))

            if(mqttClientOrder in faceRecognitionProcessing.get_names()):
                mqtt_return = clientMQTT.publish(mqttTopic, faceRecognitionProcessing.get_stringJSON_data())
                logging.info("MQTT publish: done, in {0:.2g} sec.".format(time.time()-currentTime))
                if(isVerbose):
                    print("MQTT.publish return: {}".format(mqtt_return))

            # loop over the recognized faces
#
#             currentTime = time.time()
#             for ((top, right, bottom, left), name) in zip(faceRecognitionProcessing.get_boxes(), faceRecognitionProcessing.get_names()):
#                 # draw the predicted face name on the image - color is in BGR
#                 cv2.rectangle(frame, (left, top), (right, bottom),
#                         (0, 255, 225), 2)
#                 y = top - 15 if top - 15 > 15 else top + 15
#                 cv2.putText(frame, name, (left, y), cv2.FONT_HERSHEY_SIMPLEX,
#                         .8, (0, 255, 255), 2)
#             logging.info("OpenCV draw rectanlges on image: done, in {0:.2g} sec.".format(time.time()-currentTime))

            # display the image to our screen
            cv2.imshow("Facial Recognition is Running", faceRecognitionProcessing.get_frame())
            #cv2.imwrite("dataset/recognitions/boxed_frame_%s.jpg" % recognition_count, frame)
            # recognition_count =  recognition_count + 1
            key = cv2.waitKey(1) & 0xFF

            # quit when 'q' key is pressed
            if key == ord("q"):
                # update the FPS counter
                fps.update()

                # stop the timer and display FPS information
                fps.stop()
                print("[INFO] elasped time: {:.2f}".format(fps.elapsed()))
                print("[INFO] approx. FPS: {:.2f}".format(fps.fps()))

                # do a bit of cleanup
                cv2.destroyAllWindows()
                #vs.stop()
                break

    print('websocket connection closed')

    return ws

# LOGGING setup
currentTime = time.time()
logging.basicConfig(format='%(asctime)s %(message)s',
#                     filename="log/raspberry4.log",
                    encoding='utf-8',
                    level=logging.DEBUG)
logging.info("[INFO] Face Recognition: loading encodings + face detector...")

# FACE RECOGNITION:
faceRecognitionProcessing = ImgProcessing()
# load the known faces and embeddings along with OpenCV's Haar cascade for face detection
logging.info("[INFO] Face Recognition: loading encodings + face detector...")
#print("[INFO] Face Recognition: loading encodings + face detector...")
data = pickle.loads(open(encodingsP, "rb").read())

# MQTT
logging.info("MQTT connecting to {}:{}...".format(mqttServer, mqttPort))
#print("[INFO] MQTT connecting to {}:{}...".format(mqttServer, mqttPort), end="")
clientMQTT = mqtt.Client()
clientMQTT.on_publish =  on_publish
clientMQTT.connect(mqttServer, mqttPort)
logging.info("MQTT connected.")
#print("MQTT connected.")

# WEBSOCKET
print("[INFO] WebSocket setup...", end="; ")
app = web.Application()
print("aiohttp application done", end="; ")
app.add_routes([web.get('/', websocket_handler)])
print("aiohttp routes added")
web.run_app(app)


import datetime

from django.test import TestCase
from django.utils import timezone
from django.urls import reverse

from .models import Question

"""
a separate TestClass for each model or view
a separate test method for each set of conditions you want to test
test method names that describe their function
"""

#  to run use : python manage.py test polls
class QuestionModelsTests(TestCase):

    # check if published in future
    def test_was_published_recently_with_future_question(self):
        """
        was published_recently() returns False for questions whose pub_date is in the future
        """
        time = timezone.now() + datetime.timedelta(days=30)
        future_question = Question(pub_date=time)
        self.assertIs(future_question.was_published_recently(), False)

    # check if published yesterday
    def test_was_published_recently_old_future_question(self):
        """
        was_published_recently() returns False for questions whose pub_date
        is older than 1 day.
        """
        time = timezone.now() - datetime.timedelta(days=1, seconds=1)
        old_question = Question(pub_date=time)
        self.assertIs(old_question.was_published_recently(), False)

    # checking if published today
    def test_was_published_recently_with_recent_question(self):
        """
        was_published_recently() return True for questions whose pub_date is within last day
        """
        time = timezone.now() - datetime.timedelta(hours=23, minutes=59,seconds=59)
        recent_question = Question(pub_date=time)
        self.assertIs(recent_question.was_published_recently(),True)

# used to take some repetition out of the process of creating questions.
def create_question(question_text,days):
    """
    Create a question with the given `question_text` and published the
    given number of `days` offset to now (negative for questions published
    in the past, positive for questions that have yet to be published).
    """
    time = timezone.now() + datetime.timedelta(days=days)
    return Question.objects.create(question_text=question_text,pub_date=time)

class QuestionIndexViewTests(TestCase):

    # doesn’t create any questions, but checks the message: “No polls are available.” and verifies the latest_question_list is empty.
    def test_no_questions(self):
        """If no questions exist, an appropriate message is displayed"""
        response = self.client.get(reverse("polls:index"))
        self.assertEqual(response.status_code,200)
        self.assertContains(response,"No polls are available")

        # check if the list is null for no questions
        self.assertQuerysetEqual(response.context['latest_question_list'], [])

    # we create a question and verify that it appears in the list.
    def test_past_question(self):
        """Question with a pub_date in the past are displayed on the insdex page"""
        question = create_question(question_text="Past question.", days=-30)
        response = self.client.get(reverse("polls:index"))
        self.assertQuerysetEqual(
            response.context["latest_question_list"],
            [question],
        )

    # we create a question with a pub_date in the future. The database is reset for each test method,
    # so the first question is no longer there, and so again the index shouldn’t have any questions in it.
    def test_future_question(self):
        """Questions with a pub_date in the future aren't displayed on"""
        create_question(question_text="Future question.", days=30)
        response = self.client.get(reverse("polls:index"))
        self.assertContains(response, "No polls are available.")
        self.assertQuerysetEqual(response.context["latest_question_list"], [])

    def test_future_question_and_past_question(self):
        """Evend if both past and future questions exist, only past questions are displayed"""
        question = create_question(question_text="Past question.", days=-30)
        create_question(question_text="Future question.", days=30)
        response = self.client.get(reverse("polls:index"))
        self.assertQuerysetEqual(
            response.context["latest_question_list"],
            [question],
        )

    def test_two_past_questions(self):
        """The qustions index page may display multiple questions"""
        question1 = create_question(question_text="Past question 1.", days=-30)
        question2 = create_question(question_text="Past question 2.", days=-5)
        response = self.client.get(reverse("polls:index"))
        self.assertQuerysetEqual(
            response.context["latest_question_list"],
            [question2, question1],
        )

class QuestionDetailViewTests(TestCase):

    def test_future_question(self):
        """
        The detail view of a question with a pub_date in the future
        returns 404 not found.
        """
        future_question = create_question(question_text="Future question.", days=5)
        url = reverse("polls:detail", args=(future_question.id,))
        response = self.client.get(url)
        self.assertEqual(response.status_code,404)

    def test_past_question(self):
        """
        The detail view of a question with a pub_date in the past
        displays the question's text
        """
        past_question = create_question(question_text="Past Question.",days=-5)
        url = reverse("polls:detail",args=(past_question.id,))
        response = self.client.get(url)
        self.assertContains(response,past_question.question_text)


import string
import random
"""
# Exercise 1
list1 = [1, 2, 3, 4]
list2 = [6, 7, 8, 9]
list1.extend(list2)
print(list1)

# Exercise 2
list = []
for i in range(1, 4):
  number = input("Please enter a number:")
  list.append(int(number))
print(list)
print(f"The max number is {max(list)}")

# Exercise 3
letter_string = string.ascii_lowercase
for letter in letter_string:
  if letter == "a" or letter == "e" or letter == "i" or letter == "o" or letter == "u":
    print(f"{letter} is a vowel")
  else:
    print(f"{letter} is a consonant")

#Exercise 4
names = ['Samus', 'Cortana', 'V', 'Link', 'Mario', 'Cortana', 'Samus']
user = input("What´s your name?")
if user in names:
  print(names.index(user))
else:
  print("not in")

#Exercise 5
words = []
while len(words) < 7:
  word = input("Please enter a word:")
  words.append(word)
print(words)

letter = input("Please enter 1 single Character:")

print(letter)

for word in words:
    position = word.find(letter)
    if position != -1:
        print(f"Your Character is in position {position} of the word {word}")
    else:
        print(f"The letter {letter} is not in the word {word}")

# Exercise 6
numbers = []
for number in range(1,1000001):
    numbers.append(number)

print(numbers)
print(max(numbers))
print(min(numbers))
print(sum(numbers))

# Exercise 7
string = input("Please enter some numbers, separated by comma:")
new_list = string.split(",")
new_tuple = tuple(new_list)
print(new_list)
print(new_tuple)

# Exercise 8
user_number = ""
random_number = random.randint(1,9)
guess_counter = 0
guess_success = 0
guess_or_not = True
print(random_number)
user_number = int(input("Please enter a number between 1 and 9:"))

while guess_or_not:
    guess_counter += 1
    if user_number == random_number:
        guess_success += 1
        guess_counter += 1
        print("You guessed correct!")
        play_again = input("Want to play again? Enter \"y\\n\"")
        if play_again != "y":
            break
        else:
            user_number = int(input("Enter a number between 1 and 9:"))
            guess_or_not = True

    else:
        guess_counter += guess_counter
        play_again = input("Number was wrong. Want to play again? Enter \"y\\n\"")
        if play_again != "y":
            break
        else:
            user_number = int(input("Enter a number between 1 and 9:"))
            guess_or_not = True

print(f"You guessed {guess_success} time(s) correct out of a total of {guess_counter} guesses")
"""

# Exercise 9
user_info = {
  "customer_name": "",
  "waiter": "",
  "item": "",
  "price": "",
  "qty": "",
  "discount": ""
}

user_info["customer_name"] = input("Whats your name?\n")
user_info["waiter"] = input("Who was your waiter?\n")
user_info["item"] = input("What did you order \n")
user_info["price"] = input("What was the price? \n")
user_info["qty"] = input("How many did you have?\n")
user_info["discount"] = input("How much discount did you get?\n")

total_charge = int(user_info["price"]) * int(user_info["qty"])

print(f"The total charge excluding VAT is ${total_charge}.\n Including VAT is ${total_charge * 1.16}. You had: \n blablabla ")
print(user_info)

# while guess_or_not == True:
#     if user_number == random_number:
#         print("Congrats you guessed the right number!")
#         guess_counter += 1
#         break
#     else:
#         guess_or_not = input("Sorry, not the right number. If you want to guess again, please enter a number between 1 and 9. If you want to stop, please enter \"done\".")

#  print(f"You guessed {guess_counter} times until you hit the right number!")

# letter = "g"
# word = "dfsgdfsd"
# position = word.index(letter)
# print(position)

# txt = "Hello, welcome to my world."
# x = txt.index("welcome")
# print(x)


import random

def innings(batter, to_chase):
    print("Enter numbers between 1 and 6. If you both choose the same number then " + batter + " is out")
    total = 0

    while True:
        pnum = input_num(1, 6)
        cnum = random.randint(1, 6)

        print("User chose", pnum)
        print("Computer chose", cnum)

        if pnum == cnum:
            print(batter + " is out")
            return total
        else:
            total = total + (pnum if batter == "User" else cnum)
            print(batter + " score is", total)
            if to_chase is not None and total > to_chase:
                return total

def input_num(min, max):
    num = int(input())

    if num > max or num < min:
        print("Enter valid choice")
        return input_num(min, max)
    else:
        return num

print("Time for toss, Enter 0 for Heads and 1 for Tails")
choice = input_num(0, 1)
coin = random.randint(0, 1)
player_bowls = False

if coin == choice:
    print("You have won the toss")
    print("Select 0 to bat, 1 to bowl ")
    player_bowls = input_num(0, 1) == 1

    if player_bowls:
        print("You chose to bowl")
    else:
        print("You chose to bat")
else:
    print("Computer won the toss")
    player_bowls = random.randint(0, 1) == 1

    if player_bowls:
        print("Computer chose to bat")
    else:
        print("Computer chose to bowl")

if player_bowls:
    comp_score = innings("Computer", None)
    user_score = innings("User", comp_score)
else:
    user_score = innings("User", None)
    comp_score = innings("Computer", user_score)

if comp_score < user_score:
    print("User wins")
elif user_score < comp_score:
    print("Computer wins")
else:
    print("Match draw")


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

""" Annotations for instance attributes, functions arguments, etc.
"""

import sys
import operator
import collections.abc
import logging
import typing

logging.getLogger().addHandler(logging.NullHandler())

T = typing.TypeVar('T')  # pylint: disable=invalid-name

@typing.runtime_checkable
class SupportsDescription(typing.Protocol):
    """ An ABC with description attribute of type string.

    Can be used with ``isinstance`` to check whether object contains such attribute.
    Supposed for using with custom enums in meta modules.
    """
    description: str

class Description:
    """ Description for attribute's or function argument's annotation

    ``description`` can be used for wide description.

    ``short_description`` can be used for short description, e.g. for label of input in HTML form.
    """
    description: str
    short_description: str = None

    def __init__(self, description: str, short_description: str = None):
        """ Initialize description.

        :param description: Description of attribute or argument.
        :param short_description: Short description (optional).
        """
        self.description = str(description)
        self.short_description = None if short_description is None else str(short_description)

class Volatile:
    """ Describes attribute or function argument which value even through not optional,
    but can be reevaluated each time when uses for instantiation or function calling.

    For example: some source demands interval [date_from; date_to] for download history data,
    but we need to download it every day. So we store this interval somewhere in configuration
    and mark date_to as ``Volatile`` with reevaluating as ``date.today()``
    every time we fetch parameters from configuration.
    Thus, interval widened every day without modifying configuration.

    ::

        class Params:
            isin: str
            date_from: datetime.date
            date_to: Annotated[datetime.date, Volatile(lambda ctx: datetime.date.today(), datetime.date.max)]
    """

    def __init__(self,
                 generator: typing.Callable[[typing.Dict[str, typing.Any]], T],
                 stub_value: T = None):
        """ Initialize volatile annotation.

        :param generator: Function for reevaluating;
                          takes context of instantiation or function calling (like ``kwargs``)
                          and returns new value of attribute.
        :param stub_value: Stub value for not optional attributes.
        """
        if not callable(generator):
            raise TypeError("generator is not callable")

        self.generator = generator
        self.stub_value = stub_value

    def generate(self, context: typing.Dict[str, typing.Any]) -> T:
        """ Generate actual value of attribute or function argument based on context.

        :param context: Context of instantiation or function calling (like ``kwargs``)
        :return: Actual value of volatile attribute.
        """
        return self.generator(context)

# Support legacy 3.8 version:
# Got from https://github.com/python/typing (typing_extensions module)

# After PEP 560, internal typing API was substantially reworked.
# This is especially important for Protocol class which uses internal APIs
# quite extensively.
PEP_560 = sys.version_info[:3] >= (3, 7, 0)

try:  # pragma: no cover
    # noinspection PyProtectedMember
    from typing import _tp_cache
except ImportError:  # pragma: no cover
    def _tp_cache(x):  # pylint: disable=invalid-name
        return x

LEGACY_ANNOTATIONS = True  # flag about legacy python version

# Python 3.9+ has PEP 593 (Annotated and modified get_type_hints)
if hasattr(typing, 'Annotated'):  # pragma: no cover

    Annotated = typing.Annotated
    get_type_hints = typing.get_type_hints

    # Not exported and not a public API, but needed for get_origin() and get_args()
    # to work.
    # noinspection PyUnresolvedReferences,PyProtectedMember
    _AnnotatedAlias = typing._AnnotatedAlias

    LEGACY_ANNOTATIONS = False

elif PEP_560:  # pragma: no cover

    # noinspection PyUnresolvedReferences,PyProtectedMember
    class _AnnotatedAlias(typing._GenericAlias, _root=True):
        # pylint: disable=consider-using-f-string
        """Runtime representation of an annotated type.
        At its core 'Annotated[t, dec1, dec2, ...]' is an alias for the type 't'
        with extra annotations. The alias behaves like a normal typing alias,
        instantiating is the same as instantiating the underlying type, binding
        it to types is also the same.
        """
        def __init__(self, origin, metadata):
            if isinstance(origin, _AnnotatedAlias):
                metadata = origin.__metadata__ + metadata
                origin = origin.__origin__
            super().__init__(origin, origin)
            self.__metadata__ = metadata

        def copy_with(self, params):
            assert len(params) == 1
            new_type = params[0]
            return _AnnotatedAlias(new_type, self.__metadata__)

        def __repr__(self):
            return "typing_extensions.Annotated[{}, {}]".format(
                typing._type_repr(self.__origin__),
                ", ".join(repr(a) for a in self.__metadata__)
            )

        def __reduce__(self):
            return operator.getitem, (
                Annotated, (self.__origin__,) + self.__metadata__
            )

        def __eq__(self, other):
            if not isinstance(other, _AnnotatedAlias):
                return NotImplemented
            if self.__origin__ != other.__origin__:
                return False
            return self.__metadata__ == other.__metadata__

        def __hash__(self):
            return hash((self.__origin__, self.__metadata__))

    class Annotated:
        # pylint: disable=consider-using-f-string
        """Add context specific metadata to a type.
        Example: Annotated[int, runtime_check.Unsigned] indicates to the
        hypothetical runtime_check module that this type is an unsigned int.
        Every other consumer of this type can ignore this metadata and treat
        this type as int.
        The first argument to Annotated must be a valid type (and will be in
        the __origin__ field), the remaining arguments are kept as a tuple in
        the __extra__ field.
        Details:
        - It's an error to call `Annotated` with less than two arguments.
        - Nested Annotated are flattened::
            Annotated[Annotated[T, Ann1, Ann2], Ann3] == Annotated[T, Ann1, Ann2, Ann3]
        - Instantiating an annotated type is equivalent to instantiating the
        underlying type::
            Annotated[C, Ann1](5) == C(5)
        - Annotated can be used as a generic type alias::
            Optimized = Annotated[T, runtime.Optimize()]
            Optimized[int] == Annotated[int, runtime.Optimize()]
            OptimizedList = Annotated[List[T], runtime.Optimize()]
            OptimizedList[int] == Annotated[List[int], runtime.Optimize()]
        """

        __slots__ = ()

        def __new__(cls, *args, **kwargs):
            raise TypeError("Type Annotated cannot be instantiated.")

        @_tp_cache
        def __class_getitem__(cls, params):
            if not isinstance(params, tuple) or len(params) < 2:
                raise TypeError("Annotated[...] should be used "
                                "with at least two arguments (a type and an "
                                "annotation).")
            msg = "Annotated[t, ...]: t must be a type."
            # noinspection PyUnresolvedReferences,PyProtectedMember
            origin = typing._type_check(params[0], msg)
            metadata = tuple(params[1:])
            return _AnnotatedAlias(origin, metadata)

        def __init_subclass__(cls, *args, **kwargs):
            raise TypeError(
                "Cannot subclass {}.Annotated".format(cls.__module__)
            )

    def _strip_annotations(t):  # pylint: disable=invalid-name
        """Strips the annotations from a given type.
        """
        if isinstance(t, _AnnotatedAlias):
            return _strip_annotations(t.__origin__)
        # noinspection PyUnresolvedReferences,PyProtectedMember
        if isinstance(t, typing._GenericAlias):
            stripped_args = tuple(_strip_annotations(a) for a in t.__args__)
            if stripped_args == t.__args__:
                return t
            res = t.copy_with(stripped_args)
            # noinspection PyProtectedMember
            res._special = t._special
            return res
        return t

    def get_type_hints(obj, globalns=None, localns=None, include_extras=False):
        """Return type hints for an object.
        This is often the same as obj.__annotations__, but it handles
        forward references encoded as string literals, adds Optional[t] if a
        default value equal to None is set and recursively replaces all
        'Annotated[T, ...]' with 'T' (unless 'include_extras=True').
        The argument may be a module, class, method, or function. The annotations
        are returned as a dictionary. For classes, annotations include also
        inherited members.
        TypeError is raised if the argument is not of a type that can contain
        annotations, and an empty dictionary is returned if no annotations are
        present.
        BEWARE -- the behavior of globalns and localns is counterintuitive
        (unless you are familiar with how eval() and exec() work).  The
        search order is locals first, then globals.
        - If no dict arguments are passed, an attempt is made to use the
          globals from obj (or the respective module's globals for classes),
          and these are also used as the locals.  If the object does not appear
          to have globals, an empty dictionary is used.
        - If one dict argument is passed, it is used for both globals and
          locals.
        - If two dict arguments are passed, they specify globals and
          locals, respectively.
        """
        hint = typing.get_type_hints(obj, globalns=globalns, localns=localns)
        if include_extras:
            return hint
        return {k: _strip_annotations(t) for k, t in hint.items()}

# Python 3.8 has get_origin() and get_args() but those implementations aren't
# Annotated-aware, so we can't use those, only Python 3.9 versions will do.
if sys.version_info[:2] >= (3, 9):  # pragma: no cover

    get_origin = typing.get_origin
    get_args = typing.get_args

elif PEP_560:  # pragma: no cover

    # noinspection PyUnresolvedReferences,PyProtectedMember
    from typing import _GenericAlias
    try:
        # 3.9+
        # noinspection PyProtectedMember
        from typing import _BaseGenericAlias
    except ImportError:
        _BaseGenericAlias = _GenericAlias
    try:
        # 3.9+
        from typing import GenericAlias
    except ImportError:
        GenericAlias = _GenericAlias

    def get_origin(tp):  # pylint: disable=invalid-name
        """Get the unsubscripted version of a type.

        This supports generic types, Callable, Tuple, Union, Literal, Final, ClassVar
        and Annotated. Return None for unsupported types. Examples::

            get_origin(Literal[42]) is Literal
            get_origin(int) is None
            get_origin(ClassVar[int]) is ClassVar
            get_origin(Generic) is Generic
            get_origin(Generic[T]) is Generic
            get_origin(Union[T, int]) is Union
            get_origin(List[Tuple[T, T]][int]) == list
        """
        if isinstance(tp, _AnnotatedAlias):
            return Annotated
        if isinstance(tp, (_BaseGenericAlias, GenericAlias)):
            return tp.__origin__
        if tp is typing.Generic:
            return typing.Generic
        return None

    def get_args(tp):  # pylint: disable=invalid-name
        """Get type arguments with all substitutions performed.

        For unions, basic simplifications used by Union constructor are performed.
        Examples::
            get_args(Dict[str, int]) == (str, int)
            get_args(int) == ()
            get_args(Union[int, Union[T, int], str][int]) == (int, str)
            get_args(Union[int, Tuple[T, int]][str]) == (int, Tuple[str, int])
            get_args(Callable[[], T][int]) == ([], int)
        """
        if isinstance(tp, _AnnotatedAlias):
            return (tp.__origin__,) + tp.__metadata__
        if isinstance(tp, (_GenericAlias, GenericAlias)):
            res = tp.__args__
            if tp.__origin__ is collections.abc.Callable and res[0] is not Ellipsis:
                res = (list(res[:-1]), res[-1])
            return res
        return ()


import numpy as np
import torch
import torch.nn as nn

from copy import deepcopy
from typing import Dict, Union, Tuple
from offlinerlmoup.policy import BasePolicy
from torch.nn import functional as F
from offlinerlmoup.dynamics import BaseDynamics

deta = 10000
class SACPolicy(BasePolicy):

    def __init__(
        self,
        actor: nn.Module,
        critic1: nn.Module,
        critic2: nn.Module,
        dynamics: BaseDynamics,
        behavior_policy: nn.Module,
        actor_optim: torch.optim.Optimizer,
        critic1_optim: torch.optim.Optimizer,
        critic2_optim: torch.optim.Optimizer,
        behavior_policy_optim: torch.optim.Optimizer,
        tau: float = 0.005,
        gamma: float  = 0.99,
        alpha: Union[float, Tuple[float, torch.Tensor, torch.optim.Optimizer]] = 0.2,
        num_samples_mmd_match: int = 4
    ) -> None:
        super().__init__()

        self.actor = actor
        self.critic1, self.critic1_old = critic1, deepcopy(critic1)
        self.critic1_old.eval()
        self.critic2, self.critic2_old = critic2, deepcopy(critic2)
        self.critic2_old.eval()
        self.behavior_policy = behavior_policy

        self.actor_optim = actor_optim
        self.critic1_optim = critic1_optim
        self.critic2_optim = critic2_optim
        self.behavior_policy_optim = behavior_policy_optim

        self._tau = tau
        self._gamma = gamma
        self.num_samples_mmd_match = num_samples_mmd_match
        self.dynamics = dynamics

        self._is_auto_alpha = False
        if isinstance(alpha, tuple):
            self._is_auto_alpha = True
            self._target_entropy, self._log_alpha, self.alpha_optim = alpha
            self._alpha = self._log_alpha.detach().exp()
        else:
            self._alpha = alpha

    def train(self) -> None:
        self.actor.train()
        self.critic1.train()
        self.critic2.train()

    def eval(self) -> None:
        self.actor.eval()
        self.critic1.eval()
        self.critic2.eval()

    def _sync_weight(self) -> None:
        for o, n in zip(self.critic1_old.parameters(), self.critic1.parameters()):
            o.data.copy_(o.data * (1.0 - self._tau) + n.data * self._tau)
        for o, n in zip(self.critic2_old.parameters(), self.critic2.parameters()):
            o.data.copy_(o.data * (1.0 - self._tau) + n.data * self._tau)

    def actforward_multy(
        self,
        obs: torch.Tensor,
        nums: int,
        deterministic: bool = False
    ) -> Tuple[torch.Tensor, torch.Tensor]:
        dist = self.actor(obs)
        res = []
        for i in range(nums):
            squashed_action, raw_action = dist.rsample()
            res.append(raw_action)
        return torch.stack(res)

    def actforward(
        self,
        obs: torch.Tensor,
        deterministic: bool = False
    ) -> Tuple[torch.Tensor, torch.Tensor]:
        dist = self.actor(obs)
        if deterministic:
            squashed_action, raw_action = dist.mode()
        else:
            squashed_action, raw_action = dist.rsample()
        log_prob = dist.log_prob(squashed_action, raw_action)
        return squashed_action, log_prob

    def select_action(
        self,
        obs: np.ndarray,
        deterministic: bool = False
    ) -> np.ndarray:
        with torch.no_grad():
            action, _ = self.actforward(obs, deterministic)
        return action.cpu().numpy()

    def mmd_loss_laplacian(self, samples1, samples2, sigma=20):
        diff_x_x = samples1.unsqueeze(2) - samples1.unsqueeze(1)
        diff_x_x = torch.mean((-(diff_x_x.abs()).sum(-1)/(2.0 * sigma)).exp(), dim=(1,2))

        diff_x_y = samples1.unsqueeze(2) - samples2.unsqueeze(1)
        diff_x_y = torch.mean((-(diff_x_y.abs()).sum(-1)/(2.0 * sigma)).exp(), dim=(1, 2))

        diff_y_y = samples2.unsqueeze(2) - samples2.unsqueeze(1)
        diff_y_y = torch.mean((-(diff_y_y.abs()).sum(-1)/(2.0 * sigma)).exp(), dim=(1,2))

        overall_loss = (diff_x_x + diff_y_y - 2.0 * diff_x_y + 1e-6).sqrt()
        return overall_loss

    def learn(self, real_batch, fake_batch, batch: Dict) -> Dict[str, float]:
        obss, actions, next_obss, rewards, terminals = batch["observations"], batch["actions"], \
            batch["next_observations"], batch["rewards"], batch["terminals"]

        # global deta
        # deta += 1
        # real_obs = real_batch["observations"]
        # real_act = real_batch["actions"]
        # real_next_obs = real_batch["next_observations"]
        # fake_obs = fake_batch["observations"]
        # fake_act = fake_batch["actions"]
        # fake_next_obs = fake_batch["next_observations"]
        # real_obs_act = torch.cat((real_obs, real_act), axis=-1)
        # fack_obs_act = torch.cat((fake_obs, fake_act), axis=-1)
        # ksi = torch.zeros(real_obs.shape).to(self.actor.device)
        # nn.init.normal_(tensor=ksi, mean=0.0, std=0.1)

        # recon, mean, std = self.behavior_policy(real_obs_act, real_next_obs)
        # recon_loss = F.mse_loss(recon, real_next_obs)
        # kl_loss = -0.5 * (1 + torch.log(std.pow(2)) - mean.pow(2) - std.pow(2)).mean()
        # vae_loss = recon_loss + 0.5 * kl_loss
        # self.behavior_policy_optim.zero_grad()
        # vae_loss.backward()
        # self.behavior_policy_optim.step()

        # with torch.no_grad():
        #     _, fack_raw_sampled_obs = self.behavior_policy.decode_multiple(fack_obs_act, num_decode=self.num_samples_mmd_match)
        #     fake_raw_next_obs = fake_next_obs.unsqueeze(1).repeat(1,4,1)
        #     mmd_loss = self.mmd_loss_laplacian(fake_raw_next_obs, fack_raw_sampled_obs)
        #     test_mmd = (mmd_loss-0.07).mean()

        #     actor_fake_action, _ = self.actforward(fake_obs)
        #     actor_fack_obs_act = torch.cat((fake_obs, actor_fake_action), axis=-1)
        #     _, actor_fack_sampled_obs = self.behavior_policy.decode_multiple(actor_fack_obs_act, num_decode=2)
        #     real_o = [real_obs]
        #     real_o.append(real_next_obs)
        #     real_raw_obs = torch.stack(real_o, dim = 1)
        #     actor_mmd_loss = self.mmd_loss_laplacian(actor_fack_sampled_obs, real_raw_obs)
        #     actor_test_mmd = (actor_mmd_loss-0.07).mean()

        #     loss_deta = 10000/deta
        #     mmd_q_loss = loss_deta *test_mmd * actor_test_mmd
        global deta
        deta += 1
        real_obs = real_batch["observations"]
        real_next_obs = real_batch["next_observations"]
        fake_obs = fake_batch["observations"]
        ksi = torch.zeros(real_obs.shape).to(self.actor.device)
        nn.init.normal_(tensor=ksi, mean=0.0, std=0.1)

        recon, mean, std = self.behavior_policy(real_obs, real_next_obs)
        recon_loss = F.mse_loss(recon, real_next_obs)
        kl_loss = -0.5 * (1 + torch.log(std.pow(2)) - mean.pow(2) - std.pow(2)).mean()
        vae_loss = recon_loss + 0.5 * kl_loss
        self.behavior_policy_optim.zero_grad()
        vae_loss.backward()
        self.behavior_policy_optim.step()

        with torch.no_grad():
            _, fack_raw_sampled_obs = self.behavior_policy.decode_multiple(fake_obs, num_decode=self.num_samples_mmd_match)
            actor_fake_action, _ = self.actforward(fake_obs)
            fake_obs_cpu = fake_obs.cpu().numpy()
            actor_fake_action_cpu = actor_fake_action.cpu().numpy()
            fake_next_obs, _, _, _= self.dynamics.step(fake_obs_cpu, actor_fake_action_cpu)
            fake_raw_next_obs = torch.tensor(fake_next_obs).unsqueeze(1).repeat(1,4,1).to(self.actor.device)
            mmd_loss = self.mmd_loss_laplacian(fake_raw_next_obs, fack_raw_sampled_obs)
            test_mmd = (mmd_loss-0.07).mean()
            loss_deta = 10000/deta
            mmd_q_loss = test_mmd

        # global deta
        # deta += 1
        # real_obs = real_batch["observations"]
        # real_next_obs = real_batch["next_observations"]
        # fake_obs = fake_batch["observations"]
        # ksi = torch.zeros(real_obs.shape).to(self.actor.device)
        # nn.init.normal_(tensor=ksi, mean=0.0, std=0.1)

        # # recon, mean, std = self.behavior_policy(real_obs, real_next_obs)
        # # recon_loss = F.mse_loss(recon, real_next_obs)
        # # kl_loss = -0.5 * (1 + torch.log(std.pow(2)) - mean.pow(2) - std.pow(2)).mean()
        # # vae_loss = recon_loss + 0.5 * kl_loss
        # # self.behavior_policy_optim.zero_grad()
        # # vae_loss.backward()
        # # self.behavior_policy_optim.step()

        # with torch.no_grad():
        #     # _, fack_raw_sampled_obs = self.behavior_policy.decode_multiple(fake_obs, num_decode=self.num_samples_mmd_match)
        #     actor_fake_action, _ = self.actforward(fake_obs)
        #     fake_obs_cpu = fake_obs.cpu().numpy()
        #     actor_fake_action_cpu = actor_fake_action.cpu().numpy()
        #     fake_next_obs, _, _, _= self.dynamics.step(fake_obs_cpu, actor_fake_action_cpu)
        #     fake_raw_next_obs = torch.tensor(fake_next_obs).unsqueeze(1).repeat(1,2,1).to(self.actor.device)
        #     real_o = [real_obs]
        #     real_o.append(real_next_obs)
        #     real_raw_obs = torch.stack(real_o, dim = 1)
        #     mmd_loss = self.mmd_loss_laplacian(fake_raw_next_obs, real_raw_obs)
        #     test_mmd = (mmd_loss-0.07).mean()
        #     loss_deta = 10000/deta
        #     mmd_q_loss = test_mmd
        # update critic
        q1, q2 = self.critic1(obss, actions), self.critic2(obss, actions)
        with torch.no_grad():
            next_actions, next_log_probs = self.actforward(next_obss)
            next_q = torch.min(
                self.critic1_old(next_obss, next_actions), self.critic2_old(next_obss, next_actions)
            ) - self._alpha * next_log_probs
            next_q = next_q #- mmd_q_loss
            target_q = rewards + self._gamma * (1 - terminals) * next_q

        critic1_loss = ((q1 - target_q).pow(2)).mean() #+ mmd_q_loss
        self.critic1_optim.zero_grad()
        critic1_loss.backward()
        self.critic1_optim.step()

        critic2_loss = ((q2 - target_q).pow(2)).mean()# + mmd_q_loss
        self.critic2_optim.zero_grad()
        critic2_loss.backward()
        self.critic2_optim.step()

        a, log_probs = self.actforward(obss)
        q1a, q2a = self.critic1(obss, a), self.critic2(obss, a)

        actor_loss = - torch.min(q1a, q2a).mean() + self._alpha * log_probs.mean() + 10*mmd_q_loss#- loss_deta * actor_test_mmd
        self.actor_optim.zero_grad()
        actor_loss.backward()
        self.actor_optim.step()

        if self._is_auto_alpha:
            log_probs = log_probs.detach() + self._target_entropy
            alpha_loss = -(self._log_alpha * log_probs).mean()
            self.alpha_optim.zero_grad()
            alpha_loss.backward()
            self.alpha_optim.step()
            self._alpha = torch.clamp(self._log_alpha.detach().exp(), 0.0, 1.0)

        self._sync_weight()

        result = {
            "loss/actor": actor_loss.item(),
            "loss/critic1": critic1_loss.item(),
            "loss/critic2": critic2_loss.item(),
            "loss/mmd": mmd_q_loss.item(),
            # "loss/actor_mmd": actor_test_mmd.item(),
            "loss/fack_mmd": test_mmd.item(),
            # "loss/vae_loss": vae_loss.item(),
        }

        if self._is_auto_alpha:
            result["loss/alpha"] = alpha_loss.item()
            result["alpha"] = self._alpha.item()

        return result


# Copyright (c) 2012, the Mozilla Foundation. All rights reserved.
# Use of this source code is governed by the Simplified BSD License which can
# be found in the LICENSE file.

import os
import ctypes
import errno
import stat
from ctypes import POINTER, WinError, sizeof, byref
from ctypes import wintypes
from ctypes.wintypes import DWORD, HANDLE, BOOL
kernel32 = ctypes.windll.kernel32

LPDWORD = POINTER(DWORD)

GENERIC_READ  = 0x80000000
GENERIC_WRITE = 0x40000000

FILE_SHARE_READ   = 0x00000001
FILE_SHARE_WRITE  = 0x00000002
FILE_SHARE_DELETE = 0x00000004

FILE_SUPPORTS_HARD_LINKS     = 0x00400000
FILE_SUPPORTS_REPARSE_POINTS = 0x00000080

FILE_ATTRIBUTE_READONLY      = 0x00000001
FILE_ATTRIBUTE_DIRECTORY     = 0x00000010
FILE_ATTRIBUTE_REPARSE_POINT = 0x00000400

FILE_FLAG_OPEN_REPARSE_POINT = 0x00200000
FILE_FLAG_BACKUP_SEMANTICS   = 0x02000000

# Various constants from windows.h
ERROR_FILE_NOT_FOUND = 2
ERROR_NO_MORE_FILES = 18

# Numer of seconds between 1601-01-01 and 1970-01-01
SECONDS_BETWEEN_EPOCHS = 11644473600

OPEN_EXISTING = 3

MAX_PATH = 260

INVALID_HANDLE_VALUE = ctypes.c_void_p(-1).value

class FILETIME(ctypes.Structure):
    _fields_ = [("dwLowDateTime", DWORD),
                ("dwHighDateTime", DWORD)]

class BY_HANDLE_FILE_INFORMATION(ctypes.Structure):
    _fields_ = [("dwFileAttributes", DWORD),
                ("ftCreationTime", FILETIME),
                ("ftLastAccessTime", FILETIME),
                ("ftLastWriteTime", FILETIME),
                ("dwVolumeSerialNumber", DWORD),
                ("nFileSizeHigh", DWORD),
                ("nFileSizeLow", DWORD),
                ("nNumberOfLinks", DWORD),
                ("nFileIndexHigh", DWORD),
                ("nFileIndexLow", DWORD)]

# http://msdn.microsoft.com/en-us/library/windows/desktop/aa363858
CreateFile = ctypes.windll.kernel32.CreateFileW
CreateFile.argtypes = [ctypes.c_wchar_p, DWORD, DWORD, ctypes.c_void_p,
                       DWORD, DWORD, HANDLE]
CreateFile.restype = HANDLE

# http://msdn.microsoft.com/en-us/library/windows/desktop/aa364944
GetFileAttributes = ctypes.windll.kernel32.GetFileAttributesW
GetFileAttributes.argtypes = [ctypes.c_wchar_p]
GetFileAttributes.restype = DWORD

# http://msdn.microsoft.com/en-us/library/windows/desktop/aa364952
GetFileInformationByHandle = ctypes.windll.kernel32.GetFileInformationByHandle
GetFileInformationByHandle.argtypes = [HANDLE, POINTER(BY_HANDLE_FILE_INFORMATION)]
GetFileInformationByHandle.restype = BOOL

# http://msdn.microsoft.com/en-us/library/windows/desktop/aa364996
GetVolumePathName = ctypes.windll.kernel32.GetVolumePathNameW
GetVolumePathName.argtypes = [ctypes.c_wchar_p, ctypes.c_wchar_p, DWORD]
GetVolumePathName.restype = BOOL

# http://msdn.microsoft.com/en-us/library/windows/desktop/aa364993
GetVolumeInformation = ctypes.windll.kernel32.GetVolumeInformationW
GetVolumeInformation.argtypes = [ctypes.c_wchar_p, ctypes.c_wchar_p, DWORD,
                                 LPDWORD, LPDWORD, LPDWORD, ctypes.c_wchar_p,
                                 DWORD]
GetVolumeInformation.restype = BOOL

# http://msdn.microsoft.com/en-us/library/windows/desktop/aa363216
DeviceIoControl = ctypes.windll.kernel32.DeviceIoControl
DeviceIoControl.argtypes = [HANDLE, DWORD, ctypes.c_void_p, DWORD,
                            ctypes.c_void_p, DWORD, LPDWORD, ctypes.c_void_p]
DeviceIoControl.restype = BOOL

# http://msdn.microsoft.com/en-us/library/windows/desktop/ms724211
CloseHandle = ctypes.windll.kernel32.CloseHandle
CloseHandle.argtypes = [HANDLE]
CloseHandle.restype = BOOL

FindFirstFileNameW = ctypes.windll.kernel32.FindFirstFileNameW
FindFirstFileNameW.argtypes = [
    ctypes.c_wchar_p, DWORD, LPDWORD, ctypes.c_wchar_p
]
FindFirstFileNameW.restype = HANDLE

FindNextFileNameW = ctypes.windll.kernel32.FindNextFileNameW
FindNextFileNameW.argtypes = [HANDLE, LPDWORD, ctypes.c_wchar_p]
FindNextFileNameW.restype = BOOL

FindClose = ctypes.windll.kernel32.FindClose
FindClose.argtypes = [HANDLE]
FindClose.restype = BOOL

def getfileinfo(path):
    """
    Return information for the file at the given path. This is going to be a
    struct of type BY_HANDLE_FILE_INFORMATION.
    """
    flags = FILE_FLAG_OPEN_REPARSE_POINT | FILE_FLAG_BACKUP_SEMANTICS
    hfile = CreateFile(path, GENERIC_READ, FILE_SHARE_READ, None, OPEN_EXISTING, flags, None)
    if hfile is None:
        raise win_error(ctypes.GetLastError(), path)
    info = BY_HANDLE_FILE_INFORMATION()
    rv = GetFileInformationByHandle(hfile, info)
    CloseHandle(hfile)
    if rv == 0:
        raise win_error(ctypes.GetLastError(), path)
    return info

def getvolumepath(path):
    # Add 1 for a trailing backslash if necessary, and 1 for the terminating
    # null character.
    volpath = ctypes.create_unicode_buffer(len(path) + 2)
    rv = GetVolumePathName(path, volpath, len(volpath))
    if rv == 0:
        raise win_error(ctypes.GetLastError(), path)
    return volpath.value

def getvolumeinfo(path):
    """
    Return information for the volume containing the given path. This is going
    to be a pair containing (file system, file system flags).
    """

    volpath = getvolumepath(path)

    fsnamebuf = ctypes.create_unicode_buffer(MAX_PATH + 1)
    fsflags = DWORD(0)
    rv = GetVolumeInformation(volpath, None, 0, None, None, byref(fsflags),
                              fsnamebuf, len(fsnamebuf))
    if rv == 0:
        raise win_error(ctypes.GetLastError(), path)

    return (fsnamebuf.value, fsflags.value)

def get_all_hardlinkds(path):
    all_links = set()
    volume_root = getvolumepath(path)
    link_name = ctypes.create_unicode_buffer(65536)
    link_name_len = DWORD(len(link_name))

    hFind = FindFirstFileNameW(path, 0, byref(link_name_len), link_name)
    if hFind == INVALID_HANDLE_VALUE:
        return all_links
    while True:
        newLink = os.path.join(volume_root, str(link_name.value))
        all_links.add(newLink)
        if FindNextFileNameW(hFind, byref(link_name_len), link_name) != True:
            break
    FindClose(hFind)
    all_links.discard(path)
    return all_links

def hardlinks_supported(path):
    (fsname, fsflags) = getvolumeinfo(path)
    # FILE_SUPPORTS_HARD_LINKS isn't supported until Windows 7, so also check
    # whether the file system is NTFS
    return bool((fsflags & FILE_SUPPORTS_HARD_LINKS) or (fsname == "NTFS"))

def junctions_supported(path):
    (fsname, fsflags) = getvolumeinfo(path)
    return bool(fsflags & FILE_SUPPORTS_REPARSE_POINTS)

def win_error(error, filename):
    if error == 6:
        ose = OSError()
        ose.errno = errno.ENOENT
        ose.filename = filename
        ose.message = ctypes.FormatError(error)
        return ose
    exc = WindowsError(error, ctypes.FormatError(error))
    exc.filename = filename
    return exc

def attributes_to_mode(data, attributes):
    """Convert Win32 dwFileAttributes to st_mode."""
    mode = 0
    if attributes & FILE_ATTRIBUTE_DIRECTORY and \
        not (attributes & FILE_ATTRIBUTE_REPARSE_POINT):
        mode |= stat.S_IFDIR | 0o111
    else:
        mode |= stat.S_IFREG
    if attributes & FILE_ATTRIBUTE_READONLY:
        mode |= 0o444
    else:
        mode |= 0o666
    if attributes & FILE_ATTRIBUTE_REPARSE_POINT or \
        data.nNumberOfLinks >= 2:
        mode |= stat.S_IFLNK
    return mode

def filetime_to_time(filetime):
    """Convert Win32 FILETIME to time since Unix epoch in seconds."""
    total = filetime.dwHighDateTime << 32 | filetime.dwLowDateTime
    return total / 10000000.0 - SECONDS_BETWEEN_EPOCHS

def find_data_to_stat(data):
    """Convert Win32 FIND_DATA struct to stat_result."""
    st_mode = attributes_to_mode(data, data.dwFileAttributes)
    st_size = data.nFileSizeHigh << 32 | data.nFileSizeLow
    st_atime = filetime_to_time(data.ftLastAccessTime)
    st_mtime = filetime_to_time(data.ftLastWriteTime)
    st_ctime = filetime_to_time(data.ftCreationTime)
    # These are set to zero rather than None, per CPython's posixmodule.c
    st_ino = 0
    st_dev = 0
    st_nlink = 0
    st_uid = 0
    st_gid = 0
    return os.stat_result((st_mode, st_ino, st_dev, st_nlink, st_uid,
                           st_gid, st_size, st_atime, st_mtime, st_ctime))

def lstat(path):
    data = getfileinfo(path)
    return find_data_to_stat(data)


import numpy as np

def simplex(A):

    row = len(A)
    col = len(A[0])

    solution = np.array([0.0 for i in range(row)])
    solutionIndx = np.array([0 for i in range(row)])

    mini = np.argmin(A[row-1])

    while(A[row-1][mini]<0):

        ic = np.array([0.0 for i in range(row-1)])
        for i in range(row-1):
            ic[i] = A[i][col-1]/A[i][mini]
        #print(ic)
        icMin = np.argmin(ic)
        solutionIndx[mini] = icMin+1

        A[icMin] = A[icMin]/A[icMin][mini]
        #print(A)

        for i in range(row):
            if(i == icMin):
                continue
            A[i] = A[i] + A[i][mini]*(-1)*A[icMin]

        mini = np.argmin(A[row-1])
        print(A)
        #print(A[row-1][mini])

    for i in range(row):
        if(solutionIndx[i] != 0):
            solution[i] = A[solutionIndx[i]-1][col-1]

    solution[row-1] = A[row-1][col-1]

    return solution

#Driver Code

file = open("in2.txt","r")

z = np.array([file.readline().split()],float)

A = np.array([line.split() for line in file],float)

n = len(z[0])

z= -z

z = np.append(z, 0)

table = np.append(A,[z],axis =0)

I = np.array([[0.0] * len(table) for i in range(len(table))])

for i in range(len(table)):
    I[i][i] = 1

mat = np.matrix(table, float)
I = np.matrix(I, float)

mat = np.insert(mat, n, I, axis=1)

mat = np.array(mat)

result = simplex(mat)

for i in range(n):
    print("x",end="")
    print(i,end=" ")
    print("=",end="")
    print(result[i])

print("z= ",end="")
print(result[-1])


'''
一个机器人位于一个 m x n 网格的左上角 （起始点在下图中标记为“Start” ）。

机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角（在下图中标记为“Finish”）。

问总共有多少条不同的路径？

示例 1:

输入: m = 3, n = 2
输出: 3
解释:
从左上角开始，总共有 3 条路径可以到达右下角。
1. 向右 -> 向右 -> 向下
2. 向右 -> 向下 -> 向右
3. 向下 -> 向右 -> 向右
示例 2:

输入: m = 7, n = 3
输出: 28

来源：力扣（LeetCode）
链接：https://leetcode-cn.com/problems/unique-paths
著作权归领扣网络所有。商业转载请联系官方授权，非商业转载请注明出处。
'''
# 组合排列
'''class Solution(object):
    def uniquePaths(self, m, n):
        """
        :type m: int
        :type n: int
        :rtype: int
        """
        total = m + n -2
        xroad = m - 1
        return self.arrangement(total, min(xroad, total - xroad))

    def arrangement(self, n, m):
        o = 1
        p = 1
        q = 1
        for i in range(1, n + 1):
            o = o * i
            if i <= m:
                p = p * i
            if i <= n - m:
                q = q * i
        return int(o / (p * q))
'''

class Solution(object):
    def uniquePaths(self, m, n):
        """
        :type m: int
        :type n: int
        :rtype: int
        """
        memo = [[0] * n for _ in range(m)]
        # print(memo)
        return self.dfs(m - 1, n - 1, memo)

    def dfs(self, m, n, memo):
        if m == 0 or n == 0:
            return 1
        if memo[m][n]:
            return memo[m][n]
        up = self.dfs(m - 1, n, memo)
        left = self.dfs(m, n - 1, memo)
        memo[m][n] = up + left
        return memo[m][n]

s = Solution()
print(s.uniquePaths(3,2))

s = Solution()
print(s.uniquePaths(7,3))


from morse.middleware.ros import ROSPublisher
from nuclear_radiation_sensor.modifiers.gaussian_noise import \
    GaussianNoiseModifier

class ROSPublisherBase(ROSPublisher):
    """Base class for publishers, provides some shared code (get variance from
    modifier).
    """
    def initialize(self):
        self.variance = {}
        ROSPublisher.initialize(self)

    def get_variance(self, ci, field):
        """Get the variance, if only one modifier of type GaussianNoiseModifier
        is used. Otherwise, zero is returned.
        """
        try:
            return self.variance[field]
        except KeyError:
            # the output_modifiers list only contains the methods, use __self__
            # to get the instance
            modifier = ci.output_modifiers[0].__self__ if len(
                ci.output_modifiers) == 1 else None
            if isinstance(modifier, GaussianNoiseModifier):
                self.variance[field] = modifier.get_std_dev(field)**2
            else:
                self.variance[field] = 0.0
        return self.variance[field]


import codecs
from collections import Counter
import os
import glob
from textblob import TextBlob as tb
import math

def tf(word, blob):
	return (float)(blob.words.count(word)) / (float)(len(blob.words))

def n_containing(word, bloblist):
	return sum(1 for blob in bloblist if word in blob.words)

def idf(word, bloblist):
	return (float)(math.log(len(bloblist)) / (float)(1 + n_containing(word, bloblist)))

def tfidf(word, blob, bloblist):
	return tf(word, blob) * idf(word, bloblist)

words_list = []
stop = []
spam_bloblist = []
ham_bloblist = []
words_dict = {}
splsym = ["-",",","!","#","%","^","&","*","(",")","!", ":",".","{","}", "[","]",">","<","?","/", "*","~", "@"]

print(splsym)

stop_words = open("Stop_words.txt").read()
for line in stop_words:
	for word in line.split(' '):
		stop.append(word)

stop_string = ''.join(stop)

path_to_spam = "hw2_train/train/spam/*.txt"
path_to_ham = "hw2_train/train/ham/*.txt"

for file in glob.glob(path_to_spam):
	file_words = codecs.open(file, "r", encoding='utf-8', errors='ignore' )
	for line in file_words:
		for word in line.split():
			if not word in stop_string and not word in splsym and not word.isdigit():
				words_list.append(word)
	blob = tb(' '.join(words_list))
	spam_bloblist.append(blob)
	words_list = []

#print(spam_bloblist[3] + "\n")

for file in glob.glob(path_to_ham):
	file_words = codecs.open(file, "r", encoding='utf-8', errors='ignore' )
	for line in file_words:
		for word in line.split():
			if not word in stop_string and not word in splsym and not word.isdigit():
				words_list.append(word)
	blob = tb(' '.join(words_list))
	ham_bloblist.append(blob)
	words_list = []

prior_spam_probability = (len(spam_bloblist)) / (len(spam_bloblist) + len(ham_bloblist))

prior_ham_probability = 1.0 - prior_spam_probability

# for i, blob in enumerate(bloblist):

#print(ham_bloblist[3])

# for i in words:
# 	if not i in words_dict.keys():
# 		count = blob.words.count(i)
# 		words_dict[i] = count

spam_tfidf_dict = {}

for i, blob in enumerate(spam_bloblist):
	#print("Top words in document {}".format(i+1))
	scores = {word: tfidf(word, blob, spam_bloblist) for word in blob.words}
	sorted_words = sorted(scores.items(), key=lambda x: x[1], reverse=True)
	for word, score in sorted_words[:]:
	 	#print("\tWord: {}, TF-IDF: {}".format(word, round(score, 3)))
	 	if word in spam_tfidf_dict:
	 		spam_tfidf_dict[word] += round(score,3)
	 	else:
	 		spam_tfidf_dict[word] = round(score,3)

ham_tfidf_dict = {}

for i, blob in enumerate(ham_bloblist):
	print("Top words in document {}".format(i+1))
	scores = {word: tfidf(word, blob, ham_bloblist) for word in blob.words}
	sorted_words = sorted(scores.items(), key=lambda x: x[1], reverse=True)
	for word, score in sorted_words[:]:
	 	print("\tWord: {}, TF-IDF: {}".format(word, round(score, 3)))
	 	if word in ham_tfidf_dict:
	 		ham_tfidf_dict[word] += round(score,3)
	 	else:
	 		ham_tfidf_dict[word] = round(score,3)

# for i in words_dict.keys():
# # 	if words_dict[i] > 15:
# 	print(i, words_dict[i])


import matplotlib.pyplot as plt
import numpy as np
import helper as hlp

plt.xkcd()
plt.grid(True)

title1 = 'Example8. Nagy p.190'

#====================
va = [1, 0, 4]
#c=0
b = 1
IC0 = [0, 0]
t0 = 0
tb = 10
N = 100
#====================

def y_ex(t):
	return 0.5*(hlp.heaviside(t-np.pi)-hlp.heaviside(t-2*np.pi))*np.sin(2*t)

vb = [1, -1, 2, 0]
vc = [np.pi, 2.0*np.pi, -np.pi, 2]

Z2 = hlp.mkSlnT1_c(va, vb, vc, IC0, t0, tb, N)

hlp.showNumSolution(plt, Z2, y_ex, title1)
hlp.showPhase(plt, Z2, title1)