# Copyright 2021-2023 H2020 TeraFlow (https://www.teraflow-h2020.eu/)
#
# 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 enum import IntEnum
from typing import Dict
from common.proto.context_pb2 import Device, Link, Service
from common.tools.grpc.Tools import grpc_message_to_json_string

class CapacityUnit(IntEnum):
    TB   = 0
    TBPS = 1
    GB   = 2
    GBPS = 3
    MB   = 4
    MBPS = 5
    KB   = 6
    KBPS = 7
    GHZ  = 8
    MHZ  = 9

class LinkPortDirection(IntEnum):
    BIDIRECTIONAL = 0
    INPUT         = 1
    OUTPUT        = 2
    UNKNOWN       = 3

class TerminationDirection(IntEnum):
    BIDIRECTIONAL = 0
    SINK          = 1
    SOURCE        = 2
    UNKNOWN       = 3

class TerminationState(IntEnum):
    CAN_NEVER_TERMINATE         = 0
    NOT_TERMINATED              = 1
    TERMINATED_SERVER_TO_CLIENT = 2
    TERMINATED_CLIENT_TO_SERVER = 3
    TERMINATED_BIDIRECTIONAL    = 4
    PERMENANTLY_TERMINATED      = 5
    TERMINATION_STATE_UNKNOWN   = 6

class LinkForwardingDirection(IntEnum):
    BIDIRECTIONAL  = 0
    UNIDIRECTIONAL = 1
    UNKNOWN        = 2

def compose_topology_id(context_uuid : str, topology_uuid : str) -> Dict:
    return {'contextId': context_uuid, 'topology_uuid': topology_uuid}

def compose_service_id(context_uuid : str, service_uuid : str) -> Dict:
    return {'contextId': context_uuid, 'service_uuid': service_uuid}

def compose_endpoint_id(topology_id : Dict, device_uuid : str, endpoint_uuid : str) -> Dict:
    return {'topology_id': topology_id, 'device_id': device_uuid, 'endpoint_uuid': endpoint_uuid}

def compose_capacity(value : str, unit : str) -> Dict:
    return {'total-size': {'value': value, 'unit': unit}}

def compose_endpoint(
    endpoint_id : Dict, endpoint_type : str, link_port_direction : int, termination_direction : int,
    termination_state : int, total_potential_capacity : Dict, available_capacity : Dict
) -> Dict:
    return {
        'endpoint_id': endpoint_id, 'endpoint_type': endpoint_type, 'link_port_direction': link_port_direction,
        'termination-direction': termination_direction, 'termination-state': termination_state,
        'total-potential-capacity': total_potential_capacity, 'available-capacity': available_capacity,
    }

def compose_cost_characteristics(cost_name : str, cost_value : str, cost_algorithm : str) -> Dict:
    return {'cost-name': cost_name, 'cost-value': cost_value, 'cost-algorithm': cost_algorithm}

def compose_latency_characteristics(fixed_latency_characteristic : str) -> Dict:
    return {'fixed-latency-characteristic': fixed_latency_characteristic}

def compose_constraint(constraint_type : str, constraint_value : str) -> Dict:
    return {'constraint_type': constraint_type, 'constraint_value': constraint_value}

def compose_device(grpc_device : Device) -> Dict:
    device_uuid = grpc_device.device_id.device_uuid.uuid
    device_type = grpc_device.device_type

    endpoints = []
    for device_endpoint in grpc_device.device_endpoints:
        context_uuid = device_endpoint.endpoint_id.topology_id.context_id.context_uuid.uuid
        topology_uuid = device_endpoint.endpoint_id.topology_id.topology_uuid.uuid
        endpoint_uuid = device_endpoint.endpoint_id.endpoint_uuid.uuid
        endpoint_type = device_endpoint.endpoint_type

        topology_id = compose_topology_id(context_uuid, topology_uuid)
        endpoint_id = compose_endpoint_id(topology_id, device_uuid, endpoint_uuid)

        link_port_direction = LinkPortDirection.BIDIRECTIONAL.value
        termination_direction = TerminationDirection.BIDIRECTIONAL.value
        termination_state = TerminationState.TERMINATED_BIDIRECTIONAL.value
        total_potential_capacity = compose_capacity(200, CapacityUnit.MBPS.value)
        available_capacity = compose_capacity(200, CapacityUnit.MBPS.value)
        endpoint = compose_endpoint(
            endpoint_id, endpoint_type, link_port_direction, termination_direction,
            termination_state, total_potential_capacity, available_capacity)
        endpoints.append(endpoint)

    return {'device_Id': device_uuid, 'device_type': device_type, 'device_endpoints': endpoints}

def compose_link(grpc_link : Link) -> Dict:
    link_uuid = grpc_link.link_id.link_uuid.uuid

    endpoint_ids = []
    for link_endpoint_id in grpc_link.link_endpoint_ids:
        context_uuid = link_endpoint_id.topology_id.context_id.context_uuid.uuid
        topology_uuid = link_endpoint_id.topology_id.topology_uuid.uuid
        device_uuid = link_endpoint_id.device_id.device_uuid.uuid
        endpoint_uuid = link_endpoint_id.endpoint_uuid.uuid
        topology_id = compose_topology_id(context_uuid, topology_uuid)
        endpoint_id = compose_endpoint_id(topology_id, device_uuid, endpoint_uuid)
        endpoint_ids.append({'endpoint_id' : endpoint_id})

    forwarding_direction = LinkForwardingDirection.UNIDIRECTIONAL.value
    total_potential_capacity = compose_capacity(200, CapacityUnit.MBPS.value)
    available_capacity = compose_capacity(200, CapacityUnit.MBPS.value)
    cost_characteristics = compose_cost_characteristics('linkcost', '1', '0')
    latency_characteristics = compose_latency_characteristics('2')

    return {
        'link_Id': link_uuid, 'link_endpoint_ids': endpoint_ids, 'forwarding_direction': forwarding_direction,
        'total-potential-capacity': total_potential_capacity, 'available-capacity': available_capacity,
        'cost-characteristics': cost_characteristics, 'latency-characteristics': latency_characteristics,
    }

def compose_service(grpc_service : Service, algorithm : Dict) -> Dict:
    context_uuid = grpc_service.service_id.service_id.context_id.context_uuid.uuid
    service_uuid = grpc_service.service_id.service_id.service_uuid.uuid

    service_id = compose_service_id(context_uuid, service_uuid)
    service_type = grpc_service.service_type

    endpoint_ids = []
    for service_endpoint_id in grpc_service.service_endpoint_ids:
        context_uuid = service_endpoint_id.topology_id.context_id.context_uuid.uuid
        topology_uuid = service_endpoint_id.topology_id.topology_uuid.uuid
        device_uuid = service_endpoint_id.device_id.device_uuid.uuid
        endpoint_uuid = service_endpoint_id.endpoint_uuid.uuid
        topology_id = compose_topology_id(context_uuid, topology_uuid)
        endpoint_id = compose_endpoint_id(topology_id, device_uuid, endpoint_uuid)
        endpoint_ids.append(endpoint_id)

    constraints = []
    for service_constraint in grpc_service.service_constraints:
        if service_constraint.WhichOneof('constraint') != 'custom':
            MSG = 'Constraint({:s}) not supported'
            str_constraint = grpc_message_to_json_string(service_constraint)
            raise NotImplementedError(MSG.format(str_constraint))
        constraint_type = service_constraint.custom.constraint_type
        constraint_value = service_constraint.custom.constraint_value
        constraints.append(compose_constraint(constraint_type, constraint_value))

    # algorithm to be executed
    algorithm_id = algorithm.get('id', 'SP')

    # if multiple services included in the request, prevent contention
    # If true, services are computed one after the other and resources
    # assigned to service i, are considered as used by services i+1..n
    sync_paths = algorithm.get('sync', False)

    k_paths = algorithm.get('k_paths', 1)

    return {
        'serviceId': service_id,
        'serviceType': service_type,
        'service_endpoints_ids': endpoint_ids,
        'service_constraints': constraints,

        'algId': algorithm_id,
        'syncPaths': sync_paths,
        'kPaths': k_paths,
    }
