"""
평가 시스템 설정 파일
평가 지표별 가중치, 임계값, 렌더링 파라미터 등을 정의합니다.
"""

import math
import numpy as np

# 평가 지표별 가중치 설정
EVALUATION_WEIGHTS = {
    '2d_map': 0.25,          # 2D mAP 가중치
    '3d_map': 0.30,          # 3D mAP 가중치
    'chamfer_distance': 0.20, # Chamfer Distance 가중치
    'emd': 0.15,             # EMD 가중치
    'class_accuracy': 0.10   # 클래스 정확도 가중치
}

# 평가 지표별 임계값 설정 (합리적인 기준 적용)
EVALUATION_THRESHOLDS = {
    '2d_map': 0.8,           # 2D mAP 임계값
    '3d_map': 0.7,           # 3D mAP 임계값
    'chamfer_distance': 0.3, # Chamfer Distance 임계값 (합리적으로 조정)
    'emd': 0.3,              # EMD 임계값 (합리적으로 조정)
    'class_accuracy': 0.8    # 클래스 정확도 임계값 (80% 정확도에서 50점)
}

# 렌더링 파라미터 설정
RENDERING_CONFIG = {
    'num_views': 36,         # 렌더링할 뷰의 개수
    'image_size': (512, 512), # 렌더링 이미지 크기 (width, height)
    'lighting_conditions': ['default', 'bright', 'dim'], # 조명 조건
    'camera_distance': 2.0,  # 카메라 거리
    'camera_elevation': 30.0, # 카메라 고도각
    'num_views_for_evaluation': 8 # 평가용 뷰 개수
}

# 점군 생성 설정
POINTCLOUD_CONFIG = {
    'num_points': 10000,     # 기본 점군 점의 개수
    'num_points_emd': 5000,  # EMD 계산용 점의 개수
    'num_points_chamfer': 10000 # Chamfer Distance 계산용 점의 개수
}

# 클래스 분류 설정
CLASSIFICATION_CONFIG = {
    'num_classes': 10,       # 예상 클래스 개수
    'class_names': [         # 클래스 이름 리스트
        'chair', 'table', 'sofa', 'bed', 'desk',
        'bookshelf', 'lamp', 'cabinet', 'door', 'window'
    ],
    'clustering_enabled': True, # 클러스터링 기반 분류 사용 여부
    'num_clusters': 5        # 클러스터 개수
}

# 성능 등급 기준
GRADE_THRESHOLDS = {
    'A': 90.0,               # A등급: 90점 이상
    'B': 80.0,               # B등급: 80점 이상
    'C': 70.0,               # C등급: 70점 이상
    'D': 60.0,               # D등급: 60점 이상
    'F': 0.0                 # F등급: 60점 미만
}

# IoU 임계값 설정
IOU_THRESHOLDS = {
    '2d_iou': [0.5, 0.75],   # 2D IoU 임계값 리스트
    '3d_iou': [0.5, 0.7]     # 3D IoU 임계값 리스트
}

# 출력 설정
OUTPUT_CONFIG = {
    'save_images': True,     # 렌더링된 이미지 저장 여부
    'save_pointclouds': False, # 점군 데이터 저장 여부
    'generate_report': True, # 리포트 생성 여부
    'report_format': 'html', # 리포트 형식 ('html', 'json', 'txt')
    'visualize_results': True # 결과 시각화 여부
}

# 파일 경로 설정
FILE_PATHS = {
    'output_dir': 'results', # 결과 출력 디렉토리
    'images_dir': 'results/images', # 이미지 저장 디렉토리
    'reports_dir': 'results/reports', # 리포트 저장 디렉토리
    'temp_dir': 'results/temp' # 임시 파일 디렉토리
}

# 로깅 설정
LOGGING_CONFIG = {
    'level': 'INFO',         # 로그 레벨
    'format': '%(asctime)s - %(name)s - %(levelname)s - %(message)s',
    'file': 'results/evaluation.log' # 로그 파일 경로
}

# 성능 최적화 설정
PERFORMANCE_CONFIG = {
    'parallel_processing': True, # 병렬 처리 사용 여부
    'max_workers': 4,        # 최대 워커 수
    'memory_limit_gb': 8,    # 메모리 제한 (GB)
    'cache_enabled': True,   # 캐시 사용 여부
    'cache_size_mb': 100     # 캐시 크기 (MB)
}

# 검증 설정
VALIDATION_CONFIG = {
    'validate_input_files': True, # 입력 파일 검증 여부
    'check_file_formats': True,   # 파일 형식 검증 여부
    'min_file_size_kb': 1,        # 최소 파일 크기 (KB)
    'max_file_size_mb': 100       # 최대 파일 크기 (MB)
}

# 전체 평가 설정
EVALUATION_CONFIG = {
    'weights': EVALUATION_WEIGHTS,
    'thresholds': EVALUATION_THRESHOLDS,
    'rendering': RENDERING_CONFIG,
    'pointcloud': POINTCLOUD_CONFIG,
    'classification': CLASSIFICATION_CONFIG,
    'grade_thresholds': GRADE_THRESHOLDS,
    'iou_thresholds': IOU_THRESHOLDS,
    'output': OUTPUT_CONFIG,
    'file_paths': FILE_PATHS,
    'logging': LOGGING_CONFIG,
    'performance': PERFORMANCE_CONFIG,
    'validation': VALIDATION_CONFIG
}

# 설정 검증 함수
def validate_config(config: dict) -> bool:
    """
    설정의 유효성을 검증합니다.
    
    Args:
        config (dict): 검증할 설정
        
    Returns:
        bool: 설정 유효성
    """
    try:
        # 가중치 합이 1.0인지 확인
        weights = config.get('weights', {})
        weight_sum = sum(weights.values())
        if abs(weight_sum - 1.0) > 1e-6:
            print(f"경고: 가중치 합이 1.0이 아닙니다: {weight_sum}")
        
        # 임계값이 유효한 범위인지 확인
        thresholds = config.get('thresholds', {})
        for metric, threshold in thresholds.items():
            if not (0.0 <= threshold <= 1.0):
                print(f"경고: {metric} 임계값이 유효 범위를 벗어났습니다: {threshold}")
        
        # 렌더링 설정 검증
        rendering = config.get('rendering', {})
        if rendering.get('num_views', 0) <= 0:
            print("경고: 렌더링 뷰 개수가 0 이하입니다")
        
        return True
        
    except Exception as e:
        print(f"설정 검증 실패: {e}")
        return False

# 설정 업데이트 함수
def update_config(new_config: dict) -> dict:
    """
    새로운 설정으로 기존 설정을 업데이트합니다.
    
    Args:
        new_config (dict): 새로운 설정
        
    Returns:
        dict: 업데이트된 설정
    """
    updated_config = EVALUATION_CONFIG.copy()
    
    for key, value in new_config.items():
        if key in updated_config:
            if isinstance(value, dict) and isinstance(updated_config[key], dict):
                updated_config[key].update(value)
            else:
                updated_config[key] = value
        else:
            updated_config[key] = value
    
    return updated_config

# 설정 가져오기 함수
def get_config(config_name: str = None):
    """
    설정을 가져옵니다.
    
    Args:
        config_name (str): 가져올 설정 이름 (None이면 전체 설정)
        
    Returns:
        설정 값 또는 전체 설정
    """
    if config_name is None:
        return EVALUATION_CONFIG
    else:
        return EVALUATION_CONFIG.get(config_name, {})

# 개선된 점수 정규화 함수
def normalize_score_improved(metric_name: str, raw_value: float, threshold: float = None) -> float:
    """
    개선된 점수 정규화 (동적 임계값, 무한대 값 처리, 지표별 특성 고려).
    
    Args:
        metric_name (str): 지표 이름
        raw_value (float): 원본 값
        threshold (float): 임계값 (None이면 기본값 사용)
        
    Returns:
        float: 정규화된 점수 (0-100)
    """
    import math
    
    # 무한대 값 처리 (더 관대한 처리)
    if math.isinf(raw_value):
        if raw_value > 0:  # 양의 무한대
            # 무한대 값을 임계값의 10배로 간주하여 부분 점수 부여
            if threshold is None:
                threshold = EVALUATION_THRESHOLDS.get(metric_name, 1.0)
            return max(5.0, 100.0 * threshold / (threshold * 10))  # 최소 5점 보장
        else:  # 음의 무한대
            return 100.0
    
    # NaN 값 처리
    if math.isnan(raw_value):
        return 0.0
    
    # 기본 임계값 설정
    if threshold is None:
        threshold = EVALUATION_THRESHOLDS.get(metric_name, 1.0)
    
    # 지표별 특성에 따른 정규화
    if metric_name in ['chamfer_distance', 'emd']:
        # 낮을수록 좋은 지표 (역정규화)
        return _normalize_lower_is_better(raw_value, threshold)
    else:
        # 높을수록 좋은 지표
        return _normalize_higher_is_better(raw_value, threshold)

def _normalize_lower_is_better(raw_value: float, threshold: float) -> float:
    """
    낮을수록 좋은 메트릭을 위한 정규화 함수 (완화된 기준 적용)
    Args:
        raw_value: 원본 값
        threshold: 임계값 (이 값에서 70점)
    Returns:
        정규화된 점수 (0-100)
    """
    if raw_value <= 0:
        return 100.0
    
    # 완화된 임계값 검증 (임계값의 10배 이상이면 0점)
    if raw_value > threshold * 10:  
        return 0.0
    
    # 완화된 정규화 적용
    ratio = raw_value / threshold
    if ratio <= 0.5:
        # 임계값의 절반 이하: 90-100점 (우수)
        normalized = 100.0 - (ratio * 20.0)
    elif ratio <= 1.0:
        # 임계값 이하: 70-90점 (양호)
        normalized = 90.0 - ((ratio - 0.5) * 40.0)
    elif ratio <= 2.0:
        # 임계값의 2배 이하: 40-70점 (보통)
        normalized = 70.0 - ((ratio - 1.0) * 30.0)
    else:
        # 임계값의 2배 초과: 0-40점 (완만한 지수적 감소)
        normalized = 40.0 * math.exp(-(ratio - 2.0) * 1.0)
    
    return max(0.0, min(100.0, normalized))

def _normalize_higher_is_better(raw_value: float, threshold: float) -> float:
    """높을수록 좋은 지표의 정규화 (더 엄격한 기준 적용)."""
    if raw_value <= 0:
        return 0.0
    
    # 더 엄격한 정규화 적용
    ratio = raw_value / threshold
    if ratio >= 1.0:
        # 임계값 이상: 80-100점 (우수)
        normalized = 80.0 + min(20.0, (ratio - 1.0) * 20.0)
    elif ratio >= 0.8:
        # 임계값의 80% 이상: 60-80점 (양호)
        normalized = 60.0 + ((ratio - 0.8) * 100.0)
    elif ratio >= 0.5:
        # 임계값의 50% 이상: 30-60점 (보통)
        normalized = 30.0 + ((ratio - 0.5) * 100.0)
    else:
        # 임계값의 50% 미만: 0-30점 (부족)
        normalized = ratio * 60.0
    
    return max(0.0, min(100.0, normalized))

def calculate_dynamic_threshold(metric_name: str, historical_values: list) -> float:
    """
    동적 임계값 계산 (과거 값들을 기반으로).
    
    Args:
        metric_name (str): 지표 이름
        historical_values (list): 과거 값들의 리스트
        
    Returns:
        float: 동적 임계값
    """
    if not historical_values:
        return EVALUATION_THRESHOLDS.get(metric_name, 1.0)
    
    import numpy as np
    
    # 이상치 제거 (IQR 방법)
    q1 = np.percentile(historical_values, 25)
    q3 = np.percentile(historical_values, 75)
    iqr = q3 - q1
    
    if iqr > 0:
        lower_bound = q1 - 1.5 * iqr
        upper_bound = q3 + 1.5 * iqr
        filtered_values = [v for v in historical_values if lower_bound <= v <= upper_bound]
    else:
        filtered_values = historical_values
    
    if not filtered_values:
        return EVALUATION_THRESHOLDS.get(metric_name, 1.0)
    
    # 지표별 동적 임계값 계산
    if metric_name in ['chamfer_distance', 'emd']:
        # 낮을수록 좋은 지표: 75% 분위수 사용
        dynamic_threshold = np.percentile(filtered_values, 75)
    else:
        # 높을수록 좋은 지표: 25% 분위수 사용
        dynamic_threshold = np.percentile(filtered_values, 25)
    
    # 기본 임계값과의 가중 평균 (안정성 확보)
    default_threshold = EVALUATION_THRESHOLDS.get(metric_name, 1.0)
    weight = 0.7  # 동적 임계값에 더 높은 가중치
    
    final_threshold = weight * dynamic_threshold + (1 - weight) * default_threshold
    
    return max(0.001, final_threshold)  # 최소값 보장

def get_metric_characteristics(metric_name: str) -> dict:
    """
    지표별 특성 정보를 반환합니다.
    
    Args:
        metric_name (str): 지표 이름
        
    Returns:
        dict: 지표 특성 정보
    """
    characteristics = {
        '2d_map': {
            'type': 'higher_is_better',
            'range': (0.0, 1.0),
            'description': '2D 객체 감지 정확도',
            'normalization_method': 'sigmoid'
        },
        '3d_map': {
            'type': 'higher_is_better',
            'range': (0.0, 1.0),
            'description': '3D 객체 감지 정확도',
            'normalization_method': 'sigmoid'
        },
        'chamfer_distance': {
            'type': 'lower_is_better',
            'range': (0.0, float('inf')),
            'description': '3D 기하학적 유사성',
            'normalization_method': 'exponential'
        },
        'emd': {
            'type': 'lower_is_better',
            'range': (0.0, float('inf')),
            'description': '3D 분포 유사성',
            'normalization_method': 'exponential'
        },
        'class_accuracy': {
            'type': 'higher_is_better',
            'range': (0.0, 1.0),
            'description': '클래스 분류 정확도',
            'normalization_method': 'linear'
        }
    }
    
    return characteristics.get(metric_name, {
        'type': 'higher_is_better',
        'range': (0.0, 1.0),
        'description': '알 수 없는 지표',
        'normalization_method': 'linear'
    })

def validate_score_normalization(metric_name: str, raw_value: float, normalized_score: float) -> bool:
    """
    점수 정규화 결과의 유효성을 검증합니다.
    
    Args:
        metric_name (str): 지표 이름
        raw_value (float): 원본 값
        normalized_score (float): 정규화된 점수
        
    Returns:
        bool: 유효성 여부
    """
    # 기본 범위 검증
    if not (0.0 <= normalized_score <= 100.0):
        return False
    
    # 무한대/NaN 값 처리 검증
    import math
    if math.isinf(raw_value) or math.isnan(raw_value):
        return 0.0 <= normalized_score <= 100.0
    
    # 지표별 특성 검증
    characteristics = get_metric_characteristics(metric_name)
    
    if characteristics['type'] == 'higher_is_better':
        # 높을수록 좋은 지표: 원본 값이 높으면 정규화 점수도 높아야 함
        if raw_value > 0 and normalized_score < 10:
            return False
    else:
        # 낮을수록 좋은 지표: 원본 값이 낮으면 정규화 점수는 높아야 함
        if raw_value < 0.1 and normalized_score < 50:
            return False
    
    return True
