#!/usr/bin/env python3
# SPDX-License-Identifier: BSD-3-Clause OR Apache-2.0
# encoding: utf-8

"""
A tool that reads a contact plan in ION format
and configures the uD3TN nodes.
"""

import argparse
from datetime import datetime
import sys

from ud3tn_utils.aap import AAPTCPClient, AAPUnixClient
from ud3tn_utils.config import ConfigMessage, unix2dtn, Contact
from ud3tn_utils.aap.bin.helpers import get_config_eid


def get_nodes_data(nodesData):
    """Reads text file with nodes' addresses and adds them to dictionary

    Args:
        nodesData: Path to nodes configuration .txt-file
    Returns:
        nodes (dict): dictionary with each nodes and its addresses
    """
    textReader = open(nodesData, "r")
    nodesData = textReader.readlines()
    textReader.close()

    # dictionary in format name of the node : (cla-adress, (ip, port)/socket)
    # if aap connects via tcp then (cla-address, (ip, port))
    # if aap connects via socket then (cla-address, socketpath)
    nodes = {}

    for line in nodesData:
        # skipping empty lines
        if len(line) > 1:
            nodeInfo = line.split()
        else:
            continue
        if (int(nodeInfo[0]) < 1 or int(nodeInfo[0]) > 2 ** 64 - 1):
            raise Exception(
                "The given node number doesn't match the " +
                "required format. Should be: unsigned 64bit number; " +
                f"Was: {nodeInfo[0]}")

        if nodeInfo[2] == "tcp":
            nodes[nodeInfo[0]] = (nodeInfo[1], (nodeInfo[3], int(nodeInfo[4])))
        else:
            nodes[nodeInfo[0]] = (nodeInfo[1], nodeInfo[3])

    return nodes


def create_contact(start, end, bitrate):
    """Creates a `Contact` tuple for the ConfigMessage converts
    absolute or relative times into DTN timestamps

    Args:
        start (str): Start point of the contact
        end (str): End point of the contact
        bitrate (str): Bitrate of the contact
    Returns:
        Contact: tuple with DTN timestamps
    """
    if start.startswith("+"):
        start_time = unix2dtn(datetime.timestamp(datetime.now()) + int(start))
        end_time = unix2dtn(datetime.timestamp(datetime.now()) + int(end))
        return Contact(
            start=int(round(start_time)),
            end=int(round(end_time)),
            bitrate=int(bitrate),
        )
    else:
        start_time = datetime.strptime(start, "%Y/%m/%d-%H:%M:%S")
        end_time = datetime.strptime(end, "%Y/%m/%d-%H:%M:%S")

        if start_time < datetime.now() or end_time < datetime.now():
            print("Warning! The planned time for the contact has passed.")

        return Contact(
            start=int(round(unix2dtn(datetime.timestamp(start_time)))),
            end=int(round(unix2dtn(datetime.timestamp(end_time)))),
            bitrate=int(bitrate),
        )


# Converts node's name into ipn EID scheme format
def to_ipn(nodeNumber: str):
    return "ipn:" + str(nodeNumber) + ".0"


def configure_nodes(contactPlan, nodes: dict):
    """Configure the nodes by scheduling the contacts

    Args:
        contactPlan: Path to contact plan .txt-file
        nodes (dict): dictionary of nodes and their addresses
    """
    textReader = open(contactPlan, "r")
    contactPlan = textReader.readlines()
    textReader.close()

    for i, line in enumerate(contactPlan):

        # processing only a contact command
        if line.startswith("a contact"):
            planLine = line.split()
            if len(planLine) < 7:
                print(f"Error in contact plan on line {i + 1}:",
                      "Too few fields provided!")
                sys.exit(1)

            # exception when the nodes were not written in configuration file
            if planLine[4] not in nodes or planLine[5] not in nodes:
                raise Exception(f"Configuration of the nodes: {planLine[4]}",
                                f" or {planLine[5]} was not given")

            msg = bytes(ConfigMessage(
                to_ipn(planLine[5]),
                nodes[planLine[5]][0],
                contacts=[create_contact(planLine[2],
                                         planLine[3],
                                         planLine[6])],
            ))

            print(
                f"Establishing contact from {planLine[4]} to -> {planLine[5]}",
                f"Start time: {planLine[2]} end time: {planLine[3]}",
                f"With transmit rate: {planLine[6]} (bytes/s)"
                )

            # checking the type of aap port tcp/socket
            if isinstance(nodes[planLine[4]][1], tuple):
                addr = nodes[planLine[4]][1]
                with AAPTCPClient(address=addr) as aap_client:
                    aap_client.register()
                    dest_eid = to_ipn(planLine[4]) or aap_client.node_eid
                    aap_client.send_bundle(get_config_eid(dest_eid), msg)
            else:
                addr = nodes[planLine[4]][1]
                with AAPUnixClient(address=addr) as aap_client:
                    aap_client.register()
                    dest_eid = to_ipn(planLine[4]) or aap_client.node_eid
                    aap_client.send_bundle(get_config_eid(dest_eid), msg)

        else:
            # skipping empty lines and comments
            if line == '\n' or line.startswith('#'):
                continue
            else:
                print(f"Skipping unsupported command in line {i + 1}")
                continue


def main():
    parser = argparse.ArgumentParser(
        description="reads the text file",
    )

    parser.add_argument(
        "--plan",
        default=None,
        help="the path to the contact plan as txt file",
    )

    parser.add_argument(
        "--nodes",
        default=None,
        help="the path to the txt file with information about nodes"
    )

    print(
        "WARNING: This tool does not support new security mechanisms "
        "provided by µD3TN and, thus, has been deprecated. Please use "
        "aap2-config to configure contacts with the compatibility BDM "
        "and provide the administrative secret to both µD3TN and the "
        "configuration tool in release builds. "
        "For using this tool with the current version of µD3TN, "
        "please specify the --allow-remote-config commandline flag."
    )

    args = parser.parse_args()
    configure_nodes(args.plan, get_nodes_data(args.nodes))


if __name__ == "__main__":
    main()
