import connexion
import json

from models.entities import TelemetryData
from services.services import TelemetryDataService
from schemas.schemas import TelemetryDataSchema
from swagger_server.models.error_response import ErrorResponse  # noqa: E501
from swagger_server.models.error_type_enum import ErrorTypeEnum  # noqa: E501
from custom_errors import EntityNotFound, InvalidPayload, BaseCustomError

from swagger_server.models.create_telemetry_data_request import CreateTelemetryDataRequest  # noqa: E501
from swagger_server.models.create_telemetry_data_response import CreateTelemetryDataResponse  # noqa: E501
from swagger_server.models.error_response import ErrorResponse  # noqa: E501
from swagger_server.models.get_telemetry_data_response import GetTelemetryDataResponse  # noqa: E501
from swagger_server.models.update_telemetry_data_request import UpdateTelemetryDataRequest  # noqa: E501
from swagger_server.models.list_telemetry_data_response import ListTelemetryDataResponse  # noqa: E501

telemetry_data_service = TelemetryDataService()
telemetry_data_schema = TelemetryDataSchema()

def create_telemetry_data(body):  # noqa: E501
    """Create Telemetry data tracking

    This operation is used to create new a telemetry data tracking. # noqa: E501

    :param body:
    :type body: dict | bytes

    :rtype: CreateTelemetryDataResponse
    """
    response = None
    response_code = None
    try:
        if not connexion.request.is_json:
            raise InvalidPayload(code="CST002", message="Invalid Request Payload",
                                 details=f"Request payload is not a JSON valid")
        body = CreateTelemetryDataRequest.from_dict(connexion.request.get_json())  # noqa: E501
        entity = TelemetryData(data_id=None, vehicle_id=body.vehicle_id, data_hora=body.data_hora,
                               latitude=body.latitude, longitude=body.longitude, altimeter=body.altimeter,
                               telemetry_value=body.telemetry_value, tipo_sensor=body.tipo_sensor)
        entity = telemetry_data_service.save(entity)
        response = CreateTelemetryDataResponse.from_dict(json.loads(telemetry_data_schema.dumps(entity)))
        response_code = 201

    except BaseCustomError as bce:
        response_code = bce.http_code
        response = bce.to_error_response()
    except Exception as e:
        response_code = 500
        response = ErrorResponse(code="CST0002", type=ErrorTypeEnum.PERSISTENCE,
                                 message="Error on create new TelemetryData", details=str(e))

    return response.to_dict(), response_code

def delete_telemetry_data(data_id):  # noqa: E501
    """Delete Telemetry data tracking

    This operation is used to delete a specific telemetry data. # noqa: E501

    :param data_id: Unique identifier of the Telemetry Data in the database
    :type data_id: dict | bytes

    :rtype: None
    """
    response = None
    response_code = None
    try:
        entity = telemetry_data_service.fetch_by_id(data_id)
        if entity is None:
            raise EntityNotFound(code="CST001", message="TelemetryData not found",
                                 details=f"Unable to find data ID {data_id}")
        telemetry_data_service.delete(data_id)
        response_code = 204
    except BaseCustomError as bce:
        response_code = bce.http_code
        response = bce.to_error_response()
    except Exception as e:
        response_code = 500
        response = ErrorResponse(code="CSM999", type=ErrorTypeEnum.UNKNOWN,
                                 message="Ops.. Unknown error..", details=str(e))
    if response is None:
        return None, response_code
    else:
        return response.to_dict(), response_code

def get_telemetry_data(data_id):  # noqa: E501
    """Get a single telemetry Data info

    This operation is used to retrieve the details of a specific telemetry data. # noqa: E501

    :param data_id: Unique identifier of the Telemetry Data in the database
    :type data_id: dict | bytes

    :rtype: GetTelemetryDataResponse
    """
    response = None
    response_code = None
    try:
        entity = telemetry_data_service.fetch_by_id(entity_id=data_id)
        if entity is None:
            raise EntityNotFound(code="CST001", message="TelemetryData not found",
                                 details=f"Unable to find data ID {data_id}")
        response = GetTelemetryDataResponse.from_dict(json.loads(telemetry_data_schema.dumps(entity)))
        response_code = 200
    except BaseCustomError as bce:
        response_code = bce.http_code
        response = bce.to_error_response()
    except Exception as e:
        response_code = 500
        response = ErrorResponse(code="CSM999", type=ErrorTypeEnum.UNKNOWN,
                                 message="Ops.. Unknown error..", details=str(e))
    return response.to_dict(), response_code

def list_telemetry_data():  # noqa: E501
    """Retrieve a list of telemetry data sended

    Retrieve a list of telemetry data # noqa: E501

    :rtype: List[GetTelemetryDataResponse]
    """
    entities = telemetry_data_service.fetch_all()

    telemetry_data_response_list = []
    for entity in entities:
        telemetry_data_response_list.append(
            GetTelemetryDataResponse.from_dict(json.loads(telemetry_data_schema.dumps(entity))))

    response = ListTelemetryDataResponse(content=telemetry_data_response_list,
                                         total_results=len(telemetry_data_response_list))
    return response.to_dict(), 200

def update_telemetry_data(body, data_id):  # noqa: E501
    """Update Telemetry data tracking

    This operation is used to update the details of a specific telemetry data. # noqa: E501

    :param body:
    :type body: dict | bytes
    :param data_id: Unique identifier of the Telemetry Data in the database
    :type data_id: dict | bytes

    :rtype: None
    """
    response = None
    response_code = None
    try:
        if not connexion.request.is_json:
            raise InvalidPayload(code="CST002", message="Invalid Request Payload",
                                 details=f"Request payload is not a JSON valid")
        body = UpdateTelemetryDataRequest.from_dict(connexion.request.get_json())  # noqa: E501
        entity = telemetry_data_service.fetch_by_id(data_id)
        if entity is None:
            raise EntityNotFound(code="CST001", message="Telemetry Data not found",
                                 details=f"Unable to find data ID {data_id}")

        entity.vehicle_id = body.vehicle_id
        entity.data_hora = body.data_hora
        entity.latitude = body.latitude
        entity.longitude = body.longitude
        entity.altimeter = body.altimeter
        entity.telemetry_value = body.telemetry_value
        entity.tipo_sensor = body.tipo_sensor

        telemetry_data_service.save(entity)
        response_code = 204
    except BaseCustomError as bce:
        response_code = bce.http_code
        response = bce.to_error_response()
    except Exception as e:
        response_code = 500
        response = ErrorResponse(code="CSM999", type=ErrorTypeEnum.UNKNOWN,
                                 message="Ops.. Unknown error..", details=str(e))

    if response is None:
        return None, response_code
    else:
        return response.to_dict(), response_code


#Name:Yu-Wei Chang
#File name: M02CaseStudy
#Purpose: Apply the knowledge and skill in coding if...else and while statements in Python

while True:
    last_name = input("Enter student's last name (enter 'ZZZ' to quit): ")
    if last_name == 'ZZZ':
        break

    first_name = input("Enter student's first name: ")
    gpa = float(input("Enter student's GPA: "))

    if gpa >= 3.5:
        print(f"{first_name} {last_name} has made the Dean's List!")
    elif gpa >= 3.25:
        print(f"{first_name} {last_name} has made the Honor Roll.")
    else :
        print(f"{first_name} {last_name} did not make into Dean's List or Honor Roll.")


from tkinter import filedialog
import datetime
from enum import Enum
import sys
import os
import traceback

class File_Type(Enum):
    PROJ_FILE, PARAM_FILE, DATA_FILE, ALG_FILE, CONFIG_FILE = range(5)

class file_module:
    def __init__(self, heide):
        self.heide = heide
        self.config_file = None
        self.proj_file = None
        self.param_file = None
        self.data_file = None
        self.alg_file = None

    def get_existing_loc(self, entry):
        entry.insert(0, filedialog.askopenfilename())

    def read_file(self, file_name, file_type):
        # 1. open file "file_name"
        # 2. read file
        # 3. set internal variable to keep track of open files
        try:
            file = open(file_name)
            contents = file.read()

            if file_type == File_Type.CONFIG_FILE:
                self.config_file = file_name
            elif file_type == File_Type.PROJ_FILE:
                self.proj_file = file_name
            elif file_type == File_Type.PARAM_FILE:
                self.param_file = file_name
            elif file_type == File_Type.DATA_FILE:
                self.data_file = file_name
            elif file_type == File_Type.ALG_FILE:
                self.alg_file = file_name
            else:
                error = ["Unexpected Error",
                         "Invalid File_Type",
                         ''.join(traceback.format_stack())]
                self.heide.gui_mod.display_error(error[0], error[1])
                self.write_to_error_log_internal(error)
                return ""

            return contents
        except:
            error = sys.exc_info()
            self.heide.gui_mod.display_error(error[0], error[1])
            self.write_to_error_log_internal(error)
            return ""

    def write_file(self, file_name, contents):
        try:
            file = open(file_name, "w")
            file.write(contents)
        except:
            error = sys.exc_info()
            self.heide.gui_mod.display_error(error[0], error[1])
            self.write_to_error_log_internal(error)
            return

    # used when writing errors that occured during a subprocess execution
    def write_to_error_log_subprocess(self, out, err):
        self.write_to_error_log("SUBPROCESS ERROR\n" +
                        "\nSTDOUT: \n" +
                        out.decode("utf-8") +
                        "\nSTDERR: \n" +
                        err.decode("utf-8"))

    # used for errors that occurred internally to heide
    def write_to_error_log_internal(self, error):
        self.write_to_error_log("INTERNAL ERROR\n" +
                                str(error[0]) + "\n" +
                                str(error[1]) + "\n" +
                                str(error[2]) + "\n")

    def write_to_error_log(self, message):
        now = datetime.datetime.now()

        try:
            error_log = open("./logs/error_log", "a")
            error_log.write("-" * 80 + "\n" +
                            str(now) + "\n" +
                            message)
            error_log.close()
        except:
            self.heide.gui_mod.display_error("File Write Error",
                                             "Error while writing to "
                                             "error log.\n" +
                                             str(sys.exc_info()[0]) + "\n")
            self.heide.gui_mod.root.quit()
            sys.exit()

    def open_file_proj(self):
        return self.open_read_file((("HEIDE proj files", "*.heide_proj"),
                               ("All Files", "*")), File_Type.PROJ_FILE)

    def open_file_param(self):
        return self.open_read_file((("HEIDE param files", "*.heide_params"),
                               ("All Files", "*")), File_Type.PARAM_FILE)

    def open_file_data(self):
        return self.open_read_file((("HEIDE data file", "*.heide_data"),
                               ("All Files", "*")), File_Type.ALG_FILE)

    def open_file_alg(self):
        return self.open_read_file((("HEIDE alg files", "*.heide_alg"),
                               ("All Files", "*")), File_Type.DATA_FILE)

    def open_read_file(self, file_types, file_type):
        file_name = filedialog.askopenfilename(filetypes=file_types)

        # For some reason if the user selects cancel on filedialog, the first
        # time it returns as () but then on subsequent calls it is empty.
        if str(file_name) == "" or str(file_name) == "()":
            return ""
        else:
            return self.read_file(file_name, file_type)

    def save_params(self, contents):
        self.save_to_file(File_Type.PARAM_FILE, self.param_file, contents)

    def save_data(self, contents):
        self.save_to_file(File_Type.DATA_FILE, self.data_file, contents)

    def save_alg(self, contents):
        self.save_to_file(File_Type.ALG_FILE, self.alg_file, contents)

    def save_as_params(self, contents):
        self.save_to_file(File_Type.PARAM_FILE, None, contents)

    def save_as_data(self, contents):
        self.save_to_file(File_Type.DATA_FILE, None, contents)

    def save_as_alg(self, contents):
        self.save_to_file(File_Type.ALG_FILE, None, contents)

    def save_to_file(self, file_type, file_name, contents):
        if file_name == None:
            file_name = filedialog.asksaveasfilename()

            # For some reason if the user selects cancel on filedialog, the first
            # time it returns as () but then on subsequent calls it is empty.
            # TODO look into this more?
            if str(file_name) == "" or str(file_name) == "()":
                return

            if file_type == File_Type.PARAM_FILE:
                self.param_file = file_name
                self.heide.gui_mod.param_custom_font.configure(weight="normal")
                self.heide.gui_mod.param_bold = False
            elif file_type == File_Type.DATA_FILE:
                self.data_file = file_name
                self.heide.gui_mod.data_custom_font.configure(weight="normal")
                self.heide.gui_mod.data_bold = False
            elif file_type == File_Type.ALG_FILE:
                self.alg_file = file_name
                self.heide.gui_mod.alg_custom_font.configure(weight="normal")
                self.heide.gui_mod.alg_bold = False
            else:
                error = ["Unexpected Error",
                         "Invalid File_Type.",
                         ''.join(traceback.format_stack())]
                self.heide.gui_mod.display_error(error[0], error[1])
                self.write_to_error_log_internal(error)
                return

        self.write_file(file_name, contents)

        if file_type == File_Type.PARAM_FILE:
            self.heide.gui_mod.param_custom_font.configure(weight="normal")
            self.heide.edit_mod.param_bold = False
        elif file_type == File_Type.DATA_FILE:
            self.heide.gui_mod.data_custom_font.configure(weight="normal")
            self.heide.edit_mod.data_bold = False
        elif file_type == File_Type.ALG_FILE:
            self.heide.gui_mod.alg_custom_font.configure(weight="normal")
            self.heide.edit_mod.alg_bold = False
        else:
            error = ["Unexpected Error",
                     "Invalid File_Type.",
                     ''.join(traceback.format_stack())]
            self.heide.gui_mod.display_error(error[0], error[1])
            self.write_to_error_log_internal(error)
            return

    def create_proj(self, he_src, proj_loc, proj_name,
                    existing_param, existing_data, existing_alg):

        # format file contents so that it is the string form of the
        # json object which holds the data.
        file_contents = "{\n\t" + self.format_settings_entry(he_src) + \
                        "\n\t\"param_file\": " + \
                        self.format_file_entry(proj_loc, proj_name,
                                          File_Type.PARAM_FILE) + \
                        ",\n\t\"data_file\": " + \
                        self.format_file_entry(proj_loc, proj_name,
                                          File_Type.DATA_FILE) + \
                        ",\n\t\"alg_file\": " + \
                        self.format_file_entry(proj_loc, proj_name,
                                          File_Type.ALG_FILE) + \
                        "\n}"

        try:
            os.mkdir(proj_loc + "/" + proj_name)
        except:
            error = sys.exc_info()
            self.heide.gui_mod.display_error(error[0], error[1])
            return

        base_file_name = proj_loc + "/" + proj_name + "/" + proj_name
        proj_file_name = base_file_name + ".heide_proj"
        params_file_name = base_file_name + ".heide_params"
        data_file_name = base_file_name + ".heide_data"
        alg_file_name = base_file_name + ".heide_alg"

        # write project file and set internal variable to keep track of open
        # file
        self.write_file(proj_file_name, file_contents)
        self.proj_file = proj_file_name

        # for the param, data, and alg file
        #   if importing from an existing file read existing file's contents
        #   and then write to project file
        # set internal variable to keep track of open file
        if existing_param != "":
            contents = self.read_file(existing_param, File_Type.PARAM_FILE)
        else:
            contents = "pd = {\t\"p\" : 65537,\n" \
                       "\t\"r\" : 1, \n" \
                       "\t\"L\" : 15, \n" \
                       "\t\"w\" : 64, \n" \
                       "\t\"d\" : 0, \n" \
                       "\t\"security\" : 128} \n" \
                       "\n%+pd\n"

        self.write_file(params_file_name, contents)
        self.param_file = params_file_name

        if existing_data != "":
            contents = self.read_file(existing_data, File_Type.DATA_FILE)
        else:
            contents = ""

        self.write_file(data_file_name, contents)
        self.data_file = data_file_name

        if existing_alg != "":
            contents = self.read_file(existing_alg, File_Type.ALG_FILE)
        else:
            contents = ""

        self.write_file(alg_file_name, contents)
        self.alg_file = alg_file_name

    def format_settings_entry(self, he_src):
        return "\"settings\": {\n\t\t\"he_src\": \"" + he_src + "\"\n\t},"

    def format_file_entry(self, proj_loc, proj_name, file_type):
        if file_type == File_Type.PARAM_FILE:
            return "\"" + proj_loc + "/" + proj_name + "/" + proj_name + \
                   ".heide_params\""
        elif file_type == File_Type.DATA_FILE:
            return "\"" + proj_loc + "/" + proj_name + "/" + proj_name + \
                   ".heide_data\""
        elif file_type == File_Type.ALG_FILE:
            return "\"" + proj_loc + "/" + proj_name + "/" + proj_name + \
                   ".heide_alg\""
        else:
            error = ["Unexpected Error",
                     "Invalid File_Type.",
                     ''.join(traceback.format_stack())]
            self.heide.gui_mod.display_error(error[0], error[1])
            self.write_to_error_log_internal(error)
            return

    def clear_files(self):
        self.proj_file = None
        self.param_file = None
        self.data_file = None
        self.param_file = None


# Definition for singly-linked list.
# class ListNode(object):
#     def __init__(self, x):
#         self.val = x
#         self.next = None

def node_add(l1, l2, carry):
    v1 = l1.val if hasattr(l1, 'val') else 0
    v2 = l2.val if hasattr(l2, 'val') else 0
    carry, s = divmod(v1 + v2 + carry, 10)
    return carry, ListNode(s)

class Solution(object):
    def addTwoNumbers(self, l1, l2):
        """
        :type l1: ListNode
        :type l2: ListNode
        :rtype: ListNode
        """
        node_1 = l1
        node_2 = l2
        ret = ListNode(None)
        node_ret_cur = ret
        carry = 0
        while hasattr(node_1, 'val') or hasattr(node_2, 'val') or carry:
            carry, node_ret_cur.next = node_add(node_1, node_2, carry)
            node_ret_cur = node_ret_cur.next
            node_1 = getattr(node_1, 'next', None)
            node_2 = getattr(node_2, 'next', None)
        r = []
        r_cur = ret
        while r_cur.next:
            r.append(r_cur.next.val)
            r_cur = r_cur.next
        return r