import tensorflow as tf
import yaml
import gym
import ma_gym
import os
import numpy as np
from utils.init_env import init_env
from configs.config import *
import glob

def calculate_nash_conv(env, config, arg, devices):
    n_agents = env.n_agents
    checkpoint_path = args.checkpoint_path
    with tf.device(device):
        current_trainer = TRAINERs[config["trainer"]](env, n_agents, config)
        current_trainer.restore(checkpoint_path, args.agent_ids)

        # to get the current policy evaluate value
        current_values = []
        current_trainer.buf.clear()
        current_trainer.run_one_epoch(["original"] * n_agents)
        current_data = current_trainer.buf.get()
        # n_agents x shape of state
        obs = current_data[0]

        for j in range(n_agents):
            values = current_trainer.agents[j].get_value(
                current_trainer.sess, np.array(obs[:, j]), "original"
            )
            # n_agents x n_states
            current_values.append(values)

        tf.compat.v1.reset_default_graph()
        br_values = []

        br_trainer = TRAINERs[config["trainer"]](env, n_agents, config)
        br_trainer.restore(checkpoint_path, args.agent_ids)
        br_trainer.rollouts(1, ["original"] * n_agents)
        br_trainer.update_tmp_policies()

        for i in range(args.num_games_eval):
            br_trainer.buf.clear()
            br_trainer.run_one_epoch(["tmp"] * n_agents)
            br_data = br_trainer.buf.get()
            for j in range(n_agents):
                values = br_trainer.agents[j].get_value(
                    br_trainer.sess, np.array(obs[:, j]), "tmp"
                )
                br_values.append(values)
            nash_conv = 0
            for i in range(n_agents):
                nash_conv += sum(
                    np.array(br_values[i][:]) - np.array(current_values[i][:])
                )
    return nash_conv

def calculate_score(env, sess, agents):
    """
    rollout with two policies, find the score of using policy1 against policy 2
    """
    n_agents = env.n_agents
    obs_n, rew_n, done_n, ep_ret_n, ep_len = (
        env.reset(),
        [0 for i in range(n_agents)],
        False,
        [0 for i in range(n_agents)],
        0,
    )
    done = False
    while not done:
        act_n = []
        for i in range(n_agents):
            action, _, _ = agents[i].get_action(sess, obs_n[i], "original")
            act_n.append(action[0])
        obs_n, rew_n, done_n, _ = env.step(act_n)
        ep_ret_n = [n + r for n, r in zip(ep_ret_n, rew_n)]
        ep_len += 1
        if np.array(done_n).any():
            done = True

    return ep_ret_n

def calculate_population_performance(env, config, args, device):
    matrix_size = 2
    matrix = np.zeros((matrix_size, matrix_size))
    # load saved policies
    n_agents = env.n_agents
    checkpoints = []
    print(args.checkpoint_path)
    for i in range(n_agents):
        model_checkpoints =[]
        for j in range(matrix_size):
            model_checkpoints.append(args.checkpoint_path[i] + "model_{}.cpt-{}".format(i, j))
        checkpoints.append(model_checkpoints)

    # run simulations with policy pairs
    num_games = 20
    trainer = TRAINERs[config["trainer"]](env, n_agents, config)
    for i in range(matrix_size):
        for j in range(matrix_size):
            scores = []
            tf.compat.v1.reset_default_graph()
            trainer.restore([checkpoints[0][i], checkpoints[1][j]], args.agent_ids)
            # each policy pair will play 20 episodes to get the average score
            for _ in range(num_games):
                score = calculate_score(env, trainer.sess, trainer.agents)
                scores.append(score[0])
            matrix[i][j] = np.mean(scores)
    return matrix

if __name__ == "__main__":
    import argparse

    parser = argparse.ArgumentParser()

    parser.add_argument("--config_file", type=str, default="")
    parser.add_argument("--checkpoint_path", nargs='+', default="")
    parser.add_argument("--agent_ids", nargs= '+', default="")
    parser.add_argument("--evaluate_method", type=str, default="nash_conv")
    parser.add_argument("--num_games_eval", type=int, default=10)
    parser.add_argument("--output_folder", type=str, default="evaluate_logs")
    parser.add_argument("--device", type=str, default="CPU")
    args = parser.parse_args()

    a_yaml_file = open(args.config_file)
    config = yaml.load(a_yaml_file, Loader=yaml.FullLoader)

    device = args.device
    if device == "CPU" or device == "GPU":
        device = f"/{args.device}:0"
    elif device not in ["/GPU:1", "/GPU:2", "/GPU:3"]:
        raise ValueError

    env, n_agents, env_info = init_env(config)

    print("output folder is: ", config["output_dir"])
    print("env info: ", env.observation_space, env.action_space, env.n_agents)

    if args.evaluate_method == "nash_conv":
        nash_conv = calculate_nash_conv(env, config, args, device)
        print("evaluate score -- nash conv is: ", nash_conv)
    elif args.evaluate_method == "population_performance":
        matrix = calculate_population_performance( env, config, args, device)
        print(matrix)


import pandas as pd #for reading csv
from sklearn.cluster import KMeans
import matplotlib.pyplot as plt
import numpy as np
from sklearn.metrics import silhouette_score
from sklearn.neighbors import KNeighborsClassifier
from math import sqrt
import dash
import random
import dash_core_components as dcc
import dash_html_components as html
from dash.dependencies import Input, Output
import dash_core_components as dcc
import base64
import os
#external_stylesheets = ['https://codepen.io/chriddyp/pen/bWLwgP.css']
ALLOWED_TYPES = (
    "text", "number", "password", "email", "search",
    "tel", "url", "range", "hidden",
)
app = dash.Dash(__name__)
dataframe=pd.read_csv('train-data.csv')

dataframe=dataframe.drop('Unnamed: 0',axis=1) #we dropped 'Unnamed: 0' column because it was irrelavent

#print(dataframe.isnull().sum()) #here we checked how many columns had null values

#let's drop rows with null values

#print(dataframe.shape)

dataframe=dataframe[dataframe['Mileage'].notna()]
dataframe=dataframe[dataframe['Engine'].notna()]
dataframe=dataframe[dataframe['Power'].notna()]
dataframe=dataframe[dataframe['Seats'].notna()]

#we'll drop new peices column since it has a lot of null values
dataframe=dataframe.drop('New_Price',axis=1) #we dropped 'Unnamed: 0' column because it was irrelavent

#the name column can be mined down into more important information
dataframe=dataframe.reset_index(drop=True)
for i in range(dataframe.shape[0]):
    dataframe.at[i, 'Company'] = dataframe['Name'][i].split()[0]
    dataframe.at[i, 'Mileage(km/kg)'] = dataframe['Mileage'][i].split()[0]
    dataframe.at[i, 'Engine(CC)'] = dataframe['Engine'][i].split()[0]
    dataframe.at[i, 'Power(bhp)'] = dataframe['Power'][i].split()[0]

#print(dataframe.head())

dataframe['Mileage(km/kg)'] = dataframe['Mileage(km/kg)'].astype(float)
#dataframe['Power(bhp)'] = dataframe['Power(bhp)'].astype(float)
dataframe['Engine(CC)'] = dataframe['Engine(CC)'].astype(float)

x = 'n'
count = 0
position = []
dataframe=dataframe.reset_index(drop=True)
for i in range(dataframe.shape[0]):
    if dataframe['Power(bhp)'][i]=='null':
        x = 'Y'
        count = count + 1
        position.append(i)
#        dataframe = dataframe.drop(dataframe.index[position])
 #       dataframe = dataframe.reset_index(drop=True)
dataframe = dataframe.drop(dataframe.index[position])
dataframe = dataframe.reset_index(drop=True)

dataframe['Power(bhp)'] = dataframe['Power(bhp)'].astype(float)

#dataframe.drop(["Name"],axis=1,inplace=True)
dataframe.drop(["Mileage"],axis=1,inplace=True)
dataframe.drop(["Engine"],axis=1,inplace=True)
dataframe.drop(["Power"],axis=1,inplace=True)

Location = dataframe[['Location']]
Location = pd.get_dummies(Location,drop_first=True)
#print(Location.head())
Fuel_t = dataframe[['Fuel_Type']]
Fuel_t = pd.get_dummies(Fuel_t,drop_first=True)
#print(Fuel_t.head())

Transmission = dataframe[['Transmission']]
Transmission = pd.get_dummies(Transmission,drop_first=True)

dataframe.replace({"First":1,"Second":2,"Third": 3,"Fourth & Above":4},inplace=True)

dataframe.drop(["Company"],axis=1,inplace=True)

final_train= pd.concat([dataframe,Location,Fuel_t,Transmission],axis=1)

final_train.drop(["Location","Fuel_Type","Transmission"],axis=1,inplace=True)
#print(final_train.shape)

test_data=pd.read_csv('test-data.csv')

test_data = test_data.iloc[:,1:]

#print("Shape of test data Before dropping any Row: ",dataframe.shape)
test_data = test_data[test_data['Mileage'].notna()]
#print("Shape of test data After dropping Rows with NULL values in Mileage: ",test_data.shape)
test_data = test_data[test_data['Engine'].notna()]
#print("Shape of test data After dropping Rows with NULL values in Engine : ",test_data.shape)
test_data = test_data[test_data['Power'].notna()]
#print("Shape of test data After dropping Rows with NULL values in Power  : ",test_data.shape)
test_data = test_data[test_data['Seats'].notna()]
#print("Shape of test data After dropping Rows with NULL values in Seats  : ",test_data.shape)
#print('Droping null done')

test_data = test_data.reset_index(drop=True)
#print('Index reset done')
for i in range(test_data.shape[0]):
    test_data.at[i, 'Mileage(km/kg)'] = test_data['Mileage'][i].split()[0]
    test_data.at[i, 'Engine(CC)'] = test_data['Engine'][i].split()[0]
    test_data.at[i, 'Power(bhp)'] = test_data['Power'][i].split()[0]
#print('Split Done')

test_data['Mileage(km/kg)'] = test_data['Mileage(km/kg)'].astype(float)
test_data['Engine(CC)'] = test_data['Engine(CC)'].astype(float)

#print('casting 1 Done')

position = []
for i in range(test_data.shape[0]):
    if test_data['Power(bhp)'][i]=='null':
        position.append(i)

test_data = test_data.drop(test_data.index[position])
test_data = test_data.reset_index(drop=True)
test_data['Power(bhp)'] = test_data['Power(bhp)'].astype(float)
#print('casting 2 Done')

for i in range(test_data.shape[0]):
    if pd.isnull(test_data.loc[i,'New_Price']) == False:
        test_data.at[i,'New_car_Price'] = test_data['New_Price'][i].split()[0]

test_data['New_car_Price'] = test_data['New_car_Price'].astype(float)

test_data.drop(["Name"],axis=1,inplace=True)
test_data.drop(["Mileage"],axis=1,inplace=True)
test_data.drop(["Engine"],axis=1,inplace=True)
test_data.drop(["Power"],axis=1,inplace=True)
test_data.drop(["New_Price"],axis=1,inplace=True)

var = 'Location'
Location = test_data[[var]]
Location = pd.get_dummies(Location,drop_first=True)
Location.head()
var = 'Fuel_Type'
Fuel_t = test_data[[var]]
Fuel_t = pd.get_dummies(Fuel_t,drop_first=True)
Fuel_t.head()

var = 'Transmission'
Transmission = test_data[[var]]
Transmission = pd.get_dummies(Transmission,drop_first=True)
Transmission.head()

test_data.replace({"First":1,"Second":2,"Third": 3,"Fourth & Above":4},inplace=True)
test_data.head()

final_test= pd.concat([test_data,Location,Fuel_t,Transmission],axis=1)
final_test.head()

final_test.drop(["Location","Fuel_Type","Transmission","New_car_Price"],axis=1,inplace=True)
final_test.head()

#print("Final Test Size: ",final_test.shape)
#print(final_test.head())
#x=dataframe.iloc[,[]]
X = final_train.loc[:,['Year', 'Kilometers_Driven', 'Owner_Type', 'Seats',
       'Mileage(km/kg)', 'Engine(CC)', 'Power(bhp)',
       'Location_Bangalore', 'Location_Chennai', 'Location_Coimbatore',
       'Location_Delhi', 'Location_Hyderabad', 'Location_Jaipur',
       'Location_Kochi', 'Location_Kolkata', 'Location_Mumbai',
       'Location_Pune', 'Fuel_Type_Diesel', 'Fuel_Type_LPG',
       'Fuel_Type_Petrol', 'Transmission_Manual','Price']]
'''
g=[]
for i in range(X.shape[0]):
    h=[]
    for j in range(X.shape[1]):
        h.append(X[X.columns[j]][i])
    g.append(h)

wcss = []
for i in range(1,20):
 kmeans = KMeans(n_clusters=i,init='k-means++',max_iter=300,n_init=10,random_state=0)
 kmeans.fit(g)
 wcss.append(kmeans.inertia_)
 print("Cluster", i, "Inertia", kmeans.inertia_)
plt.plot(range(1,20),wcss)
plt.title('The Elbow Curve')
plt.xlabel('Number of clusters')
plt.ylabel('WCSS') ##WCSS stands for total within-cluster sum of square
plt.savefig('./assets/elbow.png')
'''
kmeans = KMeans(n_clusters=5,init='k-means++',max_iter=300,n_init=10,random_state=0)
y_kmeans=kmeans.fit(X)
gg=kmeans.fit_predict(X)
print(y_kmeans.cluster_centers_)
#print(X.iloc[:,21],X.iloc[:,4])
plt.scatter(X.iloc[:,21],X.iloc[:,3],c=gg,cmap='rainbow')
#plt.scatter(y_kmeans.cluster_centers_[:,0],y_kmeans.cluster_centers_[:,1],marker='*',c='red')
#plt.show()
plt.savefig('./assets/plot.png', dpi=300, bbox_inches='tight')
#plt.scatter(X.iloc[:,21],X.iloc[:,3],c=gg,cmap='rainbow')
index_closest=-1
def recommendation(km):
    diff = np.array(y_kmeans.cluster_centers_) - np.array(km)[0]  # NumPy broadcasting
    #print(diff)
    dist = np.sqrt(np.sum(diff**2, axis=-1))  # Euclidean distance
    print(dist)
    closest_centroid = np.array(y_kmeans.cluster_centers_)[np.argmin(dist),]
    '''print("closest centroid:\n",closest_centroid)'''

    for i in range(5):
        if(np.array(y_kmeans.cluster_centers_)[i]==closest_centroid).all():
            global index_closest
            index_closest=i
            break
#recommendation()
    cluster_map=pd.DataFrame()
    cluster_map['cluster'] = y_kmeans.labels_
    print("recommended ",(np.array(cluster_map[cluster_map.cluster == index_closest].head().index)))
    ii=random.random()*(len(np.array(cluster_map[cluster_map.cluster == index_closest].index))-1)
    ii=int(ii)
    #for i in range(len(np.array(cluster_map[cluster_map.cluster == index_closest].index))):
    return dataframe['Name'][ii]
#print(np.array(y_kmeans.cluster_centers_))
#print(abs(closest_centroid[-1]-np.array(X)[1,:][-1]))
#print(y_kmeans.inertia_)
Distances=100000000
index=[]
def KNN(test_data):
    global final_train,index,Distances,index
    final_train = final_train.loc[:,['Year', 'Kilometers_Driven', 'Owner_Type', 'Seats',
        'Mileage(km/kg)', 'Engine(CC)', 'Power(bhp)',
        'Location_Bangalore', 'Location_Chennai', 'Location_Coimbatore',
        'Location_Delhi', 'Location_Hyderabad', 'Location_Jaipur',
        'Location_Kochi', 'Location_Kolkata', 'Location_Mumbai',
        'Location_Pune', 'Fuel_Type_Diesel', 'Fuel_Type_LPG',
        'Fuel_Type_Petrol', 'Transmission_Manual','Price']]
    f_t=np.array(final_train)
    print("Yayy",f_t,test_data)
    for i in range(5000):
        dd=0
        for j in range(21):
            dd=dd+sqrt(abs(f_t[i][j]**2-test_data[j]**2))
            #print(dd)
        if(dd<Distances):
            Distances=dd
            index.append(f_t[i][-1])
    print(index[0])
    return sum(index) / len(index)
'''print('closest_neighbour',index[-1],test_data[-1])'''
#fig = go.Figure(data=[go.Scatter(x=[1, 2, 3], y=[4, 1, 2])])
#encoded_image = base64.b64encode(open('/home/dell/ddd/minipro/plot.png', 'rb').read())
app.layout = html.Div(style={'position':'absolute','top':'7vh','left':'45vw'},children=[
    dcc.Input(
            id="text1",
            type="text",
            placeholder="Year",
            style={'borderStyle':'solid','height':'5vh','textAlign':'center'}
        ),
        html.P(''),
        dcc.Input(
            id="text2",
            type="text",
            placeholder="Kilometers Driven",
            style={'borderStyle':'solid','height':'5vh','textAlign':'center'}
        ),
        html.P(''),
        dcc.Input(
            id="text3",
            type="text",
            placeholder="Times Resold",
            style={'borderStyle':'solid','height':'5vh','textAlign':'center'}
        ),
        html.P(''),
                dcc.Input(
            id="text4",
            type="text",
            placeholder="Seats",
            style={'borderStyle':'solid','height':'5vh','textAlign':'center'}
        ),
        html.P(''),
        dcc.Input(
            id="text5",
            type="text",
            placeholder="Mileage(km/kg)",
            style={'borderStyle':'solid','height':'5vh','textAlign':'center'}
        ),
        html.P(''),
        dcc.Input(
            id="text6",
            type="text",
            placeholder="Engine(CC)",
            style={'borderStyle':'solid','height':'5vh','textAlign':'center'}
        ),
        html.P(''),
        dcc.Input(
            id="text7",
            type="text",
            placeholder="Power(bhp)",
            style={'borderStyle':'solid','height':'5vh','textAlign':'center'}
        ),
        html.P(''),
        dcc.Input(
            id="text8",
            type="text",
            placeholder="City",
            style={'borderStyle':'solid','height':'5vh','textAlign':'center'}
        ),
        html.P(''),
        dcc.Input(
            id="text9",
            type="text",
            placeholder="Fuel type",
            style={'borderStyle':'solid','height':'5vh','textAlign':'center'}
        ),
        html.P(''),
        dcc.Input(
            id="text10",
            type="text",
            placeholder="Transmission",
            style={'borderStyle':'solid','height':'5vh','textAlign':'center'}
        ),
        html.P('',id='output'),
        html.P(''),
        html.P('',style={'backgroundImage':'url("/assets/elbow.png")','height':'500px','width':'500px','backgroundSize':'cover','position':'relative','left':'-20vw'}),
        #html.P('elbow method results'),
        html.P('',style={'backgroundImage':'url("/assets/plot.png")','height':'500px','width':'500px','backgroundSize':'cover','position':'relative','left':'-20vw'})
        ,html.P('mileage vs prices'),
        html.P('',style={'backgroundImage':'url("/assets/engine.png")','height':'500px','width':'500px','backgroundSize':'cover','position':'relative','left':'-20vw'}),
        html.P('engine vs prices'),
])
@app.callback(
    Output("output", "children"),
    Input("text1", "value"),
    Input("text2", "value"),
    Input("text3", "value"),
    Input("text4", "value"),
    Input("text5", "value"),
    Input("text6", "value"),
    Input("text7", "value"),
    Input("text8", "value"),
    Input("text9", "value"),
    Input("text10", "value"),

)
def update_output(text1, text2,text3,text4,text5,text6,text7,text8,text9,text10):
    if text1 is None:
        raise dash.exceptions.PreventUpdate
    if text2 is None:
        raise dash.exceptions.PreventUpdate
    if text3 is None:
        raise dash.exceptions.PreventUpdate
    if text4 is None:
        raise dash.exceptions.PreventUpdate
    if text5 is None:
        raise dash.exceptions.PreventUpdate
    if text6 is None:
        raise dash.exceptions.PreventUpdate
    if text7 is None:
        raise dash.exceptions.PreventUpdate
    if text8 is None:
        raise dash.exceptions.PreventUpdate
    if text9 is None:
        raise dash.exceptions.PreventUpdate
    if text10 is None:
        raise dash.exceptions.PreventUpdate
    else:
        t8={'Banglore':1,'Chennai': 2,'Coimbatore': 3,
            'Delhi': 4,'Hyderabad': 5,'Jaipur': 6,'Kochi': 7,
            'Kolkata': 8,'Mumbai': 9,'Pune': 10}
        t9={'Diesel': 0,'LPG': 1,'Petrol': 2}
        t10={'Automatic': 0,'Manual': 1}
        if text8 not in t8:
            return "invalid entry at city"
        if text9 not in t9:
            return "invalid entry at Fuel"
        if text10 not in t10:
            return "invalid entry at transmission"
        try:
            text1=float(text1)
            text2=float(text2)
            text3=float(text3)
            text4=float(text4)
            text5=float(text5)
            text6=float(text6)
            text7=float(text7)
        except:
            return "invalid entry,please check the values entered"
        text8=t8[text8]
        text9=t9[text9]
        text10=t10[text10]
        text3=text3+1
        a1=[text1,text2,text3,text4,text5,text7]
        for i in range(text8):
            a1.append(0)
        a1.append(1)

        for j in range(text8+1,11):
            a1.append(0)
        for k in range(text9):
            a1.append(0)
        a1.append(1)
        for k in range(text9+1,3):
            a1.append(0)
        a1.append(text10)
        gg=KNN(a1)
        km=recommendation(a1)
        return ("Predicted Price: ",gg," Recommended Car:",km)

if __name__ == '__main__':
    app.run_server(debug=True)


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

import argparse

import numpy as np
import tensorflow as tf

import os
import urllib
from collections import defaultdict

try:
    from urllib.parse import unquote, urlparse, parse_qs
except ImportError:
    from urlparse import unquote, urlparse, parse_qs

import json

from flask import Flask
from flask import request
from flask import url_for
from flask import jsonify
from flask_cors import CORS

from werkzeug.utils import secure_filename
import re
import base64

"""
Uploader variables
"""
UPLOAD_PATH = '/some-path'
ALLOWED_EXTENSIONS = set(['jpg', 'jpeg', 'png'])

"""
Predictor variables
"""
MODEL_FILE = "../model/output_graph.pb"
FILE_NAME = ""
LABEL_FILE = "../model/output_labels.txt"

INPUT_HEIGHT = 299
INPUT_WIDTH = 299
INPUT_MEAN = 0
INPUT_STD = 255
INPUT_LAYER = "Placeholder"
OUTPUT_LAYER = "final_result"

app = Flask(__name__)
app.config["UPLOAD_PATH"] = UPLOAD_PATH
CORS(app)

class Predictor():
    def load_graph(self):
        graph = tf.Graph()
        graph_def = tf.GraphDef()

        with open(MODEL_FILE, "rb") as f:
            graph_def.ParseFromString(f.read())
        with graph.as_default():
            tf.import_graph_def(graph_def)

        return graph

    def read_tensor_from_image_file(
            self,
            file_name,
            input_height=299,
            input_width=299,
            input_mean=0,
            input_std=255):
        input_name = "file_reader"
        output_name = "normalized"

        file_reader = tf.read_file(file_name, input_name)
        if file_name.endswith(".png"):
            image_reader = tf.image.decode_png(
                    file_reader, channels=3, name="png_reader")
        elif file_name.endswith(".gif"):
            image_reader = tf.squeeze(
                    tf.image.decode_gif(file_reader, name="gif_reader"))
        elif file_name.endswith(".bmp"):
            image_reader = tf.image.decode_bmp(file_reader, name="bmp_reader")
        else:
            image_reader = tf.image.decode_jpeg(
                    file_reader, channels=3, name="jpeg_reader")

        float_caster = tf.cast(image_reader, tf.float32)
        dims_expander = tf.expand_dims(float_caster, 0)
        resized = tf.image.resize_bilinear(dims_expander, [input_height, input_width])
        normalized = tf.divide(tf.subtract(resized, [input_mean]), [input_std])
        sess = tf.Session()
        result = sess.run(normalized)

        return result

    def load_labels(self, label_file):
        label = []
        proto_as_ascii_lines = tf.gfile.GFile(label_file).readlines()
        for l in proto_as_ascii_lines:
            label.append(l.rstrip())
        return label

@app.route("/predict", methods=["GET", "POST"])
def predict():
    image_data = request.json['image']
    image_base_64 = image_data[22:]

    with open("base64data", "wb") as fh:
        fh.write(image_data)

    with open("uploaded_image.png", "wb") as fh:
        fh.write(base64.b64decode(image_base_64))

    main_predictor = Predictor()

    data = request.args

    file_name = "./uploaded_image.png"

    graph = main_predictor.load_graph()
    t = main_predictor.read_tensor_from_image_file(
            file_name = file_name,
            input_height=INPUT_HEIGHT,
            input_width=INPUT_WIDTH,
            input_mean=INPUT_MEAN,
            input_std=INPUT_STD)
    input_name = "import/" + INPUT_LAYER
    output_name = "import/" + OUTPUT_LAYER
    input_operation = graph.get_operation_by_name(input_name)
    output_operation = graph.get_operation_by_name(output_name)

    with tf.Session(graph=graph) as sess:
        results = sess.run(output_operation.outputs[0], {
            input_operation.outputs[0]: t
            })
        results = np.squeeze(results)

    top_k = results.argsort()[-5:][::-1]
    labels = main_predictor.load_labels(LABEL_FILE)
    label_results = defaultdict(str)

    for i in top_k:
        label_results[labels[i]] = str(results[i])

    return jsonify(label_results)

if __name__ == '__main__':
    port = os.environ.get('PORT', 5000)
    app.run(host='127.0.0.1', port = port)


import datetime
import decimal
import logging
from dataclasses import dataclass, fields
from typing import Callable, Dict, List, Optional, Tuple, cast

from bespoke import errors
from bespoke.companies import partnership_util
from bespoke.date import date_util
from bespoke.db import db_constants, models, models_util
from bespoke.email import sendgrid_util
from bespoke.finance.payments import payment_util
from sqlalchemy.orm import Session
from bespoke.finance.types import payment_types

REQUIRED_KEYS_FOR_APPROVAL = (
	'invoice_number',
	'subtotal_amount',
	'taxes_amount',
	'total_amount',
	'invoice_date',
	'invoice_due_date',
)

REQUIRED_POSITIVE_VALUES_FOR_APPROVAL = (
	'subtotal_amount',
	'taxes_amount',
	'total_amount',
)

@dataclass
class InvoiceFileItem:
	file_id: str
	invoice_id: str
	file_type: str

	@staticmethod
	def from_dict(d: Dict) -> 'InvoiceFileItem':
		return InvoiceFileItem(
			d.get('file_id'),
			d.get('invoice_id'),
			d.get('file_type')
		)

@dataclass
class InvoiceData:
	id: str
	company_id: str
	payor_id: str
	invoice_number: str
	subtotal_amount: float
	taxes_amount: float
	total_amount: float
	invoice_date: datetime.date
	invoice_due_date: datetime.date
	status: str
	rejection_note: str
	is_cannabis: bool

	def to_model(self) -> models.Invoice:
		return models.Invoice( # type: ignore
			id=self.id,
			company_id=self.company_id,
			payor_id=self.payor_id,
			invoice_number=self.invoice_number,
			subtotal_amount=decimal.Decimal(self.subtotal_amount) if self.subtotal_amount is not None else None,
			taxes_amount=decimal.Decimal(self.taxes_amount) if self.taxes_amount is not None else None,
			total_amount=decimal.Decimal(self.total_amount) if self.total_amount is not None else None,
			invoice_date=self.invoice_date,
			invoice_due_date=self.invoice_due_date,
			status=self.status,
			rejection_note=self.rejection_note,
			is_cannabis=self.is_cannabis,
		)

	@staticmethod
	def from_dict(d: Dict) -> Tuple['InvoiceData', errors.Error]:
		if not d:
			return None, errors.Error('cannot instantiate InvoiceData from nothing')

		invoice_date = d.get('invoice_date')
		if invoice_date:
			try:
				invoice_date = date_util.load_date_str(invoice_date)
			except:
				return None, errors.Error('invalid invoice_date')

		return InvoiceData(
			id=d.get('id'),
			company_id=d.get('company_id'),
			payor_id=d.get('payor_id'),
			invoice_number=d.get('invoice_number'),
			subtotal_amount=InvoiceData.parse_numeric_safely(d, 'subtotal_amount'),
			taxes_amount=InvoiceData.parse_numeric_safely(d, 'taxes_amount'),
			total_amount=InvoiceData.parse_numeric_safely(d, 'total_amount'),
			invoice_date=InvoiceData.parse_date_safely(d, 'invoice_date'),
			invoice_due_date=InvoiceData.parse_date_safely(d, 'invoice_due_date'),
			status=d.get('status'),
			rejection_note=d.get('rejection_note'),
			is_cannabis=d.get('is_cannabis'),
		), None

	@staticmethod
	def parse_date_safely(obj: Dict, key: str) -> Optional[datetime.date]:
		value = obj.get(key)
		if value:
			return date_util.load_date_str(value)
		return None

	@staticmethod
	def parse_numeric_safely(obj: Dict, key: str) -> Optional[float]:
		value = obj.get(key)
		if value or value == 0:
			return float(value)
		return None

@dataclass
class InvoiceUpsertRequest:
	invoice: InvoiceData
	invoice_files: List[InvoiceFileItem]

	@staticmethod
	def from_dict(d: Dict) -> Tuple['InvoiceUpsertRequest', errors.Error]:
		data, err = InvoiceData.from_dict(d.get('invoice'))
		if err:
			return None, err

		return InvoiceUpsertRequest(
			invoice=data,
			invoice_files=[InvoiceFileItem.from_dict(f) for f in d.get('invoice_files', [])]
		), None

@dataclass
class SubmitForPaymentRequest:
	invoice_ids: List[str]

	@staticmethod
	def from_dict(d: Dict) -> 'SubmitForPaymentRequest':
		return SubmitForPaymentRequest(d.get('invoice_ids', []))

@dataclass
class InvoicePaymentRequestResponse:
	invoice_id: str
	new_status: db_constants.RequestStatusEnum
	link_val: str
	rejection_note: Optional[str]
	amount: Optional[float]
	anticipated_payment_date: Optional[datetime.date]
	payment_method: Optional[db_constants.PaymentMethodEnum]

	@staticmethod
	def from_dict(d: Dict) -> Tuple['InvoicePaymentRequestResponse', errors.Error]:
		invoice_id = d.get('invoice_id')
		if not invoice_id:
			return None, errors.Error('missing key: invoice_id')

		link_val = d.get('link_val')
		if not link_val:
			return None, errors.Error('missing key: link_val')

		new_status = d.get('new_status')
		if not new_status:
			return None, errors.Error('missing key: new_status')

		rejection_note = d.get('rejection_note')

		if new_status == db_constants.RequestStatusEnum.REJECTED:
			if not rejection_note:
				return None, errors.Error('rejected invoice payments require a note')
			return InvoicePaymentRequestResponse(
				invoice_id,
				new_status,
				link_val,
				rejection_note,
				None,
				None,
				None
			), None

		if new_status != db_constants.RequestStatusEnum.APPROVED:
			return None, errors.Error("invalid new status")

		amount = d.get('amount')
		if not amount:
			return None, errors.Error('missing key: amount')

		if str(amount).lower() == 'nan':
			return None, errors.Error("'amount' cannot be NaN")

		try:
			amount = float(amount)
		except:
			return None, errors.Error("'amount' is not a float")

		anticipated_payment_date = d.get('anticipated_payment_date')
		if not anticipated_payment_date:
			return None, errors.Error('missing key: anticipated_payment_date')

		try:
			anticipated_payment_date = date_util.load_date_str(anticipated_payment_date)
		except:
			return None, errors.Error('invalid anticipated_payment_date')

		payment_method = d.get('payment_method')
		if not payment_method:
			return None, errors.Error('missing key: payment_method')

		if payment_method not in db_constants.ALL_PAYMENT_METHODS:
			return None, errors.Error('invalid payment method')

		return InvoicePaymentRequestResponse(
			invoice_id,
			new_status,
			link_val,
			rejection_note,
			amount,
			anticipated_payment_date,
			payment_method
		), None

@errors.return_error_tuple
def create_update_invoice(
	session_maker: Callable,
	request: InvoiceUpsertRequest
) -> Tuple[str, errors.Error]:
	is_create_invoice = False

	with models.session_scope(session_maker) as session:
		if request.invoice.id is not None:
			existing_invoice = cast(
				models.Invoice,
				session.query(models.Invoice).get(request.invoice.id))

			for field in fields(InvoiceData):
				value = getattr(request.invoice, field.name)
				if value is not None:
					setattr(existing_invoice, field.name, value)

			session.flush()
			invoice = existing_invoice
		else:
			duplicate_invoice = cast(
				models.Invoice,
				session.query(models.Invoice).filter(
					cast(Callable, models.Invoice.is_deleted.isnot)(True)
				).filter(
					models.Invoice.payor_id == request.invoice.payor_id
				).filter(
					models.Invoice.invoice_number == request.invoice.invoice_number
				).first())

			if duplicate_invoice is not None:
				raise errors.Error(f'An invoice with this payor and invoice number already exists')

			invoice = request.invoice.to_model()
			session.add(invoice)
			session.flush()

			is_create_invoice = True

		invoice_id = str(invoice.id)

		# Invoice files
		existing_invoice_files = cast(
			List[models.InvoiceFile],
			session.query(models.InvoiceFile).filter(
				models.InvoiceFile.invoice_id == invoice_id
			).all())

		invoice_files_to_delete = []
		for existing_invoice_file in existing_invoice_files:
			is_invoice_file_deleted = len(list(filter(
				lambda invoice_file_request: (
					invoice_file_request.invoice_id == existing_invoice_file.invoice_id and
					invoice_file_request.file_id == existing_invoice_file.file_id and
					invoice_file_request.file_type == existing_invoice_file.file_type
				),
				request.invoice_files
			))) <= 0
			if is_invoice_file_deleted:
				invoice_files_to_delete.append(existing_invoice_file)

		for invoice_file_to_delete in invoice_files_to_delete:
			cast(Callable, session.delete)(invoice_file_to_delete)

		session.flush()

		invoice_file_dicts = []
		for invoice_file_request in request.invoice_files:
			existing_invoice_file = cast(
				models.InvoiceFile,
				session.query(models.InvoiceFile).get([
					invoice_file_request.invoice_id,
					invoice_file_request.file_id,
				]))
			if existing_invoice_file:
				invoice_file_dicts.append(existing_invoice_file.as_dict())
			else:
				invoice_file = models.InvoiceFile(
					file_id=invoice_file_request.file_id,
					invoice_id=invoice_id,
					file_type=invoice_file_request.file_type,
				)
				session.add(invoice_file)
				invoice_file_dicts.append(invoice_file.as_dict())

		# if is_create_invoice:
			# sendgrid_client = cast(
			# 	sendgrid_util.Client,
			# 	current_app.sendgrid_client,
			# )

			# template_data = {
			# 	'customer_name': customer.name,
			# 	'vendor_name': vendor.name,
			# 	'purchase_order_number': purchase_order.order_number,
			# 	'purchase_order_amount': number_util.to_dollar_format(float(purchase_order.amount)) if purchase_order.amount else None,
			# }
			# _, err = sendgrid_client.send(
			# 	template_name=sendgrid_util.TemplateNames.CUSTOMER_CREATED_PURCHASE_ORDER,
			# 	template_data=template_data,
			# 	recipients=current_app.app_config.BANK_NOTIFY_EMAIL_ADDRESSES,
			# )
			# if err:
			# 	raise err

	return invoice_id, None

def is_invoice_ready_for_approval(
	session_maker: Callable,
	invoice_id: str
) -> errors.Error:
	try:
		with models.session_scope(session_maker) as session:
			invoice = session.query(models.Invoice).get(invoice_id)
			if not invoice:
				return errors.Error("no invoice with that id")

			for key in REQUIRED_KEYS_FOR_APPROVAL:
				value = getattr(invoice, key)
				if value is None:
					return errors.Error(f"missing key: {key}")

			for key in REQUIRED_POSITIVE_VALUES_FOR_APPROVAL:
				value = getattr(invoice, key)
				if value < 0:
					return errors.Error(f"{key} cannot be negative")

			invoice_file = session.query(models.InvoiceFile) \
				.filter_by(
					invoice_id=invoice.id,
					file_type=db_constants.InvoiceFileTypeEnum.Invoice
				).first()
			if not invoice_file:
				return errors.Error("no invoice file")

			relationship = session.query(models.CompanyPayorPartnership) \
				.filter_by(company_id=invoice.company_id, payor_id=invoice.payor_id) \
				.first()

			if not relationship or relationship.approved_at is None:
				return errors.Error("payor is not approved")

			payor_users = models_util.get_active_users(company_id=invoice.payor_id, session=session)
			if not payor_users:
				return errors.Error("no users configured for this payor")
	except Exception as e:
		logging.exception("caught exception while checking if invoice is ready for approval")
		return errors.Error(str(e))
	return None

@errors.return_error_tuple
def handle_invoice_approval_request(
	session_maker: Callable,
	sendgrid_client: sendgrid_util.Client,
	invoice_id: str
	) -> Tuple[bool, errors.Error]:
	err = is_invoice_ready_for_approval(session_maker, invoice_id)
	if err:
		raise err

	info = models.TwoFactorFormInfoDict(
		type=db_constants.TwoFactorLinkType.CONFIRM_INVOICE,
		payload={'invoice_id': invoice_id},
	)

	payload = sendgrid_util.TwoFactorPayloadDict(
		form_info=info,
		expires_at=date_util.hours_from_today(24 * 14)
	)

	try:
		with models.session_scope(session_maker) as session:
			invoice = session.query(models.Invoice).get(invoice_id)
			customer = session.query(models.Company).get(invoice.company_id)
			payor = session.query(models.Company).get(invoice.payor_id)

			payor_users, err = partnership_util.get_partner_contacts_using_company_ids(
				customer_company_id=str(invoice.company_id),
				partner_company_id=str(invoice.payor_id),
				partnership_type=db_constants.CompanyType.Payor,
				session=session)

			if err:
				raise err

			if not payor_users:
				raise errors.Error('No payor users to send a notification to for invoice approval')

			emails = [u['email'] for u in payor_users]

			template_data = {
				'payor_name': payor.get_display_name(),
				'customer_name': customer.get_display_name(),
			}

			_, err = sendgrid_client.send(
				sendgrid_util.TemplateNames.PAYOR_TO_APPROVE_INVOICE,
				template_data,
				emails,
				two_factor_payload=payload,
			)
			if err:
				raise err

			invoice.status = db_constants.RequestStatusEnum.APPROVAL_REQUESTED
			invoice.requested_at = date_util.now()
	except errors.Error as e:
		raise e
	except Exception as e:
		logging.exception("Caught exception while sending approval notification email")
		raise errors.Error(str(e))

	return True, None

@errors.return_error_tuple
def send_one_notification_for_payment(
	session: Session,
	client: sendgrid_util.Client,
	invoice: models.Invoice,
	customer: models.Company) -> Tuple[bool, errors.Error]:

	info = models.TwoFactorFormInfoDict(
		type=db_constants.TwoFactorLinkType.PAY_INVOICE,
		payload={'invoice_id': str(invoice.id)}
	)

	payload = sendgrid_util.TwoFactorPayloadDict(
		form_info=info,
		expires_at=date_util.hours_from_today(24 * 14)
	)

	template_data = {
		'payor_name': invoice.payor.get_display_name(),
		'customer_name': customer.get_display_name(),
	}

	payor_users, err = partnership_util.get_partner_contacts_using_company_ids(
		customer_company_id=str(customer.id),
		partner_company_id=str(invoice.payor.id),
		partnership_type=db_constants.CompanyType.Payor,
		session=session)

	if err:
		raise err

	if not payor_users:
		raise errors.Error('No payor users to send a notification to for payment')

	emails = [u['email'] for u in payor_users if u['email']]

	if len(emails):
		_, err = client.send(
			sendgrid_util.TemplateNames.PAYOR_TO_PAY_INVOICE,
			template_data,
			emails,
			two_factor_payload=payload,
		)
		if err:
			raise err

	# Update the payment_requested_at timestamp
	invoice.payment_requested_at = date_util.now()

	return True, None

@errors.return_error_tuple
def submit_invoices_for_payment(
	session_maker: Callable,
	client: sendgrid_util.Client,
	request: SubmitForPaymentRequest
) -> Tuple[bool, errors.Error]:
	# Ensure that all of the invoices belong to the given company
	with models.session_scope(session_maker) as session:
		invoices = session.query(models.Invoice) \
			.filter(models.Invoice.id.in_(request.invoice_ids)) \
			.all()

		if len(invoices) != len(request.invoice_ids):
			raise errors.Error(
				"the number of retrieved invoices did not match the number of ids given")

		# We also make sure that all of the invoices have an approved_at timestamp set
		not_approved = [invoice for invoice in invoices if not invoice.approved_at]
		if len(not_approved):
			raise errors.Error(f"{len(not_approved)} of the given invoices not yet approved")

		# All good. For each invoice, send an email to the payor
		for invoice in invoices:
			customer = session.query(models.Company).get(invoice.company_id)

			success, err = send_one_notification_for_payment(session, client, invoice, customer)
			if err:
				raise err

	return True, None

@errors.return_error_tuple
def submit_new_invoice_for_payment(
	session_maker: Callable,
	client: sendgrid_util.Client,
	invoice_id: str,
) -> Tuple[bool, errors.Error]:
	# Ensure that all of the invoices belong to the given company
	with models.session_scope(session_maker) as session:
		invoice = session.query(models.Invoice) \
			.filter(models.Invoice.id == invoice_id) \
			.first()

		if not invoice:
			raise errors.Error("Invoice not found")

		# Approve invoice before it is sent out to payor for payment
		invoice.status = db_constants.RequestStatusEnum.APPROVED

		customer = session.query(models.Company).get(invoice.company_id)
		success, err = send_one_notification_for_payment(session, client, invoice, customer)
		if err:
			raise err

	return True, None

@errors.return_error_tuple
def respond_to_payment_request(
	session: Session,
	client: sendgrid_util.Client,
	email: str,
	data: InvoicePaymentRequestResponse) -> Tuple[bool, errors.Error]:

	user = session.query(models.User).filter(
		models.User.email == email.lower()
	).first()
	if not user:
		raise errors.Error("could not find user")

	invoice = session.query(models.Invoice).get(data.invoice_id)

	if invoice.payor_id != user.company_id:
		raise errors.Error("user cannot approve this invoice")

	# Do we need to email folks when a payment is rejected?
	if data.new_status == db_constants.RequestStatusEnum.REJECTED:
		invoice.payment_rejected_at = date_util.now()
		invoice.payment_rejection_note = data.rejection_note
		return None

	invoice.payment_confirmed_at = date_util.now()

	payment = payment_util.create_repayment_payment(
		company_id=str(invoice.company_id),
		payment_type=db_constants.PaymentType.REPAYMENT,
		payment_input=payment_types.RepaymentPaymentInputDict(
			payment_method=str(data.payment_method),
			requested_amount=data.amount,
			requested_payment_date=data.anticipated_payment_date,
			payment_date=data.anticipated_payment_date,
			items_covered=payment_types.PaymentItemsCoveredDict(
				invoice_ids=[str(invoice.id)]
			),
			company_bank_account_id=None,
			customer_note='',
			bank_note=None
		),
		created_by_user_id=str(user.id))

	session.add(payment)
	session.commit()
	session.refresh(payment)

	invoice.payment_id = payment.id
	return True, None


#
#    Licensed under the Apache License, Version 2.0 (the "License"); you may
#    not use this file except in compliance with the License. You may obtain
#    a copy of the License at
#
#         http://www.apache.org/licenses/LICENSE-2.0
#
#    Unless required by applicable law or agreed to in writing, software
#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
#    License for the specific language governing permissions and limitations
#    under the License.

from urllib.parse import urlparse

from ironic_lib import metrics_utils
from oslo_log import log
from oslo_utils import importutils
from oslo_utils import timeutils

from ironic.common import exception
from ironic.common.i18n import _
from ironic.common import states
from ironic.conductor import periodics
from ironic.conductor import utils as manager_utils
from ironic.conf import CONF
from ironic.drivers import base
from ironic.drivers.modules import deploy_utils
from ironic.drivers.modules.redfish import firmware_utils
from ironic.drivers.modules.redfish import utils as redfish_utils
from ironic import objects

LOG = log.getLogger(__name__)

METRICS = metrics_utils.get_metrics_logger(__name__)

sushy = importutils.try_import('sushy')

class RedfishFirmware(base.FirmwareInterface):

    _FW_SETTINGS_ARGSINFO = {
        'settings': {
            'description': (
                'A list of dicts with firmware components to be updated'
            ),
            'required': True
        }
    }

    def __init__(self):
        super(RedfishFirmware, self).__init__()
        if sushy is None:
            raise exception.DriverLoadError(
                driver='redfish',
                reason=_("Unable to import the sushy library"))

    def get_properties(self):
        """Return the properties of the interface.

        :returns: dictionary of <property name>:<property description> entries.
        """
        return redfish_utils.COMMON_PROPERTIES.copy()

    def validate(self, task):
        """Validates the driver information needed by the redfish driver.

        :param task: a TaskManager instance containing the node to act on.
        :raises: InvalidParameterValue on malformed parameter(s)
        :raises: MissingParameterValue on missing parameter(s)
        """
        redfish_utils.parse_driver_info(task.node)

    def cache_firmware_components(self, task):
        """Store or update Firmware Components on the given node.

        This method stores Firmware Components to the firmware_information
        table during 'cleaning' operation. It will also update the timestamp
        of each Firmware Component.

        :param task: a TaskManager instance.
        :raises: UnsupportedDriverExtension, if the node's driver doesn't
            support getting Firmware Components from bare metal.
        """

        node_id = task.node.id
        settings = []
        # NOTE(iurygregory): currently we will only retrieve BIOS and BMC
        # firmware information trough the redfish system and manager.

        system = redfish_utils.get_system(task.node)

        bios_fw = {'component': 'bios',
                   'current_version': system.bios_version}
        settings.append(bios_fw)

        # NOTE(iurygregory): normally we only relay on the System to
        # perform actions, but to retrieve the BMC Firmware we need to
        # access the Manager.
        try:
            manager = redfish_utils.get_manager(task.node, system)
            bmc_fw = {'component': 'bmc',
                      'current_version': manager.firmware_version}
            settings.append(bmc_fw)
        except exception.RedfishError:
            LOG.warning('No manager available to retrieve Firmware '
                        'from the bmc of node %s', task.node.uuid)

        if not settings:
            error_msg = (_('Cannot retrieve firmware for node %s.')
                         % task.node.uuid)
            LOG.error(error_msg)
            raise exception.UnsupportedDriverExtension(error_msg)

        create_list, update_list, nochange_list = (
            objects.FirmwareComponentList.sync_firmware_components(
                task.context, node_id, settings))

        if create_list:
            for new_fw in create_list:
                new_fw_cmp = objects.FirmwareComponent(
                    task.context,
                    node_id=node_id,
                    component=new_fw['component'],
                    current_version=new_fw['current_version']
                )
                new_fw_cmp.create()
        if update_list:
            for up_fw in update_list:
                up_fw_cmp = objects.FirmwareComponent.get(
                    task.context,
                    node_id=node_id,
                    name=up_fw['component']
                )
                up_fw_cmp.last_version_flashed = up_fw.get('current_version')
                up_fw_cmp.current_version = up_fw.get('current_version')
                up_fw_cmp.save()

    @METRICS.timer('RedfishFirmware.update')
    @base.deploy_step(priority=0, argsinfo=_FW_SETTINGS_ARGSINFO)
    @base.clean_step(priority=0, abortable=False,
                     argsinfo=_FW_SETTINGS_ARGSINFO,
                     requires_ramdisk=True)
    @base.cache_firmware_components
    def update(self, task, settings):
        """Update the Firmware on the node using the settings for components.

        :param task: a TaskManager instance.
        :param settings: a list of dictionaries, each dictionary contains the
            component name and the url that will be used to update the
            firmware.
        :raises: UnsupportedDriverExtension, if the node's driver doesn't
            support update via the interface.
        :raises: InvalidParameterValue, if validation of the settings fails.
        :raises: MissingParamterValue, if some required parameters are
            missing.
        :returns: states.CLEANWAIT if Firmware update with the settings is in
            progress asynchronously of None if it is complete.
        """
        node = task.node

        update_service = redfish_utils.get_update_service(node)

        LOG.debug('Updating Firmware on node %(node_uuid)s with settings '
                  '%(settings)s',
                  {'node_uuid': node.uuid, 'settings': settings})

        self._execute_firmware_update(node, update_service, settings)

        fw_upd = settings[0]
        wait_interval = fw_upd.get('wait')

        deploy_utils.set_async_step_flags(
            node,
            reboot=True,
            skip_current_step=True,
            polling=True
        )

        return deploy_utils.reboot_to_finish_step(task, timeout=wait_interval)

    def _execute_firmware_update(self, node, update_service, settings):
        """Executes the next firmware update to the node

        Executes the first firmware update in the settings list to the node.

        :param node: the node that will have a firmware update executed.
        :param update_service: the sushy firmware update service.
        :param settings: remaining settings for firmware update that needs
            to be executed.
        """
        fw_upd = settings[0]
        component_url, cleanup = self._stage_firmware_file(node, fw_upd)

        LOG.debug('Applying new firmware %(url)s for %(component)s on node '
                  '%(node_uuid)s',
                  {'url': fw_upd['url'], 'component': fw_upd['component'],
                   'node_uuid': node.uuid})

        task_monitor = update_service.simple_update(component_url)

        fw_upd['task_monitor'] = task_monitor.task_monitor_uri
        node.set_driver_internal_info('redfish_fw_updates', settings)

        if cleanup:
            fw_clean = node.driver_internal_info.get('firmware_cleanup')
            if not fw_clean:
                fw_clean = [cleanup]
            elif cleanup not in fw_clean:
                fw_clean.append(cleanup)
            node.set_driver_internal_info('firmware_cleanup', fw_clean)

    def _continue_updates(self, task, update_service, settings):
        """Continues processing the firmware updates

        Continues to process the firmware updates on the node.

        Note that the caller must have an exclusive lock on the node.

        :param task: a TaskManager instance containing the node to act on.
        :param update_service: the sushy firmware update service
        :param settings: the remaining firmware updates to apply
        """
        node = task.node
        fw_upd = settings[0]
        wait_interval = fw_upd.get('wait')
        if wait_interval:
            time_now = str(timeutils.utcnow().isoformat())
            fw_upd['wait_start_time'] = time_now

            LOG.debug('Waiting at %(time)s for %(seconds)s seconds after '
                      '%(component)s firmware update %(url)s '
                      'on node %(node)s',
                      {'time': time_now,
                       'seconds': wait_interval,
                       'component': fw_upd['component'],
                       'url': fw_upd['url'],
                       'node': node.uuid})

            node.set_driver_internal_info('redfish_fw_updates', settings)
            node.save()
            return

        if len(settings) == 1:
            self._clear_updates(node)

            LOG.info('Firmware updates completed for node %(node)s',
                     {'node': node.uuid})

            manager_utils.notify_conductor_resume_clean(task)
        else:
            settings.pop(0)
            self._execute_firmware_update(node,
                                          update_service,
                                          settings)
            node.save()
            manager_utils.node_power_action(task, states.REBOOT)

    def _clear_updates(self, node):
        """Clears firmware updates artifacts

        Clears firmware updates from driver_internal_info and any files
        that were staged.

        Note that the caller must have an exclusive lock on the node.

        :param node: the node to clear the firmware updates from
        """
        firmware_utils.cleanup(node)
        node.del_driver_internal_info('redfish_fw_updates')
        node.del_driver_internal_info('firmware_cleanup')
        node.save()

    @METRICS.timer('RedfishFirmware._query_update_failed')
    @periodics.node_periodic(
        purpose='checking if async update of firmware component failed',
        spacing=CONF.redfish.firmware_update_fail_interval,
        filters={'reserved': False, 'provision_state': states.CLEANFAIL,
                 'maintenance': True},
        predicate_extra_fields=['driver_internal_info'],
        predicate=lambda n: n.driver_internal_info.get('redfish_fw_updates'),
    )
    def _query_update_failed(self, task, manager, context):

        """Periodic job to check for failed firmware updates."""
        # A firmware update failed. Discard any remaining firmware
        # updates so when the user takes the node out of
        # maintenance mode, pending firmware updates do not
        # automatically continue.
        LOG.error('Update firmware failed for node %(node)s. '
                  'Discarding remaining firmware updates.',
                  {'node': task.node.uuid})

        task.upgrade_lock()
        self._clear_updates(task.node)

    @METRICS.timer('RedfishFirmware._query_update_status')
    @periodics.node_periodic(
        purpose='checking async update of firmware component',
        spacing=CONF.redfish.firmware_update_fail_interval,
        filters={'reserved': False, 'provision_state': states.CLEANWAIT},
        predicate_extra_fields=['driver_internal_info'],
        predicate=lambda n: n.driver_internal_info.get('redfish_fw_updates'),
    )
    def _query_update_status(self, task, manager, context):
        """Periodic job to check firmware update tasks."""
        self._check_node_redfish_firmware_update(task)

    @METRICS.timer('RedfishFirmware._check_node_redfish_firmware_update')
    def _check_node_redfish_firmware_update(self, task):
        """Check the progress of running firmware update on a node."""

        node = task.node

        settings = node.driver_internal_info['redfish_fw_updates']
        current_update = settings[0]

        try:
            update_service = redfish_utils.get_update_service(node)
        except exception.RedfishConnectionError as e:
            # If the BMC firmware is being updated, the BMC will be
            # unavailable for some amount of time.
            LOG.warning('Unable to communicate with firmware update service '
                        'on node %(node)s. Will try again on the next poll. '
                        'Error: %(error)s',
                        {'node': node.uuid,
                         'error': e})
            return

        wait_start_time = current_update.get('wait_start_time')
        if wait_start_time:
            wait_start = timeutils.parse_isotime(wait_start_time)

            elapsed_time = timeutils.utcnow(True) - wait_start
            if elapsed_time.seconds >= current_update['wait']:
                LOG.debug('Finished waiting after firmware update '
                          '%(firmware_image)s on node %(node)s. '
                          'Elapsed time: %(seconds)s seconds',
                          {'firmware_image': current_update['url'],
                           'node': node.uuid,
                           'seconds': elapsed_time.seconds})
                current_update.pop('wait', None)
                current_update.pop('wait_start_time', None)

                self._continue_updates(task, update_service, settings)
            else:
                LOG.debug('Continuing to wait after firmware update '
                          '%(firmware_image)s on node %(node)s. '
                          'Elapsed time: %(seconds)s seconds',
                          {'firmware_image': current_update['url'],
                           'node': node.uuid,
                           'seconds': elapsed_time.seconds})

            return

        try:
            task_monitor = redfish_utils.get_task_monitor(
                node, current_update['task_monitor'])
        except exception.RedfishError:
            # The BMC deleted the Task before we could query it
            LOG.warning('Firmware update completed for node %(node)s, '
                        'firmware %(firmware_image)s, but success of the '
                        'update is unknown.  Assuming update was successful.',
                        {'node': node.uuid,
                         'firmware_image': current_update['url']})
            self._continue_updates(task, update_service, settings)
            return

        if not task_monitor.is_processing:
            # The last response does not necessarily contain a Task,
            # so get it
            sushy_task = task_monitor.get_task()

            # Only parse the messages if the BMC did not return parsed
            # messages
            messages = []
            if sushy_task.messages and not sushy_task.messages[0].message:
                sushy_task.parse_messages()

            if sushy_task.messages is not None:
                messages = [m.message for m in sushy_task.messages]

            task.upgrade_lock()
            if (sushy_task.task_state == sushy.TASK_STATE_COMPLETED
                    and sushy_task.task_status in
                    [sushy.HEALTH_OK, sushy.HEALTH_WARNING]):
                LOG.info('Firmware update succeeded for node %(node)s, '
                         'firmware %(firmware_image)s: %(messages)s',
                         {'node': node.uuid,
                          'firmware_image': current_update['url'],
                          'messages': ", ".join(messages)})

                self._continue_updates(task, update_service, settings)
            else:
                error_msg = (_('Firmware update failed for node %(node)s, '
                               'firmware %(firmware_image)s. '
                               'Error: %(errors)s') %
                             {'node': node.uuid,
                              'firmware_image': current_update['url'],
                              'errors': ",  ".join(messages)})

                self._clear_updates(node)
                if task.node.clean_step:
                    manager_utils.cleaning_error_handler(task, error_msg)
                else:
                    manager_utils.deploying_error_handler(task, error_msg)

        else:
            LOG.debug('Firmware update in progress for node %(node)s, '
                      'firmware %(firmware_image)s.',
                      {'node': node.uuid,
                       'firmware_image': current_update['url']})

    def _stage_firmware_file(self, node, component_update):

        try:
            url = component_update['url']
            name = component_update['component']
            parsed_url = urlparse(url)
            scheme = parsed_url.scheme.lower()
            source = (CONF.redfish.firmware_source).lower()

            # Keep it simple, in further processing TLS does not matter
            if scheme == 'https':
                scheme = 'http'

            # If source and scheme is HTTP, then no staging,
            # returning original location
            if scheme == 'http' and source == scheme:
                LOG.debug('For node %(node)s serving firmware for '
                          '%(component)s from original location %(url)s',
                          {'node': node.uuid, 'component': name, 'url': url})
                return url, None

            # If source and scheme is Swift, then not moving, but
            # returning Swift temp URL
            if scheme == 'swift' and source == scheme:
                temp_url = firmware_utils.get_swift_temp_url(parsed_url)
                LOG.debug('For node %(node)s serving original firmware at '
                          'for %(component)s at %(url)s via Swift temporary '
                          'url %(temp_url)s',
                          {'node': node.uuid, 'component': name, 'url': url,
                           'temp_url': temp_url})
                return temp_url, None

            # For remaining, download the image to temporary location
            temp_file = firmware_utils.download_to_temp(node, url)

            return firmware_utils.stage(node, source, temp_file)

        except exception.IronicException:
            firmware_utils.cleanup(node)


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

import argparse
import config
import dbtools
import instance

def main():
    parser = argparse.ArgumentParser()
    parser.add_argument('--config',
                        metavar="CONFIG",
                        type=str,
                        default="conf/db_default.json",
                        help='Path to configuration file')
    parser.add_argument('--threads',
                        metavar="THREADS",
                        type=int,
                        default=1,
                        help='Number of threads to be launched')
    args = parser.parse_args()

    conf = config.Config(args.config)
    db = dbtools.Database(conf)
    db.update_all(args.threads)
    db.write()

if __name__ == "__main__":
    main()


import logging

from alib import datamodel

class ExtendedGraph(datamodel.Graph):
    """ Initial extended graph construction for chain requests. Deferred. It serves no real purpose anymore.
    """

    def get_node_name(self, u, ve):
        vtail, vhead = ve
        return (u, vtail, vhead)

    def get_edge_name(self, se, ve):
        stail, shead = se
        vtail, vhead = ve
        return (stail, shead, vtail, vhead)

    def __init__(self, request, substrate):
        logger = logging.getLogger(__name__)
        logger.debug('init extended graph with substrate %s', substrate)

        super(ExtendedGraph, self).__init__(request.name)

        self.super_source = "supersource_{}".format(request.name)
        self.super_sink = "supersink_{}".format(request.name)

        # source and sink
        self.add_node(self.super_source, origin=None, layer_index=0)
        self.add_node(self.super_sink, origin=None, layer_index=len(request.edges) + 1)
        self.nodes_of_ve = {}
        # for each edges ve in request generate unique duplicate of substrate
        # graph
        for index, req_node in enumerate(request.sequence):
            nodelist = []
            if req_node == request.sequence[-1]:
                break
            if len(request.out_edges[req_node]) != 1:
                raise ValueError("Too many or too few edges")
            ve = request.out_edges[req_node][0]
            for u in substrate.nodes:
                node_name = self.get_node_name(u, ve)
                self.add_node(node_name, origin=u, layer_index=index + 1)
                nodelist.append(node_name)

            for se in substrate.edges:
                self.add_intra_edge(self.get_node_name(se[0], ve), self.get_node_name(se[1], ve), origin=(se, ve))
            self.nodes_of_ve[ve] = nodelist

        # function mapping : 1. supersource 2. internal functions 3. supersink
        # 1. supersource
        start = request.sequence[0]
        start_type = request.get_type(start)
        for u in substrate.get_nodes_by_type(start_type):
            start_allowed_nodes = request.get_allowed_nodes(start)
            if start_allowed_nodes is None or u in start_allowed_nodes:
                self.add_inter_edge(self.super_source,
                                    self.get_node_name(u, request.get_out_edge(start)),
                                    origin=(start_type, u, start))

        # 2. between nodes
        if len(request.sequence) > 2:
            for req_index in xrange(0, len(request.sequence) - 2):
                current = request.sequence[req_index]
                nextnode = request.sequence[req_index + 1]
                nextnode_type = request.get_type(nextnode)
                for u in substrate.get_nodes_by_type(nextnode_type):
                    nextnode_allowed_nodes = request.get_allowed_nodes(nextnode)
                    if nextnode_allowed_nodes is None or u in nextnode_allowed_nodes:
                        self.add_inter_edge(self.get_node_name(u, request.get_out_edge(current)),
                                            self.get_node_name(u, request.get_out_edge(nextnode)),
                                            origin=(nextnode_type, u, nextnode))
        # 3. supersink
        end = request.sequence[len(request.sequence) - 1]
        before_end = request.sequence[len(request.sequence) - 2]
        end_type = request.get_type(end)
        for u in substrate.get_nodes_by_type(end_type):
            end_allowed_nodes = request.get_allowed_nodes(end)
            if end_allowed_nodes is None or u in end_allowed_nodes:
                self.add_inter_edge(self.get_node_name(u, request.get_out_edge(before_end)),
                                    self.super_sink,
                                    origin=(end_type, u, end))

    def add_node(self, u, origin, layer_index):
        super(ExtendedGraph, self).add_node(u, origin=origin, layer_index=layer_index)

    def add_intra_edge(self, tail, head, origin):
        super(self.__class__, self).add_edge(tail, head, edge_origin=origin)

    def add_inter_edge(self, tail, head, origin):
        super(self.__class__, self).add_edge(tail, head, node_origin=origin)

    def __str__(self):
        return super(ExtendedGraph, self).__str__()


from flask import Flask, jsonify, request
from flask_restx import Api, Resource, fields
from flask_sqlalchemy import SQLAlchemy
import os
from datetime import datetime

basedir = os.path.dirname(os.path.realpath(__file__))

app = Flask(__name__)

app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///' + os.path.join(basedir, 'cars.db')
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
app.config['SQLALCHEMY_ECHO'] = True

api = Api(app,
          doc='/',
          title='An API for cars sellers',
          description='A simple REST API for cars sellers')

db = SQLAlchemy(app)

class Car(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    veiculo = db.Column(db.String(40), nullable=False)
    marca = db.Column(db.String(40), nullable=False)
    ano = db.Column(db.Integer, nullable=False)
    cor = db.Column(db.String(10), nullable=False)
    descricao = db.Column(db.String(255))
    vendido = db.Column(db.Boolean, default=False, nullable=False)
    created = db.Column(db.DateTime(), nullable=False)
    updated = db.Column(db.DateTime(), nullable=False)

    def __repr__(self):
        return self.veiculo

car_model = api.model(
    'Car',
    {
        'id': fields.Integer,
        'veiculo': fields.String,
        'marca': fields.String,
        'ano': fields.Integer,
        'cor': fields.String,
        'descricao': fields.String,
        'vendido': fields.Boolean
    }
)

car_model_for_update_or_post = api.model(
    'Car',
    {
        'veiculo': fields.String,
        'marca': fields.String,
        'ano': fields.Integer,
        'cor': fields.String,
        'descricao': fields.String,
        'vendido': fields.Boolean
    }
)

car_model_detailed = api.model(
    'Car',
    {
        'id': fields.Integer,
        'veiculo': fields.String,
        'marca': fields.String,
        'ano': fields.Integer,
        'cor': fields.String,
        'descricao': fields.String,
        'vendido': fields.Boolean,
        'created': fields.DateTime,
        'updated': fields.DateTime
    }
)

@api.route('/veiculos')
class Cars(Resource):

    @api.marshal_list_with(car_model, code = 200, envelope = "Veiculos")
    def get(self):
        """
        Caso sem filtro retorna todos os veículos, com filtro retorna de acordo com os parametros
        """
        marca = request.args.get('marca')
        ano = request.args.get('ano')
        cor = request.args.get('cor')

        if marca == None and ano == None and cor == None:
            cars = db.session.execute(
                db.select(db.text('*'))
                .where(Car.marca == marca, Car.ano == ano, Car.cor == cor)).all()

        else:
            cars = Car.query.all()

        return cars, 200

    @api.marshal_with(car_model, code = 201, envelope = "Veiculo")
    @api.doc(body = car_model_for_update_or_post)
    def post(self):
        """
        Adiciona um novo veículo
        """
        data = request.get_json()

        veiculo = data.get('veiculo')
        marca = data.get('marca')
        ano = data.get('ano')
        cor = data.get('cor')
        descricao = data.get('descricao')
        vendido = data.get('vendido')
        created = datetime.now()

        new_car = Car(veiculo = veiculo,
                       marca = marca,
                       ano = ano,
                       cor = cor,
                       descricao = descricao,
                       vendido = vendido,
                       created = created,
                       updated = created)

        db.session.add(new_car)
        db.session.commit()

        return new_car, 201

@api.route('/veiculos/<int:id>')
class CarsResource(Resource):

    @api.marshal_with(car_model_detailed, code = 200, envelope = "veiculo")
    def get(self, id):
        """
        Retorna os detalhes do veículo
        """
        veiculo = Car.query.get_or_404(id)

        return veiculo

    @api.marshal_with(car_model, code = 200, envelope = "veiculo")
    @api.expect(car_model_for_update_or_post)
    def put(self, id):
        """
        Atualiza os dados de um veículo
        """
        car_to_update = Car.query.get_or_404(id)

        data = request.get_json()

        car_to_update.veiculo = data.get('veiculo')
        car_to_update.marca = data.get('marca')
        car_to_update.ano = data.get('ano')
        car_to_update.cor = data.get('cor')
        car_to_update.descricao = data.get('descricao')
        car_to_update.vendido = data.get('vendido')
        car_to_update.updated = datetime.now()

        db.session.commit()

        return car_to_update, 200

    @api.marshal_with(car_model, code = 200, envelope = "veiculo")
    @api.expect(car_model_for_update_or_post)
    def patch(self, id):
        """
        Atualiza apenas alguns dados do veículo
        """
        car_to_update = Car.query.get_or_404(id)

        data = request.get_json()

        car_to_update.veiculo = data.get('veiculo') if data.get('veiculo') != None else car_to_update.veiculo
        car_to_update.marca = data.get('marca') if data.get('marca') != None else car_to_update.marca
        car_to_update.ano = data.get('ano') if data.get('ano') != None else car_to_update.ano
        car_to_update.cor = data.get('cor') if data.get('cor') != None else car_to_update.cor
        car_to_update.descricao = data.get('descricao') if data.get('descricao') != None else car_to_update.descricao
        car_to_update.vendido = data.get('vendido') if data.get('vendido') != None else car_to_update.vendido
        car_to_update.updated = datetime.now()

        db.session.commit()

        return car_to_update, 200

    @api.marshal_with(car_model, code = 200, envelope = "carro deletado")
    def delete(self, id):
        """
        Apaga o veículo.
        """
        car_to_delete = Car.query.get_or_404(id)

        db.session.delete(car_to_delete)

        db.session.commit()

        return car_to_delete, 200

@app.shell_context_processor
def make_shell_context():
    return {
        'db': db,
        'Car': Car
    }

if __name__ == '__main__':
    assert os.path.exists('.env')
    os.environ['FLASK_ENV'] = 'development'
    app.run(debug = True)


# -*- coding: utf-8 -*-

from datetime import date
from odoo import models, fields, api, _
from odoo.exceptions import UserError

class ChangeManagement(models.Model):
    _name = 'change.management.request'
    _description = 'Change Management Request'
    _inherit = ['portal.mixin', 'mail.activity.mixin', 'mail.thread']
    _order = 'create_date DESC'

    state = fields.Selection([
        ('draft', 'New'),
        ('submit', 'Submitted'),
        ('change_coordinator_approved', 'Change Coordinator Approved'),
        ('grc_analyst_approved', 'GRC Analyst Approved'),
        ('service_delivery_approved', 'Service Delivery Approved'),
        ('director_operation_approved', 'Director of Operations Approved'),
        ('approved', 'SDM Implementation Approved'),
        ('reject', 'Rejected'),
        ], string='Approval Status', readonly=False, index=True, copy=False, default='draft', tracking=True)

    ref = fields.Char(string='Order Reference', readonly=True, required=True, index=True, copy=False, default='New')

    name = fields.Char(string='Summary', required=True, copy=False)
    change_type = fields.Char(string='Change Type')

    change_category = fields.Selection([
        ('onboarding', 'Onboarding'),
        ('decommissioning', 'Decommissioning'),
        ('cross_connect', 'Cross-Connect'),
        ('others', 'Others'),
        ], string='Change Category', copy=False, tracking=True)

    partner_id = fields.Many2one(comodel_name="res.partner", string='Client')
    actual_start_date = fields.Date(string='Actual Start Date')
    actual_end_date = fields.Date(string='Actual End Date')
    closure_date_time = fields.Datetime(string='Closure date and time')
    status = fields.Selection([
        ('draft', 'New'),
        ('request_for_authorization', 'Request For Authorization'),
        ('planning_in_progress', 'Planning In Progress'),
        ('cancelled', 'Cancelled'),
        ], string='Status', copy=False, default='draft', tracking=True)

    change_coordinator = fields.Many2one(comodel_name="res.users", string='Change Coordinator')
    change_manager = fields.Many2one(comodel_name="res.users", string='Change Manager')

    submit_date = fields.Date(string='Submit Date', default=date.today())

    coordinator_group = fields.Char(string='Coordinator Group')
    service = fields.Char(string='Service')
    summary = fields.Char(string='Summary')
    change_class = fields.Char(string='Class')
    change_reason = fields.Char(string='Change Reason')
    target_date = fields.Date(string='Target date')
    impact = fields.Selection([
        ('minor', 'Minor/Localized'),
        ('major', 'Major'),
        ], string='Impact', tracking=True)
    urgency = fields.Selection([
        ('low', 'Low'),
        ('mid', 'Medium'),
        ('high', 'Hign'),
        ], string='Urgency', tracking=True)
    priority = fields.Selection([
        ('0', 'Nil'),
        ('1', 'Low'),
        ('2', 'Medium'),
        ('3', 'High'),
        ('4', 'Critical'),
        ], string='Priority', default='1', tracking=True)

    change_type = fields.Selection([
        ('minor', 'Minor'),
        ('major', 'Major'),
        ], string='Change Type', default='minor', tracking=True, compute='_compute_change_type')

    risk_level = fields.Char(string='Risk Level')
    manager_group = fields.Char(string='Manager Group')

    scheduled_start_date = fields.Date(string='Scheduled Start Date & Time')
    scheduled_end_date = fields.Date(string='Scheduled End Date & time')

    scope_and_impact = fields.Text(string='Scope and Impact of Change')
    docs_impacted = fields.Char(string='Documents Impacted')

    controls_required = fields.Text(string='Controls required')
    financial_impact = fields.Text(string='Financial Impact')
    risk_assessment = fields.Text(string='Risk assessment')

    #Business Justification for the Proposed Change
    business_case_benefits = fields.Text(string='Business Case / Benefits')
    technical_case = fields.Text(string='Technical case, for and against the Change')
    estimated_cost_resources_required = fields.Text(string='Estimated Cost / Resources required for the change.')

    def _compute_change_type(self):
        for type in self:
            if type.priority == '0' or type.priority == '1' or type.priority == '2':
                type.change_type = 'minor'
            else:
                type.change_type = 'major'

    additional_info = fields.Char(string='Additional Information')

    tasks_line_ids = fields.One2many('change.management.tasks', 'change_management_id', string="Tasks", copy=True)

    #Approvals
    change_coordinator_id = fields.Many2one(comodel_name="res.users", string='Change Coordinator', readonly=True)
    change_coordinator_approval_date = fields.Date(string='Change Coordinator Approval Date', readonly=True)

    grc_analyst_id = fields.Many2one(comodel_name="res.users", string='GRC Analyst', readonly=True)
    grc_approval_date = fields.Date(string='GRC Approval Date', readonly=True)

    service_delivery_manager_id = fields.Many2one(comodel_name="res.users", string='Service Delivery Manger', readonly=True)
    service_delivery_approval_date = fields.Date(string='Service Delivery Manger Approval Date', readonly=True)

    director_operation_id = fields.Many2one(comodel_name="res.users", string='Director of Operations', readonly=True)
    director_operation_approval_date = fields.Date(string='Director of Operations Approval Date', readonly=True)

    @api.model
    def create(self, vals):
        if vals.get('ref', 'New') == 'New':
            vals['ref'] = self.env['ir.sequence'].next_by_code('change.management') or '/'
        res = super(ChangeManagement, self).create(vals)
        res.action_alert_manager()
        return res

    #alerts cc
    def action_alert_manager(self):
        group_id = self.env['ir.model.data'].xmlid_to_object('rc_service.group_ccm')
        partner_ids = []
        for user in group_id.users:
            partner_ids.append(user.partner_id.id)
        self.message_subscribe(partner_ids=partner_ids)
        subject = "A new Change Management Request '{}' with reference '{}', needs review".format(self.name, self.ref)
        self.message_post(subject=subject,body=subject,partner_ids=partner_ids)

    #submit to change coordinator
    def button_submit(self):
        self.write({'state': 'submit'})
        #self.submit_date = date.today()
        partner_ids = []
        if not self.change_coordinator:
            raise UserError(_('Please Specify the Change Coordinator.'))
        partner_ids.append(self.change_coordinator.partner_id.id)
        self.message_subscribe(partner_ids=partner_ids)
        subject = "Change Management Request '{}', needs approval".format(self.ref)
        self.message_post(subject=subject,body=subject,partner_ids=partner_ids)

    #submit to GRC Analyst
    def button_change_coordinator_approve(self):
        self.write({'state': 'change_coordinator_approved'})
        self.change_coordinator_id = self.env.uid
        self.change_coordinator_approval_date = date.today()
        group_id = self.env['ir.model.data'].xmlid_to_object('rc_service.group_grc')
        partner_ids = []
        for user in group_id.users:
            partner_ids.append(user.partner_id.id)
        self.message_subscribe(partner_ids=partner_ids)
        subject = "Change Management Request '{}', has been approved by change coordinator and needs your approval".format(self.ref)
        self.message_post(subject=subject,body=subject,partner_ids=partner_ids)

    #submit to Service Delivery Manger
    def button_grc_analyst_approve(self):
        self.write({'state': 'grc_analyst_approved'})
        self.grc_analyst_id = self.env.uid
        self.grc_approval_date = date.today()
        group_id = self.env['ir.model.data'].xmlid_to_object('rc_service.group_smd_manager')
        partner_ids = []
        for user in group_id.users:
            partner_ids.append(user.partner_id.id)
        self.message_subscribe(partner_ids=partner_ids)
        subject = "Change Management Request '{}', has been approved by GRC Analyst and needs your approval".format(self.ref)
        self.message_post(subject=subject,body=subject,partner_ids=partner_ids)

    #submit to Director of Operations
    def button_service_delivery_approve(self):
        self.write({'state': 'service_delivery_approved'})
        self.service_delivery_manager_id = self.env.uid
        self.service_delivery_approval_date = date.today()
        group_id = self.env['ir.model.data'].xmlid_to_object('rc_service.group_doo')
        partner_ids = []
        for user in group_id.users:
            partner_ids.append(user.partner_id.id)
        self.message_subscribe(partner_ids=partner_ids)
        subject = "Change Management Request '{}', has been approved by Service Delivery Manger and needs your approval".format(self.ref)
        self.message_post(subject=subject,body=subject,partner_ids=partner_ids)

    #approved by Director of Operations
    def button_director_operation_approve(self):
        self.write({'state': 'director_operation_approved'})
        self.director_operation_id = self.env.uid
        self.director_operation_approval_date = date.today()
        subject = "Change Management Request '{}', has been Approved by Director of Operations".format(self.name)
        partner_ids = []
        for partner in self.message_partner_ids:
            partner_ids.append(partner.id)
        self.message_post(subject=subject,body=subject,partner_ids=partner_ids)

    #approved by SDM
    def button_approve(self):
        self.write({'state': 'approved'})
        subject = "Change Management Request Implementation for '{}', has been Approved by SDM".format(self.name)
        partner_ids = []
        for partner in self.message_partner_ids:
            partner_ids.append(partner.id)
        self.message_post(subject=subject,body=subject,partner_ids=partner_ids)

    def button_reject(self):
        self.write({'state': 'reject'})
        subject = "Change Management Request '{}', has been rejected".format(self.name)
        partner_ids = []
        for partner in self.message_partner_ids:
            partner_ids.append(partner.id)
        self.message_post(subject=subject,body=subject,partner_ids=partner_ids)

    def button_reset(self):
        self.write({'state': 'new'})

class ChangeManagementTasks(models.Model):
    _name = 'change.management.tasks'
    _description = 'Change Management Tasks'
    _inherit = ['portal.mixin', 'mail.activity.mixin', 'mail.thread']
    _order = 'create_date DESC'

    change_management_id = fields.Many2one(comodel_name="change.management.request", string='change management')

    name = fields.Char(string='Task Name', required=True)
    summary = fields.Char(string='Task Summary')
    details = fields.Char(string='Task Details / Note')

    start_date = fields.Datetime(string='Scheduled Start Date & Time')
    end_date = fields.Datetime(string='Scheduled End Date & Time')


import numpy as np
from os import path as osp
from PIL import Image
import torch
from tqdm import tqdm
from multiprocessing import Pool
from functools import partial
import mmcv
from shapely.geometry import LineString
from .mean_ap import average_precision
from .tpfp import tpfp_gen, custom_tpfp_gen, custom_tpfp_gen_1
from sklearn.metrics import precision_score,f1_score,recall_score
from mmcv.utils import print_log
from terminaltables import AsciiTable
import json
import os
from scipy.spatial import cKDTree
from skimage import measure
from scipy.sparse.csgraph import dijkstra
import pickle
import multiprocessing
from functools import partial

def update_graph(start_vertex,end_vertex,graph,set_value=1):
    start_vertex = np.array([int(start_vertex[0]),int(start_vertex[1])])
    end_vertex = np.array([int(end_vertex[0]),int(end_vertex[1])])
    p = start_vertex
    d = end_vertex - start_vertex
    N = np.max(np.abs(d))
    graph[start_vertex[0],start_vertex[1]] = set_value
    graph[end_vertex[0],end_vertex[1]] = set_value
    if N:
        s = d / (N)
        for i in range(0,N):
            p = p + s
            graph[int(round(p[0])),int(round(p[1]))] = set_value
    return graph

def eval_metric(graph, mask, name, gt_data):
    def tuple2list(t):
        return [[t[0][x],t[1][x]] for x in range(len(t[0]))]

    graph = graph.cpu().detach().numpy().squeeze()
    gt_image = mask
    gt_points = tuple2list(np.where(gt_image!=0))
    graph_points = tuple2list(np.where(graph!=0))
    if not len(graph_points):
        return [0, 0, 0], [0, 0, 0], [0, 0, 0], 0, 0, 0, 1
    graph_acc = []
    graph_recall = []
    r_f = []
    apls = 0
    entropy_conn = 0
    naive_conn = 0
    gt_tree = cKDTree(gt_points)
    for c_i, thre in enumerate([2, 5, 10]):
        graph_acc_num = 0
        graph_recall_num = 0
        if len(graph_points):
            graph_tree = cKDTree(graph_points)
            dis_gt2graph,_ = graph_tree.query(gt_points, k=1)
            dis_graph2gt,_ = gt_tree.query(graph_points, k=1)
            graph_recall_num = len([x for x in dis_gt2graph if x<thre])/len(dis_gt2graph)
            graph_acc_num = len([x for x in dis_graph2gt if x<thre])/len(dis_graph2gt)
        if graph_acc_num*graph_recall_num:
            r_f_num = 2*graph_recall_num * graph_acc_num / (graph_acc_num+graph_recall_num)
        else:
            r_f_num = 0
        graph_acc.append(graph_acc_num)
        graph_recall.append(graph_recall_num)
        r_f.append(r_f_num)

    pre_image = graph
    gt_instance_map = measure.label(gt_image / 255,background=0)
    gt_instance_indexes = np.unique(gt_instance_map)[1:]
    gt_assigned_lengths = [[] for x in range(len(gt_instance_indexes))]
    gt_instance_length = []
    gt_instance_points = []
    gt_covered = []
    for index in gt_instance_indexes:
        instance_map = (gt_instance_map == index)
        instance_points = np.where(instance_map==1)
        instance_points = [[instance_points[0][i],instance_points[1][i]] for i in range(len(instance_points[0]))]
        gt_instance_length.append(len(instance_points))
        gt_covered.append(np.zeros((len(instance_points))))
        gt_instance_points.append(instance_points)
    pre_instance_map = measure.label(pre_image, background=0)
    pre_instance_indexes = np.unique(pre_instance_map)[1:]
    gt_points = np.where(gt_image!=0)
    gt_points = [[gt_points[0][i],gt_points[1][i]] for i in range(len(gt_points[0]))]
    tree = cKDTree(gt_points)
    # each pre_index is an predicted instance
    for index in pre_instance_indexes:
        votes = []
        instance_map = (pre_instance_map == index)
        instance_points = np.where(instance_map==1)
        instance_points = [[instance_points[0][i],instance_points[1][i]] for i in range(len(instance_points[0]))]
        if instance_points:
            # Each predicted point of the current pre-instance finds its closest gt point and votes
            # to the gt-instance that the closest gt point belongs to.
            _, iis = tree.query(instance_points,k=[1])
            closest_gt_points = [[gt_points[x[0]][0],gt_points[x[0]][1]] for x in iis]
            votes = [gt_instance_map[x[0],x[1]] for x in closest_gt_points]
        # count the voting results
        votes_summary = np.zeros((len(gt_instance_indexes)))
        for j in range(len(gt_instance_indexes)):
            # the number of votes made to gt-instance j+1
            votes_summary[j] = votes.count(j+1)
        # find the gt-instance winning the most vote and assign the current pre-instance to it
        if np.max(votes_summary):
            vote_result = np.where(votes_summary==np.max(votes_summary))[0][0]
            # the length of the pre-instance assigned to corresponding gt-instance
            gt_assigned_lengths[vote_result].append(len(instance_points))
            # calculate projection of the predicted instance to corresponding gt-instance
            instance_tree = cKDTree(gt_instance_points[vote_result])
            _, iis = instance_tree.query(instance_points,k=[1])
            gt_covered[vote_result][np.min(iis):np.max(iis)+1] = 1

    # calculate ECM
    # iterate all gt-instances, calculate connectivity of each of them
    for j,lengths in enumerate(gt_assigned_lengths):
        # lengths are the length of assigned pre-instances to the current gt-instance
        if len(lengths):
            lengths = np.array(lengths)
            # contribution of each assigned pre-instance
            probs = (lengths / np.sum(lengths)).tolist()
            C_j = 0
            for p in probs:
                C_j += -p*np.log2(p)
            entropy_conn += np.exp(-C_j) * np.sum(gt_covered[j]) / len(gt_points)
            naive_conn += 1 / len(lengths)
    if len(gt_assigned_lengths):
        naive_conn = naive_conn / len(gt_assigned_lengths)

    apls_counter = 0
    pre_data = generate_graph(graph)
    adjacent = pre_data['adj']
    pre_points = pre_data['vertices']
    if len(pre_points):

        tree = cKDTree(pre_points)
        for gt_data_instance in gt_data:
            gt_points_instance = gt_data_instance['vertices']
            gt_adjacent = gt_data_instance['adj']
            if len(gt_points_instance) > 5:
                # randomly find some pairs of points for APLS calculation
                for ii in range(len(gt_points_instance)//3):
                    select_index = np.random.choice(len(gt_points_instance),2,replace=False)
                    init_v = gt_points_instance[select_index[0]]
                    end_v = gt_points_instance[select_index[1]]
                    source = select_index[0]
                    target = select_index[1]
                    dist_matrix = dijkstra(gt_adjacent, directed=False, indices=[source],
                                            unweighted=False)
                    gt_length = dist_matrix[0,target]
                    # find corresponding pre_vertex by min-Euclidean distance
                    dds,iis = tree.query([init_v,end_v],k=1)
                    source = iis[0]
                    target = iis[1]
                    # vertex too far away is treated as not reachable
                    if np.max(dds) < 50:
                        dist_matrix = dijkstra(adjacent, directed=False, indices=[source],
                                            unweighted=False)
                        pre_length = dist_matrix[0,target]
                    else:
                        pre_length = 10000
                    apls += min(1,abs(gt_length - pre_length) / (gt_length))
                    apls_counter += 1

    if apls_counter:
        apls = 1 - apls / apls_counter
    return graph_acc, graph_recall, r_f, entropy_conn, naive_conn, apls, 0

class Vertex():
    def __init__(self,v):
        self.coord = v
        self.index = v[0] * 1000 + v[1]
        self.neighbors = []
        self.unprocessed_neighbors = []
        self.processed_neighbors = []
        self.sampled_neighbors = []
        self.key_vertex = False
    def compare(self,v):
        if self.coord[0] == v[0] and self.coord[1] == v[1]:
            return True
        return False
    def next(self,previous):
        neighbors = self.neighbors.copy()
        neighbors.remove(previous)
        return neighbors[0]
    def distance(self,v):
        return pow(pow(self.coord[0]-v.coord[0],2)+pow(self.coord[1]-v.coord[1],2),0.5)

class Graph():
    def __init__(self):
        self.vertices = []
        self.key_vertices = []
        self.sampled_vertices = []
    def find_vertex(self,index):
        for v in self.vertices:
            if index == v.index:
                return v
        return None
    def add_v(self,v,neighbors):
        self.vertices.append(v)
        for n in neighbors:
            index = n[0] * 1000 + n[1]
            u = self.find_vertex(index)
            if u is not None:
                u.neighbors.append(v)
                v.neighbors.append(u)
                u.unprocessed_neighbors.append(v)
                v.unprocessed_neighbors.append(u)
    def find_key_vertices(self):
        for v in self.vertices:
            if len(v.neighbors)!=2:
                v.key_vertex = True
                self.key_vertices.append(v)
                self.sampled_vertices.append(v)

def generate_graph(skeleton):
    def find_neighbors(v,img,remove=False):
        output_v = []
        def get_pixel_value(u):
            if max(u) > 999 or min(u) < 0:
                return
            if img[u[0],u[1]]:
                output_v.append(u)

        get_pixel_value([v[0]+1,v[1]])
        get_pixel_value([v[0]-1,v[1]])
        get_pixel_value([v[0],v[1]-1])
        get_pixel_value([v[0],v[1]+1])
        get_pixel_value([v[0]+1,v[1]-1])
        get_pixel_value([v[0]+1,v[1]+1])
        get_pixel_value([v[0]-1,v[1]-1])
        get_pixel_value([v[0]-1,v[1]+1])
        if remove:
            img[v[0],v[1]] = 0
        return output_v

    graph = Graph()
    img = skeleton
    pre_points = np.where(img!=0)
    pre_points = [[pre_points[0][i],pre_points[1][i]] for i in range(len(pre_points[0]))]
    for point in pre_points:
        v = Vertex(point)
        graph.add_v(v,find_neighbors(point,img))
    graph.find_key_vertices()
    for key_vertex in graph.key_vertices:
        if len(key_vertex.unprocessed_neighbors):
            for neighbor in key_vertex.unprocessed_neighbors:
                key_vertex.unprocessed_neighbors.remove(neighbor)
                #
                curr_v = neighbor
                pre_v = key_vertex
                sampled_v = key_vertex
                counter = 1
                while(not curr_v.key_vertex):
                    if counter % 30 == 0:
                        sampled_v.sampled_neighbors.append(curr_v)
                        curr_v.sampled_neighbors.append(sampled_v)
                        sampled_v = curr_v
                        if not sampled_v.key_vertex:
                            graph.sampled_vertices.append(sampled_v)
                    next_v = curr_v.next(pre_v)
                    pre_v = curr_v
                    curr_v = next_v
                    counter += 1
                sampled_v.sampled_neighbors.append(curr_v)
                curr_v.sampled_neighbors.append(sampled_v)
                curr_v.unprocessed_neighbors.remove(pre_v)

    adjacent = np.ones((len(graph.sampled_vertices),len(graph.sampled_vertices))) * np.inf
    vertices = []
    for ii, v in enumerate(graph.sampled_vertices):
        v.index = ii
        vertices.append([int(v.coord[0]),int(v.coord[1])])
    for v in graph.sampled_vertices:
        for u in v.sampled_neighbors:
            dist = v.distance(u)
            adjacent[v.index,u.index] = dist
            adjacent[u.index,v.index] = dist
    return {'vertices':vertices,'adj':adjacent}

def evaluate_single(pred_result, mask_dir):

    pred_instances = pred_result['pred_instances']
    image_name = pred_result['image_path'].split('/')[-1]
    if image_name.endswith('tiff'):
        image_name = image_name.replace('tiff', 'png')
    else:
        image_name = image_name.replace('jpg', 'png')
    mask_image_path = osp.join(mask_dir, image_name)
    if image_name.endswith('png'):
        sample_seq_path = mask_image_path.replace('binary_map', 'sampled_seq').replace('png', 'pickle')
    else:
        sample_seq_path = mask_image_path.replace('binary_map', 'sampled_seq').replace('jpg', 'pickle')

    with open(sample_seq_path,'rb') as jf:
        gt_data = pickle.load(jf)
    mask = np.array(Image.open(mask_image_path))[:,:,0]
    graph = torch.zeros(1000,1000)
    for instance in pred_instances:
        for i in range(1, len(instance['data'])):
            # import pdb;pdb.set_trace()
            start_vertex, end_vertex = instance['data'][i-1], instance['data'][i]
            graph = update_graph(start_vertex, end_vertex, graph)
    return eval_metric(graph, mask, image_name, gt_data)

def evaluate_all(results_file, mask_dir):
    with open(results_file, 'r') as f:
        pred_results = json.load(f)
    total_image = len(pred_results)
    graph_acc, graph_recall, r_f =[0, 0, 0], [0, 0, 0], [0, 0, 0]
    ECM, Naive, APLS = 0, 0, 0
    results = []
    for pred_result in tqdm(pred_results.values(), desc='evaluate metric'):
        results.append(evaluate_single(pred_result, mask_dir))
    # with multiprocessing.Pool(processes = 16) as pool:
    #     partial_func = partial(evaluate_single, mask_dir=mask_dir)
    #     results = list(tqdm(pool.imap(partial_func, pred_results.values()), total=len(pred_results.values()), desc='evaluate the metric'))
    for acc_single, recall_single, r_f_single, entropy_conn, naive_conn, apls, non_pred_image in results:
        graph_acc  = [i + j for i, j in zip(graph_acc, acc_single)]
        graph_recall = [i + j for i, j in zip(graph_recall, recall_single)]
        r_f = [i + j for i, j in zip(r_f, r_f_single)]
        ECM += entropy_conn
        Naive += naive_conn
        APLS += apls
        total_image -= non_pred_image

    graph_acc_1, graph_recall_1, r_f_1 = graph_acc[0]/total_image, graph_recall[0]/total_image, r_f[0]/total_image
    graph_acc_2, graph_recall_2, r_f_2 = graph_acc[1]/total_image, graph_recall[1]/total_image, r_f[1]/total_image
    graph_acc_5, graph_recall_5, r_f_5 = graph_acc[2]/total_image, graph_recall[2]/total_image, r_f[2]/total_image
    ECM = ECM / total_image
    Naive = Naive /total_image
    APLS = APLS /total_image

    return {'accuracy_1':graph_acc_1, 'accuracy_2':graph_acc_2, 'accuracy_5':graph_acc_5, \
            'recall_1':graph_recall_1, 'recall_2':graph_recall_2, 'recall_5':graph_recall_5, \
            'f1_score_1': r_f_1, 'f1_score_2': r_f_2, 'f1_score_5': r_f_5, 'ECM':ECM, 'Naive':Naive, 'APLS':APLS}


from typing import List, Dict
import cProfile

class Solution:
    def climbStairs(self, n: int) -> int:
        solution_dict = {
            1: 1,
            2: 2
        }
        return self.recursive_find_solution(n, solution_dict)

    def recursive_find_solution(self, n, solution_dict:Dict[int, int]) -> int:

        if solution_dict.get(n):
            return solution_dict[n]

        if n > 2:
            solution_dict[n] = self.recursive_find_solution(n-2, solution_dict) + self.recursive_find_solution( n-1, solution_dict)
            return solution_dict[n]

        return None

def test():
    sol = Solution()

    n=2
    solution = sol.climbStairs(n)
    print(solution)
    assert solution == 2
    print("correct solution")

    n=3
    solution = sol.climbStairs(n)
    print(solution)
    assert solution == 3
    print("correct solution")

    n=8
    solution = sol.climbStairs(n)
    print(solution)
    assert solution == 34
    print("correct solution")

if __name__ == "__main__":
    cProfile.run('test()')