"""
    Programming
    University of Applied Sciences Utrecht
    TICT-V1PROG-15 Project
"""

import datetime
import operator
import os
import sqlite3
import xml.etree.ElementTree as Et

import requests

import classes.CardMachine as CardMachine
import classes.GenerateMechanic as GenerateMechanic
import classes.Mechanic as Mechanic
import classes.Notification as Notification


class PopulateDataLists:
    """
        Static class that reads XML data files to fill data lists with previous data.
        There are three types of data that need to be retrieved and checked before continuing.

        1. NS API data for stations, and to derive CardMachine objects from it.
        2. Mechanic object data saved to XML. This file saves Mechanic attributes to display changes after program went
            offline. If this file is not present, it is created and filled with GenerateMechanic.py
        3. Notification object data saved to XML. This file saves Notification data to save a history of
            events in the program.
    """

    @staticmethod
    def populate_card_machine_list(config: dict) -> list:
        """
            Calls out to NS API to retrieve station information. Creates CardMachine objects based
            on the station information.

            :param config: YAML configuration file

            Check if the function returns a list.
            >>> type(
            ...     PopulateDataLists.populate_card_machine_list(
            ...         "floris.dekruijff@student.hu.nl", "FK7CDKplQPsyOpBuPtkURW8incvUdT3T2ZSVoSkrTRdF7r5ARvCOyQ"
            ...     )
            ... )
            <class 'list'>

            Check if the function returns at least 400 stations (as it should).
            >>> len(
            ...     PopulateDataLists.populate_card_machine_list(
            ...         "floris.dekruijff@student.hu.nl", "FK7CDKplQPsyOpBuPtkURW8incvUdT3T2ZSVoSkrTRdF7r5ARvCOyQ"
            ...     )
            ... ) > 400
            True
         """
        try:
            return_list = []
            response = requests.get(url=config['api']['ns']['url'],
                                    auth=(config['api']['ns']['username'], config['api']['ns']['password'])
                                    )

            # Parse the XML data and derive necessary information from it.
            for element in Et.fromstring(response.text):
                is_in_netherlands = False
                station_name = ""
                latitude = ""
                longitude = ""
                for meta in element:
                    if meta.tag == "Namen":
                        for name in meta:
                            if name.tag == "Lang":
                                station_name = name.text
                    if meta.text == "NL":
                        is_in_netherlands = True
                    if meta.tag == "Lat":
                        latitude = meta.text
                    if meta.tag == "Lon":
                        longitude = meta.text
                # We only want train stations based in The Netherlands, if so create the CardMachine object.
                if is_in_netherlands:
                    return_list.append(CardMachine.CardMachine(station_name, longitude, latitude))
            return return_list
        except requests.exceptions.ConnectionError:
            exit("Failed to establish connection with NS API, please try again.")

    @staticmethod
    def create_table(c):
        c.execute(
            'CREATE TABLE IF NOT EXISTS mechanics('
            'name TEXT, gender TEXT, age TEXT, latitude TEXT, '
            'longitude TEXT, region TEXT, schedule TEXT, '
            'availability TEXT, shift TEXT, phone TEXT)'
        )

    @staticmethod
    def data_entry_mechanics(c, conn, name, gender, age, latitude, longitude, region, schedule, availability, shift,
                             phone):
        name = name.replace('"', '')
        gender = gender.replace('"', '')
        region = region.replace('"', '')
        availability = availability.replace('"', '')
        shift = shift.replace('"', '')
        phone = phone.replace('"', '')
        c.execute(
            "INSERT INTO mechanics VALUES('{}','{}','{}','{}','{}','{}','{}','{}','{}','{}')".format(
                name, gender, age, latitude, longitude, region, schedule, availability, shift, phone))
        conn.commit()

    @staticmethod
    def populate_mechanic_list(mechanic_file):
        """
            Reads mechanic_file if it's present and has data in it.
            If it is empty Mechanics will be generated by GenerateMechanic and written to the XML file.
            If it has data in it it will be parsed and returned as list.

            :param mechanic_file: string to the location of the mechanic XML file

            Check if the function returns a list.
            >>> type(
            ...     PopulateDataLists.populate_mechanic_list(
            ...         "../mechanics.xml"
            ...     )
            ... )
            <class 'list'>

            Check if the function returns at least 12 mechanics (one per province).
            >>> len(
            ...     PopulateDataLists.populate_mechanic_list(
            ...         "../mechanics.xml"
            ...     )
            ... ) > 12
            True
         """

        conn = sqlite3.connect('nsdefect.db')
        c = conn.cursor()
        PopulateDataLists.create_table(c)

        return_list = []
        if os.path.isfile(mechanic_file):  # Checks if file is present.
            if os.stat(mechanic_file).st_size == 0:  # Checks if file has data in it
                root = Et.Element("mechanics")
                for province in GenerateMechanic.GenerateMechanic.regions:
                    for amount in range(5):
                        # If the file was empty or not present Mechanics will be generated and written to the file.
                        mechanic = GenerateMechanic.GenerateMechanic.generate_mechanic(province)
                        return_list.append(mechanic)

                        PopulateDataLists.data_entry_mechanics(c, conn, mechanic.name, mechanic.gender, mechanic.age,
                                                               mechanic.latitude, mechanic.longitude, mechanic.region,
                                                               mechanic.schedule, mechanic.availability, mechanic.shift,
                                                               mechanic.phone_number)

                        mec = Et.SubElement(root, "mechanic")
                        Et.SubElement(mec, "name").text = str(mechanic.name)
                        Et.SubElement(mec, "gender").text = str(mechanic.gender)
                        Et.SubElement(mec, "age").text = str(mechanic.age)
                        Et.SubElement(mec, "latitude").text = str(mechanic.latitude)
                        Et.SubElement(mec, "longitude").text = str(mechanic.longitude)
                        Et.SubElement(mec, "region").text = str(mechanic.region)
                        Et.SubElement(mec, "schedule").text = str(mechanic.schedule)
                        Et.SubElement(mec, "availability").text = str(mechanic.availability)
                        Et.SubElement(mec, "shift").text = str(mechanic.shift)
                        Et.SubElement(mec, "phone").text = str(mechanic.phone_number)
                tree = Et.ElementTree(root)
                tree.write(mechanic_file)
                c.close()
                conn.close()
            else:
                # If there is data in the XML file it's being parsed and new Mechanic objects will be made from it.
                tree = Et.parse(mechanic_file)
                for x in tree.getroot():
                    name = gender = age = latitude = longitude = region = schedule = availability = shift = phone = ""
                    for meta in x:
                        if meta.tag == "name":
                            name = meta.text
                        if meta.tag == "gender":
                            gender = meta.text
                        if meta.tag == "age":
                            age = int(meta.text)
                        if meta.tag == "latitude":
                            latitude = float(meta.text)
                        if meta.tag == "longitude":
                            longitude = float(meta.text)
                        if meta.tag == "region":
                            region = meta.text
                        if meta.tag == "schedule":
                            schedule = meta.text
                        if meta.tag == "availability":
                            availability = meta.text
                        if meta.tag == "shift":
                            shift = meta.text
                        if meta.tag == "phone":
                            phone = meta.text
                    return_list.append(
                        Mechanic.Mechanic(name, gender, age, latitude, longitude, region, schedule, availability, shift,
                                          phone)
                    )
            # Sort the Mechanic list based on the name on alphabetical order.
            return_list.sort(key=operator.attrgetter('name'))
            return return_list
        else:
            # If the file is missing, create it and call the function again.
            open(mechanic_file, 'w')
            return PopulateDataLists.populate_mechanic_list(mechanic_file)

    @staticmethod
    def populate_notification_list(notification_file):
        """
            Reads notification_file if it's present and has data in it.
            If it is empty the basic notifications Element root will be written to it.
            If it has data in it it will be parsed and returned as list.

            :param notification_file: string to the location of the notification XML file

            Check if the function returns a list.
            >>> type(
            ...     PopulateDataLists.populate_notification_list(
            ...         "../notifications.xml"
            ...     )
            ... )
            <class 'list'>
         """
        return_list = []
        if os.path.isfile(notification_file):
            if os.stat(notification_file).st_size == 0:
                notifications = Et.Element("notifications")
                tree = Et.ElementTree(notifications)
                tree.write(notification_file)
            else:
                tree = Et.parse(notification_file)
                for x in tree.getroot():
                    time = message = ""
                    for meta in x:
                        if meta.tag == 'time':
                            time = meta.text
                        if meta.tag == 'message':
                            message = meta.text
                    return_list.append(Notification.Notification(datetime.datetime.strptime(
                        time, "%d-%m-%Y %H:%M"), message))
                return_list.sort(key=operator.attrgetter('time'))
            return return_list
        else:
            open(notification_file, 'w')
            return PopulateDataLists.populate_notification_list(notification_file)
