import os
import sys
import glob

try:
    import cv2
except Exception as exc:
    sys.stderr.write("OpenCV(cv2) import failed: %s\n" % exc)
    sys.exit(10)


def sort_rect_records(records):
    return sorted(records, key=lambda item: (-float(item[1]), int(item[0][1]), int(item[0][0])))


def emit_rects(mode_name, version_text, rects, areas=None, scores=None):
    print("VERSION\t%s" % version_text)
    print("MODE\t%s" % mode_name)
    print("COUNT\t%d" % len(rects))
    for idx, rect in enumerate(rects, 1):
        x, y, w, h = [int(v) for v in rect]
        area = float(w * h)
        score = 1.0
        if areas is not None and idx - 1 < len(areas):
            try:
                area = float(areas[idx - 1])
            except Exception:
                area = float(w * h)
        if scores is not None and idx - 1 < len(scores):
            try:
                score = float(scores[idx - 1])
            except Exception:
                score = 1.0
        print("RECT\t%d\t%d\t%d\t%d\t%d\t%.6f\t%.6f" % (idx, x, y, w, h, area, score))


def load_color_image(image_path, label):
    image = cv2.imread(image_path, cv2.IMREAD_COLOR)
    if image is None:
        raise RuntimeError("%s could not be loaded: %s" % (label, image_path))
    return image


def detect_people(image):
    if image is None:
        return [], [], []
    if len(image.shape) < 2:
        return [], [], []
    height, width = image.shape[:2]
    if width < 64 or height < 128:
        return [], [], []
    hog = cv2.HOGDescriptor()
    hog.setSVMDetector(cv2.HOGDescriptor_getDefaultPeopleDetector())
    rects, weights = hog.detectMultiScale(
        image,
        winStride=(8, 8),
        padding=(8, 8),
        scale=1.05,
    )
    areas = [float(int(w) * int(h)) for (_, _, w, h) in rects]
    return rects, areas, [float(v) for v in weights]


def find_default_face_cascade():
    candidates = []
    if hasattr(cv2, "data") and hasattr(cv2.data, "haarcascades"):
        candidates.append(os.path.join(cv2.data.haarcascades, "haarcascade_frontalface_default.xml"))
    cv2_file = getattr(cv2, "__file__", "")
    if cv2_file:
        base = os.path.dirname(cv2_file)
        candidates.extend(
            [
                os.path.join(base, "data", "haarcascade_frontalface_default.xml"),
                os.path.join(base, "..", "share", "opencv4", "haarcascades", "haarcascade_frontalface_default.xml"),
                os.path.join(base, "..", "share", "opencv5", "haarcascades", "haarcascade_frontalface_default.xml"),
            ]
        )
    candidates.extend(
        sorted(glob.glob("/opt/homebrew/Cellar/opencv/*/share/opencv*/haarcascades/haarcascade_frontalface_default.xml"))
    )
    candidates.extend(
        sorted(glob.glob("/usr/local/Cellar/opencv/*/share/opencv*/haarcascades/haarcascade_frontalface_default.xml"))
    )
    for path in candidates:
        norm = os.path.normpath(path)
        if os.path.exists(norm):
            return norm
    raise RuntimeError("default face cascade could not be found")


def detect_faces(gray):
    cascade_path = find_default_face_cascade()
    cascade = cv2.CascadeClassifier(cascade_path)
    if cascade.empty():
        raise RuntimeError("default face cascade could not be loaded: %s" % cascade_path)
    rects = cascade.detectMultiScale(gray, scaleFactor=1.1, minNeighbors=5, minSize=(24, 24))
    areas = [float(int(w) * int(h)) for (_, _, w, h) in rects]
    return rects, areas, None


def detect_cascade(gray, cascade_path):
    cascade = cv2.CascadeClassifier(cascade_path)
    if cascade.empty():
        raise RuntimeError("cascade XML could not be loaded: %s" % cascade_path)
    rects = cascade.detectMultiScale(gray, scaleFactor=1.1, minNeighbors=3, minSize=(16, 16))
    areas = [float(int(w) * int(h)) for (_, _, w, h) in rects]
    return rects, areas, None


def detect_motion(base_path, target_path, threshold_value, min_area):
    base_image = load_color_image(base_path, "base image")
    target_image = load_color_image(target_path, "target image")
    if base_image.shape[:2] != target_image.shape[:2]:
        target_image = cv2.resize(
            target_image,
            (base_image.shape[1], base_image.shape[0]),
            interpolation=cv2.INTER_LINEAR,
        )
    base_gray = cv2.cvtColor(base_image, cv2.COLOR_BGR2GRAY)
    target_gray = cv2.cvtColor(target_image, cv2.COLOR_BGR2GRAY)
    diff = cv2.absdiff(base_gray, target_gray)
    _, mask = cv2.threshold(diff, int(round(threshold_value)), 255, cv2.THRESH_BINARY)
    mask = cv2.dilate(mask, None, iterations=2)
    contours, _ = cv2.findContours(mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
    records = []
    for contour in contours:
        area = float(cv2.contourArea(contour))
        if area < float(min_area):
            continue
        rect = cv2.boundingRect(contour)
        records.append((rect, area, 1.0))
    records = sort_rect_records(records)
    rects = [item[0] for item in records]
    areas = [item[1] for item in records]
    scores = [item[2] for item in records]
    return rects, areas, scores


def detect_contours(image, threshold_value, min_area):
    gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
    _, mask = cv2.threshold(gray, int(round(threshold_value)), 255, cv2.THRESH_BINARY)
    contours, _ = cv2.findContours(mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
    records = []
    for contour in contours:
        area = float(cv2.contourArea(contour))
        if area < float(min_area):
            continue
        rect = cv2.boundingRect(contour)
        records.append((rect, area, 1.0))
    records = sort_rect_records(records)
    rects = [item[0] for item in records]
    areas = [item[1] for item in records]
    scores = [item[2] for item in records]
    return rects, areas, scores


def detect_template(image_path, template_path, min_score):
    image = load_color_image(image_path, "image")
    template = cv2.imread(template_path, cv2.IMREAD_COLOR)
    if template is None:
        raise RuntimeError("template could not be loaded: %s" % template_path)
    source_gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
    template_gray = cv2.cvtColor(template, cv2.COLOR_BGR2GRAY)
    if (
        template_gray.shape[0] > source_gray.shape[0]
        or template_gray.shape[1] > source_gray.shape[1]
        or template_gray.shape[0] <= 0
        or template_gray.shape[1] <= 0
    ):
        return [], [], []
    result = cv2.matchTemplate(source_gray, template_gray, cv2.TM_CCOEFF_NORMED)
    _, max_val, _, max_loc = cv2.minMaxLoc(result)
    if float(max_val) < float(min_score):
        return [], [], []
    rect = (int(max_loc[0]), int(max_loc[1]), int(template.shape[1]), int(template.shape[0]))
    area = float(template.shape[1] * template.shape[0])
    return [rect], [area], [float(max_val)]


def main(argv):
    if len(argv) < 5:
        sys.stderr.write("usage: bridge.py MODE ARG1 ARG2 NUM1 NUM2\n")
        return 2
    mode = argv[1]
    arg1 = argv[2]
    arg2 = argv[3]
    try:
        num1 = float(argv[4])
    except Exception:
        num1 = -1.0
    try:
        num2 = float(argv[5]) if len(argv) >= 6 else -1.0
    except Exception:
        num2 = -1.0
    version_text = getattr(cv2, "__version__", "unknown")

    if mode in ("HASPERSON", "PERSONDETECT"):
        image = load_color_image(arg1, "image")
        rects, areas, scores = detect_people(image)
        emit_rects(mode, version_text, rects, areas, scores)
        return 0
    if mode in ("HASFACE", "FACEDETECT"):
        image = load_color_image(arg1, "image")
        gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
        rects, areas, scores = detect_faces(gray)
        emit_rects(mode, version_text, rects, areas, scores)
        return 0
    if mode == "CASCADE":
        if not arg1:
            sys.stderr.write("cascade XML path is required for CASCADE mode\n")
            return 4
        image = load_color_image(arg2, "image")
        gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
        rects, areas, scores = detect_cascade(gray, arg1)
        emit_rects(mode, version_text, rects, areas, scores)
        return 0
    if mode in ("MOTIONDETECT", "HASMOTION"):
        rects, areas, scores = detect_motion(arg1, arg2, num1, num2)
        emit_rects(mode, version_text, rects, areas, scores)
        return 0
    if mode == "CONTOURS":
        image = load_color_image(arg1, "image")
        rects, areas, scores = detect_contours(image, num1, num2)
        emit_rects(mode, version_text, rects, areas, scores)
        return 0
    if mode in ("TEMPLATE", "HASTEMPLATE"):
        rects, areas, scores = detect_template(arg1, arg2, num1)
        emit_rects(mode, version_text, rects, areas, scores)
        return 0
    sys.stderr.write("unsupported mode: %s\n" % mode)
    return 5


if __name__ == "__main__":
    raise SystemExit(main(sys.argv))
