# Copyright 2022-2023 ETSI TeraFlowSDN - TFS OSG (https://tfs.etsi.org/)
#
# 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.

# Convert the path defined as explicit hops with ingress and egress endpoints per device into a set of connections and
# compute the dependencies among them.
#
# Example:
# o-- int DC1 eth1 -- 10/1 CS1 1/2 -- 1/2 R2 2/1 -- a7.. OLS 60.. -- 2/1 R3 1/1 -- 1/1 CS2 10/1 -- eth1 DC2 int --o
#         APP              PKT            PKT            CTRL            PKT           PKT              APP
#
# path_hops = [
#     {'device': 'DC1-GW', 'ingress_ep': 'int', 'egress_ep': 'eth1'},
#     {'device': 'CS1-GW1', 'ingress_ep': '10/1', 'egress_ep': '1/2'},
#     {'device': 'TN-R2', 'ingress_ep': '1/2', 'egress_ep': '2/1'},
#     {'device': 'TN-OLS', 'ingress_ep': 'a7a80b23a703', 'egress_ep': '60519106029e'},
#     {'device': 'TN-R3', 'ingress_ep': '2/1', 'egress_ep': '1/1'},
#     {'device': 'CS2-GW1', 'ingress_ep': '1/1', 'egress_ep': '10/1'},
#     {'device': 'DC2-GW', 'ingress_ep': 'eth1', 'egress_ep': 'int'}
# ]
#
# connections=[
#     (UUID('7548edf7-ee7c-4adf-ac0f-c7a0c0dfba8e'), <DeviceLayerEnum.OPTICAL_CONTROLLER: 1>, [
#             {'device': 'TN-OLS', 'ingress_ep': '833760219d0f', 'egress_ep': 'cf176771a4b9'}
#         ], []),
#     (UUID('c2e57966-5d82-4705-a5fe-44cf6487219e'), <DeviceLayerEnum.PACKET_DEVICE: 30>, [
#             {'device': 'CS1-GW1', 'ingress_ep': '10/1', 'egress_ep': '1/2'},
#             {'device': 'TN-R2', 'ingress_ep': '1/2', 'egress_ep': '2/1'},
#             {'device': 'TN-R3', 'ingress_ep': '2/1', 'egress_ep': '1/1'},
#             {'device': 'CS2-GW1', 'ingress_ep': '1/1', 'egress_ep': '10/1'}
#         ], [UUID('7548edf7-ee7c-4adf-ac0f-c7a0c0dfba8e')]),
#     (UUID('1e205c82-f6ea-4977-9e97-dc27ef1f4802'), <DeviceLayerEnum.APPLICATION_DEVICE: 40>, [
#             {'device': 'DC1-GW', 'ingress_ep': 'int', 'egress_ep': 'eth1'},
#             {'device': 'DC2-GW', 'ingress_ep': 'eth1', 'egress_ep': 'int'}
#         ], [UUID('c2e57966-5d82-4705-a5fe-44cf6487219e')])
# ]

import queue, uuid
from typing import Dict, List, Tuple
from common.proto.context_pb2 import Device
from .ConstantsMappings import DEVICE_TYPE_TO_LAYER, DeviceLayerEnum

def convert_explicit_path_hops_to_connections(
    path_hops : List[Dict], device_dict : Dict[str, Tuple[Dict, Device]], main_connection_uuid : str
) -> List[Tuple[str, DeviceLayerEnum, List[str], List[str]]]:

    connection_stack = queue.LifoQueue()
    connections : List[Tuple[str, DeviceLayerEnum, List[str], List[str]]] = list()
    old_device_layer = None
    last_device_uuid = None
    for path_hop in path_hops:
        device_uuid = path_hop['device']
        if last_device_uuid == device_uuid: continue
        device_tuple = device_dict.get(device_uuid)
        if device_tuple is None: raise Exception('Device({:s}) not found'.format(str(device_uuid)))
        json_device,_ = device_tuple
        device_type = json_device['device_type']
        device_layer = DEVICE_TYPE_TO_LAYER.get(device_type)
        if device_layer is None: raise Exception('Undefined Layer for DeviceType({:s})'.format(str(device_type)))

        if old_device_layer is None:
            # path ingress
            connection_stack.put((main_connection_uuid, device_layer, [path_hop], []))
        elif old_device_layer > device_layer:
            # underlying connection begins
            connection_uuid = str(uuid.uuid4())
            connection_stack.put((connection_uuid, device_layer, [path_hop], []))
        elif old_device_layer == device_layer:
            # same connection continues
            connection_stack.queue[-1][2].append(path_hop)
        elif old_device_layer < device_layer:
            # underlying connection ended
            connection = connection_stack.get()
            connections.append(connection)
            connection_stack.queue[-1][3].append(connection[0])
            connection_stack.queue[-1][2].append(path_hop)
        else:
            raise Exception('Uncontrolled condition')

        old_device_layer = device_layer
        last_device_uuid = device_uuid

    # path egress
    connections.append(connection_stack.get())
    assert connection_stack.empty()
    return connections

def convert_explicit_path_hops_to_plain_connection(
    path_hops : List[Dict], main_connection_uuid : str
) -> List[Tuple[str, DeviceLayerEnum, List[str], List[str]]]:

    connection : Tuple[str, DeviceLayerEnum, List[str], List[str]] = \
        (main_connection_uuid, DeviceLayerEnum.PACKET_DEVICE, [], [])

    last_device_uuid = None
    for path_hop in path_hops:
        device_uuid = path_hop['device']
        if last_device_uuid == device_uuid: continue
        connection[2].append(path_hop)
        last_device_uuid = device_uuid

    return [connection]
