import os
import json
import shutil
from collections import defaultdict

# =============================================================================
# [설정 부분]
# =============================================================================
# 입력 JSON 파일 경로 (mmyolo 형식의 coco_annotations_modify.json 파일)
input_json_path = "/ltb/media/ltb/Samsung_T5/ltb/dataset_for_mmyolo_darwin/robo_head_only_1561_250117/it1_head_only_roboflow_1561_각종agumenetation적용_modify.json"

# 원본 이미지들이 위치한 상위 폴더 (이미지 절대 경로에서 /images/ 이하의 경로를 사용)
# (이 변수는 이미지 파일의 상대 경로를 추출할 때 사용됩니다.)
source_images_base_dir = "/media/ltb/Samsung_T5/ltb/dataset_for_mmyolo_darwin/robo_head_only_1561_250117/train/images"

# 출력 데이터셋 경로 (YOLO11 형식의 변환 결과 저장 폴더)
# 하위에 images와 labels 폴더가 생성됩니다.
output_dataset_dir = "/ltb/media/ltb/90887173887158A4/Users/ltb/박스_데이터셋/박스_yolo11/robo_head_only_1561_250117"
output_images_dir = os.path.join(output_dataset_dir, "images")
output_labels_dir = os.path.join(output_dataset_dir, "labels")

# -----------------------------------------------------------------------------
# [category_id 매핑 옵션]
# -----------------------------------------------------------------------------
# mmyolo의 카테고리 id는 보통 1부터 시작하지만, YOLO는 0부터 시작하도록 변환하는 경우가 많습니다.
# 아래 두 가지 방식 중 하나로 선택해서 사용하세요.
#
# (1) 기본적으로 COCO형식 category id에 대해 -1 처리 
#     (예: 1 -> 0, 2 -> 1, 3 -> 2, ...)
#
# (2) 사용자 정의 매핑: 필요시 아래와 같이 매핑 딕셔너리를 정의합니다.
#     예: {1: 0, 2: 1, 3: 2, 4: 3}
#
category_id_mapping = {}  # 빈 딕셔너리이면 기본적으로 -1 방식(-1 처리)을 수행

# =============================================================================
# [유틸리티 함수]
# =============================================================================
def ensure_dir(directory):
    """
    지정한 경로의 폴더가 존재하지 않으면 생성합니다.
    """
    if not os.path.exists(directory):
        os.makedirs(directory)
        print(f"디렉토리 생성: {directory}")
    else:
        print(f"디렉토리 이미 존재: {directory}")

def load_json(json_path):
    """
    주어진 JSON 파일을 읽어 파이썬 딕셔너리로 반환합니다.
    """
    with open(json_path, 'r', encoding='utf-8') as f:
        data = json.load(f)
    return data

def convert_bbox_to_yolo(bbox, img_width, img_height):
    """
    COCO 형식 bbox ([x, y, width, height])를 YOLO 형식 
    (정규화된 x_center, y_center, width, height)로 변환합니다.
    """
    x, y, w, h = bbox
    x_center = x + w / 2.0
    y_center = y + h / 2.0
    # 정규화: 이미지 크기로 나눔
    x_center_norm = x_center / img_width
    y_center_norm = y_center / img_height
    w_norm = w / img_width
    h_norm = h / img_height
    return [x_center_norm, y_center_norm, w_norm, h_norm]

def convert_annotations(json_data):
    """
    JSON 내에서 이미지와 annotation 정보를 추출하여 반환합니다.
    
    만약 JSON 파일에 "annotations" 키가 없으면, images 내의 데이터 구조에 맞게 분리합니다.
    
    Returns:
      images_info: 이미지 정보를 담은 리스트 (각 항목은 딕셔너리)
      annotations_info: image_id를 key로 가지며 해당 이미지의 annotation 목록을 값으로 하는 딕셔너리
    """
    images_info = json_data.get("images", [])
    annotations_list = json_data.get("annotations", [])
    
    # "annotations"가 없으면 images 내 항목 중 file_name이 없는 경우를 annotation으로 간주
    if not annotations_list:
        new_images_info = []
        annotations_list = []
        for item in images_info:
            if "file_name" in item:
                new_images_info.append(item)
            else:
                annotations_list.append(item)
        images_info = new_images_info
    
    # image_id 기준으로 annotation 그룹화
    annotations_info = defaultdict(list)
    for ann in annotations_list:
        img_id = ann["image_id"]
        annotations_info[img_id].append(ann)
    
    return images_info, annotations_info

def get_unique_filename(file_path):
    """
    주어진 이미지의 절대경로(file_path)에서 원본 이미지 파일명의 고유성을 위해
    하위 폴더 이름(또는 여러 단계의 폴더 경로)을 접두사로 추가한 새로운 파일명을 생성합니다.
    
    방법:
      - file_path에서 source_images_base_dir 이후의 상대 경로를 계산합니다.
      - 그 상대 경로의 디렉터리 부분(하위 폴더 경로)을 추출합니다.
      - 디렉터리 구분자인 os.sep를 '_'로 변경하여 하나의 문자열로 결합합니다.
      - 원본 파일명과 결합하여 반환합니다.
    
    Parameters:
      file_path: 절대 이미지 파일 경로 (예: /.../images/하위폴더/00000.jpg)
    
    Returns:
      new_img_base_name: 하위 폴더명이 접두어로 붙은 새로운 파일명 (예: 하위폴더_00000.jpg)
    """
    # 만약 source_images_base_dir가 file_path 내에 포함되어 있다면 상대경로 계산
    if source_images_base_dir in file_path:
        relative_path = os.path.relpath(file_path, source_images_base_dir)
        # 상대경로 내 디렉터리 부분 추출 (하위 폴더 경로)
        folder_path = os.path.dirname(relative_path)
        # 경로 구분자를 언더바(_)로 대체하여 하나의 문자열로 변환
        folder_prefix = folder_path.replace(os.sep, "_") if folder_path else ""
    else:
        folder_prefix = ""
    
    # 원본 파일명 추출
    orig_file_name = os.path.basename(file_path)
    # folder_prefix가 존재하면 접두어로 추가, 없으면 그대로 반환
    new_img_base_name = folder_prefix + "_" + orig_file_name if folder_prefix else orig_file_name
    return new_img_base_name

# =============================================================================
# [변환 메인 함수]
# =============================================================================
def process_conversion():
    """
    전체 변환 과정을 수행합니다.
    
    1. 출력 디렉토리(이미지, 라벨 폴더) 생성
    2. JSON 파일 로드 및 images, annotations 정보 추출
    3. 각 이미지에 대해 하위 폴더명을 포함한 고유 파일명 생성 및 YOLO 형식 라벨 txt 파일 생성
       - 동일 파일명이 이미 존재하면 복사 및 저장을 건너뛰어 덮어쓰기를 방지함
    4. 원본 이미지 파일 복사 (존재 시)
    """
    # 출력 디렉토리 생성
    ensure_dir(output_dataset_dir)
    ensure_dir(output_images_dir)
    ensure_dir(output_labels_dir)
    
    # JSON 파일 로드
    print("JSON 파일 로드 중...")
    json_data = load_json(input_json_path)
    
    # 이미지 및 annotation 정보 분리
    images_info, annotations_info = convert_annotations(json_data)
    print(f"로드된 이미지 수: {len(images_info)}")
    print(f"총 annotation 수: {sum(len(v) for v in annotations_info.values())}")
    
    # 각 이미지별 처리
    for img in images_info:
        img_id = img["id"]
        file_path = img["file_name"]  # 이미지의 절대경로 (하위 폴더 포함)
        img_width = img["width"]
        img_height = img["height"]
        
        # 하위 폴더명을 포함한 고유 파일명을 생성
        new_img_base_name = get_unique_filename(file_path)
        file_name_no_ext, _ = os.path.splitext(new_img_base_name)
        label_file_name = file_name_no_ext + ".txt"  # YOLO 형식의 라벨 파일
        
        # 출력 파일 경로 지정
        out_img_path = os.path.join(output_images_dir, new_img_base_name)
        out_label_path = os.path.join(output_labels_dir, label_file_name)
        
        # 이미지 및 라벨 파일이 이미 존재하면 건너뛰기
        if os.path.exists(out_label_path) and os.path.exists(out_img_path):
            print(f"이미 변환된 파일 스킵: {new_img_base_name}")
            continue
        
        # 해당 이미지에 해당하는 모든 annotation을 YOLO 형식으로 변환
        yolo_labels = []
        anns = annotations_info.get(img_id, [])
        for ann in anns:
            bbox = ann["bbox"]  # COCO 형식 [x, y, width, height]
            yolo_bbox = convert_bbox_to_yolo(bbox, img_width, img_height)
            
            # 원본 category id 추출 및 매핑 처리
            cat_id = ann["category_id"]
            if category_id_mapping:
                yolo_class = category_id_mapping.get(cat_id, cat_id)
            else:
                yolo_class = cat_id - 1
            
            # YOLO 형식 문자열 생성: class, x_center, y_center, width, height (공백 구분)
            label_str = f"{yolo_class} {yolo_bbox[0]} {yolo_bbox[1]} {yolo_bbox[2]} {yolo_bbox[3]}"
            yolo_labels.append(label_str)
        
        # annotation이 있는 경우 라벨 파일 생성
        if yolo_labels:
            if not os.path.exists(out_label_path):
                with open(out_label_path, 'w', encoding='utf-8') as f:
                    f.write("\n".join(yolo_labels))
                print(f"라벨 파일 저장: {out_label_path}")
        else:
            print(f"해당 이미지({new_img_base_name})에 annotation이 없습니다.")
        
        # 원본 이미지 파일 복사 (존재하는 경우)
        if os.path.exists(file_path):
            if not os.path.exists(out_img_path):
                shutil.copy2(file_path, out_img_path)
                print(f"이미지 파일 복사 완료: {out_img_path}")
        else:
            print(f"원본 이미지 파일을 찾을 수 없음: {file_path}")
    
    print("데이터셋 변환 완료.")

# =============================================================================
# [스크립트 실행]
# =============================================================================
if __name__ == "__main__":
    process_conversion()
