import torch
import torch.nn as nn
import torch.nn.functional as F
import torch.optim as optim
import pandas as pd
import numpy as np
import os
from sklearn.model_selection import train_test_split as tts
from matplotlib import pyplot as plt
from zipfile import ZipFile
import gensim
from gensim.models import FastText, Word2Vec
from sklearn import svm
from sklearn.manifold import TSNE
import sys

os.chdir(sys.argv[1])
files = os.listdir()

dataArray = []
vectorArray = []
labelArray = []

for f in files:
    currentMatrix = []
    openedFile = np.load(f, allow_pickle=True)

    listOfLists = openedFile[0]

    for l in listOfLists:
        if len(l) == 0:
            currentMatrix.append(np.zeros(100))
        else:
            currentMatrix.append(l)

    currentMatrix = np.stack(currentMatrix)
    currentMatrix = torch.from_numpy(currentMatrix)
    currentMatrix = currentMatrix.type(torch.FloatTensor)
    vectorArray.append(currentMatrix.reshape(-1).numpy())
    dataArray.append(currentMatrix)

    labelArray.append(openedFile[1])

vecTrainX, vecTestX, vecTrainy, vecTesty = tts(vectorArray, labelArray, test_size = 0.3, stratify = labelArray)
trainX, testX, trainY, testY = tts(dataArray, labelArray, test_size = 0.3, stratify = labelArray)

train_set = []
for i in range(len(trainX)):
  # print(trainX[i], trainY[i])
  train_set.append((trainX[i].reshape(-1, 100, 24), trainY[i]))
  # train_set.append((train_array[i], label_array[i]))

test_set = []
for i in range(len(testX)):
  # print(trainX[i], trainY[i])
  test_set.append((testX[i].reshape(-1, 100, 24), testY[i]))
  # train_set.append((train_array[i], label_array[i]))
BATCH_SIZE = 10
train_set = torch.utils.data.DataLoader(train_set, batch_size = BATCH_SIZE, shuffle = True)
test_set = torch.utils.data.DataLoader(test_set, batch_size = BATCH_SIZE, shuffle = True)

baseline = svm.LinearSVC()
baseline.fit(vecTrainX, vecTrainy)
print("SVM score:", baseline.score(vecTestX, vecTesty))

class FeedForward(nn.Module):
    def __init__(self, input_size, hidden1_size, hidden2_size, hidden3_size, num_classes):
        super(FeedForward, self).__init__()
        self.fc1= nn.Linear(input_size, hidden1_size)
        self.ReLU1 = nn.ReLU()
        self.fc2 = nn.Linear(hidden1_size, hidden2_size)
        self.ReLU2 = nn.ReLU()
        self.fc3 = nn.Linear(hidden1_size, hidden2_size)
        self.ReLU2 = nn.ReLU()
        self.fc4 = nn.Linear(hidden2_size, num_classes)

    def  forward(self,x):
        # print(x)
        x = x.view(-1, self.num_flat_features(x))
        out = self.fc1(x)
        out = self.ReLU1(out)
        # print(out)
        # r_out, (h_n, h_c) = self.rnn(out.view(-1,1,512), None)
        # print(r_out)
        # out = r_out[:, -1, :]
        # out = self.ReLU2(out)
        out = self.fc2(out)
        out = self.ReLU2(out)
        out = self.fc3(out)
        m = nn.Dropout(p=0.2)
        out = m(out)
        out = self.ReLU3(out)
        out = F.softmax(self.fc4(out))
        # print(out)
        return out

    def num_flat_features(self, x): # To see dimensions of layers
        size = x.size()[1:]  # all dimensions except the batch dimension
        num_features = 1
        for s in size:
            num_features *= s
        return num_features

class RNN(nn.Module):
    def __init__(self, input_size, hidden1_size, hidden2_size, num_classes):
        super(RNN, self).__init__()
        self.fc1= nn.Linear(input_size, hidden1_size)
        self.ReLU1 = nn.ReLU()
        self.rnn = nn.LSTM(
            input_size=hidden1_size,
            hidden_size=hidden2_size,
            num_layers=1,
            batch_first=True,
        )
        self.ReLU2 = nn.ReLU()
        self.fc3 = nn.Linear(hidden2_size, num_classes)

    def forward(self,x):
        out = None
        cumOut = []
        for patient in x:
          for mat in patient:
            mat = mat.transpose(1, 0)
            # ordering = [14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24]
            for row in mat:
              out = self.rowForward(row)[0]
          cumOut.append(out)
        return torch.stack(cumOut)

    def rowForward(self, x):
        out = self.fc1(x)
        out = self.ReLU1(out)
        r_out, (h_n, h_c) = self.rnn(out.view(-1,1,256), None)
        out = r_out[:, -1, :]
        out = self.ReLU2(out)
        out = F.softmax(self.fc3(out))
        return out

model = RNN(100, 256, 128, 2)

# print("========================START RNN TRAINING===========================")

# optimizer = optim.SGD(model.parameters(), lr=0.1, momentum=0.5)
# loss_f = nn.CrossEntropyLoss()

# lossX = np.arange(30)
# lossY = []
# testingY = []
# model.train()
# numEpoch = 1
# for i in range(30):
#   cum_loss = 0
#   epoch_loss = []
#   loss_per_batch = []
#   for inputs, labels in train_set:
#     # print(inputs.shape)
#     # print(labels)
#     # break
#     optimizer.zero_grad()
#     outputs = model(inputs.float())
#     # print(outputs[0])
#     # print(outputs)
#     # print(outputs.squeeze(2))
#     loss = loss_f(outputs, labels.long())
#     # loss.requires_grad = True
#     loss.backward()
#     optimizer.step()
#     # cum_loss += loss.data.item()
#     loss_per_batch.append(loss.data.item())
#     #add min, max, avg, std of loss
#     # print("batch loss avg")
#   loss_per_batch = torch.Tensor(loss_per_batch)
#   print("Epoch", numEpoch, "Loss avg: ", torch.mean(loss_per_batch) / BATCH_SIZE)
#   print("Epoch", numEpoch, "Loss std: ", (torch.std(loss_per_batch) / BATCH_SIZE) ** 0.5)
#   print("Epoch", numEpoch, "Loss min: ", torch.min(loss_per_batch) / BATCH_SIZE)
#   print("Epoch", numEpoch, "Loss max: ", torch.max(loss_per_batch) / BATCH_SIZE)
#   with torch.no_grad():
#     total = 0
#     correct = 0
#     for inputs, labels in test_set:
#       out = model(inputs)
#       for i in range(len(out)):
#         prediction = out[i].argmax().item()
#         # print(prediction)
#         total += 1
#         if prediction == labels[i]:
#           # print("Correct: ", prediction)
#           correct += 1
#     accuracy = correct / total
#     print("Epoch", numEpoch, "Test Accuracy: ", accuracy)
#     testingY.append(accuracy)

#     for inputs, labels in train_set:
#       out = model(inputs)
#       for i in range(len(out)):
#         prediction = out[i].argmax().item()
#         # print(prediction)
#         total += 1
#         if prediction == labels[i]:
#           # print("Correct: ", prediction)
#           correct += 1
#     accuracy = correct / total
#     print("Epoch", numEpoch, "Train Accuracy: ", accuracy)

#   lossY.append(cum_loss)
#   numEpoch += 1

print("========================START FFN TRAINING===========================")

FFN = FeedForward(100 * 24, 128, 64, 2)

optimizer = optim.SGD(FFN.parameters(), lr=0.1, momentum=0.5)
loss_f = nn.CrossEntropyLoss()

lossX = np.arange(100)
lossY = []
testingY = []
FFN.train()
numEpoch = 1
for i in range(100):
  cum_loss = 0
  epoch_loss = []
  loss_per_batch = []
  for inputs, labels in train_set:
    # print(inputs.shape)
    # print(labels)
    # break
    optimizer.zero_grad()
    outputs = FFN(inputs.float())
    # print(outputs[0])
    # print(outputs)
    # print(outputs.squeeze(2))
    loss = loss_f(outputs, labels.long())
    # loss.requires_grad = True
    loss.backward()
    optimizer.step()
    # cum_loss += loss.data.item()
    loss_per_batch.append(loss.data.item())
    #add min, max, avg, std of loss
    # print("batch loss avg")
  loss_per_batch = torch.Tensor(loss_per_batch)
  print("Epoch", numEpoch, "Loss avg: ", torch.mean(loss_per_batch) / BATCH_SIZE)
  print("Epoch", numEpoch, "Loss std: ", (torch.std(loss_per_batch) / BATCH_SIZE) ** 0.5)
  print("Epoch", numEpoch, "Loss min: ", torch.min(loss_per_batch) / BATCH_SIZE)
  print("Epoch", numEpoch, "Loss max: ", torch.max(loss_per_batch) / BATCH_SIZE)
  with torch.no_grad():
    total = 0
    correct = 0
    for inputs, labels in test_set:
      out = FFN(inputs)
      for i in range(len(out)):
        prediction = out[i].argmax().item()
        # print(prediction)
        total += 1
        if prediction == labels[i]:
          # print("Correct: ", prediction)
          correct += 1
    accuracy = correct / total
    print("Epoch", numEpoch, "Test Accuracy: ", accuracy)
    testingY.append(accuracy)

    for inputs, labels in train_set:
      out = FFN(inputs)
      for i in range(len(out)):
        prediction = out[i].argmax().item()
        # print(prediction)
        total += 1
        if prediction == labels[i]:
          # print("Correct: ", prediction)
          correct += 1
    accuracy = correct / total
    print("Epoch", numEpoch, "Train Accuracy: ", accuracy)

  lossY.append(cum_loss)
  numEpoch += 1

class CNN(nn.Module):
  def __init__(self):
    super(CNN, self).__init__()
    self.conv1 = nn.Conv2d(1, 6, (100 - 1,4))
    self.conv2 = nn.Conv2d(6, 16, (1, 7))
    # self.conv3 = nn.Conv2d(16, 32, 3)
    # self.conv4 = nn.Conv2d(32, 64, 2)
    self.fc1 = nn.Linear(240, 120)
    self.fc2 = nn.Linear(120, 84)
    self.fc3 = nn.Linear(84, 2)

  def forward(self, x):
    # print(x)
    x = self.conv1(x)
    # x = F.avg_pool2d(x, (2, 2))
    # x = nn.Dropout(x)
    x = self.conv2(torch.relu(x))
    x = F.avg_pool2d(torch.relu(x), (2,1))
    # x = self.conv3(torch.relu(x))
    # x = F.avg_pool2d(torch.relu(x), 2)
    # x = self.conv4(torch.relu(x))
    # x = F.avg_pool2d(torch.relu(x), (2,2))
    x = x.view(-1, self.num_flat_features(x))
    x = torch.relu(self.fc1(x))
    x = torch.relu(self.fc2(x))
    x = F.softmax(self.fc3(x))
    return x

  def num_flat_features(self, x):
        size = x.size()[1:]
        num_features = 1
        for s in size:
            num_features *= s
        return num_features

print("========================START CNN TRAINING===========================")
CoNN = CNN()
optimizer = optim.SGD(CoNN.parameters(), lr=0.1, momentum=0.5)
loss_f = nn.CrossEntropyLoss()

lossX = np.arange(100)
lossY = []
testingY = []
CoNN.train()
numEpoch = 1
for i in range(100):
  # cum_loss = 0
  loss_per_batch = []
  for inputs, labels in train_set:
    # print(inputs.shape)
    # print(labels)
    # break
    optimizer.zero_grad()
    outputs = CoNN(inputs)
    # print(outputs[0])
    # print(outputs)
    # print(outputs.squeeze(2))
    loss = loss_f(outputs, labels.long())
    # loss.requires_grad = True
    loss.backward()
    optimizer.step()
    loss_per_batch.append(loss.data.item())
  loss_per_batch = torch.tensor(loss_per_batch)
  print("Epoch", numEpoch, "Loss avg: ", torch.mean(loss_per_batch) / BATCH_SIZE)
  print("Epoch", numEpoch, "Loss std: ", (torch.std(loss_per_batch) / BATCH_SIZE) ** 0.5)
  print("Epoch", numEpoch, "Loss min: ", torch.min(loss_per_batch) / BATCH_SIZE)
  print("Epoch", numEpoch, "Loss max: ", torch.max(loss_per_batch) / BATCH_SIZE)
  with torch.no_grad():
    total = 0
    correct = 0
    for inputs, labels in test_set:
      out = CoNN(inputs)
      for i in range(len(out)):
        prediction = out[i].argmax().item()
        # print(prediction)
        total += 1
        if prediction == labels[i]:
          # print("Correct: ", prediction)
          correct += 1
    accuracy = correct / total
    print("Epoch", numEpoch, "Test Accuracy: ", accuracy)
    testingY.append(accuracy)

    for inputs, labels in train_set:
      out = CoNN(inputs)
      for i in range(len(out)):
        prediction = out[i].argmax().item()
        # print(prediction)
        total += 1
        if prediction == labels[i]:
          # print("Correct: ", prediction)
          correct += 1
    accuracy = correct / total
    print("Epoch", numEpoch, "Train Accuracy: ", accuracy)
    # testingY.append(accuracy)

  lossY.append(cum_loss)
  numEpoch += 1
  # break


# -*- coding: utf-8 -*-

# Test formulary functions.

__all__ = ('Tests',)
__version__ = '19.05.02'

from base import TestsBase

from pygeodesy import R_M, antipode, equirectangular, euclidean, \
                      haversine, heightOf, horizon, isantipode, \
                      vincentys

class Tests(TestsBase):

    def testDistances(self, a1, b1, a2, b2, x):
        k = x * 0.003  # allow 0.3% margin
        d = haversine(a1, b1, a2, b2)
        self.test('haversine', d, x, fmt='%.3f', known=abs(d - x) < k)

        d = vincentys(a1, b1, a2, b2)
        self.test('vincentys', d, x, fmt='%.3f', known=abs(d - x) < k)

        k = x * 0.02  # allow 2% margin
        d = equirectangular(a1, b1, a2, b2, limit=90)
        self.test('equirectangular', d, x, fmt='%.3f', known=abs(d - x) < k)

        k = x * 0.11  # allow 11% margin
        d = euclidean(a1, b1, a2, b2)
        self.test('euclidean', d, x, fmt='%.3f', known=abs(d - x) < k)

    def testFormy(self):

        self.test('antipode1', antipode( 89,  179), (-89, -1))
        self.test('antipode2', antipode(-89, -179), (89, 1))

        self.test('isantipode1', isantipode( 89,  179, -89, -1), True)
        self.test('isantipode2', isantipode(-89, -179,  89,  1), True)
        self.test('isantipode3', isantipode(-89, -179, -89, -1), False)

        self.test('heightOf0',   heightOf(0,   R_M), 2638958.23912, fmt='%.5f')
        self.test('heightOf45',  heightOf(45,  R_M), 5401080.43931, fmt='%.5f')
        self.test('heightOf90',  heightOf(90,  R_M), R_M)
        self.test('heightOf135', heightOf(135, R_M), 5401080.43931, fmt='%.5f')

        self.test('horizon0',     horizon(0), 0.0)
        self.test('horizon10Km',  horizon(10000), '357099.672', fmt='%.3f')
        self.test('horizon30Kft', horizon(10000, refraction=True), '392310.704', fmt='%.3f')
        self.test('horizon10Kft', horizon( 3000, refraction=True), '214877.422', fmt='%.3f')

        Boston    = 42.3541165, -71.0693514
        Cleveland = 41.499498, -81.695391
        MtDiablo  = 37.8816, -121.9142
        Newport   = 41.49008, -71.312796
        NewYork   = 40.7791472, -73.9680804
        # <https://GeographicLib.SourceForge.io/cgi-bin/GeodSolve>
        for ll1, ll2, x in ((Boston, NewYork,    298396.057),
                            (Boston, Newport,     98071.559),
                            (Cleveland, NewYork, 653456.173),
                            (NewYork, MtDiablo, 4094953.628)):
            abx = ll1 + ll2 + (x,)
            self.testDistances(*abx)

if __name__ == '__main__':

    t = Tests(__file__, __version__)
    t.testFormy()
    t.results()
    t.exit()


# Author:
# - Kangneoung Lee

import rclpy
from rclpy.node import Node
from std_msgs.msg import Float64, Int8
import cv2
from sensor_msgs.msg import NavSatFix, Image
from cv_bridge import CvBridge
from nri_plan_msgs.msg import Gps3WayPoints

import os
import math
import time

from haversine import haversine, Unit
import numpy as np
from dataclasses import dataclass, field

@dataclass
class agent_info:
  enable: bool
  wp_num: int
  cur_wp: int
  goal_send_cnt: int
  cur_lat: float
  cur_long: float
  goal_lat: float
  goal_long: float
  part_img: np.ndarray
  waypoint: list[dict]
  dir_idx: int
  dir_cnt: int

class NriPlanWayPoint(Node):

  def __init__(self):
    """
    Class constructor to set up the node
    """

    super().__init__('nri_plan_waypoint')

    self.declare_parameter('max_agent_num', 10)
    self.declare_parameter('crop_row_width', 1.0)
    self.declare_parameter('goal_send_count_th', 3)
    self.declare_parameter('goal_reach_dist_th', 0.1)
    self.declare_parameter('trunk_dir', 'trunk_dir_default')
    self.declare_parameter('pivot_gps_lu_txt_dir', 'pivot_gps_lu_txt_dir_default')
    self.declare_parameter('pivot_gps_lb_txt_dir', 'pivot_gps_lb_txt_dir_default')
    self.declare_parameter('pivot_gps_ru_txt_dir', 'pivot_gps_ru_txt_dir_default')
    self.declare_parameter('pivot_gps_rb_txt_dir', 'pivot_gps_rb_txt_dir_default')
    self.declare_parameter('datum_txt_dir', 'datum_txt_dir_default')
    self.declare_parameter('robot_gps_goal_topic', 'robot_gps_goal_topic_default')
    self.declare_parameter('robot_gps_topic', 'global_position')

    self.max_agent_num = self.get_parameter('max_agent_num').value
    self.crop_row_width = self.get_parameter('crop_row_width').value
    self.goal_send_count_th = self.get_parameter('goal_send_count_th').value
    self.goal_reach_dist_th = self.get_parameter('goal_reach_dist_th').value
    self.trunk_dir = self.get_parameter('trunk_dir').get_parameter_value().string_value
    self.pivot_gps_lu_txt_dir = self.get_parameter('pivot_gps_lu_txt_dir').get_parameter_value().string_value
    self.pivot_gps_lb_txt_dir = self.get_parameter('pivot_gps_lb_txt_dir').get_parameter_value().string_value
    self.pivot_gps_ru_txt_dir = self.get_parameter('pivot_gps_ru_txt_dir').get_parameter_value().string_value
    self.pivot_gps_rb_txt_dir = self.get_parameter('pivot_gps_rb_txt_dir').get_parameter_value().string_value
    self.datum_txt_dir = self.get_parameter('datum_txt_dir').get_parameter_value().string_value
    self.robot_gps_goal_topic = self.get_parameter('robot_gps_goal_topic').get_parameter_value().string_value
    self.robot_gps_topic = self.get_parameter('robot_gps_topic').get_parameter_value().string_value

    self.waypoint_gen_option = 1 # value table : 0, 1
    self.npp_sub = self.create_subscription(Int8, "nri_plan_partition_ok", self.npp_callback, 3)

    self.npw_ok_topic_pub = self.create_publisher(Int8, 'nri_plan_waypoint_ok', 1)

    self.agent_gps_goal_pub = []
    self.agent_gps3p_goal_pub = []
    self.agent_gps_sub = []
    self.agent_infos = []

    for i in range(0, self.max_agent_num):
      gps_goal_pub_topic = 'Robot' + str(i) + '/' + self.robot_gps_goal_topic
      gps_goal_pub = self.create_publisher(NavSatFix, gps_goal_pub_topic, 1)
      self.agent_gps_goal_pub.append(gps_goal_pub)

      gps3p_goal_pub_topic = 'Robot' + str(i) + '/' + self.robot_gps_goal_topic + '_3p'
      gps3p_goal_pub = self.create_publisher(Gps3WayPoints, gps3p_goal_pub_topic, 1)
      self.agent_gps3p_goal_pub.append(gps3p_goal_pub)

      gps_sub_topic = 'Robot' + str(i) + '/' + self.robot_gps_topic

      # lamda function callback doesn't work with variable i.., therefore, hard coding is required to set up the number
      if i == 0:
        gps_sub = self.create_subscription(NavSatFix, gps_sub_topic, lambda msg: self.gps_callback(msg, 0), 2)
      elif i == 1:
        gps_sub = self.create_subscription(NavSatFix, gps_sub_topic, lambda msg: self.gps_callback(msg, 1), 2)
      elif i == 2:
        gps_sub = self.create_subscription(NavSatFix, gps_sub_topic, lambda msg: self.gps_callback(msg, 2), 2)
      elif i == 3:
        gps_sub = self.create_subscription(NavSatFix, gps_sub_topic, lambda msg: self.gps_callback(msg, 3), 2)
      elif i == 4:
        gps_sub = self.create_subscription(NavSatFix, gps_sub_topic, lambda msg: self.gps_callback(msg, 4), 2)
      elif i == 5:
        gps_sub = self.create_subscription(NavSatFix, gps_sub_topic, lambda msg: self.gps_callback(msg, 5), 2)
      elif i == 6:
        gps_sub = self.create_subscription(NavSatFix, gps_sub_topic, lambda msg: self.gps_callback(msg, 6), 2)
      elif i == 7:
        gps_sub = self.create_subscription(NavSatFix, gps_sub_topic, lambda msg: self.gps_callback(msg, 7), 2)
      elif i == 8:
        gps_sub = self.create_subscription(NavSatFix, gps_sub_topic, lambda msg: self.gps_callback(msg, 8), 2)
      elif i == 9:
        gps_sub = self.create_subscription(NavSatFix, gps_sub_topic, lambda msg: self.gps_callback(msg, 9), 2)
      self.agent_gps_sub.append(gps_sub)

      single_agent_info = agent_info(False, -1, -1, 0, -1, -1, -1, -1, np.zeros((1,1)), [], -1, -1)

      self.agent_infos.append(single_agent_info)

    timer_period = 1.0 # seconds
    self.timer = self.create_timer(timer_period, self.timer_callback)
    self.time_stamp = self.get_clock().now().to_msg()

    self.pivot_lat = 0.0
    self.pivot_long = 0.0

    self.x_axis_del_lat = 0.0
    self.x_axis_del_long = 0.0
    self.y_axis_del_lat = 0.0
    self.y_axis_del_long = 0.0

    self.map_width = 0
    self.map_height = 0
    self.pixel_per_m = 0.0
    self.map_width_meter = 0.0

    self.datum_x_norm = 0.0
    self.datum_y_norm = 0.0

    self.wp_dir_x = [[0, 1, 0, 1], [0, -1, 0, -1], [0, 1, 0, 1] , [0, -1, 0, -1]]  #N - E - S - E,  N - W - S - W,  S - E - N - E,  S - W - N - W
    self.wp_dir_y = [[-1, 0, 1, 0], [-1, 0, 1, 0], [1, 0, -1, 0] , [1, 0, -1, 0]]  #N - E - S - E,  N - W - S - W,  S - E - N - E,  S - W - N - W

    self.completed = False
  def goal_reached_check(self, robot_index):
    cur_lat = self.agent_infos[robot_index].cur_lat
    cur_long = self.agent_infos[robot_index].cur_long
    goal_lat = self.agent_infos[robot_index].goal_lat
    goal_long = self.agent_infos[robot_index].goal_long
    self.get_logger().info(f"goal reach check... {robot_index} agent, cur_lat : {cur_lat}")
    self.get_logger().info(f"goal reach check... {robot_index} agent, goal_lat : {goal_lat}")
    self.get_logger().info(f"goal reach check... {robot_index} agent, cur_long : {cur_long}")
    self.get_logger().info(f"goal reach check... {robot_index} agent, goal_long : {goal_long}")
    dist = haversine((cur_lat, cur_long), (goal_lat, goal_long), unit=Unit.METERS)
    self.get_logger().info(f"goal reach check... {robot_index} agent, dist : {dist}")
    if dist < self.goal_reach_dist_th:
      return True
    else:
      return False #return False

  def send_gps_goal_3p(self):
    for i in range(0, self.max_agent_num):
      if self.agent_infos[i].enable == False:
        continue

      if self.agent_infos[i].wp_num != 3:
        self.get_logger().info(f" {i} th agent waypoint generation fail... num of wp = {self.agent_infos[i].wp_num} ")
        continue
      msg = Gps3WayPoints()
      st_msg = NavSatFix()

      for j in range(0,3):
        wp = self.agent_infos[i].waypoint[j]
        y_goal_pixel = wp['y']
        x_goal_pixel = wp['x']
        gps_lat = self.pivot_lat + self.y_axis_del_lat * (self.map_height - y_goal_pixel)/self.map_height + self.x_axis_del_lat * x_goal_pixel/self.map_width
        gps_long = self.pivot_long + self.y_axis_del_long * (self.map_height - y_goal_pixel)/self.map_height + self.x_axis_del_long * x_goal_pixel/self.map_width
        msg.latitudes.append(gps_lat)
        msg.longitudes.append(gps_long)

        if j == 0:
          st_msg.latitude = gps_lat
          st_msg.longitude = gps_long

      self.agent_gps3p_goal_pub[i].publish(msg)

      self.agent_gps_goal_pub[i].publish(st_msg)

  def send_gps_goal(self):
    for i in range(0, self.max_agent_num):
      if self.agent_infos[i].enable == False:
        continue

      if self.agent_infos[i].cur_wp >= self.agent_infos[i].wp_num:
        continue

      if self.agent_infos[i].cur_wp == 0:
        wp = self.agent_infos[i].waypoint[self.agent_infos[i].cur_wp]
        y_goal_pixel = wp['y']
        x_goal_pixel = wp['x']
        gps_lat = self.pivot_lat + self.y_axis_del_lat * (self.map_height - y_goal_pixel)/self.map_height + self.x_axis_del_lat * x_goal_pixel/self.map_width
        gps_long = self.pivot_long + self.y_axis_del_long * (self.map_height - y_goal_pixel)/self.map_height + self.x_axis_del_long * x_goal_pixel/self.map_width
        self.agent_infos[i].goal_lat = gps_lat
        self.agent_infos[i].goal_long = gps_long

      reached = self.goal_reached_check(i)
      if reached is True:
        self.agent_infos[i].cur_wp = self.agent_infos[i].cur_wp + 1
        self.agent_infos[i].goal_send_cnt = 1

        wp = self.agent_infos[i].waypoint[self.agent_infos[i].cur_wp]
        y_goal_pixel = wp['y']
        x_goal_pixel = wp['x']

        gps_lat = self.pivot_lat + self.y_axis_del_lat * (self.map_height - y_goal_pixel)/self.map_height + self.x_axis_del_lat * x_goal_pixel/self.map_width
        gps_long = self.pivot_long + self.y_axis_del_long * (self.map_height - y_goal_pixel)/self.map_height + self.x_axis_del_long * x_goal_pixel/self.map_width
        self.agent_infos[i].goal_lat = gps_lat
        self.agent_infos[i].goal_long = gps_long

        msg = NavSatFix()
        msg.latitude = gps_lat
        msg.longitude = gps_long
        self.agent_gps_goal_pub[i].publish(msg)
      else:
        if self.agent_infos[i].goal_send_cnt > self.goal_send_count_th:
          continue
        self.agent_infos[i].goal_send_cnt = self.agent_infos[i].goal_send_cnt + 1
        msg = NavSatFix()
        msg.latitude = self.agent_infos[i].goal_lat
        msg.longitude = self.agent_infos[i].goal_long
        self.agent_gps_goal_pub[i].publish(msg)

  def gps_goal_debug(self):
    for i in range(0, self.max_agent_num):
      if self.agent_infos[i].enable == False:
        continue

      debug_txt_dir = self.trunk_dir + '/agent_wp_gps_debug_' + str(i) +'.txt'
      f = open(debug_txt_dir, "w")
      f.writelines(f"waypoint number : {self.agent_infos[i].wp_num} \n")
      f.writelines(f"current waypoint idx : {self.agent_infos[i].cur_wp} \n")
      f.writelines(f"current dir idx : {self.agent_infos[i].dir_idx} \n")

      for j in range(0, self.agent_infos[i].wp_num):
        wp = self.agent_infos[i].waypoint[j]
        y_goal_pixel = wp['y']
        x_goal_pixel = wp['x']

        gps_lat = self.pivot_lat + self.y_axis_del_lat * (self.map_height - y_goal_pixel)/self.map_height + self.x_axis_del_lat * x_goal_pixel/self.map_width
        gps_long = self.pivot_long + self.y_axis_del_long * (self.map_height - y_goal_pixel)/self.map_height + self.x_axis_del_long * x_goal_pixel/self.map_width
        f.writelines(f"{j}th gps waypoint... gps_goal_lat : {gps_lat} gps_goal_long : {gps_long}\n")

      f.close()

  def timer_callback(self):
    if self.completed is True:
      msg = Int8()
      msg.data = 1
      if self.waypoint_gen_option == 0:
        self.send_gps_goal()
      elif self.waypoint_gen_option == 1:
        self.send_gps_goal_3p()

      self.npw_ok_topic_pub.publish(msg)

    else:
      for i in range(0, self.max_agent_num):
        msg = NavSatFix()
        msg.latitude = 0.0
        msg.longitude = 0.0
        self.agent_gps_goal_pub[i].publish(msg)

      msg = Int8()
      msg.data = 0
      self.npw_ok_topic_pub.publish(msg)

  def npp_callback(self, msg):
    if msg.data == 0:
      self.completed = False
      for i in range(0, self.max_agent_num):
        self.agent_infos[i].enable = False
        self.agent_infos[i].wp_num = -1
        self.agent_infos[i].cur_wp = -1
        self.agent_infos[i].goal_send_cnt = 0
        #self.agent_infos[i].cur_lat = -1
        #self.agent_infos[i].cur_long = -1
        self.agent_infos[i].goal_lat = -1
        self.agent_infos[i].goal_long = -1
        self.agent_infos[i].part_img = np.zeros((1,1))
        self.agent_infos[i].waypoint = []
        self.agent_infos[i].dir_idx = -1
        self.agent_infos[i].dir_cnt = -1
    else:
      if self.completed is False:
        rd_ok = self.read_dataum()
        if rd_ok is False:
          self.get_logger().info(" read datum text file fail ")
          return

        self.read_gps_pivot()
        self.meter_of_map_width()

        for i in range(0, self.max_agent_num):
          rp_ok = self.read_partition(i)
          self.get_logger().info(f"{i}th agent availability status : {self.agent_infos[i].enable}")
          if rp_ok is False:
            self.get_logger().info(f"{i}th robot: read partition text file fail ")
            continue
        if self.waypoint_gen_option == 0:
          self.waypoint_gen()

        elif self.waypoint_gen_option == 1:
          self.waypoint_gen_3p()

        self.waypoint_gen_debug()
        self.gps_goal_debug()
        self.completed = True

  def gps_callback(self, msg, robot_index):
    self.agent_infos[robot_index].cur_lat = msg.latitude
    self.agent_infos[robot_index].cur_long = msg.longitude
    #self.get_logger().info(f"gps_callback... {robot_index} agent, cur_lat : {self.agent_infos[robot_index].cur_lat}")
    #self.get_logger().info(f"gps_callback... {robot_index} agent, cur_long : {self.agent_infos[robot_index].cur_long}")

  def read_gps_pivot(self):
    f = open(self.pivot_gps_lb_txt_dir, "r")
    string = f.readline()
    lb_latitude = float(string.split()[0])
    lb_longitude = float(string.split()[1])
    f.close()

    f = open(self.pivot_gps_rb_txt_dir, "r")
    string = f.readline()
    rb_latitude = float(string.split()[0])
    rb_longitude = float(string.split()[1])
    f.close()

    f = open(self.pivot_gps_lu_txt_dir, "r")
    string = f.readline()
    lu_latitude = float(string.split()[0])
    lu_longitude = float(string.split()[1])
    f.close()

    self.pivot_lat = lb_latitude
    self.pivot_long = lb_longitude

    self.x_axis_del_lat = rb_latitude - lb_latitude
    self.x_axis_del_long = rb_longitude - lb_longitude

    self.y_axis_del_lat = lu_latitude - lb_latitude
    self.y_axis_del_long = lu_longitude - lb_longitude

  def meter_of_map_width(self):
    self.map_width_meter = haversine((self.pivot_lat, self.pivot_long), (self.pivot_lat + self.x_axis_del_lat, self.pivot_long + self.x_axis_del_long), unit=Unit.METERS)
    self.get_logger().info(f"self.map_width_meter : {self.map_width_meter}")

  def read_dataum(self):
    if not os.path.exists(self.datum_txt_dir):
      return False

    try:
      f = open(self.datum_txt_dir, "r")
      string = f.readline()
      self.datum_x_norm = float(string.split()[0])
      self.datum_y_norm = float(string.split()[1])
      f.close()
    except:
      return False

    return True

  def read_partition(self, robot_index):

    rp_ok = False

    txt_dir = self.trunk_dir + '/agent' + str(robot_index) +'.txt'
    img_dir = self.trunk_dir + '/agent' + str(robot_index) +'.png'

    if not os.path.exists(txt_dir) or not os.path.exists(img_dir):
      self.get_logger().info(f"one of path is fail")
      self.agent_infos[robot_index].enable = False
      rp_ok = False
      return rp_ok

    try:
      f = open(txt_dir, "r")
      string = f.readline()
      wp = {}
      wp['x'] = int(string.split()[0])
      wp['y'] = int(string.split()[1])
      f.close()

      img = cv2.imread(img_dir, cv2.IMREAD_GRAYSCALE)
      self.map_width = img.shape[1]
      self.map_height = img.shape[0]
      thres = 254
      max_val = 255
      ret, bin_img = cv2.threshold(img, thres, max_val, cv2.THRESH_BINARY_INV)
      debug_bin_img_dir = self.trunk_dir + '/bin_img_raw_' + str(robot_index) +'.png'
      cv2.imwrite(debug_bin_img_dir, bin_img)
      contours = cv2.findContours(bin_img, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
      contours = contours[0] if len(contours) == 2 else contours[1]   # if len is 2, first element is value and second one is hierarchy
      contour_props = []
      for contour in contours:
        x1, y1, w, h = cv2.boundingRect(contour)
        x2 = x1 + w
        y2 = y1 + h
        if x1 > 0:
          x1 = x1 -1
        if x2 < self.map_width - 1:
          x2 = x2 + 1
        if y1 > 0:
          y1 = y1 -1
        if y2 < self.map_height - 1:
          y2 = y2 + 1
        add = x1 , y1, x2, y2
        contour_props.append(add)
        #self.get_logger().info(f"{robot_index}th agent contour info... x1 : {x1}, x2 : {x2}, y1 : {y1}, y2 : {y2}")
      #bin_test_img = np.zeros((self.map_height, self.map_width), np.uint8)
      for data in contour_props:
        #self.get_logger().info(f"{robot_index}th agent contour info... data[0] : {data[0]}, data[2] : {data[2]}, data[1] : {data[1]}, data[3] : {data[3]}")
        #bin_test_img = cv2.cvtColor(bin_test_img, cv2.COLOR_GRAY2BGR);
        bin_img = cv2.rectangle(bin_img, (data[0], data[1]), (data[2], data[3]), (255, 255, 255), cv2.FILLED)
      #debug_bin_img_rec_dir = self.trunk_dir + '/bin_img_rec_' + str(robot_index) +'.png'
      #cv2.imwrite(debug_bin_img_rec_dir, bin_img)
      #kernel = np.ones((3, 3), np.uint8)
      #bin_img = cv2.dilate(bin_img, kernel, iterations = 1)
      #debug_bin_img_dilate_dir = self.trunk_dir + '/bin_img_dilate_' + str(robot_index) +'.png'
      #cv2.imwrite(debug_bin_img_dilate_dir, bin_img)
      self.agent_infos[robot_index].part_img = bin_img.copy()

      x_init_correction = 0
      self.pixel_per_m =  self.map_width/self.map_width_meter
      self.crop_row_pixel = self.pixel_per_m * self.crop_row_width
      if (wp['x'] >= self.datum_x_norm*self.map_width) and (wp['y'] < self.datum_y_norm*self.map_height):
        self.agent_infos[robot_index].dir_idx = 0
        x_init_correction = self.crop_row_pixel
      elif (wp['x'] < self.datum_x_norm*self.map_width) and (wp['y'] < self.datum_y_norm*self.map_height):
        self.agent_infos[robot_index].dir_idx = 1
        x_init_correction = -self.crop_row_pixel
      elif (wp['x'] >= self.datum_x_norm*self.map_width) and (wp['y'] >= self.datum_y_norm*self.map_height):
        self.agent_infos[robot_index].dir_idx = 2
        x_init_correction = self.crop_row_pixel
      elif (wp['x'] < self.datum_x_norm*self.map_width) and (wp['y'] >= self.datum_y_norm*self.map_height):
        self.agent_infos[robot_index].dir_idx = 3
        x_init_correction = -self.crop_row_pixel

      wp['x'] = wp['x'] #+ x_init_correction
      self.agent_infos[robot_index].dir_cnt = self.agent_infos[robot_index].dir_cnt + 1
      self.agent_infos[robot_index].waypoint.append(wp)
      self.agent_infos[robot_index].wp_num = 1
      self.agent_infos[robot_index].cur_wp = 0

      rp_ok = True
      self.agent_infos[robot_index].enable = True
    except Exception as error:
      self.get_logger().info(f"image processing error : {error}")
      rp_ok = False
      self.agent_infos[robot_index].enable = False

    return rp_ok

  def vertical_search(self, robot_index, cur_x, cur_y, cur_dir_y, dir_idx):
    next_y_temp = cur_y + self.map_height*cur_dir_y

    if next_y_temp < 0:
      next_y_temp = 0
    else:
      next_y_temp = self.map_height - 1

    round_cur_x = round(cur_x)
    if dir_idx == 0 or dir_idx == 2:
      round_cur_x = round_cur_x + 1 # compensate truncation error
    else:
      round_cur_x = round_cur_x -1 # compensate truncation error

    if round_cur_x < 0:
      round_cur_x = 0
    elif round_cur_x >= self.map_width:
      round_cur_x = self.map_width - 1

    if self.agent_infos[robot_index].part_img[int(next_y_temp), int(round_cur_x)] == 255:
      return next_y_temp

    if next_y_temp == cur_y:
      return next_y_temp

    lo = 0
    ho = 0
    if next_y_temp > cur_y :
      lo = cur_y
      ho = next_y_temp
      forward = True
    else:
      lo = next_y_temp
      ho = cur_y
      forward = False

    if forward is True:
      while(lo + 1 < ho):
        mid = int((lo + ho)/2)
        if self.agent_infos[robot_index].part_img[mid, int(round_cur_x)] == 255:
          lo = mid
        else:
          ho = mid
      return lo
    else:
      while(lo + 1 < ho):
        mid = int((lo + ho)/2)
        if self.agent_infos[robot_index].part_img[mid, int(round_cur_x)] == 255:
          ho = mid
        else:
          lo = mid
      return ho

  def horizontal_search_3p(self, robot_index, cur_x, cur_y, cur_dir_x, dir_idx):
    round_cur_x = round(cur_x)
    if dir_idx == 0 or dir_idx == 2:
      round_cur_x = round_cur_x + 1 # compensate truncation error
    else:
      round_cur_x = round_cur_x -1 # compensate truncation error
    if round_cur_x < 0:
      round_cur_x = 0

    next_x_temp = round_cur_x + self.map_width*cur_dir_x

    if next_x_temp < 0:
      next_x_temp = 0
    else:
      next_x_temp = self.map_width - 1

    if self.agent_infos[robot_index].part_img[int(cur_y), int(next_x_temp)] == 255:
      return next_x_temp

    if next_x_temp == round_cur_x:
      return next_x_temp

    lo = 0
    ho = 0

    if next_x_temp > round_cur_x :
      lo = round_cur_x
      ho = next_x_temp
      forward = True
    else:
      lo = next_x_temp
      ho = round_cur_x
      forward = False

    if forward is True:
      while(lo + 1 < ho):
        mid = int((lo + ho)/2)
        if self.agent_infos[robot_index].part_img[int(cur_y), mid] == 255:
          lo = mid
        else:
          ho = mid
      return lo
    else:
      while(lo + 1 < ho):
        mid = int((lo + ho)/2)
        if self.agent_infos[robot_index].part_img[int(cur_y), mid] == 255:
          ho = mid
        else:
          lo = mid
      return ho

  def horizontal_search(self, robot_index, cur_x, cur_y, cur_dir_x):
    next_x_temp = cur_x + self.crop_row_pixel*cur_dir_x
    if next_x_temp < 0:
      next_x_temp = 0
      return cur_x, True
    elif next_x_temp >= self.map_width:
      next_x_temp = self.map_width - 1
      return cur_x, True

    round_next_x_temp = round(next_x_temp)
    px_val = self.agent_infos[robot_index].part_img[int(cur_y), int(round_next_x_temp)]

    if px_val == 255:
      return next_x_temp, False

    while(True):
      next_x_temp = next_x_temp + self.crop_row_pixel*cur_dir_x
      round_next_x_temp = round(next_x_temp)
      if next_x_temp < 1 or next_x_temp >= self.map_width - 1:
        return cur_x, True

      if self.agent_infos[robot_index].part_img[int(cur_y), int(round_next_x_temp)] == 255:
        return next_x_temp, False

  def waypoint_gen(self):
    for i in range(0, self.max_agent_num):
      if self.agent_infos[i].enable is False:
        continue
      end_flag = False
      while(True):

        dir_x = self.wp_dir_x[self.agent_infos[i].dir_idx]
        dir_y = self.wp_dir_y[self.agent_infos[i].dir_idx]
        cur_dir_x = dir_x[self.agent_infos[i].dir_cnt]
        cur_dir_y = dir_y[self.agent_infos[i].dir_cnt]

        cur_x = self.agent_infos[i].waypoint[self.agent_infos[i].cur_wp]['x']
        cur_y = self.agent_infos[i].waypoint[self.agent_infos[i].cur_wp]['y']

        next_x = cur_x
        next_y = cur_y

        if cur_dir_x == 0:
          next_y = self.vertical_search(i, cur_x, cur_y, cur_dir_y, self.agent_infos[i].dir_idx)
        elif cur_dir_y == 0:
          next_x, end_flag = self.horizontal_search(i, cur_x, cur_y, cur_dir_x)

        if end_flag is True:
          break

        wp = {}
        wp['x'] = next_x
        wp['y'] = next_y

        self.agent_infos[i].waypoint.append(wp)

        self.agent_infos[i].wp_num = self.agent_infos[i].wp_num + 1
        self.agent_infos[i].cur_wp = self.agent_infos[i].cur_wp + 1

        self.agent_infos[i].dir_cnt = self.agent_infos[i].dir_cnt + 1
        if self.agent_infos[i].dir_cnt >= 4:
          self.agent_infos[i].dir_cnt = 0

      self.agent_infos[i].cur_wp = 0
      self.agent_infos[i].dir_cnt = 0

  def waypoint_gen_3p(self):
    for i in range(0, self.max_agent_num):
      if self.agent_infos[i].enable is False:
        continue
      end_flag = False
      for j in range(0,2):

        dir_x = self.wp_dir_x[self.agent_infos[i].dir_idx]
        dir_y = self.wp_dir_y[self.agent_infos[i].dir_idx]
        cur_dir_x = dir_x[self.agent_infos[i].dir_cnt]
        cur_dir_y = dir_y[self.agent_infos[i].dir_cnt]

        cur_x = self.agent_infos[i].waypoint[self.agent_infos[i].cur_wp]['x']
        cur_y = self.agent_infos[i].waypoint[self.agent_infos[i].cur_wp]['y']

        next_x = cur_x
        next_y = cur_y

        if cur_dir_x == 0:
          next_y = self.vertical_search(i, cur_x, cur_y, cur_dir_y, self.agent_infos[i].dir_idx)
        elif cur_dir_y == 0:
          next_x= self.horizontal_search_3p(i, cur_x, cur_y, cur_dir_x, self.agent_infos[i].dir_idx)

        wp = {}
        wp['x'] = next_x
        wp['y'] = next_y

        self.agent_infos[i].waypoint.append(wp)

        self.agent_infos[i].wp_num = self.agent_infos[i].wp_num + 1
        self.agent_infos[i].cur_wp = self.agent_infos[i].cur_wp + 1

        self.agent_infos[i].dir_cnt = self.agent_infos[i].dir_cnt + 1
        if self.agent_infos[i].dir_cnt >= 4:
          self.agent_infos[i].dir_cnt = 0

      self.agent_infos[i].cur_wp = 0
      self.agent_infos[i].dir_cnt = 0

  def waypoint_gen_debug(self):
    for i in range(0, self.max_agent_num):
      if self.agent_infos[i].enable is False:
        continue
      debug_txt_dir = self.trunk_dir + '/agent_wp_debug_' + str(i) +'.txt'
      debug_img_dir = self.trunk_dir + '/agent_wp_debug_' + str(i) +'.png'
      f = open(debug_txt_dir, "w")
      f.writelines(f"waypoint number : {self.agent_infos[i].wp_num} \n")
      f.writelines(f"current waypoint idx : {self.agent_infos[i].cur_wp} \n")
      f.writelines(f"current dir idx : {self.agent_infos[i].dir_idx} \n")

      img = self.agent_infos[i].part_img.copy()
      img_rgb = np.zeros((img.shape[0], img.shape[1], 3))
      img_rgb[:,:,0] = img
      img_rgb[:,:,1] = img
      img_rgb[:,:,2] = img
      for j, wp in enumerate(self.agent_infos[i].waypoint):
        f.writelines(f"{j}th waypoint x : {wp['x']},   waypoint y : {wp['y']} \n")
        img_rgb[int(wp['y']), int(wp['x']), 0] = 0
        img_rgb[int(wp['y']), int(wp['x']), 1] = 255
        img_rgb[int(wp['y']), int(wp['x']), 2] = 0

      cv2.imwrite(debug_img_dir, img_rgb)
      f.close()

def main(args=None):

  # Initialize the rclpy library
  rclpy.init(args=args)

  # Create the node
  nri_plan_waypoint_ros = NriPlanWayPoint()

  # Spin the node so the callback function is called.
  rclpy.spin(nri_plan_waypoint_ros)

  # Destroy the node explicitly
  # (optional - otherwise it will be done automatically
  # when the garbage collector destroys the node object)
  nri_plan_waypoint_ros.destroy_node()

  # Shutdown the ROS client library for Python
  rclpy.shutdown()

if __name__ == '__main__':
  main()


#!/usr/bin/python

import os, sys, threading, smtplib
from email.MIMEText import MIMEText

def checkDevArea():
  os.system('ls /mvista/dev_area > /dev/null')

def main():
  thread = threading.Thread(target=checkDevArea)
  thread.start()
  thread.join(10)
  alive = thread.isAlive()
  if alive:
    #Issues with dumbo, send email
    smtpObject = smtplib.SMTP()
    mailMsg    = MIMEText('')
    mailMsg['From'] = "build@mvista.com"
    mailMsg['To'] = "9253374727@vtext.com"
    mailMsg['Subject'] = "/mvista/dev_area is down"
    #Send Message
    smtpObject.connect('mail.mvista.com')
    smtpObject.sendmail('cvanarsdall@mvista.com','9253374727@vtext.com', mailMsg.as_string())
    smtpObject.close()

  else:
    sys.exit(1)

if __name__ == "__main__":
  main()


from flask import Flask
from graphene import ObjectType, String, Schema
from flask_graphql import GraphQLView

class Query(ObjectType):
    hello = String(description='Hello')
    def resolve_hello(self, args, info):
        return 'World'

app = Flask(__name__)
app.debug = True
app.add_url_rule('/', view_func=GraphQLView.as_view('graphql', schema=Schema(query=Query)))

if __name__ == '__main__':
    app.run()


'''
@author: Dallas Fraser
@author: 2019-03-13
@organization: MLSB API
@summary: Tests the schedule view api
'''
from api.helper import loads
from api.routes import Routes
from api.errors import LeagueDoesNotExist
from api.test.BaseTest import TestSetup, INVALID_ID
from api.test.advanced.mock_league import MockLeague
import datetime

class TestSchedule(TestSetup):

    def testEmptyYear(self):
        """Test schedule view for empty league"""
        rv = self.app.get(Routes['vschedule'] + "/2012" + "/1")
        expect = []
        self.output(loads(rv.data))
        self.output(expect)
        self.assertEqual(expect, loads(rv.data)['items'],
                         Routes['vschedule'] + "View: on 2012 year")

    def testLeagueDoesNotExist(self):
        """Test schedule view for empty league"""
        url = Routes['vschedule'] + "/2012" + "/" + str(INVALID_ID)
        rv = self.app.get(url)
        self.output(rv.status_code)
        self.output(LeagueDoesNotExist.status_code)
        self.assertEqual(rv.status_code, LeagueDoesNotExist.status_code,
                         Routes['vschedule'] + "View: on invalid league")

    def testMockLeague(self):
        """Test schedule view for mocked league"""
        league_id = MockLeague(self).league['league_id']
        year = datetime.datetime.now().year
        url = Routes['vschedule'] + "/" + str(year) + "/" + str(league_id)
        rv = self.app.get(url)
        game = {'away_team': 'Advanced Test Sponsor Test Team 2',
                'home_team': 'Advanced Test Sponsor Test Team',
                'score': '1-6'}
        expect = ["1-6", "0-0", ""]
        self.output(loads(rv.data))
        self.output(expect)
        for index, game in enumerate(loads(rv.data)['items']):
            self.assertEqual(expect[index], game['score'])


import os
import tempfile

import configparser
import pytest
from configparser import NoOptionError, NoSectionError

from assertpy import assert_that
from pcluster.config.param_types import Param
from pcluster.config.pcluster_config import PclusterConfig
from tests.pcluster.config.defaults import CFN_CONFIG_NUM_OF_PARAMS, DefaultDict

def get_param_definition(section_definition, param_key):
    param_definition = section_definition.get("params").get(param_key)
    return param_definition, param_definition.get("type", Param)

def merge_dicts(*args):
    """Merge multiple dictionaries into a new dictionary as a shallow copy."""
    merged_dict = {}
    for input_dict in args:
        merged_dict.update(input_dict)
    return merged_dict

def get_pcluster_config_example():
    current_dir = os.path.dirname(os.path.abspath(__file__))
    return os.path.join(current_dir, "..", "..", "..", "pcluster", "examples", "config")

def assert_param_from_file(mocker, section_definition, param_key, param_value, expected_value, expected_message):
    section_label = section_definition.get("default_label")
    section_name = "{0}{1}".format(section_definition.get("key"), " {0}".format(section_label) if section_label else "")
    config_parser = configparser.ConfigParser()
    config_parser.add_section(section_name)

    pcluster_config = get_mocked_pcluster_config(mocker)

    if param_value:
        config_parser.set(section_name, param_key, param_value)

    param_definition, param_type = get_param_definition(section_definition, param_key)

    if expected_message:
        with pytest.raises(SystemExit, match=expected_message):
            param_type(
                section_definition.get("key"), section_label, param_key, param_definition, pcluster_config
            ).from_file(config_parser)
    else:
        param = param_type(
            section_definition.get("key"), section_label, param_key, param_definition, pcluster_config
        ).from_file(config_parser)
        assert_that(param.value, description="{0} assert fail".format(param.key)).is_equal_to(expected_value)

def assert_param_validator(mocker, config_parser_dict, expected_message=None):
    config_parser = configparser.ConfigParser()
    config_parser.read_dict(config_parser_dict)

    mocker.patch("pcluster.config.param_types.get_avail_zone", return_value="mocked_avail_zone")
    mocker.patch.object(PclusterConfig, "_PclusterConfig__check_account_capacity")

    if expected_message:
        with pytest.raises(SystemExit, match=expected_message):
            _ = init_pcluster_config_from_configparser(config_parser)
    else:
        _ = init_pcluster_config_from_configparser(config_parser)

def assert_section_from_cfn(mocker, section_definition, cfn_params_dict, expected_section_dict):

    cfn_params = []
    for cfn_key, cfn_value in cfn_params_dict.items():
        cfn_params.append({"ParameterKey": cfn_key, "ParameterValue": cfn_value})

    pcluster_config = get_mocked_pcluster_config(mocker)

    section_type = section_definition.get("type")
    section = section_type(section_definition, pcluster_config).from_cfn_params(cfn_params)

    if section.label:
        assert_that(section.label).is_equal_to("default")

    # update expected dictionary
    default_dict = DefaultDict[section_definition.get("key")].value
    expected_dict = default_dict.copy()
    if isinstance(expected_section_dict, dict):
        expected_dict.update(expected_section_dict)

    section_dict = {}
    for param_key, param in section.params.items():
        section_dict[param_key] = param.value

    assert_that(section_dict).is_equal_to(expected_dict)

def get_mocked_pcluster_config(mocker):
    return PclusterConfig(config_file="wrong-file")

def assert_section_from_file(mocker, section_definition, config_parser_dict, expected_dict_params, expected_message):
    config_parser = configparser.ConfigParser()
    config_parser.read_dict(config_parser_dict)

    # update expected dictionary
    default_dict_key = section_definition.get("key")
    if default_dict_key == "global":
        default_dict_key += "_"
    default_dict = DefaultDict[default_dict_key].value
    expected_dict = default_dict.copy()
    if isinstance(expected_dict_params, dict):
        expected_dict.update(expected_dict_params)

    pcluster_config = get_mocked_pcluster_config(mocker)

    section_type = section_definition.get("type")
    if expected_message:
        with pytest.raises(SystemExit, match=expected_message):
            _ = section_type(section_definition, pcluster_config).from_file(config_parser)
    else:
        section = section_type(section_definition, pcluster_config).from_file(config_parser)
        section_dict = {}
        for param_key, param in section.params.items():
            section_dict[param_key] = param.value

        assert_that(section_dict).is_equal_to(expected_dict)

def assert_section_to_file(mocker, section_definition, section_dict, expected_config_parser_dict, expected_message):
    expected_config_parser = configparser.ConfigParser()
    expected_config_parser.read_dict(expected_config_parser_dict)

    pcluster_config = get_mocked_pcluster_config(mocker)

    output_config_parser = configparser.ConfigParser()
    section_type = section_definition.get("type")
    section = section_type(section_definition, pcluster_config, section_label="default")

    for param_key, param_value in section_dict.items():
        param_definition, param_type = get_param_definition(section.definition, param_key)
        param = param_type(section_definition.get("key"), "default", param_key, param_definition, pcluster_config)
        param.value = param_value
        section.add_param(param)

    section.to_file(output_config_parser)

    for section_key, section_params in expected_config_parser_dict.items():
        for param_key, param_value in section_params.items():

            assert_that(output_config_parser.has_option(section_key, param_key))
            if expected_message is not None:
                if "No section" in expected_message:
                    with pytest.raises(NoSectionError, match=expected_message):
                        assert_that(output_config_parser.get(section_key, param_key)).is_equal_to(param_value)
                else:
                    with pytest.raises(NoOptionError, match=expected_message):
                        assert_that(output_config_parser.get(section_key, param_key)).is_equal_to(param_value)

            else:
                assert_that(output_config_parser.get(section_key, param_key)).is_equal_to(param_value)

def assert_section_to_cfn(mocker, section_definition, section_dict, expected_cfn_params):

    pcluster_config = get_mocked_pcluster_config(mocker)

    section_type = section_definition.get("type")
    section = section_type(section_definition, pcluster_config)
    for param_key, param_value in section_dict.items():
        param_definition, param_type = get_param_definition(section_definition, param_key)
        param = param_type(section_definition.get("key"), "default", param_key, param_definition, pcluster_config)
        param.value = param_value
        section.add_param(param)
    pcluster_config.add_section(section)

    cfn_params = section.to_cfn()
    assert_that(cfn_params).is_equal_to(expected_cfn_params)

def assert_section_params(mocker, pcluster_config_reader, settings_label, expected_cfn_params):
    if isinstance(expected_cfn_params, SystemExit):
        with pytest.raises(SystemExit):
            PclusterConfig(
                cluster_label="default",
                config_file=pcluster_config_reader(settings_label=settings_label),
                fail_on_file_absence=True,
                fail_on_error=True,
            )
    else:
        pcluster_config = PclusterConfig(
            config_file=pcluster_config_reader(settings_label=settings_label), fail_on_file_absence=True
        )

        cfn_params = pcluster_config.to_cfn()

        assert_that(len(cfn_params)).is_equal_to(CFN_CONFIG_NUM_OF_PARAMS)

        for param_key, _ in cfn_params.items():
            assert_that(cfn_params.get(param_key), description=param_key).is_equal_to(
                expected_cfn_params.get(param_key)
            )

def init_pcluster_config_from_configparser(config_parser, validate=True):
    with tempfile.NamedTemporaryFile(delete=False) as config_file:

        with open(config_file.name, "w") as cf:
            config_parser.write(cf)

        pcluster_config = PclusterConfig(
            config_file=config_file.name, cluster_label="default", fail_on_file_absence=True
        )
        if validate:
            pcluster_config.validate()
    return pcluster_config


from __future__ import annotations
import os
import logging
import secrets
from typing import Any
from dataclasses import dataclass
from PyQt5.QtCore import QSettings

@dataclass
class DefaultSetting:
    """Default settings."""

    settings: QSettings
    name: str
    value: Any
    group_name: str = None

    def initialize_setting(self) -> DefaultSetting:
        """Initialize the default setting or pulls the current setting value."""
        if self.group_name:
            self.settings.beginGroup(self.group_name)

        if not self.settings.contains(self.name):
            self.settings.setValue(self.name, self.value)
        else:
            self.value = self.settings.value(self.name)

        if self.group_name:
            self.settings.endGroup()
        return self

    def save(self) -> DefaultSetting:
        """Save the default setting."""
        if self.group_name:
            self.settings.beginGroup(self.group_name)

        self.settings.setValue(self.name, self.value)

        if self.group_name:
            self.settings.endGroup()
        return self

COMPANY_NAME = "DF-Software"
PROGRAM_NAME = "Cut List Generator"
PROGRAM_VERSION = "1.0.7"
USER_HOME_FOLDER = os.path.expanduser("~")
COMPANY_FOLDER = os.path.join(USER_HOME_FOLDER, "Documents", COMPANY_NAME)
PROGRAM_FOLDER = os.path.join(COMPANY_FOLDER, PROGRAM_NAME)
LOG_FOLDER = os.path.join(PROGRAM_FOLDER, "Logs")
LOG_SQLALCHEMY_FOLDER = os.path.join(LOG_FOLDER, "SQLAlchemy")

settings = QSettings(COMPANY_NAME, PROGRAM_NAME)

# Program settings
DATE_TIME_FORMAT = "%m-%d-%Y %H:%M"
DATE_FORMAT = "%m-%d-%Y"
DEFAULT_DUE_DATE_PUSH_BACK_DAYS = 30

LAST_USERNAME = DefaultSetting(
    settings=settings, name="last_username", value=""
).initialize_setting()
DEBUG = (
    DefaultSetting(settings=settings, name="debug", value=False)
    .initialize_setting()
    .value
)
if DEBUG == "true":
    DEBUG = True
else:
    DEBUG = False

# Logging settings
LOG_FILE = "cutlistgenerator.log"
SQLALCHEMY_ENGINE_LOG_FILE = "sqlalchemy_engine.log"
SQLALCHEMY_POOL_LOG_FILE = "sqlalchemy_pool.log"
SQLALCHEMY_DIALECT_LOG_FILE = "sqlalchemy_dialect.log"
SQLALCHEMY_ORM_LOG_FILE = "sqlalchemy_orm.log"
MAX_LOG_SIZE_MB = (
    DefaultSetting(
        settings=settings, group_name="Logging", name="max_log_size_mb", value=5
    )
    .initialize_setting()
    .value
)
MAX_LOG_COUNT = (
    DefaultSetting(
        settings=settings, group_name="Logging", name="max_log_count", value=3
    )
    .initialize_setting()
    .value
)
LOG_LEVEL = (
    DefaultSetting(
        settings=settings, group_name="Logging", name="log_level", value=logging.INFO
    )
    .initialize_setting()
    .value
)
if DEBUG:
    LOG_LEVEL = logging.DEBUG

# Database settings
DATABASE_USER = (
    DefaultSetting(settings=settings, group_name="Database", name="user", value="root")
    .initialize_setting()
    .value
)
DATABASE_PASSWORD = (
    DefaultSetting(
        settings=settings, group_name="Database", name="password", value="Redpurple23"
    )
    .initialize_setting()
    .value
)
DATABASE_HOST = (
    DefaultSetting(
        settings=settings, group_name="Database", name="host", value="localhost"
    )
    .initialize_setting()
    .value
)
DATABASE_PORT = (
    DefaultSetting(settings=settings, group_name="Database", name="port", value="3306")
    .initialize_setting()
    .value
)
SCHEMA_NAME = (
    DefaultSetting(
        settings=settings,
        group_name="Database",
        name="schema",
        value="cutlistgenerator_new",
    )
    .initialize_setting()
    .value
)
SCHEMA_CREATE = f"CREATE SCHEMA IF NOT EXISTS {SCHEMA_NAME}"
DATABASE_URL_WITHOUT_SCHEMA = f"mysql+pymysql://{DATABASE_USER}:{DATABASE_PASSWORD}@{DATABASE_HOST}:{DATABASE_PORT}"
DATABASE_URL_WITH_SCHEMA = f"{DATABASE_URL_WITHOUT_SCHEMA}/{SCHEMA_NAME}"
FORCE_REBUILD_DATABASE = (
    DefaultSetting(
        settings=settings,
        group_name="Database",
        name="force_rebuild_database",
        value=False,
    )
    .initialize_setting()
    .value
)
if FORCE_REBUILD_DATABASE == "true":
    FORCE_REBUILD_DATABASE = True
else:
    FORCE_REBUILD_DATABASE = False

# Fishbowl settings
FISHBOWL_DATABASE_USER = (
    DefaultSetting(settings=settings, group_name="Fishbowl", name="user", value="gone")
    .initialize_setting()
    .value
)
FISHBOWL_DATABASE_PASSWORD = (
    DefaultSetting(
        settings=settings, group_name="Fishbowl", name="password", value="fishing"
    )
    .initialize_setting()
    .value
)
FISHBOWL_DATABASE_HOST = (
    DefaultSetting(
        settings=settings, group_name="Fishbowl", name="host", value="localhost"
    )
    .initialize_setting()
    .value
)
FISHBOWL_DATABASE_PORT = (
    DefaultSetting(settings=settings, group_name="Fishbowl", name="port", value="3305")
    .initialize_setting()
    .value
)
FISHBOWL_DATABASE_SCHEMA = (
    DefaultSetting(settings=settings, group_name="Fishbowl", name="schema", value="qes")
    .initialize_setting()
    .value
)
FISHBOWL_DATABASE_URL = f"mysql+pymysql://{FISHBOWL_DATABASE_USER}:{FISHBOWL_DATABASE_PASSWORD}@{FISHBOWL_DATABASE_HOST}:{FISHBOWL_DATABASE_PORT}/{FISHBOWL_DATABASE_SCHEMA}"

# Github settings
GITHUB_USERNAME = "dominickfau"
GITHUB_REPO_NAME = "CutListGenerator"

GITHUB_LATEST_RELEASE_ENDPOINT = (
    f"https://api.github.com/repos/{GITHUB_USERNAME}/{GITHUB_REPO_NAME}/releases/latest"
)

# Thread settings
THREAD_UPDATE_INTERVAL = 5  # seconds

# API settings
API_TOKEN_VALIDITY = 1  # days

FLASK_SECRET_KEY = (
    DefaultSetting(
        settings=settings,
        group_name="Flask",
        name="secret_key",
        value=secrets.token_urlsafe(64),
    )
    .initialize_setting()
    .value
)

if not os.path.exists(COMPANY_FOLDER):
    os.mkdir(COMPANY_FOLDER)

if not os.path.exists(PROGRAM_FOLDER):
    os.makedirs(PROGRAM_FOLDER)

if not os.path.exists(LOG_FOLDER):
    os.makedirs(LOG_FOLDER)

if not os.path.exists(LOG_SQLALCHEMY_FOLDER):
    os.makedirs(LOG_SQLALCHEMY_FOLDER)