#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Created on Tue Apr  8 10:07:17 2025

@author: wqg
"""

import csv
import os
import platform
import sys
import pickle
import cv2
import numpy as np
from pathlib import Path
import matplotlib.pyplot as plt
import matplotlib.pyplot as plt
from typing import List, Tuple
from scipy.spatial.distance import cdist
from scipy.spatial import ConvexHull
from shapely.geometry import Point, Polygon

##################################################### for method: run_yrt()
FILE = Path(__file__).resolve()
ROOT = FILE.parents[1]
if str(ROOT) not in sys.path:
    sys.path.insert(0, str(ROOT))

from track_reid import yolov10_resnet_tracker
from event_time_specify import devide_motion_state


def cross(o: Tuple[float, float], a: Tuple[float, float], b: Tuple[float, float]) -> float:
    """ 计算向量 OA × OB 的叉积 """
    return (a[0] - o[0]) * (b[1] - o[1]) - (a[1] - o[1]) * (b[0] - o[0])

def compute_convex_hull(points: List[Tuple[float, float]]) -> List[Tuple[float, float]]:
    """ 使用 Andrew's Monotone Chain 算法求二维点集的凸包 """
    points = sorted(set(points))  # 排序并去重
    if len(points) <= 1:
        return points

    lower = []
    for p in points:
        while len(lower) >= 2 and cross(lower[-2], lower[-1], p) <= 0:
            lower.pop()
        lower.append(p)

    upper = []
    for p in reversed(points):
        while len(upper) >= 2 and cross(upper[-2], upper[-1], p) <= 0:
            upper.pop()
        upper.append(p)

    # 去掉重复的连接点
    return lower[:-1] + upper[:-1]

def is_point_in_convex_hull(point: Tuple[float, float], hull: List[Tuple[float, float]]) -> bool:
    """ 判断一个点是否在凸包（含边界）内 """
    n = len(hull)
    if n < 3:
        # 对于点或线段，直接判断是否共线或在线段上
        if n == 1:
            return point == hull[0]
        if n == 2:
            a, b = hull
            return abs(cross(a, b, point)) < 1e-10 and min(a[0], b[0]) <= point[0] <= max(a[0], b[0]) and min(a[1], b[1]) <= point[1] <= max(a[1], b[1])
        return False

    for i in range(n):
        a = hull[i]
        b = hull[(i + 1) % n]
        if cross(a, b, point) < -1e-10:  # 必须全部在左边或边上
            return False
    return True



def plot_convex_hull(points: List[Tuple[float, float]], hull: List[Tuple[float, float]], test_points: List[Tuple[float, float]] = None):
    x_all, y_all = zip(*points)
    fig, ax = plt.subplots()
    
    ax.set_xlim(0, 1024)
    ax.set_ylim(1280, 0)

    ax.plot(x_all, y_all, 'o', label='Points')

    # 凸包闭环线
    hull_loop = hull + [hull[0]]
    hx, hy = zip(*hull_loop)
    ax.plot(hx, hy, 'r-', linewidth=2, label='Convex Hull')

    # 如果有测试点
    if test_points:
        for pt in test_points:
            color = 'green' if is_point_in_convex_hull(pt, hull) else 'black'
            ax.plot(pt[0], pt[1], 's', color=color, markersize=8)
            ax.text(pt[0] + 0.05, pt[1], f'{pt}', fontsize=9)

    ax.legend()
    ax.grid(True)
    plt.title("Convex Hull Visualization")
    plt.show()


def convex_scipy():   
    points = np.array([
    [0, 0],
    [2, 0],
    [1, 1],
    [2, 2],
    [0, 2],
    [1, 0.5]]) 
    hull = ConvexHull(points)
    
    # 凸包顶点的索引
    print("凸包顶点索引:{}".format(hull.vertices))
    print("凸包顶点坐标:")
    for i in hull.vertices:
        print(points[i])
        

    # 将凸包坐标构造成 Polygon
    hull_points = points[hull.vertices]
    polygon = Polygon(hull_points)
    
    # 判断一个点是否在凸包内
    p = Point(1, 1)  # 示例点
    print("是否在凸包内:", polygon.contains(p))  # True or False
    

def test_convex():
    # 测试数据
    sample_points = [(0, 0), (1, 1), (2, 2), (2, 0), (0, 2), (1, 0.5)]
    convex_hull = compute_convex_hull(sample_points)
    
    # 测试点在凸包内
    test_point_inside = (1, 1)
    test_point_outside = (3, 3)
    test_point_on_edge = (1, 0)
    
    inside = is_point_in_convex_hull(test_point_inside, convex_hull)
    outside = is_point_in_convex_hull(test_point_outside, convex_hull)
    on_edge = is_point_in_convex_hull(test_point_on_edge, convex_hull)
    
    convex_hull, inside, outside, on_edge
    
    # 展示图像
    plot_convex_hull(sample_points, convex_hull, [test_point_inside, test_point_outside, test_point_on_edge])

def array2frame(tboxes):
    "tboxes: [x1, y1, x2, y2, track_id, score, cls, frame_index, box_index]"
    idx = np.where(tboxes[:, 6] != 0)[0]
    bboxes = tboxes[idx, :]
    frameID = np.sort(np.unique(bboxes[:, 7].astype(int)))    
    fboxes  = [] 
    for fid in frameID: 
        idx = np.where(bboxes[:, 7] == fid)[0]
        box = bboxes[idx, :]
        fboxes.append(box)
    return fboxes

def convex_based(tboxes, width, TH=40):
    fboxes = array2frame(tboxes)
    fnum = len(fboxes)
    
    fids = np.array([i+1 for i in range(fnum)])[:, np.newaxis]
    state = np.zeros((fnum, 1), dtype=np.int64)
    frameState = np.concatenate((fids, state), axis = 1).astype(np.int64)
    if fnum < width: 
        return frameState
    
    for idx1 in range(width, fnum+1):
        idx0 = idx1 - width
        idx = idx1 - width//2 - 1
        
        iboxes = fboxes[:idx]
        cboxes = fboxes[idx][:, 0:4]

        cur_xy = np.zeros((len(cboxes), 2))
        cur_xy[:, 0] = (fboxes[idx][:, 0]+fboxes[idx][:, 2])/2
        cur_xy[:, 1] = (fboxes[idx][:, 1]+fboxes[idx][:, 3])/2
        for i in range(width//2):
            x1, y1, x2, y2 = iboxes[i][:, 0], iboxes[i][:, 1], iboxes[i][:, 2], iboxes[i][:, 3]
            
            boxes = np.array([(x1, y1), (x1, y2), (x2, y1), (x2, y2)]).transpose(0, 2, 1).reshape(-1, 2)
            box1 = [(x, y) for x, y in boxes]
            convex_hull = compute_convex_hull(box1)
            
            for pt in cur_xy:
                inside = is_point_in_convex_hull(pt, convex_hull)
    
                if not inside:
                    break
            if not inside:
                break
        
        # Based on the distance between the four corners of the current frame boxes 
        # and adjacent frame boxes
        iboxes = fboxes[idx0:idx] + fboxes[idx+1:idx1]
        cboxes = fboxes[idx][:, 0:4] 
        cx1, cy1, cx2, cy2 = cboxes[:, 0], cboxes[:, 1], cboxes[:, 2], cboxes[:, 3]
        cxy = np.array([(cx1, cy1), (cx1, cy2), (cx2, cy1), (cx2, cy2)]).transpose(0, 2, 1).reshape(-1, 2)
        
        iiboxes = np.concatenate(iboxes, axis=0)
        ix1, iy1, ix2, iy2 = iiboxes[:, 0], iiboxes[:, 1], iiboxes[:, 2], iiboxes[:, 3]
        ixy = np.array([(ix1, iy1), (ix1, iy2), (ix2, iy1), (ix2, iy2)]).transpose(0, 2, 1).reshape(-1, 2)
        
        Dist = cdist(cxy, ixy).round(2)
        max_dist = np.max(np.min(Dist, axis=1))
        if max_dist > TH and not inside:
            frameState[idx, 1] = 1
            # plot_convex_hull(boxes, convex_hull, [pt])
            frameState[idx, 1] = 1
    
    return frameState


def single_point(tboxes, width, TH=60):
    """width: window width, >=2"""
    
    
    fboxes = array2frame(tboxes)
    fnum = len(fboxes)
    
    fids = np.array([i+1 for i in range(fnum)])[:, np.newaxis]
    state = np.zeros((fnum, 1), dtype=np.int64)
    frameState = np.concatenate((fids, state), axis = 1).astype(np.int64)
    
    
    if fnum < width: 
        return frameState
    
    for idx1 in range(width, fnum+1):
        idx0 = idx1 - width
        idx = idx1 - width//2 - 1

        iboxe1 = fboxes[idx0:idx]
        iboxe2 = fboxes[idx+1:idx1]
        iboxes = fboxes[idx0:idx] + fboxes[idx+1:idx1]
        
        cboxes = fboxes[idx][:, 0:4]
        cur_xy = np.zeros((len(cboxes), 2))
        cur_xy[:, 0] = (fboxes[idx][:, 0]+fboxes[idx][:, 2])/2
        cur_xy[:, 1] = (fboxes[idx][:, 1]+fboxes[idx][:, 3])/2
        Dist = np.empty((len(cboxes), 0))
        for i in range(width-1):
            boxes = iboxes[i][:, 0:4]
            
            box_xy = np.zeros((len(boxes), 2))
            box_xy[:, 0] = (boxes[:, 0]+boxes[:, 2])/2
            box_xy[:, 1] = (boxes[:, 1]+boxes[:, 3])/2
            dist2 = cdist(cur_xy, box_xy).round(2)
            
            Dist = np.concatenate((Dist, dist2), axis=1)
        
        max_dist = np.max(np.min(Dist, axis=1))
        
        if max_dist > TH:
            frameState[idx, 1] = 1
            
    return frameState



def intrude():
    pkpath = Path("/home/wqg/dataset/small-goods/pkfiles")
    savepath = Path("/home/wqg/dataset/small-goods/illustration_convex")
    
    if not savepath.exists():
        savepath.mkdir(parents=True, exist_ok=True)

    err_trail, err_single, err_all = [], [], []
    num = 0
    for pth in pkpath.iterdir():
        # item = r"69042386_20250407-145737_front_returnGood_b82d28427666_15_17700000001.pickle"
        # pth = pkpath/item
        
        with open(str(pth), 'rb') as f:
            yrt = pickle.load(f)
            
        evtname = pth.stem
            
        bboxes = []
        trackerboxes = np.empty((0, 10), dtype=np.float64)
        for frameDict in yrt:            
            boxes = frameDict["bboxes"]
            tboxes = frameDict["tboxes"]
            tboxes = np.concatenate((tboxes, tboxes[:,7][:, None]), axis=1)
            
            bboxes.append(boxes) 
            
            trackerboxes = np.concatenate((trackerboxes, np.array(tboxes)), axis=0)
        
        '''single-points based for intrusion detection'''
        # wd =5
        # fstate1 = single_point(trackerboxes, wd)
        
        '''convex-based '''
        width = 5
        fstate = convex_based(trackerboxes, width, TH=60)
        
        # fstate = np.zeros(fstate1.shape)
        # fstate[:, 0] = fstate1[:, 0]
        # fstate[:, 1] = fstate1[:, 1] * fstate2[:, 1]
        
        '''trajectory based for intrusion detection 
        period:  0           1                  2                  3
                fid   timestamp(fid)   基于滑动窗的tid扩展    滑动窗覆盖的运动区间
        '''
        win_width = 12
        period, handState = devide_motion_state(trackerboxes, win_width)

        num += 1
        if np.all(period[:,2:4]==0):
            err_trail.append(evtname)
        if np.all(fstate[:,1]==0):
            err_single.append(evtname)   
        if np.all(period[:,2:4]==0) and np.all(fstate[:,1]==0):
            err_all.append(evtname)

        fig, (ax1, ax2) = plt.subplots(2, 1)
        ax1.plot(period[:, 1], period[:, 2], 'bo-', linewidth=1, markersize=4)
        ax1.plot(period[:, 1], period[:, 3], 'rx-', linewidth=1, markersize=8)

        ax2.plot(fstate[:, 0], fstate[:, 1], 'rx-', linewidth=1, markersize=8)
        plt.savefig(os.path.join(str(savepath), f"{evtname}.png"))
        
        
        plt.close()
        # if num==1:
        #     break
        
    rate_trail = 1 - len(err_trail)/num
    rate_single = 1 - len(err_single)/num
    rate_all = 1 - len(err_all)/num
    
    print(f"rate_trail: {rate_trail}")
    print(f"rate_single: {rate_single}")
    print(f"rate_all: {rate_all}")
    
    txtpath = savepath.parents[0] / "error.txt"
    with open(str(txtpath), "w") as f:
        f.write(f"rate_trail: {rate_trail}" + "\n")
        f.write(f"rate_single: {rate_single}" + "\n")
        f.write(f"rate_all: {rate_all}" + "\n")
        
        f.write("\n" + "err_trail" + "\n")
        for line in err_trail:
            f.write(line + "\n")
        
        f.write("\n" + "err_single" + "\n")
        for line in err_single:
            f.write(line + "\n")
        
        f.write("\n" + "err_all" + "\n")
        for line in err_all:
            f.write(line + "\n")      
    print("Done!")
    
    


def run_yrt():
    datapath = Path("/home/wqg/dataset/small-goods/videos/")
    savepath = Path("/home/wqg/dataset/small-goods/result/")
    pkpath = Path("/home/wqg/dataset/small-goods/pkfiles/")
    
    if not savepath.exists():
        savepath.mkdir(parents=True, exist_ok=True)
    if not pkpath.exists():
        pkpath.mkdir(parents=True, exist_ok=True)
    

    optdict = {}
    optdict["weights"] =  ROOT / 'ckpts/best_v10s_width0375_1205.pt' 
    optdict["is_save_img"] = False
    optdict["is_save_video"] = True
    
    k = 0
    for pth in datapath.iterdir():
        item = "69042386_20250407-145819_back_returnGood_b82d28427666_15_17700000001.mp4"
        pth = pth.parents[0] /item
    
        optdict["source"] = pth
        optdict["save_dir"] = savepath
        
        # try:
        yrtOut = yolov10_resnet_tracker(**optdict)

        pkpath_ = pkpath / f"{Path(pth).stem}.pickle"
        with open(str(pkpath_), 'wb') as f:
            pickle.dump(yrtOut, f)
        
        k += 1
        if k==1:
            break
        # except Exception as e:
        #     print("abc")
        


if __name__ == '__main__':    
    # run_yrt()
    
    intrude()
    
    # test_convex()
    










