# 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
    
    # 计算L1评分 - 基于数据的稀疏性（非零值比例）
    non_zero_count = sum(1 for v in x if v != 0.0)
    score_l1 = non_zero_count / n  # 已经在 0-1 之间
    
    # 计算L2评分 - 基于数据的分布均匀性
    mean = np.mean(x)
    variance = np.var(x)
    std_dev = np.sqrt(variance)
    
    # L2归一化适合标准差适中的数据
    if std_dev > 0:
        # 标准差接近1时得分最高，使用高斯函数平滑，结果在 0-1 之间
        score_l2 = np.exp(-0.5 * ((std_dev - 1.0) ** 2))
    else:
        score_l2 = 0.0
    
    # 计算Max评分 - 基于数据的范围
    max_val = max(x)
    min_val = min(x)
    range_val = max_val - min_val
    
    # Max归一化适合范围较大的数据
    if range_val > 0:
        # 使用sigmoid函数将范围映射到 0-1 之间
        # 调整参数使范围为100时得分约为0.9，范围为1000时得分约为0.99
        score_max = 1.0 / (1.0 + np.exp(-0.005 * (range_val - 100)))
    else:
        score_max = 0.0
    
    # 计算Robust评分 - 基于数据的异常值情况
    # 使用四分位距(IQR)来衡量数据的分散程度，IQR越大，越适合使用Robust归一化
    q1 = np.percentile(x, 25)
    q3 = np.percentile(x, 75)
    iqr = q3 - q1
    if iqr > 0:
        # IQR越大，得分越高
        score_robust = min(iqr / range_val if range_val > 0 else 1.0, 1.0)
    else:
        score_robust = 0.0
    
    # 计算幂次归一化评分 - 基于数据的偏度
    if std_dev > 0:
        skewness = np.abs(np.mean(((x - mean) / std_dev) ** 3))
        # 偏度越大，越适合使用幂次归一化
        score_power = min(skewness / 3, 1.0)  # 偏度为3时得分约为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)
            score_log = min(log_range / 5, 1.0)  # 对数范围为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:
            sqrt_range = np.sqrt(max_val) - np.sqrt(min_val)
            score_sqrt = min(sqrt_range / 10, 1.0)  # 平方根范围为10时得分约为1.0
        else:
            score_sqrt = 0.0
    else:
        score_sqrt = 0.0
    
    # 最后进行比较，返回评分最高的那个归一化方法
    scores = [score_l1, score_l2, score_max, score_robust, score_power, score_log, score_sqrt]
    best_index = np.argmax(scores)
    
    # 返回对应的归一化方法
    normalizer_methods = ['L1归一化', 'L2归一化', 'Max归一化', 'Robust归一化', '幂次归一化', '对数归一化', '平方根归一化']
    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归一化', 'L2归一化', 'Max归一化', 'Robust归一化', '幂次归一化', '对数归一化', '平方根归一化', '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归一化'   # L1归一化
    'L2归一化'   # L2归一化
    'Max归一化'   # Max归一化
    'Robust归一化'   # Robust归一化，对异常值不敏感
    '幂次归一化'   # 幂次归一化，适合偏度较大的数据
    '对数归一化'   # 对数归一化，适合取值范围大的正数数据
    '平方根归一化'   # 平方根归一化，适合非负数据
    '跳过'   # 跳过归一化处理
"""

