from ast import Param
import json
import math
from telnetlib import STATUS

from django.http import HttpResponse, JsonResponse
from django.shortcuts import render, redirect
#from django.template import loader
from django.contrib.auth.decorators import login_required
from django.views.decorators.http import require_safe, require_POST
from django.contrib.auth import logout
from django.urls.base import reverse
from django.contrib.auth.forms import PasswordChangeForm
from django.contrib import messages
from django.contrib.auth import update_session_auth_hash
from django.utils.translation import ugettext as _
from django.contrib.sites.shortcuts import get_current_site

import ctypes as ct
import json
import os
from pathlib import Path, PurePath
import time

import yaml
from io import open as iopen

from interface.validators import validate_settings, validate_request_data, validate_float, validate_selected_boards, validate_u8_number, validate_board_name
import fpga_cli.cli as cli

from django.core.exceptions import ValidationError


FOLDER_PATH = Path.cwd().parents[0] / "textfiles"
SETTINGS_FILE = "config.yaml"
OUTPUT_PATH = Path.cwd().parents[2] / "output"

PYTHON_PROGRAM = FOLDER_PATH / "plot.py"
#PYTHON_PROGRAM = Path.cwd().parents[1] / "sw" / "examples" / "n_rand_neurons.py"

BOARDS_LIST = []


@login_required
def base_view(request):
    return render(request, "interface/base.html")

@login_required
def logout_view(request):
    print("Logout!!")
    logout(request)
    return redirect(reverse("login:login"))

@login_required
def change_password(request):
    if request.method == 'POST':
        form = PasswordChangeForm(request.user, request.POST)
        if form.is_valid():
            user = form.save()
            update_session_auth_hash(request, user)
            messages.success(request, _(
                'Your password was successfully updates!'))
            return redirect(reverse("interface:base"))
        else:
            messages.error(request, _('Please correct the error below.'))
    else:
        form = PasswordChangeForm(request.user)
    return render(request, 'interface/password_change.html', {'form': form})
    
# Set Parameters in Config file
###############################
def propagator_32(tau_syn, tau, c, h):
    P32_LINEAR = 1 / (2 * c * tau * tau) * h * h * (tau_syn - tau) * math.exp(-h / tau)
    P32_SINGULAR = h / c * math.exp( -h / tau)
    P32 = -tau / (c * (1 - tau / tau_syn)) * math.exp(-h / tau_syn) * (math.exp( h * ( 1 / tau_syn -1 / tau)) -1)

    DEV_P32 = abs(P32 - P32_SINGULAR)

    if(tau == tau_syn or (abs(tau-tau_syn) < 0.1 and DEV_P32 > 2 * abs(P32_LINEAR))):
        return P32_SINGULAR
    else:
        return P32

def write_data_to_configfile(data):

    with open(FOLDER_PATH / SETTINGS_FILE) as configfile:
        configuration = yaml.safe_load(configfile)

    configuration["Scheduler"].update({"Syncs per Timestep": int(data["syncsPerTimeStep"])})
    configuration["Scheduler"].update({"Timestep Limit": int(data["timestepLimit"])})
    
    neuron_dict = configuration["Neuron"]

    #Set Params
    neuron_dict.update({"V_thresh": float(data["v_thresh_mv"])})
    neuron_dict.update({"V_reset": float(data["v_reset_mv"])})
    neuron_dict.update({"T_ref": float(data["t_ref_ms"])})
    neuron_dict.update({"P11_exc" : math.exp(float(data["h_ms"])*-1/ float(data["tau_ex_ms"]))})
    neuron_dict.update({"P11_inh" : math.exp(float(data["h_ms"])*-1/ float(data["tau_in_ms"]))})
    neuron_dict.update({"P22" : math.exp(float(data["h_ms"])*-1/ float(data["tau_ms"]))})
    neuron_dict.update({"P20" : float(data["tau_ms"]) / float(data["c_pf"]) * (1.0 - neuron_dict["P22"])})
    neuron_dict.update({"P21_exc" : propagator_32(float(data["tau_ex_ms"]), float(data["tau_ms"]), float(data["c_pf"]), float(data["h_ms"]))})
    neuron_dict.update({"P21_inh" : propagator_32(float(data["tau_in_ms"]), float(data["tau_ms"]), float(data["c_pf"]), float(data["h_ms"]))})
    neuron_dict.update({"Exc Weight Decimal Places": int(data["weightFracExc"])})
    neuron_dict.update({"Inh Weight Decimal Places": int(data["weightFracInh"])})
    

    configuration["Neuron"].update(neuron_dict)

    with iopen(FOLDER_PATH / SETTINGS_FILE, "w", encoding="utf-8") as out:
        yaml.dump(configuration, out, default_flow_style=False, sort_keys=False, allow_unicode=True)

def get_surounding_boards(board_list):
    first_board = board_list[0]
    last_board = board_list[-1]

    ret = []

    for index, row in enumerate(BOARDS_LIST):
        if first_board in row:
            for j, element in enumerate(row):
                if element == first_board:
                    top_left_corner = (index, j)

        if last_board in row:
            for j, element in enumerate(row):
                if element == last_board:
                    bottom_right_corner = (index, j)

    if top_left_corner[0] == 0 and top_left_corner[1] == 0 and bottom_right_corner[0]+1 == len(BOARDS_LIST) and bottom_right_corner[1]+1 == len(BOARDS_LIST[0]) :
        return [];
    
    cur_pos = bottom_right_corner[0]

    if len(BOARDS_LIST)-(bottom_right_corner[0]+1-top_left_corner[0]) == 1:
        ret.extend(BOARDS_LIST[top_left_corner[0]-1][top_left_corner[1]:bottom_right_corner[1]+1])
    elif len(BOARDS_LIST)-(bottom_right_corner[0]+1-top_left_corner[0]) > 1:
        ret.extend(BOARDS_LIST[top_left_corner[0]-1][top_left_corner[1]:bottom_right_corner[1]+1])

        if(bottom_right_corner[0]+1 == len(BOARDS_LIST)):
            cur_pos = -1;
        ret.extend(BOARDS_LIST[cur_pos+1][top_left_corner[1]:bottom_right_corner[1]+1])

    cur_pos = bottom_right_corner[1]
    if len(BOARDS_LIST[0])-(bottom_right_corner[1]+1-top_left_corner[1]) == 1:
        ret.extend([r[top_left_corner[1]-1] for r in BOARDS_LIST[top_left_corner[0]: bottom_right_corner[0]+1]])
    elif len(BOARDS_LIST[0])-(bottom_right_corner[1]+1-top_left_corner[1]) > 1:
        ret.extend([r[top_left_corner[1]-1] for r in BOARDS_LIST[top_left_corner[0]: bottom_right_corner[0]+1]])

        if(bottom_right_corner[1]+1 == len(BOARDS_LIST[0])):
            cur_pos = -1

        ret.extend([r[cur_pos+1] for r in BOARDS_LIST[top_left_corner[0]: bottom_right_corner[0]+1]])

    #print(ret)
    return ret

def init_cluster(selected_boards):

    surrounding_boards = get_surounding_boards(selected_boards)
    ret = cli.init_cluster_for_simulation(selected_boards, surrounding_boards)
    response = {"first_output": ret[0].replace("\n", "<br>"), "second_output": ret[1].replace("\n", "<br>")}
    return response
    
@login_required
@require_POST
def set_network_params(request):
        request_data = json.loads(request.body.decode("utf-8"))

        #Validate Data
        try:
            validate_request_data(request_data)
        except ValidationError as e:
            print(e)
            return HttpResponse(e.message, status=400)
        try:
            for element in request_data:
                validate_settings(request_data[element], element)
        except ValidationError as e:
            print(e.message)
            return HttpResponse(e.message, status=400)
        try:
            validate_selected_boards(request_data["Boards"], BOARDS_LIST)
        except ValidationError as e:
            print(e.message)
            return HttpResponse(e.message, status=400)

        write_data_to_configfile(request_data)
        print("Write settings to configfile.")

        res = init_cluster(request_data["Boards"])
            
        return JsonResponse(res, status=200)

def get_main_board(board_list):
    for l in BOARDS_LIST:
        if board_list[-1] in l:
            row = l
            break

    for element in row:
        if element in board_list:
            main_node = element
            break
    return main_node

def get_node_id(board_name: str)->str:
    posX = 0
    posY = 0

    for lineidx, line in enumerate(BOARDS_LIST):
        if board_name in line:
            posY = len(BOARDS_LIST)-(lineidx+1)
            for rowidx, row in enumerate(line):
                if board_name == row:
                    posX = rowidx
                    return "{} {}".format(posX, posY)

@login_required
@require_POST
def start_simulation(request):
    print(request.body.decode("utf-8"))
    request_data = json.loads(request.body.decode("utf-8"))
    try:
        validate_selected_boards(request_data["Boards"], BOARDS_LIST)
        validate_u8_number(request_data["timeStep"])
        validate_u8_number(request_data["numNeurons"])
    except ValidationError:
        return HttpResponse("invalid start parameter", status=400)

   

    main_board = get_main_board(request_data["Boards"])

    simulation_data = cli.start_simulation(main_board, request_data["timeStep"], request_data["numNeurons"])
    info_data = cli.get_simulation_info(main_board)

    res = {}
    res.update({"simulation_output": simulation_data.replace("\n", "<br>")})
    res.update({"info_output": info_data.replace("\n", "<br>")})

    info_simulation = cli.read_simulation_information(simulation_data)
    info_communication = cli.read_communication_informations(info_data)
    res.update({"sim_info": info_simulation})
    res.update({"comm_info": info_communication})

    res.update({"main_board_id": get_node_id(main_board)})
    res.update({"main_board": main_board})

    return JsonResponse(res, status=200)

@login_required
@require_POST
def restart_simulation(request):
    request_data = json.loads(request.body.decode("utf-8"))
    num_timesteps = request_data["timeStep"]
    main_board = request_data["mainBoard"]

    try:
        validate_u8_number(num_timesteps)
        validate_board_name(main_board)
    except:
        return HttpResponse("invalid restart parameter", status=400)


    simulation_data = cli.restart_simulation(main_board, num_timesteps)
    info_data = cli.get_simulation_info(main_board)

    res = {}
    res.update({"simulation_output": simulation_data.replace("\n", "<br>")})
    res.update({"info_output": info_data.replace("\n", "<br>")})

    info_simulation = cli.read_simulation_information(simulation_data)
    info_communication = cli.read_communication_informations(info_data)
    res.update({"sim_info": info_simulation})
    res.update({"comm_info": info_communication})

    res.update({"main_board_id": get_node_id(main_board)})

    return JsonResponse(res, status=200)

# Calculate Metrics
#######################
def get_files_from_folder(folder, file_prefix, file_suffix):
    file_list = os.listdir(folder)
    data_list = []
    for element in file_list:
        if element.startswith(file_prefix) and element.endswith(file_suffix):
            # data_list.append(str(PurePath.as_posix(FOLDER_PATH / "output" / element)))
            data_list.append(str(PurePath.as_posix(OUTPUT_PATH / element)))
    return data_list

@login_required
def calculate_metrics(request):
    file_list = get_files_from_folder(OUTPUT_PATH, "read_spikes_node", "bin")
    command = "cd \"{}\" ; ./merger.exe".format(str(PurePath.as_posix(FOLDER_PATH/ "programs")))

    cur_path = Path.cwd();

    for file in file_list:
        command += " \"{}\"".format(file)
    # command += " -o \"{}\"".format(str(PurePath.as_posix(FOLDER_PATH / "output")))
    command += " -o \"{}\"".format(str(PurePath.as_posix(OUTPUT_PATH)))

    command += " & cd \"{}\"".format(str(PurePath.as_posix(cur_path)))
    print(command)

    start_time = time.time()
    res = os.popen(command)
    print("Merger Time: {}".format(time.time()- start_time))
    print(res.read())

    #print("NumFiles: {0}, Files: {1}".format(num_files, file_str));


    library_path = FOLDER_PATH / "programs" / "createMetrics.so"
    __creater = ct.CDLL(str(library_path))
    __creater.create.restype = ct.c_int
    __creater.create.argstypes = [ct.c_int, ct.c_char_p, ct.c_char_p]

    num_files = 1
    # file_str = str(FOLDER_PATH/ "output" / "spikes.bin")
    file_str = str(OUTPUT_PATH / "spikes.bin")
    req_files = file_str.encode("utf-8")

    # dir = str(FOLDER_PATH / "output").encode("utf-8")
    dir = str(OUTPUT_PATH).encode("utf-8")

    start_time = time.time()
    ret = __creater.create(num_files, req_files, dir)
    print("Metrict calculation time: {}".format(time.time()- start_time))
    print(ret)
    return HttpResponse("ok")

@login_required
@require_safe
def read_cluster_information(request):
    if request.method == "GET":
        path = FOLDER_PATH / "clusterlayout.txt"
        ret = {}

        global BOARDS_LIST
        BOARDS_LIST  = []
        with open(path, "r") as file:
            for line in file:
                if "#" in line:
                    continue
                if "names:" in line:
                    for line in file:
                        BOARDS_LIST.append([])
                        content = line.split(",")
                        for element in content:
                            element = element.replace("\n", "")
                            BOARDS_LIST[-1].append(element)
                    break;
                content = line.partition(":")
                ret.setdefault(content[0].strip().lower(), content[2].strip().lower())
        ret.setdefault("names", BOARDS_LIST)

        url = "http://{0}{1}".format(get_current_site(request), reverse("output:output"));
        ret.update({"url": url})

        json_file = json.dumps(ret, indent=4)

        return HttpResponse(json_file, status=200)
    return HttpResponse("wrong method", status=405)

#DELETE Not in use
@login_required
@require_safe
def get_connections_infomations(request):
    if request.method == "GET":
        ret = {}

        # response = os.popen("");
        # response = [zeile.strip() for zeile in response]
        # for line in response: 
        #     if "Neighbour" in line:
        #         infos = line.split(" ")
        #         if len(infos) <7:
        #             break
        #         startIndex = 3 if len(infos)==8 else 2;
        #         if "(255|255)" in infos[startIndex]:
        #             continue
            
        #         ret.setdefault(line[startIndex], [infos[startIndex+1].rstrip(",").strip(), infos[startIndex+2].rstrip(",").strip(), infos[startIndex+3].rstrip(",").strip(), infos[startIndex+4].rstrip(",").strip()])
        ret.setdefault("(100|104)", ["1274", "0", "0", "300"])
        return HttpResponse(json.dumps(ret, indent=4), status=200);
    return HttpResponse("wrong method", status=405)

@login_required
def get_tempreture():
    # response = os.popen("./run.sh --node 100 100 read temperature"); TODO
    response = "Node (100|100): 56.968°C\nDisconnecting\ndone!"
    start = response.rfind("Node")
    end = response.find("C", start+14)
    temp = (response[start:end+1]).spilt(":")[1]
    temp = temp.strip();
    print(temp)

@login_required
@require_safe
def get_nest_code(request):

    with open(PYTHON_PROGRAM, "r") as file:
        content = file.readlines();
    return HttpResponse(content);

@login_required
@require_POST
def set_nest_code(request):
    content = request.body.decode("utf-8")
    with open(PYTHON_PROGRAM, "w") as file:
        file.write(content)
    return HttpResponse("set code", status=200)

@login_required
@require_POST
def save_parameters(request):
    content = request.body.decode("utf-8")
    print(content)
    resp = HttpResponse(content, content_type="application/json", status=200)
    resp["Content-Disposition"] = "attachment; filename=%s" % "param_settings.zip"

    return resp