import glob
import json

import numpy as np
import os
import cv2

from configs.constants import PoseTrack_Keypoint_Pairs, COLOR_DICT, \
    PoseTrack_Official_Keypoint_Pairs, PoseTrack_Official_Keypoint_Ordering, PoseTrack_COCO_Keypoint_Ordering
import logging
import motmetrics as mm

from utils.heatmap import get_max_preds
from utils.keypoints import coco2posetrack_ord
from utils.setup import convert_videos
from shapely import geometry

MIN_SCORE = -9999
MAX_TRACK_ID = 10000

def removeIgnoredPointsRects(rects, polyList):
    ridxs = list(range(len(rects)))
    for ridx in range(len(rects)):
        points = rects[ridx]["annopoints"][0]["point"]
        pidxs = list(range(len(points)))
        for pidx in range(len(points)):
            pt = geometry.Point(points[pidx]["x"][0], points[pidx]["y"][0])
            bIgnore = False
            for poidx in range(len(polyList)):
                poly = polyList[poidx]
                if poly.contains(pt):
                    bIgnore = True
                    break
            if bIgnore:
                pidxs.remove(pidx)
        points = [points[pidx] for pidx in pidxs]
        if len(points) > 0:
            rects[ridx]["annopoints"][0]["point"] = points
        else:
            ridxs.remove(ridx)
    rects = [rects[ridx] for ridx in ridxs]
    return rects

def removeIgnoredPoints(gtFramesAll, prFramesAll):
    imgidxs = []
    for imgidx in range(len(gtFramesAll)):
        if ("ignore_regions" in gtFramesAll[imgidx].keys() and
                len(gtFramesAll[imgidx]["ignore_regions"]) > 0):
            regions = gtFramesAll[imgidx]["ignore_regions"]
            polyList = []
            for ridx in range(len(regions)):
                points = regions[ridx]["point"]
                pointList = []
                for pidx in range(len(points)):
                    pt = geometry.Point(points[pidx]["x"][0], points[pidx]["y"][0])
                    pointList += [pt]
                poly = geometry.Polygon([[p.x, p.y] for p in pointList])
                polyList += [poly]

            rects = prFramesAll[imgidx]["annorect"]
            prFramesAll[imgidx]["annorect"] = removeIgnoredPointsRects(rects, polyList)
            rects = gtFramesAll[imgidx]["annorect"]
            gtFramesAll[imgidx]["annorect"] = removeIgnoredPointsRects(rects, polyList)

    return gtFramesAll, prFramesAll

def rectHasPoints(rect):
    return (("annopoints" in rect.keys()) and
            (len(rect["annopoints"]) > 0 and len(rect["annopoints"][0]) > 0) and
            ("point" in rect["annopoints"][0].keys()))

def removeRectsWithoutPoints(rects):
    idxsPr = []
    for ridxPr in range(len(rects)):
        if rectHasPoints(rects[ridxPr]):
            idxsPr += [ridxPr]
    rects = [rects[ridx] for ridx in idxsPr]
    return rects

def cleanupData(gtFramesAll, prFramesAll):
    # remove all GT frames with empty annorects and remove corresponding entries from predictions
    imgidxs = []
    for imgidx in range(len(gtFramesAll)):
        if len(gtFramesAll[imgidx]["annorect"]) > 0:
            imgidxs += [imgidx]
    gtFramesAll = [gtFramesAll[imgidx] for imgidx in imgidxs]
    prFramesAll = [prFramesAll[imgidx] for imgidx in imgidxs]

    # remove all gt rectangles that do not have annotations
    for imgidx in range(len(gtFramesAll)):
        gtFramesAll[imgidx]["annorect"] = removeRectsWithoutPoints(gtFramesAll[imgidx]["annorect"])
        prFramesAll[imgidx]["annorect"] = removeRectsWithoutPoints(prFramesAll[imgidx]["annorect"])

    return gtFramesAll, prFramesAll

class Joint:
    def __init__(self):
        self.count = 15
        self.right_ankle = 0
        self.right_knee = 1
        self.right_hip = 2
        self.left_hip = 3
        self.left_knee = 4
        self.left_ankle = 5
        self.right_wrist = 6
        self.right_elbow = 7
        self.right_shoulder = 8
        self.left_shoulder = 9
        self.left_elbow = 10
        self.left_wrist = 11
        self.neck = 12
        self.nose = 13
        self.head_top = 14

        self.name = {self.right_ankle: "right_ankle", self.right_knee: "right_knee", self.right_hip: "right_hip",
                     self.right_shoulder: "right_shoulder", self.right_elbow: "right_elbow",
                     self.right_wrist: "right_wrist", self.left_ankle: "left_ankle", self.left_knee: "left_knee",
                     self.left_hip: "left_hip", self.left_shoulder: "left_shoulder", self.left_elbow: "left_elbow",
                     self.left_wrist: "left_wrist", self.neck: "neck", self.nose: "nose", self.head_top: "head_top"}

        self.symmetric_joint = {self.right_ankle: self.left_ankle, self.right_knee: self.left_knee,
                                self.right_hip: self.left_hip, self.right_shoulder: self.left_shoulder,
                                self.right_elbow: self.left_elbow, self.right_wrist: self.left_wrist,
                                self.left_ankle: self.right_ankle, self.left_knee: self.right_knee,
                                self.left_hip: self.right_hip, self.left_shoulder: self.right_shoulder,
                                self.left_elbow: self.right_elbow, self.left_wrist: self.right_wrist, self.neck: -1,
                                self.nose: -1, self.head_top: -1}

def getCum(vals):
    cum = []
    n = -1
    cum += [(vals[[Joint().head_top, Joint().neck, Joint().nose], 0].mean())]
    # cum += [(vals[[Joint().neck],0].mean())]

    cum += [(vals[[Joint().right_shoulder, Joint().left_shoulder], 0].mean())]
    cum += [(vals[[Joint().right_elbow, Joint().left_elbow], 0].mean())]
    cum += [(vals[[Joint().right_wrist, Joint().left_wrist], 0].mean())]
    cum += [(vals[[Joint().right_hip, Joint().left_hip], 0].mean())]
    cum += [(vals[[Joint().right_knee, Joint().left_knee], 0].mean())]
    cum += [(vals[[Joint().right_ankle, Joint().left_ankle], 0].mean())]
    for i in range(Joint().count, len(vals)):
        cum += [vals[i, 0]]
    return cum

def getFormatRow(cum):
    """
        cum  - val list
    """
    row = "&"
    for i in range(len(cum) - 1):
        row += formatCell(cum[i], " &")
    row += formatCell(cum[len(cum) - 1], (" %s" % "\\" + "\\"))
    return row

def formatCell(val, delim):
    return "{:>5}".format("%1.1f" % val) + delim

def getMotHeader():
    strHeader = "&"
    strHeader += " MOTA &"
    strHeader += " MOTA &"
    strHeader += " MOTA &"
    strHeader += " MOTA &"
    strHeader += " MOTA &"
    strHeader += " MOTA &"
    strHeader += " MOTA &"
    strHeader += " MOTA &"
    strHeader += " MOTP &"
    strHeader += " Prec &"
    strHeader += " Rec  %s\n" % ("\\" + "\\")
    strHeader += "&"
    strHeader += " Head &"
    strHeader += " Shou &"
    strHeader += " Elb  &"
    strHeader += " Wri  &"
    strHeader += " Hip  &"
    strHeader += " Knee &"
    strHeader += " Ankl &"
    strHeader += " Total&"
    strHeader += " Total&"
    strHeader += " Total&"
    strHeader += " Total%s" % ("\\" + "\\")

    return strHeader

def printTable(vals, motHeader=False):
    cum = getCum(vals)
    row = getFormatRow(cum)
    if motHeader:
        header = getMotHeader()
    else:
        header = getHeader()
        # header = ["Head", "Shou", "Elb", "Wri", "Hip", "Knee", "Ankl", "# Total"]

    # result = []
    # for item in cum:
    # result.append(item)
    # table = tabulate([result], tablefmt="pipe", headers=header, numalign="left",)
    logger = logging.getLogger(__name__)
    # print()
    # logger.info(f"=> Result Table: \n" + colored(table, "magenta"))
    logger.info(header)
    logger.info(row)
    # return header+"\n", row+"\n"
    return cum

def getHeader():
    strHeader = "&"
    strHeader += " Head &"
    strHeader += " Shou &"
    strHeader += " Elb  &"
    strHeader += " Wri  &"
    strHeader += " Hip  &"
    strHeader += " Knee &"
    strHeader += " Ankl &"
    strHeader += " Total%s" % ("\\" + "\\")
    return strHeader

def evaluate_tracking(gtFramesAll, prFramesAll, outputDir, saveAll=True, saveSeq=False):
    distThresh = 0.5
    # assign predicted poses to GT poses
    #    _, _, _, motAll = eval_helpers.assignGTmulti(gtFramesAll, prFramesAll, distThresh)
    _, _, _, motAll = assignGTmulti(gtFramesAll, prFramesAll, distThresh)

    # compute MOT metrics per part
    metricsAll = computeMetrics(gtFramesAll, motAll, outputDir, saveAll, saveSeq)

    return metricsAll

def save_fusion_images(dir_out, img, name='', heatmaps=None):
    if not os.path.isdir(dir_out):
        os.makedirs(dir_out)

    img = (img - img.min()) / (img.max() - img.min()) * 255
    for i in range(len(PoseTrack_COCO_Keypoint_Ordering)):
        k = PoseTrack_COCO_Keypoint_Ordering[i]
        heatmap = heatmaps[i]
        heatmap = np.clip(heatmap * 255, 0, 255).astype(np.uint8)
        colored_heatmap = cv2.applyColorMap(heatmap, cv2.COLORMAP_BONE)
        colored_heatmap = cv2.resize(colored_heatmap, (img.shape[1], img.shape[0]))
        img_heatmap = img * 0.3 + colored_heatmap * 0.7
        cv2.imwrite(os.path.join(dir_out, '{}{}_img_heatmap.png'.format(name, k)), img_heatmap)

def save_f_inv_images(dir_out, img, name='', heatmaps=None):
    if not os.path.isdir(dir_out):
        os.makedirs(dir_out)

    img = (img - img.min()) / (img.max() - img.min()) * 255
    for i in range(len(PoseTrack_COCO_Keypoint_Ordering)):
        k = PoseTrack_COCO_Keypoint_Ordering[i]
        heatmap = heatmaps[i]
        heatmap = np.clip(heatmap * -255, 0, 255).astype(np.uint8)
        colored_heatmap = cv2.applyColorMap(heatmap, cv2.COLORMAP_BONE)
        colored_heatmap = cv2.resize(colored_heatmap, (img.shape[1], img.shape[0]))
        img_heatmap = img * 0.3 + colored_heatmap * 0.7
        cv2.imwrite(os.path.join(dir_out, '{}{}_img_heatmap.png'.format(name, k)), img_heatmap)

def save_inv_result_images(dir_out, img, pose, vis, name='', heatmaps=None, label=None):
    if not os.path.isdir(dir_out):
        os.makedirs(dir_out)

    img = (img - img.min()) / (img.max() - img.min()) * 255
    img = img[:,:,::-1]
    cv2.imwrite(os.path.join(dir_out, '{}img.png'.format(name)), img)
    img_pose = img.copy()
    pose = pose * [img.shape[0] / heatmaps.shape[1], img.shape[1] / heatmaps.shape[2]]
    for i in range(len(PoseTrack_Keypoint_Pairs)):
        c, p = PoseTrack_Keypoint_Pairs[i][0], PoseTrack_Keypoint_Pairs[i][1]
        if vis[c] < 0.1 or vis[p] < 0.1:
            continue
        child = tuple(pose[c].astype(int))
        parent = tuple(pose[p].astype(int))
        color = COLOR_DICT[PoseTrack_Keypoint_Pairs[i][2]]
        cv2.line(img_pose, child, parent, color, 4)

    if label is not None:
        for i in range(len(pose)):
            coord = pose[i]
            cv2.circle(img_pose, coord.astype(int), 3, (0, 255 * label[i] if label is not None else 0, 255), -1)
    cv2.imwrite(os.path.join(dir_out, '{}img_pose.png'.format(name)), img_pose)

    if heatmaps is not None:
        heatmap = np.sum(heatmaps, axis=0)
        heatmap = np.clip(heatmap * -255, 0, 255).astype(np.uint8)
        colored_heatmap = cv2.applyColorMap(heatmap, cv2.COLORMAP_BONE)
        colored_heatmap = cv2.resize(colored_heatmap, (img.shape[1], img.shape[0]))
        cv2.imwrite(os.path.join(dir_out, '{}heatmap.png'.format(name)), colored_heatmap)
        img_heatmap = img_pose * 0.7 + colored_heatmap * 0.3
        cv2.imwrite(os.path.join(dir_out, '{}img_heatmap.png'.format(name)), img_heatmap)

def save_result_images(dir_out, img, pose, vis, name='', heatmaps=None, label=None):
    if not os.path.isdir(dir_out):
        os.makedirs(dir_out)

    img = (img - img.min()) / (img.max() - img.min()) * 255
    img = img[:,:,::-1]
    cv2.imwrite(os.path.join(dir_out, '{}img.png'.format(name)), img)
    img_pose = img.copy()
    pose = pose * [img.shape[0] / heatmaps.shape[1], img.shape[1] / heatmaps.shape[2]]
    for i in range(len(PoseTrack_Keypoint_Pairs)):
        c, p = PoseTrack_Keypoint_Pairs[i][0], PoseTrack_Keypoint_Pairs[i][1]
        if vis[c] < 0.1 or vis[p] < 0.1:
            continue
        child = tuple(pose[c].astype(int))
        parent = tuple(pose[p].astype(int))
        color = COLOR_DICT[PoseTrack_Keypoint_Pairs[i][2]]
        cv2.line(img_pose, child, parent, color, 4)

    if label is not None:
        for i in range(len(pose)):
            coord = pose[i]
            cv2.circle(img_pose, coord.astype(int), 3, (0, 255 * label[i] if label is not None else 0, 255), -1)
    cv2.imwrite(os.path.join(dir_out, '{}img_pose.png'.format(name)), img_pose)

    if heatmaps is not None:
        heatmap = np.sum(heatmaps, axis=0)
        heatmap = np.clip(heatmap * 255, 0, 255).astype(np.uint8)
        colored_heatmap = cv2.applyColorMap(heatmap, cv2.COLORMAP_BONE)
        colored_heatmap = cv2.resize(colored_heatmap, (img.shape[1], img.shape[0]))
        cv2.imwrite(os.path.join(dir_out, '{}heatmap.png'.format(name)), colored_heatmap)
        img_heatmap = img_pose * 0.7 + colored_heatmap * 0.3
        cv2.imwrite(os.path.join(dir_out, '{}img_heatmap.png'.format(name)), img_heatmap)

def save_seg_images(dir_out, img, heatmaps=None, name=''):
    img = (img - img.min()) / (img.max() - img.min()) * 255
    img = img[:,:,::-1]

    if heatmaps is not None:
        heatmaps = np.asarray(heatmaps, dtype=np.uint8)
        cv2.imwrite(os.path.join(dir_out, '{}.png'.format(name)), heatmaps)
        img_heatmap = img * 0.7 + heatmaps * 0.3
        cv2.imwrite(os.path.join(dir_out, '{}img.png'.format(name)), img_heatmap)

def calc_dists(preds, target, normalize):
    preds = preds.astype(np.float32)
    target = target.astype(np.float32)
    dists = np.zeros((preds.shape[1], preds.shape[0]))
    for n in range(preds.shape[0]):  # batch num
        for c in range(preds.shape[1]):  # keypoint type
            if target[n, c, 0] > 1 and target[n, c, 1] > 1:
                normed_preds = preds[n, c, :] / normalize[n]
                normed_targets = target[n, c, :] / normalize[n]
                dists[c, n] = np.linalg.norm(normed_preds - normed_targets)  # Euclidean distance
            else:
                dists[c, n] = -1
    return dists

def dist_acc(dists, thr=0.5, percentage=True):
    """ Return percentage below threshold while ignoring values with a -1 """
    dist_cal = np.not_equal(dists, -1)
    num_dist_cal = dist_cal.sum()
    if num_dist_cal > 0:
        less_thr_count = np.less(dists[dist_cal], thr).sum() * 1.0
        if percentage:
            return less_thr_count / num_dist_cal
        else:
            return less_thr_count, num_dist_cal  # less_thr_count = match  / num_dist_cal （val）
    else:
        if percentage:
            return -1
        else:
            return -1, -1

def accuracy(output, target, hm_type='gaussian', thr=0.5):
    """
    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
    """
    idx = list(range(output.shape[1]))
    norm = 1.0
    if hm_type == 'gaussian':
        pred, _ = get_max_preds(output)
        target, _ = get_max_preds(target)
        h = output.shape[2]
        w = output.shape[3]
        norm = np.ones((pred.shape[0], 2)) * np.array([h, w]) / 10
    dists = calc_dists(pred, target, norm)  # use a fixed length as a measure rather than the length of body parts

    acc = np.zeros((len(idx) + 1))
    avg_acc = 0
    cnt = 0

    for i in range(len(idx)):
        acc[i + 1] = dist_acc(dists[idx[i]], thr)
        if acc[i + 1] >= 0:
            avg_acc = avg_acc + acc[i + 1]
            cnt += 1

    avg_acc = avg_acc / cnt if cnt != 0 else 0
    if cnt != 0:
        acc[0] = avg_acc

    return acc, avg_acc, cnt, pred

def pckh5(output, target, hm_type='gaussian', thr=0.5):
    """
    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
    """
    idx = list(range(output.shape[1]))
    norm = 1.0
    if hm_type == 'gaussian':
        pred, _ = get_max_preds(output)
        target, _ = get_max_preds(target)
        h = output.shape[2]
        w = output.shape[3]
        norm = np.ones((pred.shape[0], 2)) * np.array([h, w]) / 10
    dists = calc_dists(pred, target, norm)  # use a fixed length as a measure rather than the length of body parts

    acc = np.zeros((len(idx) + 1))
    avg_acc = 0
    cnt = 0

    for i in range(len(idx)):
        acc[i + 1] = dist_acc(dists[idx[i]], thr)
        if acc[i + 1] >= 0:
            avg_acc = avg_acc + acc[i + 1]
            cnt += 1

    avg_acc = avg_acc / cnt if cnt != 0 else 0
    if cnt != 0:
        acc[0] = avg_acc

    return acc, avg_acc, cnt, pred

def get_point_gt_by_id(points, pidx):
    point = []
    for i in range(len(points)):
        if points[i]["id"] != None and points[i]["id"][0] == pidx:  # if joint id matches
            point = points[i]
            break

    return point

def get_head_size(x1, y1, x2, y2):
    headSize = 0.6 * np.linalg.norm(np.subtract([x2, y2], [x1, y1]))
    return headSize

def assignGTmulti(gtFrames, prFrames, distThresh):
    assert (len(gtFrames) == len(prFrames))

    nJoints = Joint().count
    # part detection scores
    scoresAll = {}
    # positive / negative labels
    labelsAll = {}
    # number of annotated GT joints per image
    nGTall = np.zeros([nJoints, len(gtFrames)])
    for pidx in range(nJoints):
        scoresAll[pidx] = {}
        labelsAll[pidx] = {}
        for imgidx in range(len(gtFrames)):
            scoresAll[pidx][imgidx] = np.zeros([0, 0], dtype=np.float32)
            labelsAll[pidx][imgidx] = np.zeros([0, 0], dtype=np.int8)

    # GT track IDs
    trackidxGT = []

    # prediction track IDs
    trackidxPr = []

    # number of GT poses
    nGTPeople = np.zeros((len(gtFrames), 1))
    # number of predicted poses
    nPrPeople = np.zeros((len(gtFrames), 1))

    # container to save info for computing MOT metrics
    motAll = {}

    for imgidx in range(len(gtFrames)):
        # distance between predicted and GT joints
        dist = np.full((len(prFrames[imgidx]["annorect"]), len(gtFrames[imgidx]["annorect"]), nJoints), np.inf)
        # score of the predicted joint
        score = np.full((len(prFrames[imgidx]["annorect"]), nJoints), np.nan)
        # body joint prediction exist
        hasPr = np.zeros((len(prFrames[imgidx]["annorect"]), nJoints), dtype=bool)
        # body joint is annotated
        hasGT = np.zeros((len(gtFrames[imgidx]["annorect"]), nJoints), dtype=bool)

        trackidxGT = []
        trackidxPr = []
        idxsPr = []
        for ridxPr in range(len(prFrames[imgidx]["annorect"])):
            if (("annopoints" in prFrames[imgidx]["annorect"][ridxPr].keys()) and
                    ("point" in prFrames[imgidx]["annorect"][ridxPr]["annopoints"][0].keys())):
                idxsPr += [ridxPr]
        prFrames[imgidx]["annorect"] = [prFrames[imgidx]["annorect"][ridx] for ridx in idxsPr]

        nPrPeople[imgidx, 0] = len(prFrames[imgidx]["annorect"])
        nGTPeople[imgidx, 0] = len(gtFrames[imgidx]["annorect"])
        # iterate over GT poses
        for ridxGT in range(len(gtFrames[imgidx]["annorect"])):
            # GT pose
            rectGT = gtFrames[imgidx]["annorect"][ridxGT]
            if "track_id" in rectGT.keys():
                trackidxGT += [rectGT["track_id"][0]]
            pointsGT = []
            if len(rectGT["annopoints"]) > 0:
                pointsGT = rectGT["annopoints"][0]["point"]
            # iterate over all possible body joints
            for i in range(nJoints):
                # GT joint in LSP format
                ppGT = get_point_gt_by_id(pointsGT, i)
                if len(ppGT) > 0:
                    hasGT[ridxGT, i] = True

        # iterate over predicted poses
        for ridxPr in range(len(prFrames[imgidx]["annorect"])):
            # predicted pose
            rectPr = prFrames[imgidx]["annorect"][ridxPr]
            if "track_id" in rectPr.keys():
                trackidxPr += [rectPr["track_id"][0]]
            pointsPr = rectPr["annopoints"][0]["point"]
            for i in range(nJoints):
                # predicted joint in LSP format
                ppPr = get_point_gt_by_id(pointsPr, i)
                if len(ppPr) > 0:
                    if not ("score" in ppPr.keys()):
                        # use minimum score if predicted score is missing
                        if imgidx == 0:
                            print('WARNING: prediction score is missing. Setting fallback score={}'.format(MIN_SCORE))
                        score[ridxPr, i] = MIN_SCORE
                    else:
                        score[ridxPr, i] = ppPr["score"][0]
                    hasPr[ridxPr, i] = True

        if len(prFrames[imgidx]["annorect"]) and len(gtFrames[imgidx]["annorect"]):
            # predictions and GT are present
            # iterate over GT poses
            for ridxGT in range(len(gtFrames[imgidx]["annorect"])):
                # GT pose
                rectGT = gtFrames[imgidx]["annorect"][ridxGT]
                # compute reference distance as head size
                headSize = get_head_size(rectGT["x1"][0], rectGT["y1"][0],
                                         rectGT["x2"][0], rectGT["y2"][0])
                pointsGT = []
                if len(rectGT["annopoints"]) > 0:
                    pointsGT = rectGT["annopoints"][0]["point"]
                # iterate over predicted poses
                for ridxPr in range(len(prFrames[imgidx]["annorect"])):
                    # predicted pose
                    rectPr = prFrames[imgidx]["annorect"][ridxPr]
                    pointsPr = rectPr["annopoints"][0]["point"]

                    # iterate over all possible body joints
                    for i in range(nJoints):
                        # GT joint
                        ppGT = get_point_gt_by_id(pointsGT, i)
                        # predicted joint
                        ppPr = get_point_gt_by_id(pointsPr, i)
                        # compute distance between predicted and GT joint locations
                        if hasPr[ridxPr, i] and hasGT[ridxGT, i]:
                            pointGT = [ppGT["x"][0], ppGT["y"][0]]
                            pointPr = [ppPr["x"][0], ppPr["y"][0]]
                            dist[ridxPr, ridxGT, i] = np.linalg.norm(np.subtract(pointGT, pointPr)) / headSize

            dist = np.array(dist)
            hasGT = np.array(hasGT)

            # number of annotated joints
            nGTp = np.sum(hasGT, axis=1)
            match = dist <= distThresh
            pck = 1.0 * np.sum(match, axis=2)
            for i in range(hasPr.shape[0]):
                for j in range(hasGT.shape[0]):
                    if nGTp[j] > 0:
                        pck[i, j] = pck[i, j] / nGTp[j]

            # preserve best GT match only
            idx = np.argmax(pck, axis=1)
            val = np.max(pck, axis=1)
            for ridxPr in range(pck.shape[0]):
                for ridxGT in range(pck.shape[1]):
                    if ridxGT != idx[ridxPr]:
                        pck[ridxPr, ridxGT] = 0
            prToGT = np.argmax(pck, axis=0)
            val = np.max(pck, axis=0)
            prToGT[val == 0] = -1

            # info to compute MOT metrics
            mot = {}
            for i in range(nJoints):
                mot[i] = {}

            for i in range(nJoints):
                ridxsGT = np.argwhere(hasGT[:, i] == True)
                ridxsGT = ridxsGT.flatten().tolist()
                ridxsPr = np.argwhere(hasPr[:, i] == True)
                ridxsPr = ridxsPr.flatten().tolist()
                mot[i]["trackidxGT"] = [trackidxGT[idx] for idx in ridxsGT]
                mot[i]["trackidxPr"] = [trackidxPr[idx] for idx in ridxsPr]
                mot[i]["ridxsGT"] = np.array(ridxsGT)
                mot[i]["ridxsPr"] = np.array(ridxsPr)
                mot[i]["dist"] = np.full((len(ridxsGT), len(ridxsPr)), np.nan)
                for iPr in range(len(ridxsPr)):
                    for iGT in range(len(ridxsGT)):
                        if match[ridxsPr[iPr], ridxsGT[iGT], i]:
                            mot[i]["dist"][iGT, iPr] = dist[ridxsPr[iPr], ridxsGT[iGT], i]

            # assign predicted poses to GT poses
            for ridxPr in range(hasPr.shape[0]):
                if ridxPr in prToGT:  # pose matches to GT
                    # GT pose that matches the predicted pose
                    ridxGT = np.argwhere(prToGT == ridxPr)
                    assert (ridxGT.size == 1)
                    ridxGT = ridxGT[0, 0]
                    s = score[ridxPr, :]
                    m = np.squeeze(match[ridxPr, ridxGT, :])
                    hp = hasPr[ridxPr, :]
                    for i in range(len(hp)):
                        if hp[i]:
                            scoresAll[i][imgidx] = np.append(scoresAll[i][imgidx], s[i])
                            labelsAll[i][imgidx] = np.append(labelsAll[i][imgidx], m[i])

                else:  # no matching to GT
                    s = score[ridxPr, :]
                    m = np.zeros([match.shape[2], 1], dtype=bool)
                    hp = hasPr[ridxPr, :]
                    for i in range(len(hp)):
                        if hp[i]:
                            scoresAll[i][imgidx] = np.append(scoresAll[i][imgidx], s[i])
                            labelsAll[i][imgidx] = np.append(labelsAll[i][imgidx], m[i])
        else:
            if not len(gtFrames[imgidx]["annorect"]):
                # No GT available. All predictions are false positives
                for ridxPr in range(hasPr.shape[0]):
                    s = score[ridxPr, :]
                    m = np.zeros([nJoints, 1], dtype=bool)
                    hp = hasPr[ridxPr, :]
                    for i in range(len(hp)):
                        if hp[i]:
                            scoresAll[i][imgidx] = np.append(scoresAll[i][imgidx], s[i])
                            labelsAll[i][imgidx] = np.append(labelsAll[i][imgidx], m[i])
            mot = {}
            for i in range(nJoints):
                mot[i] = {}
            for i in range(nJoints):
                ridxsGT = [0]
                ridxsPr = [0]
                mot[i]["trackidxGT"] = [0]
                mot[i]["trackidxPr"] = [0]
                mot[i]["ridxsGT"] = np.array(ridxsGT)
                mot[i]["ridxsPr"] = np.array(ridxsPr)
                mot[i]["dist"] = np.full((len(ridxsGT), len(ridxsPr)), np.nan)

        # save number of GT joints
        for ridxGT in range(hasGT.shape[0]):
            hg = hasGT[ridxGT, :]
            for i in range(len(hg)):
                nGTall[i, imgidx] += hg[i]

        motAll[imgidx] = mot

    return scoresAll, labelsAll, nGTall, motAll

# compute recall/precision curve (RPC) values
def compute_rpc(scores, labels, totalPos):
    precision = np.zeros(len(scores))
    recall = np.zeros(len(scores))
    npos = 0

    idxsSort = np.array(scores).argsort()[::-1]
    labelsSort = labels[idxsSort]

    for sidx in range(len(idxsSort)):
        if labelsSort[sidx] == 1:
            npos += 1
        # recall: how many true positives were found out of the total number of positives?
        recall[sidx] = 1.0 * npos / totalPos
        # precision: how many true positives were found out of the total number of samples?
        precision[sidx] = 1.0 * npos / (sidx + 1)

    return precision, recall, idxsSort

def compute_metrics(scoresAll, labelsAll, nGTall):
    apAll = np.zeros((nGTall.shape[0] + 1, 1))
    recAll = np.zeros((nGTall.shape[0] + 1, 1))
    preAll = np.zeros((nGTall.shape[0] + 1, 1))
    # iterate over joints
    for j in range(nGTall.shape[0]):
        scores = np.zeros([0, 0], dtype=np.float32)
        labels = np.zeros([0, 0], dtype=np.int8)
        # iterate over images
        for imgidx in range(nGTall.shape[1]):
            scores = np.append(scores, scoresAll[j][imgidx])
            labels = np.append(labels, labelsAll[j][imgidx])
        # compute recall/precision values
        nGT = sum(nGTall[j, :])
        precision, recall, scoresSortedIdxs = compute_rpc(scores, labels, nGT)
        if len(precision) > 0:
            apAll[j] = vocap(recall, precision) * 100
            preAll[j] = precision[len(precision) - 1] * 100
            recAll[j] = recall[len(recall) - 1] * 100
    idxs = np.argwhere(~np.isnan(apAll[:nGTall.shape[0], 0]))
    apAll[nGTall.shape[0]] = apAll[idxs, 0].mean()
    idxs = np.argwhere(~np.isnan(recAll[:nGTall.shape[0], 0]))
    recAll[nGTall.shape[0]] = recAll[idxs, 0].mean()
    idxs = np.argwhere(~np.isnan(preAll[:nGTall.shape[0], 0]))
    preAll[nGTall.shape[0]] = preAll[idxs, 0].mean()

    return apAll, preAll, recAll

# compute Average Precision using recall/precision values
def vocap(rec, prec):
    mpre = np.zeros([1, 2 + len(prec)])
    mpre[0, 1:len(prec) + 1] = prec
    mrec = np.zeros([1, 2 + len(rec)])
    mrec[0, 1:len(rec) + 1] = rec
    mrec[0, len(rec) + 1] = 1.0

    for i in range(mpre.size - 2, -1, -1):
        mpre[0, i] = max(mpre[0, i], mpre[0, i + 1])

    i = np.argwhere(~np.equal(mrec[0, 1:], mrec[0, :mrec.shape[1] - 1])) + 1
    i = i.flatten()

    # compute area under the curve
    ap = np.sum(np.multiply(np.subtract(mrec[0, i], mrec[0, i - 1]), mpre[0, i]))

    return ap

def evaluate_ap(gtFramesAll, prFramesAll):
    distThresh = 0.5

    # assign predicted poses to GT poses
    scoresAll, labelsAll, nGTall, _ = assignGTmulti(gtFramesAll, prFramesAll, distThresh)

    # compute average precision (AP), precision and recall per part
    apAll, preAll, recAll = compute_metrics(scoresAll, labelsAll, nGTall)

    return apAll, preAll, recAll

def convert_data_to_annorect_struct(poses, tracks, boxes, **kwargs):
    """
            Args:
                boxes (np.ndarray): Nx5 size matrix with boxes on this frame
                poses (list of np.ndarray): N length list with each element as 4x17 array
                tracks (list): N length list with track ID for each box/pose
    """
    num_dets = len(poses)
    annorect = []

    eval_tracking = kwargs.get("eval_tracking", False)
    tracking_threshold = kwargs.get("tracking_threshold", 0)
    for j in range(num_dets):
        score = boxes[j][0, 5]
        if eval_tracking and score > tracking_threshold:
            continue

        point = coco2posetrack_ord(poses[j], global_score=score)  # here poses 4*17
        annorect.append({'annopoints': [{'point': point}],
                         'score': [float(score)],
                         'track_id': [tracks[j]]})
    if num_dets == 0:
        annorect.append({
            'annopoints': [{'point': [{
                'id': [0],
                'x': [0],
                'y': [0],
                'score': [-100.0],
            }]}],
            'score': [0],
            'track_id': [0]})
    return annorect

def process_arguments(argv):
    mode = 'multi'

    if len(argv) > 3:
        mode = str.lower(argv[3])
    elif len(argv) < 3 or len(argv) > 4:
        help()

    gt_file = argv[1]
    pred_file = argv[2]

    if not os.path.exists(gt_file):
        help('Given ground truth directory does not exist!\n')

    if not os.path.exists(pred_file):
        help('Given prediction directory does not exist!\n')

    return gt_file, pred_file, mode

def load_data_dir(argv):
    gt_dir, pred_dir, mode = process_arguments(argv)
    if not os.path.exists(gt_dir):
        help('Given GT directory ' + gt_dir + ' does not exist!\n')
    if not os.path.exists(pred_dir):
        help('Given prediction directory ' + pred_dir + ' does not exist!\n')
    filenames = glob.glob(gt_dir + "/*.json")
    gtFramesAll = []
    prFramesAll = []

    for i in range(len(filenames)):
        # load each annotation json file
        # print('GT:')
        with open(filenames[i]) as data_file:
            data = json.load(data_file)
        if not "annolist" in data:
            data = convert_videos(data)[0]
        gt = data["annolist"]
        for imgidx in range(len(gt)):
            gt[imgidx]["seq_id"] = i
            gt[imgidx]["seq_name"] = os.path.basename(filenames[i]).split('.')[0]
            for ridxGT in range(len(gt[imgidx]["annorect"])):
                if "track_id" in gt[imgidx]["annorect"][ridxGT].keys():
                    # adjust track_ids to make them unique across all sequences
                    #                print(os.path.basename(filenames[i]).split('.')[0])
                    #                print(gt[imgidx]["annorect"][ridxGT]["track_id"][0])
                    assert (gt[imgidx]["annorect"][ridxGT]["track_id"][0] < MAX_TRACK_ID)
                    gt[imgidx]["annorect"][ridxGT]["track_id"][0] += i * MAX_TRACK_ID
        gtFramesAll += gt
        gtBasename = os.path.basename(filenames[i])
        # predFilename = pred_dir + gtBasename
        predFilename = os.path.join(pred_dir, gtBasename)
        # print(xy)

        if not os.path.exists(predFilename):
            raise IOError('Prediction file ' + predFilename + ' does not exist')

        # load predictions
        #    print('PRED:')
        with open(predFilename) as data_file:
            data = json.load(data_file)
        if not "annolist" in data:
            data = convert_videos(data)[0]
        pr = data["annolist"]
        if len(pr) != len(gt):
            raise Exception('# prediction frames %d != # GT frames %d for %s' % (len(pr), len(gt), predFilename))
        for imgidx in range(len(pr)):
            for ridxPr in range(len(pr[imgidx]["annorect"])):
                if "track_id" in pr[imgidx]["annorect"][ridxPr].keys():
                    # adjust track_ids to make them unique across all sequences
                    #                print(os.path.basename(filenames[i]).split('.')[0])
                    #                print(pr[imgidx]["annorect"][ridxPr]["track_id"][0])
                    assert (pr[imgidx]["annorect"][ridxPr]["track_id"][0] < MAX_TRACK_ID)
                    pr[imgidx]["annorect"][ridxPr]["track_id"][0] += i * MAX_TRACK_ID
        prFramesAll += pr
    #    print(xy)

    gtFramesAll, prFramesAll = cleanupData(gtFramesAll, prFramesAll)

    gtFramesAll, prFramesAll = removeIgnoredPoints(gtFramesAll, prFramesAll)
    return gtFramesAll, prFramesAll

def evaluate(gtdir, preddir, eval_pose=True, eval_track=True,
             eval_upper_bound=False):
    logger = logging.getLogger(__name__)
    gtFramesAll, prFramesAll = load_data_dir(['', gtdir, preddir])

    logger.info('# gt frames  : {}'.format(str(len(gtFramesAll))))
    logger.info('# pred frames: {}'.format(str(len(prFramesAll))))

    apAll = np.full((Joint().count + 1, 1), np.nan)
    preAll = np.full((Joint().count + 1, 1), np.nan)
    recAll = np.full((Joint().count + 1, 1), np.nan)
    cum = None
    track_cum = None
    if eval_pose:
        apAll, preAll, recAll = evaluate_ap(gtFramesAll, prFramesAll)

    logger.info('Average Precision (AP) metric:')
    # printTable(apAll)
    cum = printTable(apAll)

    metrics = np.full((Joint().count + 4, 1), np.nan)

    if eval_track:
        # print(xy)
        metricsAll = evaluate_tracking(
            gtFramesAll, prFramesAll, eval_upper_bound)

        for i in range(Joint().count + 1):
            metrics[i, 0] = metricsAll['mota'][0, i]
        metrics[Joint().count + 1, 0] = metricsAll['motp'][0, Joint().count]
        metrics[Joint().count + 2, 0] = metricsAll['pre'][0, Joint().count]
        metrics[Joint().count + 3, 0] = metricsAll['rec'][0, Joint().count]
        logger.info('Multiple Object Tracking (MOT) mmetrics:')
        # print('Multiple Object Tracking (MOT) mmetrics:')
        track_cum = printTable(metrics, motHeader=True)
    # return (apAll, preAll, recAll), metrics
    # print(xy)
    return cum, track_cum

def computeMetrics(gtFramesAll, motAll, outputDir, bSaveAll, bSaveSeq):

    assert(len(gtFramesAll) == len(motAll))
    logger = logging.getLogger(__name__)

    nJoints = Joint().count
    seqidxs = []
    for imgidx in range(len(gtFramesAll)):
        seqidxs += [gtFramesAll[imgidx]["seq_id"]]
    seqidxs = np.array(seqidxs)

    seqidxsUniq = np.unique(seqidxs)

    # intermediate metrics
    metricsMidNames = ['num_misses', 'num_switches', 'num_false_positives', 'num_objects', 'num_detections']

    # final metrics computed from intermediate metrics
    metricsFinNames = ['mota','motp','pre','rec']

    # initialize intermediate metrics
    metricsMidAll = {}
    for name in metricsMidNames:
        metricsMidAll[name] = np.zeros([1,nJoints])
    metricsMidAll['sumD'] = np.zeros([1,nJoints])

    # initialize final metrics
    metricsFinAll = {}
    for name in metricsFinNames:
        metricsFinAll[name] = np.zeros([1,nJoints+1])

    # create metrics
    mh = mm.metrics.create()

    imgidxfirst = 0
    # iterate over tracking sequences
    # seqidxsUniq = seqidxsUniq[:20]
    nSeq = len(seqidxsUniq)

    # initialize per-sequence metrics
    metricsSeqAll = {}
    for si in range(nSeq):
        metricsSeqAll[si] = {}
        for name in metricsFinNames:
            metricsSeqAll[si][name] = np.zeros([1,nJoints+1])

    names = Joint().name
    names['15'] = 'total'

    for si in range(nSeq):
    #for si in range(5):
        logger.info("seqidx: %d/%d" % (si+1,nSeq))

        # init per-joint metrics accumulator
        accAll = {}
        for i in range(nJoints):
            accAll[i] = mm.MOTAccumulator(auto_id=True)

        # extract frames IDs for the sequence
        imgidxs = np.argwhere(seqidxs == seqidxsUniq[si])
        imgidxs = imgidxs[:-1].copy()
        seqName = gtFramesAll[imgidxs[0,0]]["seq_name"]
        logger.info(seqName)
        # create an accumulator that will be updated during each frame
        # iterate over frames
        for j in range(len(imgidxs)):
            imgidx = imgidxs[j,0]
            # iterate over joints
            for i in range(nJoints):
                # GT tracking ID
                trackidxGT = motAll[imgidx][i]["trackidxGT"]
                # prediction tracking ID
                trackidxPr = motAll[imgidx][i]["trackidxPr"]
                # distance GT <-> pred part to compute MOT metrics
                # 'NaN' means force no match
                dist = motAll[imgidx][i]["dist"]
                # Call update once per frame
                accAll[i].update(
                    trackidxGT,                 # Ground truth objects in this frame
                    trackidxPr,                 # Detector hypotheses in this frame
                    dist                        # Distances from objects to hypotheses
                )

        # compute intermediate metrics per joint per sequence
        for i in range(nJoints):
            metricsMid = mh.compute(accAll[i], metrics=metricsMidNames, return_dataframe=False, name='acc')
            for name in metricsMidNames:
                metricsMidAll[name][0,i] += metricsMid[name]
            s = accAll[i].events['D'].sum()
            if (np.isnan(s)):
                s = 0
            metricsMidAll['sumD'][0,i] += s

#        if (bSaveSeq):
        if False:
            # compute metrics per joint per sequence
            for i in range(nJoints):
                metricsMid = mh.compute(accAll[i], metrics=metricsMidNames, return_dataframe=False, name='acc')

                # compute final metrics per sequence
                if (metricsMid['num_objects'] > 0):
                    numObj = metricsMid['num_objects']
                else:
                    numObj = np.nan
                numFP = metricsMid['num_false_positives']
                metricsSeqAll[si]['mota'][0,i] = 100*(1. - 1.*(metricsMid['num_misses'] +
                                                    metricsMid['num_switches'] +
                                                    numFP) /
                                                    numObj)
                numDet = metricsMid['num_detections']
                s = accAll[i].events['D'].sum()
                if (numDet == 0 or np.isnan(s)):
                    metricsSeqAll[si]['motp'][0,i] = 0.0
                else:
                    metricsSeqAll[si]['motp'][0,i] = 100*(1. - (1.*s / numDet))
                if (numFP+numDet > 0):
                    totalDet = numFP+numDet
                else:
                    totalDet = np.nan
                metricsSeqAll[si]['pre'][0,i]  = 100*(1.*numDet /
                                                totalDet)
                metricsSeqAll[si]['rec'][0,i]  = 100*(1.*numDet /
                                        numObj)

            # average metrics over all joints per sequence
            idxs = np.argwhere(~np.isnan(metricsSeqAll[si]['mota'][0,:nJoints]))
            metricsSeqAll[si]['mota'][0,nJoints] = metricsSeqAll[si]['mota'][0,idxs].mean()
            idxs = np.argwhere(~np.isnan(metricsSeqAll[si]['motp'][0,:nJoints]))
            metricsSeqAll[si]['motp'][0,nJoints] = metricsSeqAll[si]['motp'][0,idxs].mean()
            idxs = np.argwhere(~np.isnan(metricsSeqAll[si]['pre'][0,:nJoints]))
            metricsSeqAll[si]['pre'][0,nJoints]  = metricsSeqAll[si]['pre'] [0,idxs].mean()
            idxs = np.argwhere(~np.isnan(metricsSeqAll[si]['rec'][0,:nJoints]))
            metricsSeqAll[si]['rec'][0,nJoints]  = metricsSeqAll[si]['rec'] [0,idxs].mean()

            metricsSeq = metricsSeqAll[si].copy()
            metricsSeq['mota'] = metricsSeq['mota'].flatten().tolist()
            metricsSeq['motp'] = metricsSeq['motp'].flatten().tolist()
            metricsSeq['pre'] = metricsSeq['pre'].flatten().tolist()
            metricsSeq['rec'] = metricsSeq['rec'].flatten().tolist()
            metricsSeq['names'] = names

            filename = outputDir + '/' + seqName + '_MOT_metrics.json'
            logger.info('saving results to', filename)
            #eval_helpers.writeJson(metricsSeq,filename)
            writeJson(metricsSeq,filename)

    # compute final metrics per joint for all sequences
    for i in range(nJoints):
        if (metricsMidAll['num_objects'][0,i] > 0):
            numObj = metricsMidAll['num_objects'][0,i]
        else:
            numObj = np.nan
        numFP = metricsMidAll['num_false_positives'][0,i]
        metricsFinAll['mota'][0,i] = 100*(1. - (metricsMidAll['num_misses'][0,i] +
                                                metricsMidAll['num_switches'][0,i] +
                                                numFP) /
                                                numObj)
        numDet = metricsMidAll['num_detections'][0,i]
        s = metricsMidAll['sumD'][0,i]
        if (numDet == 0 or np.isnan(s)):
            metricsFinAll['motp'][0,i] = 0.0
        else:
            metricsFinAll['motp'][0,i] = 100*(1. - (s / numDet))
        if (numFP+numDet > 0):
            totalDet = numFP+numDet
        else:
            totalDet = np.nan

        metricsFinAll['pre'][0,i]  = 100*(1.*numDet /
                                       totalDet)
        metricsFinAll['rec'][0,i]  = 100*(1.*numDet /
                                       numObj)

    # average metrics over all joints over all sequences
    idxs = np.argwhere(~np.isnan(metricsFinAll['mota'][0,:nJoints]))
    metricsFinAll['mota'][0,nJoints] = metricsFinAll['mota'][0,idxs].mean()
    idxs = np.argwhere(~np.isnan(metricsFinAll['motp'][0,:nJoints]))
    metricsFinAll['motp'][0,nJoints] = metricsFinAll['motp'][0,idxs].mean()
    idxs = np.argwhere(~np.isnan(metricsFinAll['pre'][0,:nJoints]))
    metricsFinAll['pre'][0,nJoints]  = metricsFinAll['pre'] [0,idxs].mean()
    idxs = np.argwhere(~np.isnan(metricsFinAll['rec'][0,:nJoints]))
    metricsFinAll['rec'][0,nJoints]  = metricsFinAll['rec'] [0,idxs].mean()

#    if (bSaveAll):
    if False:
        metricsFin = metricsFinAll.copy()
        metricsFin['mota'] = metricsFin['mota'].flatten().tolist()
        metricsFin['motp'] = metricsFin['motp'].flatten().tolist()
        metricsFin['pre'] = metricsFin['pre'].flatten().tolist()
        metricsFin['rec'] = metricsFin['rec'].flatten().tolist()
        metricsFin['names'] = names

        filename = outputDir + '/total_MOT_metrics.json'
        logger.info('saving results to', filename)
#        eval_helpers.writeJson(metricsFin,filename)
        writeJson(metricsFin,filename)

    return metricsFinAll


# Two Pointers, String, Greedy
# Facebook 30 Microsoft 2 Apple 2 2 7 TikTok 2 Amazon 2 Oracle 6 Walmart Global Tech 4 Bloomberg 3 Google 2 eBay Yahoo
# https://leetcode.com/problems/valid-palindrome-ii/description/

# Given a string s, return true if the s can be palindrome after deleting at most one character from it.

# Example 1:
# Input: s = "aba"
# Output: true

# Example 2:
# Input: s = "abca"
# Output: true
# Explanation: You could delete the character 'c'.

# Example 3:
# Input: s = "abc"
# Output: false

# continue is_palindrome validation if mismatch is found by ignoring left or right
class ValidPalindromeII:
    def validPalindrome(self, s: str) -> bool:
        left = 0
        right = len(s) - 1

        while left < right:
            if s[left] != s[right]:
                delete_left_is_palindrome = self.is_palindrome(s, left + 1, right)
                delete_right_is_palindrome = self.is_palindrome(s, left, right - 1)
                return delete_left_is_palindrome or delete_right_is_palindrome

            left += 1
            right -= 1

        return True

    def is_palindrome(self, s: str, left: int, right: int) -> bool:
        while left < right:
            if s[left] != s[right]:
                return False

            left += 1
            right -= 1

        return True


import random

def crossover_pt(individual1, individual2, uniform_rate):
    from ecosystem import Individual
    new_individual = Individual()

    point = int(uniform_rate * len(individual1.genes))
    new_individual.genes = individual1.genes[0:point]
    new_individual.genes += individual2.genes[point:]
    return new_individual

def crossover_rand(individual1, individual2, uniform_rate):
    from ecosystem import Individual
    new_individual = Individual()

    for i in range(len(individual1.genes)):
        selected_individual = individual1 if random.random() <= uniform_rate else individual2
        new_individual.genes.append(selected_individual.genes[i])
    return new_individual

def mutate(individual, charset, mutation_rate):
    for i in range(len(individual.genes)):
        if random.random() <= mutation_rate:
            individual.genes[i] = random.choice(charset)


#!/usr/bin/python
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)

from __future__ import (absolute_import, division, print_function)
__metaclass__ = type

from ansible.errors import AnsibleFilterError

def meteor_version(version):
    if version.find('-') != -1:
        release, label = version.split('-')
    else:
        release = version
        label = ""
    if len(release.split('.')) == 2:
        release = release + '.0'
    if label:
        release = release + '-' + label
    return release

class FilterModule(object):
    '''Meteor version filter '''

    def filters(self):
        return {
            'meteor_version': meteor_version
        }


from typing import List

class Solution:
    def compareVersion(version1: str, version2: str) -> int:
        s1, s2 = version1.split('.'), version2.split('.')
        for i in range(len(s1)):
            if i > len(s2) - 1:
                if int(s1[i]) > 0:
                    return 1
            elif int(s1[i]) < int(s2[i]):
                return -1
            elif int(s1[i]) > int(s2[i]):
                return 1
        if len(s2) == len(s1):
            return 0
        for j in range(len(s1), len(s2)):
            if int(s2[j]) > 0:
                return -1
        return 0


from pytest import *

import sys
sys.path.append('../')

from datetime import date
from shared_constants import test_as_of_date
from vol_surface import *

#TODO: implement properly once VolSurface is properly implemented
def vol_surface_returns_something():
    # arrange
    as_of_date = date(2022, 1, 1)
    a_date = date(2022, 4, 11)
    a_spot = 100500
    surface = VolSurface(as_of_date)

    # act
    vol = surface.get_vol(a_date, a_spot)

    # assert
    assert vol == 0.15, f"Expected 0.05 but got {vol}"

def vol_surface_returns_zero_for_weekends():
    # arrange
    as_of_date = date(2022, 1, 1)
    sat = date(2022, 4, 9)
    sun = date(2022, 4, 10)
    a_spot = 100500
    surface = VolSurface(as_of_date)

    # act
    sat_vol = surface.get_vol(sat, a_spot)
    sun_vol = surface.get_vol(sun, a_spot)

    # assert
    assert sat_vol == 0, f"Expected Sat vol to be 0 but got {vol}"
    assert sun_vol == 0, f"Expected Sun vol to be 0, but got {vol}"

def vol_surface_returns_correct_vol_by_year_fraction():
    # arrange
    as_of_date = date(2022, 1, 1)
    expiry = date(2022, 4, 10)
    a_spot = 100500
    surface = VolSurface(as_of_date)
    year_fraction = calc_year_fraction_from_dates(as_of_date, expiry)
    expected_vol = surface.get_vol(expiry, a_spot);

    # act
    vol = surface.get_vol_yf(year_fraction, a_spot)

    # assert
    assert vol == expected_vol, f"Expected {expected_vol} but got {vol}"

def vol_surface_returns_as_of_date():
    # arrange
    as_of_date = date(2022, 1, 1)
    surface = VolSurface(as_of_date)

    # act
    actual = surface.get_as_of_date()

    # assert
    assert actual == as_of_date, f"Expected AsOfDate {as_of_date} but got {actual}"

def mock_vol_surface_returns_const():
    # arrange
    const_vol = 0.01
    a_date = date(2022, 4, 10)
    a_spot = 100500
    surface = MockVolSurface(const_vol, test_as_of_date)

    # act
    vol = surface.get_vol(a_date, a_spot)

    # assert
    assert vol == const_vol, f"Expected {const_vol} but got {vol}"

def mock_vol_surface_returns_test_as_of_date():
    # arrange
    surface = MockVolSurface(42, test_as_of_date)

    # act
    actual = surface.get_as_of_date()

    # assert
    assert actual == test_as_of_date, f"Expected AsOfDate {test_as_of_date} but got {actual}"

def dump_vol_surface():
    as_of_date = date(2020, 1, 21)
    surface = VolSurfaceBase("GOOGL", as_of_date)

    result = pd.DataFrame()

    for d in range(1, 365*2):
        cur_date = as_of_date + timedelta(days=d)
        for s in range(500, 2000, 10):
            yf = d / 365.25
            vol = surface.get_vol_yf(yf, s)
            result.at[s,d]=vol
    print(result)
    result.to_csv("vol_surface.csv")

def all_vol_surface_tests():
    vol_surface_returns_something()
    vol_surface_returns_correct_vol_by_year_fraction()
    vol_surface_returns_as_of_date()
    mock_vol_surface_returns_const()
    mock_vol_surface_returns_test_as_of_date()
    dump_vol_surface()

all_vol_surface_tests()


"""Programa que simula una cola de cajero de manera gráfica usando Pygame."""

import sys, pygame, random, pandas
import logic, view, params

if __name__ == '__main__':
    pygame.init()

    # Declaración de variables de ejecución
    screen = pygame.display.set_mode((params.SCREEN_WIDTH, params.SCREEN_HEIGHT))
    pygame.display.set_caption('Proceso de colas')
    clock = pygame.time.Clock()
    time = 0

    # Declaración de los eventos para atención y si la ejecución es automática.
    MANUAL_RESPOND = pygame.USEREVENT + 1
    AUTOMATIC_RESPOND = pygame.USEREVENT + 2
    pygame.time.set_timer(AUTOMATIC_RESPOND, params.AUTOMATIC_RESPOND_TIME, -1)
    automatic = False

    # Instanciación de la tabla y su representación gráfica.
    table_data = pandas.DataFrame(columns=('Proceso', 'Estado', 'T. Llegada', 'Prioridad', 'Ráfaga', 'T. Comienzo', 'T. Final', 'T. Retorno', 'T. Espera'))
    table = view.Table(table_data, 10, 10, 100, 20, 1, 7, 2, 'Comic Sans MS', 15)

    # Instanciación del diagrama de Grant.
    grant = view.Grant(400, 370, 480, 270, 'Comic Sans MS', 15)

    # Instanciación de la cola.
    if params.ENABLE_PRIORITY:
        queue = logic.Priority_Server_Queue(params.SERVER_CAPACITY)
    else:
        queue = logic.SRTF_Server_Queue(params.SERVER_CAPACITY)

    def create_new_client(id: str, n_requests: int, n_priority: int) -> None:
        """Crea un nuevo cliente para uso del programa."""

        if not params.ENABLE_PRIORITY:
            n_priority = None

        queue_client = logic.Queue_Client(id,n_requests, time, n_priority)
        queue.enqueue(queue_client)
        grant.add_tag(str(queue_client.get_id()))
        new_table_line(queue_client)

    def new_table_line(queue_client: logic.Queue_Client, arrival_time: int = None) -> None:
        """Crea una nueva línea en la tabla con la información del cliente y el tiempo de llegada indicado."""
        table_data.loc[len(table_data)] = (
            str(queue_client.get_id()),                         # Id
            'Esperando',                                        # Estado
            time + 1 if arrival_time is None else arrival_time, # Tiempo de llegada
            queue_client.get_priority(),                        # Prioridad
            queue_client.get_number_of_requests(),              # Número de solicitudes.
            None,
            None,
            None,
            None
        )

    def expel_table_line(queue_client: logic.Queue_Client) -> pandas.Series:
        """Devuelve una fila con la infomarción calculada tras la expulsión de un proceso."""

        # Obtener todas las filas del proceso.
        client_rows = table_data[table_data['Proceso'] == queue_client.get_id()].iloc

        # Agregar el tiempo final en la última.
        client_rows[-1, table_data.columns.get_loc('T. Final')] = time + 1

        # Agregar el tiempo de retorno en la última.
        client_rows[-1, table_data.columns.get_loc('T. Retorno')] =\
            client_rows[-1]['T. Final'] - client_rows[-1]['T. Llegada']

        # Para calcular el tiempo de espera se inicia con el tiempo de retorno actual.
        client_rows[-1, table_data.columns.get_loc('T. Espera')] = client_rows[-1]['T. Retorno']
        # Se le resta la ráfaga ejecutada de cada fila del proceso.
        for client_row in client_rows:
            # Sólo se restan los que tienen el mismo tiempo de llegada.
            if client_row['T. Llegada'] != client_rows[-1]['T. Llegada']:
                continue

            client_rows[-1, table_data.columns.get_loc('T. Espera')] -=\
                client_row['T. Final'] - client_row['T. Comienzo']

        # Cambiar estado a expulsado.
        client_rows[-1, table_data.columns.get_loc('Estado')] = 'Expulsado'

        return client_rows[-1]

    # Clientes iniciales.
    for i in range(5):
        id = chr(ord('A') + i)
        create_new_client(id,random.randint(1,15),random.randint(1,5))

    # Cliente bloqueado actualmente.
    blocked_client: logic.Queue_Client = None

    # Instanciación de etiquetas
    time_tag = view.Tag(20, 370, f'Tiempo: {time + 1}', 'Comic Sans MS', 15, 'Black')
    critical_section_tag = view.Tag(20, 610, f'En sección crítica: -', 'Comic Sans MS', 15, 'Black')
    waiting_tag = view.Tag(200, 610, f'Procesos en espera: {queue.get_size() - 1}', 'Comic Sans MS', 15, 'Black')
    tag_list = [
        view.Tag(80, 450, 'Id:', 'Comic Sans MS', 15, 'Black'),
        view.Tag(20, 490, 'Solicitudes:', 'Comic Sans MS', 15, 'Black'),
        time_tag,
        critical_section_tag,
        waiting_tag
    ]

    if params.ENABLE_PRIORITY:
        tag_list.append(view.Tag(30, 530, 'Prioridad:', 'Comic Sans MS', 15, 'Black'))

    # Instanciación de cajas de texto
    textbox_list = []

    id_textbox = view.Textbox(120, 450, 100, 30, 2, 'Comic Sans MS', 15)
    textbox_list.append(id_textbox)

    requests_textbox = view.Textbox(120, 490, 100, 30, 2, 'Comic Sans MS', 15)
    textbox_list.append(requests_textbox)

    priority_textbox = view.Textbox(120, 530, 100, 30, 2, 'Comic Sans MS', 15)
    if params.ENABLE_PRIORITY:
        textbox_list.append(priority_textbox)

    # Instanciación de botones
    button_list = []

    automatic_button = view.Button(120, 370, 200, 30, 2, 'Encender Automático', 'Comic Sans MS', 15)
    automatic_button.box_color_idle = 'Red'
    button_list.append(automatic_button)

    manual_button = view.Button(120, 410, 200, 30, 2, 'Siguiente Paso', 'Comic Sans MS', 15)
    button_list.append(manual_button)

    addclient_button = view.Button(230, 450, 90, 110, 2, 'Añadir', 'Comic Sans MS', 15)
    button_list.append(addclient_button)

    block_button = view.Button(120, 570, 200, 30, 2, 'Bloquear', 'Comic Sans MS', 15)
    button_list.append(block_button)

    # Acciones de los botones
    def automatic_button_action() -> None:
        """Activa o desactiva el modo automático."""

        global automatic
        if not automatic:
            automatic = True
            automatic_button.tag = 'Apagar Automático'
            automatic_button.box_color_idle = 'Green'
        else:
            automatic = False
            automatic_button.tag = 'Encender Automático'
            automatic_button.box_color_idle = 'Red'

    automatic_button.action = automatic_button_action

    def manual_button_action() -> None:
        """Envía el evento de cambio de estado manual."""

        pygame.event.post(pygame.event.Event(MANUAL_RESPOND))

    manual_button.action = manual_button_action

    def addclient_button_action() -> None:
        """Añade un nuevo cliente a la cola y sale del estado HALT."""

        global id_textbox, requests_textbox

        if    id_textbox.text in [str(queue_client.get_id()) for queue_client in list(queue)[1:]]\
           or (blocked_client and id_textbox.text == blocked_client.get_id())\
           or id_textbox.text == '':
            id_textbox.text = '¡ERROR!'
            return

        try:
            requests = requests_textbox.text
            if requests == '':
                requests = random.randint(1, 15)
            else:
                requests = int(requests)
                if requests <= 0:
                    requests_textbox.text = '¡ERROR!'
                    return
        except ValueError:
            requests_textbox.text = '¡ERROR!'
            return
        try:
            priority = priority_textbox.text
            if priority == '':
                priority = random.randint(1, 5)
            else:
                priority = int(priority)
                if priority <= 0 or priority > 5:
                    priority_textbox.text = '¡ERROR!'
                    return
        except ValueError:
            priority_textbox.text = '¡ERROR!'
            return

        past_service = queue.get_current_service()
        try: front_client = queue.get(1)
        except IndexError: front_client = None
        create_new_client(id_textbox.text, int(requests), int(priority))
        if queue.get_current_service() != past_service and front_client is not None:
            client_row = expel_table_line(front_client)
            table_data.loc[client_row.name] = client_row
            new_table_line(queue_client, client_row['T. Llegada'])
            table_data.iloc[-1, table_data.columns.get_loc('Estado')] = 'Esperando'

        id_textbox.text = ''
        requests_textbox.text = ''
        priority_textbox.text = ''

    addclient_button.action = addclient_button_action

    def block_button_action() -> None:
        """Bloquea o desbloquea un cliente dado."""

        global blocked_client
        if blocked_client:
            new_state = 'Esperando'
            block_button.tag = 'Bloquear'
            queue_client = blocked_client
            blocked_client = None
            index = table_data[table_data['Proceso'] == queue_client.get_id()].iloc[-1].name
            queue.enqueue(queue_client)

        else:
            new_state = 'Bloqueado'
            block_button.tag = 'Desbloquear Bloqueado'
            queue_client = queue.get(1)
            blocked_client = queue_client
            index = table_data[table_data['Proceso'] == queue_client.get_id()].iloc[-1].name
            if queue.get_current_service() > 0:
                client_row = expel_table_line(queue_client)
                table_data.loc[client_row.name] = client_row
                new_table_line(queue_client, client_row['T. Llegada'])
                index = table_data.iloc[-1].name

            queue.remove(queue_client)

        table_data.loc[index, 'Estado'] = new_state

    block_button.action = block_button_action

    # Ejecución del programa
    while True:
        for event in pygame.event.get():
            # Oprimir el botón de cerrar ventana.
            if event.type == pygame.QUIT:
                pygame.quit()
                sys.exit()

            # Atención a la cola.
            if event.type == MANUAL_RESPOND or event.type == AUTOMATIC_RESPOND and automatic:
                time += 1
                # Sólo si hay clientes en fila.
                if queue.get_size() > 1:
                    queue_client = queue.get(1)
                    grant.add_line(
                        current_tag=str(queue_client.get_id()),
                        blocked_tag=str(blocked_client.get_id()) if blocked_client else None
                    )
                    queue.dequeue()

                    # Dando tiempo de llegada a proceso actual.
                    client_row = table_data[table_data['Proceso'] == str(queue_client.get_id())].iloc[-1]
                    client_row['Estado'] = 'En Ejecución'
                    if client_row['T. Comienzo'] is None:
                        client_row['T. Comienzo'] = time

                    # Actulizar la nueva fila en la tabla.
                    table_data.loc[client_row.name] = client_row

                    # Cuando se terminó de atender a un cliente.
                    if queue.get_current_service() == 0 and queue.get_size() == 1 or queue.get(1) is not queue_client:
                        client_row = expel_table_line(queue_client)
                        if queue_client.is_done():
                            grant.remove_tag(str(queue_client.get_id()))
                            client_row['Estado'] = 'Terminado'
                        else:
                            new_table_line(queue_client, client_row['T. Llegada'])

                        # Actulizar la nueva fila en la tabla.
                        table_data.loc[client_row.name] = client_row

                # Cuando no hay clientes en fila.
                else:
                    grant.add_line(
                        blocked_tag=str(blocked_client.get_id()) if blocked_client else None
                    )

            # Hacer click en una caja de texto.
            if event.type == pygame.MOUSEBUTTONDOWN:
                if event.button == pygame.BUTTON_LEFT:
                    for textbox in textbox_list:
                        textbox.check_active()

            # Escribir en las cajas de texto.
            if event.type == pygame.KEYDOWN:
                for textbox in textbox_list:
                    textbox.add_text(event.unicode)

        # Llenar la pantalla de blanco.
        screen.fill('White')

        # Actualizando elementos.
        if automatic:
            manual_button.active = False
        else:
            manual_button.active = True

        if blocked_client or queue.get_size() > 1:
            block_button.active = True
        else:
            block_button.active = False

        for button in button_list:
            button.update()

        # Simulación Semáforo
        time_tag.tag = f'Tiempo: {time}'
        if queue.get_current_service() > 0 and queue.get_size() > 1:
            queue_client = queue.get(1)
            critical_section_tag.tag = f'En sección crítica: {queue_client.get_id()}'
            waiting_tag.tag = f'Procesos en espera: {queue.get_size() - 2}'

        else:
            critical_section_tag.tag = f'En seccion crítica: -'
            waiting_tag.tag = f'Procesos en espera: {queue.get_size() - 1}'

        # Dibujando elementos.
        for tag in tag_list:
            tag.draw(screen)

        for textbox in textbox_list:
            textbox.draw(screen)

        for button in button_list:
            button.draw(screen)

        table.draw(screen)
        grant.draw(screen)

        # Actualizar pantalla y esperar.
        pygame.display.update()
        clock.tick()


# coding=UTF-8
import sys
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D

# 格式：t x y z qx qy qz qw
if __name__ == '__main__':
    if len(sys.argv) != 3:
        print('please inenter trajectory.txt !')
        exit(1)
    else:
        path1 = sys.argv[1] # time=30s
        path2 = sys.argv[2] # time=1s

        path_data1 = np.genfromtxt(path1, delimiter=',')
        path_data2 = np.genfromtxt(path2, delimiter=',')
        # fig = plt.subplots()
        fig = plt.figure(figsize=(12.0, 12.0))
        ax = fig.add_subplot(111, projection='3d')
        # ax = Axes3D(fig)
        plt.plot(path_data1[:, 0], path_data1[:, 1], path_data1[:, 2], color = 'r', linewidth=3, label="time=30s")
        plt.plot(path_data2[:, 0], path_data2[:, 1], path_data2[:, 2], color = 'b', linewidth=3, label="time=1s")
        plt.legend(prop={'family' : 'Times New Roman', 'size'   : 16})
        ax.set_xlabel("x", fontsize=18, labelpad=22)
        ax.set_ylabel("y", fontsize=18, labelpad=22)
        ax.set_zlabel("z", fontsize=18, labelpad=22)
        fig.suptitle("SE3  trajectory", size = 20)

        plt.show()

        # fig = plt.figure()
        # ax = fig.add_subplot(projection='3d')
        # plt.rcParams['figure.figsize'] = (16.0, 12.0)
        # plt.title('3D path comparison')
        # # colorList = ['darkred', 'red', 'darkblue', 'blue']
        # colorList = ['r','g','b','y']

        # for i in range(4):
        #     path = sys.argv[i + 1]
        #     path_data = np.genfromtxt(path, delimiter=',')

        #     ax.plot(path_data[:, 0], path_data[:, 1], path_data[:, 2], color = colorList[i])
        #     # plt.title('3D path comparison')
        #     # plt.legend()
        # plt.show()

        exit(1)


import numpy as np
import matplotlib.pyplot as plt
import soundfile as sf

# 音声の読み込み
path = 'C:/Users/hibiki/Desktop/Acoustic_analysis/sample_sound/sounds_a.wav'
master, fs = sf.read(path)

def hz2mel(f):
    """Hzをmelに変換"""
    return 2595 * np.log(f / 700.0 + 1.0)

def mel2hz(m):
    """melをHzに変換"""
    return 700 * (np.exp(m / 2595) - 1.0)

def melFilterBank(fs, N, numChannels):
    """メルフィルタバンクを作成"""
    # ナイキスト周波数（Hz）
    fmax = fs / 2
    # ナイキスト周波数（mel）
    melmax = hz2mel(fmax)
    # 周波数インデックスの最大数
    nmax = N // 2
    # 周波数解像度（周波数インデックス1あたりのHz幅）
    df = fs / N
    # メル尺度における各フィルタの中心周波数を求める
    dmel = melmax / (numChannels + 1)
    melcenters = np.arange(1, numChannels + 1) * dmel
    # 各フィルタの中心周波数をHzに変換
    fcenters = mel2hz(melcenters)
    # 各フィルタの中心周波数を周波数インデックスに変換
    indexcenter = np.round(fcenters / df)
    # 各フィルタの開始位置のインデックス
    indexstart = np.hstack(([0], indexcenter[0:numChannels - 1]))
    # 各フィルタの終了位置のインデックス
    indexstop = np.hstack((indexcenter[1:numChannels], [nmax]))
    filterbank = np.zeros((numChannels, nmax))
    print(indexstop)
    for c in range(0, numChannels):
        # 三角フィルタの左の直線の傾きから点を求める
        increment = 1.0 / (indexcenter[c] - indexstart[c])
        for i in range(int(indexstart[c]), int(indexcenter[c])):
            filterbank[c, i] = (i - indexstart[c]) * increment
        # 三角フィルタの右の直線の傾きから点を求める
        decrement = 1.0 / (indexstop[c] - indexcenter[c])
        for i in range(int(indexcenter[c]),int(indexstop[c])):
            filterbank[c, i] = 1.0 -((i - indexcenter[c]) * decrement)

    return filterbank, fcenters

# メルフィルタバンクを作成
N = 2048  # FFTのサンプル数
numChannels = 20    # メルフィルタバンクのチャネル数
df = fs / N     # 周波数解像度（周波数インデックス1あたりのHz幅）
filterbank, fcenters = melFilterBank(fs, N, numChannels)

# メルフィルタバンクのプロット
for c in np.arange(0, numChannels):
    plt.plot(np.arange(0, N / 2) * df, filterbank[c])

plt.title("Mel filter bank")
plt.xlabel("Frequency [Hz]")
plt.show()


import tensorflow as tf
import numpy as np
import pandas as pd
from sklearn.model_selection import train_test_split

STATE = 516841

def df_to_dataset(dataframe, shuffle=True, batch_size=32):
    dataframe = dataframe.copy()
    labels = dataframe.pop('landslide')
    ds = tf.data.Dataset.from_tensor_slices((dict(dataframe), labels))
    if shuffle:
        ds = ds.shuffle(buffer_size=len(dataframe))
    ds = ds.batch(batch_size)
    return ds

ls_cdf = pd.read_csv("../data/nasa_global_landslide_catalog_point.csv")
fake_rows = ls_cdf.sample(2000)
fake_rows['landslide'] = 0
ls_cdf['landslide'] = 1

# randomly_vary X for the fake rows
fake_rows['gazetteer_distance'] = fake_rows.gazetteer_distance.apply(lambda x: x * np.random.uniform(0.8, 1.2))

# stack vertically
dataframe = pd.concat(
    [ls_cdf[['gazetteer_distance', 'landslide']],
    fake_rows[['gazetteer_distance', 'landslide']]],
    ignore_index=True).reset_index(drop=True)

# output toy data
dataframe.to_pickle('../data/toy_data.pkl')

train, test = train_test_split(dataframe, test_size = .2, random_state = STATE)
train, val = train_test_split(train, test_size=.2, random_state = STATE)

print(len(train), 'train examples')
print(len(val), 'val examples')
print(len(test), 'test examples')

# batch_size = 5
# train_ds = df_to_dataset(train, batch_size=batch_size)
# val_ds = df_to_dataset(val, shuffle=False, batch_size=batch_size)
# test_ds = df_to_dataset(test, shuffle=False, batch_size=batch_size)

# for feature_batch, label_batch in train_ds.take(1):
#     print('Every feature:', list(feature_batch.keys()))
#     print('ages', feature_batch['gazetteer_distance'])
#     print('targets', label_batch)

# example_batch = next(iter(train_ds))
feature_columns = []
for header in ['gazetteer_distance']:
    feature_columns.append(tf.feature_column.numeric_column(header))

feature_layer = tf.keras.layers.DenseFeatures(feature_columns)

batch_size = 32
train_ds = df_to_dataset(train, batch_size=batch_size)
val_ds = df_to_dataset(val, shuffle=False, batch_size=batch_size)
test_ds = df_to_dataset(test, shuffle=False, batch_size=batch_size)

for feature_batch, label_batch in train_ds.take(1):
    print('Every feature:', list(feature_batch.keys()))
    print('ages', feature_batch['gazetteer_distance'])
    print('targets', label_batch)

feature_layer = tf.keras.layers.DenseFeatures(feature_columns)

model = tf.keras.Sequential([
    feature_layer,
    tf.keras.layers.Dense(128, activation='relu'),
    tf.keras.layers.Dense(128, activation='relu'),
    tf.keras.layers.Dropout(.1),
    tf.keras.layers.Dense(1)
])

## For multi class classification, loss is done via categorical cross entropy instead of binary.
model.compile(optimizer='adam', loss=tf.keras.losses.BinaryCrossentropy(from_logits=True), metrics=['accuracy'])

model.fit(train_ds, validation_data=val_ds, epochs=10)

# arr = np.array(merged_cdf)
# arr_np = np.asarray(arr)
# np_split = np.hsplit(arr_np,2)
# input_vector = np_split[0]
# output_vector = np_split[1]
# print(input_vector.shape)
# print(output_vector.shape)pi


import os
import time
import webbrowser
import speech_recognition as sr
import wikipedia
# import smtplib
import pyttsx3
import datetime
import pyjokes
# import subprocess
engine = pyttsx3.init('sapi5')
voices = engine.getProperty("voices")
engine.setProperty("voice", voices[1].id)

# dictionaryForEmail = {
#     "rahul": "rahularyan9898@gmail.com",
#     "sayan": "raysyan222@gmail.com",
#     "dipika":"dipikahait1173@gmail.com",
#     "nandan": "nandandasegre@gmail.com",
# }

# dictionaryForDob = {
#     "rahul": "twenty six june nineteen ninety eight",
#     "sayan": "_______________ nineteen ninety eight",
#     "dipika": "fifteen june nineteen ninety eight"
# }

restrict_search = ["how to have an abortion", "how bombs are made", "hire a Assassin", "process of making drugs", "open xvideos", "find child porn", "process making of gun"]

def speak(text):
    engine.say(text)
    engine.runAndWait()

def wishMe():
    hour = datetime.datetime.now().hour
    if hour >= 0 and hour < 12:
        speak("Good Morning")
        print("Good Morning")

    elif hour >= 12 and hour < 18:
        speak("Good Afternoon")
        print("Good Afternoon")

    else:
        speak("Good Evening")
        print("Good Evening")

def takeCommand():
    r = sr.Recognizer()
    with sr.Microphone() as source:
        print("Listening....")
        r.pause_threshold = 1
        audio = r.listen(source)
        try:
            print("Recognizing...")
            statement = r.recognize_google(audio, language='en-in')
            print(f"user said: {statement} \n")

        except Exception as e:
            speak("Sorry, I can't hear you properly. Can you say again please!")
            return "None"
        return statement

# def sendEmail(to, content):
#     server = smtplib.SMTP('smtp.gmail.com', 465)
#     server.ehlo()
#     server.starttls()
#
#     server.login('mca.vu2020@gmail.com','Vu1@mca20')
#     server.sendmail('mca.vu2020@gmail.com', to, content)
#     server.close()

print("Loading your personal assistant")
speak("Loading your personal assistant")
wishMe()

# The main Function :-

if __name__ == '__main__':

    while True:
        speak("Tell me how can I help you ?")
        statement = takeCommand().lower()
        if statement == 0:
            continue

        if 'who are you' in statement:
            print('I am NANCY your personal assistant.')
            speak('I am NANCY your personal assistant.')

        if 'how are you' in statement:
            print("I am fine, Thank you")
            speak("I am fine, Thank you")
            print("How are you, Sir?")
            speak("How are you, Sir?")

        if 'i am fine' in statement:
            print("It is good to know that you are fine")
            speak("It is good to know that you are fine")

        if 'what can you do for me' in statement:
            print('I am programmed to minor task like opening youtube, browser, gmail and get top headline news from The HINDU')
            speak('I am programmed to minor task like opening youtube, browser, gmail and get top headline news from The HINDU')

        # elif 'change name' in statement:
        #     speak("What would you like to call me, Sir?")
        #     print()

        elif 'reason to create you' in statement:
            print("I was created as a Minor project under the guidance of Mr. Debkumar Bera")
            speak("I was created as a Minor project under the guidance of Mr. Debkumar Bera")

        # elif 'empty recycle bin' in statement:

# Predicting time:-

        elif 'what time is it right now' in statement:
            spTime = datetime.datetime.now().strftime("%H:%M:%S")
            print(f"the time is {spTime}")
            speak(f"the time is {spTime}")

# For Jokes:-

        elif "tell me a joke" in statement:
            try:
                joke = pyjokes.get_joke()
                print(joke)
                speak(joke)
            except Exception as e:
                print(e)
                speak("Your voice is not clear sir please say that again")

        # elif "birthday" in statement:
        #     try:
        #         print('whose birthday')
        #         speak('whose birthday')
        #         to = takeCommand()
        #         date = dictionaryForDob[to]
        #         speak(date)
        #         print(date)
        #     except Exception as e:
        #         print(e)
        #         speak("Sorry sir I don't know your dath of birth")
# Wikipedia:-

        elif 'wikipedia' in statement:
            speak('serching wikipedia....')
            statement = statement.replace("wikipedia", "")
            results = wikipedia.summary(statement, sentences = 3)
            print("Acording to wikipedia")
            speak("Acording to wikipedia")
            print(results)
            speak(results)

# Webbrowsers:-

        elif 'open youtube' in statement:
            webbrowser.open_new_tab("https://www.youtube.com/")
            print("Youtube is open now")
            speak("Youtube is open now")
            time.sleep(5)

        elif 'open google' in statement:
            webbrowser.open_new_tab("https://www.google.com/")
            print("google is open now")
            speak("google is open now")
            time.sleep(5)

        elif 'open gmail' in statement:
            webbrowser.open_new_tab("https://mail.google.com/")
            print("gmail is open now")
            speak("gmail is open now")
            time.sleep(5)

        elif 'open stack overflow' in statement:
            print("Here you go to stack over flow.Happy coding")
            speak("Here you go to stack over flow.Happy coding")
            webbrowser.open("stackoverflow.com")

        # elif 'mail' in statement:
        #     try:
        #         speak('whom to send')
        #         to = takeCommand()
        #         to = dictionaryForEmail[to]
        #         print(to)
        #         speak("what should i say")
        #         content = takeCommand()
        #         sendEmail(to, content)
        #         speak("Email has been sent")
        #     except Exception as e:
        #         print(e)
        #         speak("Sorry sir i am unable to send the mail")

# For News

        elif 'show me latest news' in statement:
            news = webbrowser.open_new_tab("https://www.thehindu.com/latest-news/")
            print('Here are some latest news from the hindu')
            speak('Here are some latest news from the hindu')
            time.sleep(5)

# Serching data from web:-

        elif 'search' in statement:
            statement = statement.replace("search", "")
            webbrowser.open_new_tab(statement)
            time.sleep(5)

# For restrict_search

        # elif 'search' in statement:
        #     if statement != restrict_search:
        #         statement = statement.replace("search", "")
        #         webbrowser.open_new_tab(statement)
        #     else:
        #         print("This is comes under restric search as per indian government rule.")
        #         speak("This is comes under restric search as per indian government rule.")
        #         break

        # elif 'search' in statement:
        #     try:
        #         statement = statement.replace("search","")
        #         webbrowser.open_new_tab(statement)
        #     except Exception as e:
        #         statement = restrict_search
        #         print("This is comes under restric search as per indian government rule.")
        #         speak("This is comes under restric search as per indian government rule.")
        #         break

        # elif "porn" in statement or "sex" in statement:
        #     print("This word comes under a restriction word")
        #     speak("This word comes under a restriction word")
        #     break
# restrict_search = ["how to have an abortion", "how bombs are made", "hire a Assassin","process of making drugs", "open xvideos", "find child porn", ]

        elif statement in restrict_search:
            print("Sorry, This is comes under restric search as per indian government rule.")
            speak("Sorry, This is comes under restric search as per indian government rule.")
            print("This may be risky for you")
            speak("This may be risky for you")
            break

# open powerpoint:-

        elif 'open powerpoint presentation' in statement:
            print("Opening power point presentation")
            speak("Opening power point presentation")
            # power = r"C:\\Users\\rahul\\OneDrive\\Desktop\\Nancy\\Speech_Recognition with protected search.pptx"
            power = r"C:\\Users\\rahul\\OneDrive\\Desktop\\Speech_Recognition with protected search.pptx"
            os.startfile(power)

# for holding:-
        elif "don't listen me" in statement or "stop listening" in statement:
            print("For how much time you want to stop me from listening command")
            speak("For how much time you want to stop me from listening command")
            a = int(takeCommand())
            time.sleep(a)
            print(a)

# For Stop running command:-

        elif "exit" in statement or "good bye" in statement or "ok bye" in statement:
            print("Thanks for giving me your valuable time")
            speak("Thanks for giving me your valuable time")
            print("Your personal assistant NANCY is shutting down, Good Bye")
            speak("Your personal assistant NANCY is shutting down, Good Bye")
            # break
            exit()

        # elif 'exit' in statement:
        #     print("Thanks for giving me your valuable time")
        #     speak("Thanks for giving me your valuable time")
        #     exit()

        # elif 'sleep' in statement:
        #     print("Your system is going to sleep mode.")
        #     speak("Your system is going to sleep mode.")
        #     subprocess.call("shutdown/h")

        elif 'log off' in statement or 'sign out' in statement:
            try:
                print('Are you sure to log off your system')
                speak('Are you sure to log off your system')
                choice = takeCommand()
                os.system("shutdown /l")
                time.sleep(7)
            except Exception as e:
                speak('Sorry Sir, I am unable to sign out')

        # elif 'log off' in statement or 'sign out' in statement:
        #     speak("sign out")
        #     subprocess.call(["shutdown /l"])
        #     time.sleep(5)


class Node:
    def __init__(self,data):
        self.data = data
        self.next = None

class circular_linkedList:

    def __init__(self):
        self.head = None

    def insert_node(self,data):
        new_node = Node(data)
        if self.head == None:
            self.head = new_node
            new_node.next = self.head
            return
        else:
            temp = self.head
            while temp.next != self.head:
                temp = temp.next
            temp.next = new_node
            new_node.next = self.head
            return

    def print_list(self):
        temp = self.head
        while temp.next:
            print(temp.data,end=" ")
            temp = temp.next
            if temp == self.head:
                break
        return

    def find_len(self):
        count = 0
        temp = self.head
        while temp:
            count += 1
            temp = temp.next
            if temp == self.head:
                break
        return count

    def spit_list(self):
        size = self.find_len()
        mid = size//2
        prev = None
        nxt = None
        temp = self.head
        count = 1
        while temp and count < mid:
            temp = temp.next
            count += 1
        nxt = temp.next
        temp.next = self.head
        # printing the first half list
        print("\nfirst half list : ",end= " ")
        self.print_list()

        new_list = circular_linkedList()
        while nxt.next != self.head:
            new_list.insert_node(nxt.data)
            nxt = nxt.next
        new_list.insert_node(nxt.data)
        print('\nsecond half list : ',end=" ")
        new_list.print_list()

if __name__ == '__main__':

    l2 = circular_linkedList()
    l2.insert_node(5)
    l2.insert_node(6)
    l2.insert_node(7)
    l2.insert_node(8)
    l2.insert_node(9)
    l2.insert_node(10)
    print("Full List")
    l2.print_list()
    l2.spit_list()


# https://www.geeksforgeeks.org/auto-complete-feature-using-trie/
# Question : We are given a Trie with a set of strings stored in it. Now the user types in a prefix of his search
# query, we need to give him all recommendations to auto-complete his query based on the strings stored in the Trie.
# We assume that the Trie stores past searches by the users.
#
# For example if the Trie store {"abc", "abcd", "aa", "abbbaba"} and the User types in "ab" then he must be shown
# {"abc", "abcd", "abbbaba"}.
#
# Question Type : Generic
# Used : Insert all the words in trie.
#        Search for given query using standard Trie search algorithm.
#        If query prefix itself is not present, return -1 to indicate the same.
#        If query is present and is end of word in Trie, print query.
#        Else recursively print all nodes under subtree of last matching node for which
#        isEndOfWord is True.
# Logic: class TrieNode:
#        def __init__(self):
#           self.children = [None] * MAX_CHAR
#           self.isEndOfWord = False
#        def insert(self, insStr):
#           temp = self.root
#           for level in range(len(insStr)):
#               index = charToIndex(insStr[level])
#               if not temp.children[index]:
#                   temp.children[index] = TrieNode()
#               temp = temp.children[index]
#        temp.isEndOfWord = True
# Complexity : O(n * m) while inserting words in trie

MAX_CHAR = 26

def charToIndex(ch):
    return ord(ch) - ord('a')

def indexToChar(i):
    return chr(i + ord('a'))

class TrieNode:
    def __init__(self):
        self.children = [None] * MAX_CHAR
        self.isEndOfWord = False

class Trie:
    def __init__(self):
        self.root = TrieNode()

    def insert(self, insStr):
        temp = self.root
        for level in range(len(insStr)):
            index = charToIndex(insStr[level])
            if not temp.children[index]:
                temp.children[index] = TrieNode()
            temp = temp.children[index]

        temp.isEndOfWord = True

    def search(self, insStr):
        temp = self.root
        for level in range(len(insStr)):
            index = charToIndex(insStr[level])
            if not temp.children[index]:
                return False
            temp = temp.children[index]

        if temp is not None and temp.isEndOfWord:
            print(insStr)
            return True
        elif temp is not None:
            searchFullWord(temp, insStr)
            return True
        return False

def searchFullWord(root, resStr):
        if root.isEndOfWord is True:
                print(resStr)

        for i in range(MAX_CHAR):
            if root.children[i] is not None:
                char = indexToChar(i)
                searchFullWord(root.children[i], resStr + char)

if __name__ == "__main__":
    words = ["abc", "abcd", "aa", "abbbaba"]
    t = Trie()
    for word in words:
        t.insert(word)

    if not t.search("abc"):
        print("not found")

    print
    if not t.search("ab"):
        print("not found")

    print("")
    if not t.search("ss"):
        print("not found")


import csv
import os

from django.core.management import BaseCommand

from reviews.models import (
    User,
    Category,
    Review,
    Comment,
    Title,
    Genre,
    GenreTitle
)

FILES_DIR = 'static/data'

CLASSES = {
    'users.csv': User,
    'category.csv': Category,
    'titles.csv': Title,
    'review.csv': Review,
    'comments.csv': Comment,
    'genre.csv': Genre,
    'genre_title.csv': GenreTitle
}

FOREIGN_FIELDS = {
    'category': ('category', Category),
    'title_id': ('title', Title),
    'genre_id': ('genre', Genre),
    'author': ('author', User),
    'review_id': ('review', Review),
}

def open_file(file):
    file_path = os.path.join(FILES_DIR, file)
    with open(file_path, encoding='utf-8') as csv_file:
        return list(csv.reader(csv_file))

def load_file(file, model_type):
    data = open_file(file)
    rows = data[1:]
    for row in rows:
        csv_data = dict(zip(data[0], row))
        new_data_csv = dict(csv_data)
        for key, value in csv_data.items():
            if key in FOREIGN_FIELDS.keys():
                new_key = FOREIGN_FIELDS[key][0]
                new_data_csv[new_key] = (
                    FOREIGN_FIELDS[key][1].objects.get(pk=value)
                )
        database = model_type(**new_data_csv)
        database.save()

class Command(BaseCommand):
    """Загрузка данных, для тестирования."""

    help = 'Загрузка данных'

    def handle(self, *args, **options):
        for key, value in CLASSES.items():
            load_file(key, value)


def print_matrix(matrix):
    for i in range(m):
        print(' '.join(matrix[i]))

def print_not_valid():
    print('Invalid input!')

def valid_input(matrix, commands):
    if command != 'swap':
        print_not_valid()
    elif len(args) != 4:
        print_not_valid()
    row_one, col_one, row_two, col_two = list((map(int, args)))

    if row_one < 0 or row_one >= m \
            or row_two < 0 or row_two >= m \
            or col_one < 0 or col_one >= n \
            or col_two < 0 or col_two >= n:
        print_not_valid()
    else:
        return True

m, n = [int(i) for i in input().split()]
matrix = [[r for r in input().split()] for row in range(m)]
rows = len(matrix)
cols = len(matrix[0])
total_length = rows * cols

command, *args = input().split()

while command != 'END':
    if valid_input(matrix, command):
        row_one, col_one, row_two, col_two = list(map(int, args))
        matrix[row_one][col_one], matrix[row_two][col_two] = matrix[row_two][col_two], matrix[row_one][col_one]
        print_matrix(matrix)
    else:
        print_not_valid()

    command, *args = input().split()