"""
Name: avms_data_consumer
Title: AVMS Data Consumer
Author: Cooper
Date: 019 19 04 2023

Desc:  This module is intended to gather all the related bits of data and either:
- Give the basics thats only needed for destination controllers
- Extract all the data needed for STFTs *gulp*

AVMS describes a series of operations which defines a vehicles route/journey:
- RunMonitoring
    Don't really see the point in this one, as all it does is state the current journey reference and the next and
    the run states of both (RunPattern, DeadRun, RunToPattern)

- PlannedPattern
    This contains the information needed for a journey, such as destination, origin, vias,  all the stops that will
    be visited

- VehicleMonitoring
    This contains the vehicles progress on the journey, this will state the previous stop, the next stop and the
    percentage/distance between them

- JourneyMonitoring
    This contains all the ETAs of the stops generally.
    For the next stop (MonitoredCall), there is an AtStop so could be used to change template s etc

In basic mode, only plannedpattern is subscribed to and this will provide information such as destination, route and in
specific cases destination code.  This mode is suitable for controllers and signs where only limited information is
needed

In full fat mode a lot more has to happen in order to make the data usable:
- Ensure that the pattern reference is stored and used to compare services (Although this is the behaviour of the French
    module that never worked, is it ever safe to assume that any consumed data has matching references?
- Extract the destination, route and other "static" information
- Extract all the stops in a given journey
- Jiggle it so that it can be used

The question is whether this is responsible for importing the subscription portion of this or should it just be given
data

"""
import xmltodict

class AVMSDataConsumer():
    def __init__(self, debug=False):
        self.debug = debug

        self.run_monitoring_dict = None
        self.journey_monitoring_dict = None
        self.planned_pattern_dict = None
        self.vehicle_monitoring_dict = None

        #AVMS shizz.
        self.run_state = None
        self.journey_pattern_ref = None
        self.vehicle_journey_ref = None

        #Stop shizz.
        self.destination_code = None
        self.list_of_stops = None
        self.stops_order = None
        self.current_stop = None
        self.at_stop = None
        self.next_stop = None
        self.origin = None
        self.destination = None
        self.route_number = None        # This is the publishedlinelabel which is the value known to customers according to the spec
        self.route_ref = None
        self.line_name = None
        self.line_ref = None
        self.link_percentage = None

    """
    ###################################################################################################################
    Data Extractors
    """
    def check_journey_pattern_reference(self, operations_dictionary):
        """
        Sanity check to see if the other operations match the pattern reference stated in the run monitoring block.

        For some reason though, the reference is either PatternRef or JourneyPatternRef depending on the operation
        :return:
        """
        if self.journey_pattern_ref != None:
            operations_pattern_ref = None

            try:
                operations_pattern_ref = operations_dictionary["PatternRef"]
            except KeyError:
                pass

            try:
                operations_pattern_ref = operations_dictionary["JourneyPatternRef"]
            except KeyError:
                pass

            if self.journey_pattern_ref == operations_pattern_ref:
                return True

        print("AVMSDC: PatternRef Mismatch")
        return False


    def extract_journey_references(self):
        """
        This grabs several parameters from the Run Monitoring block such as
        - JourneyPatternRef
        - VehicleJourneyRef
        - RunState

        Although what is the difference between patternref and vehiclejourney ref I do not know not does the spec appear
        to say very much about it except it is unique and used to identify things
        :return:
        """
        if self.run_monitoring_dict != None:
            try:
                self.run_state = self.run_monitoring_dict["CurrentRunInfo"]["RunState"]
                self.journey_pattern_ref = self.run_monitoring_dict["CurrentRunInfo"]["JourneyPatternRef"]
                self.vehicle_journey_ref = self.run_monitoring_dict["CurrentRunInfo"]["VehicleJourneyRef"]
            except KeyError as e:
                self.run_state = None
                self.journey_pattern_ref = None
                self.vehicle_journey_ref = None

            if self.debug:
                print("RunState:", self.run_state)
                print("JourneyPatRef:", self.journey_pattern_ref)
                print("VehJourRef:", self.vehicle_journey_ref)

        return self.run_state, self.journey_pattern_ref, self.vehicle_journey_ref

    def extract_destination_code(self):
        """
        Whilst this isn't an official part of the AVMS spec, it is used to set the destination code on a controller
        It is within the PlannedPatternDelivery under DestinationShortName
        :return:
        """
        if self.planned_pattern_dict != None:
            try:
                self.destination_code = self.planned_pattern_dict["DestinationShortName"]
            except KeyError:
                self.destination_code = None

            if self.debug:
                print("DestCode:", self.destination_code)

        return self.destination_code

    def extract_list_of_stops(self):
        """
        Extracts all the stops from planned pattern, this is a dictionary of all the stops where the key is the reference.
        Although this will typically extract the stops in the order in which they are listed, I think this in combination
        of JourneyMonitoring should be the way to assign order.

        Get amount of stops, create a list of that size, the shove in the stops reference relative to their index
        :return:
        """
        if not self.check_journey_pattern_reference(self.planned_pattern_dict):
            return None, None

        stops_dict = {}
        stops_order = [""]

        #Add in the origin!... MAYBE
        # origin_ref = self.planned_pattern_dict["OriginName"]
        # origin = self.planned_pattern_dict["OriginName"]

        if self.planned_pattern_dict != None:
            stops_list = self.planned_pattern_dict["PatternStops"]
            number_of_stops = len(stops_list)
            stops_order = stops_order * number_of_stops

            for stops in stops_list:
                # TODO handle if this isnt a number...
                order = int(stops["Order"]) - 1     #Python lists start at zero, assume that this always start at 1...
                ref = stops["StopPointRef"]
                stop_point_name = stops["StopPointName"]

                stops_order[order] = ref
                stops_dict[ref] = stop_point_name

        if self.debug:
            print("Stops dict", stops_dict)
            print("Stops order", stops_order)

        self.list_of_stops = stops_dict
        self.stops_order = stops_order
        return stops_dict, stops_order

    def extract_current_stop(self):
        """
        Obtains the current stop and next stop from Journey Monitoring.

        So what is current stop at hanover land doesn't really have an equivalent it seems in ITxPT land.
        When a vehicle departs a stop, the next/current stop is the MonitoredCall
        :return:
        """
        # if not self.check_journey_pattern_reference(self.journey_monitoring_dict):
        #     return None, False, None

        if self.journey_monitoring_dict != None:
            current_stop_details = self.journey_monitoring_dict["MonitoredCall"]
            current_stop_ref = current_stop_details["StopPointRef"]
            at_stop = current_stop_details["VehicleAtStop"]
            next_stop_details = self.journey_monitoring_dict["OnwardCalls"][0]
            next_stop_ref = next_stop_details["StopPointRef"]

            if self.debug:
                print("Current stop:", current_stop_details)
                print("Next stop:", next_stop_details)

            self.current_stop = current_stop_ref
            self.at_stop = at_stop
            self.next_stop = next_stop_ref
            return current_stop_ref, at_stop, next_stop_ref
        else:
            return None, False, None

        #return current stop, at stop, next stop

    def extract_journey_details(self):
        """

        :return:
        """
        # if not self.check_journey_pattern_reference(self.planned_pattern_dict):
        #     return None, None, None

        if self.planned_pattern_dict != None:
            destination = self.planned_pattern_dict["DestinationName"]
            origin = self.planned_pattern_dict["OriginName"]
            route_number = self.planned_pattern_dict["PublishedLineLabel"]
            route_ref = self.planned_pattern_dict["RouteRef"]
            line_ref = self.planned_pattern_dict["LineRef"]

            try:
                #LineName is not madatory
                line_name = self.planned_pattern_dict["LineName"]
            except KeyError:
                line_name = ""

            if self.debug:
                print("Destination", destination)
                print("Origin", origin)
                print("Route number", route_number)
                print("RouteRef,LineName,LineRef", route_ref, line_name, line_ref)

            self.route_number = route_number
            self.destination = destination
            self.origin = origin
            self.route_ref = route_ref
            self.line_name = line_name
            self.line_ref = line_ref
            return route_number, destination, origin, route_ref, line_name, line_ref
        else:
            return None, None, None

    """
    ###################################################################################################################
    Operation Parsers
    """
    def extract_ordered_dict_contents(self, ordered_dict):
        """
        This turns an ordered dict list into just a list!
        :param ordered_dict:
        :return:
        """
        temp_dict = {}

        for key, value in ordered_dict.items():
            if type(value) == list:
                temp_list = []
                for value_ in value:
                    contents = self.extract_ordered_dict_contents(value_)        #Recursion :O
                    temp_list.append(contents)
                temp_dict[key] = temp_list
            else:
                temp_dict[key] = value

        return temp_dict

    def parse_run_monitoring(self, raw_xml):
        """
        This operation allows to follow the running service state of the vehicle and identifies if
        the vehicle is logged on a block.
        The operation is addressed via “avms/runmonitoring”.
        RunMonitoringDelivery is distributed at connection, at any change and at least 60sec
        after last distribution.
        :return:
        """
        run_monitoring_dict = {"Name": "RunMonitoring"}

        if self.debug:
            print(raw_xml)

        avms_data = xmltodict.parse(raw_xml, encoding="UTF-8")
        # Strip out some uncessary layers...
        avms_data = avms_data["RunMonitoringDelivery"]["MonitoredRunState"]

        if self.debug:
            print("\n")
            print(avms_data)

        for key, value in avms_data.items():
            if key == "CurrentRunInfo" or key == "NextRunInfo":
                run_monitoring_dict[key] = self.extract_ordered_dict_contents(value)
            else:
                run_monitoring_dict[key] = value

        self.run_monitoring_dict = run_monitoring_dict
        return run_monitoring_dict

    def parse_planned_pattern(self, raw_xml):
        """
        This operation distributes the structure of the patterns runs by the vehicle.
        The operation is addressed via “avms/plannedpattern”.
        PlannedPatternDelivery is distributed at connection for current and next running
        pattern according to the current and next running state (if any), and at any change of
        them (if any).
        :return:
        """
        planned_pattern_dict = {"Name": "PlannedPattern"}

        if self.debug:
            print(raw_xml)

        avms_data = xmltodict.parse(raw_xml, encoding="UTF-8")
        #Strip out some uncessary layers...
        avms_data = avms_data["PlannedPatternDelivery"]["PlannedPattern"]

        if self.debug:
            print("\n")
            print(avms_data)

        for key, value in avms_data.items():
            if key == "Via":
                planned_pattern_dict["PlaceRef"] = value["PlaceRef"]        #I think this is in the wrong place but whatever
                temp_via_list = []
                for via_values in value["PlaceName"]:
                    contents = self.extract_ordered_dict_contents(via_values)
                    temp_via_list.append(contents)
                planned_pattern_dict["Via"] = temp_via_list

            elif key == "PatternStops":
                if value is None:
                    continue
                temp_stops_list = []
                for stops in value["PatternStop"]:
                    if isinstance(stops, dict):
                        contents = self.extract_ordered_dict_contents(stops)
                    else:
                        contents = stops

                    temp_stops_list.append(contents)

                planned_pattern_dict["PatternStops"] = temp_stops_list
            else:
                planned_pattern_dict[key] = value

        self.planned_pattern_dict = planned_pattern_dict
        return planned_pattern_dict


    def parse_vehicle_monitoring(self, raw_xml):
        """
        This operation allows following how the vehicle runs service patterns.
        The operation is addressed via “avms/patternmonitoring”.
        PatternMonitoringDelivery is distributed at connection, at any change and at least
        60sec after last distribution.
        :return:
        """
        vehicle_monitoring_dict = {"Name": "VehicleMonitoring"}

        if self.debug:
            print(raw_xml)

        avms_data = xmltodict.parse(raw_xml, encoding="UTF-8")
        # Strip out some uncessary layers...
        avms_data = avms_data["VehicleMonitoringDelivery"]["VehicleActivity"]
        #TODO handle VehicleActivityCancellation

        if self.debug:
            print("\n")
            print(avms_data)

        for key, value in avms_data.items():
            if key == "ProgressBetweenStops":
                temp_dict = {}
                for key_, value_ in value.items():
                    if key_ == "PreviousCallRef" or key_ == "MonitoredCallRef":
                       contents = self.extract_ordered_dict_contents(value_)
                       temp_dict[key_] = contents
                    else:
                        temp_dict[key_] = value_
                vehicle_monitoring_dict[key] = temp_dict
            else:
                vehicle_monitoring_dict[key] = value

        self.vehicle_monitoring_dict = vehicle_monitoring_dict
        return vehicle_monitoring_dict

    def parse_journey_monitoring(self, raw_xml):
        """
        This operation allows following the timetables of the current Pattern (case of Block
        login).
        The operation is addressed via “avms/journeymonitoring”.
        JourneyMonitoringDelivery is distributed at connection, at any change
        :return:
        """
        journey_monitoring_dict = {"Name": "JourneyMonitoring"}

        if self.debug:
            print(raw_xml)

        avms_data = xmltodict.parse(raw_xml, encoding="UTF-8")
        #Strip out some uncessary layers...
        avms_data = avms_data["JourneyMonitoringDelivery"]["MonitoredJourney"]

        if self.debug:
            print("\n")
            print(avms_data)

        for key, value in avms_data.items():
            if key == "PreviousCalls" or key == "OnwardCalls":
                #This is a list of all the previous calls...
                temp_call_list = []
                try:
                    for sub_key, sub_value in value.items():
                        for previous_call in sub_value:
                            temp_call_list.append(self.extract_ordered_dict_contents(previous_call))

                    journey_monitoring_dict[key] = temp_call_list
                except AttributeError:
                    pass

            elif key == "MonitoredCall":
                journey_monitoring_dict[key] = self.extract_ordered_dict_contents(value)
            else:
                journey_monitoring_dict[key] = value

        self.journey_monitoring_dict = journey_monitoring_dict
        return journey_monitoring_dict


if __name__ == "__main__":
    import os

    adc = AVMSDataConsumer(debug=True)

    test_xml_location = r"C:\Git\HanIP\test_scripts\itxpt\avms_xml"

    if 1:
        print("Parsing Planned Pattern")
        with open(os.path.join(test_xml_location, "PlannedPatternDelivery.xml"), "r") as plannedpatternfile:
            plannedpattern = plannedpatternfile.read()
            print(adc.parse_planned_pattern(plannedpattern))

        print("\n\n")
    else:
        print("Parsing Planned Pattern - Orleans Not in Service")
        with open(os.path.join(test_xml_location, "PlannedPatternDelivery _OrleansNIS.xml"), "r") as plannedpatternfile:
            plannedpattern = plannedpatternfile.read()
            print(adc.parse_planned_pattern(plannedpattern))

        print("\n\n")

    print("Parsing Run Monitoring")
    with open(os.path.join(test_xml_location, "RunMonitoringDelivery.xml"), "r") as runmonitoringfile:
        runmonitoring = runmonitoringfile.read()
        print(adc.parse_run_monitoring(runmonitoring))

    print("\n\n")

    print("Parsing Journey Monitoring")
    with open(os.path.join(test_xml_location, "JourneyMonitoringDelivery.xml"), "r") as journeymonitoringfile:
        journeymonitoring = journeymonitoringfile.read()
        print(adc.parse_journey_monitoring(journeymonitoring))

    print("\n\n")

    print("Parsing Vehicle Monitoring")
    with open(os.path.join(test_xml_location, "VehicleMonitoringDelivery.xml"), "r") as vehiclemonitoringfile:
        vehiclemonitoring = vehiclemonitoringfile.read()
        print(adc.parse_vehicle_monitoring(vehiclemonitoring))

    print("\n\n")

    adc.extract_journey_references()
    adc.extract_destination_code()
    adc.extract_list_of_stops()
    adc.extract_current_stop()
    adc.extract_journey_details()