import numpy as np
import time
import math
import os
class nait():
    class standard():
        def train(training_model, training_epochs):
            start_time = time.time()
            training_inputs = training_model[0]
            training_expectations = training_model[1]
            number_of_inputs = len(training_inputs[0])
            number_of_outputs = len(training_expectations[0])
            version = "BETA"
            epoch_size = 3000
            number_of_iterations = epoch_size * training_epochs
            os.system(f'title NAIT {version}')
            init_weights_l1 = [[1/number_of_inputs]*number_of_inputs]*16
            init_weights_l2 = [[1/16]*16]*16
            init_weights_l3 = [[1/16]*16]*number_of_outputs
            init_biases_l1 = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
            init_biases_l2 = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
            init_biases_l3 = [0]*number_of_outputs
            weights_l1 = [init_weights_l1]
            weights_l2 = [init_weights_l2]
            weights_l3 = [init_weights_l3]
            biases_l1 = [init_biases_l1]
            biases_l2 = [init_biases_l2]
            biases_l3 = [init_biases_l3]
            def layer_foward(function_input, function_weights, function_biases):
                return np.dot(function_input, np.array(function_weights).T) + function_biases
            def sigmoid(x):
                return 1 / (1 + math.exp(-x))
            length_of_bar = 20
            epoch_bar_size = 0
            print(f"    Epoch (1/{training_epochs})")
            epoch_start_time = time.time()
            for evolving in range(number_of_iterations):
                cost_list = []
                for sample in range(len(weights_l1)):
                    output = layer_foward(training_inputs, weights_l1[sample], biases_l1[sample])
                    output = layer_foward(output, weights_l2[sample], biases_l2[sample])
                    output = layer_foward(output, weights_l3[sample], biases_l3[sample])
                    cost = sum(sum(abs(output - training_expectations) * abs(output - training_expectations)))
                    cost_list.append(cost)
                best_index = cost_list.index(min(cost_list))
                if evolving % 100 == 0:
                    bar_filled = "="
                    bar_unfilled = "."
                    if evolving - epoch_bar_size > epoch_size-1 :
                        epoch_bar_size += epoch_size
                        print(f"    [{bar_filled * length_of_bar}] {evolving // (number_of_iterations // 100)}% Accuracy: {1 - ((sigmoid(min(cost_list)*10)-0.5)*2):.9f} Loss: {min(cost_list):.9f} {round(time.time() - epoch_start_time, 1)}s                    ", end = "\r")
                        print(f"\n    Epoch ({epoch_bar_size//epoch_size+1}/{training_epochs})")
                        epoch_start_time = time.time()
                    print(f"    [{bar_filled * ((evolving - epoch_bar_size) // (epoch_size // length_of_bar) + 1)}{bar_unfilled * (length_of_bar - ((evolving - epoch_bar_size) // (epoch_size // length_of_bar)) - 1)}] {evolving // (number_of_iterations // 100)}% Accuracy: {1 - ((sigmoid(min(cost_list)*10)-0.5)*2):.9f} Loss: {min(cost_list):.9f} {round(time.time() - epoch_start_time, 1)}s                    ", end = "\r")
                weights_l1 = [weights_l1[best_index]]
                weights_l2 = [weights_l2[best_index]]
                weights_l3 = [weights_l3[best_index]]
                biases_l1 = [biases_l1[best_index]]
                biases_l2 = [biases_l2[best_index]]
                biases_l3 = [biases_l3[best_index]]
                for iterations in range(10):
                    weights_l1.append(weights_l1[0]+np.random.uniform(-0.001, 0.001, (16,number_of_inputs)))
                    weights_l2.append(weights_l2[0]+np.random.uniform(-0.001, 0.001, (16,16)))
                    weights_l3.append(weights_l3[0]+np.random.uniform(-0.001, 0.001, (number_of_outputs,16)))
                for iterations in range(10):
                    biases_l1.append(biases_l1[0]+np.random.uniform(-0.001, 0.001, (16)))
                    biases_l2.append(biases_l2[0]+np.random.uniform(-0.001, 0.001, (16)))
                    biases_l3.append(biases_l3[0]+np.random.uniform(-0.001, 0.001, (number_of_outputs)))
                for iterations in range(9):
                    weights_l1.append(weights_l1[0]+np.random.uniform(-0.0001, 0.0001, (16,number_of_inputs)))
                    weights_l2.append(weights_l2[0]+np.random.uniform(-0.0001, 0.0001, (16,16)))
                    weights_l3.append(weights_l3[0]+np.random.uniform(-0.0001, 0.0001, (number_of_outputs,16)))
                for iterations in range(9):
                    biases_l1.append(biases_l1[0]+np.random.uniform(-0.0001, 0.0001, (16)))
                    biases_l2.append(biases_l2[0]+np.random.uniform(-0.0001, 0.0001, (16)))
                    biases_l3.append(biases_l3[0]+np.random.uniform(-0.0001, 0.0001, (number_of_outputs)))
            data = []
            data.append("wl1=["+str(list(weights_l1[0])).replace("array(", "").replace(")", "")+"]")
            data.append("wl2=["+str(list(weights_l2[0])).replace("array(", "").replace(")", "")+"]")
            data.append("wl3=["+str(list(weights_l3[0])).replace("array(", "").replace(")", "")+"]")
            data.append("bl1=["+str(list(biases_l1[0])).replace("array(", "").replace(")", "")+"]")
            data.append("bl2=["+str(list(biases_l2[0])).replace("array(", "").replace(")", "")+"]")
            data.append("bl3=["+str(list(biases_l3[0])).replace("array(", "").replace(")", "")+"]")
            textfile = open("nait_model.py", "w")
            for line in data:
                textfile.write(line+"\n")
            textfile.close()
            print(f"    [{bar_filled * length_of_bar}] 100% Accuracy: {1 - ((sigmoid(min(cost_list)*10)-0.5)*2):.9f} Loss: {min(cost_list):.9f} {round(time.time() - epoch_start_time, 1)}s                    ")
            print(f"    Data saved to 'nait_model.py'                                                                                \n    Final accuracy: {1 - ((sigmoid(min(cost_list)*10)-0.5)*2):.9f} Final loss: {min(cost_list):.9f} Total time: {round(time.time() - start_time, 1)}s")
        def load(loading_inputs):
            from nait_model import wl1, wl2, wl3, bl1, bl2, bl3
            def layer_foward(function_input, function_weights, function_biases):
                return np.dot(function_input, np.array(function_weights).T) + function_biases
            output = layer_foward(loading_inputs, wl1[0], bl1[0])
            output = layer_foward(output, wl2[0], bl2[0])
            output = layer_foward(output, wl3[0], bl3[0])
            return output
    class complex():
        def train(training_model, training_epochs):
            start_time = time.time()
            training_inputs = training_model[0]
            training_expectations = training_model[1]
            number_of_inputs = len(training_inputs[0])
            number_of_outputs = len(training_expectations[0])
            version = "BETA"
            epoch_size = 3000
            number_of_iterations = epoch_size * training_epochs
            os.system(f'title NAIT {version}')
            init_weights_l1 = [[1/number_of_inputs]*number_of_inputs]*64
            init_weights_l2 = [[1/64]*64]*64
            init_weights_l3 = [[1/64]*64]*number_of_outputs
            init_biases_l1 = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
            init_biases_l2 = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
            init_biases_l3 = [0]*number_of_outputs
            weights_l1 = [init_weights_l1]
            weights_l2 = [init_weights_l2]
            weights_l3 = [init_weights_l3]
            biases_l1 = [init_biases_l1]
            biases_l2 = [init_biases_l2]
            biases_l3 = [init_biases_l3]
            def layer_foward(function_input, function_weights, function_biases):
                return np.dot(function_input, np.array(function_weights).T) + function_biases
            def sigmoid(x):
                return 1 / (1 + math.exp(-x))
            length_of_bar = 20
            epoch_bar_size = 0
            print(f"    Epoch (1/{training_epochs})")
            epoch_start_time = time.time()
            for evolving in range(number_of_iterations):
                cost_list = []
                for sample in range(len(weights_l1)):
                    output = layer_foward(training_inputs, weights_l1[sample], biases_l1[sample])
                    output = layer_foward(output, weights_l2[sample], biases_l2[sample])
                    output = layer_foward(output, weights_l3[sample], biases_l3[sample])
                    cost = sum(sum(abs(output - training_expectations) * abs(output - training_expectations)))
                    cost_list.append(cost)
                best_index = cost_list.index(min(cost_list))
                if evolving % 100 == 0:
                    bar_filled = "="
                    bar_unfilled = "."
                    if evolving - epoch_bar_size > epoch_size-1 :
                        epoch_bar_size += epoch_size
                        print(f"    [{bar_filled * length_of_bar}] {evolving // (number_of_iterations // 100)}% Accuracy: {1 - ((sigmoid(min(cost_list)*10)-0.5)*2):.9f} Loss: {min(cost_list):.9f} {round(time.time() - epoch_start_time, 1)}s                    ", end = "\r")
                        print(f"\n    Epoch ({epoch_bar_size//epoch_size+1}/{training_epochs})")
                    print(f"    [{bar_filled * ((evolving - epoch_bar_size) // (epoch_size // length_of_bar) + 1)}{bar_unfilled * (length_of_bar - ((evolving - epoch_bar_size) // (epoch_size // length_of_bar)) - 1)}] {evolving // (number_of_iterations // 100)}% Accuracy: {1 - ((sigmoid(min(cost_list)*10)-0.5)*2):.9f} Loss: {min(cost_list):.9f} {round(time.time() - epoch_start_time, 1)}s                    ", end = "\r")
                weights_l1 = [weights_l1[best_index]]
                weights_l2 = [weights_l2[best_index]]
                weights_l3 = [weights_l3[best_index]]
                biases_l1 = [biases_l1[best_index]]
                biases_l2 = [biases_l2[best_index]]
                biases_l3 = [biases_l3[best_index]]
                for iterations in range(10):
                    weights_l1.append(weights_l1[0]+np.random.uniform(-0.001, 0.001, (64,number_of_inputs)))
                    weights_l2.append(weights_l2[0]+np.random.uniform(-0.001, 0.001, (64,64)))
                    weights_l3.append(weights_l3[0]+np.random.uniform(-0.001, 0.001, (number_of_outputs,64)))
                for iterations in range(10):
                    biases_l1.append(biases_l1[0]+np.random.uniform(-0.001, 0.001, (64)))
                    biases_l2.append(biases_l2[0]+np.random.uniform(-0.001, 0.001, (64)))
                    biases_l3.append(biases_l3[0]+np.random.uniform(-0.001, 0.001, (number_of_outputs)))
                for iterations in range(9):
                    weights_l1.append(weights_l1[0]+np.random.uniform(-0.0001, 0.0001, (64,number_of_inputs)))
                    weights_l2.append(weights_l2[0]+np.random.uniform(-0.0001, 0.0001, (64,64)))
                    weights_l3.append(weights_l3[0]+np.random.uniform(-0.001, 0.001, (number_of_outputs,64)))
                for iterations in range(9):
                    biases_l1.append(biases_l1[0]+np.random.uniform(-0.0001, 0.0001, (64)))
                    biases_l2.append(biases_l2[0]+np.random.uniform(-0.0001, 0.0001, (64)))
                    biases_l3.append(biases_l3[0]+np.random.uniform(-0.0001, 0.0001, (number_of_outputs)))
            data = []
            data.append("wl1=["+str(list(weights_l1[0])).replace("array(", "").replace(")", "")+"]")
            data.append("wl2=["+str(list(weights_l2[0])).replace("array(", "").replace(")", "")+"]")
            data.append("wl3=["+str(list(weights_l3[0])).replace("array(", "").replace(")", "")+"]")
            data.append("bl1=["+str(list(biases_l1[0])).replace("array(", "").replace(")", "")+"]")
            data.append("bl2=["+str(list(biases_l2[0])).replace("array(", "").replace(")", "")+"]")
            data.append("bl3=["+str(list(biases_l3[0])).replace("array(", "").replace(")", "")+"]")
            textfile = open("nait_model.py", "w")
            for line in data:
                textfile.write(line+"\n")
            textfile.close()
            print(f"    [{bar_filled * length_of_bar}] 100% Accuracy: {1 - ((sigmoid(min(cost_list)*10)-0.5)*2):.9f} Loss: {min(cost_list):.9f} {round(time.time() - epoch_start_time, 1)}s                    ")
            print(f"    Data saved to 'nait_model.py'                                                                                \n    Final accuracy: {1 - ((sigmoid(min(cost_list)*10)-0.5)*2):.9f} Final loss: {min(cost_list):.9f} Total time: {round(time.time() - start_time, 1)}s")
        def load(loading_inputs):
            from nait_model import wl1, wl2, wl3, bl1, bl2, bl3
            def layer_foward(function_input, function_weights, function_biases):
                return np.dot(function_input, np.array(function_weights).T) + function_biases
            output = layer_foward(loading_inputs, wl1[0], bl1[0])
            output = layer_foward(output, wl2[0], bl2[0])
            output = layer_foward(output, wl3[0], bl3[0])
            return output
    class simple():
        def train(training_model, training_epochs):
            start_time = time.time()
            training_inputs = training_model[0]
            training_expectations = training_model[1]
            number_of_inputs = len(training_inputs[0])
            number_of_outputs = len(training_expectations[0])
            version = "BETA"
            epoch_size = 3000
            number_of_iterations = epoch_size * training_epochs
            os.system(f'title NAIT {version}')
            init_weights_l1 = [[1/number_of_inputs]*number_of_inputs]*8
            init_weights_l2 = [[1/8]*8]*8
            init_weights_l3 = [[1/8]*8]*number_of_outputs
            init_biases_l1 = [0, 0, 0, 0, 0, 0, 0, 0]
            init_biases_l2 = [0, 0, 0, 0, 0, 0, 0, 0]
            init_biases_l3 = [0]*number_of_outputs
            weights_l1 = [init_weights_l1]
            weights_l2 = [init_weights_l2]
            weights_l3 = [init_weights_l3]
            biases_l1 = [init_biases_l1]
            biases_l2 = [init_biases_l2]
            biases_l3 = [init_biases_l3]
            def layer_foward(function_input, function_weights, function_biases):
                return np.dot(function_input, np.array(function_weights).T) + function_biases
            def sigmoid(x):
                return 1 / (1 + math.exp(-x))
            length_of_bar = 20
            epoch_bar_size = 0
            print(f"    Epoch (1/{training_epochs})")
            epoch_start_time = time.time()
            for evolving in range(number_of_iterations):
                cost_list = []
                for sample in range(len(weights_l1)):
                    output = layer_foward(training_inputs, weights_l1[sample], biases_l1[sample])
                    output = layer_foward(output, weights_l2[sample], biases_l2[sample])
                    output = layer_foward(output, weights_l3[sample], biases_l3[sample])
                    cost = sum(sum(abs(output - training_expectations) * abs(output - training_expectations)))
                    cost_list.append(cost)
                best_index = cost_list.index(min(cost_list))
                if evolving % 100 == 0:
                    bar_filled = "="
                    bar_unfilled = "."
                    if evolving - epoch_bar_size > epoch_size-1 :
                        epoch_bar_size += epoch_size
                        print(f"    [{bar_filled * length_of_bar}] {evolving // (number_of_iterations // 100)}% Accuracy: {1 - ((sigmoid(min(cost_list)*10)-0.5)*2):.9f} Loss: {min(cost_list):.9f} {round(time.time() - epoch_start_time, 1)}s                    ", end = "\r")
                        print(f"\n    Epoch ({epoch_bar_size//epoch_size+1}/{training_epochs})")
                    print(f"    [{bar_filled * ((evolving - epoch_bar_size) // (epoch_size // length_of_bar) + 1)}{bar_unfilled * (length_of_bar - ((evolving - epoch_bar_size) // (epoch_size // length_of_bar)) - 1)}] {evolving // (number_of_iterations // 100)}% Accuracy: {1 - ((sigmoid(min(cost_list)*10)-0.5)*2):.9f} Loss: {min(cost_list):.9f} {round(time.time() - epoch_start_time, 1)}s                    ", end = "\r")
                weights_l1 = [weights_l1[best_index]]
                weights_l2 = [weights_l2[best_index]]
                weights_l3 = [weights_l3[best_index]]
                biases_l1 = [biases_l1[best_index]]
                biases_l2 = [biases_l2[best_index]]
                biases_l3 = [biases_l3[best_index]]
                for iterations in range(10):
                    weights_l1.append(weights_l1[0]+np.random.uniform(-0.001, 0.001, (8,number_of_inputs)))
                    weights_l2.append(weights_l2[0]+np.random.uniform(-0.001, 0.001, (8,8)))
                    weights_l3.append(weights_l3[0]+np.random.uniform(-0.001, 0.001, (number_of_outputs,8)))
                for iterations in range(10):
                    biases_l1.append(biases_l1[0]+np.random.uniform(-0.001, 0.001, (8)))
                    biases_l2.append(biases_l2[0]+np.random.uniform(-0.001, 0.001, (8)))
                    biases_l3.append(biases_l3[0]+np.random.uniform(-0.001, 0.001, (number_of_outputs)))
                for iterations in range(9):
                    weights_l1.append(weights_l1[0]+np.random.uniform(-0.0001, 0.0001, (8,number_of_inputs)))
                    weights_l2.append(weights_l2[0]+np.random.uniform(-0.0001, 0.0001, (8,8)))
                    weights_l3.append(weights_l3[0]+np.random.uniform(-0.0001, 0.0001, (number_of_outputs,8)))
                for iterations in range(9):
                    biases_l1.append(biases_l1[0]+np.random.uniform(-0.0001, 0.0001, (8)))
                    biases_l2.append(biases_l2[0]+np.random.uniform(-0.0001, 0.0001, (8)))
                    biases_l3.append(biases_l3[0]+np.random.uniform(-0.0001, 0.0001, (number_of_outputs)))
            data = []
            data.append("wl1=["+str(list(weights_l1[0])).replace("array(", "").replace(")", "")+"]")
            data.append("wl2=["+str(list(weights_l2[0])).replace("array(", "").replace(")", "")+"]")
            data.append("wl3=["+str(list(weights_l3[0])).replace("array(", "").replace(")", "")+"]")
            data.append("bl1=["+str(list(biases_l1[0])).replace("array(", "").replace(")", "")+"]")
            data.append("bl2=["+str(list(biases_l2[0])).replace("array(", "").replace(")", "")+"]")
            data.append("bl3=["+str(list(biases_l3[0])).replace("array(", "").replace(")", "")+"]")
            textfile = open("nait_model.py", "w")
            for line in data:
                textfile.write(line+"\n")
            textfile.close()
            print(f"    [{bar_filled * length_of_bar}] 100% Accuracy: {1 - ((sigmoid(min(cost_list)*10)-0.5)*2):.9f} Loss: {min(cost_list):.9f} {round(time.time() - epoch_start_time, 1)}s                    ")
            print(f"    Data saved to 'nait_model.py'                                                                                \n    Final accuracy: {1 - ((sigmoid(min(cost_list)*10)-0.5)*2):.9f} Final loss: {min(cost_list):.9f} Total time: {round(time.time() - start_time, 1)}s")
        def load(loading_inputs):
            from nait_model import wl1, wl2, wl3, bl1, bl2, bl3
            def layer_foward(function_input, function_weights, function_biases):
                return np.dot(function_input, np.array(function_weights).T) + function_biases
            output = layer_foward(loading_inputs, wl1[0], bl1[0])
            output = layer_foward(output, wl2[0], bl2[0])
            output = layer_foward(output, wl3[0], bl3[0])
            return output