from base_optimizer.optimizer_common import *


def convert_pcbdata_to_result(pcb_data, component_data):
    opt_res = OptResult()

    assigned_part = [-1 for _ in range(max_head_index)]
    assigned_slot = [-1 for _ in range(max_head_index)]
    assigned_point = [-1 for _ in range(max_head_index)]
    assigned_sequence = []

    point_num = len(pcb_data)           # total mount points num
    for point_cnt in range(point_num + 1):

        cycle_start = 1 if point_cnt == point_num else pcb_data.loc[point_cnt, 'cs']
        if (cycle_start and point_cnt != 0) or -1 not in assigned_part:

            if len(opt_res.component_assign) != 0 and opt_res.component_assign[-1] == assigned_part:
                opt_res.cycle_assign[-1] += 1
            else:
                opt_res.component_assign.append(assigned_part)
                opt_res.feeder_slot_assign.append(assigned_slot)
                opt_res.cycle_assign.append(1)

            # assigned_sequence = list(reversed(assigned_sequence))       # Samsung拾取顺序相反

            opt_res.placement_assign.append(assigned_point)
            opt_res.head_sequence.append(assigned_sequence)

            assigned_part = [-1 for _ in range(max_head_index)]
            assigned_slot = [-1 for _ in range(max_head_index)]
            assigned_point = [-1 for _ in range(max_head_index)]
            assigned_sequence = []

            if point_cnt == point_num:
                break

        slot = pcb_data.loc[point_cnt, 'fdr'].split(' ')[0]
        if slot == 'A':
            slot, part = 0, pcb_data.loc[point_cnt].part
        else:
            slot, part = int(slot[1:]), pcb_data.loc[point_cnt].fdr.split(' ', 1)[1]
        head = pcb_data.loc[point_cnt].hd - 1

        part_index = component_data[component_data.part == part].index.tolist()[0]

        assigned_part[head] = part_index
        assigned_slot[head] = slot
        assigned_point[head] = point_cnt
        assigned_sequence.append(head)

    return opt_res


# 绘制各周期从供料器周期拾取的元件位置
def pickup_cycle_schematic(optimizer_result):
    plt.rcParams['font.sans-serif'] = ['KaiTi']  # 指定默认字体
    plt.rcParams['axes.unicode_minus'] = False  # 解决保存图像是负号'-'显示为方块的问题
    # data
    bar_width = .7
    feeder_part = np.zeros(int(max_slot_index / 2), dtype=np.int)
    for cycle in range(len(optimizer_result.feeder_slot_assign)):
        label_str = '周期' + str(cycle + 1)
        cur_feeder_part = np.zeros(int(max_slot_index / 2), dtype=np.int)
        for slot in optimizer_result.feeder_slot_assign[cycle]:
            if slot > 0:
                cur_feeder_part[slot] += optimizer_result.cycle_assign[cycle]

        plt.bar(np.arange(max_slot_index / 2), cur_feeder_part, bar_width, edgecolor='black', bottom=feeder_part,
                label=label_str)

        for slot in optimizer_result.feeder_slot_assign[cycle]:
            if slot > 0:
                feeder_part[slot] += optimizer_result.cycle_assign[cycle]

    plt.legend()
    plt.show()


def placement_route_schematic(pcb_data, component_data, optimizer_result, cycle=-1):

    plt.figure('cycle {}'.format(cycle + 1))
    pos_x, pos_y = [], []
    for i in range(len(pcb_data)):
        pos_x.append(pcb_data.loc[i]['x'] + stopper_pos[0])
        pos_y.append(pcb_data.loc[i]['y'] + stopper_pos[1])
        # plt.text(pcb_data.loc[i]['x'], pcb_data.loc[i]['y'] + 0.1, '%d' % i, ha='center', va = 'bottom', size = 8)

    mount_pos = []
    for head in optimizer_result.head_sequence[cycle]:
        index = optimizer_result.placement_assign[cycle][head]
        plt.text(pos_x[index], pos_y[index] + 0.1, 'HD%d' % (head + 1), ha='center', va='bottom', size=10)
        plt.plot([pos_x[index], pos_x[index] - head * head_interval], [pos_y[index], pos_y[index]], linestyle='-.',
                 color='black', linewidth=1)
        mount_pos.append([pos_x[index] - head * head_interval, pos_y[index]])
        plt.plot(mount_pos[-1][0], mount_pos[-1][1], marker='^', color='red', markerfacecolor='white')

        # plt.text(mount_pos[-1][0], mount_pos[-1][1], '%d' % index, size=8)

    # 绘制贴装路径
    for i in range(len(mount_pos) - 1):
        plt.plot([mount_pos[i][0], mount_pos[i + 1][0]], [mount_pos[i][1], mount_pos[i + 1][1]], color='blue',
                 linewidth=1)

    draw_x, draw_y = [], []
    for c in range(cycle, len(optimizer_result.placement_assign)):
        for h in range(max_head_index):
            i = optimizer_result.placement_assign[c][h]
            if i == -1:
                continue
            draw_x.append(pcb_data.loc[i]['x'] + stopper_pos[0])
            draw_y.append(pcb_data.loc[i]['y'] + stopper_pos[1])

            # plt.text(draw_x[-1], draw_y[-1] - 5, '%d' % i, ha='center', va='bottom', size=10)

    plt.scatter(draw_x, draw_y, s=8)

    # 绘制供料器位置布局
    for slot in range(max_slot_index // 2):
        plt.scatter(slotf1_pos[0] + slot_interval * slot, slotf1_pos[1], marker='x', s=12, color='green')
        plt.text(slotf1_pos[0] + slot_interval * slot, slotf1_pos[1] - 50, slot + 1, ha='center', va='bottom', size=8)

    feeder_part, feeder_counter = {}, {}
    placement_cycle = 0
    for cycle_, components in enumerate(optimizer_result.component_assign):
        for head, component in enumerate(components):
            if component == -1:
                continue
            placement = optimizer_result.placement_assign[placement_cycle][head]
            slot = optimizer_result.feeder_slot_assign[cycle_][head]
            feeder_part[slot] = pcb_data.loc[placement]['part'] + component_data.iloc[optimizer_result.component_assign[cycle_][head]].fdr
            if slot not in feeder_counter.keys():
                feeder_counter[slot] = 0

            feeder_counter[slot] += optimizer_result.cycle_assign[cycle_]
        placement_cycle += optimizer_result.cycle_assign[cycle_]

    for slot, part in feeder_part.items():
        plt.text(slotf1_pos[0] + slot_interval * (slot - 1), slotf1_pos[1] + 15,
                 part + ': ' + str(feeder_counter[slot]), ha='center', size=7, rotation=90)

    plt.plot([slotf1_pos[0] - slot_interval / 2, slotf1_pos[0] + slot_interval * (max_slot_index // 2 - 1 + 0.5)],
                    [slotf1_pos[1] + 10, slotf1_pos[1] + 10], color='black')
    plt.plot([slotf1_pos[0] - slot_interval / 2, slotf1_pos[0] + slot_interval * (max_slot_index // 2 - 1 + 0.5)],
                    [slotf1_pos[1] - 40, slotf1_pos[1] - 40], color='black')

    for counter in range(max_slot_index // 2 + 1):
        pos = slotf1_pos[0] + (counter - 0.5) * slot_interval
        plt.plot([pos, pos], [slotf1_pos[1] + 10, slotf1_pos[1] - 40], color='black', linewidth=1)

    # 绘制拾取路径
    pick_slot = []
    cycle_group = 0
    while sum(optimizer_result.cycle_assign[0: cycle_group + 1]) < cycle:
        cycle_group += 1
    for head, slot in enumerate(optimizer_result.feeder_slot_assign[cycle_group]):
        if slot == -1:
            continue
        pick_slot.append(slot - head * interval_ratio)
    pick_slot = list(set(pick_slot))
    pick_slot = sorted(pick_slot)

    next_cycle_group = 0
    next_pick_slot = max_slot_index
    while sum(optimizer_result.cycle_assign[0: next_cycle_group + 1]) < cycle + 1:
        next_cycle_group += 1
    if next_cycle_group < len(optimizer_result.feeder_slot_assign):
        for head, slot in enumerate(optimizer_result.feeder_slot_assign[cycle_group]):
            if slot == -1:
                continue
            next_pick_slot = min(next_pick_slot, slot - head * interval_ratio)

    # 前往PCB贴装
    plt.plot([mount_pos[-1][0], slotf1_pos[0] + slot_interval * (pick_slot[-1] - 1)], [mount_pos[-1][1], slotf1_pos[1]],
             color='blue', linewidth=1)
    # 基座移动路径
    plt.plot([slotf1_pos[0] + slot_interval * (pick_slot[0] - 1), slotf1_pos[0] + slot_interval * (pick_slot[-1] - 1)],
             [slotf1_pos[1], slotf1_pos[1]], color='blue', linewidth=1)
    # 返回基座取料
    plt.plot([mount_pos[0][0], slotf1_pos[0] + slot_interval * (next_pick_slot - 1)], [mount_pos[0][1], slotf1_pos[1]],
             color='blue', linewidth=1)

    plt.show()


def save_placement_route_figure(file_name, pcb_data, optimizer_result):
    path = 'result/' + file_name[:file_name.find('.')]
    if not os.path.exists(path):
        os.mkdir(path)

    pos_x, pos_y = [], []
    for i in range(len(pcb_data)):
        pos_x.append(pcb_data.loc[i]['x'] + stopper_pos[0])
        pos_y.append(pcb_data.loc[i]['y'] + stopper_pos[1])
        # plt.text(pcb_data.loc[i]['x'], pcb_data.loc[i]['y'] + 0.1, '%d' % i, ha='center', va = 'bottom', size = 8)

    with tqdm(total=100) as pbar:
        pbar.set_description('save figure')
        for cycle in range(len(optimizer_result.placement_assign)):
            plt.figure(cycle)

            mount_pos = []
            for head in optimizer_result.head_sequence[cycle]:
                index = optimizer_result.placement_assign[cycle][head]
                plt.text(pos_x[index], pos_y[index] + 0.1, 'HD%d' % (head + 1), ha='center', va='bottom', size=10)
                plt.plot([pos_x[index], pos_x[index] - head * head_interval], [pos_y[index], pos_y[index]],
                         linestyle='-.', color='black', linewidth=1)
                mount_pos.append([pos_x[index] - head * head_interval, pos_y[index]])
                plt.plot(mount_pos[-1][0], mount_pos[-1][1], marker='^', color='red', markerfacecolor='white')

            # 绘制贴装路径
            for i in range(len(mount_pos) - 1):
                plt.plot([mount_pos[i][0], mount_pos[i + 1][0]], [mount_pos[i][1], mount_pos[i + 1][1]], color='blue',
                         linewidth=1)

            draw_x, draw_y = [], []
            for c in range(cycle, len(optimizer_result.placement_assign)):
                for h in range(max_head_index):
                    i = optimizer_result.placement_assign[c][h]
                    if i == -1:
                        continue
                    draw_x.append(pcb_data.loc[i]['x'] + stopper_pos[0])
                    draw_y.append(pcb_data.loc[i]['y'] + stopper_pos[1])

                    # plt.text(draw_x[-1], draw_y[-1] - 5, '%d' % i, ha='center', va='bottom', size=10)

            plt.scatter(pos_x, pos_y, s=8)
            # 绘制供料器位置布局
            for slot in range(max_slot_index // 2):
                plt.scatter(slotf1_pos[0] + slot_interval * slot, slotf1_pos[1], marker='x', s=12, color='green')
                plt.text(slotf1_pos[0] + slot_interval * slot, slotf1_pos[1] - 50, slot + 1, ha='center', va='bottom', size=8)

            feeder_part, feeder_counter = {}, {}
            placement_cycle = 0
            for cycle_, components in enumerate(optimizer_result.component_assign):
                for head, component in enumerate(components):
                    if component == -1:
                        continue
                    placement = optimizer_result.placement_assign[placement_cycle][head]
                    slot = optimizer_result.feeder_slot_assign[cycle_][head]
                    feeder_part[slot] = pcb_data.loc[placement]['part']
                    if slot not in feeder_counter.keys():
                        feeder_counter[slot] = 0

                    feeder_counter[slot] += optimizer_result.cycle_assign[cycle_]
                placement_cycle += optimizer_result.cycle_assign[cycle_]

            for slot, part in feeder_part.items():
                plt.text(slotf1_pos[0] + slot_interval * (slot - 1), slotf1_pos[1] + 15,
                         part + ': ' + str(feeder_counter[slot]), ha='center', size=7, rotation=90)

            plt.plot(
                [slotf1_pos[0] - slot_interval / 2, slotf1_pos[0] + slot_interval * (max_slot_index // 2 - 1 + 0.5)],
                [slotf1_pos[1] + 10, slotf1_pos[1] + 10], color='black')
            plt.plot(
                [slotf1_pos[0] - slot_interval / 2, slotf1_pos[0] + slot_interval * (max_slot_index // 2 - 1 + 0.5)],
                [slotf1_pos[1] - 40, slotf1_pos[1] - 40], color='black')

            for counter in range(max_slot_index // 2 + 1):
                pos = slotf1_pos[0] + (counter - 0.5) * slot_interval
                plt.plot([pos, pos], [slotf1_pos[1] + 10, slotf1_pos[1] - 40], color='black', linewidth=1)

            # 绘制拾取路径
            pick_slot = []
            cycle_group = 0
            while sum(optimizer_result.cycle_assign[0: cycle_group + 1]) < cycle:
                cycle_group += 1
            for head, slot in enumerate(optimizer_result.feeder_slot_assign[cycle_group]):
                if slot == -1:
                    continue
                pick_slot.append(slot - head * interval_ratio)
            pick_slot = list(set(pick_slot))
            pick_slot = sorted(pick_slot)

            plt.plot([mount_pos[0][0], slotf1_pos[0] + slot_interval * (pick_slot[0] - 1)],
                     [mount_pos[0][1], slotf1_pos[1]], color='blue', linewidth=1)
            plt.plot([mount_pos[-1][0], slotf1_pos[0] + slot_interval * (pick_slot[-1] - 1)],
                     [mount_pos[-1][1], slotf1_pos[1]], color='blue', linewidth=1)
            plt.plot([slotf1_pos[0] + slot_interval * (pick_slot[0] - 1),
                      slotf1_pos[0] + slot_interval * (pick_slot[-1] - 1)], [slotf1_pos[1], slotf1_pos[1]],
                     color='blue', linewidth=1)

            plt.savefig(path + '/cycle_{}'.format(cycle + 1))

            plt.close(cycle)
            pbar.update(100 / len(optimizer_result.placement_assign))


def output_optimize_result(file_path, component_data, pcb_data, optimizer_result):
    assert len(optimizer_result.component_assign) == len(optimizer_result.feeder_slot_assign)

    output_data = pcb_data.copy(deep=True)

    # 默认ANC参数
    anc_list = defaultdict(list)
    anc_list['CN065'] = list(range(14, 25, 2))
    anc_list['CN020'] = list(range(15, 26, 2))
    anc_list['CN140'] = list(range(26, 37, 2))
    anc_list['CN040'] = list(range(27, 38, 2))

    placement_index = []
    assigned_nozzle, assigned_anc_hole = ['' for _ in range(max_head_index)], [-1 for _ in range(max_head_index)]
    for cycle_set in range(len(optimizer_result.cycle_assign)):
        floor_cycle, ceil_cycle = sum(optimizer_result.cycle_assign[:cycle_set]), sum(optimizer_result.cycle_assign[:(cycle_set + 1)])
        for cycle in range(floor_cycle, ceil_cycle):
            cycle_start = True
            cycle_nozzle = ['' for _ in range(max_head_index)]
            head_indexes = [-1 for _ in range(max_head_index)]
            for head in optimizer_result.head_sequence[cycle]:
                index_ = optimizer_result.placement_assign[cycle][head]
                if index_ == -1:
                    continue
                head_indexes[head] = index_
                placement_index.append(index_)

                output_data.loc[index_, 'cs'] = 1 if cycle_start else 0
                output_data.loc[index_, 'cy'] = cycle + 1
                output_data.loc[index_, 'hd'] = head + 1

                cycle_start = False

                # 供料器信息
                slot = optimizer_result.feeder_slot_assign[cycle_set][head]
                fdr = 'F' + str(slot) if slot < max_slot_index // 2 else 'R' + str(slot - max_slot_index // 2)

                output_data.loc[index_, 'fdr'] = fdr + ' ' + component_data.loc[
                    optimizer_result.component_assign[cycle_set][head], 'part']

                # ANC信息
                cycle_nozzle[head] = component_data.loc[optimizer_result.component_assign[cycle_set][head], 'nz']

            for head in range(max_head_index):
                nozzle = cycle_nozzle[head]
                if nozzle == '':
                    continue
                if nozzle != assigned_nozzle[head]:
                    # 已分配有吸嘴，卸载原吸嘴
                    if assigned_nozzle[head] != '':
                        anc_list[assigned_nozzle[head]].append(assigned_anc_hole[head])
                        anc_list[assigned_nozzle[head]] = sorted(anc_list[assigned_nozzle[head]])

                    # 安装新的吸嘴
                    assigned_nozzle[head] = nozzle
                    try:
                        assigned_anc_hole[head] = anc_list[nozzle][0]
                    except IndexError:
                        info = 'the number of nozzle for [' + nozzle + '] exceeds the quantity limit'
                        raise IndexError(info)
                    anc_list[nozzle].pop(0)

                output_data.loc[head_indexes[head], 'nz'] = '1-' + str(assigned_anc_hole[head]) + ' ' + nozzle

    output_data = output_data.reindex(placement_index)
    output_data = output_data.reset_index(drop=True)
    if 'desc' not in output_data.columns:
        column_index = int(np.where(output_data.columns.values.reshape(-1) == 'part')[0][0])
        output_data.insert(loc=column_index + 1, column='desc', value='')

    output_data.to_csv('result/' + file_path + '.txt', sep='\t', float_format='%.3f', header=False, index=False)


def optimization_assign_result(component_data, pcb_data, optimizer_result, nozzle_hinter=False, component_hinter=False, 
                               feeder_hinter=False, placement_hinter=False):
    if nozzle_hinter:
        columns = ['H{}'.format(i + 1) for i in range(max_head_index)] + ['cycle']

        nozzle_assign = pd.DataFrame(columns=columns)
        for cycle, components in enumerate(optimizer_result.component_assign):
            nozzle_assign_row = len(nozzle_assign)
            nozzle_assign.loc[nozzle_assign_row, 'cycle'] = optimizer_result.cycle_assign[cycle]

            for head in range(max_head_index):
                index = optimizer_result.component_assign[cycle][head]
                if index == -1:
                    nozzle_assign.loc[nozzle_assign_row, 'H{}'.format(head + 1)] = ''
                else:
                    nozzle = component_data.loc[index]['nz']
                    nozzle_assign.loc[nozzle_assign_row, 'H{}'.format(head + 1)] = nozzle

            for head in range(max_head_index):
                if nozzle_assign_row == 0 or nozzle_assign.loc[nozzle_assign_row - 1, 'H{}'.format(head + 1)] != \
                        nozzle_assign.loc[nozzle_assign_row, 'H{}'.format(head + 1)]:
                    break
            else:
                nozzle_assign.loc[nozzle_assign_row - 1, 'cycle'] += nozzle_assign.loc[nozzle_assign_row, 'cycle']
                nozzle_assign.drop([len(nozzle_assign) - 1], inplace=True)

        print(nozzle_assign)
        print('')

    if component_hinter:
        columns = ['H{}'.format(i + 1) for i in range(max_head_index)] + ['cycle']

        component_assign = pd.DataFrame(columns=columns)
        for cycle, components in enumerate(optimizer_result.component_assign):
            component_assign.loc[cycle, 'cycle'] = optimizer_result.cycle_assign[cycle]
            for head in range(max_head_index):
                index = optimizer_result.component_assign[cycle][head]
                if index == -1:
                    component_assign.loc[cycle, 'H{}'.format(head + 1)] = ''
                else:
                    # component_assign.loc[cycle, 'H{}'.format(head + 1)] = component_data.loc[index]['part']
                    component_assign.loc[cycle, 'H{}'.format(head + 1)] = 'C' + str(index)

        print(component_assign)
        print('')

    if feeder_hinter:
        columns = ['H{}'.format(i + 1) for i in range(max_head_index)] + ['cycle']

        feedr_assign = pd.DataFrame(columns=columns)
        for cycle, components in enumerate(optimizer_result.feeder_slot_assign):
            feedr_assign.loc[cycle, 'cycle'] = optimizer_result.cycle_assign[cycle]
            for head in range(max_head_index):
                slot = optimizer_result.feeder_slot_assign[cycle][head]
                if slot == -1:
                    feedr_assign.loc[cycle, 'H{}'.format(head + 1)] = 'A'
                else:
                    try:
                        feedr_assign.loc[cycle, 'H{}'.format(head + 1)] = 'F{}'.format(
                            slot) if slot <= max_slot_index // 2 else 'R{}'.format(slot - max_head_index)
                    except:
                        print('')

        print(feedr_assign)
        print('')

    if placement_hinter:
        columns = ['H{}'.format(i + 1) for i in range(max_head_index)] + ['cycle']

        placement_assign = pd.DataFrame(columns=columns)
        for cycle, _ in enumerate(optimizer_result.placement_assign):
            placement_assign.loc[cycle, 'cycle'] = 1
            for head in range(max_head_index):
                point = optimizer_result.placement_assign[cycle][head]
                if point != -1:
                    placement_assign.loc[cycle, 'H{}'.format(head + 1)] = 'P{}'.format(point)
                else:
                    placement_assign.loc[cycle, 'H{}'.format(head + 1)] = ''

        headseq_assign = pd.DataFrame(columns=columns)
        for cycle, headseq in enumerate(optimizer_result.head_sequence):
            headseq_assign.loc[cycle, 'cycle'] = 1
            for head in range(len(headseq)):
                headseq_assign.loc[cycle, 'H{}'.format(head + 1)] = 'H{}'.format(headseq[head])

        print(placement_assign)
        print(headseq_assign)
        print('')


def placement_info_evaluation(component_data, pcb_data, optimizer_result, hinter=False):
    # === 优化结果参数 ===
    info = OptInfo()
    # === 校验 ===
    info.total_points = 0
    for cycle, components in enumerate(optimizer_result.component_assign):
        for head, component in enumerate(components):
            if component == -1:
                continue
            info.total_points += optimizer_result.cycle_assign[cycle]

    if info.total_points != len(pcb_data):
        warning_info = 'the number of placement points is not match with the PCB data. '
        warnings.warn(warning_info, UserWarning)
        return OptInfo()

    if optimizer_result.placement_assign:
        total_points = info.total_points
        for placements in optimizer_result.placement_assign:
            for placement in placements:
                if placement == -1:
                    continue
                total_points -= 1

        if total_points != 0:
            warnings.warn(
                'the optimization result of component assignment result and placement result are not consistent. ',
                UserWarning)
            return OptInfo()

    feeder_arrangement = defaultdict(set)
    for cycle, feeder_slots in enumerate(optimizer_result.feeder_slot_assign):
        for head, slot in enumerate(feeder_slots):
            if slot == -1:
                continue
            feeder_arrangement[optimizer_result.component_assign[cycle][head]].add(slot)

    info.total_components = len(feeder_arrangement.keys())
    for part, data in component_data.iterrows():
        if part in feeder_arrangement.keys() and data.fdn < len(feeder_arrangement[part]):
            info = 'the number of arranged feeder of [' + data['part'] + '] exceeds the quantity limit'
            warnings.warn(info, UserWarning)
            return 0.

    cur_pos, next_pos = anc_marker_pos, [0, 0]                          # 贴装头当前位置

    # 初始化首个周期的吸嘴装配信息
    nozzle_assigned = ['Empty' for _ in range(max_head_index)]
    for head in range(max_head_index):
        for cycle in range(len(optimizer_result.component_assign)):
            idx = optimizer_result.component_assign[cycle][head]
            if idx == -1:
                continue
            else:
                nozzle_assigned[head] = component_data.loc[idx]['nz']

    for cycle_set, _ in enumerate(optimizer_result.component_assign):
        floor_cycle, ceil_cycle = sum(optimizer_result.cycle_assign[:cycle_set]), sum(optimizer_result.cycle_assign[:(cycle_set + 1)])
        for cycle in range(floor_cycle, ceil_cycle):
            if sum(optimizer_result.component_assign[cycle_set]) == -max_head_index:
                continue
            pick_slot, mount_pos, mount_angle = [], [], []
            nozzle_pick_counter, nozzle_put_counter = 0, 0  # 吸嘴更换次数统计（拾取/放置分别算一次）
            for head in range(max_head_index):
                if optimizer_result.feeder_slot_assign[cycle_set][head] != -1:
                    pick_slot.append(optimizer_result.feeder_slot_assign[cycle_set][head] - interval_ratio * head)
                if optimizer_result.component_assign[cycle_set][head] == -1:
                    continue
                nozzle = component_data.loc[optimizer_result.component_assign[cycle_set][head]]['nz']
                if nozzle != nozzle_assigned[head]:
                    if nozzle_assigned[head] != 'Empty':
                        nozzle_put_counter += 1
                    nozzle_pick_counter += 1
                    nozzle_assigned[head] = nozzle

            # ANC处进行吸嘴更换
            if nozzle_pick_counter + nozzle_put_counter > 0:
                next_pos = anc_marker_pos
                move_time = max(axis_moving_time(cur_pos[0] - next_pos[0], 0),
                                axis_moving_time(cur_pos[1] - next_pos[1], 1))
                info.round_time += move_time
                info.anc_round_counter += 1
                info.total_distance += max(abs(cur_pos[0] - next_pos[0]), abs(cur_pos[1] - next_pos[1]))
                cur_pos = next_pos

            pick_slot = list(set(pick_slot))
            pick_slot = sorted(pick_slot, reverse=True)

            # 拾取路径(自右向左)
            for idx, slot in enumerate(pick_slot):
                if slot < max_slot_index // 2:
                    next_pos = [slotf1_pos[0] + slot_interval * (slot - 1), slotf1_pos[1]]
                else:
                    next_pos = [slotr1_pos[0] - slot_interval * (max_slot_index - slot - 1), slotr1_pos[1]]
                info.operation_time += t_pick
                info.pickup_counter += 1

                move_time = max(axis_moving_time(cur_pos[0] - next_pos[0], 0),
                                axis_moving_time(cur_pos[1] - next_pos[1], 1))
                if idx == 0:
                    info.round_time += move_time
                else:
                    info.pickup_time += move_time

                info.total_distance += max(abs(cur_pos[0] - next_pos[0]), abs(cur_pos[1] - next_pos[1]))
                if slot != pick_slot[0]:
                    info.pickup_distance += max(abs(cur_pos[0] - next_pos[0]), abs(cur_pos[1] - next_pos[1]))
                cur_pos = next_pos

            # 固定相机检测
            # for head in range(max_head_index):
            #     if optimizer_result.component_assign[cycle_set][head] == -1:
            #         continue
            #     camera = component_data.loc[optimizer_result.component_assign[cycle_set][head]]['camera']
            #     if camera == '固定相机':
            #         next_pos = [fix_camera_pos[0] - head * head_interval, fix_camera_pos[1]]
            #         move_time = max(axis_moving_time(cur_pos[0] - next_pos[0], 0),
            #                         axis_moving_time(cur_pos[1] - next_pos[1], 1))
            #         info.round_time += move_time
            #
            #         info.total_distance += max(abs(cur_pos[0] - next_pos[0]), abs(cur_pos[1] - next_pos[1]))
            #         info.operation_time += t_fix_camera_check
            #         cur_pos = next_pos

            # 贴装路径
            if optimizer_result.placement_assign and optimizer_result.head_sequence:
                head_angle = [0 for _ in range(max_head_index)]
                for head in optimizer_result.head_sequence[cycle]:
                    index = optimizer_result.placement_assign[cycle][head]
                    if index == -1:
                        continue
                    mount_pos.append([pcb_data.iloc[index]['x'] - head * head_interval + stopper_pos[0],
                                      pcb_data.iloc[index]['y'] + stopper_pos[1]])
                    head_angle[head] = pcb_data.iloc[index]['r']

                # 单独计算贴装路径
                for cntPoints in range(len(mount_pos) - 1):
                    info.place_distance += max(abs(mount_pos[cntPoints][0] - mount_pos[cntPoints + 1][0]),
                                               abs(mount_pos[cntPoints][1] - mount_pos[cntPoints + 1][1]))

                if mount_pos[0][0] < mount_pos[-1][0]:
                    mount_pos = reversed(mount_pos)

                # 考虑R轴预旋转，补偿同轴角度转动带来的额外贴装用时
                info.operation_time += t_nozzle_put * nozzle_put_counter + t_nozzle_pick * nozzle_pick_counter
                for idx, pos in enumerate(mount_pos):
                    info.operation_time += t_place

                    if idx == 0:
                        move_time = max(axis_moving_time(cur_pos[0] - pos[0], 0),
                                        axis_moving_time(cur_pos[1] - pos[1], 1))
                        info.round_time += move_time
                    else:

                        cur_head = optimizer_result.head_sequence[cycle][idx]
                        side_head = cur_head - 1 if cur_head % 2 else cur_head + 1
                        if optimizer_result.head_sequence[cycle][idx - 1] != side_head:
                            move_time = max(axis_moving_time(cur_pos[0] - pos[0], 0),
                                            axis_moving_time(cur_pos[1] - pos[1], 1))
                        else:
                            move_time = max(axis_moving_time(cur_pos[0] - pos[0], 0),
                                            axis_moving_time(cur_pos[1] - pos[1], 1),
                                            head_rotary_time(head_angle[cur_head] - head_angle[side_head]))
                        info.place_time += move_time

                    info.total_distance += max(abs(cur_pos[0] - pos[0]), abs(cur_pos[1] - pos[1]))
                    cur_pos = pos

            info.nozzle_change_counter += nozzle_put_counter + nozzle_pick_counter

    info.total_time = info.pickup_time + info.round_time + info.place_time + info.operation_time
    info.cycle_counter = sum(optimizer_result.cycle_assign)
    if hinter:
        optimization_assign_result(component_data, pcb_data, optimizer_result, nozzle_hinter=False, 
                                   component_hinter=False, feeder_hinter=False)
        info.print()

    return info


