#This script will test whether the is_game_won function is working correctly.

#TESTING PLAN
#WHAT DO WE NEED TO DO?
    #calculate all possible cases (board cofigurations x latest position)
    #create pandas table that describes cases
    #create function that translates into board configuration x latest position inputs that can be inputted into is_game_won function
    #run is_won_function for each of these cases and add output to table.
    #add columns that recognize each potential winning combination (8 columns)
    #compare results
#CAVEATS
    #This may be impractical given the large number of possible cases -> consider random sampling
    #Because the bulk of the possible cases come from the case in which five board spots are filled, it may make sense not to add these configurations in when I'm just building the functions.
    #That case can be added later in full or in part. But no need to wait through the processing times until the end.

#Importing Libraries

import random
import pandas as pd
import matplotlib.pyplot as plt

#for factorials
import math
#for permutation
import itertools

# Importing from Game Script
from game_script import \
position_to_index, \
show_board, \
update_board, \
is_valid_move, \
is_symbol, \
turn, \
is_game_won, \
opening_sequence, \
load_empty_board

#FUNCTIONS

#FACTORIAL & COMBINATION FUNCTIONS

#Factorial Function (just a wrapper)
def factorial(val):
    return math.factorial(val)

#Generates number of combinations (used for calculatin number of board configurations)
def combination_num(n,r):
    return factorial(n)/((factorial(r)*factorial(n-r)))

#Generates combinations for n choose k.
#n defaults to ten since we will ultimately be chosing k positions out of 9 positions.
def combination_list(k, n = 10):
    #this generates a list of lists
    combs_list = list(itertools.combinations(list(range(1,n)),k))
    #generating strings with each item
    combs_strings = []
    for comb in combs_list:
        string = ""
        for i in range(len(comb)):
            string += str(comb[i])
        combs_strings.append(string)
    return combs_strings

#EDITING FUNCTIONS & BASIC CHECKS

def is_digit_in_integer(digit,integer):
    for i in range(len(str(integer))):
        if str(digit) in (str(integer)):
            return True
    return False

def drop_is_pos_columns(df):
    df.drop(columns = ["is_pos_1", "is_pos_2", "is_pos_3", "is_pos_4", "is_pos_5", "is_pos_6", "is_pos_7", "is_pos_8", "is_pos_9"], inplace = True)

def drop_combo_column(df):
    df.drop(columns = ["combo"], inplace = True)

def drop_win_combination_columns(df):
    df.drop(columns = ["is_win_123", "is_win_456", "is_win_789", "is_win_147", "is_win_258", "is_win_369", "is_win_159", "is_win_357"], inplace = True)

#FUNCTIONS FOR GENERATING THE TESTING_DF

#Generates latest position for a given board configuration
def position_generator(string_list):
    position_list = []
    for string in string_list:
        for i in range(len(string)):
            position_list.append((int(string), int(string[i])))
    return position_list

#generates columns for each position, showing whether it has been filled
def is_pos_generator():
    testing_df["is_pos_1"] = testing_df.apply(lambda x: is_digit_in_integer(1,x["combo"]), axis =1)
    testing_df["is_pos_2"] = testing_df.apply(lambda x: is_digit_in_integer(2,x["combo"]), axis =1)
    testing_df["is_pos_3"] = testing_df.apply(lambda x: is_digit_in_integer(3,x["combo"]), axis =1)
    testing_df["is_pos_4"] = testing_df.apply(lambda x: is_digit_in_integer(4,x["combo"]), axis =1)
    testing_df["is_pos_5"] = testing_df.apply(lambda x: is_digit_in_integer(5,x["combo"]), axis =1)
    testing_df["is_pos_6"] = testing_df.apply(lambda x: is_digit_in_integer(6,x["combo"]), axis =1)
    testing_df["is_pos_7"] = testing_df.apply(lambda x: is_digit_in_integer(7,x["combo"]), axis =1)
    testing_df["is_pos_8"] = testing_df.apply(lambda x: is_digit_in_integer(8,x["combo"]), axis =1)
    testing_df["is_pos_9"] = testing_df.apply(lambda x: is_digit_in_integer(9,x["combo"]), axis =1)

#Generates board based on row in testing dataframe
def row_to_board(row_index, symbol = "x"):
    #Start with an empty board
    global board_df
    board_df = load_empty_board()
    #Update the board based on the positions described in testing_df
    if testing_df.is_pos_1.iloc[row_index]:
        update_board(1,symbol)
    if testing_df.is_pos_2.iloc[row_index]:
        update_board(2,symbol)
    if testing_df.is_pos_3.iloc[row_index]:
        update_board(3,symbol)
    if testing_df.is_pos_4.iloc[row_index]:
        update_board(4,symbol)
    if testing_df.is_pos_5.iloc[row_index]:
        update_board(5,symbol)
    if testing_df.is_pos_6.iloc[row_index]:
        update_board(6,symbol)
    if testing_df.is_pos_7.iloc[row_index]:
        update_board(7,symbol)
    if testing_df.is_pos_8.iloc[row_index]:
        update_board(8,symbol)
    if testing_df.is_pos_9.iloc[row_index]:
        update_board(9,symbol)

# FUNCTIONS FOR GENERATING GAME RESULTS

#Generates adds the result of the is_game_won function to the testing df.
#By default, assumes 6 turns have been played.
def game_result_generator(symbol = "x", turn_count = 6):
    testing_df["is_actual_win"] = ""
    #Adding game results based on each row in counter.
    for row_index in range(testing_df.is_valid_win.count()):
        #create the board
        row_to_board(row_index, symbol)
        #generate game result - this isn't working right.
        game_result = is_game_won(testing_df.latest_pos.iloc[row_index],symbol,turn_count)
        testing_df.at[row_index,"is_actual_win"] = game_result

def game_result_validator():
    testing_df["is_result_valid"] = testing_df.is_actual_win == testing_df.is_valid_win

#FUNCTIONS FOR GENERATING THE INVALID DF

def invalid_result_generator():
    #selecting rows with invalid results
    invalid = testing_df[testing_df.is_result_valid == False].reset_index(drop = True)
    invalid.drop(columns = ["is_result_valid"], inplace = True)
    return invalid

#FUNCTIONS FOR SUMARIZING THE INVALID DF

def result_validity_summary():
    summary = testing_df.is_result_valid.value_counts()
    print("\nResults Validity Summary:\n", summary)
    return summary

def error_type_summary():
    summary = testing_df.groupby(["is_valid_win", "is_actual_win"]).latest_pos.count().reset_index()
    print("\nError Type Summary:\n", summary)
    return summary

def latest_position_summary():
    summary = invalid_df.latest_pos.value_counts()
    summary.sort_values(inplace = True)
    print("\nLatest Position Summary:\n",summary)
    return summary

def valid_win_location_summary():
    summary = invalid_df.valid_win_location.value_counts()
    print("\nValid Win Location Summary:\n", summary)
    return summary

#WHAT CASES DO WE NEED TO TEST IN ORDER TO BE SATISFIED THAT THE GAME IS WORKING CORRECTLY?

#Because the is_game_won function looks only at one symbol, I am going to focus on the spots occupied by x only.
#How many different ways can the board be filled with x's?
    #1-2 x on the board: these cases are irrelevant. The is_game_won function returns false until the players have had the opportunity to fill three positions.
    #3 x's on the board: C(9,3) possibilities.
    #4 x's on the board: C(9,4) possibilities.
    #5 x's on the board: C(9,5) possibilities.
    #6+ x's on the board: these cases are impossible. No character can occupy more than five positions on the board.

#print(combination_num(9,5) + combination_num(9,4) + combination_num(9,3))
#336 board configurations

#Because the is_game_won function depends on the most recent move played, we need to test each of the above board configurations with each possible latest played position.

#print(5*combination_num(9,5) + 4*combination_num(9,4) + 3*combination_num(9,3))
#1,386 test cases
    #Only 1,098 need to be tested as 288 are impossible (ie, moves were placed on the board after the game was already won) if the possible 1,098 cases are evaluated correctly.

#GENERATING A DATAFRAME THAT DESCRIBES ALL THE POSSIBLE COMBINATIONS

#list of board configurations
board_configuration_list = combination_list(3) + combination_list(4) + combination_list(5)
#list of test cases
test_case_list = position_generator(board_configuration_list)

#generating the dataframe
testing_df = pd.DataFrame(test_case_list, columns = ['combo', 'latest_pos'])

#New Columns Show Whether Each Position is Filled
is_pos_generator()

#Adding to the dataframe whether each scenario should have resulted in a win.
#winning by row
testing_df["is_win_123"] = testing_df.apply(lambda x: (x["is_pos_1"] == True) and (x["is_pos_2"] == True) and (x["is_pos_3"] == True), axis =1)
testing_df["is_win_456"] = testing_df.apply(lambda x: (x["is_pos_4"] == True) and (x["is_pos_5"] == True) and (x["is_pos_6"] == True), axis =1)
testing_df["is_win_789"] = testing_df.apply(lambda x: (x["is_pos_7"] == True) and (x["is_pos_8"] == True) and (x["is_pos_9"] == True), axis =1)
#winning by column
testing_df["is_win_147"] = testing_df.apply(lambda x: (x["is_pos_1"] == True) and (x["is_pos_4"] == True) and (x["is_pos_7"] == True), axis =1)
testing_df["is_win_258"] = testing_df.apply(lambda x: (x["is_pos_2"] == True) and (x["is_pos_5"] == True) and (x["is_pos_8"] == True), axis =1)
testing_df["is_win_369"] = testing_df.apply(lambda x: (x["is_pos_3"] == True) and (x["is_pos_6"] == True) and (x["is_pos_9"] == True), axis =1)
#winning by diagnal
testing_df["is_win_159"] = testing_df.apply(lambda x: (x["is_pos_1"] == True) and (x["is_pos_5"] == True) and (x["is_pos_9"] == True), axis =1)
testing_df["is_win_357"] = testing_df.apply(lambda x: (x["is_pos_3"] == True) and (x["is_pos_5"] == True) and (x["is_pos_7"] == True), axis =1)

#I want to pull out impossible situations, in which the game was won and then additional moves were played.
#The is_game_won function will work incorrectly in these cases, but if it is working correctly, these cases will never occur.

testing_df["is_impossible"] = testing_df.apply(lambda x: \
((x["is_win_123"] == True) and x["latest_pos"] not in [1,2,3]) or \
((x["is_win_456"] == True) and x["latest_pos"] not in [4,5,6]) or \
((x["is_win_789"] == True) and x["latest_pos"] not in [7,8,9]) or \
((x["is_win_147"] == True) and x["latest_pos"] not in [1,4,7]) or \
((x["is_win_258"] == True) and x["latest_pos"] not in [2,5,8]) or \
((x["is_win_369"] == True) and x["latest_pos"] not in [3,6,9]) or \
((x["is_win_159"] == True) and x["latest_pos"] not in [1,5,9]) or \
((x["is_win_357"] == True) and x["latest_pos"] not in [3,5,7]),
axis =1)

#possibility_table = testing_df.groupby("is_impossible").combo.count()
#print(possibility_table)

testing_df = testing_df[testing_df.is_impossible == False].reset_index(drop = True)
testing_df.drop(columns = ["is_impossible"], inplace = True)

#Adding to the table whether there is a win on the board.
testing_df["is_valid_win"] = testing_df.apply(lambda x: \
(x["is_win_123"] == True) or \
(x["is_win_456"] == True) or \
(x["is_win_789"] == True) or \
(x["is_win_147"] == True) or \
(x["is_win_258"] == True) or \
(x["is_win_369"] == True) or \
(x["is_win_159"] == True) or \
(x["is_win_357"] == True),
axis =1)

#I really do not want all these columns. Creating a single column that shows how the board has been won.

testing_df["valid_win_location"] = ""

for row_index in range(testing_df.is_valid_win.count()):
    win = None
    if testing_df.is_win_123.iloc[row_index]:
        win = 123
    elif testing_df.is_win_456.iloc[row_index]:
        win = 456
    elif testing_df.is_win_789.iloc[row_index]:
        win = 789
    elif testing_df.is_win_147.iloc[row_index]:
        win = 147
    elif testing_df.is_win_258.iloc[row_index]:
        win = 258
    elif testing_df.is_win_369.iloc[row_index]:
        win = 369
    elif testing_df.is_win_159.iloc[row_index]:
        win = 159
    elif testing_df.is_win_357.iloc[row_index]:
        win = 357
    testing_df.at[row_index,"valid_win_location"] = win

#print(testing_df)

#RUNNING THE GAME FOR EACH SCENARIO DESCRIBED IN THE TESTING SCENARIO & EVALUATING WHETHER RESULTS ARE CORRECT.

game_result_generator()
game_result_validator()

#INVESTIGATING ERRORS

invalid_df = invalid_result_generator()

drop_is_pos_columns(invalid_df)
drop_win_combination_columns(invalid_df)
print(invalid_df)

result_validity_summary()
error_type_summary()
#latest_position_summary()
#valid_win_location_summary()


import asyncio
import time

from aiozk import exc, WatchEvent

from .recipe import Recipe

class Barrier(Recipe):

    def __init__(self, path):
        super(Barrier, self).__init__()
        self.path = path

    async def create(self):
        await self.ensure_path()
        await self.create_znode(self.path)

    async def lift(self):
        try:
            await self.client.delete(self.path)
        except exc.NoNode:
            pass

    async def wait(self, timeout=None):
        time_limit = None
        if timeout is not None:
            time_limit = time.time() + timeout

        barrier_lifted = self.client.wait_for_events(
            [WatchEvent.DELETED], self.path
        )

        exists = await self.client.exists(path=self.path, watch=True)
        if not exists:
            return

        try:
            if time_limit:
                await asyncio.wait_for(barrier_lifted, time_limit, loop=self.client.loop)
            else:
                await barrier_lifted
        except asyncio.TimeoutError:
            raise exc.TimeoutError


import  lmdb
import  os
import  face_comm

class face_lmdb:
    def __init__(self):
        self.db_file=os.path.abspath(face_comm.get_conf('lmdb','lmdb_path'))
    def add_embed_to_lmdb(self,id,vector):
        if type(id) is not 'string':
            id = str(id)
        evn = lmdb.open(self.db_file)
        wfp = evn.begin(write=True)
        wfp.put(key=id.encode(), value=face_comm.embed_to_str(vector).encode())
        wfp.commit()
        evn.close()

if __name__=='__main__':
    #插入数据
    embed = face_lmdb()


from __future__ import division
from __future__ import print_function
from builtins import range

import torch
import torch.nn as nn
import torch.nn.functional as nnf
import numpy as np
from torch.autograd import Variable

class PointNetSeg(nn.Module):
    def __init__(self, nf_conv, nf_fc, nf_conv_stn, nf_fc_stn, nfeat, nfeat_stn=2, nfeat_global=1, prelast_do=0.5,
                 last_ac=False):
        super(PointNetSeg, self).__init__()
        if nfeat_stn > 0:
            self.stn = STNkD(nfeat, nf_conv_stn, nf_fc_stn, BN=False)
        self.nfeat_stn = nfeat_stn

        modules2 = []
        modules3 = []
        for i in range(len(nf_conv)):
            if i <= 1:
                modules2.append(nn.Conv1d(nf_conv[i - 1] if i > 0 else nfeat, nf_conv[i], 1))
                modules2.append(nn.BatchNorm1d(nf_conv[i]))
                modules2.append(nn.ReLU(True))
                self.convs2 = nn.Sequential(*modules2)
            else:
                modules3.append(nn.Conv1d(nf_conv[i - 1] if i > 0 else nfeat, nf_conv[i], 1))
                modules3.append(nn.BatchNorm1d(nf_conv[i]))
                modules3.append(nn.ReLU(True))
                self.convs3 = nn.Sequential(*modules3)

        modules = []
        modules.append(nn.Linear(320, 256))
        modules.append(nn.BatchNorm1d(256))
        modules.append(nn.ReLU(True))
        modules.append(nn.Linear(256, 128))
        modules.append(nn.BatchNorm1d(128))
        modules.append(nn.ReLU(True))
        modules.append(nn.Linear(128, 5))
        self.fcs = nn.Sequential(*modules)

    def forward(self, input):
        if self.nfeat_stn > 0:
            T = self.stn(input[:, :self.nfeat_stn, :])
            xy_transf = torch.bmm(input[:, :2, :].transpose(1, 2), T).transpose(1, 2)
            input = torch.cat([xy_transf, input[:, 2:, :]], 1)

        input2 = self.convs2(input)
        input = self.convs3(input2)
        input = nnf.max_pool1d(input, input.size(2)).squeeze(2)
        Segstart = torch.cat([input.view(-1, np.shape(input)[1], 1).repeat(1, 1, np.shape(input2)[2]), input2], 1)
        Segstart = self.fcs(Segstart[0].transpose(0, 1))
        return Segstart

class STNkD(nn.Module):
    """
    Spatial Transformer Net for PointNet, producing a KxK transformation matrix.
    Parameters:
      nfeat: number of input features
      nf_conv: list of layer widths of point embeddings (before maxpool)
      nf_fc: list of layer widths of joint embeddings (after maxpool)
    """
    def __init__(self, nfeat, nf_conv, nf_fc, K=2, BN=True):
        super(STNkD, self).__init__()

        modules = []
        for i in range(len(nf_conv)):
            modules.append(nn.Conv1d(nf_conv[i-1] if i>0 else nfeat, nf_conv[i], 1))
            modules.append(nn.BatchNorm1d(nf_conv[i]))
            modules.append(nn.ReLU(True))
        self.convs = nn.Sequential(*modules)

        modules = []
        for i in range(len(nf_fc)):
            modules.append(nn.Linear(nf_fc[i-1] if i>0 else nf_conv[-1], nf_fc[i]))
            if BN:
                modules.append(nn.BatchNorm1d(nf_fc[i]))
            modules.append(nn.ReLU(True))
        self.fcs = nn.Sequential(*modules)

        self.proj = nn.Linear(nf_fc[-1], K*K)
        nn.init.constant(self.proj.weight, 0); nn.init.constant(self.proj.bias, 0)
        self.eye = torch.eye(K).unsqueeze(0)

    def forward(self, input):
        self.eye = self.eye.cuda() if input.is_cuda else self.eye
        input = self.convs(input)
        input = nnf.max_pool1d(input, input.size(2)).squeeze(2)
        input = self.fcs(input)
        input = self.proj(input)
        return input.view(-1,self.eye.size(1),self.eye.size(2)) + Variable(self.eye)


'''
Small class to handle a single fiber. The two main references are
1.
2.

We use a linearized implicit penalty method to enforce inextensibility

X_s * X_st = (\tau / dt) * (1 - X^n * X^{n+1})

We use a local mobility derived from slender body theory, see Ref 1 or 2.

M = c_0 * (1 + X_s * X_s) + c_1 * (1 - X_s * X_2)

with
c_0 = -log(e * epsilon**2) / (8 * pi * viscosity)
c_1 = 2 / (8 * pi * viscosity)
'''
# Standard imports
import numpy as np
import scipy.linalg
from functools import partial
import sys
sys.path.append('../')

# Local imports
from utils import cheb

class fiber(object):
  '''
  Small class to handle a single fiber.
  '''
  def __init__(self,
               num_points = 32,
               length = 1.0,
               epsilon = 1e-03,
               E = 1.0,
               viscosity = 1.0,
               dt = 1e-03,
               BC_start_0 = 'force',
               BC_start_1 = 'torque',
               BC_end_0 = 'force',
               BC_end_1 = 'torque',
               BC_start_vec_0 = np.zeros(3),
               BC_start_vec_1 = np.zeros(3),
               BC_end_vec_0 = np.zeros(3),
               BC_end_vec_1 = np.zeros(3)):
    # Store some parameters
    self.num_points = num_points
    self.length = length
    self.epsilon = epsilon
    self.E = E
    self.viscosity = viscosity
    self.dt = dt
    self.c_0 = -np.log(np.e * epsilon**2) / (8.0 * np.pi * viscosity)
    self.c_1 = 2.0 / (8.0 * np.pi * viscosity)
    self.beta = -np.log(np.e * epsilon**2) * np.maximum(E, 10.0) / (length**4 * dt)
    self.BC_start_0 = BC_start_0
    self.BC_start_1 = BC_start_1
    self.BC_end_0 = BC_end_0
    self.BC_end_1 = BC_end_1
    self.BC_start_vec_0 = BC_start_vec_0
    self.BC_start_vec_1 = BC_start_vec_1
    self.BC_end_vec_0 = BC_end_vec_0
    self.BC_end_vec_1 = BC_end_vec_1

    # Get Chebyshev differential matrix
    D_1, s = cheb.cheb(num_points - 1)

    # Flip material coordinate to go from -1 to 1
    self.s = np.flipud(s)
    self.D_1_0 = np.flipud(np.flipud(D_1.T).T)

    # Get high order derivatives, enforce D * vector_ones = 0
    self.D_2_0 = np.dot(self.D_1_0, self.D_1_0)
    self.D_2_0 -= np.diag(np.sum(self.D_2_0.T, axis=0))
    self.D_3_0 = np.dot(self.D_2_0, self.D_1_0)
    self.D_3_0 -= np.diag(np.sum(self.D_3_0.T, axis=0))
    self.D_4_0 = np.dot(self.D_2_0, self.D_2_0)
    self.D_4_0 -= np.diag(np.sum(self.D_4_0.T, axis=0))

    # Differential matrices for any length
    self.D_1 = self.D_1_0 * (2.0 / self.length)
    self.D_2 = self.D_2_0 * (2.0 / self.length)**2
    self.D_3 = self.D_3_0 * (2.0 / self.length)**3
    self.D_4 = self.D_4_0 * (2.0 / self.length)**4

    # Create fiber configuration, straight fiber along x
    self.x = np.zeros((num_points, 3))
    self.x[:,0] = -s / 2.0
    return

  def scale_D():
    '''
    Scale differential matrices with fiber length. Note that Chebyshev polynomial
    assumes a domain of length 2 ([-1, 1]).
    '''
    D_1 = D_1_0 * (2.0 / self.length)
    D_2 = D_2_0 * (2.0 / self.length)**2
    D_3 = D_3_0 * (2.0 / self.length)**3
    D_4 = D_4_0 * (2.0 / self.length)**4

  def set_conf(self, x):
    '''
    Set fiber configuration. It does not check if inextensibility holds.
    '''
    self.x = x
    return

  def set_BC(self,
             BC_start_0 = 'force',
             BC_start_1 = 'torque',
             BC_end_0 = 'force',
             BC_end_1 = 'torque',
             BC_start_vec_0 = np.zeros(3),
             BC_start_vec_1 = np.zeros(3),
             BC_end_vec_0 = np.zeros(3),
             BC_end_vec_1 = np.zeros(3)):
    '''
    Set Boundary Conditions options. For each end of the fiber
    (labeled start and end) we need to provide two boundary conditions,
    one for the translational degrees of freedom and other for the orientations.

    options for translation:
    force = it applies a force to the end of the fiber.
    position = it prescribes the position of the fiber's end.

    options for rotation:
    torque = it applies a torque to the end of the fiber.
    angle = it enforces an orientation of the fiber's end.

    '''
    self.BC_start_0 = BC_start_0
    self.BC_start_1 = BC_start_1
    self.BC_end_0 = BC_end_0
    self.BC_end_1 = BC_end_1
    self.BC_start_vec_0 = BC_start_vec_0
    self.BC_start_vec_1 = BC_start_vec_1
    self.BC_end_vec_0 = BC_end_vec_0
    self.BC_end_vec_1 = BC_end_vec_1
    return

  def form_linear_operator(self):
    '''
    Returns the linear operator A that define the linear system

    A * (X^{n+1}, T^{n+1}) = RHS
    '''
    # Compute material derivatives at time
    xs = np.dot(self.D_1, self.x)
    xss = np.dot(self.D_2, self.x)
    xsss = np.dot(self.D_3, self.x)
    xssss = np.dot(self.D_4, self.x)

    # Allocate memory for matrix
    A = np.zeros((4 * self.num_points, 4 * self.num_points))

    I = np.eye(self.num_points)

    # Build submatrices to couple coordinates to coordinates
    A_XX = (I / self.dt) \
           + self.E * self.c_0 * (np.dot((I + np.diag(xs[:,0]**2)), self.D_4)) \
           + self.E * self.c_1 * (np.dot((I - np.diag(xs[:,0]**2)), self.D_4))
    A_XY = self.E * self.c_0 * np.dot(np.diag(xs[:,0]*xs[:,1]), self.D_4) - self.E * self.c_1 * np.dot(np.diag(xs[:,0]*xs[:,1]), self.D_4)
    A_XZ = self.E * self.c_0 * np.dot(np.diag(xs[:,0]*xs[:,2]), self.D_4) - self.E * self.c_1 * np.dot(np.diag(xs[:,0]*xs[:,2]), self.D_4)
    A_YY = (I / self.dt) \
           + self.E * self.c_0 * np.dot((I + np.diag(xs[:,1]**2)), self.D_4) \
           + self.E * self.c_1 * np.dot((I - np.diag(xs[:,1]**2)), self.D_4)
    A_YZ = self.E * self.c_0 * np.dot(np.diag(xs[:,1]*xs[:,2]), self.D_4) - self.E * self.c_1 * np.dot(np.diag(xs[:,1]*xs[:,2]), self.D_4)
    A_ZZ = (I / self.dt) \
           + self.E * self.c_0 * np.dot((I + np.diag(xs[:,2]**2)), self.D_4) \
           + self.E * self.c_1 * np.dot((I - np.diag(xs[:,2]**2)), self.D_4)

    # Build submatrices to couple tension to coordinates
    A_XT = -self.c_0 * (2.0 * np.dot(np.diag(xs[:,0]), self.D_1) + np.diag(xss[:,0])) - self.c_1 * np.diag(xss[:,0])
    A_YT = -self.c_0 * (2.0 * np.dot(np.diag(xs[:,1]), self.D_1) + np.diag(xss[:,1])) - self.c_1 * np.diag(xss[:,1])
    A_ZT = -self.c_0 * (2.0 * np.dot(np.diag(xs[:,2]), self.D_1) + np.diag(xss[:,2])) - self.c_1 * np.diag(xss[:,2])

    # Build submatrices coordinates to tension
    c = np.log(np.e * self.epsilon**2);
    A_TX = self.E * 6.0 * c * np.dot(np.diag(xsss[:,0]), self.D_3) - \
           self.E * (2.0 - 7.0 * c) * np.dot(np.diag(xss[:,0]), self.D_4) - \
           self.beta * np.dot(np.diag(xs[:,0]), self.D_1)
    A_TY = self.E * 6.0 * c * np.dot(np.diag(xsss[:,1]), self.D_3) - \
           self.E * (2.0 - 7.0 * c) * np.dot(np.diag(xss[:,1]), self.D_4) - \
           self.beta * np.dot(np.diag(xs[:,1]), self.D_1)
    A_TZ = self.E * 6.0 * c * np.dot(np.diag(xsss[:,2]), self.D_3) - \
           self.E * (2.0 - 7.0 * c) * np.dot(np.diag(xss[:,2]), self.D_4) - \
           self.beta * np.dot(np.diag(xs[:,2]), self.D_1)

    # Build submatrices tension to tension
    A_TT = 2.0 * c * self.D_2 + (2.0 - c) * np.diag(xss[:,0]**2  + xss[:,1]**2 + xss[:,2]**2)

    # Collect all block matrices
    A = np.vstack((np.hstack((A_XX, A_XY, A_XZ, A_XT)),
                   np.hstack((A_XY, A_YY, A_YZ, A_YT)),
                   np.hstack((A_XZ, A_YZ, A_ZZ, A_ZT)),
                   np.hstack((A_TX, A_TY, A_TZ, A_TT))))
    return A

  def compute_RHS(self, force_external = None, flow = None):
    '''
    Compute the Right Hand Side for the linear system
    A * (X^{n+1}, T^{n+1}) = RHS

    with
    RHS = (X^n / dt + flow + Mobility * force_external, ...)

    Note that the internal force contributions (flexibility and in extensibility)
    force_internal includes flexibility and inextensibility contributions have been
    included in the linear operator A.
    '''
    # Compute material derivatives at time n
    xs = np.dot(self.D_1, self.x)

    I = np.eye(self.num_points)

    # Build RHS
    RHS = np.zeros(4 * self.num_points)
    RHS[0:self.num_points]                   = self.x[:,0] / self.dt
    RHS[self.num_points:2*self.num_points]   = self.x[:,1] / self.dt
    RHS[2*self.num_points:3*self.num_points] = self.x[:,2] / self.dt
    RHS[3*self.num_points:]                  = -self.beta

    # Add background flow contribution
    if flow is not None:
      RHS[0:self.num_points]                   += flow[:,0]
      RHS[self.num_points:2*self.num_points]   += flow[:,1]
      RHS[2*self.num_points:3*self.num_points] += flow[:,2]
      RHS[3*self.num_points:] -= (8.0 * np.pi * self.viscosity) * (xs[:,0] * np.dot(self.D_1, flow[:,0]) +
                                                                   xs[:,1] * np.dot(self.D_1, flow[:,1]) +
                                                                   xs[:,2] * np.dot(self.D_1, flow[:,2]))

    # Add external force contribution
    if force_external is not None:
      xss = np.dot(self.D_2, self.x)
      fs = np.dot(self.D_1, force_external)
      RHS[0:self.num_points] += self.c_0 * np.dot((I + np.diag(xs[:,0]**2)),         force_external[:,0]) + \
                                self.c_0 * np.dot((                          np.diag(xs[:,0] * xs[:,1])),  force_external[:,1]) + \
                                self.c_0 * np.dot((                          np.diag(xs[:,0] * xs[:,2])),  force_external[:,2]) + \
                                self.c_1 * np.dot((I - np.diag(xs[:,0]**2)),         force_external[:,0]) + \
                                self.c_1 * np.dot((                        - np.diag(xs[:,0] * xs[:,1])),  force_external[:,1]) + \
                                self.c_1 * np.dot((                        - np.diag(xs[:,0] * xs[:,2])),  force_external[:,2])
      RHS[self.num_points:2*self.num_points] += self.c_0 * np.dot((                          np.diag(xs[:,1] * xs[:,0])), force_external[:,0]) + \
                                                self.c_0 * np.dot((I + np.diag(xs[:,1]**2)),        force_external[:,1]) + \
                                                self.c_0 * np.dot((                          np.diag(xs[:,1] * xs[:,2])), force_external[:,2]) + \
                                                self.c_1 * np.dot((                        - np.diag(xs[:,1] * xs[:,0])), force_external[:,0]) + \
                                                self.c_1 * np.dot((I - np.diag(xs[:,1]**2)),        force_external[:,1]) + \
                                                self.c_1 * np.dot((                        - np.diag(xs[:,1] * xs[:,2])), force_external[:,2])
      RHS[2*self.num_points:3*self.num_points] += self.c_0 * np.dot((                          np.diag(xs[:,2] * xs[:,0])),  force_external[:,0]) + \
                                                  self.c_0 * np.dot((                          np.diag(xs[:,2] * xs[:,1])),  force_external[:,1]) + \
                                                  self.c_0 * np.dot((I + np.diag(xs[:,2]**2)),         force_external[:,2]) + \
                                                  self.c_1 * np.dot((                        - np.diag(xs[:,2] * xs[:,0])),  force_external[:,0]) + \
                                                  self.c_1 * np.dot((                        - np.diag(xs[:,2] * xs[:,1])),  force_external[:,1]) + \
                                                  self.c_1 * np.dot((I - np.diag(xs[:,2]**2)),         force_external[:,2])
      RHS[3*self.num_points:] += self.c_0 * (2.0 * (xs[:,0] * fs[:,0] + xs[:,1] * fs[:,1] + xs[:,2] * fs[:,2]) +
                                             (xss[:,0] * force_external[:,0] + xss[:,1] * force_external[:,1] + xss[:,2] * force_external[:,2])) - \
        self.c_1 * (xss[:,0] * force_external[:,0] + xss[:,1] * force_external[:,1] + xss[:,2] * force_external[:,2])
    return RHS

  def apply_BC(self, A, RHS):
    '''
    Modify linear operator A and RHS to obey boundary conditions.
    '''
    # Compute material derivatives at time n
    xs = np.dot(self.D_1, self.x)
    xss = np.dot(self.D_2, self.x)

    I = np.eye(self.num_points)
    num = self.num_points

    # Apply BC at one end
    if self.BC_start_0 == 'force':
      # Apply force
      A[0,:]               = 0
      A[0,0:num]           = -self.D_3[0,:]
      A[0,3*num:]          = xs[0,0] * I[0,:]
      A[num,:]             = 0
      A[num,num:2*num]     = -self.D_3[0,:]
      A[num,3*num:]        = xs[0,1] * I[0,:]
      A[2*num,:]           = 0
      A[2*num,2*num:3*num] = -self.D_3[0,:]
      A[2*num,3*num:]      = xs[0,2] * I[0,:]
      A[3*num,:]           = 0
      A[3*num,0:num]       = self.D_2[0,:] * xss[0,0]
      A[3*num,num:2*num]   = self.D_2[0,:] * xss[0,1]
      A[3*num,2*num:3*num] = self.D_2[0,:] * xss[0,2]
      A[3*num,3*num:]      = I[0,:]

      RHS[0]     = self.BC_start_vec_0[0]
      RHS[num]   = self.BC_start_vec_0[1]
      RHS[2*num] = self.BC_start_vec_0[2]
      RHS[3*num] = np.dot(self.BC_start_vec_0, xs[0,:])
    else:
      # Enforce position
      A[0,:] = 0
      A[0,0:num]           = I[0,:] / self.dt
      A[num,:] = 0
      A[num,num:2*num]     = I[0,:] / self.dt
      A[2*num,:] = 0
      A[2*num,2*num:3*num] = I[0,:] / self.dt
      A[3*num,:] = 0
      A[3*num,0:num]       = self.E * 3 * xss[0,0] * self.D_3[0,:]
      A[3*num,num:2*num]   = self.E * 3 * xss[0,1] * self.D_3[0,:]
      A[3*num,2*num:3*num] = self.E * 3 * xss[0,2] * self.D_3[0,:]
      A[3*num,3*num:]      = self.D_1[0,:]

      RHS[0]     = self.BC_start_vec_0[0] / self.dt
      RHS[num]   = self.BC_start_vec_0[1] / self.dt
      RHS[2*num] = self.BC_start_vec_0[2] / self.dt
      RHS[3*num] = 0
    if self.BC_start_1 == 'torque':
      # Apply torque
      offset = 1
      A[offset,:]                 = 0
      A[offset,0:num]             = self.D_2[offset-1,:]
      A[offset+num,:]             = 0
      A[offset+num,num:2*num]     = self.D_2[offset-1,:]
      A[offset+2*num,:]           = 0
      A[offset+2*num,2*num:3*num] = self.D_2[offset-1,:]

      RHS[offset]       = self.BC_start_vec_1[0]
      RHS[offset+num]   = self.BC_start_vec_1[1]
      RHS[offset+2*num] = self.BC_start_vec_1[2]
    else:
      # Enforce orientation
      offset = 1
      A[offset,:] = 0
      A[offset,0:num] = self.D_1[offset-1,:]
      A[offset+num,:] = 0
      A[offset+num,num:2*num] = self.D_1[offset-1,:]
      A[offset+2*num,:] = 0
      A[offset+2*num,2*num:3*num] = self.D_1[offset-1,:]

      RHS[offset] = self.BC_start_vec_1[0]
      RHS[offset+num] = self.BC_start_vec_1[1]
      RHS[offset+2*num] = self.BC_start_vec_1[2]

    # Apply BC at the other end
    if self.BC_end_0 == 'force':
      # Apply force
      offset = num - 1
      A[offset,:]                 = 0
      A[offset,0:num]             = self.D_3[offset,:]
      A[offset,3*num:]            = -xs[offset,0] * I[offset,:]
      A[offset+num,:]             = 0
      A[offset+num,num:2*num]     = self.D_3[offset,:]
      A[offset+num,3*num:]        = -xs[offset,1] * I[offset,:]
      A[offset+2*num,:]           = 0
      A[offset+2*num,2*num:3*num] = self.D_3[offset,:]
      A[offset+2*num,3*num:]      = -xs[offset,2] * I[offset,:]
      A[offset+3*num,:]           = 0
      A[offset+3*num,0:num]       = -self.D_2[offset,:] * xss[offset,0]
      A[offset+3*num,num:2*num]   = -self.D_2[offset,:] * xss[offset,1]
      A[offset+3*num,2*num:3*num] = -self.D_2[offset,:] * xss[offset,2]
      A[offset+3*num,3*num:]      = -I[offset,:]

      RHS[offset]       = self.BC_end_vec_0[0]
      RHS[offset+num]   = self.BC_end_vec_0[1]
      RHS[offset+2*num] = self.BC_end_vec_0[2]
      RHS[offset+3*num] = np.dot(self.BC_end_vec_0, xs[offset,:])
    else:
      # Enforce position
      offset = num - 1
      A[offset,:]                 = 0
      A[offset,0:num]             = I[offset,:] / self.dt
      A[offset+num,:]             = 0
      A[offset+num,num:2*num]     = I[offset,:] / self.dt
      A[offset+2*num,:]           = 0
      A[offset+2*num,2*num:3*num] = I[offset,:] / self.dt
      A[offset+3*num,:]           = 0
      A[offset+3*num,0:num]       = self.E * 3 * xss[offset,0] * self.D_3[offset,:]
      A[offset+3*num,num:2*num]   = self.E * 3 * xss[offset,1] * self.D_3[offset,:]
      A[offset+3*num,2*num:3*num] = self.E * 3 * xss[offset,2] * self.D_3[offset,:]
      A[offset+3*num,3*num:]      = self.D_1[offset,:]

      RHS[offset]       = self.BC_end_vec_0[0] / self.dt
      RHS[offset+num]   = self.BC_end_vec_0[1] / self.dt
      RHS[offset+2*num] = self.BC_end_vec_0[2] / self.dt
      RHS[offset+3*num] = 0
    if self.BC_end_1 == 'torque':
      # Apply torque
      offset = num - 2
      A[offset,:]                 = 0
      A[offset,0:num]             = self.D_2[offset+1,:]
      A[offset+num,:]             = 0
      A[offset+num,num:2*num]     = self.D_2[offset+1,:]
      A[offset+2*num,:]           = 0
      A[offset+2*num,2*num:3*num] = self.D_2[offset+1,:]

      RHS[offset]       = self.BC_end_vec_1[0]
      RHS[offset+num]   = self.BC_end_vec_1[1]
      RHS[offset+2*num] = self.BC_end_vec_1[2]
    else:
      # Enforce orientation
      offset = num - 2
      A[offset,:] = 0
      A[offset,0:num] = self.D_1[offset+1,:]
      A[offset+num,:] = 0
      A[offset+num,num:2*num] = self.D_1[offset+1,:]
      A[offset+2*num,:] = 0
      A[offset+2*num,2*num:3*num] = self.D_1[offset+1,:]

      RHS[offset] = self.BC_end_vec_1[0]
      RHS[offset+num] = self.BC_end_vec_1[1]
      RHS[offset+2*num] = self.BC_end_vec_1[2]
    return A, RHS


#!/usr/bin/env python3

#
# Build the DDPB dataset from remote sources
#
# Usage:
#   $ python3 build.py
#
# Requirements (get them with `make download`):
#   - country-code.json
#   - country-population.json
#   - ncov.json
#

import datetime
import json
import csv
from typing import List

EPOCH = datetime.date(2020, 1, 23)
DAYS = (datetime.date.today() - EPOCH).days

def totald(ncov: List[dict], country: str) -> List[int]:
    """ Builds the list of daily total death datapoints for a country

    >>> td = totald([], 'IT')
    >>> sum(td)
    0
    >>> len(td) == DAYS
    True
    >>> td = totald([{'dead': 1, 'countryCode': 'UK', 'date': '2020-01-23'}, {'dead': 0, 'date': '2020-01-23'}, {'dead': 12, 'countryCode': 'CN', 'date': '2020-01-23'}, {'dead': 2, 'countryCode': 'UK', 'date': '2020-01-25'}], 'UK')
    >>> td[0:3]
    [1, 0, 2]
    """  # noqa
    series = [0]*DAYS

    for d in ncov:
        if d.get('countryCode') == country or d.get('country') == country:
            date = datetime.datetime.strptime(d["date"], '%Y-%m-%d').date()
            delta = (date - EPOCH).days
            # exclude out of range data
            if delta < 0 or delta >= DAYS:
                continue
            series[delta] = d['dead']

    return series

def deltad(history: List[int]) -> List[int]:
    """ Computes a delta between daily datapoints

    >>> deltad([1,2,3,5])
    [1, 1, 1, 2]
    >>> deltad([])
    []
    """
    if not history:
        return []
    return [history[0]] + [max(0, history[n] - history[n-1]) for n in range(1, len(history))]

def ddpb(daily: int, pop: int) -> int:
    """ Computes the daily death rate per billion people

    >>> ddpb(32, 1000000000)
    32
    >>> ddpb(1, 60009)
    16664
    """
    return int(daily / pop * 10**9)

def trimzero(data: List[int]) -> List[int]:
    """ Removes zeroes at the hedge of a list, in-place

    >>> trimzero([])
    []
    >>> trimzero([1, 0, 3])
    [1, 0, 3]
    >>> trimzero([0, 0, 1, 0, 3, 0, 0])
    [1, 0, 3]
    """
    # trim left
    while data and data[0] == 0:
        data.pop(0)

    # trim right
    while data and data[-1] == 0:
        data.pop(len(data)-1)

    return data

if __name__ == "__main__":
    with open('country-code.json') as c:
        code = {d['country']: d['abbreviation'] for d in json.load(c)}
        code.pop('Northern Ireland', None)  # duplicate of UK (GB)

    with open('country-population.json') as p:
        population = {d['country']: int(d['population']) for d in json.load(p) if d['population']}

    with open('ncov.json') as n:
        ncov = json.load(n)
        ncov = [r for r in ncov if not r.get('provinceCode')]

    ds = {}
    for country, pop in population.items():
        # exclude countries with no matching code
        if country not in code:
            continue

        td = totald(ncov, code[country])
        dd = deltad(td)

        # exclude countries without "enough" daily deaths
        sg = trimzero(list(dd))
        if not sg or len(sg) < 10 or (sum(sg) / len(sg)) < 2:
            continue

        # compute series of daily death per billion
        ds[country] = [ddpb(d, pop) for d in dd]

    # output
    with open('dataset.json', 'w') as f:
        json.dump(ds, f)

    with open('dataset.csv', 'w', newline='') as f:
        writer = csv.writer(f, quoting=csv.QUOTE_MINIMAL)

        # headers
        headers = ['Country']
        for d in range(DAYS):
            headers.append((EPOCH + datetime.timedelta(days=d)).isoformat())
        writer.writerow(headers)

        # one row per country
        for c, d in ds.items():
            writer.writerow([c] + d)


from tempest.api.compute import base
from tempest.common.utils import data_utils
from tempest.lib import exceptions as lib_exc
from tempest import test

class ImagesMetadataTestJSON(base.BaseV2ComputeTest):

    @classmethod
    def setup_clients(cls):
        super(ImagesMetadataTestJSON, cls).setup_clients()
        cls.client = cls.compute_images_client

    @test.attr(type=['negative'])
    @test.idempotent_id('94069db2-792f-4fa8-8bd3-2271a6e0c095')
    def test_list_nonexistent_image_metadata(self):
        # Negative test: List on nonexistent image
        # metadata should not happen
        self.assertRaises(lib_exc.NotFound, self.client.list_image_metadata,
                          data_utils.rand_uuid())

    @test.attr(type=['negative'])
    @test.idempotent_id('a403ef9e-9f95-427c-b70a-3ce3388796f1')
    def test_update_nonexistent_image_metadata(self):
        # Negative test:An update should not happen for a non-existent image
        meta = {'os_distro': 'alt1', 'os_version': 'alt2'}
        self.assertRaises(lib_exc.NotFound,
                          self.client.update_image_metadata,
                          data_utils.rand_uuid(), meta)

    @test.attr(type=['negative'])
    @test.idempotent_id('41ae052c-6ee6-405c-985e-5712393a620d')
    def test_get_nonexistent_image_metadata_item(self):
        # Negative test: Get on non-existent image should not happen
        self.assertRaises(lib_exc.NotFound,
                          self.client.show_image_metadata_item,
                          data_utils.rand_uuid(), 'os_version')

    @test.attr(type=['negative'])
    @test.idempotent_id('dc64f2ce-77e8-45b0-88c8-e15041d08eaf')
    def test_set_nonexistent_image_metadata(self):
        # Negative test: Metadata should not be set to a non-existent image
        meta = {'os_distro': 'alt1', 'os_version': 'alt2'}
        self.assertRaises(lib_exc.NotFound, self.client.set_image_metadata,
                          data_utils.rand_uuid(), meta)

    @test.attr(type=['negative'])
    @test.idempotent_id('2154fd03-ab54-457c-8874-e6e3eb56e9cf')
    def test_set_nonexistent_image_metadata_item(self):
        # Negative test: Metadata item should not be set to a
        # nonexistent image
        meta = {'os_distro': 'alt'}
        self.assertRaises(lib_exc.NotFound,
                          self.client.set_image_metadata_item,
                          data_utils.rand_uuid(), 'os_distro',
                          meta)

    @test.attr(type=['negative'])
    @test.idempotent_id('848e157f-6bcf-4b2e-a5dd-5124025a8518')
    def test_delete_nonexistent_image_metadata_item(self):
        # Negative test: Shouldn't be able to delete metadata
        # item from non-existent image
        self.assertRaises(lib_exc.NotFound,
                          self.client.delete_image_metadata_item,
                          data_utils.rand_uuid(), 'os_distro')


from odoo import fields, models, _
from odoo.exceptions import ValidationError
import logging
_logger = logging.getLogger(__name__)

class ResCompany(models.Model):
    _inherit = 'res.company'

    rejected_check_produc_id = fields.Many2one(
        'product.product',
        'Servicio para cheques rechazados',
    )

    def _get_check_product(self, check_type):
        self.ensure_one()
        if check_type == 'rejected':
            # account = self.rejected_check_account_id
            if not self.rejected_check_produc_id:
                raise ValidationError("Debe informar el servicio (Concepto) a utilizar en el Rechazo de Cheques de Terceros (Parámetro de la companía) ")
            product = self.rejected_check_produc_id
        return product


import json
import math
import random
import itertools
from functools import *

"""
played_cards = [False for i in range(54)]
owned_cards = [False for i in range(54)]

# State: levels-played (1 * 15) + levels-owned (1 * 15) => (2 * 15)

for card in reqs[0]["own"]:
    owned_cards[card] = True

for req in reqs:
    for cards in req["history"]:
        for card in cards:
            played_cards[card] = True
for response in full_input["responses"]:
    for card in response:
        played_cards[card] = False
"""

ComboScore = {
    "Pass"      : 0,
    "Single"    : 1,
    "Pair"      : 2,
    "Straight"  : 6,
    "DStraight" : 6,
    "Three"     : 4,
    "Three1"    : 4, # 3 + 1
    "Three2"    : 4, # 3 + 2
    "Plane"     : 8,
    "Plane1"    : 8, # 3 * N + 1 * N
    "Plane2"    : 8, # 3 * N + 2 * N
    "Four1"     : 8, # 4 + 1 + 1
    "Four2"     : 8, # 4 + 2 + 2
    "Bomb"      : 10,
    "Rocket"    : 16, # j + J
    "Shuffle"   : 10, # 4 * 2
    "TShuffle"  : 20, # 4 * N (N >= 3)
    "Shuffle1"  : 10, # 4 * 2 + 1 * 2 * 2
    "Shuffle2"  : 10, # 4 * 2 + 2 * 2 * 2
    "TShuffle1" : 20, # 4 * 3 + 1 * 2 * 3
}

ComboPriority = {
    "Single"    : 0.15,
    "Pair"      : 0.15,
    "Straight"  : -0.1,
    "DStraight" : -0.3,
    "Three"     : 0.15,
    "Three1"    : 0.10, # 3 + 1
    "Three2"    : 0.05, # 3 + 2
    "Plane"     : 0.1,
    "Plane1"    : -0.1, # 3 * N + 1 * N
    "Plane2"    : 0, # 3 * N + 2 * N
    "Four1"     : 0.7, # 4 + 1 + 1
    "Four2"     : 0.6, # 4 + 2 + 2
    "Bomb"      : 1,
    "Rocket"    : 2, # j + J
    "Shuffle"   : 0.5, # 4 * 2
    "TShuffle"  : 0.4, # 4 * N (N >= 3)
    "Shuffle1"  : 0.5, # 4 * 2 + 1 * 2 * 2
    "Shuffle2"  : 0.5, # 4 * 2 + 2 * 2 * 2
    "TShuffle1" : 0, # 4 * 3 + 1 * 2 * 3
}

StraightTypes = ["Straight", "DStraight", "Shuffle", "TShuffle", "Shuffle1", "Shuffle2", "TShuffle1",
                    "Plane", "Plane1", "Plane2"]
AffiliateTypes = ["Three1", "Three2", "Four1", "Four2", "Plane1", "Plane2", "Shuffle1", "Shuffle2", "TShuffle1"]

ComboType = set(ComboScore.keys())

# 3 4 5 6 7 8 9 10 J Q K  A  2  jo Jo
# 0 1 2 3 4 5 6 7  8 9 10 11 12 13 14

# Colors:
# Heart, Diamond, Spade, Club, joker, Joker: 0, 1, 2, 3, 4, 5
# Numbers: 3, 4, 5,..., K, A, 2, joker, Joker:0, 1, 2,..., 13, 14
def getIdentity(card):
    # return color, number
    assert card < 54 and card >=0
    if card < 52: # if not jokers
        return card % 4, card // 4
    elif card == 52:
        return 4, 13
    elif card == 53:
        return 5, 14
    else:
        raise ValueError("Not Expected Card " + str(card))

# return count of cards of same level...
def levelCount(cards): # card is a set
    clvls = list(map(lambda card: getIdentity(card)[1], cards))
    level = [0 for i in range(15)]
    for clvl in clvls:
        level[clvl] += 1
    return level

def levelSplitedCards(cards):
    scards = [[] for i in range(15)]
    for card in cards: # level ranges from 0 to 14
        scards[getIdentity(card)[1]].insert(0, card)
    return scards

def compareCombo(c1, c2):
    # return if c1 > c2
    """
    cbp1, cbp2 = ComboPriority[c1.type], ComboPriority[c2.type]
    assert cbp1 < 2 or cbp2 < 2 # not both rocket
    if cbp1 != cbp2:
        return cbp1 > cbp2
    clv1, clv2 = c1.mainCardLevel(), c2.mainCardLevel()
    if cbp1 == 1: # if both bomb
        return clv1 > clv2
    else: # if both plain cards combo
        assert c1.type == c2.type
        if c1.type in StraightTypes: # if straights
            assert c1.slen == c2.slen
            return clv1 > clv2
        return clv1 > clv2
    """
    return keyCombo(c1)[:2] > keyCombo(c2)[:2] # for comp of actual combos, dismiss affiliates

def keyCombo(c): # used in calculate Combo Comparison Key: (type_pri, main_level, affiliate_levels)
    if c.type == "Pass":
        return -1
    cbp = ComboPriority[c.type]
    if cbp == 2:
        return (2, 0)
    elif cbp == 1:
        return (1, c.mainCardLevel())
    elif cbp < 1:
        # cnt = c.mainCardLevel() * 0xFFFF
        if c.type in AffiliateTypes:
            aff = c.cards[1]
            aff_cards = list(itertools.chain.from_iterable(aff))
            levels = sorted(list(map(lambda card: getIdentity(card)[1], aff_cards)))
            # print(levels)
            return (cbp, c.mainCardLevel(), levels)
        else:
            return (cbp, c.mainCardLevel())

def findCombo(cards, type=None): # cards here should be cards set-like
    level = levelCount(cards)
    if len(cards) == 0:
        return [Combo((), "Pass")]

    combos = []

    pairs = findN(cards, 2)
    threes = findN(cards, 3)
    fours = findN(cards, 4)
    # find except bombs
    bomb_levels = list(map(lambda cardPack: getIdentity(cardPack[0])[1], fours))
    sgls = findN(cards, 1, bomb_levels)
    pairs = findN(cards, 2, bomb_levels)
    threes = findN(cards, 3, bomb_levels)
    shuffles_2 = findStraight(cards, 2, 4)

    # Single combo
    if type is None or type == "Single":
        for card in sgls:
            combos.insert(len(combos), Combo((card,), "Single"))

    # Pairs
    # tmp = cards.copy()
    if type is None or type == "Pair":
        for pair in pairs:
            combos.insert(len(combos), Combo((pair,), "Pair"))

    # Three
    if type is None or type == "Three":
        for three in threes:
            combos.insert(len(combos), Combo((three,), "Three"))

    # Three1
    # threes = findN(cards, 3)
    if type is None or type == "Three1":
        for three in threes:
            _, id = getIdentity(three[0])
            ones = findN(cards, 1, [id] + bomb_levels)
            for one in ones:
                combos.insert(len(combos), Combo((three, (one,)), "Three1"))

    # Three2
    # threes = findN(cards, 3)
    if type is None or type == "Three2":
        for three in threes:
            _, id = getIdentity(three[0])
            ps = findN(cards, 2, [id] + bomb_levels)
            for p in ps:
                combos.insert(len(combos), Combo((three, (p,)), "Three2"))

    # Bomb
    if type is None or type == "Bomb":
        for four in fours:
            combos.insert(len(combos), Combo((four,), "Bomb"))

    # Four2
    # fours = findN(cards, 4)
    if type is None or type == "Four2":
        for four in fours:
            _, id = getIdentity(four[0])
            ps = findN(cards, 2, [id] + bomb_levels)
            for p in ps:
                for p_ in ps:
                    _, id_ = getIdentity(p_[0])
                    if id_ <= id: # second pair must larger than first, to ensure uniqueness
                        continue
                    combos.insert(len(combos), Combo((four, (p, p_)), "Four2"))

    # Four1
    if type is None or type == "Four1":
        for four in fours:
            _, id = getIdentity(four[0])
            ones = findN(cards, 1, [id] + bomb_levels)
            for one in ones:
                for one_ in ones:
                    _, id_ = getIdentity(one_[0])
                    if id_ <= id: # second pair must larger than first
                        continue
                    combos.insert(len(combos), Combo((four, (one, one_)), "Four2"))

    # Straight
    if type is None or type == "Straight":
        for i in range(5, 11): # max 3-J
            straights1 = findStraight(cards, i, 1)
            for st in straights1:
                combos.insert(len(combos), Combo(tuple([st],), "Straight", straight_len=i))

    # DStraight
    if type is None or type == "DStraight":
        for i in range(3, 11): # max 10 * 2 cards, min 3 * 2 cards
            straights2 = findStraight(cards, i, 2)
            for st in straights2:
                combos.insert(len(combos), Combo(tuple([st],), "DStraight", straight_len=i))

    # Plane - plain plane
    if type is None or type == "Plane":
        for i in range(2, 8): # max 3 * 7
            planes = findStraight(cards, i, 3)
            for plane in planes:
                combos.insert(len(combos), Combo((plane,), "Plane", straight_len=i))

    # Plane1
    if type is None or type == "Plane1":
        for i in range(2, 6): # max 3 * 5 + 1 * 5
            if 4 * i > len(cards):
                break
            planes = findStraight(cards, i, 3)
            for plane in planes:
                # find singles except id
                n = len(plane) // 3
                ids = []
                for i in range(n):
                    ids.insert(len(ids), getIdentity(plane[i * 3])[1])
                ids = tuple(ids + bomb_levels)
                # _, id = getIdentity(plane[0])
                singles = findN(cards, 1, ids)
                s_affs = findAffiliate(singles, n)
                for s_aff in s_affs:
                    combos.insert(len(combos), Combo((plane, tuple(s_aff)), "Plane1", straight_len=i))

    # Plane2
    if type is None or type == "Plane2":
        for i in range(2, 5): # max 3 * 4 + 2 * 4
            if 5 * i > len(cards):
                break
            planes = findStraight(cards, i, 3)
            for plane in planes:
                n = len(plane) // 3
                ids = []
                for i in range(n):
                    ids.insert(len(ids), getIdentity(plane[i * 3])[1])
                ids = tuple(ids + bomb_levels)
                ps = findN(cards, 2, ids)
                d_affs = findAffiliate(ps, n)
                for d_aff in d_affs:
                    combos.insert(len(combos), Combo((plane, tuple(d_aff)), "Plane2", straight_len=i))

    # Rocket
    if type is None or type == "Rocket":
        if 52 in cards and 53 in cards:
            combos.insert(len(combos), Combo((52, 53), "Rocket"))

    # Shuffle(Continuously 2)
    if type is None or type == "Shuffle":
        for shuffle in shuffles_2:
            combos.insert(len(combos), Combo((shuffle), "Shuffle", straight_len=2))

    # TShuffle(Continuosly 3+)
    if type is None or type == "TShuffle":
        for i in range(3, 6): # at most 4 * 5
            shuffles_i = findStraight(cards, i, 4)
            for shuffle in shuffles_i:
                combos.insert(len(combos), Combo((shuffle), "TShuffle", straight_len=i))

    # Shuffle1(4 * 2 + 1 * 2 * 2, 12 in total)
    if (type is None or type == "Shuffle1") and len(cards) >= 12:
        for shuffle in shuffles_2:
            _, id = getIdentity(shuffle[0])
            n = len(shuffle) // 4
            ids = []
            for i in range(n):
                ids.insert(len(ids), getIdentity(shuffle[i * 4])[1])
            ids = tuple(ids + bomb_levels)
            singles = findN(cards, 1, ids)
            for single1 in singles:
                _, id1 = getIdentity(single1)
                for single2 in singles:
                    _, id2 = getIdentity(single2)
                    if id1 <= id2:
                        continue
                    for single3 in singles:
                        _, id3 = getIdentity(single3)
                        if id2 <= id3:
                            continue
                        for single4 in singles:
                            _, id4 = getIdentity(single4)
                            if id3 <= id4:
                                continue
                            combos.insert(len(combos),
                                Combo((shuffle, single1, single2, single3, single4), "Shuffle1", straight_len=2))

    # Shuffle2(4 * 2 + 2 * 2 * 2, 16 in total)
    if (type is None or type == "Shuffle2") and len(cards) >= 16:
        for shuffle in shuffles_2:
            _, id = getIdentity(shuffle[0])
            n = len(shuffle) // 4
            ids = []
            for i in range(n):
                ids.insert(len(ids), getIdentity(shuffle[i * 4])[1])
            ids = tuple(ids + bomb_levels)
            ps = findN(cards, 2, ids)
            for p1 in ps:
                _, id1 = getIdentity(p1)
                for p2 in ps:
                    _, id2 = getIdentity(p2)
                    if id1 <= id2:
                        continue
                    for p3 in ps:
                        _, id3 = getIdentity(p3)
                        if id2 <= id3:
                            continue
                        for p4 in ps:
                            _, id4 = getIdentity(p4)
                            if id3 <= id4:
                                continue
                            combos.insert(len(combos), Combo((shuffle, p1, p2, p3, p4), "Shuffle2", straight_len=2))

    # TShuffle1(4 * 3 + 1 * 6, 20 in total)
    if (type is None or type == "TShuffle1") and len(cards) >= 20:
        shuffles_3 = findStraight(cards, 3, 4)
        for shuffle in shuffles_3:
            # I WILL NEVER DRAW THIS!!!!!
            _, id = getIdentity(shuffle[0])
            n = len(shuffle + bomb_levels) // 4
            ids = []
            for i in range(n):
                ids.insert(len(ids), getIdentity(shuffle[i * 4])[1])
            ids = tuple(ids)
            singles = findN(cards, 1, ids)
            s_affs = findAffiliate(singles, 6)
            for s_aff in s_affs:
                combos.insert(len(combos), Combo((shuffle, tuple(s_aff)), "TShuffle1", straight_len=3))

    return combos

def findStraight(cards, length, n=1):
    # Note: 2 j J is not allowed, thus max length is 11.
    # max min element in Straight is Q---9
    assert (n == 1 and length >= 5) or (n == 2 and length >= 3) or (n >= 3 and length >= 2)
    level = levelCount(cards)
    scards = levelSplitedCards(cards)
    sts = []
    vis = [False for i in range(15)]
    for card in cards:
        _, id = getIdentity(card)
        if vis[id] is True:
            continue
        else:
            vis[id] = True
        st = []
        has = True
        for j in range(0, length):
            cur_id = id + j
            # if cur_id >= 12:
            # can't include 2, jo, Jo, or Bomb in the middle
            if level[cur_id] < n or cur_id >= 12 or level[cur_id] == 4:
                has = False
                break
            else:
                level_cards = scards[cur_id][:n]
                st += level_cards
        if has is True:
            sts.insert(-1, st)

    return sts

def findN(cardSet, n, excepts=(-1,)):
    # only find one single/pair/three etc. on one level
    level = levelCount(cardSet)
    vis = [False for i in range(15)]
    ns = []
    for card in cardSet:
        color, id = getIdentity(card)
        if vis[id] is True or id in excepts:
            continue
        vis[id] = True
        if level[id] >= n:
            n_cards = list(filter(lambda c: getIdentity(c)[1] == id, cardSet))[:n]
            ns.insert(-1, n_cards)

    return ns

def findAffiliate(combos, num=1, minLevel=-1, excepts=()):
    # find affliates, not in bombs

    levels = list(map(lambda comboN: getIdentity(comboN[0])[1], combos))
    # print(levels)
    vis = [False for i in range(15)]

    # not access 2, jo, Jo
    vis[12] = True
    vis[13] = True
    vis[14] = True
    for id in excepts:
        vis[id] = True # except given ids
    affiliates = []
    if num == 1:
        for i in range(len(combos)):
            if levels[i] > minLevel:
                affiliates.append([combos[i]])

    for i in range(len(combos)):
        combo, level = combos[i], levels[i]
        if vis[level] is True or level <= minLevel:
            continue
        else:
            vis[level] = True
        less_affs = findAffiliate(combos, num - 1, minLevel=level)
        # print(less_affs)
        for aff in less_affs:
            affiliates.append([combo] + aff)

    return affiliates

class Combo(object):
    def __init__(self, cards, type=None, straight_len=0):
        assert type in ComboType
        self.cards = cards # a tuple, rather than array
        self.type = type
        self.slen = straight_len

    def mainCardLevel(self):
        if self.type == "Pass":
            return -1
        mainCombo = self.cards[0]
        return getIdentity(mainCombo[0])[1]

    def allCards(self):
        cnt = []
        # print(self.cards)
        for combo in self.cards:
            try:
                for p in combo:
                    try:
                        cnt += p
                    except:
                        cnt += combo
                        break
            except:
                cnt += self.cards
                break
        return cnt

    def length(self):
        cnt = 0
        # print(self.cards)
        for combo in self.cards:
            try:
                for p in combo:
                    try:
                        cnt += len(p)
                    except:
                        cnt += len(combo)
                        break
            except:
                cnt += len(self.cards)
                break
        return cnt

    def reward(self, isFinished=False):
        return ComboScore[self.type] / 100 + self.length() / 100 + (2 if isFinished else 0)
        # Reward for Reinforcement Learning

    def cardsNum(self):
        return self.length()

    def score(self):
        return ComboScore[self.type]

    def __str__(self):
        return "[" + self.type + ", " + str(self.cards) + "]"

    __repr__ = __str__

def checkCombo(cards):
    combos = findCombo(cards)
    # print(combos)
    combo = list(filter(lambda cb: cb.length() == len(cards), combos))[0]
    return combo

def checkComboType(cards):
    return checkCombo(cards).type

def restoreCards(input):
    hand = input["requests"][0]["own"]
    last = input["requests"][-1]["history"][-1]
    if last == []:
        last = input["requests"][-1]["history"][-2]
    responses = input["responses"]
    for response in responses:
        for card in response:
             hand.remove(card)
    return sorted(hand), sorted(last)

def findLarger(cards, minCombo):
    # combos = find(combos)
    # type = ["Rocket", "Bomb"]
    combo = checkCombo(minCombo) if not isinstance(minCombo, Combo) else minCombo
    if combo.type == "Rocket":
        return Combo((), "Pass") # Nothing larger than rocket
    elif combo.type == "Bomb":
        combos = []
        types = ["Bomb", "Rocket"]
        for type in types:
            combos += findCombo(cards, type)
        combos.sort(key=keyCombo)
        for combo in combos:
            if compareCombo(combo, minCombo):
                return combo
                # find minimum to beat
        return Combo((), "Pass")
    elif minCombo.type != "Pass": # if last not passed
        types = ["Bomb", "Rocket", minCombo.type]
        combos = []
        for type in types:
            combos += findCombo(cards, type=type)
        combos.sort(key=keyCombo)
        # print(combos)
        for combo in combos:
            if minCombo.type in StraightTypes:
                if minCombo.slen != combo.slen: # length must match
                    continue
            if compareCombo(combo, minCombo):
                return combo
        return Combo((), "Pass")
    else: # if last just passed
        # TODO: CHOOSE WHAT TO PLAY IF LAST PASSED
        combos = findCombo(cards)
        combos.sort(key=keyCombo)
        return combos[0]

if __name__ == "__main__":
    full_input = json.loads(input())
    """
    full_input example:
    {"requests":
        [
            {"own":[25,12,38,41,27,20,48,36,17,34,51,52,37,45,31,6,46],
            "history":[[],[26,29,32,39,40]],
            "publiccard":[14,7,19]
            },
            {"history":[[],[4,7,8,9,10,13,14,15,18,19]]},
            {"history":[[],[0,1,2,3]]}
        ],
    "responses":[[],[]]}
    """

    my_history = full_input["responses"]
    use_info = full_input["requests"][0]
    poker, history, publiccard = use_info["own"], use_info["history"], use_info["publiccard"]
    last_history = full_input["requests"][-1]["history"]

    currBotID = 0 # 判断自己是什么身份，地主0 or 农民甲1 or 农民乙2
    if len(history[0]) == 0:
        if len(history[1]) != 0:
            currBotID = 1
    else:
        currBotID = 2
    history = history[2-currBotID:]

    # reqs = full_input["requests"]
    hand, last = restoreCards(full_input)

    last_combo = checkCombo(last)
    play = findLarger(hand, last_combo)
    play_cards = play.allCards()
    print(json.dumps({
        "response": play_cards
    }))


from flask import Blueprint, request, jsonify
from app.models import Anime, Review, db
from app.forms import ReviewForm, UpdateReviewForm
from flask_login import login_required, current_user

review_routes = Blueprint('review', __name__)

def validation_errors_to_error_messages(validation_errors):
    """
    Simple function that turns the WTForms validation errors into a simple list
    """
    errorMessages = []
    for field in validation_errors:
        for error in validation_errors[field]:
            errorMessages.append(error)
    return errorMessages

# Find all reviews of an anime based on animeId
@review_routes.route('/anime/<int:animeId>')
def anime_reviews(animeId):
    reviews = Review.query.filter_by(animeId=animeId).all();
    return {'reviews': [review.to_dict() for review in reviews]}

# Delete review based on review's ID
@review_routes.route('<int:reviewId>/anime/<int:animeId>', methods=["DELETE"])
def anime_review(reviewId, animeId):
    review = Review.query.filter_by(id=reviewId).first();
    db.session.delete(review);
    db.session.commit();
    return {}

# Create a review
@review_routes.route('/anime/<int:animeId>', methods=["POST"])
@login_required
def create_review(animeId):
    form = ReviewForm()
    form["csrf_token"].data = request.cookies["csrf_token"]
    if form.validate_on_submit():
        review = Review(userId=current_user.id, animeId=animeId, content=form.data["content"], rating=form.data["rating"])
        db.session.add(review)
        db.session.commit()
        return {"review": review.to_dict()}
    return {"errors": validation_errors_to_error_messages(form.errors)}, 401

# Update a review
@review_routes.route('<int:reviewId>/anime/<int:animeId>', methods=["PATCH"])
def update_review(reviewId, animeId):
    form = UpdateReviewForm()
    form["csrf_token"].data = request.cookies["csrf_token"]
    if form.validate_on_submit():
        review = Review.query.filter_by(id=reviewId).first()

        review.content = form.data["content"]
        review.rating = form.data["rating"]

        db.session.commit()

        return {'review': review.to_dict()}
    return {"errors": validation_errors_to_error_messages(form.errors)}, 401

# @review_routes.route('<int:reviewId>/anime/<int:animeId>/', methods=["PUT"])
# def anime_review(reviewId, animeId):
#     review = Review.query.filter_by(id=reviewId).first();


import json
from concurrent.futures.thread import ThreadPoolExecutor
from datetime import datetime
from pprint import pprint
from typing import Dict

import cloudscraper
from sqlalchemy import and_

from app import app
from core.db_connector import db
from core.helpers.mailer import Mailer
from core.models import CarProduct
from scrappers.cars.common.helpers import (
    adjust_car_data,
    _convert_odometer,
    filter_value,
)
from core.helpers.mailer.templates.export_template import cars_export_stats

class BaseCarScrapper:
    def __init__(
        self,
        source,
        project,
        source_id=None,
        vinaudit_key=None,
        carsxe_key=None,
        force_carsxe=None,
        fuelapi_key=None,
        apis_keys: Dict = None,
        send_report=None,
    ):
        self.MAX_RETRIES_ON_CAR = 3
        self.failed_counter = {}

        self.source = source
        self.project = project
        self.source_id = source_id

        self.vinaudit_key = vinaudit_key
        self.carsxe_key = carsxe_key
        self.force_carsxe = force_carsxe
        self.fuelapi_key = fuelapi_key
        if apis_keys:
            self.vinaudit_key = apis_keys.get("vinaudit")
            self.carsxe_key = apis_keys.get("carsxe")
            self.force_carsxe = apis_keys.get("force_carsxe")
            self.fuelapi_key = apis_keys.get("fuelapi")

        self.cars_urls = []
        self.vins_retrieved = []

        self.total_new = 0
        self.total_active = 0
        self.total_sold = 0
        self.send_report = send_report

    def start_scrapping(self):
        if not self._get_cars_urls():
            return
        self.get_cars_data()
        self.update_sold()

    def _get_cars_urls(self):
        max_retries = 5
        while max_retries > 0:
            try:
                self.get_cars_urls()
                return True
            except KeyboardInterrupt:
                return False
            except:
                max_retries -= 1
        return False

    def get_cars_urls(self):
        raise NotImplementedError

    def update_sold(self):
        vins_retrieved = [str(vin).upper() for vin in self.vins_retrieved]
        print(self.vins_retrieved, vins_retrieved)
        with app.app_context():
            missing = CarProduct.query.filter(
                and_(
                    CarProduct.vin.notin_(vins_retrieved),
                    CarProduct.source == self.source,
                )
            ).all()
            for missing_car in missing:
                if missing_car.status != "sold":
                    self.total_sold += 1
                missing_car.status = "sold"
            db.session.commit()

    def get_cars_data(self):
        failed = []
        succeed = 0
        passed = 0
        total = len(self.cars_urls)

        vins = []
        entities = CarProduct.query.with_entities(CarProduct.vin).all()
        for entity in entities:
            vins.append(entity.vin)

        with ThreadPoolExecutor(max_workers=8) as executor:
            for car_data in executor.map(self.get_car_data, self.cars_urls):
                passed += 1
                if "out" in car_data:
                    continue

                car_data.update({"project": self.project})

                if "failed" not in car_data:

                    vin = car_data["vin"].strip()
                    source = car_data["source"]
                    car_data = {k: filter_value(v) for k, v in car_data.items()}
                    car_data = adjust_car_data(car_data)

                    if self.car_exists(vin, source):
                        car = CarProduct.query.filter(
                            and_(
                                CarProduct.vin == vin,
                                CarProduct.source == source,
                            )
                        ).first()

                        car_data.pop("images", None)
                        car_data.update({"status": "active"})

                        for key, value in car_data.items():
                            value = filter_value(value)
                            setattr(car, key, value)

                        self.total_active += 1
                    else:
                        self.total_new += 1
                        # noinspection PyArgumentList
                        car = CarProduct(**car_data)
                        db.session.add(car)
                    succeed += 1
                    print(f"{succeed}/{passed}/{total}")
                else:
                    failed_url = car_data.get("failed", "")
                    self.failed_counter.update({
                        failed_url: self.failed_counter.get(failed_url, 0) + 1
                    })
                    failed.append(failed_url)
                    print(f"{succeed}/{passed}/{total}")
                db.session.commit()
        self.cars_urls = failed
        if failed:
            self.get_cars_data()

    def get_car_data(self, car_url: str, force_apis=False):
        if self.failed_counter.get(car_url, 0) >= self.MAX_RETRIES_ON_CAR:
            return {"out": car_url}

        dealership_data = self.get_dealership_data(car_url)
        if "failed" in dealership_data:
            print("dealership failed")
            return {"failed": car_url}

        if "out" in dealership_data:
            print("dealership out")
            return {"out": car_url}

        car_data = dealership_data
        vin = dealership_data["vin"]
        source = dealership_data["source"]
        self.vins_retrieved.append(vin)

        if not self.car_exists(vin, source) or force_apis:

            if self.vinaudit_key:
                vin_audit_data = self.get_vin_audit_data(vin)
                if "failed" in vin_audit_data:
                    print("vinaudit failed")
                    return {"failed": car_url}
                if "out" in dealership_data:
                    print("vinaudit out")
                    return {"out": car_url}
                car_data.update(vin_audit_data)
            car_data.update({"status": "new"})

            if self.fuelapi_key:
                # today = datetime.today()
                # today_year = today.year
                # if (car_data.get("year") - today_year) in [0, 1]:
                #     images = self.get_images_from_api(
                #         car_data.get("make"),
                #         car_data.get("model"),
                #         car_data.get("year"),
                #         car_data.get("exterior_color"),
                #     )
                #     car_data.update({"images": images})
                pass

            if self.carsxe_key:
                today = datetime.today()
                today_year = today.year
                if self.force_carsxe or (
                    car_data.get("year")
                    and (car_data.get("year") - today_year) in [0, 1]
                ):
                    images = self.get_images_from_api(
                        car_data.get("make"),
                        car_data.get("model"),
                        car_data.get("year"),
                        car_data.get("exterior_color"),
                    )
                    car_data.update({"images": images})

        return car_data

    def get_dealership_data(self, car_url: str):
        raise NotImplementedError

    def get_vin_audit_data(self, vin: str):
        try:
            url = (
                "https://specifications.vinaudit.com/v3/specifications?"
                "format=json&key={}&vin={}"
                "&include=attributes,equipment,warranties,colors"
            )
            scraper = cloudscraper.create_scraper()
            resp = scraper.get(url.format(self.vinaudit_key, vin))
            resp_data = json.loads(resp.text)
            if not resp_data.get("success", None):
                return {"out": True}

            _input = resp_data.get("input", {})
            _attributes = resp_data.get("attributes", {})

            _equipments = resp_data.get("equipments", {})
            airbags = 0
            equipments = []
            for equipment in _equipments:
                name = equipment.get("name", "")
                if "airbag" in name.lower():
                    airbags += 1
                equipments.append(
                    {
                        "group": equipment.get("group", ""),
                        "name": name,
                    }
                )

            data_formatter = {
                "make": {"dbname": "make"},
                "model": {"dbname": "model"},
                "year": {"lambda": lambda x: int(x)},
                "type": {"dbname": "body_type"},
                "size": {"dbname": "body_style"},
                "transmission_type": {"dbname": "transmission"},
                "trim": {},
                "engine": {},
                "engine_size": {"lambda": lambda x: f"{x} L"},
                "engine_cylinders": {"lambda": lambda x: int(x)},
                "drivetrain": {"dbname": "driveline"},
                "doors": {"lambda": lambda x: int(x.split("-", 1)[0])},
                "standard_seating": {
                    "dbname": "passengers",
                    "lambda": lambda x: int(x),
                },
                "fuel_type": {"dbname": "fuel"},
                "city_mileage": {
                    "lambda": lambda x: _convert_odometer(x),
                    "dbname": "city_fuel",
                },
                "highway_mileage": {
                    "lambda": lambda x: _convert_odometer(x),
                    "dbname": "hwy_fuel",
                },
            }
            options = [e["name"] for e in _equipments]
            car_data = {
                "vin": _input.get("vin", vin),
                "airbags": airbags,
                "options": options,
            }
            for field, data in data_formatter.items():
                value = _attributes.get(field, None)
                if value:
                    _lambda = data.get("lambda", lambda x: x)
                    _field = data.get("dbname", field)
                    car_data.update({_field: _lambda(value)})
            return car_data
        except Exception as e:
            print(e)
            return {"failed": True}

    def get_images_from_api(self, make, model, year, color):
        link = (
            f"http://api.carsxe.com/images?key={self.carsxe_key}&make={make}&"
            f"model={model}&year={year}&color={color}&size=Large&format=json"
        )
        scrapper = cloudscraper.create_scraper()
        resp = scrapper.get(link, timeout=120)
        data = json.loads(resp.text)

        images = []
        _images = data.get("images")
        if not data.get("success"):
            print(resp.text)
        for image in _images:
            url = image.get("link")
            if url:
                images.append(url)
        images = images[:1]

        return images

    @staticmethod
    def car_exists(vin, source):
        with app.app_context():
            exists = (
                CarProduct.query.filter(
                    and_(CarProduct.vin == vin, CarProduct.source == source)
                ).first()
                is not None
            )
        return exists

    def get_cars_data_for_test(
        self,
        cars_urls: list = None,
        first_n=None,
        force_apis=False,
        print_data=True,
        save=False,
    ):
        if not cars_urls:
            self.get_cars_urls()
            if first_n:
                self.cars_urls = self.cars_urls[:first_n]
        else:
            self.cars_urls = cars_urls

        print(f"Total in {self.__class__.__name__}: {len(self.cars_urls)}")

        for car_url in self.cars_urls:
            print(car_url)
            car_data = self.get_car_data(car_url, force_apis)

            if "out" in car_data:
                print(f"OUT {car_data['out']}")
                continue
            if "failed" in car_data:
                print(f"FAILED {car_data['failed']}")
                continue

            car_data = {k: filter_value(v) for k, v in car_data.items()}
            car_data = adjust_car_data(car_data)

            car_data["status"] = (
                "active"
                if CarProduct.query.filter(
                    CarProduct.vin == car_data["vin"].upper()
                ).first()
                is not None
                else "new"
            )

            if print_data:
                pprint(car_data)

            vin = car_data["vin"]
            if save and not self.car_exists(vin, car_data['source']):
                car_data.update({"status": "new"})
                # noinspection PyArgumentList
                new_car = CarProduct(**car_data)
                db.session.add(new_car)
                db.session.commit()
                print(f"Saved {car_data['vin']}")


import os
import time
import sys
from selenium import webdriver
from selenium.webdriver.common.by import By
from fake_useragent import UserAgent
from selenium.common import exceptions
from datetime import datetime
from find_url import Channel
from multiprocessing import Process

# from selenium.webdriver.common.proxy import Proxy, ProxyType
# from selenium.webdriver.firefox.options import Options

LOG_FILE = r'C:\Users\voreo\Documents\ProjectsPy\bot\LOG.txt'
CHANNEL_FILE = r'C:\Users\voreo\Documents\ProjectsPy\bot\CH.txt'
PROXY_FILE = r'C:\Users\voreo\Documents\ProjectsPy\bot\PROXY.txt'
COOKIE_DIR = r'C:\Users\voreo\Documents\ProjectsPy\bot\COOKIE_DIR'
SITES_FILE = r'C:\Users\voreo\Documents\ProjectsPy\bot\SITES.txt'

PIC_BANNER_ELEM = '/html/body/ytd-app/div/ytd-page-manager/ytd-watch-flexy/div[5]/div[1]/div/div[1]/div/div/div/ytd-player/div/div/div[4]/div[2]/div/div[3]/div[3]/img'
PLAY_BUT_ELEM = 'ytp-play-button.ytp-button'
FIRST_ADV_LINK_ELEM = 'ytp-ad-button.ytp-ad-visit-advertiser-button.ytp-ad-button-link'
SKIP_BUT_ELEM = 'ytp-ad-skip-button-icon'
ACCEPT_BUT_ELEM = '/html/body/ytd-app/ytd-consent-bump-v2-lightbox/tp-yt-paper-dialog/div[2]/div[2]/div[5]/div[2]/ytd-button-renderer[2]/a/tp-yt-paper-button'

def exit():
    sys.exit(1)

def check_exists(driver:webdriver, parameter:By ,element:webdriver) -> bool:
    try:
        driver.find_element(parameter, element)
    except exceptions.NoSuchElementException:
        return False
    return True

def check_ad(driver:webdriver, window:webdriver, time_watch:int) -> bool:

    is_exists_ad = 0
    second = 0
    banner = 0

    for i in range(0, 2):

        time.sleep(1)
        driver.find_element(By.CLASS_NAME, PLAY_BUT_ELEM).click()
        time.sleep(1)

        if check_exists(driver, By.CLASS_NAME, FIRST_ADV_LINK_ELEM):
            driver.find_element(By.CLASS_NAME, FIRST_ADV_LINK_ELEM).click()
            time.sleep(1)
            driver.switch_to.window(window)
            time.sleep(1)
            driver.find_element(By.CLASS_NAME, PLAY_BUT_ELEM).click()
            time.sleep(4)
            is_exists_ad += 1
            if check_exists(driver, By.CLASS_NAME, SKIP_BUT_ELEM):
                driver.find_element(By.CLASS_NAME, SKIP_BUT_ELEM).click()
            else:
                time.sleep(6)

        else:
            driver.find_element(By.CLASS_NAME, PLAY_BUT_ELEM).click()
            if i == 1 and is_exists_ad == False:
                while second < time_watch:
                    time.sleep(1)

                    if check_exists(driver, By.XPATH, PIC_BANNER_ELEM) and banner == False:
                        try:
                            driver.find_element(By.XPATH, PIC_BANNER_ELEM).click()
                            banner += 1
                            driver.switch_to.window(window)
                            time.sleep(1)
                            driver.find_element(By.CLASS_NAME, PLAY_BUT_ELEM).click()
                        except:
                            pass

                    second += 1

    if is_exists_ad or banner:
        return True
    else:
        return False

def ad_click(settings, channel, proxy, cookie_file_path, sites):

    ch = Channel(channel)
    click_list = ch.get_click_list(settings['num_watch_per'], settings['num_click_per'])
    with open(LOG_FILE, 'a') as log_file:
        log_file.write('\n' + 'Proxy loaded: ' + proxy + '\n')
    ua = UserAgent()
    user_agent = ua.random
    options = webdriver.ChromeOptions()
    options.add_argument(f'user-agent={user_agent}')
    # options.add_argument('--proxy-server=%s' % proxy.rstrip())
    options.add_argument('--proxy-server=socks5://' + proxy.rstrip())
    browser = webdriver.Chrome(options=options)

    if settings['cookie_mode']:
        with open(cookie_file_path, 'r') as cookie_file:
            cookies = [cookie.rstrip().split('\t') for cookie in cookie_file]

        for cookie in cookies:
            try:
                if 'www' in cookie:
                    browser.get('http://' + cookie[0])
                elif cookie[0][0] == '.':
                    browser.get('http://www' + cookie[0])
                else:
                    browser.get('http://www.' + cookie[0])
                browser.add_cookie({'name': cookie[5],
                                    'value': cookie[6],
                                    'path': cookie[2],
                                    'domain': cookie[0],
                                    'secure': False,
                                    'sameSite': 'Strict'})

                with open(LOG_FILE, 'a') as log_file:
                    log_file.write('Cookie successfully loaded from: ' + str(COOKIE_DIR + '\\' + cookie_file_path) + '\n')

            except (exceptions.WebDriverException, exceptions.InvalidCookieDomainException) as ex:
                with open(LOG_FILE, 'a') as log_file:
                    log_file.write(str(datetime.today()) + '\n' + ex.msg + '\n' + 'Cookie load error.' + '\n' + str(cookie) + '\n')

    else:
        try:
            for site in sites:
                browser.get(site)
                time.sleep(2)
            with open(LOG_FILE, 'a') as log_file:
                    log_file.write('Cookie successfully recorded.' + '\n')
        except exceptions.WebDriverException as ex:
                with open(LOG_FILE, 'a') as log_file:
                    log_file.write(str(datetime.today()) + '\n' + ex.msg + '\n' + 'Cookie record error.' + '\n' + site + '\n')

    # Настройки для Mozilla Firefox

    # options = Options()
    # options.add_argument(f'user-agent={user_agent}')
    # my_proxy = Proxy({
    #              'proxyType': ProxyType.MANUAL,
    #              'httpProxy': proxy,
    #              'ftpProxy': proxy,
    #              'sslProxy': proxy,
    #              'noProxy': '' # set this value as desired
    #             })
    # browser = webdriver.Firefox(proxy=my_proxy, options=options)

    with open(LOG_FILE, 'a') as log_file:
                        log_file.write('\n' + '===Channel loaded===' + str(ch))
    skipped_adv = 0
    click = 0

    for video in range(0, ch.num_watch):
        try:
            prev_window = None
            browser.get(ch.video_links[video])
            current_window = browser.current_window_handle
            with open(LOG_FILE, 'a') as log_file:
                            log_file.write('\n' + 'Video loaded: ' + str(ch.video_links[video]) + '\n')

            if check_exists(browser, By.XPATH, ACCEPT_BUT_ELEM):
                browser.find_element(By.XPATH, ACCEPT_BUT_ELEM).click()

            if check_exists(browser, By.CLASS_NAME, 'ytp-ad-button.ytp-ad-visit-advertiser-button.ytp-ad-button-link') == False:
                browser.switch_to.new_window('tab')
                browser.get(ch.video_links[video])
                prev_window = current_window
                current_window = browser.current_window_handle

            # browser.find_element(By.CLASS_NAME, PLAY_BUT_ELEM).click()

            if video in click_list:
                if check_ad(browser, current_window, settings['time_watch']) == False:
                    time.sleep(1)
                    skipped_adv =+ 1
                else:
                    time.sleep(1)
                    click += 1
                    with open(LOG_FILE, 'a') as log_file:
                        log_file.write('\n' + 'Adv click. Total clicks: ' + str(click) + ' of ' + str(ch.num_click) + '\n')

            else:
                if skipped_adv:
                    if check_ad(browser, current_window, settings['time_watch']) == False:
                        time.sleep(1)
                        skipped_adv += 1
                    else:
                        time.sleep(1)
                        skipped_adv -= 1
                        click += 1
                        with open(LOG_FILE, 'a') as log_file:
                            log_file.write('\n' + 'Adv click. Total clicks: ' + str(click) + ' of ' + str(ch.num_click) + '\n')
                else:
                    for i in range(2):
                        if check_exists(browser, By.CLASS_NAME, FIRST_ADV_LINK_ELEM):
                            time.sleep(5)
                            if check_exists(browser, By.CLASS_NAME, SKIP_BUT_ELEM):
                                browser.find_element(By.CLASS_NAME, SKIP_BUT_ELEM).click()
                            else:
                                time.sleep(6)

                    time.sleep(settings['time_watch'])

            if prev_window:
                browser.close()
                browser.switch_to.window(prev_window)

            with open(LOG_FILE, 'a') as log_file:
                        log_file.write('View completed. Video ' + str(video + 1) + ' of ' + str(ch.num_watch))

        except exceptions.WebDriverException as ex:
            print(ex)
            with open(LOG_FILE, 'a') as log_file:
                log_file.write('\n' + str(datetime.today()) + '\n' + ex.msg + '\n' + proxy + 'Video: ' + str(ch.video_links[video]) + '\n\n')

    browser.quit()

def main(cookie_mode:bool, num_watch_per:int, num_click_per:int, time_watch:int) -> None:

    num_channel = 0
    num_cookie = 0
    proxy_thread = []
    proxy_threads = []
    processes = []

    settings = {
        'cookie_mode': cookie_mode,
        'num_watch_per': num_watch_per,
        'num_click_per': num_click_per,
        'time_watch': time_watch,
    }

    cookie_list = os.listdir(COOKIE_DIR)

    with open(LOG_FILE, 'a') as log_file:
        log_file.write('\n\n' + str(datetime.today()) + '\n' + '=====================================START=====================================' + '\n')

    with open(PROXY_FILE, 'r') as proxy_file:
        proxies = [stroke for stroke in proxy_file]

    with open(CHANNEL_FILE, 'r') as channel_file:
        channels = [stroke for stroke in channel_file]

    with open(SITES_FILE, 'r') as sites_file:
        sites = [stroke for stroke in sites_file]

    for proxy in proxies:
        if len(proxy_thread) < 10:
            proxy_thread.append(proxy)
        else:
            proxy_threads.append(proxy_thread)
            proxy_thread = []
            proxy_thread.append(proxy)
    proxy_threads.append(proxy_thread)

    for proxy_list in proxy_threads:

        for proxy in proxy_list:

            if num_channel >= len(channels):
                num_channel = 0
            if num_cookie >= len(cookie_list):
                num_cookie = 0

            process = Process(target=ad_click, args=(settings, channels[num_channel], proxy, COOKIE_DIR + cookie_list[num_cookie], sites))
            process.start()
            processes.append(process)
            num_channel += 1
            num_cookie += 1
            time.sleep(10)

        for process in processes:
            process.join()

    with open(LOG_FILE, 'a') as log_file:
        log_file.write(str(datetime.today()) + '\n' + '=====================================END=====================================' + '\n\n')

if __name__ == '__main__':
    main(False, 1, 25, 20)


from rest_framework import serializers

from apps.friends.models import FriendRequest, FriendList
from apps.friends.services import is_mutual_friend

class CancelFriendRequestSerializer(serializers.ModelSerializer):
    class Meta:
        model = FriendRequest
        fields = ('receiver', 'receiver_id')

class DeclineFriendRequestSerializer(serializers.Serializer):
    friend_request_id = serializers.IntegerField()

class FriendSerializer(serializers.ModelSerializer):
    is_mutual = serializers.SerializerMethodField()

    class Meta:
        model = FriendList
        fields = ('id', 'user', 'is_mutual')

    def get_is_mutual(self, obj):
        user = self.context['request'].user
        user_data = obj.user
        auth_user_friend_list = FriendList.objects.get(user=user)
        return is_mutual_friend(auth_user_friend_list, obj)

class FriendRequestSerializer(serializers.ModelSerializer):
    class Meta:
        model = FriendRequest
        fields = ('id', 'sender', 'receiver', 'is_active', 'timestamp')


import random
import heapq
import sys
import networkx as nx # Biblioteka implementująca graf

# Implementacja algorytmów
def move_to_min(graph, sequence):
    total_cost = 0
    page_position = random.randint(0,num_vertices-1)
    #print("PAGE POSITION: ",page_position)
    current_sequence = []
    for a in range(len(sequence)):
        #print("PAGE POSITION: ",page_position)
        if (a+1) % 32 == 0:
            #print(current_sequence)
            values = []
            #print("A: ",a)
            #print(current_sequence)
            for j in range(graph.number_of_nodes()):
                values.append(sum(nx.shortest_path_length(graph, source=current, target=j) for current in current_sequence))
            #print(values)
            tmp_page_position = values.index(min(values))
            server_swap_cost = nx.shortest_path_length(graph, source=tmp_page_position, target=page_position)
            total_cost = total_cost +  (32 * server_swap_cost)
            #print(f"SWAP {tmp_page_position} - {page_position}, dystans: {server_swap_cost}")
            page_position = tmp_page_position
            current_sequence.clear()
        current_sequence.append(sequence[a])
        if page_position == sequence[a]:
            continue
        dist = nx.shortest_path_length(graph, source=sequence[a], target=page_position)
       # print(f"{sequence[a]} - {page_position}, dystans: {dist}")
        total_cost = total_cost +  dist
    return total_cost

def flip(graph, sequence):
    num_vertices = graph.number_of_nodes()
    #page_position = random.randint(0,num_vertices-1)
    page_position = random.randint(0,num_vertices-1)
    total_cost = 0

    for request in sequence:
        #print("POZYCJA STRONY: ", page_position)
        #print("POZYCJA ZAPYTANIA: ", request)
        if page_position == request:
            continue
        else:
            dist = nx.shortest_path_length(graph, source=request, target=page_position)
            do_swap = random.uniform(0,1)
            #print(do_swap)
            #print(1/(2*D))
            if do_swap < 1/(2*32):
                #print("SWAP")
                page_position = request
                total_cost = total_cost +  (32 * dist)
                #print(f"KOSZT TEJ RUNDY: {32} * {dist} = {total_cost}")
            else:
                #print("NIE SWAP")
                #print(f"KOSZT TEJ RUNDY: {dist}")
                total_cost = total_cost +  dist
    return total_cost

# Funkcja generująca ciąg żądań z jednostajnym rozkładem
def generate_uniform_sequence(length, num_vertices):
    return [random.randint(0, num_vertices - 1) for _ in range(length)]

# Funkcja generująca ciąg żądań z harmonicznym rozkładem
def generate_harmonic_sequence(length, num_vertices):
    harmonic_numbers = [1 / i for i in range(1, num_vertices + 1)]
    harmonic_sum = sum(harmonic_numbers)
    probabilities = [num / harmonic_sum for num in harmonic_numbers]
    return random.choices(range(num_vertices), probabilities, k=length)

# Funkcja generująca ciąg żądań z dwuharmonicznym rozkładem
def generate_two_harmonic_sequence(length, num_vertices):
    two_harmonic_numbers = [1 / (i ** 2) for i in range(1, num_vertices + 1)]
    two_harmonic_sum = sum(two_harmonic_numbers)
    probabilities = [num / two_harmonic_sum for num in two_harmonic_numbers]
    return random.choices(range(num_vertices), probabilities, k=length)

# Parametry grafów
num_vertices = 64
D = 32
# Inicjalizacja grafów
torus_graph = nx.Graph()
hypercube_graph = nx.Graph()

# Dodawanie krawędzi do grafu torusa trójwymiarowego
for i in range(num_vertices):
    torus_graph.add_edge(i, (i + 1) % num_vertices)
    torus_graph.add_edge(i, (i - 1) % num_vertices)
    torus_graph.add_edge(i, (i + 8) % num_vertices)
    torus_graph.add_edge(i, (i - 8) % num_vertices)
    torus_graph.add_edge(i, (i + 16) % num_vertices)
    torus_graph.add_edge(i, (i - 16) % num_vertices)
'''
side_length = int(num_vertices ** (1/3))
for i in range(num_vertices):
    x = i % side_length
    y = (i // side_length) % side_length
    z = (i // (side_length * side_length)) % side_length

    # Krawędzie wzdłuż osi x
    torus_graph.add_edge(i, (x + 1) % side_length + y * side_length + z * side_length * side_length)
    torus_graph.add_edge(i, (x - 1 + side_length) % side_length + y * side_length + z * side_length * side_length)

    # Krawędzie wzdłuż osi y
    torus_graph.add_edge(i, x + ((y + 1) % side_length) * side_length + z * side_length * side_length)
    torus_graph.add_edge(i, x + ((y - 1 + side_length) % side_length) * side_length + z * side_length * side_length)

    # Krawędzie wzdłuż osi z
    torus_graph.add_edge(i, x + y * side_length + ((z + 1) % side_length) * side_length * side_length)
    torus_graph.add_edge(i, x + y * side_length + ((z - 1 + side_length) % side_length) * side_length * side_length)
'''

# Dodawanie krawędzi do grafu hiperkostki
for i in range(num_vertices):
    for j in range(num_vertices):
        if bin(i ^ j).count('1') == 1:
            #print(f"{i} - {j}")
            hypercube_graph.add_edge(i, j)
# Parametry eksperymentu
sequence_length = 1024
num_trials = 1000

for graph_type in [torus_graph,hypercube_graph]:
    # Eksperyment dla grafu torusa trójwymiarowego
    if graph_type == torus_graph:
        print("Eksperyment dla grafu torusa trójwymiarowego:")
        print("--------------------------------------------------")
    else:
        # Eksperyment dla grafu hiperkostki
        print("Eksperyment dla grafu hiperkostki:")
        print("--------------------------------------------------")

    # Jednostajny rozkład
    print("Jednostajny rozkład:")
    uniform_costs_move_to_min = 0
    all_uniform_costs_move_to_min = []
    uniform_costs_flip = 0
    all_uniform_costs_flips = []
    for _ in range(num_trials):
        uniform_sequence = generate_uniform_sequence(sequence_length, num_vertices)
        uniform_costs_move_to_min = uniform_costs_move_to_min + move_to_min(graph_type, uniform_sequence)
        uniform_costs_move_to_min = uniform_costs_move_to_min / sequence_length
        all_uniform_costs_move_to_min.append(uniform_costs_move_to_min)
        uniform_costs_flip = uniform_costs_flip + flip(graph_type, uniform_sequence)
        uniform_costs_flip = uniform_costs_flip / sequence_length
        #print("UNI COSTS FLIP: ", uniform_costs_flip)
        all_uniform_costs_flips.append(uniform_costs_flip)

    print("MOVE-TO-MIN (średni koszt):", sum(all_uniform_costs_move_to_min) / num_trials)
    print("FLIP (średni koszt):", sum(all_uniform_costs_flips) / num_trials)
    print()
    # Harmoniczny rozkład
    print("Harmoniczny rozkład:")
    harmonic_costs_move_to_min = 0
    all_harmonic_costs_move_to_min = []
    harmonic_costs_flip = 0
    all_harmonic_costs_flips = []
    for _ in range(num_trials):
        harmonic_sequence = generate_harmonic_sequence(sequence_length, num_vertices)
        harmonic_costs_move_to_min = harmonic_costs_move_to_min + move_to_min(graph_type, harmonic_sequence)
        harmonic_costs_move_to_min = harmonic_costs_move_to_min / sequence_length
        all_harmonic_costs_move_to_min.append(harmonic_costs_move_to_min)

        harmonic_costs_flip = harmonic_costs_flip + flip(graph_type, harmonic_sequence)
        harmonic_costs_flip = harmonic_costs_flip / sequence_length
        #print("HARMONIC COSTS FLIP: ", harmonic_costs_flip)
        all_harmonic_costs_flips.append(harmonic_costs_flip)

    print("MOVE-TO-MIN (średni koszt):", sum(all_harmonic_costs_move_to_min) / num_trials)
    print("FLIP (średni koszt):", sum(all_harmonic_costs_flips) / num_trials)
    print()

    # Dwuharmoniczny rozkład
    print("Dwuharmoniczny rozkład:")
    two_harmonic_costs_flip = 0
    two_harmonic_costs_move_to_min = 0
    all_two_harmonic_costs_move_to_min = []
    all_two_harmonic_costs_flips = []
    for _ in range(num_trials):
        two_harmonic_sequence = generate_two_harmonic_sequence(sequence_length, num_vertices)
        #print(two_harmonic_sequence)
        two_harmonic_costs_move_to_min = two_harmonic_costs_move_to_min + move_to_min(graph_type, two_harmonic_sequence)
        two_harmonic_costs_move_to_min = two_harmonic_costs_move_to_min / sequence_length
        all_two_harmonic_costs_move_to_min.append(two_harmonic_costs_move_to_min)

        two_harmonic_costs_flip = two_harmonic_costs_flip + flip(graph_type, two_harmonic_sequence)
        two_harmonic_costs_flip = two_harmonic_costs_flip / sequence_length
        all_two_harmonic_costs_flips.append(two_harmonic_costs_flip)

    print("MOVE-TO-MIN (średni koszt):", sum(all_two_harmonic_costs_move_to_min) / num_trials)
    print("FLIP (średni koszt):", sum(all_two_harmonic_costs_flips) / num_trials)
    print()


import subprocess

# standardinput
#    Linux/Windows:
#    <
# standardoutput
#    Linux/Windows:
#    >  - overwrites
#    >> - always appends to the file
#
#    | (pipe) - takes the standardout of one program and links it to be the
#    standardin of another program

# stderr

# If want a program with arguments, pass it as an array
# (bottom) - only run command may be used on a module in Py 3.5
# proc = subprocess.run(["dir","c:\\Windows"], shell=True, stdout=subprocess.PIPE)
proc = subprocess.Popen(["dir","c:\\Windows"], shell=True, stdout=subprocess.PIPE)

for s in proc.stdout:
    s = s.decode('utf-8')   # Convert from bytes() to str()
    s = s.rstrip()          # Remove whitespace at the end-of-line

    print(">>>",s)