#!/usr/bin/env python3

# ---------------------------------------------------------------------------------------
# This script lists AirFrance-KLM flights (including price) from an origin
# to a destination using provided date for outbound flight and return flight
#
# Author: Christophe Pauliat
#
# Prereq: register as a developer at https://docs.airfranceklm.com and get your api key
#
# API docs:
# - https://docs.airfranceklm.com/docs/read/opendata/offers/POST_availableoffers_v1
# - https://docs.airfranceklm.com/docs/read/opendata/offers/Errors_Code
#
# Versions:
#     2022-01-04: initial version
#     2022-01-11: Add option --csv
# ---------------------------------------------------------------------------------------

# ---------- imports
import requests
import json
import sys
import argparse
from datetime import datetime

# ---------- Variables
my_api_key          = "xxxxxxxxxxxx"     # register for free at https://docs.airfranceklm.com/docs/read/opendata/offers to get your API key
default_cabin       = "ECONOMY"          # Can contain ECONOMY or PREMIUM or BUSINESS or FIRST or ALL
default_airline     = "AF"               # AF or KL
lowest_price        = 9999
highest_price       = 0
decile1_max_price   = 0

# ---------- Colors for output
# see https://misc.flogisoft.com/bash/tip_colors_and_formatting to customize
COLOR_YELLOW = "\033[93m"
COLOR_RED    = "\033[91m"
COLOR_GREEN  = "\033[32m"
COLOR_NORMAL = "\033[39m"
COLOR_CYAN   = "\033[96m"
COLOR_BLUE   = "\033[94m"
COLOR_GREY   = "\033[90m"

COLOR_TITLE         = COLOR_GREEN
COLOR_DATA          = COLOR_BLUE
COLOR_ITINERARY     = COLOR_YELLOW
COLOR_PRICE1        = COLOR_RED
COLOR_PRICE2        = COLOR_CYAN
COLOR_LINKS         = COLOR_GREY
COLOR_LOWEST_PRICE  = COLOR_GREEN
COLOR_HIGHEST_PRICE = COLOR_RED
COLOR_DECILE1_PRICE = COLOR_YELLOW

# ---------- Functions

# ---- Disable colored output
def disable_colored_output():
  global COLOR_NORMAL
  global COLOR_TITLE
  global COLOR_DATA
  global COLOR_ITINERARY
  global COLOR_PRICE1
  global COLOR_PRICE2
  global COLOR_LINKS
  global COLOR_LOWEST_PRICE
  global COLOR_HIGHEST_PRICE
  global COLOR_DECILE1_PRICE

  COLOR_NORMAL        = ""
  COLOR_TITLE         = ""
  COLOR_DATA          = ""
  COLOR_ITINERARY     = ""
  COLOR_PRICE1        = ""
  COLOR_PRICE2        = ""
  COLOR_LINKS         = ""
  COLOR_LOWEST_PRICE  = ""
  COLOR_HIGHEST_PRICE = ""
  COLOR_DECILE1_PRICE = ""

# ---- Get data using REST APIs
def post_request(origin, destination, date_voyage_aller, date_voyage_retour, airline, cabin):
  url = "https://api.airfranceklm.com/opendata/offers/v1/available-offers/all"

  payload = json.dumps({
    "commercialCabins": [cabin],
    "passengerCount": {
      "ADT": 1,
      "CHD": 0,
      "INF": 0
    },
    "requestedConnections": [
      {
        "departureDate": date_voyage_aller,
        "origin": {
          "airport": {
            "code": origin
          }
        },
        "destination": {
          "airport": {
            "code": destination
          }
        }
      },
      {
        "departureDate": date_voyage_retour,
        "origin": {
          "airport": {
            "code": destination
          }
        },
        "destination": {
          "airport": {
            "code": origin
          }
        }
      }
    ]
  })

  headers = {
    'Content-Type': 'application/json',
    'Accept-Language': 'en-US',
    'AFKL-TRAVEL-Host': airline,
    'AFKL-TRAVEL-Country': 'FR',
    'api-key': my_api_key,
  }

  response = requests.request("POST", url, headers=headers, data=payload)
  mydict   = json.loads(response.text)
  return mydict

# ---- Save all data to file
def save_data_to_file(mydict, filename):
  with open(filename, 'w') as file:
    file.write(json.dumps(mydict, indent=4, sort_keys=False))

# ---- Load all data from file
def load_data_from_file(filename):
  with open(filename, 'r') as file:
    mydict = json.load(file)
  return mydict

# ---- Display taxes breakdown
def display_tax_breakdown(url):
  # print (url)
  # return

  payload = {}

  headers = {
    'Content-Type': 'application/json',
    'Accept-Language': 'en-US',
    'AFKL-TRAVEL-Host': 'AF',
    'AFKL-TRAVEL-Country': 'FR',
    'api-key': my_api_key,
  }

  response = requests.request("GET", url, headers=headers, data=payload)
  mydict   = json.loads(response.text)

  print (COLOR_NORMAL+f"---------- "+COLOR_ITINERARY+"taxes breakdown"+COLOR_NORMAL+" (total taxes = "+COLOR_PRICE2+f"{mydict['price']['totalTaxes']:7.2f}"+COLOR_NORMAL+")")
  taxes = mydict['price']['pricePerPassengerTypes'][0]['taxes']
  for tax in taxes:
    title = f"{tax['code']} ({tax['name']})"
    print (f"{title:54s} : {tax['amount']:7.2f}")
  try:
    yr_tax = mydict['price']['pricePerPassengerTypes'][0]['airTransportationSurcharges'][0]
    title = f"{yr_tax['code']} ({yr_tax['name']})"
    print (f"{title:54s} : {yr_tax['amount']:7.2f}")
  except:
    pass

# ---- Display all data in JSON format
def display_raw_data(mydict):
  print (json.dumps(mydict, indent=4, sort_keys=False))

# ---- Get lowest and highest prices
def get_lowest_and_highest_prices(mydict):
  global lowest_price
  global highest_price
  global decile1_max_price

  try:
    itineraries = mydict["itineraries"]
    for itinerary in itineraries:
      price       = itinerary["flightProducts"][0]["price"]["totalPrice"]
      if price < lowest_price:
        lowest_price = price
      if price > highest_price:
        highest_price = price
    decile1_max_price = lowest_price + (highest_price - lowest_price) / 10
  except:
    pass

# ---- Display only relevant data in formatted output
def display_formatted_data(mydict):
  num_iti = 0
  connection_max_segments = 0
  try:
    itineraries = mydict["itineraries"]

    # First, compute max number of segments for connection1 (outbound flights) and connection2 (return flights)
    for itinerary in itineraries:
      connections = itinerary["connections"]
      connection1 = connections[0]
      connection2 = connections[1]
      if len(connection1["segments"]) > connection_max_segments:
        connection_max_segments = len(connection1["segments"])
      if len(connection2["segments"]) > connection_max_segments:
        connection_max_segments = len(connection2["segments"])

    # Then display information
    for itinerary in itineraries:
      num_iti += 1
      price       = itinerary["flightProducts"][0]["price"]["totalPrice"]
      currency    = itinerary["flightProducts"][0]["price"]["currency"]
      connections = itinerary["connections"]

      print (COLOR_PRICE1+"======================================== "+COLOR_ITINERARY+f"Itinerary {num_iti}"+COLOR_NORMAL+" : Price = "+COLOR_PRICE1+f"{price:.2f} {currency}  ", end="")
      if price == lowest_price:
        print (COLOR_LOWEST_PRICE+" ======== LOWEST PRICE ========")
      elif price == highest_price:
        print (COLOR_HIGHEST_PRICE+" ======== HIGHEST PRICE ========")
      elif price <= decile1_max_price:
        print (COLOR_DECILE1_PRICE+" ======== 1st DECILE PRICE ========")
      else:
        print (COLOR_NORMAL)
      print (COLOR_NORMAL)

      num_con = 0
      for connection in connections:
        price_connection = itinerary["flightProducts"][0]["connections"][num_con]["price"]["totalPrice"]
        num_con += 1
        segments = connection["segments"]
        if num_con == 1 and len(segments) == 1:
          name_con = "Outbound flight : DIRECT"
        elif num_con == 1 and len(segments) > 1:
          name_con = "Outbound flights : WITH CONNECTION"
        elif num_con > 1 and len(segments) == 1:
          name_con = "Return flight : DIRECT"
        elif num_con > 1 and len(segments) > 1:
          name_con = "Return flights : WITH CONNECTION"

        connection_title = COLOR_NORMAL+"---------- "+COLOR_ITINERARY+f"{name_con:34s}"+COLOR_NORMAL+" : price = "+COLOR_PRICE2+f"{price_connection:7.2f} {currency:3s}"+COLOR_NORMAL
        # connection_title uses 67 characters so add 17 spaces to reach 84 characters
        print (f"{connection_title}{' ':17s}",end="")
        if connection_max_segments == 3:
          print (f"{' ':42s}",end="")
        print ("   ",end="")
      print ("")

      for connection in connections:
        segments = connection["segments"]
        for segment in segments:
          line1 = COLOR_TITLE+"Flight   : "+COLOR_DATA+f"{segment['marketingFlight']['carrier']['code']}{segment['marketingFlight']['number']}"
          # line1 uses 17 characters so add 25 spaces to reach 42 characters
          print (f"{line1}{' ':25s}",end="")
        # if less segments than connection_max_segments, add spaces for each missing segment
        for _ in range(connection_max_segments - len(segments)):
          print (f"{' ':42s}",end="")
        print (COLOR_LINKS+" | "+COLOR_NORMAL,end="")
      print ("")

      for connection in connections:
        segments = connection["segments"]
        for segment in segments:
          line2 = COLOR_TITLE+"Departure: "+COLOR_DATA+f"{segment['origin']['code']} at {segment['departureDateTime']}"
          # line2 uses 37 characters so add 5 spaces to reach 42 characters
          print (f"{line2}{' ':5s}",end="")
        # if less segments than connection_max_segments, add spaces for each missing segment
        for _ in range(connection_max_segments - len(segments)):
          print (f"{' ':42s}",end="")
        print (COLOR_LINKS+" | "+COLOR_NORMAL,end="")
      print ("")

      for connection in connections:
        segments = connection["segments"]
        for segment in segments:
          line3 = COLOR_TITLE+"Arrival  : "+COLOR_DATA+f"{segment['destination']['code']} at {segment['arrivalDateTime']}"
          # line3 uses 37 characters so add 5 spaces to reach 42 characters
          print (f"{line3}{' ':5s}",end="")
        # if less segments than connection_max_segments, add spaces for each missing segment
        for _ in range(connection_max_segments - len(segments)):
          print (f"{' ':42s}",end="")
        print (COLOR_LINKS+" | "+COLOR_NORMAL,end="")
      print ("")

      for connection in connections:
        segments = connection["segments"]
        for segment in segments:
          line4 = COLOR_TITLE+"Aircraft : "+COLOR_DATA+f"{segment['marketingFlight']['operatingFlight']['equipmentType']['name']:26s}"
          # line3 uses 37 characters so add 5 spaces to reach 42 characters
          print (f"{line4}{' ':5s}",end="")
        # if less segments than connection_max_segments, add spaces for each missing segment
        for _ in range(connection_max_segments - len(segments)):
          print (f"{' ':42s}",end="")
        print (COLOR_LINKS+" | "+COLOR_NORMAL,end="")
      print ("")

      # If requested, display links for cabin plans
      if args.cabin_plan:
        print ("")
        for connection in connections:
          segments = connection["segments"]
          for segment in segments:
            try:
              flight_number = f"{segment['marketingFlight']['carrier']['code']}{segment['marketingFlight']['number']}"
              link          = f"{segment['marketingFlight']['operatingFlight']['equipmentType']['_links']['information']['href']}"
              print (COLOR_TITLE+f"Cabin plan {flight_number}: "+COLOR_LINKS+f"{link}")
            except:
              pass

      print (COLOR_NORMAL)

      # If requested, display tax breakdown for the itinerary
      if args.tax_breakdown:
        offer_link = itinerary["flightProducts"][0]["_links"]["taxBreakdown"]["href"]
        display_tax_breakdown(offer_link)
        print (COLOR_NORMAL)

  except:
    # No flight available
    print ("No outbound or return flight available !")

# ---- Display minimal data
def display_minimal_data(mydict):
  num_iti = 0
  try:
    itineraries = mydict["itineraries"]
    for itinerary in itineraries:
      num_iti += 1
      price       = itinerary["flightProducts"][0]["price"]["totalPrice"]
      currency    = itinerary["flightProducts"][0]["price"]["currency"]
      connections = itinerary["connections"]

      if price == lowest_price:
        COLOR = COLOR_LOWEST_PRICE
      elif price == highest_price:
        COLOR = COLOR_HIGHEST_PRICE
      elif price <= decile1_max_price:
        COLOR = COLOR_DECILE1_PRICE
      else:
        COLOR = COLOR_NORMAL

      print (COLOR+f"Itinerary {num_iti:3d} : Price = {price:7.2f} {currency}      ",end="")
      num_con = 0
      for connection in connections:
        price_connection = itinerary["flightProducts"][0]["connections"][num_con]["price"]["totalPrice"]
        segments         = connection["segments"]
        connection_name  = segments[0]['departureDateTime']
        num_con += 1

        for segment in segments:
          connection_name = connection_name + "-" + segment['marketingFlight']['carrier']['code'] + segment['marketingFlight']['number']

        print (f"{connection_name:41s}: {price_connection:7.2f} {currency}      ", end="")

      if price == lowest_price:
        print ("LOWEST PRICE")
      elif price == highest_price:
        print ("HIGHEST PRICE")
      elif price <= decile1_max_price:
        print ("1st DECILE PRICE")
      else:
        print (COLOR_NORMAL)

  except:
    # No flight available
    print ("No outbound or return flight available !")

# ---- Display CSV data
def display_csv_data(mydict):
  current_date = datetime.today().strftime('%Y-%m-%d')
  try:
    itineraries = mydict["itineraries"]
    for itinerary in itineraries:
      itinerary_price = itinerary["flightProducts"][0]["price"]["totalPrice"]
      connections     = itinerary["connections"]

      itinerary_name = ""
      connection_name  = []
      connection_price = []
      num_con = 0
      for connection in connections:
        connection_price.append(itinerary["flightProducts"][0]["connections"][num_con]["price"]["totalPrice"])
        connection_array = []
        num_con += 1

        for segment in connection["segments"]:
          connection_array.append(segment['marketingFlight']['carrier']['code'] + segment['marketingFlight']['number'])

        connection_name.append("-".join(connection_array))

      itinerary_name = f"{connection_name[0]}___{connection_name[1]}"

      print (f"{current_date},{itinerary_name},{itinerary_price:.2f},{connection_name[0]},{connection_price[0]:.2f},{connection_name[1]},{connection_price[1]:.2f}")
  except:
    # No flight available
    print (f"{current_date},No flight")

# ========== Main

# ---- parse arguments
parser = argparse.ArgumentParser(description = "Get Flights offers from Air-France-KLM using REST APIs")
group1  = parser.add_mutually_exclusive_group()
group1.add_argument("-r", "--raw", help="displays raw formatted data (JSON)", action="store_true")
group1.add_argument("-s", "--short", help="displays minimal data (short)", action="store_true")
group1.add_argument("-c", "--csv", help="displays minimal data in CSV format", action="store_true")
group2  = parser.add_mutually_exclusive_group()
group2.add_argument("-lf", "--load_from", help="read data from file instead of API request")
group2.add_argument("-sf", "--save_to", help="write raw data to file")
parser.add_argument("-o", "--orig", help="origin airport (3 letters code).")
parser.add_argument("-d", "--dest", help="destination airport (3 letters code).")
parser.add_argument("-od", "--odate", help="date for outward flight (YYYY-MM-DD)")
parser.add_argument("-rd", "--rdate", help="date for return flight (YYYY-MM-DD)")
parser.add_argument("-al", "--airline", help=f"airline code (AF or KL). Default is {default_airline}.", choices=["AF","KL"])
parser.add_argument("-nc", "--nocolor", help="Disable colored output", action="store_true")
parser.add_argument("-tx", "--tax_breakdown", help="Display taxes breakdown", action="store_true")
parser.add_argument("-cp", "--cabin_plan", help="Display cabin plans links", action="store_true")
parser.add_argument("-cb", "--cabin", help=f"cabin (ECONOMY or PREMIUM or BUSINESS or FIRST). Default is {default_cabin}.", choices=["ECONOMY","PREMIUM","BUSINESS","FIRST"])
args = parser.parse_args()

if args.load_from and (args.orig or args.dest or args.odate or args.rdate):
  parser.error("-lf/--load_from cannot be used with -o/--orig, -d/--dest, -od/--odate or -rd/--rdate !")

if args.load_from == None and (args.orig == None or args.dest == None or args.odate == None or args.rdate == None):
  parser.error("Arguments -o/--orig, -d/--dest, -od/--odate and -rd/--rdate are mandatory if -lf/--load_form not provided !")

if args.nocolor:
  disable_colored_output()

# ---- get data from live API request or from saved file
if args.load_from:
  mydict = load_data_from_file(args.load_from)
else:
  if args.airline:
    airline = args.airline.upper()
  else:
    airline = default_airline
  if args.cabin:
    cabin = args.cabin.upper()
  else:
    cabin = default_cabin
  mydict = post_request(origin=args.orig.upper(), destination=args.dest.upper(), date_voyage_aller=args.odate, date_voyage_retour=args.rdate, airline=airline, cabin=cabin)
  if args.save_to:
    save_data_to_file(mydict, args.save_to)

# ---- display data with selected format
get_lowest_and_highest_prices(mydict)

if args.raw:
  display_raw_data(mydict)
elif args.short:
  display_minimal_data(mydict)
elif args.csv:
  display_csv_data(mydict)
else:
  display_formatted_data(mydict)

# ---- happy end
exit(0)


import math
from queue import PriorityQueue
import time
import pygame as pyg
import numpy as np
from sortedcollections import OrderedSet

obstacle_points = []

# size of the map
X_SIZE = 600
Y_SIZE = 250

def find_intersection_pt(a1, a2, intercept1, intercept2, b1, b2):
    A = np.array([[a1, b1], [a2, b2]])
    B = np.array([[intercept1], [intercept2]])
    X = np.linalg.solve(A, B)
    return (X[0][0], X[1][0])

# create map with obstacle
def create_obstacle_map(clearance):
    points = OrderedSet()
    x_range = np.arange(0, X_SIZE+1, 0.5)
    y_range1 = np.arange(0, clearance+1, 0.5)
    y_range2 = np.arange(Y_SIZE - clearance, Y_SIZE + 1, 0.5)
    for xp in x_range:
        for yp in y_range1:
            points.add((xp,yp))
        for yp in y_range2:
            points.add((xp,yp))

    y_range = np.arange(0, Y_SIZE + 1, 0.5)
    x_range1 = np.arange(0, clearance+1, 0.5)
    x_range2 = np.arange(X_SIZE - clearance, X_SIZE + 1, 0.5)
    for yp in y_range:
        for xp in x_range1:
            points.add((xp,yp))
        for xp in x_range2:
            points.add((xp,yp))

    # Rectangles and clearance
    x_range = np.arange(100 - clearance, 150 + clearance + 1, 0.5)
    y_range = np.arange(0, Y_SIZE+1, 0.5)
    for xp in x_range:
        for yp in y_range:
            if yp <= (100 + clearance) or yp >= (150 - clearance):
                points.add((xp,yp))

    # triangle
    x_range = np.arange(460-clearance, 510+2*clearance, 0.5)
    y_range = np.arange(0, Y_SIZE, 0.5)

    m = 2
    b1, b2 = -895, 1145

    c1 = b1 + clearance * (math.sqrt(pow(m,2) + 1))
    c2 = b1 - clearance * (math.sqrt(pow(m,2) + 1))
    c3 = b2 + clearance * (math.sqrt(pow(m,2) + 1))
    c4 = b2 - clearance * (math.sqrt(pow(m,2) + 1))

    for xp in x_range:
        for yp in y_range:
            if (-m*xp+yp >= min(c1, c2)) and (m*xp+yp <= max(c3, c4)):
                    points.add((xp,yp))

    triangle_p1 = [460 - clearance, 225 + clearance]
    triangle_p2 = find_intersection_pt(m, 0, max(c3, c4), 225 + clearance, 1, 1)
    triangle_p3 = find_intersection_pt(-m, m, min(c1, c2), max(c3, c4), 1, 1)
    triangle_p4 = find_intersection_pt(-m, 0, min(c1, c2), 25 - clearance, 1, 1)
    triangle_p5 = [460 - clearance, 25 - clearance]

    triangle_pts = [triangle_p1, triangle_p2, triangle_p3, triangle_p4, triangle_p5]

    # Hexagon
    x_range = np.arange(300 - int(64.95) - clearance, 300 + int(64.95) + clearance, 0.5)
    y_range = np.arange(125 - 75 - clearance, 125 + 75 + clearance, 0.5)

    m = 15/26
    b1, b2, b3, b4 = 26.92, 373.07, 223.07, -123.21

    c1 = b1 + clearance * (math.sqrt(pow(m,2) + 1))
    c2 = b1 - clearance * (math.sqrt(pow(m,2) + 1))
    c3 = b2 + clearance * (math.sqrt(pow(m,2) + 1))
    c4 = b2 - clearance * (math.sqrt(pow(m,2) + 1))
    c5 = b3 + clearance * (math.sqrt(pow(m,2) + 1))
    c6 = b3 - clearance * (math.sqrt(pow(m,2) + 1))
    c7 = b4 + clearance * (math.sqrt(pow(m,2) + 1))
    c8 = b4 - clearance * (math.sqrt(pow(m,2) + 1))

    for xp in x_range:
        for yp in y_range:
            if  (yp - m*xp - max(c1, c2)) <= 0 and (yp + m*xp - max(c3, c4)) <= 0 and (yp - m*xp - min(c7, c8)) >= 0 and (yp + m*xp + min(c5, c6)) >= 0:
                points.add((xp,yp))

    hexagon_p1 = find_intersection_pt(-m, m, max(c1, c2), max(c3, c4), 1, 1)
    hexagon_p2 = find_intersection_pt(m, 1, max(c3, c4), 300 + 64.95 + clearance, 1, 0)
    hexagon_p3 = find_intersection_pt(1, -m, 300 + 64.95 + clearance, min(c7, c8), 0, 1)
    hexagon_p4 = find_intersection_pt(m, -m, min(c5, c6), min(c7, c8), 1, 1)
    hexagon_p5 = find_intersection_pt(m, 1, min(c5, c6), 300 - 64.95 -clearance, 1, 0)
    hexagon_p6 = find_intersection_pt(1, -m, 300 - 64.95-clearance, max(c1, c2), 0, 1)

    hexagon_pts = [hexagon_p1, hexagon_p2, hexagon_p3, hexagon_p4, hexagon_p5, hexagon_p6]

    return points, hexagon_pts, triangle_pts

# Validating input values
def get_input():
    clearance = int(input("Enter the clearance of the mobile robot: "))
    rad = int(input("Enter the radius of the mobile robot: "))

    # Create Obstacle based on the clearance and radius of the robot
    obstacle_points, hexagon_pts, triangle_pts = create_obstacle_map(clearance + rad)
    while True:
        l = int(input("Enter the step size L of the mobile robot (between 1-10): "))
        if l>=1 and l<=10:
            break
        else:
            print("Incorrect step size. Please enter positive step size between 1 and 10")

    accept_start_node, accept_goal_node = True, True
    while accept_start_node:
        start_x = int(input("Enter start x: "))
        start_y = int(input("Enter start y: "))
        start_node = (start_x, start_y)

        if start_node not in obstacle_points and start_x < X_SIZE and start_y < Y_SIZE:
            accept_start_node = False
            while True:
                start_theta = int(input("Enter orientation of the start node in degrees (between 0 to 360 degrees): "))
                if start_theta % 30 == 0:
                    break
                else:
                    print("Incorrect theta value. Please enter positive theta in multiple of 30")
            start_node = (start_x, start_y, start_theta)
        else:
            print("Entered start node is either an obstacle or out of map. Please enter a valid co-ordinate...")

    while accept_goal_node:
        goal_x = int(input("Enter goal x: "))
        goal_y = int(input("Enter goal y: "))
        goal_node = (goal_x, goal_y)
        if goal_node not in obstacle_points and goal_x < X_SIZE and goal_y < Y_SIZE:
            accept_goal_node = False
            while True:
                goal_theta = int(input("Enter orientation of the goal node in degrees (between 0 to 360 degrees): "))
                if goal_theta % 30 == 0:
                    break
                else:
                    print("Incorrect theta value. Please enter positive theta in multiple of 30")

            goal_node = (goal_x, goal_y, goal_theta)
        else:
            print("Entered goal node is either an obstacle or out of map. Please enter another co-ordinate...")

    return start_node, goal_node, clearance, l, rad, obstacle_points, hexagon_pts, triangle_pts

def euclidean_distance(pt1, pt2):
    distance = math.sqrt(pow((pt1[0] - pt2[0]), 2) + pow((pt1[1] - pt2[1]),2))
    return round(distance, 2)

def round_nearest(val):
    if val-int(val)<0.5:
        val=int(val)
    elif val-int(val) == 0.5:
        val=val
    else:
        val = int(val)+1

    return val

def move_right(curr_node):
    global goal_reached
    current_point = (curr_node[3][0], curr_node[3][1])
    next_point = (current_point[0] + l, current_point[1])
    next_angle = curr_node[3][2]
    if next_angle >= 360:
        next_angle-=360
    if next_angle < 0:
        next_angle+=360

    # round off next point to nearest 0.5
    next_point = (round_nearest(next_point[0]), round_nearest(next_point[1]), next_angle)

    ## Obstacle with clearance
    if visited_nodes[int(next_point[0]*2)][int(next_point[1]*2)][int(next_point[2]/30)] != 1 and (next_point[0],next_point[1]) not in obstacle_points:
        cost_to_come = curr_node[2] + l
        cost_to_go = euclidean_distance(next_point, goal_pt)
        total_cost =  cost_to_come + cost_to_go
        next_node = (total_cost ,cost_to_go, cost_to_come, next_point)
        for i in range(map_queue.qsize()):
            if map_queue.queue[i][3] == next_point:
                if map_queue.queue[i][0] > total_cost:
                    map_queue.queue[i] = next_node
                    parent_child_info[next_point] = curr_node[3]
                    visited_pts.append(next_point)
                    return
                else:
                    return
        map_queue.put(next_node)
        parent_child_info[next_point] = curr_node[3]
        visited_pts.append(next_point)
        if cost_to_go < 1.5:
            goal_reached = True

def move_plus_30(curr_node):
    global goal_reached
    current_point = (curr_node[3][0], curr_node[3][1])
    next_point = (current_point[0] + l*math.cos(np.deg2rad(curr_node[3][2]) + math.pi/6)), current_point[1] + l*math.sin(np.deg2rad(curr_node[3][2]) + math.pi/6)
    next_angle = curr_node[3][2] + 30
    if next_angle >= 360:
        next_angle-=360
    if next_angle < 0:
        next_angle+=360
    # round off next point to nearest 0.5
    next_point = (round_nearest(next_point[0]), round_nearest(next_point[1]), next_angle)

    ## Obstacle with clearance
    if visited_nodes[int(next_point[0]*2)][int(next_point[1]*2)][int(next_point[2]/30)] != 1 and (next_point[0],next_point[1]) not in obstacle_points:
        cost_to_come = curr_node[2] + l
        cost_to_go = euclidean_distance(next_point, goal_pt)
        total_cost = cost_to_come + cost_to_go
        next_node = (total_cost, cost_to_go, cost_to_come, next_point)
        for i in range(map_queue.qsize()):
            if map_queue.queue[i][3] == next_point:
                if map_queue.queue[i][0] > total_cost:
                    map_queue.queue[i] = next_node
                    parent_child_info[next_point] = curr_node[3]
                    visited_pts.append(next_point)
                    return
                else:
                    return
        map_queue.put(next_node)
        parent_child_info[next_point] = curr_node[3]
        visited_pts.append(next_point)
        if cost_to_go < 1.5:
            goal_reached = True

def move_plus_60(curr_node):
    global goal_reached
    current_point = (curr_node[3][0], curr_node[3][1])
    next_point = ((current_point[0] + l*math.cos(np.deg2rad(curr_node[3][2]) + math.pi/3)), current_point[1] + l*math.sin(np.deg2rad(curr_node[3][2]) + math.pi/3))
    next_angle = curr_node[3][2] + 60
    if next_angle >= 360:
        next_angle-=360
    if next_angle < 0:
        next_angle+=360
    # round off next point to nearest 0.5
    next_point = (round_nearest(next_point[0]), round_nearest(next_point[1]), next_angle)

    ## Obstacle with clearance
    if visited_nodes[int(next_point[0]*2)][int(next_point[1]*2)][int(next_point[2]/30)] != 1 and (next_point[0],next_point[1]) not in obstacle_points:
        cost_to_come = curr_node[2] + l
        cost_to_go = euclidean_distance(next_point, goal_pt)
        total_cost = cost_to_come + cost_to_go
        next_node = (total_cost, cost_to_go, cost_to_come, next_point)
        for i in range(map_queue.qsize()):
            if map_queue.queue[i][3] == next_point:
                if map_queue.queue[i][0] > total_cost:
                    map_queue.queue[i] = next_node
                    parent_child_info[next_point] = curr_node[3]
                    visited_pts.append(next_point)
                    return
                else:
                    return
        map_queue.put(next_node)
        parent_child_info[next_point] = curr_node[3]
        visited_pts.append(next_point)
        if cost_to_go < 1.5:
            goal_reached = True

def move_minus_30(curr_node):
    global goal_reached
    current_point = (curr_node[3][0], curr_node[3][1])
    next_point = ((current_point[0] + l*math.cos(np.deg2rad(curr_node[3][2])-math.pi/6)), current_point[1] + l*math.sin(np.deg2rad(curr_node[3][2])-math.pi/6))
    next_angle = curr_node[3][2] - 30
    if next_angle >= 360:
        next_angle-=360
    if next_angle < 0:
        next_angle+=360
    # round off next point to nearest 0.5
    next_point = (round_nearest(next_point[0]), round_nearest(next_point[1]), next_angle)

    ## Obstacle with clearance
    if visited_nodes[int(next_point[0]*2)][int(next_point[1]*2)][int(next_point[2]/30)] != 1 and (next_point[0],next_point[1]) not in obstacle_points:
        cost_to_come = curr_node[2] + l
        cost_to_go = euclidean_distance(next_point, goal_pt)
        total_cost = cost_to_come + cost_to_go
        next_node = (total_cost, cost_to_go, cost_to_come, next_point)
        for i in range(map_queue.qsize()):
            if map_queue.queue[i][3] == next_point:
                if map_queue.queue[i][0] > total_cost:
                    map_queue.queue[i] = next_node
                    parent_child_info[next_point] = curr_node[3]
                    visited_pts.append(next_point)
                    return
                else:
                    return
        map_queue.put(next_node)
        parent_child_info[next_point] = curr_node[3]
        visited_pts.append(next_point)
        if cost_to_go < 1.5:
            goal_reached = True

def move_minus_60(curr_node):
    global goal_reached
    current_point = (curr_node[3][0], curr_node[3][1])
    next_point = ((current_point[0] + l*math.cos(np.deg2rad(curr_node[3][2])-math.pi/3)), current_point[1] + l*math.sin(np.deg2rad(curr_node[3][2])-math.pi/3))
    next_angle = curr_node[3][2] - 60
    if next_angle >= 360:
        next_angle-=360
    if next_angle < 0:
        next_angle+=360
    # round off next point to nearest 0.5
    next_point = (round_nearest(next_point[0]), round_nearest(next_point[1]), next_angle)

    ## Obstacle with clearance
    if visited_nodes[int(next_point[0]*2)][int(next_point[1]*2)][int(next_point[2]/30)] != 1 and (next_point[0],next_point[1]) not in obstacle_points:
        cost_to_come = curr_node[2] + l
        cost_to_go = euclidean_distance(next_point, goal_pt)
        total_cost =  cost_to_come + cost_to_go
        next_node = (total_cost , cost_to_go, cost_to_come, next_point)
        for i in range(map_queue.qsize()):
            if map_queue.queue[i][3] == next_point:
                if map_queue.queue[i][0] > total_cost:
                    map_queue.queue[i] = next_node
                    parent_child_info[next_point] = curr_node[3]
                    visited_pts.append(next_point)
                    return
                else:
                    return
        map_queue.put(next_node)
        parent_child_info[next_point] = curr_node[3]
        visited_pts.append(next_point)
        if cost_to_go < 1.5:
            goal_reached = True

# function to track back from the goal node to the start node to get the shortest path

def back_tracking(path, initial_state, curr_val):
    optimal_path = []
    optimal_path.append(curr_val)
    parent_path = (curr_val)
    while parent_path != initial_state:
        parent_path = path[parent_path]
        optimal_path.append(parent_path)

    optimal_path.reverse()
    return optimal_path

# to flip the coordinates because of the origin change in pygame
def flip_points(points, height):
    return (points[0], height - points[1])

def flip_object_points(points, height, object_height):
    return (points[0], height - points[1] - object_height)

def draw_arrow(window, l_color, tri_color, initial, final, triangle_radius):
    pyg.draw.line(window, l_color, initial, final, 1)
    angle = math.degrees(math.atan2(initial[1]-final[1], final[0]-initial[0]))+90
    pyg.draw.polygon(window, tri_color, ((final[0]+triangle_radius*math.sin(math.radians(angle)), final[1]+triangle_radius*math.cos(math.radians(angle))),
                                           (final[0]+triangle_radius*math.sin(math.radians(angle-120)),
                                            final[1]+triangle_radius*math.cos(math.radians(angle-120))),
                                           (final[0]+triangle_radius*math.sin(math.radians(angle+120)), final[1]+triangle_radius*math.cos(math.radians(angle+120)))))

def pygame_visualization(visited_nodes, shortest_path, obstacle_points, hexagon_points, triangle_points):
    pyg.init()
    window = pyg.display.set_mode((X_SIZE,Y_SIZE))

    obstacle_color = "red"
    clearance_color = "pink"
    condition = True
    clock = pyg.time.Clock()

    rect2_clearance = flip_object_points([100 - clearance, 0], Y_SIZE, 100+clearance)
    rect1_clearance = flip_object_points([100 - clearance, 150 - clearance], Y_SIZE, 100+clearance)
    rect2_original = flip_object_points([100, 0], Y_SIZE, 100)
    rect1_original = flip_object_points([100, 150], Y_SIZE, 100)

    triangle1_clearance = flip_points([triangle_points[0][0], triangle_points[0][1]], Y_SIZE)
    triangle2_clearance = flip_points([triangle_points[1][0], triangle_points[1][1]], Y_SIZE)
    triangle3_clearance = flip_points([triangle_points[2][0], triangle_points[2][1]], Y_SIZE)
    triangle4_clearance = flip_points([triangle_points[3][0], triangle_points[3][1]], Y_SIZE)
    triangle5_clearance = flip_points([triangle_points[4][0], triangle_points[4][1]], Y_SIZE)

    triangle1 = (460, 25)
    triangle2 = (460, 225)
    triangle3 = (510, 125)

    hexagon1_clearance = hexagon_points[0]
    hexagon2_clearance = hexagon_points[1]
    hexagon3_clearance = hexagon_points[2]
    hexagon4_clearance = hexagon_points[3]
    hexagon5_clearance = hexagon_points[4]
    hexagon6_clearance = hexagon_points[5]

    hexagon1_org = (235,87.5)
    hexagon2_org = (300,50)
    hexagon3_org = (365,87.5)
    hexagon4_org = (365,162.5)
    hexagon5_org = (300,200)
    hexagon6_org = (235,162.5)

    while condition:
        for loop in pyg.event.get():
            if loop.type == pyg.QUIT:
                condition = False

        pyg.draw.rect(window, clearance_color, pyg.Rect(rect2_clearance[0], rect2_clearance[1], 50 + 2*clearance, 100 + clearance))
        pyg.draw.rect(window, clearance_color, pyg.Rect(rect1_clearance[0], rect1_clearance[1], 50 + 2*clearance, 100 + clearance))
        pyg.draw.rect(window, obstacle_color, pyg.Rect(rect2_original[0], rect2_original[1], 50, 100))
        pyg.draw.rect(window, obstacle_color, pyg.Rect(rect1_original[0], rect1_original[1], 50, 100))

        pyg.draw.polygon(window, clearance_color, ((triangle1_clearance),(triangle2_clearance),(triangle3_clearance), (triangle4_clearance), (triangle5_clearance)))
        pyg.draw.polygon(window, obstacle_color, ((triangle1),(triangle2),(triangle3)))

        pyg.draw.polygon(window, clearance_color, ((hexagon1_clearance),(hexagon2_clearance),(hexagon3_clearance),(hexagon4_clearance),(hexagon5_clearance),(hexagon6_clearance)))
        pyg.draw.polygon(window, obstacle_color, ((hexagon1_org),(hexagon2_org),(hexagon3_org),(hexagon4_org),(hexagon5_org),(hexagon6_org)))

        pyg.draw.rect(window, clearance_color ,pyg.Rect(0, 0, X_SIZE, clearance))
        pyg.draw.rect(window, clearance_color ,pyg.Rect(0, Y_SIZE - clearance, X_SIZE, clearance))
        pyg.draw.rect(window, clearance_color ,pyg.Rect(0, 0, clearance, Y_SIZE))
        pyg.draw.rect(window, clearance_color ,pyg.Rect(X_SIZE - clearance, 0, clearance, Y_SIZE))

        for node in visited_pts:
            if node in parent_child_info:
                child = parent_child_info[node]
                node = flip_points(node, 250)
                child = flip_points(child, 250)
                draw_arrow(window, "yellow", "white",
                      [node[0], node[1]], [child[0], child[1]], 1)
                pyg.display.flip()
                clock.tick(300)
        for points in shortest_path:
            pyg.draw.circle(window, (0, 255, 255), flip_points(points, 250), radius)
            pyg.display.flip()
            clock.tick(10)
        condition = False
    pyg.quit()

# Obtaining and validating the input from the user
start_node, goal_node, clearance, l, radius, obstacle_points, hexagon_pts, triangle_pts = get_input()

start = time.time()
map_queue = PriorityQueue()
start_pt = (start_node[0], start_node[1])
goal_pt = (goal_node[0], goal_node[1])
map_queue.put((euclidean_distance(start_pt, goal_pt), euclidean_distance(start_pt, goal_pt), 0, start_node))

visited_nodes = np.zeros((1200,500,12), dtype=int)
parent_child_info = {}
shortest_path = []
visited_pts= []
visualization_points = create_obstacle_map(clearance)
goal_reached = False
while map_queue.qsize() != 0:
    current_node = map_queue.get()
    x, y = current_node[3][0], current_node[3][1]
    if visited_nodes[int(x*2)][int(y*2)][int(current_node[3][2]/30)] != 1:
        visited_nodes[int(x*2)][int(y*2)][int(current_node[3][2]/30)]=1
        if euclidean_distance((x,y), goal_pt) > 1.5:
            if x >0  and x+l < X_SIZE and y>0 and y<Y_SIZE:
                move_right(current_node)
            if goal_reached == False and x>0 and y>0 and x+(l*math.cos(np.deg2rad(current_node[3][2]) + math.pi/6)) < X_SIZE and y+(l*math.sin(math.pi/6)) < Y_SIZE:
                move_plus_30(current_node)
            if goal_reached == False and x>0 and y>0 and x+(l*math.cos(np.deg2rad(current_node[3][2]) + math.pi/3)) < X_SIZE and y+(l*math.sin(np.deg2rad(current_node[3][2]) + math.pi/3)) < Y_SIZE:
                move_plus_60(current_node)
            if goal_reached == False and x>0 and y>0 and x+(l*math.cos(np.deg2rad(current_node[3][2]) - math.pi/6)) < X_SIZE and y+(l*math.sin(np.deg2rad(current_node[3][2]) - math.pi/6)) < Y_SIZE:
                move_minus_30(current_node)
            if goal_reached == False and x>0 and y>0 and x+(l*math.cos(np.deg2rad(current_node[3][2]) - math.pi/3)) < X_SIZE and y+(l*math.sin(np.deg2rad(current_node[3][2]) - math.pi/3)) < Y_SIZE:
                move_minus_60(current_node)

        else:
            print("Reached Goal")
            stop = time.time()
            print("Time: ",stop - start)
            shortest = back_tracking(parent_child_info, start_node, current_node[3])
            break

# xs = (np.where(visited_nodes == 1)[0])/2
# ys = (np.where(visited_nodes == 1)[1])/2
# visited_pts = []
# for x, y in zip(xs, ys):
#     visited_pts.append((int(x), int(y)))
pygame_visualization(visited_pts, shortest_path, obstacle_points, hexagon_pts, triangle_pts)


import streamlit as st
import pandas as pd
st.set_page_config(
    page_title="Ex-stream-ly Cool App",
    page_icon="ðŸ§Š",
    layout="wide"
)

st.title(" Welcome to the Smart Blood Report Generator ")

uploaded_file = st.file_uploader("Choose a file")
if uploaded_file is not None:
    # To read file as bytes:
    bytes_data = uploaded_file.getvalue()
    st.write(bytes_data)

    # To convert to a string based IO:
    stringio = StringIO(uploaded_file.getvalue().decode("utf-8"))
    st.write(stringio)

    # To read file as string:
    string_data = stringio.read()
    st.write(string_data)

    # Can be used wherever a "file-like" object is accepted:
    dataframe = pd.read_csv(uploaded_file)
    st.write(dataframe)

st.header("Dashboard")

df=pd.read_csv("./smart.csv")

for i in df.values:
    col1, col2 = st.columns(2)
    with col1:
        st.subheader(i[1])
        st.write(i[2])

    with col2:

        st.subheader(str(i[4])+str(i[3]) + "  -  " + str(i[6]))
        if(i[6]=="Normal"):
            st.success(i[7])
        elif(i[6]=="High"):
            st.error(i[7])
        else:
            st.info(i[7])


import xml.etree.ElementTree as ET

class Xml:
    def __init__(self, root):
        self._root = root

    @staticmethod
    def plain_text(tree: ET.Element):
        '''Extract plain text from the tag.'''
        txt = tree.text or ''

        for child in tree:
            txt += Xml.plain_text(child)
            txt += child.tail or ''

        return txt

    @staticmethod
    def plain_text_exclude(tree: ET.Element, exclude):
        '''Extract plain text from the tag exclude given tags.'''
        text = tree.text or ''

        for child in tree:
            if child.tag in exclude:
                pass
            else:
                text += Xml.plain_text_exclude(child, exclude)
            text += child.tail or ''

        return text.strip()

    @staticmethod
    def find_tag(tree: ET.Element, name: str):
        '''Return the tag with the given name. Recursive.'''
        if tree.tag == name:
            return tree
        for child in tree:
            found = Xml.find_tag(child, name)
            if found is not None:
                return found
        return None

    @staticmethod
    def find_tag_direct(tree: ET.Element, name: str):
        '''Return the child tag with the given name. NOT recursive.'''
        l = list(filter(lambda x: x.tag == name, tree))
        if len(l) == 0:
            return None
        return l[0]

    @staticmethod
    def filter_tags(tree: ET.Element, name: str, attribs=None):
        '''Return the list of the given name tag. Not recursive.'''
        if attribs is None:
            return list(filter(lambda x: x.tag == name, tree))
        else:
            ret = []
            filtered = list(filter(lambda x: x.tag == name, tree))
            for key in attribs:
                value = attribs[key]
                has_attrib_tags = filter(
                    lambda x: key in x.attrib.keys(),
                    filtered
                )
                match_tags = filter(
                    lambda x: x.attrib[key] == value,
                    has_attrib_tags
                )
                ret += list(match_tags)
            return ret


"""Statement parser for my consoles."""

__all__ = ["str2args", "StatementError"]

from dataclasses import dataclass

class StatementError(Exception):
    def __init__(self, statement, position, text):
        super().__init__(text)
        self.statement = statement
        self.position = position

    def explain(self):
        return f"{self.statement}\n{' '*self.position}^\n{str(self)}"

@dataclass
class _Flags:
    # This class exists so that I can set values from sub-method
    inside: bool = False  # inside argument
    kwarg: bool = False # parsing a key-value pair
    backslash: bool = False # found a backslash

def str2args(sargs):
    QUOTES = ('"', "'")
    args, kwargs = [], {}

    def new_arg():
        QBERT = "!)@(#*$&"
        part = sargs[bp+(1 if quoting else 0):i]
        # Removes single backslashes and replaces double backslashes with single backslashes (temporary operation required)
        part = part.replace("\\\\", QBERT).replace("\\", "").replace(QBERT, "\\")
        if flags.kwarg:
            kwargs[args.pop()] = part
            flags.kwarg = False
        else: args.append(part)
        flags.inside = False

    def error(text):
        return StatementError(sargs, i, text)

    def errorpart():
        return f"Error in position {i}, character '{ch}'"

    n = len(sargs)
    i = 0
    quote = None  # opening quotation mark: ' or "
    quoting = False
    expecting_space = False
    flags = _Flags()
    bp = None  # break point
    while True:
        if i == n:
            if quoting: raise error("Unclosed quotation")
            if flags.inside: new_arg()
            break
        ch = sargs[i]
        if expecting_space and ch != " ": raise error(f"{errorpart()}: expecting space")
        else: expecting_space = False

        if not quoting:
            if ch == " ":
                if flags.inside: new_arg()
            elif ch == "=":
                if flags.kwarg: raise error(f"{errorpart()}: repeated equal sign")
                if flags.inside: new_arg()
                flags.kwarg = True
            elif ch == "\\": raise error(f"{errorpart()}: backslash not allowed outside quotation marks")
            else:
                if not flags.inside:
                    flags.inside, bp = True, i
                    quoting = ch in QUOTES
                    if quoting: quote = ch
                else:
                    if ch in QUOTES: raise error(f"{errorpart()}: unexpected quotation mark")
        else:
            if flags.backslash:
                # just ignores next character
                flags.backslash = False
            else:
                if ch == quote:
                    new_arg()
                    flags.inside, quoting, expecting_space = False, False, True
                elif ch == "\\":
                    flags.backslash = True
        i += 1
    # print(f"{fg('purple_4a')+attr('bold')}str2args() is still in probation phase; therefore we'll print its results:\n<<{sargs}>>\nargs={args}\nkwargs={kwargs}{attr('reset')}\n")
    return args, kwargs


"""Constants for Zadnego Ale integration."""
from __future__ import annotations

from datetime import timedelta
from typing import Final

from homeassistant.components.sensor import SensorEntityDescription

ATTRIBUTION: Final = (
    "Dane dostarczone przez Ośrodek Badania Alergenów Środowiskowych Sp. z o.o."
)

ATTR_LEVEL: Final = "level"
ATTR_TREND: Final = "trend"
ATTR_VALUE: Final = "value"

CONF_REGION: Final = "region"

DEFAULT_NAME: Final = "Żadnego Ale"
DEFAULT_UPDATE_INTERVAL = timedelta(minutes=60)
DOMAIN: Final = "zadnego_ale"

REGIONS: Final[tuple[str, ...]] = (
    "Wybrzeże",
    "Pomorze",
    "Warmia, Mazury i Podlasie",
    "Ziemia Lubuska",
    "Śląsk i Wielkopolska",
    "Mazowsze i Ziemia Łódzka",
    "Małopolska i Ziemia Lubelska",
    "Sudety",
    "Karpaty",
)

SENSORS: Final[tuple[SensorEntityDescription, ...]] = (
    SensorEntityDescription(
        key="alder",
        device_class="zadnego_ale__concentration",
        icon="mdi:tree",
        name="Alder pollen concentration",
    ),
    SensorEntityDescription(
        key="alternaria",
        device_class="zadnego_ale__concentration",
        icon="mdi:mushroom-outline",
        name="Alternaria pollen concentration",
    ),
    SensorEntityDescription(
        key="ash_tree",
        device_class="zadnego_ale__concentration",
        icon="mdi:tree",
        name="Ash Tree pollen concentration",
    ),
    SensorEntityDescription(
        key="beech",
        device_class="zadnego_ale__concentration",
        icon="mdi:tree",
        name="Beech pollen concentration",
    ),
    SensorEntityDescription(
        key="birch_tree",
        device_class="zadnego_ale__concentration",
        icon="mdi:tree",
        name="Birch Tree pollen concentration",
    ),
    SensorEntityDescription(
        key="cladosporium",
        device_class="zadnego_ale__concentration",
        icon="mdi:mushroom-outline",
        name="Cladosporium pollen concentration",
    ),
    SensorEntityDescription(
        key="elm",
        device_class="zadnego_ale__concentration",
        icon="mdi:tree",
        name="Elm pollen concentration",
    ),
    SensorEntityDescription(
        key="goldenrod",
        device_class="zadnego_ale__concentration",
        icon="mdi:grass",
        name="Goldenrod pollen concentration",
    ),
    SensorEntityDescription(
        key="grass",
        device_class="zadnego_ale__concentration",
        icon="mdi:grass",
        name="Grass pollen concentration",
    ),
    SensorEntityDescription(
        key="hazel",
        device_class="zadnego_ale__concentration",
        icon="mdi:tree",
        name="Hazel pollen concentration",
    ),
    SensorEntityDescription(
        key="hornbeam",
        device_class="zadnego_ale__concentration",
        icon="mdi:tree",
        name="Hornbeam pollen concentration",
    ),
    SensorEntityDescription(
        key="maple",
        device_class="zadnego_ale__concentration",
        icon="mdi:tree",
        name="Maple pollen concentration",
    ),
    SensorEntityDescription(
        key="mugwort",
        device_class="zadnego_ale__concentration",
        icon="mdi:sprout",
        name="Mugwort pollen concentration",
    ),
    SensorEntityDescription(
        key="nettle",
        device_class="zadnego_ale__concentration",
        icon="mdi:sprout",
        name="Nettle pollen concentration",
    ),
    SensorEntityDescription(
        key="oak",
        device_class="zadnego_ale__concentration",
        icon="mdi:tree",
        name="Oak pollen concentration",
    ),
    SensorEntityDescription(
        key="pigweed",
        device_class="zadnego_ale__concentration",
        icon="mdi:sprout",
        name="Pigweed pollen concentration",
    ),
    SensorEntityDescription(
        key="pine",
        device_class="zadnego_ale__concentration",
        icon="mdi:pine-tree",
        name="Pine pollen concentration",
    ),
    SensorEntityDescription(
        key="plane_tree",
        device_class="zadnego_ale__concentration",
        icon="mdi:tree",
        name="Plane Tree pollen concentration",
    ),
    SensorEntityDescription(
        key="plantain",
        device_class="zadnego_ale__concentration",
        icon="mdi:sprout",
        name="Plantain pollen concentration",
    ),
    SensorEntityDescription(
        key="poplar",
        device_class="zadnego_ale__concentration",
        icon="mdi:tree",
        name="Poplar pollen concentration",
    ),
    SensorEntityDescription(
        key="ragweed",
        device_class="zadnego_ale__concentration",
        icon="mdi:sprout",
        name="Ragweed pollen concentration",
    ),
    SensorEntityDescription(
        key="sorrel",
        device_class="zadnego_ale__concentration",
        icon="mdi:sprout",
        name="Sorrel pollen concentration",
    ),
    SensorEntityDescription(
        key="willow",
        device_class="zadnego_ale__concentration",
        icon="mdi:tree",
        name="Willow pollen concentration",
    ),
    SensorEntityDescription(
        key="yew",
        device_class="zadnego_ale__concentration",
        icon="mdi:tree",
        name="Yew pollen concentration",
    ),
)

SENSORS_MIGRATION: Final[list[tuple[str, str]]] = [
    ("ambrozja", "ragweed"),
    ("babka", "plantain"),
    ("brzoza", "birch_tree"),
    ("buk", "beech"),
    ("bylica", "mugwort"),
    ("cis", "yew"),
    ("dąb", "oak"),
    ("grab", "hornbeam"),
    ("jesion", "ash_tree"),
    ("klon", "maple"),
    ("komosa", "pigweed"),
    ("leszczyna", "hazel"),
    ("olsza", "alder"),
    ("platan", "plane_tree"),
    ("pokrzywa", "nettle"),
    ("sosna", "pine"),
    ("szczaw", "sorrel"),
    ("topola", "poplar"),
    ("trawy", "grass"),
    ("wierzba", "willow"),
    ("wiąz", "elm"),
]


import networkx as nx
from random import uniform, sample, choice
import itertools

# Erdős–Rényi models
def ER_model_Gnp(number_of_nodes=100, edge_probability=.01):
    g = nx.Graph()
    g.add_nodes_from(range(number_of_nodes))
    for el in itertools.combinations(range(number_of_nodes), 2):
        if uniform(0, 1) < edge_probability:
            g.add_edge(*el)
    return g

def ER_model_GnM(number_of_nodes=100, number_of_edges=100): # clearly number_of_edges has to be <= n(n-1)/2
    g = nx.Graph()
    g.add_nodes_from(range(number_of_nodes))
    sampled_edges = sample([pair for pair in itertools.combinations(range(number_of_nodes), 2)], number_of_edges)
    g.add_edges_from(sampled_edges)
    return g

# Watts–Strogatz model
def WS_model(number_of_nodes=100, mean_degree=6, beta=0.01):
    # number_of_nodes >> mean degree >> log(number of nodes) >> 1
    # beta between 0 and 1
    # mean degree has to be an even integer
    g = nx.Graph()
    g.add_nodes_from(range(number_of_nodes))
    for i, j in itertools.combinations(range(number_of_nodes), 2):
        if abs(i-j) % (number_of_nodes - 1 - mean_degree/2) <= mean_degree/2:
            g.add_edge(*(i, j))
    for i in g.nodes:
        for j in range(i+1, i+1+int(mean_degree/2)):
            if uniform(0, 1) < beta:
                g.remove_edge(i, j % number_of_nodes)
                neighbors = list(g.neighbors(i))
                neighbors.append(i)
                g.add_edge(*(i, choice(list(set(g.nodes)-set(neighbors)))))
    return g

# Barabási-Albert model
def BA_model(number_of_nodes=30, alpha=1.):
    g = nx.Graph()
    g.add_nodes_from(range(2))
    g.add_edge(*(0, 1))
    while g.number_of_nodes() < number_of_nodes:
        den = sum([g.degree(node)**alpha for node in g.nodes])
        probabilities = [g.degree(node)**alpha/den for node in g.nodes]
        new_node = g.number_of_nodes() # label of new node
        g.add_node(new_node)
        for i in range(new_node):
            if uniform(0, 1) < probabilities[i]:
                g.add_edge(*(new_node, i))
    return g

def custom_barabasi(n=50, alpha=1., p_opp=.01, p_m=.1, t=0.):
    '''
    This model is based on the Barabasi Albert one, but:
    p_opp = probability to swap the preferential attachment of a node
    p_m = influence of preferential dis-attachment: p_m=1 means that a node could die with probability degree / sum(degrees)
    t = influence of the age of the node. Higher t means that most recent nodes has priority in preferential attachment
    '''
    g = nx.Graph()
    g.add_nodes_from(range(2))
    g.add_edge(*(0, 1))
    while g.number_of_nodes() < n:
        den = sum([(node**t)*(g.degree(node)**alpha) for node in g.nodes])
        probabilities = [(node**t)*(g.degree(node)**alpha)/den for node in g.nodes]
        if uniform(0, 1) < p_opp:
            probabilities = [1. - el for el in probabilities]
        new_node = g.number_of_nodes() # label of new node
        g.add_node(new_node)
        for i in range(new_node):
            if uniform(0, 1) < probabilities[i]:
                g.add_edge(*(new_node, i))
    node_to_remove = []
    for node in g.nodes:
        if uniform(0, 1) < p_m*g.degree(node)/sum([g.degree(n) for n in g.nodes]):
            node_to_remove.append(node)
    for node in node_to_remove:
        g.remove_node(node)
    return g


Lat = 40.5215
Lon = -74.4618
Alt = -0.00104515

facility = root.CurrentScenario.Children.New(8, 'MyFacility')  # eFacility
# IAgFacility facility: Facility Object
facility.Position.AssignGeodetic(Lat, Lon, Alt)  # Latitude, Longitude, Altitude

# Set altitude to height of terrain
facility.UseTerrain = True

# Set altitude to a distance above the ground
facility.HeightAboveGround = 0.01   # km


from .. import schemas, models
from src.base.services import BaseService
from tortoise.transactions import atomic

class AreasService(BaseService):
    model = models.Area
    create_schema = schemas.CreateArea
    update_schema = schemas.UpdateArea
    get_schema = schemas.GetArea
    get_detail = schemas.GetArea

    @atomic()
    async def update(self, unique_id: int, schema: schemas.UpdateArea) -> schemas.GetArea:
        await models.AreaPoint.filter(area_id=unique_id).delete()
        await models.Area.filter(id=unique_id).update(name=schema.name)
        for point in schema.points:
            await models.AreaPoint.create(**point.dict(exclude_unset=True), area_id=unique_id)
        return await self.get(pk=unique_id)

    async def get(self, **kwargs) -> schemas.GetArea:
        obj = await models.Area.get(**kwargs).prefetch_related('points')
        return schemas.GetArea(
            id=obj.id,
            name=obj.name,
            camera_id=obj.camera_id,
            points=[
                schemas.AreaPointSchema(
                    x1=p.x1, y1=p.y1,
                    x2=p.x2, y2=p.y2,
                    area_id=p.area_id
                ) for p in obj.points
            ]
        )

areas_s = AreasService()


from keras import layers
from keras import models
from keras import optimizers
from keras.preprocessing.image import ImageDataGenerator
import matplotlib as mpl
import matplotlib.pyplot as plt
import os

def multiple_choice():
    """Choose the right answers (one or more) from multiple choices.
    Note: if you think a is the right answer, return a;
            if your think a, b are the right answers, return a, b; ect.
    """
    # Here are 4 choices
    a = "Training accuracy increases over time and this is a problem."
    b = "Validation accuracy does not increase over time and this is a problem."
    c = "Training loss decreases over time and this is a problem."
    d = "Validation loss does not decrease over time and this is a problem."
    return b, d

def modified_cnn():
    """Return instance of keras.models.Sequential
    This is similar to build_cnn_architecture() in q1.py; however, you have to compile your model with
    a proper loss function and optimizer.
    """
    #######
    # b) 1. change Activation Function from 'sigmoid' to 'relu'
    model = models.Sequential()
    model.add(layers.Conv2D(32, (3, 3), activation='relu', input_shape=(150, 150, 3)))
    model.add(layers.MaxPooling2D((2, 2)))
    model.add(layers.Conv2D(64, (3, 3), activation='relu'))
    model.add(layers.MaxPooling2D((2, 2)))
    model.add(layers.Conv2D(128, (3, 3), activation='relu'))
    model.add(layers.MaxPooling2D((2, 2)))
    model.add(layers.Conv2D(128, (3, 3), activation='relu'))
    model.add(layers.MaxPooling2D((2, 2)))
    model.add(layers.Flatten())
    model.add(layers.Dense(512, activation='relu'))
    model.add(layers.Dense(4, activation='softmax'))
    #######
    return model

def test_multiple_choice():
    choice = multiple_choice()
    assert isinstance(choice, str) or isinstance(choice, tuple)
    print("\nPass.")

def test_modified_cnn():
    model = modified_cnn()
    assert isinstance(model, models.Sequential)
    if hasattr(model, "loss") and hasattr(model, "optimizer"):
        print("\nPass.")

def train():
    # data processing
    base_dir = '/userhome/34/zxzhao/assignment2/Datasets/cat_dog_car_bike/'
    train_dir = os.path.join(base_dir, 'train')
    val_dir = os.path.join(base_dir, 'val')
    test_dir = os.path.join(base_dir, 'test')

    train_datagen = ImageDataGenerator(rescale=1./255)
    test_datagen = ImageDataGenerator(rescale=1./255)

    train_generator = train_datagen.flow_from_directory(
        train_dir,
        target_size=(150, 150),
        batch_size=20,
        class_mode='categorical')

    validation_generator = test_datagen.flow_from_directory(
        val_dir,
        target_size=(150, 150),
        batch_size=20,
        class_mode='categorical')

    # create model
    model = modified_cnn()
    # b) 2. change Learning Rate from '0.1' to '1e-4'
    model.compile(loss = 'categorical_crossentropy',
                  optimizer = optimizers.RMSprop(lr=1e-4),
                  metrics=['acc'])

    # train
    history = model.fit_generator(
        train_generator,
        steps_per_epoch=100,
        epochs=10,
        validation_data=validation_generator,
        validation_steps=50)

    # plot
    acc = history.history['acc']
    val_acc = history.history['val_acc']
    loss = history.history['loss']
    val_loss = history.history['val_loss']

    epochs = range(1, len(acc) + 1)

    plt.plot(epochs, acc, 'bo', label='Training acc')
    plt.plot(epochs, val_acc, 'b', label='Validation acc')
    plt.title('Training and validation accuracy')
    plt.legend()
    plt.savefig('q2_acc.jpg')
    plt.figure()
    plt.plot(epochs, loss, 'bo', label='Training loss')
    plt.plot(epochs, val_loss, 'b', label='Validation loss')
    plt.title('Training and validation loss')
    plt.legend()

#     plt.show()
    plt.savefig('q2_loss.jpg')

if __name__ == '__main__':
    test_multiple_choice()
    test_modified_cnn()
    train()


import datetime, json

from flask import Blueprint, request, jsonify

from admin import admin_config
from backend import db, match_making, utility, slack_util
from backend.league_context import LeagueContext

playerboard_api = Blueprint('playerboard_api', __name__)

@playerboard_api.route('/submit-players', methods=['POST'])
def submit_players():
    players_and_groups = request.get_json().get("players")

    for group, players in players_and_groups.items():
        print(group, players)

        ensure_players_in_db(players)
        update_groupings(group, players)

    today = datetime.datetime.today()
    last_monday = today - datetime.timedelta(days=today.weekday())
    next_monday = (last_monday + datetime.timedelta(days=7)).date()

    league_name = admin_config.get_current_league()
    lctx = LeagueContext.load_from_db(league_name)
    # TODO use the config for sets needed
    match_making.create_matches_for_season(lctx, next_monday, 3, skip_weeks=[], include_byes=False)

    return "We did it boys"

@playerboard_api.route('/get-active-players', methods=['GET'])
def get_active_players():
    players = get_ranked_players()
    return jsonify(players)

@playerboard_api.route('/get-players-from-season', methods=['GET'])
def get_players_from_season():
    league_name = request.args.get("leagueName", default="", type=str)
    season = request.args.get("season", default=0, type=int)
    players = get_ranked_players(league_name, season)
    return jsonify(players)

@playerboard_api.route('/get-all-seasons', methods=['GET'])
def get_all_seasons():
    league_name = request.args.get("leagueName", default="", type=str)
    if not league_name:
        return jsonify([])
    all_seasons = db.get_all_seasons(league_name)
    return jsonify(all_seasons)

@playerboard_api.route('/inactivate-player', methods=['POST'])
def inactivate_player():
    league_name = request.get_json().get("leagueName")
    player_id = request.get_json().get("playerId")

    db.update_grouping(league_name, player_id, '')
    db.set_active(league_name, player_id, False)
    return "success"

@playerboard_api.route('/update-player-grouping-and-orders', methods=['POST'])
def update_player_grouping():
    data = request.get_json()
    league_name = data.get("leagueName")
    grouping = data.get("grouping")
    players = data.get("players")

    db.updating_grouping_and_orders(league_name, players, grouping)
    return "success"

@playerboard_api.route('/add-player', methods=['POST'])
def add_player():
    data = request.get_json()
    league_name = data.get("leagueName")
    player_name = data.get("playerName")
    grouping = data.get("grouping")
    lctx = LeagueContext.load_from_db(league_name)
    slack_id = slack_util.get_slack_id(lctx, player_name)
    if slack_id is None:
        return jsonify({'success': False, 'message': 'Could not find slack id for {}'.format(player_name)})
    try:
        db.add_player(league_name, slack_id, player_name, grouping)
        p = db.get_player_by_id(league_name, slack_id)
        return jsonify({'success': True, 'player': json.dumps(p, default=match_player_serializer)})
    except Exception as e:
        return jsonify({'success': False, 'message': str(e)})

@playerboard_api.route('/refresh-users', methods=['POST'])
def refresh_users():
    data = request.get_json()
    league_name = data.get("leagueName")
    lctx = LeagueContext.load_from_db(league_name)
    try:
        users_list = slack_util._get_users_list(lctx, True)
        return jsonify({'success': True, 'totalUsers': len(users_list), 'lastRan': slack_util.last_get_users_date})
    except Exception as e:
        return jsonify({'success': False, 'message': str(e)})

@playerboard_api.route('/slack-users-count', methods=['GET'])
def refresh_users_info():
    return jsonify({'success': True, 'totalUsers': len(slack_util.users_list), 'lastRan': slack_util.last_get_users_date})

@playerboard_api.route('/get-deactivated-players', methods=['GET'])
def get_deactivated_players():
    league_name = request.args.get("leagueName", default="", type=str)
    lctx = LeagueContext.load_from_db(league_name)
    return jsonify(slack_util.get_deactivated_slack_ids(lctx))

def ensure_players_in_db(players):
    league_name = admin_config.get_current_league()  # TODO pass league name in
    existing_players_dict = dict()
    existing_players = db.get_players(league_name)

    for existing_player in existing_players:
        existing_players_dict[existing_player.name] = existing_player

    players_to_add = []
    for player in players:
        if player['name'] not in existing_players_dict:
            print("FOUND ONE", player['name'])
            players_to_add.append(player)

    if len(players_to_add) == 0:
        return

    # TODO move this to slack util if we're gonna keep it
    lctx = LeagueContext.load_from_db(league_name)
    users = slack_util._get_users_list(lctx)
    user_map = {}
    for user in users:
        for player in players_to_add:
            if user['profile']['real_name'].startswith(player['name']) and not user['deleted']:
                user_map[player['name']] = user['id']
                db.add_player(league_name, user['id'], player['name'], player['group'])

def update_groupings(group, players):
    league_name = admin_config.get_current_league()
    existing = db.get_players(league_name)

    for player in players:
        for e in existing:
            if e.name == player['name']:
                if group == 'Trash':
                    db.update_grouping(league_name, e.slack_id, "")
                    db.set_active(league_name, e.slack_id, False)
                else:
                    db.update_grouping(league_name, e.slack_id, group)
                    db.set_active(league_name, e.slack_id, True)

def get_ranked_players(league_name=None, season=None):
    league_name = league_name or admin_config.get_current_league()
    if season is None:
        season = db.get_current_season(league_name)
    all_matches = db.get_matches_for_season(league_name, season)
    all_players = db.get_players(league_name)

    groups = sorted(list(set([m.grouping for m in all_matches])))
    return_players = []

    for group in groups:
        group_matches = [m for m in all_matches if m.grouping == group]

        players = utility.gather_scores(group_matches)

        for player in players:
            if player['player_id'] is None:
                continue

            name = [p.name for p in all_players if p.slack_id is not None and p.slack_id == player['player_id']][0]
            return_players.append(
                {
                    'name': name,
                    'slack_id': player['player_id'],
                    'sets_won': player['s_w'],
                    'sets_lost': player['s_l'],
                    'games_won': player['m_w'],
                    'games_lost': player['m_l'],
                    'group': group
                }
            )

    return return_players

def match_player_serializer(o):
    if isinstance(o, (datetime.date, datetime.datetime)):
        return o.isoformat()
    return o.__dict__


# CP Template 0.2
# Author: Mehrab (@to0th_less)

import sys
from math import gcd, sqrt
from itertools import combinations, permutations

# Constants
INF = float('inf')
MOD = 1000000007

# Faster Input Shortcut
def read_int():
    return int(sys.stdin.readline().strip())

def read_ints():
    return map(int, sys.stdin.readline().split())

def read_int_list():
    return list(map(int, sys.stdin.readline().split()))

# Faster Output Shortcut
def print_yes_no(condition):
    print('YES' if condition else 'NO')

def print_array(arr, sep=' '):
    print(sep.join(map(str, arr)))

# Main Function
def solve():
    t = read_int()
    for _ in range(t):
        teams = read_int_list()
        temp = sorted(teams,reverse=True)

        mx = temp[0]
        mx2 = temp[1]

        ans = False
        if mx in teams[:2]:
            for i in teams[2:]:
                if i >= mx2:
                    ans = True
                    break
        else:
            for i in teams[:2]:
                if i >= mx2:
                    ans = True
                    break

        print_yes_no(ans)

if __name__ == '__main__':
    solve()


"""Create charts for viewing on Raspberry Pi Web Server."""

# Raspi-sump, a sump pump monitoring system.
# Al Audet
# https://www.linuxnorth.org/raspi-sump/
#
# All configuration changes should be done in raspisump.conf
# MIT License -- https://www.linuxnorth.org/raspi-sump/license.htmlimport os

import os
import subprocess
import time
from raspisump import todaychart

def create_folders(year, month, homedir):
    """Check if folders exist in charts folder and create them if they don't"""
    if not os.path.isdir(f"{homedir}charts/{year}/"):
        _year = f"mkdir {homedir}charts/{year}"
        create_year = _year.split(" ")
        subprocess.call(create_year)

    if not os.path.isdir(f"{homedir}charts/{year}/{month}/"):
        _month = f"mkdir {homedir}charts/{year}/{month}"
        create_month = _month.split(" ")
        subprocess.call(create_month)

def create_chart(homedir):
    """Create a chart of sump pit activity and save to web folder"""
    csv_file = f"{homedir}csv/waterlevel-{time.strftime('%Y%m%d')}.csv"
    filename = f"{homedir}charts/today.png"
    todaychart.graph(csv_file, filename)

def copy_chart(year, month, today, homedir):
    """Copy today.png to year/month/day folder for web viewing"""
    copy_cmd = (
        f"cp {homedir}charts/today.png {homedir}charts/{year}/{month}/{today}.png"
    )
    copy_file = copy_cmd.split(" ")
    subprocess.call(copy_file)


REASON_LABEL = "reason"
OUTCOME_LABEL = "outcome"
EVENT_LABEL = "event"
CONDITION_LABEL = "condition"
FIRST_ENTITY_LABEL = "sameAs_arg"
SECOND_ENTITY_LABEL = "sameAs_arg"
POSSESSOR_LABEL = "possessor"
POSSESSED_LABEL = "possessed"
ARG_LABEL = "arg"
domain_label = "prop_of"

determiner_dependencies = ["det"]
conjunction_dependencies = ["cc"]
aux_dependencies = ["aux", "auxpass"]
aux_cop_dependencies = aux_dependencies + ["cop"]
passive_dependencies = ["auxpass"]# "csubjpass", "nsubjpass" also indicate passive, but must come with auxpass in the general case. For other cases see json_files/not_auxpass.json
negation_dependencies = ["neg"]
time_dependencies = ["tmod"]
subject_dependencies = ["subj","nsubj","nsubjpass","csubj","csubjpass","xsubj"]
object_dependencies = ["obj","dobj","iobj","pobj"]
clausal_complements = ["acomp","xcomp","comp","ccomp"]#"vmod"]#"dep"]
rare_dependencies = ["dep","parataxis","expl"]
arguments_dependencies = ["arg","agent"] + object_dependencies + subject_dependencies + [domain_label,FIRST_ENTITY_LABEL,SECOND_ENTITY_LABEL,POSSESSED_LABEL,POSSESSOR_LABEL,OUTCOME_LABEL,REASON_LABEL,CONDITION_LABEL,EVENT_LABEL]
clausal_complement = ["xcomp"]
adverb_dependencies = ["advmod", "advcl"]
appositional_dependencies = ["appos"]
prepositions_dependencies = ["prep"]
prt_dependency = "prt"
adjectival_mod_dependencies = ["amod","acomp"]
mod_labels = ["amod", "vmod"] + adverb_dependencies
determined_labels = ["NNP"]
definite_determiners = ["the","this","that","these","those","another"]
relclause_dependencies = ["rcmod"]
relclause_markers = ["that","which","who","whom"]
mark_labels = ["mark","advmod"]
clausal_modifiers_labels = ["advcl","dep"]
labels_ban = ["preconj"]
filter_labels_ban = lambda n:n.parent_relation not in labels_ban
#single labels = not lists
POSS_LABEL = "poss"
MARK_LABEL = "mark"
EXPL_LABEL = 'expl'
SOURCE_LABEL = 'source'
MODIFIER_LABEL = "modifier"
POSSESSIVE_LABEL = "possessive"
SUBJ_LABEL  = "subj"
OBJECT_LABEL = "obj"
DIRECT_OBJECT_LABEL = "dobj"
INDIRECT_OBJECT_LABEL = "iobj"

definite_label = "definite"
indefinite_label = "indefinite"

# POS tags
VB = "VB"       #Verb, base form
VBD = "VBD"     #Verb, past tense
VBG = "VBG"     #Verb, gerund or present participle
VBN = "VBN"     #Verb, past participle
VBP = "VBP"     #Verb, non-3rd person singular present
VBZ = "VBZ"     #Verb, 3rd person singular present
VERB_POS = [VB,VBD,VBP,VBZ,VBN] # all types of verb pos
gerund_pos = [VBG]
TO = "TO"
IN = "IN"
MD = "MD"
DOT = "."
COMMA = ","
#pos which will not be needed to cover
ignore_pos = [DOT,COMMA]
relative_adj_pos = ["JJR","JJS"]
relative_adj_words = ["some","other","own",]

#conversion from Penn notation to wordnet notation for lemmatizer
pos_penn_to_wordnet = dict([(vb,'v') for vb in VERB_POS])

def aux_children_with_pos(pos_tag):
    return (lambda node: node.get_pos() == pos_tag and node.get_parent_relation() in aux_dependencies)

##### Tense #####
TENSE_PAST = "past"
TENSE_PRESENT = "present"
TENSE_FUTURE = "future"
TENSE_UNKNOWN = "unknown"

WILL = "will"
WONT = "wo"
WOULD = "would"
ll = "'ll"
D = "'d"
HAVE = "have"
BE = "be"
BEEN = "been"

FUTURE_MODALS = [WILL, WONT, WOULD, ll, D, "may", "might"] # "wo" is the Modal part of "won't"
FUTURE_Signs = ["going", "about"]
#PREP

AS = "as"

#conditionals
COND_IF = "if"
COND_AFTER = "after"
# comparators for DepTree._get_span_of_filtered_children() method

TO_child_func = aux_children_with_pos(TO)
VB_child_func = aux_children_with_pos(VB)
VBD_child_func = aux_children_with_pos(VBD)
VBN_child_func = aux_children_with_pos(VBN)
VBP_child_func = aux_children_with_pos(VBP)
VBP_or_VBZ_child_func = lambda node: (node.get_pos() == VBP or node.get_pos() == VBZ) and node.get_parent_relation() in aux_dependencies
VBD_or_VBN_child_func = lambda node: (node.get_pos() == VBD or node.get_pos() == VBN) and node.get_parent_relation() in aux_dependencies
FUTURE_MD_child_func = lambda node: node.get_pos() == MD and node.get_parent_relation() in aux_dependencies and node.get_word().lower() in FUTURE_MODALS
VB_have_child_func = lambda node: node.get_pos() == VB and node.get_parent_relation() in aux_dependencies and node.get_word().lower() == HAVE
VB_be_child_func = lambda node: node.get_pos() == VB and node.get_parent_relation() in aux_dependencies and node.get_word().lower() == BE
VBN_been_child_func = lambda node: node.get_pos() == VBN and node.get_parent_relation() in aux_dependencies and node.get_word().lower() == BEEN
prep_as_child_func = lambda node: node.get_pos() == IN and node.get_parent_relation() in prepositions_dependencies and node.get_word().lower() == AS
adverb_child_func = lambda node: node.get_parent_relation() in adverb_dependencies
##### Tense - End #####

# copular
copular_verbs = ["be", "am", "is", "are", "being", "was", "were", "been","'s","'re","become","became","becomes"]
modal_list = ["may","might","must","shall","should"]
time_prep = ["at","in","on"]
location_prep = ["at","in","on","of"]
negating_words = ["not","no"]

# conditional marks
condition_outcome_markers = ["if","although","where","after","before","as","until","unless"]
reason_outcome_markers = ["because"]
comp_markers = ["whether"]

contractions = ["'m","'re","'ve","'s"]


import scrapy
import re

from Petharbor.items import AustinAnimalCenterItem

class AnimalCenterSpider(scrapy.Spider):
    name="austinanimalcenter"
    allowed_domains = ["petharbor.com"]
    #start_urls = ["http://www.petharbor.com/results.asp?searchtype=ADOPT&stylesheet=include/default.css&frontdoor=1&grid=1&friends=1&samaritans=1&nosuccess=0&rows=5000&imght=120&imgres=thumb&tWidth=200&view=sysadm.v_animal&fontface=arial&fontsize=10&miles=20&lat=30.300474&lon=-97.747247&shelterlist='ASTN','90343','72312','73748','73064','63250','78098','72699','78456','78552','79815','80971','88570','86145','75304','78347','83335','85034','71872','76816','82004','89469','73793','87542','69562','87125','84976','86555','73895','85366','82718','79604','78842','77070','69865','86866'&atype=dog&ADDR=undefined&nav=1&start=4&nomax=1&page=1&where=type_DOG"]

    #From austin animal center in table format with kennel number
    start_urls= ["http://www.petharbor.com/results.asp?searchtype=ADOPT&friends=1&samaritans=1&nosuccess=0&rows=1000&imght=120&imgres=thumb&tWidth=200&view=sysadm.v_austin&nobreedreq=1&bgcolor=ffffff&text=29abe2&link=024562&alink=017db3&vlink=017db3&fontface=arial&fontsize=10&col_hdr_bg=29abe2&col_hdr_fg=29abe2&col_fg=29abe2&SBG=ffffff&zip=78704&miles=10&shelterlist=%27ASTN%27&atype=&where=type_DOG,size_s&PAGE=1",
    "http://www.petharbor.com/results.asp?searchtype=ADOPT&friends=1&samaritans=1&nosuccess=0&rows=1000&imght=120&imgres=thumb&tWidth=200&view=sysadm.v_austin&nobreedreq=1&bgcolor=ffffff&text=29abe2&link=024562&alink=017db3&vlink=017db3&fontface=arial&fontsize=10&col_hdr_bg=29abe2&col_hdr_fg=29abe2&col_fg=29abe2&SBG=ffffff&zip=78704&miles=10&shelterlist=%27ASTN%27&atype=&where=type_DOG,size_m&PAGE=1",
    "http://www.petharbor.com/results.asp?searchtype=ADOPT&friends=1&samaritans=1&nosuccess=0&rows=1000&imght=120&imgres=thumb&tWidth=200&view=sysadm.v_austin&nobreedreq=1&bgcolor=ffffff&text=29abe2&link=024562&alink=017db3&vlink=017db3&fontface=arial&fontsize=10&col_hdr_bg=29abe2&col_hdr_fg=29abe2&col_fg=29abe2&SBG=ffffff&zip=78704&miles=10&shelterlist=%27ASTN%27&atype=&where=type_DOG,size_l&PAGE=1"]

    def parse(self,response):
        #with open("petharbor-scrape.txt","wb") as f:
        #    f.write(response.body)
        #items=[]
        sizes = ['size_s','size_m','size_l']
        size_map = { 'size_s' : 'small' , 'size_m' : 'medium' , 'size_l':'large'}
        for sel in response.css('.ResultsTable').css('tr'):
            item = AustinAnimalCenterItem()

            id = sel.css('td:nth-child(2)').xpath('text()').extract()
            if len(id):
                item["id"] = id[0]
            else:
                continue

            kennel = sel.css('td:nth-child(5)').xpath('text()').extract()
            if len(kennel):
                item["kennel"] = kennel[0]

            for size in sizes:
                p = re.compile(size);
                m = p.search(response.url)

                if m is not None:
                    item["size"] = size_map[size]
                    break

            yield(item)