import itertools
import random

import numpy as np

from Decode import Decode
from Instance import *


class GA():
    def __init__(self):
        self.Pop_size = 500  # 种群数量
        self.Pc = 0.8  # 交叉概率
        self.Pm = 0.15  # 降低变异概率到0.15
        self.Pv = 0.5  # 选择何种方式进行交叉的概率阈值
        self.Pw = 0.95  # 选择何种方式进行变异的概率阈值
        self.Max_Itertions = 100

    # 适应度
    def fitness(self, CHS, J, Processing_time, M_num, Len):
        Fit = []
        for i in range(len(CHS)):
            d = Decode(J, Processing_time, M_num)  # 实例化一个解码器，传入问题参数
            Fit.append(d.decode(CHS[i], Len))
        return Fit

    def pmx_crossover(self, parent1, parent2, length):
        if length <= 1:
            return parent1.copy(), parent2.copy()

        # 确保交叉区间[a, b]有效（a < b）
        a = random.randint(0, length - 2)
        b = random.randint(a + 1, length - 1)

        child1 = parent1.copy()
        child2 = parent2.copy()

        # 建立映射表（parent1[a:b+1]与parent2[a:b+1]的对应关系）
        map1 = {parent1[i]: parent2[i] for i in range(a, b + 1)}  # p1→p2的映射
        map2 = {parent2[i]: parent1[i] for i in range(a, b + 1)}  # p2→p1的映射

        # 处理child1：交叉区间外的元素替换
        for i in range(length):
            if i < a or i > b:
                val = child1[i]
                # 限制循环次数，避免无限循环（最多循环len(map1)次）
                loop_count = 0
                max_loop = len(map1)
                while val in map1 and map1[val] in parent1[a:b + 1] and loop_count < max_loop:
                    val = map1[val]
                    loop_count += 1
                child1[i] = map1.get(val, val)  # 若超出循环次数，直接使用当前val

        # 处理child2：交叉区间外的元素替换
        for i in range(length):
            if i < a or i > b:
                val = child2[i]
                loop_count = 0
                max_loop = len(map2)
                while val in map2 and map2[val] in parent2[a:b + 1] and loop_count < max_loop:
                    val = map2[val]
                    loop_count += 1
                child2[i] = map2.get(val, val)

        return child1, child2

    # 机器部分交叉
    def machine_cross(self, CHS1, CHS2, T0):
        """
        :param CHS1: 基因1
        :param CHS2: 基因2
        :param T0: 工序总数
        :return: 交叉后的基因
        """
        OS1, MS1 = CHS1[:T0], CHS1[T0:]
        OS2, MS2 = CHS2[:T0], CHS2[T0:]

        # 随机选择交叉位置
        T_r = list(range(T0))
        random.shuffle(T_r)
        r = random.randint(1, T0 // 2)  # 交叉部分长度
        R = T_r[:r]

        # 交换机器选择部分
        for i in R:
            MS1[i], MS2[i] = MS2[i], MS1[i]

        return np.hstack((OS1, MS1)), np.hstack((OS2, MS2))

    # 工序部分交叉（使用PMX交叉）
    def operation_cross(self, CHS1, CHS2, T0, J_num):
        OS1 = list(CHS1[T0:2 * T0])  # 确保转换为列表
        OS2 = list(CHS2[T0:2 * T0])
        MS1 = CHS1[0:T0].copy()
        MS2 = CHS2[0:T0].copy()

        # 调用修复后的PMX交叉
        new_os1, new_os2 = self.pmx_crossover(OS1, OS2, T0)

        CHS1 = np.hstack((MS1, new_os1))
        CHS2 = np.hstack((MS2, new_os2))
        return CHS1, CHS2

    # 机器部分变异（仅在可用机器集中选择）
    def machine_variation(self, CHS, O, T0, J):
        """
        :param CHS: 基因
        :param O: 加工时间矩阵
        :param T0: 工序总数
        :param J: 各工件加工信息
        :return: 变异后的基因
        """
        OS = CHS[:T0]
        MS = CHS[T0:]

        # 确定变异位置数量
        r = random.randint(1, max(1, T0 // 10))  # 最多变异10%的位置
        positions = random.sample(range(T0), r)

        for pos in positions:
            # 找到该位置对应的工件和工序
            site = 0
            job = -1
            op = -1
            for k, v in J.items():
                if site + v > pos:
                    job = k - 1  # 转换为0索引
                    op = pos - site
                    break
                site += v

            # 获取该工序的可用机器
            D = O[job][op]
            available_machines = [k for k, val in enumerate(D) if val != 9999]
            if len(available_machines) <= 1:
                continue  # 只有一个可用机器时不变异

            # 随机选择一个不同的可用机器
            current_idx = MS[pos]
            current_machine = available_machines[current_idx]
            new_machine = random.choice([m for m in available_machines if m != current_machine])
            MS[pos] = available_machines.index(new_machine)

        return np.hstack((OS, MS))

    # 工序部分变异
    def operation_variation(self, CHS, T0, J_num, J, O, M_num):
        """
        :param CHS: 基因
        :param T0: 工序总数
        :param J_num: 工件总数
        :param J: 各工件加工信息
        :param O: 加工时间矩阵
        :param M_num: 机器总数
        :return: 变异后的基因
        """
        OS = list(CHS[:T0])
        MS = CHS[T0:]

        # 随机选择两个位置交换
        i, j = random.sample(range(T0), 2)
        OS[i], OS[j] = OS[j], OS[i]

        return np.hstack((OS, MS))