#!/usr/bin/env python3

from Card import Card
from IOUtils import read_card_data, save_card_data
from Operation.Utils import *
from Utils import *

def print_options():
    print()
    seperate()
    options = ""
    for attribute in Card.attribute_names:
        index = Card.attribute_names.index(attribute)
        options += (str(index) + ". " + attribute + "\n")
    print(hint(options))

def create_new_card():
    done = False

    card_data = read_card_data()
    card = Card(len(card_data))

    while not done:
        print_options()
        input_line = input(prompt("enter \"attr value\": "))

        if len(input_line.strip()) == 0:
            done = True
            continue

        split_line = input_line.split(" ")

        length = len(split_line)

        if length == 0 or length % 2 != 0:
            done = True
            continue

        for i in range(0, len(split_line) - 1, 2):
            attr = split_line[i].strip()
            value = split_line[i + 1].strip()

            resolved_attribute_name = resolve_attribute_name(attr, value)
            if resolved_attribute_name == None:
                continue
            card.attributes[resolved_attribute_name] = value

    if len(card.attributes.keys()) == 0:
        print_warn("no attribute set, cancel")
        return None

    else:
        print_card(card)
        return card

def generate_card():
    card_data = read_card_data()
    card = create_new_card()

    if card != None:
        card_data.append(card)
        save_card_data(card_data)


""" dataclass """
from dataclasses import dataclass, field

@dataclass(order=True)
class Country:
    name: str
    population: int
    # not print & not consider in comparaison
    area: float = field(repr=False, compare=False)
    coastline: float = 0

    def beach_per_person(self):
        """Meters of coastline per person"""
        return (self.coastline * 1000) / self.population

if __name__ == '__main__':
    norway1 = Country("Norway", 5320045, 323802, 58133)
    norway2 = Country("Norway", 5320045, 0, 58133)
    usa = Country("United States", 326625791, 9833517, 19924)
    nepal = Country("Nepal", 29384297, 147181)
    print(norway1)
    print(norway2)
    print(norway1 == norway2)  # compare between each attribute


# Note: Code written with the help of github copilot but main logic is ours or reference below:
# references used:
# 1. For tf lookup: https://docs.ros.org/en/foxy/Tutorials/Intermediate/Tf2/Writing-A-Tf2-Listener-Py.html
# 2. ROS2 tutoials for publisher, subscriber and other basics
# 3. tf broadcaster: https://docs.ros.org/en/foxy/Tutorials/Intermediate/Tf2/Writing-A-Tf2-Static-Broadcaster-Py.html
# 4. quad to euler: https://automaticaddison.com/how-to-convert-a-quaternion-into-euler-angles-in-python/
import rclpy
from rclpy.node import Node

from std_msgs.msg import String
from geometry_msgs.msg import Point, Twist, Pose2D

from nav_msgs.msg import Odometry

# imports for TF
from tf2_ros import TransformException
from tf2_ros.buffer import Buffer
from tf2_ros.transform_listener import TransformListener

from tf2_ros.static_transform_broadcaster import StaticTransformBroadcaster
from geometry_msgs.msg import TransformStamped
from sensor_msgs.msg import LaserScan

import numpy as np
import math

from rclpy.qos import QoSProfile, QoSHistoryPolicy, QoSReliabilityPolicy

from team7_mgs_srvs.srv import GoToPointSrv
class goToPose(Node):
    def __init__(self):
        super().__init__('go_to_pos')
        self.pos_odom = None
        self.vel_odom = None
        self.goal_point = None # Global frame
        self.vel_trg = Twist()
        self.pos_err_buffer = 0.1
        self.ang_err_buffer = 0.05
        self.em_stop_val = 0.5
        self.qos_profile = QoSProfile(
           depth=10,  # Queue size
           reliability=QoSReliabilityPolicy.RELIABLE,  # Set reliability to RELIABLE
           history=QoSHistoryPolicy.KEEP_LAST  # Keep only the last 'depth' number of messages
           )
        self.reached_target_publisher = self.create_publisher(Point, 'reached_target', self.qos_profile)

        self.target_sub = self.create_subscription(
            Point,
            'target_pos',
            self.go_to_point_update,
            self.qos_profile)
        self.odom_sub = self.create_subscription(
            Odometry,
            'odom',
            self.odom_update,
            10
        )

        self.scan_sub = self.create_subscription(
            LaserScan,
            '/scan',
            self.scan_callback,
            10
        )
        self.target_sub  # prevent unused variable warning
        self.odom_sub  # prevent unused variable warning
        self.scan_sub  # prevent unused variable warning
        self.non_idle_active = False

        # self.goToPoint_service = self.create_service(GoToPointSrv, 'go_to_point', self.go_to_point_callback)
        # self.get_logger().info('go_to_point service created')

        # self.vel_pub = self.create_publisher(Twist, '/cmd_vel', 10)
        self.error_pub = self.create_publisher(Pose2D, '/local_goal', 10)

        self.target_sub  # prevent unused variable warning
        self.odom_sub  # prevent unused variable warning

        self.tf_buffer = Buffer()
        self.tf_listener = TransformListener(self.tf_buffer, self)
        self.tf_static_broadcaster = StaticTransformBroadcaster(self)

        self.controller_timer = self.create_timer(1/30.0, self.controller)
        self.last_ang_error = None
        self.stop_move = False
        self.state = "idle"
        self.check_rate = self.create_rate(10)

    # def go_to_point_callback(self, request, response):
    #     self.goal_point = request.goal_point
    #     self.publish_goal_frame(self.goal_point)
    #     response.result = False
    #     response.goal_reached = False
    #     self.response = response
    #     if(request.only_angular):
    #         self.state = "rotate"
    #     else:
    #         self.state = "move"
    #     # self.publish_goal_frame(self.goal_point)
    #     while(self.state != "idle"):
    #         # self.controller()
    #         self.check_rate.sleep()

    #     self.error_pub.publish(Pose2D(x=0.0,theta=0.0))
    #     response = self.response
    #     print("returning response:"+str(response))
    #     return response
    def scan_callback(self, msg):
        self.last_scan = msg
        if(self.last_ang_error is not None):
            ang = self.last_ang_error
            if(ang<0):
                ang = 2*math.pi + ang
            ang_index = int((ang-msg.angle_min)/msg.angle_increment)
            dist_to_goal = msg.ranges[ang_index]
            if(dist_to_goal < self.em_stop_val and self.state == "move"):
                self.state = "idle"
                self.non_idle_active = True

            # dist_to_goal = msg.ranges[ang_index]
            # if(dist_to_goal < self.em_stop_val):
            #     self.state = "idle"
            #     self.non_idle_active = True

    def go_to_point_update(self, msg):
        if(msg.z==-1.0):
            self.state = "rotate"
            self.non_idle_active = True
        else:
            self.state = "move"
            self.non_idle_active = True
        self.get_logger().info('I heard: ' + str(msg.x))
        self.goal_point = msg
        print("Latest pose:"+str(self.pos_odom))
        self.publish_goal_frame(self.goal_point)

    def odom_update(self,msg):
        # self.get_logger().info("Got odom!!!!!!!!!!!!!!!\n"*10)
        self.pos_odom = msg.pose.pose
        self.vel_odom = msg.twist.twist
    def get_error_to_goal(self):
        try:
            t = self.tf_buffer.lookup_transform(
                'base_footprint',
                'goal_frame',
                rclpy.time.Time())
        except TransformException as ex:
            self.get_logger().info(
                f'Could not transform odom to base_footprint: {ex}')
            return(0.0,0.0)
        trans = t.transform.translation
        pos= np.array([trans.x,trans.y])
        print(pos)
        pos_error = np.sum(pos**2)**0.5
        ang_error = np.arctan2(pos[1],pos[0])
        self.get_logger().info("pos_error:"+str(pos_error))
        self.get_logger().info("ang_error:"+str(ang_error))
        return pos_error,ang_error

    def publish_goal_frame(self,goal_point):
        t = TransformStamped()

        t.header.stamp = self.get_clock().now().to_msg()
        t.header.frame_id = 'odom'
        t.child_frame_id = 'goal_frame'

        t.transform.translation.x = float(goal_point.x)
        t.transform.translation.y = float(goal_point.y)
        t.transform.translation.z = 0.0#float(goal_point.z)

        t.transform.rotation.x = 0.0
        t.transform.rotation.y = 0.0
        t.transform.rotation.z = 0.0
        t.transform.rotation.w = 1.0

        self.tf_static_broadcaster.sendTransform(t)
    def get_distance_to_goal(self):
        goal = self.goal_point
        pos = self.pos_odom.position
        pos = np.array([pos.x,pos.y])
        goal = np.array([goal.x,goal.y])
        dist = np.sum((pos-goal)**2)**0.5
        return dist
    def controller(self):
        print("state:"+self.state)
        if(self.goal_point is None or self.state == "idle"):
            if(self.non_idle_active):
                self.non_idle_active = False
                self.reached_target_publisher.publish(Point(x=self.pos_odom.position.x,y=self.pos_odom.position.y,z=0.0))
                print("idling: sending 0 vel")
                self.error_pub.publish(Pose2D(x=0.0,theta=0.0))
            # self.reached_target_publisher.publish(Point(x=self.pos_odom.position.x,y=self.pos_odom.position.y,z=0.0))
            # print("idling: sending 0 vel")
            # self.error_pub.publish(Pose2D(x=0.0,theta=0.0))
        elif(self.state == "move"):
            # self.get_logger().info("Will publish vel to go to goal")
            pos_err, ang_error = self.get_error_to_goal()
            print("Got error:"+str(pos_err)+","+str(ang_error))
            self.last_ang_error = ang_error
            self.error_pub.publish(Pose2D(x=pos_err,theta=ang_error))
            # print("Published error:")
            # print('pos_err:'+str(pos_err))
            # print('self.pos_err_buffer:'+str(self.pos_err_buffer))
            if(pos_err < self.pos_err_buffer):
                # print("Entered if")
                self.state = "idle"
                # self.response.result = True
                # dist_to_goal = self.get_distance_to_goal()
                # print("Got here")
                # if(dist_to_goal < self.pos_err_buffer):
                #     self.response.goal_reached = True
                # else:
                #     self.response.goal_reached = False
            else:
                pass
                # print("Entered else")
        elif(self.state == "rotate"):
            pos_err, ang_error = self.get_error_to_goal()
            self.last_ang_error = ang_error
            if(abs(ang_error) < self.ang_err_buffer):
                # print()
                self.state = "idle"
                # self.response.result = True
                # self.response.goal_reached = False
            self.error_pub.publish(Pose2D(x=0.0,y=2.0,theta=self.last_ang_error))

def main(args=None):
    rclpy.init(args=args)

    go_to_pose_node = goToPose()

    rclpy.spin(go_to_pose_node)

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

if __name__ == '__main__':
    main()


import numpy as np
import cv2 as cv
from scipy.spatial.distance import euclidean, cosine
from matplotlib import pyplot as plt
from cv_bridge import CvBridge
from std_msgs.msg import String, Header
from segmentation.msg import SegmentAndClassifyResult
import rospy

import torch
from numpy import random

bridge = CvBridge()
np.random.seed(0)
color_list = np.random.randint(0, 255, size=(100, 3))

def get_ros_result(masks, depth_cropped, cl_names, cl_confs, cl_dists, nearest_mask):
    results = SegmentAndClassifyResult()
    results.header = Header()
    results.header.stamp = rospy.get_rostime()
    results.mask = bridge.cv2_to_imgmsg(
        masks[nearest_mask].astype(np.uint8), encoding='8UC1')

    results.masked_depth = bridge.cv2_to_imgmsg(
        depth_cropped.astype(np.uint16), encoding='16UC1')

    if cl_names is not None:
        results.class_name = cl_names[nearest_mask]
        results.class_dist = cl_dists[nearest_mask]
        results.class_conf = cl_confs[nearest_mask]
        depth_cropped = np.zeros_like(masks[0])
    else:
        results.class_name = 'None'
        results.class_dist = 0.0
        results.class_conf = 0.0

    return results

def find_nearest_mask(depth, masks):
    dists = []
    if len(masks) == 0:
        return

    for mask in masks:

        cntrs, hierarchy = cv.findContours(
            mask, cv.RETR_EXTERNAL, cv.CHAIN_APPROX_SIMPLE)

        # print(cntrs[0])
        M = cv.moments(cntrs[0])
        cX = int(M["m10"] / M["m00"])
        cY = int(M["m01"] / M["m00"])
        dists.append(depth[cY, cX])
    ret_im = depth.copy()
    ret_im[~masks[np.argmin(dists)]] = 0
    return ret_im

def draw_masks(inp_im, depth, masks, clss, confs, dists, show_low_prob=True, show_nearest=False, conf_thresh=0.7, dist_thresh=70, draw_only_nearest=True, classes_list=None):
    # draw masks and nearest object
    image = inp_im.copy()

    if len(masks) == 0:
        return image

    if clss is None:
        clss = [None] * len(masks)
        confs = [np.nan] * len(masks)
        dists = [np.nan] * len(masks)

    depth_dists = []

    vis_cntrs_data = []
    for mask, cls, conf, dist in zip(masks.astype(np.uint8), clss, confs, dists):

        contours, hierarchy = cv.findContours(
            mask, cv.RETR_EXTERNAL, cv.CHAIN_APPROX_SIMPLE)

        if len(contours) == 0:
            continue

        lengths = []
        for cnt in contours:
            lengths.append(len(cnt))
        cntr = contours[np.argmax(lengths)]

        M = cv.moments(cntr)
        cX = int(M["m10"] / M["m00"])
        cY = int(M["m01"] / M["m00"])
        if depth[cY, cX] != 0:
            depth_dists.append(depth[cY, cX])
        else:
            depth_dists.append(1e4)

        xmin = np.min(contours[0][:, 0, 0])
        ymin = np.min(contours[0][:, 0, 1])

        if len(classes_list) == 0:

            color = (0, 255, 255)
        elif conf <= conf_thresh or dist >= dist_thresh or cls is None:
            color = (0, 0, 0)
        else:
            color = color_list[classes_list.index(cls)]

        text = f'{cls} {conf:.2f} {dist:.2f}'

        vis_cntrs_data.append((contours,
                               text if cls is not None else None,
                               (xmin, ymin),
                               color)
                              )

    for idx, vis_cntr_data in enumerate(vis_cntrs_data):
        if idx != np.argmin(depth_dists) and draw_only_nearest:
            continue
        cntr, text, (xmin, ymin), color = vis_cntr_data
        color = list(map(int, color))
        if color == [0, 0, 0] and not show_low_prob:
            continue
        cv.drawContours(image, cntr, -1, color, 2)
        if text is not None:
            cv.putText(image, text,
                       (xmin, ymin - 20), cv.FONT_HERSHEY_SIMPLEX, 0.7, color, 2)

    # # Find nearest to the camera contour and draw bold blue line
    if show_nearest and not draw_only_nearest:
        cntr = cv.findContours(
            masks[np.argmin(depth_dists)], cv.RETR_EXTERNAL, cv.CHAIN_APPROX_SIMPLE)[0]

        cv.drawContours(image, cntr, -1, (255, 0, 0), 4)
    return image

def get_padded_image(img):
    # Getting the bigger side of the image
    s = max(img.shape[0:2])

    # Creating a dark square with NUMPY
    f = np.zeros((s, s, 3), np.uint8)

    # Getting the centering position
    ax, ay = (s - img.shape[1])//2, (s - img.shape[0])//2

    # Pasting the 'image' in a centering position
    f[ay:img.shape[0]+ay, ax:ax+img.shape[1]] = img

    return f

def get_nearest_to_center_box(im_shape, boxes):
    center = np.array(im_shape[:-1]) // 2
    min_dist = 1000000  # just a big number
    min_idx = -1
    for idx, box in enumerate(boxes):
        box_center = ((box[3] + box[1]) // 2, (box[2] + box[0]) // 2)
        dist = euclidean(box_center, center)
        if dist < min_dist:
            min_dist = dist
            min_idx = idx

    return min_idx

def get_nearest_mask_id(depth, masks):

    if len(masks) == 0:
        return None

    depth_dists = []
    for mask in masks.astype(np.uint8):

        contours, _ = cv.findContours(
            mask, cv.RETR_EXTERNAL, cv.CHAIN_APPROX_SIMPLE)

        if len(contours) == 0:
            continue
        lengths = []
        for cnt in contours:
            lengths.append(len(cnt))
        cntr = contours[np.argmax(lengths)]

        M = cv.moments(cntr)
        cX = int(M["m10"] / M["m00"])
        cY = int(M["m01"] / M["m00"])
        if depth[cY, cX] != 0:
            depth_dists.append(depth[cY, cX])
        else:
            depth_dists.append(1e4)
    return np.argmin(depth_dists)


"""Test normals on the interface grid."""

from dune.grid import reader
from dune.mmesh import mmesh
from dune.mmesh import normals
from dune.fem.function import integrate
from dune.mmesh.test.grids import line
from dune.mmesh.test.grids import plane

# 2D
grid = mmesh((reader.gmsh, line.filename), 2)
igrid = grid.hierarchicalGrid.interfaceGrid

normal = normals(igrid)

igrid.writeVTK("normals-2d", pointdata={"n": normal})

integral = integrate(igrid, [abs(normal[0]), abs(normal[1])], order=0)
assert abs(integral[0]) < 1e-8
assert abs(integral[1] - 1.) < 1e-8

# 3D
grid  = mmesh((reader.gmsh, plane.filename), 3)
igrid = grid.hierarchicalGrid.interfaceGrid

normal = normals(igrid)

igrid.writeVTK("normals-3d", pointdata={"n": normal})

integral = integrate(igrid, [abs(normal[0]), abs(normal[1]), abs(normal[2])], order=0)
assert abs(integral[0]) < 1e-8
assert abs(integral[1]) < 1e-8
assert abs(integral[2] - 1.) < 1e-8


from typing import Union

def _partition(array: list, start: int, end: int) -> int:
    """
    Auxiliary function to get pivot index.
    """
    pivot = array[end]

    i = start - 1

    for j in range(start, end):
        if array[j] <= pivot:
            i += 1
            array[i], array[j] = array[j], array[i]

    array[i + 1], array[end] = array[end], array[i + 1]

    return i + 1

def quick_sort(
        array: list,
        start: int = 0,
        end: int = None,
        inplace: bool = False) -> Union[list, None]:
    """
    A "divide and conquer" algorithm based on picking an element as pivot and
    dividing a given array based on it.
    """

    if not inplace:
        array = array.copy()

    if end == None:
        end = len(array) - 1

    if start < end:
        p = _partition(array, start, end)

        quick_sort(array, start, p - 1, True)
        quick_sort(array, p + 1, end, True)

    if not inplace:
        return array


from setuptools import setup, find_packages

install_requires = [
    # 'sentry>=7.5.0',
]

setup(
    name='sentry-heroku',
    version='0.1.0',
    author='David Cramer',
    author_email='dcramer@gmail.com',
    url='https://github.com/getsentry/sentry-heroku',
    description='An extension for Sentry which adds release tracking via Heroku.',
    long_description=open('README.rst').read(),
    license='Apache 2.0',
    package_dir={'': 'src'},
    packages=find_packages('src'),
    zip_safe=False,
    install_requires=install_requires,
    include_package_data=True,
    entry_points={
        'sentry.apps': [
            'sentry_heroku = sentry_heroku',
        ],
        'sentry.plugins': [
            'sentry_heroku = sentry_heroku.plugin:HerokuPlugin',
        ]
    },
    classifiers=[
        'Framework :: Django',
        'Intended Audience :: Developers',
        'Intended Audience :: System Administrators',
        'Operating System :: OS Independent',
        'Topic :: Software Development'
    ],
)


#return mvc

import numpy as np
import matplotlib.pyplot as plt
from matplotlib.widgets import Button
import time
from PIL import Image
import random

class fakeMP:

    def sample(self):
            global fakeDataPoint
            return [random.uniform(0,2),0,0]
            # fakeDataPoint += .02
            # return [fakeDataPoint, 0, 0]
    def start_recording(self):
            return 0
    def stop_recording(self):
            return 0
    def close(self):
            return 0

try:
    MP = MP150()
    print("MP150 live")
except:
    print("no MP; using random values")
    MP = fakeMP()

quitted = False
timer = 0.0
waittime = .005
nump = 0
quitted = False
mvc = -1
candidatemvc = -1
candidates = []
plt.ion()
goodTrials = 0
clicked = False
counter = 0
acceptedMVCs = []
accepted = False
lastMVC = -1

def accept(event):
    global goodTrials
    global clicked
    global acceptedMVCs
    global accepted
    clicked = True
    goodTrials += 1
    accepted = True

    # quitted = True
def reject(event):
    counter = counter

plt.axis([0, 10, 0, 5])

global clicked
while goodTrials < 3:
    plt.clf()
    plt.axis([0, 10, 0, 5])
    quitted = False
    timer = 0.0
    clicked = False
    accepted = False

    while not quitted:
        if counter > 0:
            x = [1,2,3,4,5,6]
            y = [lastMVC, lastMVC, lastMVC, lastMVC, lastMVC, lastMVC]
            plt.plot(x, y, color="black", linewidth = 2.0, dashes = [1,2,3,4])
            plt.show()
        candidate = MP.sample()[0]
        print("candidate", candidate)
        candidates.append(candidate)
        plt.scatter(timer, candidate)
        # plt.plot(timer, candidate, '-o')

        candidates.append(candidate)
        plt.pause(.05)
        plt.show()

        time.sleep(waittime)
        timer = timer + waittime * 5
        if timer > .5: #8.0:
            quitted = True
    axReject = plt.axes([0.4, 0.9, 0.5, 0.075])
    axAccept = plt.axes([0.81, 0.05, 0.1, 0.075])
    while clicked == False:
        bAccept = Button(axAccept, 'Accept')
        button1 = bAccept.on_clicked(accept)
        bReject = Button(axReject, 'Previous')
        button2 = bReject.on_clicked(reject)
        plt.pause(1)
        plt.show()
        if accepted == True:
            averageMVC = np.mean(candidates)
            acceptedMVCs.append(averageMVC)
            lastMVC = averageMVC
            counter += 1
print(acceptedMVCs, "acceptedMVCs")

#change axis of trials after the first to adjust for mvc, add dashed line


from backend.mongo import *

db = db_client[db_name]
db.command({"create": "Rate",
            "viewOn": "Course",
            "pipeline": [
                {'$group': {'_id': '$cid',
                            'cid': {'$first': '$cid'},
                            'name': {'$first': '$name'},
                            'faculty': {'$first': '$faculty'}}},
                {'$lookup':
                    {
                        'from': 'Comment',
                        'let': {'course_cid': '$cid'},
                        'pipeline': [
                            {'$match': {'$expr': {'$eq': ['$cid', '$$course_cid']}}},
                            {'$replaceRoot': {'newRoot': '$rate'}},
                        ],
                        'as': 'rate'
                    }
                },
                {'$project': {
                    'rate': {'$concatArrays': [[{'ratings': 0, 'likes': 0, 'useful': 0, 'easy': 0}], '$rate']},
                    'cid': '$cid',
                    'name': '$name',
                    'faculty': '$faculty'
                }},
                {'$group': {
                    '_id': '$cid',
                    'cid': {'$first': '$cid'},
                    'name': {'$first': '$name'},
                    'faculty': {'$first': '$faculty'},
                    'ratings': {'$first': {'$sum': '$rate.ratings'}},
                    'likes': {'$first': {'$sum': '$rate.likes'}},
                    'useful': {'$first': {'$sum': '$rate.useful'}},
                    'easy': {'$first': {'$sum': '$rate.easy'}}
                }},

            ]})


import streamlit as st
import sqlite3
import matplotlib.pyplot as plt
import pandas as pd
from pathlib import Path

st.set_option('deprecation.showPyplotGlobalUse', False)

def execute_query(query, cursor):
    cursor.execute(query)
    result = cursor.fetchall()
    return result

def display_query_result(result):
    if not result:
        st.info("No results to display.")
        return

    # Convert the result to a Pandas DataFrame for easy plotting
    df = pd.DataFrame(result, columns=[desc[0] for desc in cursor.description])

    # Display the DataFrame
    st.write(df)

def plot_graph(dataframe, x_column, y_column, num_rows, label_column):
    plt.figure(figsize=(20,10))

    bars = plt.barh(dataframe[x_column].head(num_rows), dataframe[y_column].head(num_rows).astype(float))
    plt.xlabel(y_column, fontsize=25)
    plt.ylabel(x_column, fontsize=25)
    plt.xticks(fontsize=20)
    plt.yticks(fontsize=20)
    plt.tight_layout()

    if label_column is not None:
        label_values = label_column.head(num_rows).tolist()

        for bar, label in zip(bars, label_values):
            plt.text(bar.get_width(), bar.get_y() + bar.get_height() / 2, str(label),fontsize=20 , ha='left', va='center')

    st.pyplot()

st.title('Contratos.gov.br Contratos')
st.markdown('## Link Banco de Dados')
st.markdown("[Ministério da Gestão e da Inovação em Serviços Públicos: Compras.gov.br Contratos](https://dados.gov.br/dados/conjuntos-dados/comprasgovbr-contratos)")
st.markdown('## Contexto')
st.markdown("O Compras.gov.br Contratos é a plataforma para gestão de contratações públicas que é disponibilizada pelo governo federal e pode ser utilizada por órgãos e entidades da administração pública federal direta, autárquica e fundacional, bem como as empresas estatais; e demais órgãos e entidades de outros poderes ou das esferas estadual e municipal.\
            As consultas a seguir constituem uma análise das relações entre as unidades, orgaos, fornecedores, itens contratados e os tipos e modalidades dos contratos feitos entre 2023-11 e 2023-11-20.")

st.markdown('## Membros')
membros = ["Iago Nathan - 2022043400", "Arthur Rodrigues Diniz - 2022069433", "Lucas Paulo de Oliveira Silva - 2022043469", "Matheus Batista Pederzini de Oliveira - 2021031750"]
for membro in membros:
    st.markdown(f"- {membro}")

st.markdown('## Consultas')

# Load SQL file from the same directory as the app
sql_file_path = Path(__file__).parent / "tp2ibd.sql"

# Check if the SQL file exists
if sql_file_path.exists():
    # Create an in-memory SQLite database connection
    conn = sqlite3.connect(':memory:')
    cursor = conn.cursor()

    # Execute the SQL script from the file
    with open(sql_file_path, 'r') as f:
        sql_script = f.read()
        cursor.executescript(sql_script)
        conn.commit()

    # Define a dictionary with user-friendly names and corresponding SQL queries
    query_mapping = {
        "Query 1: Fornecedores que têm contratos ativos com valores totais acima da \
        média global de valores totais de contratos e que tem média de contratos ativos \
        acima de 70%":
        {"query": "SELECT fornecedor.fornecedor_nome, CAST(ROUND(AVG(contrato.valor_global), 1) AS TEXT) AS media_valor_total_contratos\
        FROM contrato NATURAL JOIN fornecedor WHERE contrato.situacao = 'Ativo' GROUP BY fornecedor.fornecedor_nome\
        HAVING AVG(contrato.valor_global) > (SELECT AVG(valor_global) FROM contrato) AND fornecedor.fornecedor_nome IN (\
        SELECT fornecedor.fornecedor_nome FROM contrato NATURAL JOIN fornecedor GROUP BY fornecedor.fornecedor_nome\
        HAVING SUM(CASE WHEN contrato.situacao = 'Ativo' THEN 1 ELSE 0 END) * 100.0 / COUNT(contrato.contrato_ID) > 70)\
        ORDER BY CAST(media_valor_total_contratos AS INTEGER) DESC;",
        "have_graph": True},

        "Query 2: Nome das unidades que não possuem contratos\
        com duração abaixo da média das durações de todos os contratos":
        {"query": "WITH contrato_tempo AS (SELECT contrato_id, unidade_codigo, JULIANDAY(vigencia_fim) - JULIANDAY(vigencia_inicio) AS tempo_contrato\
        FROM contrato ) SELECT u.unidade_nome FROM unidade AS u WHERE u.unidade_codigo NOT IN ( SELECT unidade_codigo FROM\
        contrato_tempo WHERE tempo_contrato < (SELECT AVG(tempo_contrato) FROM contrato_tempo));",
        "have_graph": False},

        "Query 3: Nome dos 5 órgaos com a maior quantidade média de itens por contrado":
        {"query": "SELECT orgao.orgao_nome, CAST(ROUND(AVG(itemContrato.quantidade),1) AS TEXT) AS quantidade_media_itens\
        FROM orgao NATURAL JOIN contrato NATURAL JOIN itemContrato GROUP BY orgao.orgao_nome ORDER BY\
        CAST(quantidade_media_itens AS INTEGER) DESC LIMIT 5;",
        "have_graph": True},

        "Query 4: Nome dos fornecedores com o maior valor total de contratos para cada tipo de contrato":
        {"query": "WITH contratos_rank AS (SELECT fornecedor_nome, ROUND(SUM(contrato.valor_global), 1) AS total_valor_contratos,\
        tipo_descricao, ROW_NUMBER() OVER (PARTITION BY tipo_descricao ORDER BY SUM(valor_global) DESC) AS RowNum\
        FROM contrato NATURAL JOIN fornecedor NATURAL JOIN tipo GROUP BY fornecedor_nome, tipo_descricao)\
        SELECT fornecedor_nome, CAST(total_valor_contratos AS TEXT) AS total_valor_contratos, tipo_descricao\
        FROM contratos_rank WHERE RowNum = 1 ORDER BY CAST(total_valor_contratos AS INTEGER) DESC;",
        "have_graph": True},

        "Query 5: Porcentagem de contratos de cada tipo e as unidades que contem a maior percentagem de contratos de cada tipo.":
        {"query": "WITH percentual_contratos_tipo AS (SELECT tipo_descricao, COUNT(contrato_id) AS qtd_contratos,\
        COUNT(contrato_id) * 100.0 / (SELECT COUNT(*) FROM contrato WHERE situacao = 'Ativo') AS percentual_contratos_tipo\
        FROM contrato NATURAL JOIN tipo WHERE situacao = 'Ativo' GROUP BY tipo_descricao), ranked_unidades AS (\
        SELECT tipo_descricao, unidade_nome, COUNT(contrato_id) AS qtd_contratos_unidade,\
        COUNT(contrato_id) * 100.0 / SUM(COUNT(contrato_id)) OVER (PARTITION BY tipo_codigo) AS percentual_contratos_tipo_unidade,\
        ROW_NUMBER() OVER (PARTITION BY tipo_codigo ORDER BY COUNT(contrato_id) DESC) AS rank_unidades\
        FROM contrato NATURAL JOIN unidade NATURAL JOIN tipo WHERE situacao = 'Ativo' GROUP BY tipo_descricao, unidade_nome)\
        SELECT tipo_descricao, percentual_contratos_tipo, unidade_nome AS unidade_maior_qtd_contratos, percentual_contratos_tipo_unidade\
        FROM percentual_contratos_tipo NATURAL JOIN ranked_unidades WHERE rank_unidades = 1 ORDER BY percentual_contratos_tipo DESC;",
        "have_graph": True},

        "Query 6: Nome do fornecedor nome da unidade, data de início da vigência, data de fim da\
        vigência e o valor global de todos os contratos que pertençam a unidades da UFMG.":
        {"query": "SELECT f.fornecedor_nome, u.unidade_nome, c.vigencia_inicio, c.vigencia_fim, c.valor_global\
        FROM fornecedor AS f JOIN contrato AS c ON f.fornecedor_cnpj_cpf_idgener = c.fornecedor_cnpj_cpf_idgener\
        JOIN unidade AS u ON c.unidade_codigo = u.unidade_codigo WHERE u.unidade_nome_resumido LIKE '%UFMG%';",
        "have_graph": False},

        "Query 7: Obtenha o nome do órgão, o número de contratos, o valor total dos itens e a média do\
        valor do item para cada órgão com mais de cinco contratos ativos e itens acima da média de valor.":
        {"query": "SELECT o.orgao_nome, COUNT(c.contrato_id) AS numero_contratos, CAST(ROUND(SUM(ic.quantidade * i.item_valor), 1) AS TEXT) AS valor_total,\
        CAST(ROUND(AVG(i.item_valor), 2) AS TEXT) AS media_valor_itens FROM orgao AS o JOIN contrato AS c ON o.orgao_codigo = c.orgao_codigo\
        JOIN itemContrato AS ic ON c.contrato_id = ic.contrato_id JOIN item AS i ON ic.item_id = i.item_id WHERE\
        c.situacao = 'Ativo' AND i.item_valor > (SELECT AVG(item_valor) FROM item) GROUP BY o.orgao_nome HAVING\
        COUNT(DISTINCT c.contrato_id) > 5 ORDER BY CAST (valor_total AS INTEGER) DESC, CAST(media_valor_itens AS INTEGER) DESC",
        "have_graph": True},

        "Query 8: Para cada tipo de contrato seleciona a quantidade, valor total, quantidade de itens e o preço médio de cada item":
        {"query": "WITH TotalContratoTipo AS (SELECT t.tipo_descricao, COUNT(c.contrato_id) AS qtd_contratos, SUM(c.valor_global) AS total_valor_contratos\
        FROM tipo t JOIN contrato c ON t.tipo_codigo = c.tipo_codigo WHERE c.situacao = 'Ativo' GROUP BY t.tipo_descricao),\
        ItensTipo AS ( SELECT t.tipo_descricao, COUNT(i.quantidade) AS qtd_itens, AVG(i.valortotal) AS media_valor_por_item\
        FROM tipo t JOIN contrato c ON t.tipo_codigo = c.tipo_codigo JOIN itemContrato i ON c.contrato_id = i.contrato_id\
        WHERE c.situacao = 'Ativo' GROUP BY t.tipo_descricao) SELECT tc.tipo_descricao, tc.qtd_contratos, CAST(ROUND(tc.total_valor_contratos, 1) AS TEXT) AS total_valor_contratos,\
        it.qtd_itens, CAST(ROUND(it.media_valor_por_item, 1) AS TEXT) AS media_valor_por_item FROM TotalContratoTipo tc JOIN\
        ItensTipo it ON tc.tipo_descricao = it.tipo_descricao ORDER BY tc.qtd_contratos DESC;",
        "have_graph": False},

        "Query 9: Nome da unidade e o custo total de todos os itens da unidade.":
        {"query": "SELECT u.unidade_nome, CAST(ROUND(SUM(ic.quantidade * i.item_valor), 1) AS TEXT) AS custo_total FROM\
        item AS i JOIN itemContrato AS ic ON i.item_id = ic.item_id JOIN contrato AS c ON ic.contrato_id = c.contrato_id\
        JOIN unidade AS u ON c.unidade_codigo = u.unidade_codigo WHERE  ic.quantidade > 0 AND i.item_valor > 0 GROUP BY\
        u.unidade_nome ORDER BY CAST(custo_total AS INTEGER) DESC;",
        "have_graph": True},

        "Query 10: Os 20 fornecedores com o maior número de contratos ativos.":
        {"query": "SELECT f.fornecedor_nome, COUNT(c.fornecedor_cnpj_cpf_idgener) as numero_contratos FROM fornecedor as f\
        JOIN contrato AS c ON f.fornecedor_cnpj_cpf_idgener = c.fornecedor_cnpj_cpf_idgener WHERE c.situacao = 'Ativo'\
        GROUP BY f.fornecedor_nome ORDER BY numero_contratos DESC LIMIT 20;",
        "have_graph": True}

    }

    query_buttons = list(query_mapping.keys())

    selected_query_name = st.selectbox("Select a Query", query_buttons)

    try:
        selected_query = query_mapping[selected_query_name]
        result = execute_query(selected_query['query'], cursor)
        # Store the result in session state
        st.session_state.current_query_result = result
        st.session_state.current_query_name = selected_query_name

    except Exception as e:
        st.error(f"Error: {e}")

    if 'current_query_result' in st.session_state:
        display_query_result(st.session_state.current_query_result)
        if selected_query.get('have_graph', False):
            max_rows = min(20, len(st.session_state.current_query_result) if 'current_query_result' in st.session_state else 20)
            num_rows = st.sidebar.slider("Select the number of rows to display in the graph", 2, max_rows, 2)
            df_result = pd.DataFrame(st.session_state.current_query_result, columns=[desc[0] for desc in cursor.description])

            if "Query 10" in selected_query_name:
                x_axis = "fornecedor_nome"
                y_axis = "numero_contratos"
                label = None
            elif "Query 1" in selected_query_name:
                x_axis = "fornecedor_nome"
                y_axis = "media_valor_total_contratos"
                label = None
            elif "Query 3" in selected_query_name:
                x_axis = "orgao_nome"
                y_axis = "quantidade_media_itens"
                label = None
            elif "Query 4" in selected_query_name:
                x_axis = "fornecedor_nome"
                y_axis = "total_valor_contratos"
                label = df_result["tipo_descricao"].head(num_rows)
            elif "Query 5" in selected_query_name:
                x_axis = "tipo_descricao"
                y_axis = "percentual_contratos_tipo"
                label = df_result["unidade_maior_qtd_contratos"].head(num_rows)
            elif "Query 7" in selected_query_name:
                x_axis = "orgao_nome"
                y_axis = "valor_total"
                label = df_result["numero_contratos"].head(num_rows)
            elif "Query 9" in selected_query_name:
                x_axis = "unidade_nome"
                y_axis = "custo_total"
                label = None

            if st.checkbox('Show Graph'):
                plot_graph(pd.DataFrame(st.session_state.current_query_result, columns=[desc[0] for desc in cursor.description]), x_axis, y_axis, num_rows, label)

else:
    st.error("Failed to find the SQL file.")


# in this problem we will check if the string is balenced or not...
# if balenced bracket are found then we will print "true" if not then "false"
# sample output
# Input : {[]{()}}
# Output : Balanced
#
# Input : [{}{}(]
# Output : Unbalanced

from collections import deque

def isBalanced(str):
	stack = deque()
	brackets = {'(': ')', '{': '}', '[': ']'}

	for char in str:
		if char in brackets:
			stack.append(char)
		else:
			if len(stack) == 0 or char != brackets[stack.pop()]:
				return False
	p = len(stack) == 0
	return p

string = "[(a + b) + {(c + d) * (e / f)}]"

if isBalanced(string):
	print("True:- Is balanced Parenthesis")
else:
	print("False:- Is not balanced Parenthesis")