import tkinter as tk
from tkinter import ttk
import requests as rq
import time
import bs4
import re
from selenium import webdriver
from selenium.webdriver.chrome.options import Options

class Demo1:
    def __init__(self, master):
        self.master = master
        self.frame = tk.Frame(self.master)
        self.button1 = tk.Button(self.frame, text = 'New Window', width = 25, command = self.new_window)
        self.button1.pack()
        self.frame.pack()
        self.tracker = Tracking()

    def new_window(self):
        self.newWindow = tk.Toplevel(self.master)
        self.app = Demo2(self.newWindow, self.tracker)
        
        self.app

    def newUpdate(self):
        print("hello")

class Demo2:
    
    def __init__(self, master, tracker):
        self.master = master
        self.frame = tk.Frame(self.master)
        self.tracker = tracker

        tk.Label(self.frame, text="Carrier:").grid(column = 0, row = 2)
        tk.Label(self.frame, text = "Tracking Number:").grid(column = 0, row = 3)
        tk.Label(self.frame, text = "Origin:").grid(column = 0, row = 4)
        tk.Label(self.frame, text = "Description:").grid(column = 0, row = 5)

        self.cmbCarrier = ttk.Combobox(self.frame,
                                  values = ["USPS",
                                            "Fedex",
                                            "UPS"])
        self.cmbCarrier.grid(column = 1, row = 2)
        self.cmbCarrier.current(0)
        self.frame.pack()

        self.txtTrackNum = tk.Entry(self.frame, width = 20)
        self.txtTrackNum.grid(column = 1, row = 3)

        self.txtOrigin = tk.Entry(self.frame, width = 20)
        self.txtOrigin.grid(column = 1, row = 4)

        self.txtDescription = tk.Entry(self.frame, width = 20)
        self.txtDescription.grid(column = 1, row = 5)

        def addClicked():
            self.tracker.newItem(self.cmbCarrier.get(), self.txtTrackNum.get(), self.txtOrigin.get(), self.txtDescription.get())
            Demo1.newUpdate(self.master)
            self.master.destroy()
            
        self.btnAdd = tk.Button(self.frame, text="Add", command=addClicked)
        self.btnAdd.grid(column = 0, row = 6, columnspan = 2, sticky = tk.W+tk.E)
        


        
class Tracking:
    def __init__(self):
        self.trackList = []
    
    #  https://stackoverflow.com/questions/53657215/running-selenium-with-headless-chrome-webdriver
    # These options are used to run chrome headless (no gui) and with less overhead
    chrome_options = Options()
    chrome_options.add_argument("--headless")
    chrome_options.add_argument("--disable-extensions")
    chrome_options.add_argument("--disable-gpu")

    #The following are the first part of the tracking URL for the major carriers
    usps = 'https://tools.usps.com/go/TrackConfirmAction?tLabels='
    fedex = 'https://www.fedex.com/apps/fedextrack/?tracknumbers='
    ups = 'https://www.ups.com/track?loc=en_US&tracknum='

    #These headers will stop the redirection loop
    headers = {
        'Accept-Encoding': 'gzip, deflate, sdch',
        'Accept-Language': 'en-US,en;q=0.8',
        'Upgrade-Insecure-Requests': '1',
        'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Safari/537.36',
        'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8',
        'Cache-Control': 'max-age=0',
        'Connection': 'keep-alive',
    }

    """
    Creates a new package to track
    Prompts the user for package information and calls the relevant tracking method depending on the carrier
    """
    def newItem(self, carrier, trackingNum, origin, description):
        newPkg = Trackable(carrier, trackingNum, origin, description)
        self.trackList.append(newPkg)
        
        if(carrier == 'USPS'):
            trackUSPS(newPkg)
        elif(carrier == 'Fedex'):
            self.trackFedex(newPkg)
        elif(carrier == 'UPS'):
            trackUPS(newPkg)
        else:
            return

    """
    Tracks USPS packages and then calls the print method to display the results

    Uses the requests python package to download the html file for the tracking page
    Doesn't use selenium because requests is faster, but it may need selenium in the future

    Parses the HTML file using BeautifulSoup
    """
    def trackUSPS(self, newPkg):
        regexPat = "(\n)(.*)(\n\n)"
        
        #This will request and save the page... but should add a try/catch block
        uspsHTML = rq.get(usps + newPkg.trackingNum, headers=headers)
        uspsHTML.raise_for_status()
        uspsParser = bs4.BeautifulSoup(uspsHTML.text, 'html.parser')

        statusTag = uspsParser.select('.delivery_status > h2:nth-child(2) > strong:nth-child(1)')
        status = statusTag[0].getText()

        date = ""

        #If the item is delivered, it will not longer have an expected delivery day
        if(status.lower() != 'delivered'):
            dateTag = uspsParser.select('.date')
            print(dateTag)
            day = dateTag[0].get_text()

            monthYearTag = uspsParser.select('.month_year')
            monthYearFull = monthYearTag[0].get_text()
            
            monthYear = re.search(regexPat, monthYearFull).group(2)
            date = day + " " + monthYear

        newPkg.date = date
        newPkg.status = status
        
        printPkg(newPkg)

    """
    Tracks UPS packages and then calls the print method to display the results

    Uses the selenium python package and chromedriver to save the webpage.
    Requires selenium for javascript generation.

    Parses the HTML file using BeautifulSoup
    """
    def trackUPS(self, newPkg):
        #This will request using selenium and save the page
        #TODO add a try catch block
        browser = webdriver.Chrome(options=self.chrome_options)
        browser.get(ups + newPkg.trackingNum)
        upsHTML = browser.page_source
        browser.close()
        
        upsParser = bs4.BeautifulSoup(upsHTML, 'html.parser')

        statusTag = upsParser.select('#stApp_txtPackageStatus')
        status = statusTag[0].getText()

        date = ""

        if(status.lower() == 'in transit' or status.lower() == 'out for delivery today'):
            dateTag = upsParser.select('#stApp_scheduledDelivery')
            date = dateTag[0].getText()

        newPkg.date = date
        newPkg.status = status
        
        self.printPkg(newPkg)

    #Tracks and prints out a fedex package
    def trackFedex(self, newPkg):
        #This will request using selenium and save the page
        #TODO add a try catch block
        browser = webdriver.Chrome(options=self.chrome_options)
        browser.get(self.fedex + newPkg.trackingNum)
        #page is slow to load, so need to wait for it to fully load
        time.sleep(3)
        fedexHTML = browser.page_source
        browser.close()
        
        fedexParser = bs4.BeautifulSoup(fedexHTML, 'html.parser')
        
        statusTag = fedexParser.select('h3.redesignStatusChevronTVC:nth-child(2)')
        status = statusTag[0].getText()
        
        date = ""
        
        if(status.lower() == 'in transit'):
            dateTag = fedexParser.select('.snapshotController_date')
            date = dateTag[0].getText()

        newPkg.date = date
        newPkg.status = status
        
        self.printPkg(newPkg)

    #Prints out the package details to the terminal
    def printPkg(self, pkg):
        print("Package Arriving from: " + pkg.origin + " via " + pkg.carrier + "\n"
              "Description : " + pkg.description + "\n"
              "Tracing Number: " + pkg.trackingNum  + "\n"
              "Status: " + pkg.status + "\n"
              "Arriving on: " + pkg.date + "\n\n")


    #prints all the tracked items in the list object
    def showItems(self):
        for item in trackList:
            printPkg(item)
            

    #updates all of the saved tracking information
    #TODO: Parallelize the tracking if there are a lot of items
    def updateItems(self):
        for item in trackList:
            if(item.carrier == 'USPS'):
                trackUSPS(item)
            if(item.carrier == 'Fedex'):
                trackFedex(item)
            if(item.carrier == 'UPS'):
                trackUPS(item)


"""
Trackable class - class that stores all details of a trackable package

@author Nikolai Hesterberg

TODO: Make the parameters private, add getters and setters 
"""
class Trackable:
    """
    Constructor to build a trackable object

    @param carrier - the carrier of the package, USPS, Fedex, or UPS supported
    @param trackingNum - the tracking number of the package
    @param origin - origin of the package - who shipped it
    @param description - description of the package - what it contains
    """
    def __init__(self, carrier, trackingNum, origin, description):
        self.carrier = carrier
        self.trackingNum = trackingNum
        self.origin = origin
        self.description = description

    """
    Status and Date of the packages.  Store the current status and expected delivery for caching
    """
    status = ""
    date = ""



def main(): 
    root = tk.Tk()
    app = Demo1(root)
    root.mainloop()

if __name__ == '__main__':
    main()
