# Python 实现的归一化评价函数
import numpy as np
import pandas as pd

# 导入所需模块
import sys
import os

# 添加MFM目录到路径
current_dir = os.path.dirname(os.path.abspath(__file__))
# py_tools -> route -> FFEv3_2 -> FFE -> MFM -> 根目录
root_dir = os.path.dirname(os.path.dirname(os.path.dirname(os.path.dirname(os.path.dirname(current_dir)))))
if root_dir not in sys.path:
    sys.path.append(root_dir)

from myffe.tools.interactive import interactive
from myffe.tools.color_printer import ColorPrinter
from myffe.tools.len_match import len_match
from myffe.tools.visualize_route import visualize_route

def is_numeric_columns(col):
    """判断列是否为数值类型"""
    for s in col:
        try:
            float(s)
        except ValueError:
            return False
    return True

def str_to_double(col):
    """将字符串列转换为double数组"""
    return [float(s) for s in col]

def select_best_normalizer(x):
    """选择最佳归一化方法"""
    n = len(x)
    
    # 计算各种归一化方法的评分
    score_l1 = 0.0
    score_l2 = 0.0
    score_max = 0.0
    score_robust = 0.0
    score_power = 0.0
    score_log = 0.0
    score_sqrt = 0.0
    
    # 计算基本统计量
    mean = np.mean(x)
    variance = np.var(x)
    std_dev = np.sqrt(variance)
    max_val = max(x)
    min_val = min(x)
    range_val = max_val - min_val
    q1 = np.percentile(x, 25)
    q3 = np.percentile(x, 75)
    iqr = q3 - q1
    
    # 计算L1评分 - 基于数据的分布特性，而不是仅仅稀疏性
    if range_val > 0:
        # 对于非稀疏数据，基于数据的均匀性评分
        # 计算数据的变异系数
        cv = std_dev / mean if mean > 0 else 0
        # 变异系数适中时L1评分较高
        score_l1 = np.exp(-0.5 * ((cv - 1.0) ** 2))
    else:
        score_l1 = 0.0
    
    # 计算L2评分 - 基于数据的分布均匀性
    if std_dev > 0:
        # 标准差适中时得分最高
        # 调整参数，使标准差在0.5-2之间时得分较高
        score_l2 = np.exp(-0.5 * ((np.log(std_dev) - 0) ** 2))
    else:
        score_l2 = 0.0
    
    # 计算Max评分 - 基于数据的范围
    if range_val > 0:
        # 对于不同范围的数据，都有合理的评分
        # 调整参数，使范围在1-1000之间时都能得到合理的评分
        score_max = 1.0 / (1.0 + np.exp(-0.01 * (np.log10(range_val + 1) - 1)))
    else:
        score_max = 0.0
    
    # 计算Robust评分 - 基于数据的异常值情况
    if iqr > 0 and range_val > 0:
        # IQR与范围的比例越大，说明异常值越多，越适合Robust归一化
        outlier_ratio = iqr / range_val
        score_robust = outlier_ratio
    else:
        score_robust = 0.0
    
    # 计算幂次归一化评分 - 基于数据的偏度
    if std_dev > 0:
        skewness = np.abs(np.mean(((x - mean) / std_dev) ** 3))
        # 偏度越大，越适合使用幂次归一化
        score_power = min(skewness / 2, 1.0)  # 偏度为2时得分约为1.0
    else:
        score_power = 0.0
    
    # 计算对数归一化评分 - 基于数据的取值范围和偏度
    if all(v > 0 for v in x):  # 对数归一化要求所有值为正
        # 数据范围较大且偏度较大时适合对数归一化
        if max_val > 1:
            log_range = np.log10(max_val) - np.log10(min_val)
            # 计算数据的偏度
            if std_dev > 0:
                skewness = np.mean(((x - mean) / std_dev) ** 3)
                # 正偏度较大时更适合对数归一化
                if skewness > 0:
                    score_log = min((log_range / 3 + skewness / 3), 1.0)
                else:
                    score_log = min(log_range / 5, 1.0)
            else:
                score_log = min(log_range / 5, 1.0)
        else:
            score_log = 0.0
    else:
        score_log = 0.0
    
    # 计算平方根归一化评分 - 基于数据的取值范围和分布
    if all(v >= 0 for v in x):  # 平方根归一化要求所有值非负
        # 数据为非负数且有一定范围时适合平方根归一化
        if max_val > 0:
            # 计算数据的偏度
            if std_dev > 0:
                skewness = np.mean(((x - mean) / std_dev) ** 3)
                # 正偏度适中时更适合平方根归一化
                if skewness > 0 and skewness < 2:
                    score_sqrt = min((np.sqrt(range_val) / 10 + skewness / 2), 1.0)
                else:
                    score_sqrt = min(np.sqrt(range_val) / 15, 1.0)
            else:
                score_sqrt = min(np.sqrt(range_val) / 15, 1.0)
        else:
            score_sqrt = 0.0
    else:
        score_sqrt = 0.0
    
    # 为不同类型的列添加特定的偏好
    # 检测列的类型（基于值的范围和分布）
    is_id_column = max_val > 100 and min_val == 1 and np.allclose(np.diff(sorted(x)), 1)  # 连续ID
    is_age_column = min_val >= 0 and max_val <= 120 and range_val > 5  # 年龄列
    is_score_column = min_val >= 0 and max_val <= 10 and range_val < 5  # 分数列（如GPA）
    
    # 根据列类型调整评分
    if is_id_column:
        # ID列通常不需要归一化，但如果必须选择，推荐Max或L2
        score_max *= 1.5
        score_l2 *= 1.2
    elif is_age_column:
        # 年龄列适合L2或Max归一化
        score_l2 *= 1.3
        score_max *= 1.2
    elif is_score_column:
        # 分数列适合L2或Robust归一化
        score_l2 *= 1.3
        score_robust *= 1.2
    
    # 最后进行比较，返回评分最高的那个归一化方法
    scores = [score_l1, score_l2, score_max, score_robust, score_power, score_log, score_sqrt]
    best_index = np.argmax(scores)
    
    # 返回对应的归一化方法
    normalizer_methods = ['L1 Normalization', 'L2 Normalization', 'Max Normalization', 'Robust Normalization', 'Power Normalization', 'Log Normalization', 'Square Root Normalization']
    return normalizer_methods[best_index]

def get_best_normalizer(col):
    """获取最佳归一化方法"""
    data = str_to_double(col)
    return select_best_normalizer(data)

def analyze_normalization(data, auto_test=False):
    """分析数据的归一化需求，为每列推荐适合的归一化方法
    
    Args:
        data: pandas DataFrame，输入数据
        auto_test: bool，是否自动测试模式，默认为 False
    
    Returns:
        dict: 包含归一化参数的字典，使用列名列表和方法列表的结构
    """
    normalized_preprocessing = {
        'normalizer_method_choice': [],
        'normalized_col': []
    }

    normalizer_methods = ['L1 Normalization', 'L2 Normalization', 'Max Normalization', 'Robust Normalization', 'Power Normalization', 'Log Normalization', 'Square Root Normalization', 'skip']
    
    # 筛选出数值列
    numeric_columns = []
    for col in data.columns:
        # 检查是否为数值列
        col_data = data[col].astype(str).tolist()
        if not col_data or all(val == '' for val in col_data):
            continue
        if is_numeric_columns(col_data):
            numeric_columns.append(col)
    
    if not numeric_columns:
        ColorPrinter.print_info("数据中没有需要归一化的数值列")
        return 'skip'
    
    inter_normalized = False
    if auto_test:
        print("\033[31m下面进行测试使用智能推荐\033[0m")
        inter_normalized = True
    else:
        inter_normalized_tem = interactive(['ok', 'no'] , title="智能推荐？", description="选择'ok'将自动为所有数值列推荐最佳归一化方法，选择'no'需要手动选择", min_select=1, max_select=1).run()
        inter_normalized_tem = inter_normalized_tem[0]
        if inter_normalized_tem == 'ok':
            inter_normalized = True
    
    if not inter_normalized:
        while True:
            # 重置参数列表
            normalized_preprocessing['normalized_col'] = []
            normalized_preprocessing['normalizer_method_choice'] = []
            
            selected_cols = interactive(numeric_columns, title="请按上下箭头选择，回车确认归一化处理列,点击'm'键进行多选择", description="选择要进行归一化处理的列，可以多选", min_select=1, max_select=len(numeric_columns)).run()
            # 确保返回值是列表
            normalized_preprocessing['normalized_col'] = selected_cols
            ColorPrinter.print_success('你已选择归一化处理列：' + str(normalized_preprocessing['normalized_col']))
            
            # 为每列选择归一化方法
            for col in normalized_preprocessing['normalized_col']:
                selected_method = interactive(normalizer_methods, title=f"请按上下箭头选择，回车确认{col}的归一化方法", description="L1归一化:基于曼哈顿距离, L2归一化:基于欧几里得距离, Max归一化:基于最大值缩放, Robust归一化:对异常值不敏感, 幂次归一化:适合偏度较大的数据, 对数归一化:适合取值范围大的正数数据, 平方根归一化:适合非负数据, 跳过:不进行归一化处理", min_select=1, max_select=1).run()
                # 确保返回值是单个字符串
                method = selected_method[0]
                normalized_preprocessing['normalizer_method_choice'].append(method)
                ColorPrinter.print_success(f'你已选择{col}的归一化方法：{method}')
            
            # 检查列数量和方法数量是否匹配
            if not len_match(normalized_preprocessing['normalized_col'], normalized_preprocessing['normalizer_method_choice']):
                ColorPrinter.print_warning('请重新选择归一化方法')
                continue
            
            break
    else:
        # 智能推荐模式
        for col in numeric_columns:
            col_data = data[col].astype(str).tolist()
            best_method = get_best_normalizer(col_data)
            normalized_preprocessing['normalizer_method_choice'].append(best_method)
            normalized_preprocessing['normalized_col'].append(col)
            ColorPrinter.print_info(f"为列 '{col}' 推荐归一化方法：{best_method}")
    
    # 过滤掉跳过的列
    filtered_columns = [] 
    filtered_methods = []
    for col, method in zip(normalized_preprocessing['normalized_col'], normalized_preprocessing['normalizer_method_choice']):
        if method != '跳过':
            filtered_columns.append(col)
            filtered_methods.append(method)
    
    normalized_preprocessing['normalized_col'] = filtered_columns
    normalized_preprocessing['normalizer_method_choice'] = filtered_methods
    
    result = {
        'normalized_preprocessing': normalized_preprocessing
    }
    visualize_route(result)
    return result

def normalized_py(data, auto_test=False):
    """使用Python实现为每一列确定最佳归一化方法
    
    Args:
        data: pandas DataFrame，输入数据
        auto_test: bool，是否自动测试模式，默认为 False
    
    Returns:
        dict: 包含归一化参数的字典
    """
    return analyze_normalization(data, auto_test=auto_test)

if __name__ == "__main__":
    # 测试代码
    import pandas as pd
    import numpy as np
    
    # 创建测试数据
    np.random.seed(42)
    data = pd.DataFrame({
        'numeric_col1': np.random.normal(100, 10, 20),
        'numeric_col2': np.random.normal(50, 5, 20),
        'numeric_col3': np.random.uniform(0, 100, 20)
    })
    
    ColorPrinter.print_info("测试数据:")
    ColorPrinter.print_any(data.head(), color='white')
    ColorPrinter.print_any("\n数据统计:", color='blue')
    ColorPrinter.print_any(data.describe(), color='white')
    
    # 测试归一化处理
    ColorPrinter.print_info("\n测试归一化处理:")
    result = normalized_py(data)
    ColorPrinter.print_success("处理结果:")
    ColorPrinter.print_any(result, color='white')




"""
这个文件输入一个data
输出一个字典
格式是：
{
    'normalized_preprocessing': {
        'normalizer_method_choice': [...], 在里面是每个列的归一化方法列表
        'normalized_col': [...], 在里面是每个列的归一化列名列表
    }
}

normalizer_method_choice的取值有
    'L1 Normalization'   # L1 Normalization
    'L2 Normalization'   # L2 Normalization
    'Max Normalization'   # Max Normalization
    'Robust Normalization'   # Robust Normalization, not sensitive to outliers
    'Power Normalization'   # Power Normalization, suitable for data with large skewness
    'Log Normalization'   # Log Normalization, suitable for positive data with large range
    'Square Root Normalization'   # Square Root Normalization, suitable for non-negative data
    'skip'   # Skip normalization
"""

