# 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.

import threading
import logging, threading
from typing import List, Optional, Union
from common.Constants import AGGREGATED_TOPOLOGY_UUID, DOMAINS_TOPOLOGY_UUID
from common.proto.context_pb2 import (
    ConnectionEvent, ContextEvent, ContextId, DeviceEvent, DeviceId, LinkEvent, ServiceEvent, ServiceId,
    SliceEvent, SliceId, TopologyEvent)
from context.client.ContextClient import ContextClient
from context.client.EventsCollector import EventsCollector
from dlt.connector.client.DltConnectorClient import DltConnectorClient
from .tools.ContextMethods import create_abstracted_device_if_not_exists, create_interdomain_entities

LOGGER = logging.getLogger(__name__)

DltRecordIdTypes = Union[DeviceId, SliceId, ServiceId]
EventTypes = Union[
    ContextEvent, TopologyEvent, DeviceEvent, LinkEvent, ServiceEvent, SliceEvent, ConnectionEvent
]

class TopologyAbstractor(threading.Thread):
    def __init__(self) -> None:
        super().__init__(daemon=True)
        self.terminate = threading.Event()

        self.context_client = ContextClient()
        self.dlt_connector_client = DltConnectorClient()
        self.context_event_collector = EventsCollector(self.context_client)

        self.own_context_id : Optional[ContextId] = None
        self.own_abstract_device : Optional[ContextId] = None

    def stop(self):
        self.terminate.set()

    def run(self) -> None:
        self.context_client.connect()
        self.dlt_connector_client.connect()
        self.context_event_collector.start()

        while not self.terminate.is_set():
            event = self.context_event_collector.get_event(timeout=0.1)
            if event is None: continue
            if self.ignore_event(event): continue
            # TODO: filter events resulting from abstraction computation
            # TODO: filter events resulting from updating remote abstractions
            LOGGER.info('Processing Event({:s})...'.format(str(event)))
            dlt_records = self.update_abstraction(event)
            self.send_dlt_records(dlt_records)

        self.context_event_collector.stop()
        self.context_client.close()
        self.dlt_connector_client.close()

    def ignore_event(self, event : EventTypes) -> List[DltRecordIdTypes]:
        if isinstance(event, ContextEvent):
            context_uuid = event.context_id.context_uuid.uuid
            if self.own_context_id is None: return False
            own_context_uuid = self.own_context_id.context_uuid.uuid
            return context_uuid == own_context_uuid
        elif isinstance(event, TopologyEvent):
            context_uuid = event.topology_id.context_id.context_uuid.uuid
            if self.own_context_id is None: return False
            own_context_uuid = self.own_context_id.context_uuid.uuid
            if context_uuid != own_context_uuid: return True
            topology_uuid = event.topology_id.topology_uuid.uuid
            if topology_uuid in {DOMAINS_TOPOLOGY_UUID, AGGREGATED_TOPOLOGY_UUID}: return True
        return False

    def send_dlt_records(self, dlt_records : Union[DltRecordIdTypes, List[DltRecordIdTypes]]) -> None:
        for dlt_record_id in dlt_records:
            if isinstance(dlt_record_id, DeviceId):
                self.dlt_connector_client.RecordDevice(dlt_record_id)
            elif isinstance(dlt_record_id, ServiceId):
                self.dlt_connector_client.RecordService(dlt_record_id)
            elif isinstance(dlt_record_id, SliceId):
                self.dlt_connector_client.RecordSlice(dlt_record_id)
            else:
                LOGGER.error('Unsupported Record({:s})'.format(str(dlt_record_id)))

    def update_abstraction(self, event : Optional[EventTypes] = None) -> List[DltRecordIdTypes]:
        dlt_record_ids_with_changes = []

        if self.own_context_id is None:
            self.own_context_id = create_interdomain_entities(self.context_client)

        if self.own_abstract_device is None:
            self.own_abstract_device = create_abstracted_device_if_not_exists(self.context_client, self.own_context_id)
            dlt_record_ids_with_changes.append(self.own_abstract_device.device_id)

        if event is None:
            # TODO: identify initial status from topology and update endpoints accordingly
            pass
        else:
            # TODO: identify changes from event and update endpoints accordingly
            pass

        return dlt_record_ids_with_changes
