import numpy as np
import cv2
import pix_main_arena
import time
import gym
import pybullet as p
import pybullet_data
import cv2
import numpy as np
import cv2.aruco as aruco
import math
import os

import sys

curr=143

# function for getting a matrix which will be used for adjacency

ARUCO_PARAMETERS = aruco.DetectorParameters_create()
ARUCO_DICT = aruco.Dictionary_get(aruco.DICT_ARUCO_ORIGINAL)

# Create grid board object we're using in our stream
board = aruco.GridBoard_create(
        markersX=2,
        markersY=2,
        markerLength=0.09,
        markerSeparation=0.01,
        dictionary=ARUCO_DICT)

def angle(vector_1, vector_2):
    return (np.angle(complex(vector_2[0], vector_2[1]) / complex(vector_1[0], vector_1[1])) * 180) / math.pi
def Euclidean_Distance(coordinate_1, coordinate_2):
    return math.sqrt((coordinate_1[0] - coordinate_2[0]) ** 2 + (coordinate_1[1] - coordinate_2[1]) ** 2)

def image_coordinates(a,n):
    y,x =a // n , a % n;
    print("-->",x,y,"<--")
    return np.array([(x*53)+67, (y*53)+64], dtype = np.int)
# north

def arucovector(img):
    gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)

    # Detect Aruco markers
    corners, ids, rejectedImgPoints = aruco.detectMarkers(gray, ARUCO_DICT, parameters=ARUCO_PARAMETERS)
    print("kyaa")
    print(corners)

    x = 0
    y = 0
    for arr in corners[0][0]:
        if(arr[0]>460):
            arr[0]-=(5*arr[0])/100
        elif(arr[0]<360):
            arr[0] += (5 * arr[0]) / 100
        if (arr[1] > 460):
            arr[1] -= (5 * arr[1]) / 100
        elif(arr[1]< 360):
            arr[1] += (5 * arr[1]) / 100
        x = x + arr[0]
        y = y + arr[1]

    arucoinitial = [x / 4, y / 4]
    print("ayaya")
    print(arucoinitial)
    arr = corners[0][0]
    vec1 = [arr[0][1] - arr[3][1], arr[0][0] - arr[3][0]]
    return arucoinitial, vec1

# def Grid_Coordinate(coordinate):
#     return np.array([(coordinate[1] - thickness[1]) / (size[1] / n_rows),
#                      (coordinate[0] - thickness[0]) / (size[0] / n_cols)], dtype=np.int)

def Move_Bot(factor, move):
    if move == "F" or move == "B":
        speed = int(min(20, max(factor - 50, 10), max(factor - 30, 5)))
        # speed=2
        if move == "F":
            env.move_husky(speed, speed, speed, speed)
        elif move == "B":
            env.move_husky(-speed, -speed, -speed, -speed)

        for _ in range(int(min(10, factor - 10))):
            p.stepSimulation()

    elif move == "L" or move == "R":
        speed = int(min(20, factor - 5))

        if move == "L":
            env.move_husky(-speed, speed, -speed, speed)
        elif move == "R":
            env.move_husky(speed, -speed, speed, -speed)

        for _ in range(int(min(10, factor))):
            p.stepSimulation()

    env.move_husky(0, 0, 0, 0)
    p.stepSimulation()

def letsgohusky(list,n):
    points=len(list)
    for i in range(points):
        destination = image_coordinates(list[i],n)
        while True:
            img = env.camera_feed()
            position, bot_vector= arucovector(img)
            # print("shape hai bhai -->",img.shape)
            distance = Euclidean_Distance(position, destination)
            # print("---->")
            # print([position, destination])
            if distance > 12:
                my_vector=np.array([ destination[1] - position[1],destination[0] - position[0]], dtype=np.int)
                theta = angle(bot_vector, my_vector)
                print("dest",destination)
                print("pos",position)
                print("my,bot",my_vector,bot_vector)
                print("theta",theta)
                # print("---->")
                # print(bot_vector,my_vector)
                # print("---->")
                # print(theta,list[i])
                theta=0-theta

                if theta <= 10 and theta >= -10:
                    Move_Bot(distance, "F")
                elif theta < -5 and theta > -125:
                    Move_Bot(-theta, "L")
                elif theta > 5 and theta < 125:
                    Move_Bot(theta, "R")
                elif theta >= 170 or theta <= -170:
                    Move_Bot(distance, "B")
                elif theta >= 125 and theta < 175:
                    Move_Bot(180 - theta, "L")
                elif theta <= -125 and theta > -175:
                    Move_Bot(180 + theta, "R")
            else:
                break

def forsquares(res, imgtest, lower, upper, d):
    imghsv = cv2.cvtColor(imgtest, cv2.COLOR_BGR2HSV)
    # imghsv= imgtest
    mask = cv2.inRange(imghsv, lower, upper)
    lower2 = np.array([170, 70, 50])
    upper2 = np.array([180, 255, 255])
    masked1 = cv2.inRange(imghsv, lower, upper)
    masked2 = cv2.inRange(imghsv, lower2, upper2)
    mask_red = masked1 + masked2
    if (d == 4):
        mask = mask_red
    imgresult = cv2.bitwise_and(imgtest, imgtest, mask=mask)
    # if d==1:
    #     cv2.imshow('white',imgresult)
    # if d==2:
    #     cv2.imshow('yellow',imgresult)
    if d==3:
        cv2.imshow('green',imgresult)
    # if d==4:
    #     cv2.imshow('red',imgresult)
    # if d==5:
    #     cv2.imshow('pink',imgresult)
    imggray = cv2.cvtColor(imgresult, cv2.COLOR_BGR2GRAY)
    imgblur = cv2.GaussianBlur(imggray, (7, 7), 1)
    imgcanny = cv2.Canny(imgblur, 50, 50)
    count=0
    contours, hierarchy = cv2.findContours(mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)

    for cnt in contours:
        area = cv2.contourArea(cnt)
        # if d==5:
        #     cv2.imshow('pinkk',imgresult)
        # if d==4:
        #     print(area,count)
        #     count=count+1
        if (area > 100):
            # cv2.drawContours(imgcontours, cnt, -1, (255, 0, 0), 3)
            peri = cv2.arcLength(cnt, True)
            approx = cv2.approxPolyDP(cnt, 0.02 * peri, True)
            objapp = len(approx)
            M = cv2.moments(cnt)
            cx = int(M['m10'] / M['m00'])
            cy = int(M['m01'] / M['m00'])
            y = int((cx - 10) / h1)
            x = int((cy - 10) / w1)
            # if objapp == 4:
            res[x][y][2] = d
            if(d==3):
                print(area,x,y,count)
                count=count+1
            res[x][y][0] = cx
            res[x][y][1] = cy
            if (d == 5):
                res[x][y][3] = -1
            # print([x,y])
    return (res)

def onewaybluepath(res, imgtest, lower, upper):
    imghsv = cv2.cvtColor(imgtest, cv2.COLOR_BGR2HSV)
    mask = cv2.inRange(imghsv, lower, upper)
    imgresult = cv2.bitwise_and(imgtest, imgtest, mask=mask)
    cv2.imshow('blue', imgresult)
    imggray = cv2.cvtColor(imgresult, cv2.COLOR_BGR2GRAY)
    imgblur = cv2.GaussianBlur(imggray, (7, 7), 1)
    imgcanny = cv2.Canny(imgblur, 50, 50)
    contours, hierarchy = cv2.findContours(mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
    for cnt in contours:
        area = cv2.contourArea(cnt)
        # print("me blue ka area hoon")
        # print(area)
        if (area > 100):
            d = 0
            # cv2.drawContours(imgcontours, cnt, -1, (255, 0, 0), 3)
            peri = cv2.arcLength(cnt, True)
            approx = cv2.approxPolyDP(cnt, 0.04 * peri, True)
            objapp = len(approx)
            M = cv2.moments(cnt)
            cx = int(M['m10'] / M['m00'])
            cy = int(M['m01'] / M['m00'])
            y = int((cx - 10) / h1)
            x = int((cy - 10) / w1)
            if objapp == 3:
                # print("asa")
                temp = 0
                count = 0
                for i in range(0, 3, 1):
                    # print(approx[i][0][0])
                    # print(cX)
                    if (approx[i][0][0] > cx - 6 and approx[i][0][0] < cx + 6):
                        temp = i
                        count = 1
                # print("temp")
                if (count == 1):
                    # print(approx[temp][0][1])
                    if (approx[temp][0][1] > cy):
                        d = 504  # south
                    elif (approx[temp][0][1] < cy):
                        d = 503  # north
                else:
                    for i in range(0, 3, 1):
                        # print(approx[i][0][1])
                        # print(cX)
                        if (approx[i][0][1] > cy - 6 and approx[i][0][1] < cy + 6):
                            temp = i
                            count = 1
                    # print("temp")
                    if (count == 1):
                        # print(approx[temp][0][0])
                        if (approx[temp][0][0] > cy):
                            d = 501  # east
                        elif (approx[temp][0][0] < cx):
                            d = 502  # west

                res[x][y][3] = d
                # if(d==0):
                # print("notposs")
            elif objapp == 4:
                res[x][y][3]=-1
                res[x][y][2] = 600  # blue square
            else:
                res[x][y][3] = -1
                res[x][y][2] = 700  # blue circle
            res[x][y][0] = cx
            res[x][y][1] = cy
            # print([x, y])
    return (res)

def initialmatrix(res, pic):
    imgtest = pic

    # now for red green white and yellow squares
    # 1. red
    lower1 = np.array([0, 70, 50])
    upper1 = np.array([10, 255, 255])
    res = forsquares(res, imgtest, lower1, upper1, 4)

    # 2. green
    lower = np.array([36, 40, 40], np.uint8)
    upper = np.array([70, 255, 255], np.uint8)
    res = forsquares(res, imgtest, lower, upper, 2)

    # 3. yellow
    lower = np.array([22, 93, 0], np.uint8)
    upper = np.array([45, 255, 255], np.uint8)
    res = forsquares(res, imgtest, lower, upper, 3)

    # 4. white
    lower = np.array([0, 0, 168])
    upper = np.array([172, 111, 255])
    res = forsquares(res, imgtest, lower, upper, 1)

    # 5. Pink
    lower = np.array([133, 25, 183], np.uint8)
    upper = np.array([166, 255, 255], np.uint8)
    res = forsquares(res, imgtest, lower, upper, 5)

    # 6 blue triangle for one way and diresction with them 501 502 503 504 for EWNS
    # also for hospitals as they are also blue

    lower = np.array([110, 50, 50])
    upper = np.array([130, 255, 255])
    res = onewaybluepath(res, imgtest, lower, upper)

def adjacencymaker(res, kores, n):
    k = n * n
    for i in range(0, n):
        for j in range(0, n):
            m = (i * n) + j
            dir1 = res[i][j][3]
            li = i
            lj = j - 1
            ri = i
            rj = j + 1
            ui = i - 1
            uj = j
            di = i + 1
            dj = j
            if (li >= 0 and li < n and lj >= 0 and lj < n):
                dir2 = res[li][lj][3]
                m1 = (li * n) + lj
                val = 0
                if (dir2 != 501 and (dir1 == 0 or dir1 == 502 or dir1==-1) and dir2!=-1):
                    val = res[i][j][2]
                if (val == 0):
                    val = 0
                if (res[li][lj][2] == 0):
                    val = 0
                kores[m][m1] = val
            if (ri >= 0 and ri < n and rj >= 0 and rj < n):
                dir2 = res[ri][rj][3]
                m1 = (ri * n) + rj
                val = 0
                if (dir2 != 502 and (dir1 == 0 or dir1 == 501 or dir1==-1) and dir2!=-1):
                    val = res[i][j][2]
                if (val == 0):
                    val = 0
                if (res[ri][rj][2] == 0):
                    val = 0
                kores[m][m1] = val
            if (ui >= 0 and ui < n and uj >= 0 and uj < n):
                dir2 = res[ui][uj][3]
                m1 = (ui * n) + uj
                val = 0
                if (dir2 != 504 and (dir1 == 0 or dir1 == 503 or dir1==-1) and dir2!=-1):
                    val = res[i][j][2]
                if (val == 0):
                    val = 0
                if (res[ui][uj][2] == 0):
                    val = 0
                kores[m][m1] = val
            if (di >= 0 and di < n and dj >= 0 and dj < n):
                dir2 = res[di][dj][3]
                m1 = (di * n) + dj
                val = 0
                if (dir2 != 503 and (dir1 == 0 or dir1 == 504 or dir1==-1) and dir2!=-1):
                    val = res[i][j][2]
                if (val == 0):
                    val = 0
                if (res[di][dj][2] == 0):
                    val = 0
                kores[m][m1] = val

def adj_tuple(kores, n):
    lst = []
    for i in range(n * n):
        for j in range(n * n):
            if kores[i][j] > 0:
                lst.append((i, j, kores[i][j]))

    return lst

from collections import deque, namedtuple

# we'll use infinity as a default distance to nodes.
inf = float('inf')
Edge = namedtuple('Edge', 'start, end, cost')

def make_edge(start, end, cost=1):
    return Edge(start, end, cost)

class Graph:
    def __init__(self, edges):
        # let's check that the data is right
        wrong_edges = [i for i in edges if len(i) not in [2, 3]]
        if wrong_edges:
            raise ValueError('Wrong edges data: {}'.format(wrong_edges))

        self.edges = [make_edge(*edge) for edge in edges]

    @property
    def vertices(self):
        return set(
            sum(
                ([edge.start, edge.end] for edge in self.edges), []
            )
        )

    def get_node_pairs(self, n1, n2, both_ends=True):
        if both_ends:
            node_pairs = [[n1, n2], [n2, n1]]
        else:
            node_pairs = [[n1, n2]]
        return node_pairs

    def remove_edge(self, n1, n2, both_ends=True):
        node_pairs = self.get_node_pairs(n1, n2, both_ends)
        edges = self.edges[:]
        for edge in edges:
            if [edge.start, edge.end] in node_pairs:
                self.edges.remove(edge)

    def add_edge(self, n1, n2, cost=1, both_ends=True):
        node_pairs = self.get_node_pairs(n1, n2, both_ends)
        for edge in self.edges:
            if [edge.start, edge.end] in node_pairs:
                return ValueError('Edge {} {} already exists'.format(n1, n2))

        self.edges.append(Edge(start=n1, end=n2, cost=cost))
        if both_ends:
            self.edges.append(Edge(start=n2, end=n1, cost=cost))

    @property
    def neighbours(self):
        neighbours = {vertex: set() for vertex in self.vertices}
        for edge in self.edges:
            neighbours[edge.start].add((edge.end, edge.cost))

        return neighbours

    def dijkstra(self, source, dest):
        # print(source,self.vertices)
        assert source in self.vertices, 'Such source node doesn\'t exist'
        distances = {vertex: inf for vertex in self.vertices}
        previous_vertices = {
            vertex: None for vertex in self.vertices
        }
        distances[source] = 0
        vertices = self.vertices.copy()

        while vertices:
            current_vertex = min(
                vertices, key=lambda vertex: distances[vertex])
            vertices.remove(current_vertex)
            if distances[current_vertex] == inf:
                break
            for neighbour, cost in self.neighbours[current_vertex]:
                alternative_route = distances[current_vertex] + cost
                if alternative_route < distances[neighbour]:
                    distances[neighbour] = alternative_route
                    previous_vertices[neighbour] = current_vertex

        path, current_vertex = deque(), dest
        while previous_vertices[current_vertex] is not None:
            path.appendleft(current_vertex)
            current_vertex = previous_vertices[current_vertex]
        if path:
            path.appendleft(current_vertex)
        return path

def pinklist(res):
    lst = []
    all_pink = []
    for i in range(0, n, 1):
        for j in range(0, n, 1):
            if (res[i][j][2] == 5):
                # res[i][j][2]=-5 # this pink is of no use once we store its neighbours in a list
                l = i + 1
                m = j
                if (l < n and res[l][m][2] != 0 and res[l][m][2] < 5):
                    lst.append([l, m])
                l = i - 1
                m = j
                if (l >= 0 and res[l][m][2] != 0 and res[l][m][2] < 5):
                    lst.append([l, m])
                l = i
                m = j + 1
                if (m < n and res[l][m][2] != 0 and res[l][m][2] < 5):
                    lst.append([l, m])
                l = i
                m = j - 1
                if (m >= 0 and res[l][m][2] != 0 and res[l][m][2] < 5):
                    lst.append([l, m])
                all_pink.append([i, j])
    return lst, all_pink

def hospos(val, res):
    if val == 11:
        pal = 600  # square
    else:
        pal = 700  # circle
    lst = []
    all_hos = []
    for i in range(0, n, 1):
        for j in range(0, n, 1):
            if (res[i][j][2] == pal):
                # res[i][j][2]=-5 # this pink is of no use once we store its neighbours in a list
                l = i + 1
                m = j
                if (l < n and res[l][m][2] != 0 and res[l][m][2] < 5):
                    lst.append([l, m])
                l = i - 1
                m = j
                if (l >= 0 and res[l][m][2] != 0 and res[l][m][2] < 5):
                    lst.append([l, m])
                l = i
                m = j + 1
                if (m < n and res[l][m][2] != 0 and res[l][m][2] < 5):
                    lst.append([l, m])
                l = i
                m = j - 1
                if (m >= 0 and res[l][m][2] != 0 and res[l][m][2] < 5):
                    lst.append([l, m])
                all_hos.append([i, j])
    return lst, all_hos

def search(list, platform):  # python function to search for an element in a list
    for i in range(len(list)):
        if list[i] == platform:
            return True
    return False

def finalhospi(img):
    imghsv=cv2.cvtColor(img,cv2.COLOR_BGR2HSV)
    lower = np.array([110, 50, 50])
    upper = np.array([130, 255, 255])
    mask=cv2.inRange(imghsv,lower,upper)
    countours,_= cv2.findContours(mask,cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_SIMPLE)
    for cnt in countours:
        area=cv2.contourArea(cnt)
        if(area>100):
            peri= cv2.arcLength(cnt, True)
            approx= cv2.approxPolyDP(cnt,0.04*peri, True)
            objapp= len(approx)
            if(objapp==4):
                return(11) #square
            else:
                return (12) #circle

def oneround(res, graph, kores, curr, count):  # function for one round i.e getting to the patient then takiing him to hospi

    # currpos= it is the postion of the bot on the start of the motion or the new cycle currpos is in the int form
    currpos = curr
    # currpos=35
    # step 1
    pink, all_pink = pinklist(res)  # step 1 first of all we search for the pink color square and store its neighbour in a list
    # print(pink)

    paths = []
    dist = []
    # print(item)
    for item in pink:  # lets select the best nearest vertics
        pth = graph.dijkstra(currpos, item[0] * n + item[1])
        paths.append(pth)
        t = 0
        for i in range(1, len(pth), 1):
            t += kores[pth[i - 1]][pth[i]]
        dist.append(t)
    print("topink")
    print(dist)
    min_pos = dist.index(min(dist))

    runonme = paths[min_pos]  # this is the path which the bot follows
    x1 = pink[min_pos][0]  # coordinates of the point where the bot will stop for the pink rectangle
    y1 = pink[min_pos][1]

    print(runonme)
    letsgohusky(runonme, 12)
    # step 2 find the patient position i.e adjacent PINK block
    p1 = 0  # [p1,p2] coordinates of the pink position
    p2 = 0
    if search(all_pink, [x1 + 1, y1]):
        res[x1 + 1][y1][2] = -5
        p1 = x1 + 1
        p2 = y1
    elif search(all_pink, [x1 - 1, y1]):
        res[x1 - 1][y1][2] = -5
        p1 = x1 - 1
        p2 = y1
    elif search(all_pink, [x1, y1 + 1]):
        res[x1][y1 + 1][2] = -5
        p1 = x1
        p2 = y1 + 1
    elif search(all_pink, [x1, y1 - 1]):
        res[x1][y1 - 1][2] = -5
        p1 = x1
        p2 = y1 - 1

    # step 3 reveal the patient condition and store its type in the i.e square for covid circle for non covid
    # val = 11  # blue square
    # val= 12 #blue circle
    x = 0
    while True:
        p.stepSimulation()
        if x == 5000:
            env.remove_cover_plate(p1,p2)
            break
        x += 1
    time.sleep(1)
    img2=env.camera_feed()
    img3=img2[45+(p1*53)-30:45+(p1*53)+30, 45+(p2*53)-30:45+(p2*53)+30]

    val=finalhospi(img3)
    print("hospino->",val)
    # step 3.1 move the bot from [x1][y1] to [p1][p2]
    print([p1, p2])
    # p1 = x1  # for testing
    # p2 = y1  # for testing
    letsgohusky([x1 * 12 + y1, p1 * 12 + p2], 12)
    time.sleep(1)
    # step 4 now the bot will go from the position [p1][p2] to the postion adjacent to the hospital
    hospital_list, all_hos = hospos(val, res)
    paths.clear()
    dist.clear()
    for item in hospital_list:  # lets select the best nearest position of the hospital
        pth = graph.dijkstra(p1 * n + p2, item[0] * n + item[1])
        paths.append(pth)
        t = 0
        for i in range(1, len(pth), 1):
            t += kores[pth[i - 1]][pth[i]]
        dist.append(t)
    print("tohospi")
    print(dist)
    min_pos = dist.index(min(dist))
    runonme = paths[min_pos]  # this is the path which the bot follows to go from patient to respective hospital
    x1 = hospital_list[min_pos][0]  # coordinates of the point where the bot will stop for the hospital
    y1 = hospital_list[min_pos][1]

    # step 4.1 bot will run on the path runonme
    print(runonme)
    letsgohusky(runonme,12)

    # step 5 move the bot on the hospital
    p1 = 0  # [p1,p2] coordinates of the position of hospital
    p2 = 0
    if search(all_hos, [x1 + 1, y1]):
        # res[x1 + 1][y1][2] = -800
        p1 = x1 + 1
        p2 = y1
    elif search(all_hos, [x1 - 1, y1]):
        # res[x1 - 1][y1][2] = -800
        p1 = x1 - 1
        p2 = y1
    elif search(all_hos, [x1, y1 + 1]):
        # res[x1][y1 + 1][2] = -800
        p1 = x1
        p2 = y1 + 1
    elif search(all_hos, [x1, y1 - 1]):
        # res[x1][y1 - 1][2] = -800
        p1 = x1
        p2 = y1 - 1

    # step 5.2 move the bot from [x1][y1] to [p1][p2]
    print("call")
    print([p1, p2])
    letsgohusky([x1*12+y1,p1*12+p2],12)
    curr= p1*12+p2
    # p1=curr
    if count==0:
        oneround(res, graph, kores, curr,1)

def arena_make(env):
    frame = env.camera_feed(is_flat=True)
    fromCenter = False
    showCrosshair = False
    r = cv2.selectROI('image', frame, fromCenter, showCrosshair)
    img = frame[int(r[1]):int(r[1] + r[3]), int(r[0]):int(r[0] + r[2])]
    return r, img

if __name__ == "__main__":

    parent_path = os.path.dirname(os.getcwd())
    os.chdir(parent_path)
    env = gym.make("pix_main_arena-v0")
    r, arena = arena_make(env)
    # img = cv2.cvtColor(arena, cv2.COLOR_BGR2HSV)
    # cv2.imshow('img',img)

    # print(pic.size)
    # pic=cv2.imread('12x12v.png')
    pic=arena
    # pic = cv2.resize(pic, (700, 700))
    np.set_printoptions(threshold=sys.maxsize)
    n = 12
    res = np.zeros((n, n, 4), dtype=int)
    h1 = pic.shape[0] / n
    w1 = pic.shape[1] / n
    h1 -= 2
    w1 -= 2
    # print(res)
    # res2=res
    initialmatrix(res, pic)
    print(h1)
    print(w1)

    cv2.imshow('char', pic)
    # pic2 = cv2.imread('6x6v.png')
    # cv2.imshow('char2', pic2)

    # kores=adjacency matrix

    res[7][4][3] = 501  # hardcodefornow
    res[6][0][3] = 504  # hardcodefornow
    res[5][9][3] = 503  # hardcodefornow
    res[1][6][3] = 502  # hardcodefornow
    # res[5][11][2]=-5    # hardcodefornow

    kores = np.zeros((n * n, n * n), dtype=int)
    adjacencymaker(res, kores, n)
    # print(res[1][3][0])
    # print(kores)
    # print("now")
    # print([kores[33][32],kores[12][15],kores[13][12],kores[18][19],kores[19][18],kores[19][13]])

    newlist = adj_tuple(kores, n)
    graph = Graph(newlist)
    # print(graph.dijkstra(35,0))
    # print(newlist)
    # res[7][4][3]=501
    # print(res)
    time.sleep(20)
    oneround(res, graph, kores,curr,0)
    # oneround(res, graph, kores,curr)
    # print(res[6][0][3])

    # print(res)
    if (cv2.waitKey(0) & 0xFF == 27):
        cv2.destroyAllWindows()


""" Haystack search indexes for models of the coupon app. """

import datetime

from django.conf import settings
from django.db import IntegrityError, transaction

from haystack import indexes, site
from gargoyle import gargoyle

from coupon.models import Coupon, SlotTimeFrame

# Realtime search indexing thrashes for fixture resests during tests builds.
# Override it for *all* Jenkins builds, and (locally) any EnhancedTestCase).
INDEX_CLASS = indexes.RealTimeSearchIndex
if not settings.SEARCH_REALTIME:
    INDEX_CLASS = indexes.SearchIndex
try:
    # This will cause gargoyle to create the switch in an inactive state if it
    # does not already exist:
    if not gargoyle.is_active('real-time-search-index'):
        INDEX_CLASS = indexes.SearchIndex
except IntegrityError:
    # Gargoyle can fail to do an insert under test conditions, if the test loads
    # a fixture containing a switch. If that is the case; fall back to
    # SearchIndex.
    transaction.commit_unless_managed()
    INDEX_CLASS = indexes.SearchIndex

class CouponIndex(INDEX_CLASS):
    """ An index of coupons for solr searches, via haystack. """
    text = indexes.CharField(document=True, use_template=True)
    pub_date = indexes.DateTimeField(model_attr='start_date')
    site_id = indexes.IntegerField()
    categories = indexes.MultiValueField()
    suggestions = indexes.CharField()

    class Meta:
        app_label = 'coupon'

    def index_queryset(self):
        """
        Select QuerySet for use when the entire index for model is updated.
        """
        return Coupon.objects.select_related('offer', 'offer__business',
            'custom_restrictions', 'coupon_type').filter(
            expiration_date__gte=datetime.date.today()).exclude(
            coupon_type__coupon_type_name__in=('In Progress', 'Abandoned'))

    def prepare(self, obj):
        """
        Populate suggestions from text field.
        Suggestions will not be indexed, but used for spellcheck.
        """
        prepared_data = super(CouponIndex, self).prepare(obj)
        prepared_data['suggestions'] = prepared_data['text']
        return prepared_data

    @classmethod
    def prepare_categories(cls, obj):
        """ Format categories m2m as MultiValueField. """
        return [category.id for category in obj.offer.business.categories.all()]

    @classmethod
    def prepare_site_id(cls, obj):
        """ Format site_id to ensure this coupon has an active slot. """
        active_time_frame = \
            SlotTimeFrame.current_slot_time_frames.get_query_set().filter(
                coupon=obj)
        if active_time_frame:
            site_id = active_time_frame[0].slot.site.id
        else:
            site_id = None
        return site_id

site.register(Coupon, CouponIndex)


# 1791. 找出星型图的中心节点
# 有一个无向的 星型 图，由 n 个编号从 1 到 n 的节点组成。星型图有一个 中心 节点，并且恰有 n - 1 条边将中心节点与其他每个节点连接起来。

# 给你一个二维整数数组 edges ，其中 edges[i] = [ui, vi] 表示在节点 ui 和 vi 之间存在一条边。请你找出并返回 edges 所表示星型图的中心节点。

# 来源：力扣（LeetCode）
# 链接：https://leetcode-cn.com/problems/find-center-of-star-graph
# 著作权归领扣网络所有。商业转载请联系官方授权，非商业转载请注明出处。

import collections
from typing import List

class Solution:
    def findCenter(self, edges: List[List[int]]) -> int:
        vdict = collections.defaultdict(int)
        for u1,v1 in edges[:3]:
            vdict[u1]+=1
            vdict[v1]+=1
        u1,v1 =edges[0][0],edges[0][1]
        if vdict[u1]==3:
            return u1
        return v1

class Solution:
    def findCenter(self, edges: List[List[int]]) -> int:
        if edges[0][0]==edges[1][0] or edges[0][0]==edges[1][1]:
            return edges[0][0]
        return edges[0][1]

sol = Solution()
edges = [[1,2],[2,3],[4,2]]
print(sol.findCenter(edges))


# -*- coding: utf-8 -*-
"""
Created on Fri Jan 31 23:25:58 2020

@author: Administrator
"""

from PIL import Image,ImageDraw,ImageFont

def add_font(text,i):

    font=ImageFont.truetype('simhei.ttf', 80)
    img=Image.open('a.jpg')
    draw=ImageDraw.Draw(img)
    draw.text((110,110),text,(0,0,0),font=font)
    #img.show()
    img.save('pic/{}.png'.format(i))

if __name__=='__main__':
    text=['2020Flag','发CCFSCI','平安喜乐','坚持锻炼','发量不变','更爱肥肥']
    for i in range(len(text)):
        add_font(text[i],i)


from operator import gt
import numpy as np
import torch
from numpy.lib.function_base import blackman
from skimage import measure

import os
import cv2
import random
import colorsys
import pdb
from skimage import measure
from skimage.measure import find_contours
import matplotlib.pyplot as plt
from matplotlib.patches import Polygon

def cam_gen_box(scoremap_image, threshold):
    scoremap_image = scoremap_image * 255.0
    _CONTOUR_INDEX = 1 if cv2.__version__.split('.')[0] == '3' else 0
    scoremap_image = np.expand_dims((scoremap_image).astype(np.uint8), 2)
    _, thr_gray_heatmap = cv2.threshold(
        src=scoremap_image,
        thresh=int(threshold * np.max(scoremap_image)),
        maxval=255,
        type=cv2.THRESH_BINARY,
    )
    contours = cv2.findContours(
        image=thr_gray_heatmap, mode=cv2.RETR_TREE, method=cv2.CHAIN_APPROX_SIMPLE
    )[_CONTOUR_INDEX]

    if len(contours) == 0:
        return [0, 0, 224, 224]

    contour = max(contours, key=cv2.contourArea)
    x, y, w, h = cv2.boundingRect(contour)
    return [x, y, w + x, h + y]

def resize_cam(cam, size=(224, 224), mask=False):
    cam = cv2.resize(cam, (size[0], size[1]))
    cam_min, cam_max = cam.min(), cam.max()
    cam = (cam - cam_min) / (cam_max - cam_min)
    return cam

def intensity_to_rgb(intensity, cmap='cubehelix', normalize=False):
    assert intensity.ndim == 2, intensity.shape
    intensity = intensity.astype("float")

    if normalize:
        intensity -= intensity.min()
        intensity /= intensity.max()
    cmap = 'jet'
    cmap = plt.get_cmap(cmap)
    intensity = cmap(intensity)[..., :3]
    return intensity.astype('float32') * 255.0

def return_box_cam(cam, image, gt_box, th=0.5):
    # print(gt_box, len(gt_box))
    # print(gt_box)
    cam = resize_cam(cam, size=(224, 224))
    heatmap = intensity_to_rgb(cam, normalize=True).astype('uint8')
    heatmap_BGR = cv2.cvtColor(heatmap, cv2.COLOR_RGB2BGR)
    blend = image * 0.4 + heatmap_BGR * 0.6 # type: ignore

    box = np.array(get_bboxes(cam, th))
    cv2.rectangle(blend, # Green box
                        (box[0], box[1]),
                        (box[2], box[3]),
                        (0, 255, 0), 2)

    cv2.rectangle(blend, # Red box
                        (int(gt_box[0]), int(gt_box[1])),
                        (int(gt_box[2]), int(gt_box[3])),
                        (0, 0, 255), 2)

    return box, blend

def norm_atten_map(attn_map):
    min_val = np.min(attn_map)
    max_val = np.max(attn_map)
    attn_norm = (attn_map - min_val) / (max_val - min_val + 1e-15)
    if max_val - min_val == 0:
        return np.zeros_like(attn_map)
    return attn_norm

def random_colors(N, bright=True):
    """
    Generate random colors.
    """
    brightness = 1.0 if bright else 0.7
    hsv = [(i / N, 1, brightness) for i in range(N)]
    colors = list(map(lambda c: colorsys.hsv_to_rgb(*c), hsv))
    random.shuffle(colors)
    return colors

def apply_mask(image, mask, color, alpha=0.5):
    for c in range(3):
        image[:, :, c] = (
            image[:, :, c] * (1 - alpha * mask) + alpha * mask * color[c] * 255
        )
    return image

def display_instances(
    image, mask, fname="test", figsize=(5, 5), blur=False, contour=True, alpha=0.5
):
    fig = plt.figure(figsize=figsize, frameon=False)
    ax = plt.Axes(fig, [0.0, 0.0, 1.0, 1.0])
    ax.set_axis_off()
    fig.add_axes(ax)
    ax = plt.gca()

    N = 1
    mask = mask[None, :, :]
    # Generate random colors
    colors = random_colors(N)

    # Show area outside image boundaries.
    height, width = image.shape[:2]
    margin = 0
    ax.set_ylim(height + margin, -margin)
    ax.set_xlim(-margin, width + margin)
    ax.axis('off')
    masked_image = image.astype(np.uint32).copy()
    for i in range(N):
        color = colors[i]
        _mask = mask[i]
        if blur:
            _mask = cv2.blur(_mask, (10, 10))
        # Mask
        masked_image = apply_mask(masked_image, _mask, color, alpha)
        # Mask Polygon
        # Pad to ensure proper polygons for masks that touch image edges.
        if contour:
            padded_mask = np.zeros((_mask.shape[0] + 2, _mask.shape[1] + 2))
            padded_mask[1:-1, 1:-1] = _mask
            contours = find_contours(padded_mask, 0.5)
            for verts in contours:
                # Subtract the padding and flip (y, x) to (x, y)
                verts = np.fliplr(verts) - 1
                p = Polygon(verts, facecolor="none", edgecolor=color)
                ax.add_patch(p)
    ax.imshow(masked_image.astype(np.uint8), aspect='auto')
    fig.savefig(fname)
    print("{name} saved.")
    return

def re_pca_box(heatmap, size=224):
    boxes = []
    if heatmap.ndim == 3:
        for he in heatmap:
            highlight = np.zeros(he.shape)
            highlight[he > 0] = 1
            # max component
            all_labels = measure.label(highlight)
            highlight = np.zeros(highlight.shape)
            highlight[all_labels == count_max(all_labels.tolist())] = 1

            # visualize heatmap
            # show highlight in origin image
            highlight = np.round(highlight * 255)
            highlight_big = cv2.resize(
                highlight, (size, size), interpolation=cv2.INTER_NEAREST
            )
            props = measure.regionprops(highlight_big.astype(int))
            if len(props) == 0:
                bbox = [0, 0, size, size]
            else:
                temp = props[0]['bbox']
                bbox = [temp[1], temp[0], temp[3], temp[2]]
            temp_bbox = [bbox[0], bbox[1], bbox[2], bbox[3]]
            boxes.append(temp_bbox)
        return boxes  # x,y,w,h
    else:
        highlight = np.zeros(heatmap.shape)
        highlight[heatmap > 0] = 1
        # max component
        all_labels = measure.label(highlight)
        highlight = np.zeros(highlight.shape)
        highlight[all_labels == count_max(all_labels.tolist())] = 1

        # visualize heatmap
        # show highlight in origin image
        highlight = np.round(highlight * 255)
        highlight_big = cv2.resize(
            highlight, (size, size), interpolation=cv2.INTER_NEAREST
        )
        props = measure.regionprops(highlight_big.astype(int))
        if len(props) == 0:
            bbox = [0, 0, size, size]
        else:
            temp = props[0]['bbox']
            bbox = [temp[1], temp[0], temp[3], temp[2]]
        return bbox

def count_max(x):
    count_dict = {}
    for xlist in x:
        for item in xlist:
            if item == 0:
                continue
            if item not in count_dict.keys():
                count_dict[item] = 0
            count_dict[item] += 1
    if count_dict == {}:
        return -1
    count_dict = sorted(count_dict.items(), key=lambda d: d[1], reverse=True)
    return count_dict[0][0]

def cal_iou(box1, box2, method='iou'):
    """
    support:
    1. box1 and box2 are the same shape: [N, 4]
    2.
    :param box1:
    :param box2:
    :return:
    """
    box1 = np.asarray(box1, dtype=float)
    box2 = np.asarray(box2, dtype=float)
    if box1.ndim == 1:
        box1 = box1[np.newaxis, :]
    if box2.ndim == 1:
        box2 = box2[np.newaxis, :]

    iw = np.minimum(box1[:, 2], box2[:, 2]) - np.maximum(box1[:, 0], box2[:, 0]) + 1
    ih = np.minimum(box1[:, 3], box2[:, 3]) - np.maximum(box1[:, 1], box2[:, 1]) + 1

    i_area = np.maximum(iw, 0.0) * np.maximum(ih, 0.0)
    box1_area = (box1[:, 2] - box1[:, 0] + 1) * (box1[:, 3] - box1[:, 1] + 1)
    box2_area = (box2[:, 2] - box2[:, 0] + 1) * (box2[:, 3] - box2[:, 1] + 1)

    if method == 'iog':
        iou_val = i_area / (box2_area)
    elif method == 'iob':
        iou_val = i_area / (box1_area)
    else:
        iou_val = i_area / (box1_area + box2_area - i_area)
    return iou_val

def get_bboxes(cam, cam_thr=0.5):
    """
    cam: single image with shape (h, w, 1)
    thr_val: float value (0~1)
    return estimated bounding box
    """
    #pdb.set_trace()
    cam = (cam * 255.).astype(np.uint8)
    map_thr = cam_thr * np.max(cam)

    _, thr_gray_heatmap = cv2.threshold(cam,
                                        int(map_thr), 255,
                                        cv2.THRESH_TOZERO)

    contours, _ = cv2.findContours(thr_gray_heatmap,
                                       cv2.RETR_TREE,
                                       cv2.CHAIN_APPROX_SIMPLE)
    if len(contours) != 0:
        c = max(contours, key=cv2.contourArea)
        x, y, w, h = cv2.boundingRect(c)
        estimated_bbox = [x, y, x + w, y + h]
    else:
        estimated_bbox = [0, 0, 1, 1]

    return estimated_bbox  #, thr_gray_heatmap, len(contours)

def accuracy_loc( input, output, bbox_label, is_Train = True, thr=0.5,size=224):
    '''
    Calculate accuracy according to PCK,
    but uses ground truth heatmap rather than x,y locations
    First value to be returned is average accuracy across 'idxs',
    followed by individual accuracies
    '''

    cnt = input.shape[0]
    acc = np.zeros(cnt)
    tar_acc = np.zeros(cnt)
    pd_bbox = []
    for b in range(cnt):
        out_b = output[b,0, :, :]
        out_b = cv2.resize(out_b , (size, size))
        cam_min, cam_max = out_b.min(), out_b.max()
        out_b = (out_b - cam_min) / (cam_max - cam_min)
        out_bbox = np.array(get_bboxes(out_b, cam_thr=thr) )
        pd_bbox.append(out_bbox)

        if not is_Train:
            max_iou = 0
            gt_bbox = bbox_label[b].cpu()
            gt_bbox = list(map(float, gt_bbox))
            gt_box_cnt = len(gt_bbox) // 4
            for i in range(gt_box_cnt):
                gt_box = gt_bbox[i * 4:(i + 1) * 4]
                iou_i = cal_iou(out_bbox, gt_box)
                if iou_i > max_iou:
                    max_iou = iou_i
            acc[b] = max_iou
    return acc, cnt,


import json
import os
import pickle
from web3 import Web3
import sys
import readline
import streamlit as st
import pandas as pd
import requests
import math

# global variables
data = bytes(0)
BYTES_ARRAY_INDEX = 8 + 64 + 64

# Streamlit config
st.set_page_config(
    page_title = "Farm Decoder",
    page_icon = "ðŸŒ±",
    layout="wide"
)
col1, col2 = st.columns([0.4,0.6], gap ="large")

dataTypes = {
    "uint8" : 1,
    "uint16" : 2,
    "uint24" : 3,
    "uint32" : 4,
    "uint64" : 8,
    "uint128" : 16,
    "uint256" : 32,
    "int96" : 12,
    "address" : 20,
    "advancedPipeCall" : 69,
    "advancedPipe" : 420,
    "bytes": 100
}

# from beanstalk.json, generate a dictonary of selectors and their input param data types
def jsonToSelectors():
    # TODO: add specific selectors to tuple[]
    # MultiPipe
    outDict = {}
    with open('beanstalk.json') as json_file:
        _json = json.load(json_file)
        # loop through each function
        for function in _json:
            inputsStr = ""
            inputList = []
            for i , input in enumerate(function['inputs']):
                inputList.append(input['type'])
                if(i == len(function['inputs']) - 1):
                    inputsStr = inputsStr + input['type']
                else:
                    inputsStr = inputsStr + input['type'] + ","
            if(function['type'] != 'function'):
                continue
            stringThing = function['name'] + "(" + inputsStr + ")"
            functionSelector = Web3.solidity_keccak(["string"],[stringThing]).hex()[:10]
            outDict[functionSelector] = {function['name']: inputList}
        outDict['0xb452c7ae'] = {'advancedPipe': ['advancedPipeCall[]','uint256']} ## advancedPipe
        outDict['0x36bfafbd'] = {'advancedFarm': ['advancedFarmCall[]','uint256']} ## advancedFarm
    with open('Well.json') as json_file:
        _json = json.load(json_file)['abi']
        # loop through each function
        for function in _json:
            inputsStr = ""
            inputList = []
            for i , input in enumerate(function['inputs']):
                if(input == []):
                    continue
                inputList.append(input['type'])
                if(i == len(function['inputs']) - 1):
                    inputsStr = inputsStr + input['type']
                else:
                    inputsStr = inputsStr + input['type'] + ","
            if(function['type'] != 'function'):
                continue
            stringThing = function['name'] + "(" + inputsStr + ")"
            functionSelector = Web3.solidity_keccak(["string"],[stringThing]).hex()[:10]
            outDict[functionSelector] = {function['name']: inputList}
     # dump the outDict to a pickle file
    with open('selectors.pickle', 'wb') as handle:
        pickle.dump(outDict, handle, protocol=pickle.HIGHEST_PROTOCOL)

# given data, index, and json, get the selector data
def getSelectorData(index: int, beanstalkJson: dict):
    selector = '0x' + data[index:index+8].decode('utf-8')
    st.write("Function Selector: ", selector)
    function = beanstalkJson[selector]
    st.subheader("Function Selector and Name: " + selector + " , " + list(function.keys())[0])
    st.write("Function Inputs: " + str(list(function.values())[0]))
    return function

def getSelectorData2(selector: bytes):
     with open('selectors.pickle', 'rb') as handle:
        beanstalkJson = pickle.load(handle)
        try:
            function = beanstalkJson['0x' + str(selector)[2:-1]]
        except:
            function = {'NA' : 'NA'}
        return function

# loops through all the inputs of a function, and prints the input type and data
def decodeHelper(index: int, beanstalkJson: dict):
    st.divider()
    function = getSelectorData(index, beanstalkJson)
    index += 8
    # loop through all entries, and print the input type and data
    # if there are no entries, it doesn't require any inputs and therfore can be skipped
    st.write("Function inputs and data:")
    if(len(list(function.values())[0]) != 0):
        startIndex = index
        for i, value in enumerate(list(function.values())[0]):

            # check if value is an array. If its an array, we need to decode it differently
            if "[]" in value:
                # remove the last two characters in value
                value = value[:-2]
                index = decodeArray(data, startIndex, index, value)

            elif(value == "bytes"):
                decodeBytes(data, startIndex)

            else:
                inputData = int(data[index + 64 - 2*dataTypes[value]: index + 64],16)
                if(value == "address"):
                    st.write(value + ": " + "[" +  hex(inputData) + "](" + "https://etherscan.io/address/" + hex(inputData) + ")")
                else:
                    st.write(value + ": " + str(inputData))
            index += 64
    # add the remaining n bytes to the index (4 bytes from selector, so add 28 more bytes)
    index += 56
    return index

def decodeFarm():
    st.header('Results:')
    with open('selectors.pickle', 'rb') as handle:
        beanstalkJson = pickle.load(handle)
    if(data == "0x"):
        return
    index = 0
    # get selector
    function = getSelectorData(index, beanstalkJson)
    index +=8
    # next 32 bytes represent the position of where the length of bytes[] is stored:
    lengthPos = int(data[index:index + 64], 16)
    index += lengthPos*2
    lengthOfBytesArray = int(data[index:index + 64],16)
    index += 64
    st.write("Farm contains", str(lengthOfBytesArray), "Functions!")

    # get the lengths of each bytes element
    for i in range(lengthOfBytesArray):
        lengthOfBytesPos = int(data[BYTES_ARRAY_INDEX + i*64:BYTES_ARRAY_INDEX + (i+1)*64], 16)
        startOfLengthOfbytes = BYTES_ARRAY_INDEX + lengthOfBytesPos*2
        # function data starts 32 bytes after the length
        startOfFunction = startOfLengthOfbytes + 64
        decodeHelper(startOfFunction, beanstalkJson)

def decodeAdvancedFarm():
    st.header('Results:')
    with open('selectors.pickle', 'rb') as handle:
        beanstalkJson = pickle.load(handle)
    if(data == "0x"):
        return
    index = 0
    # get selector
    function = getSelectorData(index, beanstalkJson)
    index +=8
    # next 32 bytes represent the position of where the length of advancedFarm[] is stored:
    lengthPos = int(data[index:index + 64], 16)
    index += lengthPos*2
    lengthOfAdvancedFarmArray = int(data[index:index + 64],16)
    index += 64
    st.write("AdvancedFarm contains", str(lengthOfAdvancedFarmArray), "Functions!")
    clipboardData = []
    # get the location of the advancedFarmCall array
    for i in range(lengthOfAdvancedFarmArray):
        # advanced farm call has 2 bytes array
        locOfBytesPos = int(data[BYTES_ARRAY_INDEX + i*64:BYTES_ARRAY_INDEX + (i+1)*64], 16)
        # st.write("locOfBytesPos", locOfBytesPos)
        FirstBytesPos = BYTES_ARRAY_INDEX + locOfBytesPos*2
        # st.write("FirstBytesPos", FirstBytesPos)
        for j in range(2):
            # get the location of the bytes length:
            LocationBytes = int(data[FirstBytesPos + j*64:FirstBytesPos + (j+1)*64],16)
            # st.write("LocationBytes", LocationBytes)
            byteLengthLocation = FirstBytesPos + LocationBytes*2
            # st.write("byteLengthLocation", byteLengthLocation)
            lengthOfBytes = int(data[byteLengthLocation:byteLengthLocation + 64],16)
            # st.write("lengthOfBytes", lengthOfBytes)
            # st.write("------------------")
            # calldata
            if(j == 0):
                decodeHelper(byteLengthLocation + 64, beanstalkJson)
            else:
                _bytesData = data[byteLengthLocation + 64:byteLengthLocation + 64 + lengthOfBytes*2]
                clipboardData.append(_bytesData)
                decodeClipboard(_bytesData, True, clipboardData)

def decodeClipboard(_bytesData, isAdvancedFarmClipboard, clipboardData):
    if(isAdvancedFarmClipboard):
        st.subheader('AdvancedFarmClipboard:')
    else:
        st.subheader('PipelineClipboard:')
    # clipboard is decoded as such:
    # first byte determines whether to use ether.
    # second byte determines the clipboardType:
    clipboardType = int(_bytesData[0:2])
    ethFlag = int(_bytesData[2:4])
    st.write("useEthFlag:", str(bool(ethFlag)))
    st.write("clipboardType:", str(clipboardType))
    if(clipboardType == 0):
        # type 0 means that the clipboard is not used.
        if(ethFlag == 1):
            st.write("ethAmount:", str(int(_bytesData[62:],16)))
    elif(clipboardType == 1):
        # type 1 means that only 32 bytes of data is used
        st.write("clipboardData:", str(_bytesData[4:]))
        # bytes 2 - 11 specify the returnDataIndex of the function
        st.write("returnDataIndex:", str(int(_bytesData[4:24])))
        # bytes 12 - 21 specify the copyIndex of the function
        st.write("copyIndex:", str(((int(_bytesData[26:44],16) - 32) / 32)))
        # bytes 22 - 31 specify the pasteIndex of the function
        st.write("pasteIndex:", str((int(_bytesData[47:64],16) - 36) / 32))
        # bytes 32 - 63 is the the amount of eth used (if applicable)
        if(ethFlag == 1):
            st.write("ethAmount:", str(_bytesData[62:]))
    else:
        # type 2 means n bytes are being used.
        st.write("clipboardData:", str(_bytesData[4:]))
def getSelector(selector: str):
    with open('selectors.pickle', 'rb') as handle:
        beanstalkJson = pickle.load(handle)
    function = beanstalkJson[selector]
    print("function Selector: " + selector)
    print("function Name: " + list(function.keys())[0])
    print("function Inputs: " + str(list(function.values())[0]))

def twos_comp(val, bits):
    """compute the 2's complement of int value val"""
    if (val & (1 << (bits - 1))) != 0: # if sign bit is set e.g., 8bit: 128-255
        val = val - (1 << bits)        # compute negative value
    return val                         # return positive value as is

def decodeArray(data: bytes, startIndex: int, index: int, value: str):
    # first 32 bytes determine the location of the array)
    _lengthPos = data[index :index + 64]
    lengthPos = int(_lengthPos,16)
    lengthPosIndex = startIndex + lengthPos*2
    arrayLength = int(data[lengthPosIndex:lengthPosIndex + 64],16)

    arrayOutput = []
    lengthPosIndex += 64
    pipelineClipboard = []
    for i in range(arrayLength):
        if(dataTypes[value] == 69): ## advancedPipeCall is a tuple, and needs special care
            output = decodeAdvancedPipe(data, lengthPosIndex, "advancedPipe", i, pipelineClipboard)
            # arrayOutput.append(output)
        else:
            output = int(data[lengthPosIndex + (i * 64): lengthPosIndex + ((i + 1) * 64)],16)
            if("int" in value):
                output = twos_comp(output, 256)
            if(dataTypes[value] == 20):
                arrayOutput.append(hex(output))
            else:
                arrayOutput.append(output)
    if(dataTypes[value] != 69):
        st.write(value + "[]: " + str(arrayOutput))
    return index

def decodeAdvancedPipe(data: bytes, startIndex: int, value: str, m: int, pipelineClipboard):
    advancedData = []
     # first 32 bytes determine the location of the data of the array
    _lengthData = data[startIndex + (64 * m) :startIndex + (64 * (m+1))]
    lengthData = int(_lengthData,16)

    lengthDataIndex = startIndex + lengthData*2
    addressData = data[lengthDataIndex:lengthDataIndex + 64]
    addressData = hex(int(addressData,16))
    st.subheader("Target.Address: " + "[" + str(addressData) + "](https://etherscan.io/address/" + str(addressData) + ")")
    advancedData.append(("address:" + addressData))

    for i in range(2): ## get data of (bytes, bytes)
        # first 32 bytes determine the location of the length of the bytes
        _bytesLocation = data[lengthDataIndex + (64 * (i + 1)) :lengthDataIndex + (64 * (i + 2))]
        bytesLocation = int(_bytesLocation,16)
        bytesLengthIndex = lengthDataIndex + bytesLocation*2
        _bytesLength = data[bytesLengthIndex:bytesLengthIndex + 64]
        bytesLengthIndex += 64
        bytesLength = int(_bytesLength,16)
        _bytesData = data[bytesLengthIndex:bytesLengthIndex + bytesLength*2]
        if(_bytesData != b''):
            bytesData = int(_bytesData,16)
        else:
            bytesData = int(0)
        stuff = []
        loop = 0
        if(i == 0):
            loop = (bytesLength - 4) // 32 + 1
            for j in range(loop):
                if(j == 0):
                    function = getSelectorData2(_bytesData[0:8])

                    st.subheader("callData.Function Selector and Name: " + '0x' + str(_bytesData[0:8])[2:-1] + " , " + list(function.keys())[0])
                    st.write("callData.Function Inputs: " + str(list(function.values())[0]))
                    stuff.append((" Selector: " + str(hex(bytesData))[0:10]))
                else:
                    st.write(str(j) + ": " + '0x' + str(hex(bytesData)[10 + (j-1)*64:10 + j*64])[2:-1])
                    stuff.append("  data " + str(j) + ": " + '0x' + str(hex(bytesData)[10 + (j-1)*64:10 + j*64])[2:-1])
        else:
            pipelineClipboard.append(_bytesData)
            st.write(" Clipboard: " + '0x' + str(_bytesData)[2:-1])
            decodeClipboard(_bytesData, False, pipelineClipboard)
            st.write()

def decodeBytes(data: bytes, startIndex: int):
    # first 32 bytes determine the location of the length of the bytes
    bytesLocation = int(data[startIndex :startIndex + 64],16)
    bytesLengthIndex = startIndex + bytesLocation*2
    bytesLength = int(data[bytesLengthIndex:bytesLengthIndex + 64],16)
    bytesLengthIndex += 64
    _bytesData = data[bytesLengthIndex:bytesLengthIndex + bytesLength*2]
    st.write('bytes: ' +'0x' + str(_bytesData)[2:-1])

def executeDecodeFarm():
    with col1:
        st.title("FarmDecoder")
        global data
        # check that selectors.pickles exist.
        # if not, generate with jsonToSelectors
        # if you want to regenerate the selectors, delete the pickle file.
        if(os.path.isfile('./selectors.pickle') == False):
            jsonToSelectors()
        inp = st.text_input('Please input farm data (0x300dd6cf.. or 0x36bfafbd..)')
        if(inp == ''):
            return
        elif('0x300dd6cf' not in inp and '0x36bfafbd' not in inp):
            st.write("Incorrect Input")
        else:
            if('0x300dd6cf' in inp):
                st.write('Input Data:', inp)
                data = inp[2:].encode()
                with col2:
                    decodeFarm()
                    st.balloons()
            else:
                st.write('Input Data:', inp)
                data = inp[2:].encode()
                with col2:
                    decodeAdvancedFarm()
                    st.balloons()
    st.divider()
    st.subheader("[Github](https://github.com/Brean0/FarmDecoder)")
    st.subheader("[Beanstalk](https://app.bean.money/)")

if __name__ == "__main__":
    executeDecodeFarm()


import warnings
from datetime import datetime

import numpy as np
from numpy import cov
from scipy import linalg

def time_format():
    return f"{datetime.now()}|> "

def calculate_frechet_distance(mu1, sigma1, mu2, sigma2, eps=1e-6):
    """Numpy implementation of the Frechet Distance.
    The Frechet distance between two multivariate Gaussians X_1 ~ N(mu_1, C_1)
    and X_2 ~ N(mu_2, C_2) is
            d^2 = ||mu_1 - mu_2||^2 + Tr(C_1 + C_2 - 2*sqrt(C_1*C_2)).

    Stable version by Dougal J. Sutherland.
    Params:
    -- mu1 : Numpy array containing the activations of the pool_3 layer of the
             inception net ( like returned by the function 'get_predictions')
             for generated samples.
    -- mu2   : The sample mean over activations of the pool_3 layer, precalcualted
               on an representive data set.
    -- sigma1: The covariance matrix over activations of the pool_3 layer for
               generated samples.
    -- sigma2: The covariance matrix over activations of the pool_3 layer,
               precalcualted on an representive data set.
    Returns:
    --   : The Frechet Distance.
    """

    mu1 = np.atleast_1d(mu1)
    mu2 = np.atleast_1d(mu2)

    sigma1 = np.atleast_2d(sigma1)
    sigma2 = np.atleast_2d(sigma2)

    assert (
        mu1.shape == mu2.shape
    ), "Training and test mean vectors have different lengths"
    assert (
        sigma1.shape == sigma2.shape
    ), "Training and test covariances have different dimensions"

    diff = mu1 - mu2

    # product might be almost singular
    covmean, _ = linalg.sqrtm(sigma1.dot(sigma2), disp=False)
    if not np.isfinite(covmean).all():
        msg = (
            "fid calculation produces singular product; adding %s to diagonal of cov estimates"
            % eps
        )
        warnings.warn(msg)
        offset = np.eye(sigma1.shape[0]) * eps
        covmean = linalg.sqrtm((sigma1 + offset).dot(sigma2 + offset))

    # numerical error might give slight imaginary component
    if np.iscomplexobj(covmean):
        if not np.allclose(np.diagonal(covmean).imag, 0, atol=1e-3):
            m = np.max(np.abs(covmean.imag))
            raise ValueError("Imaginary component {}".format(m))
        covmean = covmean.real

    tr_covmean = np.trace(covmean)

    return (
        diff.dot(diff) + np.trace(sigma1) + np.trace(sigma2) - 2 * tr_covmean
    )

def calculate_fid(act1, act2):

    mu1, sigma1 = act1.mean(axis=0), cov(act1, rowvar=False)
    mu2, sigma2 = act2.mean(axis=0), cov(act2, rowvar=False)
    fid = calculate_frechet_distance(mu1, sigma1, mu2, sigma2)
    return fid


import csv
from sklearn.neighbors import KNeighborsClassifier

#Voter class representing voter
class Voter:
  def __init__(self, optimus_id, age, party, ethnicity, income, education, marital_status, occupation_industry,
              vh00p, vh00g, vh02p, vh02g,
              vh04p, vh04g, vh06p, vh06g,
              vh08p, vh08g, vh10p, vh10g, vh12p, vh12g, vh14p,
              net_worth, donates_to_liberal_causes, donates_to_conservative_causes, congressional_district, designated_market_area,
              g08_precinct_turnout, g10_precinct_turnout, g12_precinct_turnout,
              p08_precinct_turnout, p10_precinct_turnout, p12_precinct_turnout):
    self.optimus_id = optimus_id

    #Previous election data is coming as string
    #Converting string to integer value
    self.vh00p = int(vh00p)
    self.vh02p = int(vh02p)
    self.vh04p = int(vh04p)
    self.vh06p = int(vh06p)
    self.vh08p = int(vh08p)
    self.vh10p = int(vh10p)

    self.vh00g = int(vh00g)
    self.vh02g = int(vh02g)
    self.vh04g = int(vh04g)
    self.vh06g = int(vh06g)
    self.vh08g = int(vh08g)
    self.vh10g = int(vh10g)

    self.vh12p = int(vh12p)
    self.vh12g = int(vh12g)

    self.vh14p = int(vh14p)
    # Percint turnout information was missing on some voters
    # Checking if value string is "0.32" or ""
    if len(g08_precinct_turnout) == 0:
      g08_precinct_turnout = "0"
    if len(p08_precinct_turnout) == 0:
      p08_precinct_turnout = "0"
    if len(g10_precinct_turnout) == 0:
      g10_precinct_turnout = "0"
    if len(p10_precinct_turnout) == 0:
      p10_precinct_turnout = "0"
    if len(p12_precinct_turnout) == 0:
      p12_precinct_turnout = "0"
    if len(g12_precinct_turnout) == 0:
      g12_precinct_turnout = "0"
    self.g08_precinct_turnout = float(g08_precinct_turnout)
    self.g10_precinct_turnout = float(g10_precinct_turnout)
    self.g12_precinct_turnout = float(g12_precinct_turnout)
    self.p08_precinct_turnout = float(p08_precinct_turnout)
    self.p10_precinct_turnout = float(p10_precinct_turnout)
    self.p12_precinct_turnout = float(p12_precinct_turnout)

    # Also congressional_district was empty
    if len(congressional_district) == 0:
      congressional_district = 0
    self.congressional_district = float(congressional_district)

    # Representing values with integer values
    if "Married" in marital_status:
      self.marital_status = 1
    elif "Non-Traditional" in marital_status:
      self.marital_status = 2
    else:
      self.marital_status = 3

    # Representing values with integer values
    if "LAS VEGAS DMA (EST.)" in designated_market_area:
      self.designated_market_area = 1
    elif "RENO DMA (EST.)" in designated_market_area:
      self.designated_market_area = 2
    else:
      self.designated_market_area = 3

    # len(age) is zero when age = "" so age = 0
    if len(age) == 0:
      age = 0
    self.age = float(age)
    # Representing values with integer values
    if party == "Republican":
      self.party = 1
    elif party == "Democratic":
      self.party = 2
    elif party == "American Independent":
      self.party = 3
    else:
      self.party = 4

    # Representing values with integer values
    if ethnicity == "European":
      self.ethnicity = 1
    elif ethnicity == "African-American":
      self.ethnicity = 2
    elif ethnicity == "Asian":
      self.ethnicity = 3
    elif ethnicity == "Hispanic":
      self.ethnicity = 4
    elif ethnicity == "Other":
      self.ethnicity = 5
    else:
      self.ethnicity = 6

    # Representing values with integer values
    if "Medical" in occupation_industry:
      self.occupation_industry = 1
    elif "Manufacturing" in occupation_industry:
      self.occupation_industry = 2
    elif "Management" in occupation_industry:
      self.occupation_industry = 3
    elif "Clerical/Office" in occupation_industry:
      self.occupation_industry = 4
    elif "Other" in occupation_industry:
      self.occupation_industry = 5
    elif "Financial Services" in occupation_industry:
      self.occupation_industry = 6
    elif "Skilled Trades" in occupation_industry:
      self.occupation_industry = 7
    elif "Food Services" in occupation_industry:
      self.occupation_industry = 8
    elif "Engineering" in occupation_industry:
      self.occupation_industry = 9
    elif "Military" in occupation_industry:
      self.occupation_industry = 10
    elif "Computer Professional" in occupation_industry:
      self.occupation_industry = 11
    elif "Education" in occupation_industry:
      self.occupation_industry = 12
    elif "Financial Services" in occupation_industry:
      self.occupation_industry = 13
    else:
      self.occupation_industry = 14

    # Representing values with integer values
    if "Grad Degree" in education:
      self.education = 1
    elif "Bach Degree" in education:
      self.education = 2
    elif "Less than HS Diploma" in education:
      self.education = 3
    elif "HS Diploma" in education:
      self.education = 4
    elif "Some College" in education:
      self.education = 5
    else:
      self.education = 6

    # Representing values with integer values
    if "200k+" in income:
      self.income = 1
    elif "125k-200k" in income:
      self.income = 2
    elif "75k-125k" in income:
      self.income = 3
    elif "35k-75k" in income:
      self.income = 4
    elif "0-35k" in income:
      self.income = 5
    else:
      self.income = 6

    # Representing values with integer values
    if "$499999+" in net_worth:
      self.net_worth = 1
    elif "$250000-499999" in net_worth:
      self.net_worth = 2
    elif "$10000-24999" in net_worth:
      self.net_worth = 3
    elif "$50000-99999" in net_worth:
      self.net_worth = 4
    elif "$25000-49999" in net_worth:
      self.net_worth = 5
    elif "$10000-24999" in net_worth:
      self.net_worth = 6
    elif "$5000-9999" in net_worth:
      self.net_worth = 7
    elif "$1-4999" in net_worth:
      self.net_worth = 8
    else:
      self.net_worth = 9

    # Representing values with integer values
    if "Yes" in donates_to_liberal_causes:
      self.donates_to_liberal_causes = 1
    else:
      self.donates_to_liberal_causes = 2

    # Representing values with integer values
    if "Yes" in donates_to_conservative_causes:
      self.donates_to_conservative_causes = 1
    else:
      self.donates_to_conservative_causes = 2

  def __repr__(self):
    str = ""
    for k, v in self.__dict__.items():
      str += v + ", "
    return str

def makeVoter (args=[]):
  #returning new voter class with given arguments
  return Voter(optimus_id=args[0],
                age=args[1],
                party=args[2],
                ethnicity=args[3],
                marital_status=args[4],
                income=args[6],
                education=args[7],
                congressional_district=args[8],
                designated_market_area=args[9],
                occupation_industry=args[10],
                vh14p=args[11],
                vh12g=args[12],
                vh12p=args[13],
                vh10g=args[14],
                vh10p=args[15],
                vh08g=args[16],
                vh08p=args[17],
                vh06g=args[18],
                vh06p=args[19],
                vh04g=args[20],
                vh04p=args[21],
                vh02g=args[22],
                vh02p=args[23],
                vh00g=args[24],
                vh00p=args[25],
                net_worth=args[26],
                donates_to_liberal_causes=args[30],
                donates_to_conservative_causes=args[31],
                g08_precinct_turnout=args[33],
                g10_precinct_turnout=args[34],
                g12_precinct_turnout=args[35],
                p08_precinct_turnout=args[36],
                p10_precinct_turnout=args[37],
                p12_precinct_turnout=args[38]
                )

# choosing features for model training
def make_features(voters = []):
  features = []
  for voter in voters:
    entry = []
    entry.append(voter.age)
    entry.append(voter.party)
    entry.append(voter.ethnicity)
    entry.append(voter.income)
    entry.append(voter.marital_status)
    entry.append(voter.occupation_industry)
    entry.append(voter.vh02g)
    entry.append(voter.vh04g)
    entry.append(voter.vh06g)
    entry.append(voter.vh08g)
    entry.append(voter.vh10g)
    entry.append(voter.vh12g)
    entry.append(voter.donates_to_liberal_causes)
    entry.append(voter.congressional_district)
    features.append(entry)
  return features

# labeling training data
def make_labels(voters = []):
  labels = []
  for voter in voters:
    labels.append(voter.vh12p)
  return labels

# Returning trained model
def train_model(voters = []):
  clf = KNeighborsClassifier(n_neighbors=10)
  features = make_features(voters)
  labels = make_labels(voters)
  clf = clf.fit(features, labels)
  return clf

# Picking features of voter with given index
def pick_person (voters, index):
  person = []
  person.append(voters[index].age)
  person.append(voters[index].party)
  person.append(voters[index].ethnicity)
  person.append(voters[index].income)
  person.append(voters[index].marital_status)
  person.append(voters[index].occupation_industry)
  person.append(voters[index].vh02g)
  person.append(voters[index].vh04g)
  person.append(voters[index].vh06g)
  person.append(voters[index].vh08g)
  person.append(voters[index].vh10g)
  person.append(voters[index].vh12g)
  person.append(voters[index].donates_to_liberal_causes)
  person.append(voters[index].congressional_district)
  return person

#opening voterfile.csv file as csvfile
with open('voterfile.csv', 'r') as csvfile:
  spamreader = csv.reader(csvfile) #spamreader is now voterfile.csv
  next(spamreader) #skipping headers line
  voter_list = []
  #initializing Voter classes
  for row in spamreader:
    new_voter = makeVoter(args=row)
    voter_list.append(new_voter)

  # training model with voter data from csvfile
  model = train_model(voter_list)

  with open('results.csv', 'w') as csvfile:
    #filewriter is now results.csv file
    filewriter = csv.writer(csvfile, delimiter=',',
                            quotechar='|', quoting=csv.QUOTE_MINIMAL)

    # headers of results.csv file
    headers = ["optimus_id", "age", "party", "ethnicity", "income", "marital_status", "occupation_industry", "vh02g", "vh04g", "vh06g", "vh08g", "vh10g", "vh12g"]
    headers.append("donates_to_liberal_causes")
    headers.append("congressional_district")
    headers.append("vote")
    headers.append("vote_prob")

    filewriter.writerow(headers)

    # writing all voter forecast with features used to forecast
    for i in range(len(voter_list)):
      v = voter_list[i]
      row = [v.optimus_id, v.age, v.party, v.ethnicity, v.income, v.marital_status, v.occupation_industry, v.vh02g, v.vh04g, v.vh06g, v.vh08g, v.vh10g, v.vh12g]
      row.append(v.donates_to_liberal_causes)
      row.append(v.congressional_district)
      example = pick_person(voter_list, i)
      outcome = model.predict([example])[0]
      prob = model.predict_proba([example])[0][1]
      row.append(outcome)
      row.append(prob)

      filewriter.writerow(row)


from datetime import datetime
from enum import Enum
from pathlib import Path
import re
from sys import stderr
from time import sleep
import typing

class ReleaseTypes(Enum):
    MAJOR = 0
    MINOR = 1
    PATCH = 2

valid_release_types = typing.Literal["major", "minor", "patch"]

class ReleaseStrings:
    """An easy way to pass the various flavours of release string between functions."""

    def __init__(self, input_tag: str):
        version_mask = r"v\d+\.\d+\.\d+\D*.*"
        regex_101 = "https://regex101.com/r/dLVaNH/1"
        if re.fullmatch(version_mask, input_tag) is None:
            message = (
                "Release tag does not match the input mask:\n"
                f"{version_mask}\n"
                f"({regex_101})"
            )
            raise ValueError(message)
        else:
            self.tag = input_tag  # v1.2.3rc0

        self.series = ".".join(self.tag.split(".")[:2])  # v1.2
        self.branch = self.series + ".x"  # v1.2.x
        self.release = self.tag[1:]  # 1.2.3rc0

class WhatsNewRsts:
    """An easy way to pass the paths of various What's New files between functions."""

    def __init__(self, release_strings: ReleaseStrings):
        src_dir = Path(__file__).parents[1] / "docs" / "src"
        whatsnew_dir = src_dir / "whatsnew"
        assert whatsnew_dir.is_dir()

        self.latest = whatsnew_dir / "latest.rst"
        self.release = whatsnew_dir / (release_strings.series[1:] + ".rst")
        self.index = whatsnew_dir / "index.rst"
        self.template = self.latest.with_suffix(".rst.template")

def _break_print(message: str):
    print()
    print(message)
    # Help with flow/visibility by waiting 1secs before proceeding.
    sleep(1)

def _mark_section(section_number: int):
    _break_print(f"SECTION {section_number} ...")

def _get_input(message: str, expected_inputs: str) -> str:
    _break_print(message)
    return input(expected_inputs + " : ")

def _wait_for_done(message: str):
    _break_print(message)
    done = False
    while not done:
        done = (
            input("Step complete? y / [n] : ").casefold() == "y".casefold()
        )

def _report_problem(message: str):
    print(message, file=stderr)
    # To ensure correct sequencing of messages.
    sleep(0.5)

def get_release_type() -> ReleaseTypes:
    release_type = None
    release_types_str = " ".join(
        [f"{m.name}={m.value}" for m in ReleaseTypes.__members__.values()]
    )
    message = "What type of release are you preparing?\nhttps://semver.org/"
    while release_type is None:
        input_type = _get_input(message, release_types_str)
        try:
            release_type = ReleaseTypes(int(input_type))
        except ValueError:
            _report_problem("Invalid release type. Please try again ...")
    _break_print(f"{release_type} confirmed.")
    return release_type

def get_release_tag() -> ReleaseStrings:
    # TODO: automate using setuptools_scm.
    release_strings = None
    message = (
        "Input the release tag you are creating today, including any release "
        "candidate suffix.\n"
        "https://semver.org/\n"
        "https://scitools-iris.readthedocs.io/en/latest/developers_guide/release.html?highlight=candidate#release-candidate"
    )
    while release_strings is None:
        input_tag = _get_input(message, "e.g. v1.2.3rc0")
        try:
            release_strings = ReleaseStrings(input_tag)
        except ValueError as err:
            _report_problem(str(err))
    return release_strings

def check_release_candidate(
    release_type: ReleaseTypes, release_strings: ReleaseStrings
) -> bool:
    is_release_candidate = "rc" in release_strings.tag

    message = "Checking tag for release candidate: "
    if is_release_candidate:
        message += "DETECTED\nThis IS a release candidate."
    else:
        message += "NOT DETECTED\nThis IS NOT a release candidate."
    _break_print(message)

    if release_type == ReleaseTypes.PATCH and is_release_candidate:
        message = (
            "Release candidates are not expected for PATCH releases. "
            "Are you sure you want to continue?"
        )
        if _get_input(message, "y / [n]").casefold() != "y".casefold():
            exit()
    return is_release_candidate

def check_first_in_series(
    release_type: ReleaseTypes,
    release_strings: ReleaseStrings,
    is_release_candidate: bool,
) -> bool:
    first_in_series = False
    if release_type != ReleaseTypes.PATCH:
        message = (
            "Have there been any prior releases in the "
            f"{release_strings.series} series, including release candidates?"
        )
        first_in_series = (
            _get_input(message, "[y] / n").casefold() == "n".casefold()
        )
        if first_in_series:
            _break_print("First in series confirmed.")
            if not is_release_candidate:
                message = (
                    "The first release in a series is expected to be a "
                    "release candidate, but this is not. Are you sure you "
                    "want to continue?"
                )
                if _get_input(message, "y / [n]").casefold() != "y".casefold():
                    exit()
        else:
            _break_print("Existing series confirmed.")
    return first_in_series

def update_standard_names(first_in_series: bool) -> None:
    if first_in_series:
        message = (
            "Update the file ``etc/cf-standard-name-table.xml`` to the latest CF "
            "standard names, via a new Pull Request.\n"
            "(This is used during build to automatically generate the sourcefile "
            "``lib/iris/std_names.py``).\n"
            "Latest standard names:\n"
            'wget "http://cfconventions.org/Data/cf-standard-names/current/src/cf-standard-name-table.xml";'
        )
        _wait_for_done(message)

def check_deprecations(release_type: ReleaseTypes) -> None:
    if release_type == ReleaseTypes.MAJOR:
        message = (
            "This is a MAJOR release - be sure to finalise all deprecations "
            "and FUTUREs from previous releases, via a new Pull Request.\n"
            "https://scitools-iris.readthedocs.io/en/latest/developers_guide/contributing_deprecations.html"
        )
        _wait_for_done(message)

def _delete_local_branch(branch_name: str):
    message = (
        "Before the next step, avoid a name clash by deleting any "
        "existing local branch, if one exists.\n"
        f"git branch -D {branch_name};\n"
        f"git push -d origin {branch_name};"
    )
    _wait_for_done(message)

def create_release_branch(
    release_strings: ReleaseStrings, first_in_series: bool
) -> None:
    # TODO: automate

    _break_print("Release branch management ...")

    if first_in_series:
        message = (
            "Visit https://github.com/SciTools/iris and create the"
            f"``{release_strings.branch}`` release branch from ``main``."
        )
        _wait_for_done(message)

    else:
        message = (
            "Cherry-pick any specific commits that are needed from ``main`` "
            f"onto {release_strings.branch} , to get the CI passing.\n"
            "E.g. a new dependency pin may have been introduced since "
            f"{release_strings.branch} was last updated from ``main``.\n"
            "DO NOT squash-merge - want to preserve the original commit SHA's."
        )
        _wait_for_done(message)

def finalise_whats_new(
    release_type: ReleaseTypes,
    release_strings: ReleaseStrings,
    is_release_candidate: bool,
    first_in_series: bool,
) -> WhatsNewRsts:
    _break_print("What's New finalisation ...")

    working_branch = release_strings.branch + ".updates"
    _delete_local_branch(working_branch)
    message = (
        f"Checkout a local branch from the official {release_strings.branch} branch.\n"
        "git fetch upstream;\n"
        f"git checkout upstream/{release_strings.branch} -b "
        f"{working_branch};"
    )
    _wait_for_done(message)

    rsts = WhatsNewRsts(release_strings)

    # TODO: automate
    if first_in_series:
        message = (
            "'Cut' the What's New for the release.\n"
            f"git mv {rsts.latest.absolute()} {rsts.release.absolute()};"
        )
        _wait_for_done(message)

        message = (
            f"In {rsts.index.absolute()}:\n"
            f"Replace references to {rsts.latest.name} with {rsts.release.name}"
        )
        _wait_for_done(message)

    _break_print(f"What's New file path = {rsts.release}")

    if not release_type == ReleaseTypes.PATCH:
        whatsnew_title = f"{release_strings.series} ({datetime.today().strftime('%d %b %Y')})"
        if is_release_candidate:
            whatsnew_title += " [release candidate]"
        # TODO: automate
        message = f"In {rsts.release.name}: set the page title to:\n{whatsnew_title}\n"
        if not is_release_candidate:
            message += (
                "\nBe sure to remove any existing mentions of release "
                "candidate from the title.\n"
            )
        _wait_for_done(message)

        message = (
            f"In {rsts.release.name}: ensure the page title underline is "
            "the exact same length as the page title text."
        )
        _wait_for_done(message)

        dropdown_title = f"\n{release_strings.series} Release Highlights\n"
        message = (
            f"In {rsts.release.name}: set the sphinx-design dropdown title to:{dropdown_title}"
        )
        _wait_for_done(message)

        message = (
            f"Review {rsts.release.name} to ensure it is a good reflection of "
            f"what is new in {release_strings.series}."
        )
        _wait_for_done(message)

        message = (
            "Work with the development team to populate the Release "
            f"Highlights dropdown section at the top of {rsts.release.name}."
        )
        _wait_for_done(message)

    else:
        message = (
            "Create a patch dropdown section at the top of "
            f"{rsts.release.name}.\n"
            f"See {rsts.template} for how this should be written."
        )
        _wait_for_done(message)

    if first_in_series:
        # TODO: automate
        message = (
            "Remove the What's New template file.\n"
            f"git rm {rsts.template.absolute()};"
        )
        _wait_for_done(message)

    message = (
        "Commit and push all the What's New changes.\n"
        f'git commit -am "Whats new updates for {release_strings.tag} .";\n'
        f"git push -u origin {working_branch};"
    )
    _wait_for_done(message)

    message = (
        f"Follow the Pull Request process to get {working_branch} "
        f"merged into upstream/{release_strings.branch} .\n"
        "Make sure the documentation is previewed during this process."
    )
    _wait_for_done(message)

    return rsts

def cut_release(
    release_strings: ReleaseStrings, is_release_candidate: bool
) -> None:
    _break_print("The release ...")

    message = (
        "Visit https://github.com/SciTools/iris/releases/new to open "
        "a blank new-release web page."
    )
    _wait_for_done(message)

    message = (
        f"Select {release_strings.branch} as the Target.\n"
        f"Input {release_strings.tag} as the new tag to create, and also as "
        "the Release title."
    )
    _wait_for_done(message)

    message = (
        "Craft an appropriate release description in the main text box.\n"
        "Be sure to mention the What's New entry, conda-forge and PyPI - you "
        "will need to return later to make these into links.\n"
        "Be careful to change the appropriate words if copying from a "
        "previous release description."
    )
    _wait_for_done(message)

    if is_release_candidate:
        message = (
            "This is a release candidate - include the following instructions "
            "for installing with conda or pip:\n"
            f"conda install -c conda-forge/label/rc_iris iris={release_strings.release}\n"
            f"pip install scitools-iris=={release_strings.release}"
        )
        _wait_for_done(message)

        message = (
            "This is a release candidate - tick the box to set this as a "
            "pre-release."
        )
        _wait_for_done(message)

    else:
        message = "Tick the box to set this as the latest release."
        _wait_for_done(message)

    message = "Click: Publish release !"
    _wait_for_done(message)

    message = (
        "The CI will now run against this new tag, including automatically "
        "publishing to PyPI."
    )
    _break_print(message)

    url = "https://github.com/SciTools/iris/actions/workflows/ci-wheels.yml"
    message = (
        f"Visit {url} to monitor the building, testing and publishing of "
        "the Iris sdist and binary wheel to PyPI."
    )
    _wait_for_done(message)

def check_rtd(
    release_strings: ReleaseStrings, is_release_candidate: bool
) -> None:
    _break_print("Read the Docs checks ...")

    message = (
        "Visit https://readthedocs.org/projects/scitools-iris/versions/ and "
        "make sure you are logged in."
    )
    _wait_for_done(message)

    message = f"Set {release_strings.tag} to Active, un-Hidden."
    _wait_for_done(message)

    message = f"Set {release_strings.branch} to Active, Hidden."
    _wait_for_done(message)

    message = (
        "Keep the latest 2 branch doc builds active - those formatted 0.0.x - "
        "deactivate older ones."
    )
    _wait_for_done(message)

    message = (
        f"Visit https://scitools-iris.readthedocs.io/en/{release_strings.tag} "
        "to confirm:\n\n"
        "- The docs have rendered.\n"
        f"- The version badge in the top left reads: {release_strings.tag} .\n"
        "   (this demonstrates that setuptools_scm has worked correctly).\n"
        "- The What's New looks correct.\n"
        f"- {release_strings.tag} is available in RTD's version switcher.\n\n"
        "NOTE: the docs can take several minutes to finish building."
    )
    if not is_release_candidate:
        message += (
            "- Selecting 'stable' in the version switcher also brings up the "
            f"{release_strings.tag} render."
        )
    _wait_for_done(message)

    message = (
        f"Visit https://scitools-iris.readthedocs.io/en/{release_strings.branch} "
        "to confirm:\n\n"
        "- The docs have rendered\n"
        f"- The version badge in the top left includes: {release_strings.branch} .\n"
        f"- {release_strings.branch} is NOT available in RTD's version switcher.\n\n"
        "NOTE: the docs can take several minutes to finish building."
    )
    _wait_for_done(message)

def check_pypi(
    release_strings: ReleaseStrings, is_release_candidate: bool
) -> str:
    _break_print("PyPI checks ...")
    _break_print("If anything goes wrong, manual steps are in the documentation.")

    message = (
        "Confirm that the following URL is correctly populated:\n"
        f"https://pypi.org/project/scitools-iris/{release_strings.release}/"
    )
    _wait_for_done(message)

    message = (
        f"Confirm that {release_strings.release} is at the top of this page:\n"
        "https://pypi.org/project/scitools-iris/#history"
    )
    _wait_for_done(message)

    if is_release_candidate:
        message = (
            f"Confirm that {release_strings.release} is marked as a "
            f"pre-release on this page:\n"
            "https://pypi.org/project/scitools-iris/#history"
        )
    else:
        message = (
            f"Confirm that {release_strings.release} is the tag shown on the "
            "scitools-iris PyPI homepage:\n"
            "https://pypi.org/project/scitools-iris/"
        )
    _wait_for_done(message)

    message = (
        f"Visit the below and click `view hashes` for the Source Distribution"
        f"(`.tar.gz`):\n"
        f"https://pypi.org/project/scitools-iris/{release_strings.release}#files\n"
    )
    sha256 = _get_input(message, "Input the SHA256 hash")

    message = (
        "Confirm that pip install works as expected:\n"
        f"pip install scitools-iris=={release_strings.release};"
    )
    _wait_for_done(message)

    return sha256

def update_conda_forge(
    release_strings: ReleaseStrings, is_release_candidate: bool, sha256: str
) -> None:
    _break_print("conda-forge updates ...")

    if not is_release_candidate:
        message = (
            "NOTE: after several hours conda-forge automation will create a "
            "Pull Request against conda-forge/iris-feedstock (via the "
            "regro-cf-autotick-bot). Quicker to sort it now, manually ..."
        )
        _break_print(message)

    message = (
        "Make sure you have a GitHub fork of:\n"
        "https://github.com/conda-forge/iris-feedstock"
    )
    _wait_for_done(message)

    message = (
        "Make sure you have a local clone of your iris-feedstock fork.\n"
        "`cd` into your clone."
    )
    _wait_for_done(message)

    if is_release_candidate:
        upstream_branch = "release-candidate"
    else:
        upstream_branch = "main"

    # TODO: automate
    message = (
        "Checkout a new branch for the conda-forge changes for this release:\n"
        "git fetch upstream;\n"
        f"git checkout upstream/{upstream_branch} -b {release_strings.tag};\n"
    )
    _wait_for_done(message)

    message = (
        "Update ./recipe/meta.yaml:\n\n"
        f"- The version at the very top of the file: {release_strings.release}\n"
        f"- The sha256 hash: {sha256}\n"
        "- Requirements: align the packages and pins with those in the Iris repo\n"
        "- Maintainers: update with any changes to the dev team\n"
        "- MAKE SURE everything else is correct - plenty of other things "
        "might need one-off changes.\n"
    )
    _wait_for_done(message)

    # TODO: automate
    message = (
        "No other file normally needs changing in iris-feedstock, so push up "
        "the changes to prepare for a Pull Request:\n"
        f'git commit -am "Recipe updates for {release_strings.tag} .";\n'
        f"git push -u origin {release_strings.tag};"
    )
    _wait_for_done(message)

    message = (
        f"Follow the Pull Request process to get {release_strings.tag} branch "
        f"merged into upstream/{upstream_branch} .\n"
        "Specific conda-forge guidance will be automatically given once the "
        "PR is created."
    )
    _wait_for_done(message)

    message = (
        f"Confirm that {release_strings.release} appears in this list:\n"
        "https://anaconda.org/conda-forge/iris/files"
    )
    _wait_for_done(message)

    if not is_release_candidate:
        message = (
            f"Confirm that {release_strings.release} is displayed on this "
            "page as the latest available:\n"
            "https://anaconda.org/conda-forge/iris"
        )
        _wait_for_done(message)

    if is_release_candidate:
        channel_command = " -c conda-forge/label/rc_iris "
    else:
        channel_command = " "
    message = (
        "Confirm that conda (or mamba) install works as expected:\n"
        f"conda create -n tmp_iris{channel_command}iris={release_strings.release};\n"
        f"conda remove -n tmp_iris --all;"
    )
    _wait_for_done(message)

def update_links(release_strings: ReleaseStrings) -> None:
    _break_print("Link updates ...")

    message = (
        "Revisit the GitHub release:\n"
        f"https://github.com/SciTools/iris/releases/tag/{release_strings.tag}\n"
        "You have confirmed that Read the Docs, PyPI and conda-forge have all "
        "updated correctly. Include the following links in the release "
        "notes:\n\n"
        f"https://scitools-iris.readthedocs.io/en/{release_strings.tag}/\n"
        f"https://pypi.org/project/scitools-iris/{release_strings.release}/\n"
        f"https://anaconda.org/conda-forge/iris?version={release_strings.release}\n"
    )
    _wait_for_done(message)

    message = (
        "Update the release page in GitHub discussions, with the above links "
        "and anything else appropriate.\n"
        "https://github.com/SciTools/iris/discussions"
    )
    _wait_for_done(message)

def twitter_announce(
    release_strings: ReleaseStrings, first_in_series: bool
) -> None:
    message = (
        "Announce the release via https://twitter.com/scitools_iris, and any "
        "other appropriate message boards (e.g. Yammer).\n"
        "Any content used for the announcement should be stored in the "
        "SciTools/twitter-scitools-iris GitHub repo.\n"
    )
    if not first_in_series:
        message += (
            f"Consider replying within an existing {release_strings.series} "
            "announcement thread, if appropriate."
        )
    _wait_for_done(message)

def update_citation(
    release_strings: ReleaseStrings, is_release_candidate: bool
) -> None:
    if not is_release_candidate:
        src_dir = Path(__file__).parents[1] / "docs" / "src"
        citation_rst = src_dir / "userguide" / "citation.rst"
        assert citation_rst.is_file()
        message = (
            f"Follow the Pull Request process to update {citation_rst.name} "
            "with the correct dates, DOI and version string for "
            f"{release_strings.tag}.\n"
            f"{citation_rst.absolute()}\n\n"
            f"The PR should target {release_strings.branch} (prior to merge-back)."
        )
        _wait_for_done(message)

def merge_back(
    release_strings: ReleaseStrings, first_in_series: bool, rsts: WhatsNewRsts
) -> None:
    _break_print("Branch merge-back ...")

    merge_commit = (
        "BE SURE TO MERGE VIA A MERGE-COMMIT (not a squash-commit), to "
        "preserve the commit SHA's."
    )

    if first_in_series:
        # TODO: automate

        working_branch = release_strings.branch + ".mergeback"
        _delete_local_branch(working_branch)
        message = (
            "Checkout a local branch from the official ``main`` branch.\n"
            "git fetch upstream;\n"
            f"git checkout upstream/main -b {working_branch};"
        )
        _wait_for_done(message)

        message = (
            f"Merge in the commits from {release_strings.branch}.\n"
            f"{merge_commit}\n"
            f"git merge upstream/{release_strings.branch} --no-ff "
            '-m "Merging release branch into main";'
        )
        _wait_for_done(message)

        message = (
            "Recreate the following files, which are present in ``main``, but "
            f"are currently deleted from {working_branch}:\n"
            f"{rsts.latest.absolute()}\n"
            f"{rsts.template.absolute()}\n"
            "THEN:\n"
            f"git add {rsts.latest.absolute()};\n"
            f"git add {rsts.template.absolute()};\n"
        )
        _wait_for_done(message)

        message = (
            f"In {rsts.index.absolute()}:\n"
            f"Add {rsts.latest.name} to the top of the list of .rst files, "
            f"and set the top include:: to be {rsts.latest.name} ."
        )
        _wait_for_done(message)

        message = (
            "Commit and push all the What's New changes.\n"
            'git commit -am "Restore latest Whats New files.";\n'
            f"git push -u origin {working_branch};"
        )
        _wait_for_done(message)

        message = (
            "Follow the Pull Request process to get "
            f"{working_branch} merged into upstream/main .\n"
            "Make sure the documentation is previewed during this process.\n"
            f"{merge_commit}"
        )
        _wait_for_done(message)

    else:
        message = (
            f"Propose a merge-back from {release_strings.branch} into ``main`` by "
            f"visiting this URL and clicking `Create pull request`:\n"
            f"https://github.com/SciTools/iris/compare/main...{release_strings.branch}\n"
            f"{merge_commit}"
        )
        _wait_for_done(message)
        message = (
            f"Once the pull request is merged ensure that the {release_strings.branch} "
            "release branch is restored.\n"
            "GitHub automation rules may have automatically deleted the release branch."
        )
        _wait_for_done(message)

def main():
    _mark_section(1)
    release_type = get_release_type()

    _mark_section(2)
    release_strings = get_release_tag()

    _mark_section(3)
    is_release_candidate = check_release_candidate(
        release_type,
        release_strings,
    )

    _mark_section(4)
    is_first_in_series = check_first_in_series(
        release_type,
        release_strings,
        is_release_candidate,
    )

    _mark_section(5)
    update_standard_names(
        is_first_in_series,
    )

    _mark_section(6)
    check_deprecations(
        release_type,
    )

    _mark_section(7)
    create_release_branch(
        release_strings,
        is_first_in_series,
    )

    _mark_section(8)
    whats_new_rsts = finalise_whats_new(
        release_type,
        release_strings,
        is_release_candidate,
        is_first_in_series,
    )

    _mark_section(9)
    cut_release(
        release_strings,
        is_release_candidate,
    )

    _mark_section(10)
    check_rtd(
        release_strings,
        is_release_candidate,
    )

    _mark_section(11)
    sha256 = check_pypi(
        release_strings,
        is_release_candidate,
    )

    _mark_section(12)
    update_conda_forge(
        release_strings,
        is_release_candidate,
        sha256,
    )

    _mark_section(13)
    update_links(
        release_strings,
    )

    _mark_section(14)
    twitter_announce(
        release_strings,
        is_first_in_series,
    )

    _mark_section(15)
    update_citation(
        release_strings,
        is_release_candidate,
    )

    _mark_section(16)
    merge_back(
        release_strings,
        is_first_in_series,
        whats_new_rsts,
    )

    _break_print("RELEASE COMPLETE. Congratulations! ðŸŽ‰")

if __name__ == "__main__":
    main()