from data.type import OptResult
from opt.smm.path_plan import PathPlanOpt
from collections import defaultdict

import numpy as np
import copy


class BaseOpt:
    def __init__(self, config, part_data, step_data, feeder_data=None):
        self.part_data = part_data
        self.step_data = step_data
        self.feeder_data = feeder_data
        self.config = config

        self.result = OptResult()
        self.path_planner = PathPlanOpt(config, part_data, step_data)

        self.cycle_weight = 1
        self.nozzle_change_weight = 1
        self.pickup_weight = 1
        self.place_weight = 1
        self.move_weight = 1


class FeederAssignOpt:
    def __init__(self, config, part_data, step_data, feeder_data=None):
        self.part_data = part_data
        self.step_data = step_data
        self.feeder_data = feeder_data
        self.config = config

    def find_commonpart(self, head_group, feeder_group):
        feeder_group_len = len(feeder_group)

        max_length, max_common_part = -1, []
        for offset in range(-self.config.head_num + 1, feeder_group_len - 1):
            # offset: head_group相对于feeder_group的偏移量
            length, common_part = 0, []
            for hd_index in range(self.config.head_num):
                fd_index = hd_index + offset
                if fd_index < 0 or fd_index >= feeder_group_len:
                    common_part.append(-1)
                    continue

                if head_group[hd_index] == feeder_group[fd_index] and head_group[hd_index] != -1:
                    length += 1
                    common_part.append(head_group[hd_index])
                else:
                    common_part.append(-1)
            if length > max_length:
                max_length = length
                max_common_part = common_part

        return max_common_part

    def do(self, part_result, cycle_result):
        slot_result, feeder_group = [], []
        feeder_limit = {idx: data.fdn for idx, data in self.part_data.iterrows()}

        for part_cycle in part_result:
            new_feeder_group = []
            for part in part_cycle:
                if part == -1 or feeder_limit[part] == 0 or new_feeder_group.count(part) >= feeder_limit[part]:
                    new_feeder_group.append(-1)
                else:
                    new_feeder_group.append(part)

            if len(new_feeder_group) == 0:
                continue

            while sum(i >= 0 for i in new_feeder_group) != 0:
                max_common_part, index = [], -1
                max_common_length = -1
                for feeder_index in range(len(feeder_group)):
                    common_part = self.find_commonpart(new_feeder_group, feeder_group[feeder_index])
                    if sum(i > 0 for i in common_part) > max_common_length:
                        max_common_length = sum(i > 0 for i in common_part)
                        max_common_part, index = common_part, feeder_index

                new_feeder_length = 0
                for feeder in new_feeder_group:
                    if feeder != -1 and feeder_limit[feeder] > 0:
                        new_feeder_length += 1

                if new_feeder_length > max_common_length:
                    # 新分配供料器
                    feeder_group.append([])
                    for feeder_index in range(len(new_feeder_group)):
                        feeder = new_feeder_group[feeder_index]
                        if feeder != -1 and feeder_limit[feeder] > 0:
                            feeder_group[-1].append(feeder)
                            new_feeder_group[feeder_index] = -1
                            feeder_limit[feeder] -= 1
                        else:
                            feeder_group[-1].append(-1)
                else:
                    # 使用旧供料器
                    for feeder_index, feeder_part in enumerate(max_common_part):
                        if feeder_part != -1:
                            new_feeder_group[feeder_index] = -1

        # 去除多余的元素
        for group in feeder_group:
            while len(group) > 0 and group[0] == -1:
                group.pop(0)

            while len(group) > 0 and group[-1] == -1:
                group.pop(-1)

        # 确定供料器组的安装位置
        part_pos = defaultdict(list)
        for _, data in self.step_data.iterrows():
            idx = self.part_data[self.part_data['part'].values == data.part].index.tolist()[0]
            part_pos[idx].append(data.x + self.config.stopper_pos.x)

        # 元件使用的头
        CT_Head = defaultdict(list)
        for part_cycle in part_result:
            for head, part in enumerate(part_cycle):
                if part == -1:
                    continue
                if part not in CT_Head:
                    CT_Head[part] = [head, head]
                CT_Head[part][0] = min(CT_Head[part][0], head)
                CT_Head[part][1] = max(CT_Head[part][1], head)

        # 供料器组分配的优先顺序
        feeder_assign_sequence = []
        for i in range(len(feeder_group)):
            for j in range(len(feeder_group)):
                if j in feeder_assign_sequence:
                    continue

                if len(feeder_assign_sequence) == i:
                    feeder_assign_sequence.append(j)
                else:
                    seq = feeder_assign_sequence[-1]
                    if cycle_result[seq] * len([k for k in feeder_group[seq] if k >= 0]) < cycle_result[j] * len(
                            [k for k in feeder_group[seq] if k >= 0]):
                        feeder_assign_sequence.pop(-1)
                        feeder_assign_sequence.append(j)

        # TODO: 暂未考虑机械限位
        feeder_group_slot = [-1] * len(feeder_group)
        feeder_lane_state = [0] * self.config.slot_num  # 0表示空，1表示已占有
        intv_ratio = self.config.head_intv // self.config.slot_intv
        for index in feeder_assign_sequence:
            group = feeder_group[index]
            best_slot = []
            for cp_index, part in enumerate(group):
                if part == -1:
                    continue
                best_slot.append(round((sum(part_pos[part]) / len(part_pos[part]) - self.config.slotf1_pos.x)
                                       / self.config.slot_intv) + 1 - cp_index * intv_ratio)
            best_slot = round(sum(best_slot) / len(best_slot))

            search_dir, step = 0, 0  # dir: 1-向右, 0-向左
            left_out_range, right_out_range = False, False
            while True:
                assign_slot = best_slot + step if search_dir else best_slot - step
                # 出现越界，反向搜索
                if assign_slot + (len(group) - 1) * intv_ratio >= self.config.slot_num / 2:
                    right_out_range = True
                    search_dir = 0
                    step += 1
                elif assign_slot < 0:
                    left_out_range = True
                    search_dir = 1
                    step += 1
                else:
                    if left_out_range or right_out_range:
                        step += 1  # 单向搜索
                    else:
                        search_dir = 1 - search_dir  # 双向搜索
                        if search_dir == 0:
                            step += 1

                assign_available = True

                # === 分配对应槽位 ===
                for slot in range(assign_slot, assign_slot + intv_ratio * len(group), intv_ratio):
                    pick_part = group[(slot - assign_slot) // intv_ratio]
                    if feeder_lane_state[slot] == 1 and pick_part != -1:
                        assign_available = False
                        break

                    if pick_part != -1 and (slot - CT_Head[pick_part][0] * intv_ratio <= 0 or
                                            slot + (self.config.head_num - CT_Head[pick_part][1] - 1) *
                                            intv_ratio > self.config.slot_num // 2):
                        assign_available = False
                        break

                if assign_available:
                    for idx, part in enumerate(group):
                        if part != -1:
                            feeder_lane_state[assign_slot + idx * intv_ratio] = 1
                    feeder_group_slot[index] = assign_slot
                    break

            if feeder_group_slot[index] == -1:
                raise Exception('feeder assign error!')

        # 按照最大匹配原则，确定各元件周期拾取槽位
        for part_cycle in part_result:
            slot_result.append([-1] * self.config.head_num)
            head_index = [head for head, component in enumerate(part_cycle) if component >= 0]
            while head_index:
                max_overlap_counter = 0
                overlap_feeder_group_index, overlap_feeder_group_offset = -1, -1
                for index, group in enumerate(feeder_group):
                    # offset 头1 相对于 供料器组第一个元件的偏移量
                    for offset in range(-self.config.head_num + 1, self.config.head_num + len(group)):
                        overlap_counter = 0
                        for head in head_index:
                            if 0 <= head + offset < len(group) and part_cycle[head] == group[head + offset]:
                                overlap_counter += 1

                        if overlap_counter > max_overlap_counter:
                            max_overlap_counter = overlap_counter
                            overlap_feeder_group_index, overlap_feeder_group_offset = index, offset

                group = feeder_group[overlap_feeder_group_index]
                head_index_cpy = copy.deepcopy(head_index)

                for idx, head in enumerate(head_index_cpy):
                    if 0 <= head + overlap_feeder_group_offset < len(group) and part_cycle[head] == \
                            group[head + overlap_feeder_group_offset]:
                        slot_result[-1][head] = feeder_group_slot[overlap_feeder_group_index] + intv_ratio * (
                                                               head + overlap_feeder_group_offset)
                        head_index.remove(head)

        return slot_result


class GenOpe:
    @staticmethod
    def roulette_wheel_selection(pop_eval):
        random_val = np.random.random() * sum(pop_eval)
        for idx, val in enumerate(pop_eval):
            random_val -= val
            if random_val <= 0:
                return idx
        return len(pop_eval) - 1

    @staticmethod
    def get_top_kth(pop_val, k: int, reverse=True):
        res = []
        pop_val_cpy = copy.deepcopy(pop_val)
        pop_val_cpy.sort(reverse=reverse)

        for i in range(min(len(pop_val_cpy), k)):
            for j in range(len(pop_val)):
                if abs(pop_val_cpy[i] - pop_val[j]) < 1e-9 and j not in res:
                    res.append(j)
                    break
        return res

    @staticmethod
    def partially_mapped_crossover(parent1, parent2):
        size = len(parent1)
        start, end = sorted(np.random.randint(0, size, 2))

        def create_child(primary_parent, secondary_parent):
            child = [-1] * size
            child[start:end + 1] = copy.deepcopy(secondary_parent[start:end + 1])

            for i in range(size):
                if start <= i <= end:
                    continue

                cur_ptr, cur_elem = 0, primary_parent[i]
                while True:
                    child[i] = cur_elem
                    if child.count(cur_elem) == 1:
                        break
                    child[i] = -1

                    if cur_ptr == 0:
                        cur_ptr, cur_elem = 1, secondary_parent[i]
                    else:
                        index_ = child.index(cur_elem)
                        cur_elem = secondary_parent[index_]

            return child

        return create_child(parent1, parent2), create_child(parent2, parent1)

    @staticmethod
    def swap_mutation(parent):
        range_ = np.random.randint(0, len(parent), 2)
        parent[range_[0]], parent[range_[1]] = parent[range_[1]], parent[range_[0]]
        return parent

    @staticmethod
    def directed_edge_recombine_crossover(individual1, individual2):
        assert len(individual1) == len(individual2)
        left_edge_list, right_edge_list = defaultdict(list), defaultdict(list)

        for index in range(len(individual1) - 1):
            elem1, elem2 = individual1[index], individual1[index + 1]
            right_edge_list[elem1].append(elem2)
            left_edge_list[elem2].append(elem1)

        for index in range(len(individual2) - 1):
            elem1, elem2 = individual2[index], individual2[index + 1]
            right_edge_list[elem1].append(elem2)
            left_edge_list[elem2].append(elem1)

        offspring = []
        while len(offspring) != len(individual1):
            while True:
                center_element = np.random.choice(individual1)
                if center_element not in offspring:  # 避免重复选取
                    break
            direction, candidate = 1, [center_element]
            parent = center_element
            for edge_list in left_edge_list.values():
                while parent in edge_list:
                    edge_list.remove(parent)

            for edge_list in right_edge_list.values():
                while parent in edge_list:
                    edge_list.remove(parent)

            while True:
                max_len, max_len_neighbor = -1, 0
                if direction == 1:
                    if len(right_edge_list[parent]) == 0:
                        direction, parent = -1, center_element
                        continue
                    for neighbor in right_edge_list[parent]:
                        if max_len < len(right_edge_list[neighbor]):
                            max_len_neighbor = neighbor
                            max_len = len(right_edge_list[neighbor])
                    candidate.append(max_len_neighbor)
                    parent = max_len_neighbor
                elif direction == -1:
                    if len(left_edge_list[parent]) == 0:
                        direction, parent = 0, center_element
                        continue
                    for neighbor in left_edge_list[parent]:
                        if max_len < len(left_edge_list[neighbor]):
                            max_len_neighbor = neighbor
                            max_len = len(left_edge_list[neighbor])
                    candidate.insert(0, max_len_neighbor)
                    parent = max_len_neighbor
                else:
                    break

                # 移除重复元素
                for edge_list in left_edge_list.values():
                    while max_len_neighbor in edge_list:
                        edge_list.remove(max_len_neighbor)

                for edge_list in right_edge_list.values():
                    while max_len_neighbor in edge_list:
                        edge_list.remove(max_len_neighbor)

            offspring += candidate

        return offspring

