/*
 *   Copyright (C) 2014  Alfons Wirtz
 *   website www.freerouting.net
 *
 *   Copyright (C) 2017 Michael Hoffer <info@michaelhoffer.de>
 *   Website www.freerouting.mihosoft.eu
*
 *   This program is free software: you can redistribute it and/or modify
 *   it under the terms of the GNU General Public License as published by
 *   the Free Software Foundation, either version 3 of the License, or
 *   (at your option) any later version.
 *
 *   This program is distributed in the hope that it will be useful,
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *   GNU General Public License at <http://www.gnu.org/licenses/> 
 *   for more details.
 *
 * MazeSearchAlgo.java
 *
 * Created on 25. Januar 2004, 13:24
 */
package eu.mihosoft.freerouting.autoroute;

import eu.mihosoft.freerouting.geometry.planar.ConvexShape;
import eu.mihosoft.freerouting.geometry.planar.FloatLine;
import eu.mihosoft.freerouting.geometry.planar.FloatPoint;
import eu.mihosoft.freerouting.geometry.planar.IntPoint;
import eu.mihosoft.freerouting.geometry.planar.Point;
import eu.mihosoft.freerouting.geometry.planar.Polyline;
import eu.mihosoft.freerouting.geometry.planar.TileShape;
import eu.mihosoft.freerouting.geometry.planar.IntBox;
import eu.mihosoft.freerouting.geometry.planar.IntOctagon;
import eu.mihosoft.freerouting.geometry.planar.Line;

import java.util.Collection;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.TreeSet;
import java.util.Set;

import eu.mihosoft.freerouting.board.Connectable;
import eu.mihosoft.freerouting.board.ForcedViaAlgo;
import eu.mihosoft.freerouting.board.Item;
import eu.mihosoft.freerouting.board.PolylineTrace;
import eu.mihosoft.freerouting.board.ShapeSearchTree;
import eu.mihosoft.freerouting.board.AngleRestriction;
import eu.mihosoft.freerouting.board.SearchTreeObject;
import eu.mihosoft.freerouting.board.ItemSelectionFilter;
import eu.mihosoft.freerouting.logger.FRLogger;

/**
 * Class for autorouting an incomplete connection via a maze search algorithm.
 *
 * @author Alfons Wirtz
 */
public class MazeSearchAlgo
{

    /**
     * Initializes a new instance of MazeSearchAlgo for secrching a connection
     * between p_start_items and p_destination_items.
     * Returns null, if the initialisation failed.
     */
    public static MazeSearchAlgo get_instance(Set<Item> p_start_items,
                                              Set<Item> p_destination_items, AutorouteEngine p_autoroute_database, AutorouteControl p_ctrl)
    {
        MazeSearchAlgo new_instance = new MazeSearchAlgo(p_autoroute_database, p_ctrl);
        MazeSearchAlgo result;
        if (new_instance.init(p_start_items, p_destination_items))
        {
            result = new_instance;
        }
        else
        {
            result = null;
        }
        return result;
    }

    /** Creates a new instance of MazeSearchAlgo */
    MazeSearchAlgo(AutorouteEngine p_autoroute_engine, AutorouteControl p_ctrl)
    {
        autoroute_engine = p_autoroute_engine;
        ctrl = p_ctrl;
        random_generator.setSeed(p_ctrl.ripup_costs); // To get reproducable random numbers in the ripup algorithm.
        this.search_tree = p_autoroute_engine.autoroute_search_tree;
        maze_expansion_list = new TreeSet<MazeListElement>();
        destination_distance =
                new DestinationDistance(ctrl.trace_costs, ctrl.layer_active,
                ctrl.min_normal_via_cost, ctrl.min_cheap_via_cost);
    }

    /**
     * Does a maze search to find a connection route between the start and the
     * destination items. If the algorithm succeeds, the ExpansionDoor and its section number of
     * the found destination is returned, from where the whole found connection
     * can be backtracked. Otherwise the return value will be null.
     */
    public Result find_connection()
    {
        while (occupy_next_element())
        {
        }
        if (this.destination_door == null)
        {
            return null;
        }
        return new Result(this.destination_door, this.section_no_of_destination_door);
    }

    /**
     * Expands the next element in the maze expansion list.
     * Returns false, if the expansion list is exhausted or the destination is reached.
     */
    public boolean occupy_next_element()
    {
        if (this.destination_door != null)
        {
            return false; // destination already reached
        }
        MazeListElement list_element = null;
        MazeSearchElement curr_door_section = null;
        // Search the next element, which is not yet expanded.
        boolean next_element_found = false;
        while (!maze_expansion_list.isEmpty())
        {
            if (this.autoroute_engine.is_stop_requested())
            {
                return false;
            }
            Iterator<MazeListElement> it = maze_expansion_list.iterator();
            list_element = it.next();
            int curr_section_no = list_element.section_no_of_door;
            curr_door_section = list_element.door.get_maze_search_element(curr_section_no);
            it.remove();
            if (!curr_door_section.is_occupied)
            {
                next_element_found = true;
                break;
            }
        }
        if (!next_element_found)
        {
            return false;
        }
        curr_door_section.backtrack_door = list_element.backtrack_door;
        curr_door_section.section_no_of_backtrack_door = list_element.section_no_of_backtrack_door;
        curr_door_section.room_ripped = list_element.room_ripped;
        curr_door_section.adjustment = list_element.adjustment;

        if (list_element.door instanceof DrillPage)
        {
            expand_to_drills_of_page(list_element);
            return true;
        }

        if (list_element.door instanceof TargetItemExpansionDoor)
        {
            TargetItemExpansionDoor curr_door = (TargetItemExpansionDoor) list_element.door;
            if (curr_door.is_destination_door())
            {
                // The destination is reached.
                this.destination_door = curr_door;
                this.section_no_of_destination_door = list_element.section_no_of_door;
                return false;
            }
        }
        if (ctrl.is_fanout && list_element.door instanceof ExpansionDrill && list_element.backtrack_door instanceof ExpansionDrill)
        {
            // algorithm completed after the first drill;
            this.destination_door = list_element.door;
            this.section_no_of_destination_door = list_element.section_no_of_door;
            return false;
        }
        if (ctrl.vias_allowed && list_element.door instanceof ExpansionDrill
                && !(list_element.backtrack_door instanceof ExpansionDrill))
        {
            expand_to_other_layers(list_element);
        }

        if (list_element.next_room != null)
        {
            if (!expand_to_room_doors(list_element))
            {
                return true; // occupation by ripup is delayed or nothing was expanded
                // In case nothing was expanded allow the section to be occupied from
                // somewhere else, if the next room is thin.
            }
        }
        curr_door_section.is_occupied = true;
        return true;
    }

    /**
     * Expands the other door section of the room.
     * Returns true, if the from door section has to be occupied,
     * and false, if the occupation for is delayed.
     */
    private boolean expand_to_room_doors(MazeListElement p_list_element)
    {

        // Complete the neigbour rooms to make shure, that the
        // doors of this room will not change later on.
        int layer_no = p_list_element.next_room.get_layer();

        boolean layer_active = ctrl.layer_active[layer_no];
        if (!layer_active)
        {
            if (autoroute_engine.board.layer_structure.arr[layer_no].is_signal)
            {
                return true;
            }
        }

        double half_width = ctrl.compensated_trace_half_width[layer_no];
        boolean curr_door_is_small = false;
        if (p_list_element.door instanceof ExpansionDoor)
        {
            double half_width_add = half_width + AutorouteEngine.TRACE_WIDTH_TOLERANCE;
            ExpansionDoor curr_door = (ExpansionDoor) p_list_element.door;
            if (this.ctrl.with_neckdown)
            {
                // try evtl. neckdown at a destination pin
                double neck_down_half_width = check_neck_down_at_dest_pin(p_list_element.next_room);
                if (neck_down_half_width > 0)
                {
                    half_width_add = Math.min(half_width_add, neck_down_half_width);
                    half_width = half_width_add;
                }
            }
            curr_door_is_small = door_is_small(curr_door, 2 * half_width_add);
        }

        this.autoroute_engine.complete_neigbour_rooms(p_list_element.next_room);

        FloatPoint shape_entry_middle = p_list_element.shape_entry.a.middle_point(p_list_element.shape_entry.b);

        if (this.ctrl.with_neckdown && p_list_element.door instanceof TargetItemExpansionDoor)
        {
            // try evtl. neckdown at a start pin
            Item start_item = ((TargetItemExpansionDoor) p_list_element.door).item;
            if (start_item instanceof eu.mihosoft.freerouting.board.Pin)
            {
                double neckdown_half_width = ((eu.mihosoft.freerouting.board.Pin) start_item).get_trace_neckdown_halfwidth(layer_no);
                if (neckdown_half_width > 0)
                {
                    half_width = Math.min(half_width, neckdown_half_width);
                }
            }

        }

        boolean next_room_is_thick = true;
        if (p_list_element.next_room instanceof ObstacleExpansionRoom)
        {
            next_room_is_thick = room_shape_is_thick((ObstacleExpansionRoom) p_list_element.next_room);
        }
        else
        {
            TileShape next_room_shape = p_list_element.next_room.get_shape();
            if (next_room_shape.min_width() < 2 * half_width)
            {
                next_room_is_thick = false; // to prevent probles with the opposite side
            }
            else if (!p_list_element.already_checked && p_list_element.door.get_dimension() == 1 && !curr_door_is_small)
            {
                // The algorithm below works only, if p_location is on the border of p_room_shape.
                // That is only the case for 1 dimensional doors.
                // For small doors the check is done in check_leaving_via below.

                FloatPoint[] nearest_points = next_room_shape.nearest_border_points_approx(shape_entry_middle, 2);
                if (nearest_points.length < 2)
                {
                    FRLogger.warn("MazeSearchAlgo.expand_to_room_doors: nearest_points.length == 2 expected");
                    next_room_is_thick = false;
                }
                else
                {
                    double curr_dist = nearest_points[1].distance(shape_entry_middle);
                    next_room_is_thick = (curr_dist > half_width + 1);
                }
            }
        }
        if (!layer_active && p_list_element.door instanceof ExpansionDrill)
        {
            // check for drill to a foreign conduction area on split plane.
            Point drill_location = ((ExpansionDrill) p_list_element.door).location;
            ItemSelectionFilter filter = new ItemSelectionFilter(ItemSelectionFilter.SelectableChoices.CONDUCTION);
            Set<Item> picked_items = autoroute_engine.board.pick_items(drill_location, layer_no, filter);
            for (Item curr_item : picked_items)
            {
                if (!curr_item.contains_net(ctrl.net_no))
                {
                    return true;
                }
            }
        }
        boolean something_expanded = false;
        if (expand_to_target_doors(p_list_element, next_room_is_thick, curr_door_is_small, shape_entry_middle))
        {
            something_expanded = true;
        }

        if (!layer_active)
        {
            return true;
        }

        int ripup_costs = 0;

        if (p_list_element.next_room instanceof FreeSpaceExpansionRoom)
        {
            if (!p_list_element.already_checked)
            {
                if (curr_door_is_small)
                {
                    boolean enter_through_small_door = false;
                    if (next_room_is_thick)
                    {
                        // check to enter the thick room from a ripped item through a small door (after ripup)
                        enter_through_small_door = check_leaving_ripped_item(p_list_element);
                    }
                    if (!enter_through_small_door)
                    {
                        return something_expanded;
                    }
                }

            }
        }
        else if (p_list_element.next_room instanceof ObstacleExpansionRoom)
        {
            ObstacleExpansionRoom obstacle_room = (ObstacleExpansionRoom) p_list_element.next_room;

            if (!p_list_element.already_checked)
            {
                boolean room_rippable = false;
                if (this.ctrl.ripup_allowed)
                {
                    ripup_costs = check_ripup(p_list_element, obstacle_room.get_item(), curr_door_is_small);
                    room_rippable = (ripup_costs >= 0);
                }

                if (ripup_costs != ALREADY_RIPPED_COSTS && next_room_is_thick)
                {
                    Item obstacle_item = obstacle_room.get_item();
                    if (!curr_door_is_small && this.ctrl.max_shove_trace_recursion_depth > 0 && obstacle_item instanceof eu.mihosoft.freerouting.board.PolylineTrace)
                    {
                        if (!shove_trace_room(p_list_element, obstacle_room))
                        {
                            if (ripup_costs > 0)
                            {
                                // delay the occupation by ripup to allow shoving the room by another door sections.
                                MazeListElement new_element =
                                        new MazeListElement(p_list_element.door, p_list_element.section_no_of_door,
                                        p_list_element.backtrack_door, p_list_element.section_no_of_backtrack_door,
                                        p_list_element.expansion_value + ripup_costs, p_list_element.sorting_value + ripup_costs,
                                        p_list_element.next_room, p_list_element.shape_entry, true,
                                        p_list_element.adjustment, true);
                                this.maze_expansion_list.add(new_element);
                            }
                            return something_expanded;
                        }
                    }
                }
                if (!room_rippable)
                {
                    return true;
                }
            }
        }

        for (ExpansionDoor to_door : p_list_element.next_room.get_doors())
        {
            if (to_door == p_list_element.door)
            {
                continue;
            }
            if (expand_to_door(to_door, p_list_element, ripup_costs, next_room_is_thick, MazeSearchElement.Adjustment.NONE))
            {
                something_expanded = true;
            }
        }

        // Expand also the drill pages intersecting the room.
        if (ctrl.vias_allowed && !(p_list_element.door instanceof ExpansionDrill))
        {
            if ((something_expanded || next_room_is_thick) && p_list_element.next_room instanceof CompleteFreeSpaceExpansionRoom)
            {
                // avoid setting something_expanded to true when next_room is thin to allow occupying by different sections of the door
                Collection<DrillPage> overlapping_drill_pages =
                        this.autoroute_engine.drill_page_array.overlapping_pages(p_list_element.next_room.get_shape());
                {
                    for (DrillPage to_drill_page : overlapping_drill_pages)
                    {
                        expand_to_drill_page(to_drill_page, p_list_element);
                        something_expanded = true;
                    }
                }
            }
            else if (p_list_element.next_room instanceof ObstacleExpansionRoom)
            {
                Item curr_obstacle_item = ((ObstacleExpansionRoom) p_list_element.next_room).get_item();
                if (curr_obstacle_item instanceof eu.mihosoft.freerouting.board.Via)
                {
                    eu.mihosoft.freerouting.board.Via curr_via = (eu.mihosoft.freerouting.board.Via) curr_obstacle_item;
                    ExpansionDrill via_drill_info = curr_via.get_autoroute_drill_info(this.autoroute_engine.autoroute_search_tree);
                    expand_to_drill(via_drill_info, p_list_element, ripup_costs);
                }
            }
        }

        return something_expanded;
    }

    /** Expand the target doors of the room.  Returns true, if at leat 1 target door was expanded */
    private boolean expand_to_target_doors(MazeListElement p_list_element,
                                           boolean p_next_room_is_thick, boolean p_curr_door_is_small, FloatPoint p_shape_entry_middle)
    {
        if (p_curr_door_is_small)
        {
            boolean enter_through_small_door = false;
            if (p_list_element.door instanceof ExpansionDoor)
            {
                CompleteExpansionRoom from_room = ((ExpansionDoor) p_list_element.door).other_room(p_list_element.next_room);
                if (from_room instanceof ObstacleExpansionRoom)
                {
                    // otherwise entering through the small door may fail, because it was not checked.
                    enter_through_small_door = true;
                }
            }
            if (!enter_through_small_door)
            {
                return false;
            }
        }
        boolean result = false;
        for (TargetItemExpansionDoor to_door : p_list_element.next_room.get_target_doors())
        {
            if (to_door == p_list_element.door)
            {
                continue;
            }
            TileShape target_shape = ((Connectable) to_door.item).get_trace_connection_shape(this.autoroute_engine.autoroute_search_tree,
                    to_door.tree_entry_no);
            FloatPoint connection_point = target_shape.nearest_point_approx(p_shape_entry_middle);
            if (!p_next_room_is_thick)
            {
                // check the line from p_shape_entry_middle to the nearest point.
                int[] curr_net_no_arr = new int[1];
                curr_net_no_arr[0] = this.ctrl.net_no;
                int curr_layer = p_list_element.next_room.get_layer();
                IntPoint[] check_points = new IntPoint[2];
                check_points[0] = p_shape_entry_middle.round();
                check_points[1] = connection_point.round();
                if (!check_points[0].equals(check_points[1]))
                {
                    Polyline check_polyline = new Polyline(check_points);
                    boolean check_ok = autoroute_engine.board.check_forced_trace_polyline(check_polyline,
                            ctrl.trace_half_width[curr_layer], curr_layer, curr_net_no_arr, ctrl.trace_clearance_class_no,
                            ctrl.max_shove_trace_recursion_depth, ctrl.max_shove_via_recursion_depth,
                            ctrl.max_spring_over_recursion_depth);
                    if (!check_ok)
                    {
                        continue;
                    }
                }
            }

            FloatLine new_shape_entry = new FloatLine(connection_point, connection_point);

            if (expand_to_door_section(to_door, 0, new_shape_entry, p_list_element,
                    0, MazeSearchElement.Adjustment.NONE))
            {
                result = true;
            }
        }
        return result;
    }

    /**
     * Return true, if at least 1 door ection was expanded.
     */
    private boolean expand_to_door(ExpansionDoor p_to_door, MazeListElement p_list_element, int p_add_costs,
                                   boolean p_next_room_is_thick, MazeSearchElement.Adjustment p_adjustment)
    {
        double half_width = ctrl.compensated_trace_half_width[p_list_element.next_room.get_layer()];
        boolean something_expanded = false;
        FloatLine[] line_sections = p_to_door.get_section_segments(half_width);

        for (int i = 0; i < line_sections.length; ++i)
        {
            if (p_to_door.section_arr[i].is_occupied)
            {
                continue;
            }
            FloatLine new_shape_entry;
            if (p_next_room_is_thick)
            {
                new_shape_entry = line_sections[i];
                if (p_to_door.dimension == 1 && line_sections.length == 1 && p_to_door.first_room instanceof CompleteFreeSpaceExpansionRoom && p_to_door.second_room instanceof CompleteFreeSpaceExpansionRoom)
                {
                    // check entering the p_to_door at an acute corner of the shape of p_list_element.next_room
                    FloatPoint shape_entry_middle = new_shape_entry.a.middle_point(new_shape_entry.b);
                    TileShape room_shape = p_list_element.next_room.get_shape();
                    if (room_shape.min_width() < 2 * half_width)
                    {
                        return false;
                    }
                    FloatPoint[] nearest_points = room_shape.nearest_border_points_approx(shape_entry_middle, 2);
                    if (nearest_points.length < 2 || nearest_points[1].distance(shape_entry_middle) <= half_width + 1)
                    {
                        return false;
                    }
                }
            }
            else
            {
                // expand only doors on the opposite side of the room from the shape_entry.
                if (p_to_door.dimension == 1 && i == 0 && line_sections[0].b.distance_square(line_sections[0].a) < 1)
                {
                    // p_to_door is small belonging to a via or thin room
                    continue;
                }
                new_shape_entry = segment_projection(p_list_element.shape_entry, line_sections[i]);
                if (new_shape_entry == null)
                {
                    continue;
                }
            }

            if (expand_to_door_section(p_to_door, i, new_shape_entry, p_list_element,
                    p_add_costs, p_adjustment))
            {
                something_expanded = true;
            }
        }
        return something_expanded;
    }

    /**
     * Checks, if the width p_door is big enough for a trace with width p_trace_width.
     */
    private boolean door_is_small(ExpansionDoor p_door, double p_trace_width)
    {
        if (p_door.dimension == 1 || p_door.first_room instanceof CompleteFreeSpaceExpansionRoom && p_door.second_room instanceof CompleteFreeSpaceExpansionRoom)
        {
            TileShape door_shape = p_door.get_shape();
            if (door_shape.is_empty())
            {
                if (this.autoroute_engine.board.get_test_level().ordinal() >= eu.mihosoft.freerouting.board.TestLevel.ALL_DEBUGGING_OUTPUT.ordinal())
                {
                    FRLogger.warn("MazeSearchAlgo:check_door_width door_shape is empty");
                }
                return true;
            }

            double door_length;
            AngleRestriction angle_restriction = autoroute_engine.board.rules.get_trace_angle_restriction();
            if (angle_restriction == AngleRestriction.NINETY_DEGREE)
            {
                IntBox door_box = door_shape.bounding_box();
                door_length = door_box.max_width();
            }
            else if (angle_restriction == AngleRestriction.FORTYFIVE_DEGREE)
            {
                IntOctagon door_oct = door_shape.bounding_octagon();
                door_length = door_oct.max_width();
            }
            else
            {
                FloatLine door_line_segment = door_shape.diagonal_corner_segment();
                door_length = door_line_segment.b.distance(door_line_segment.a);
            }
            if (door_length < p_trace_width)
            {
                return true;
            }
        }
        return false;
    }

    /**
     * Return true, if the door section was successfully expanded.
     */
    private boolean expand_to_door_section(ExpandableObject p_door, int p_section_no,
                                           FloatLine p_shape_entry, MazeListElement p_from_element,
                                           int p_add_costs, MazeSearchElement.Adjustment p_adjustment)
    {
        if (p_door.get_maze_search_element(p_section_no).is_occupied || p_shape_entry == null)
        {
            return false;
        }
        CompleteExpansionRoom next_room = p_door.other_room(p_from_element.next_room);
        int layer = p_from_element.next_room.get_layer();
        FloatPoint shape_entry_middle = p_shape_entry.a.middle_point(p_shape_entry.b);
        double expansion_value = p_from_element.expansion_value + p_add_costs
                + shape_entry_middle.weighted_distance(p_from_element.shape_entry.a.middle_point(p_from_element.shape_entry.b),
                ctrl.trace_costs[layer].horizontal, ctrl.trace_costs[layer].vertical);
        double sorting_value = expansion_value + this.destination_distance.calculate(shape_entry_middle, layer);
        boolean room_ripped = p_add_costs > 0 && p_adjustment == MazeSearchElement.Adjustment.NONE || p_from_element.already_checked && p_from_element.room_ripped;

        MazeListElement new_element =
                new MazeListElement(p_door, p_section_no, p_from_element.door, p_from_element.section_no_of_door,
                expansion_value, sorting_value, next_room, p_shape_entry, room_ripped, p_adjustment, false);
        this.maze_expansion_list.add(new_element);
        return true;
    }

    private void expand_to_drill(ExpansionDrill p_drill, MazeListElement p_from_element, int p_add_costs)
    {
        int layer = p_from_element.next_room.get_layer();
        int trace_half_width = this.ctrl.compensated_trace_half_width[layer];
        boolean room_shape_is_thin =
                p_from_element.next_room.get_shape().min_width() < 2 * trace_half_width;

        if (room_shape_is_thin)
        {
            // expand only drills intersecting the backtrack door
            if (p_from_element.backtrack_door == null || !p_drill.get_shape().intersects(p_from_element.backtrack_door.get_shape()))
            {
                return;
            }
        }

        double via_radius = ctrl.via_radius_arr[layer];
        ConvexShape shrinked_drill_shape = p_drill.get_shape().shrink(via_radius);
        FloatPoint compare_corner = p_from_element.shape_entry.a.middle_point(p_from_element.shape_entry.b);
        if (p_from_element.door instanceof DrillPage && p_from_element.backtrack_door instanceof TargetItemExpansionDoor)
        {
            // If expansion comes from a pin with trace exit directions the eapansion_value is calculated
            // from the nearest trace exit point instead from the center olf the pin.
            Item from_item = ((TargetItemExpansionDoor) p_from_element.backtrack_door).item;
            if (from_item instanceof eu.mihosoft.freerouting.board.Pin)
            {
                FloatPoint nearest_exit_corner = ((eu.mihosoft.freerouting.board.Pin) from_item).nearest_trace_exit_corner(p_drill.location.to_float(), trace_half_width, layer);
                if (nearest_exit_corner != null)
                {
                    compare_corner = nearest_exit_corner;
                }
            }
        }
        FloatPoint nearest_point =
                shrinked_drill_shape.nearest_point_approx(compare_corner);
        FloatLine shape_entry = new FloatLine(nearest_point, nearest_point);
        int section_no = layer - p_drill.first_layer;
        double expansion_value = p_from_element.expansion_value + p_add_costs
                + nearest_point.weighted_distance(compare_corner,
                ctrl.trace_costs[layer].horizontal, ctrl.trace_costs[layer].vertical);
        ExpandableObject new_backtrack_door;
        int new_section_no_of_backtrack_door;
        if (p_from_element.door instanceof DrillPage)
        {
            new_backtrack_door = p_from_element.backtrack_door;
            new_section_no_of_backtrack_door = p_from_element.section_no_of_backtrack_door;
        }
        else
        {
            // Expanded directly through already existing via
            // The step expand_to_drill_page is skipped
            new_backtrack_door = p_from_element.door;
            new_section_no_of_backtrack_door = p_from_element.section_no_of_door;
            expansion_value += ctrl.min_normal_via_cost;
        }
        double sorting_value = expansion_value + this.destination_distance.calculate(nearest_point, layer);
        MazeListElement new_element =
                new MazeListElement(p_drill, section_no, new_backtrack_door,
                new_section_no_of_backtrack_door, expansion_value, sorting_value, null, shape_entry,
                p_from_element.room_ripped, MazeSearchElement.Adjustment.NONE, false);
        this.maze_expansion_list.add(new_element);
    }

    /**
     * A drill page is inserted between an expansion roomm and the drill to expand
     * in order to prevent performance problems with rooms with big shapes containing many drills.
     */
    private void expand_to_drill_page(DrillPage p_drill_page, MazeListElement p_from_element)
    {

        int layer = p_from_element.next_room.get_layer();
        FloatPoint from_element_shape_entry_middle = p_from_element.shape_entry.a.middle_point(p_from_element.shape_entry.b);
        FloatPoint nearest_point =
                p_drill_page.shape.nearest_point(from_element_shape_entry_middle);
        double expansion_value = p_from_element.expansion_value + ctrl.min_normal_via_cost;
        double sorting_value =
                expansion_value + nearest_point.weighted_distance(from_element_shape_entry_middle,
                ctrl.trace_costs[layer].horizontal, ctrl.trace_costs[layer].vertical) + this.destination_distance.calculate(nearest_point, layer);
        MazeListElement new_element =
                new MazeListElement(p_drill_page, layer, p_from_element.door, p_from_element.section_no_of_door,
                expansion_value, sorting_value, p_from_element.next_room, p_from_element.shape_entry,
                p_from_element.room_ripped, MazeSearchElement.Adjustment.NONE, false);
        this.maze_expansion_list.add(new_element);
    }

    private void expand_to_drills_of_page(MazeListElement p_from_element)
    {
        int from_room_layer = p_from_element.section_no_of_door;
        DrillPage drill_page = (DrillPage) p_from_element.door;
        Collection<ExpansionDrill> drill_list = drill_page.get_drills(this.autoroute_engine, this.ctrl.attach_smd_allowed);
        for (ExpansionDrill curr_drill : drill_list)
        {
            int section_no = from_room_layer - curr_drill.first_layer;
            if (section_no < 0 || section_no >= curr_drill.room_arr.length)
            {
                continue;
            }
            if (curr_drill.room_arr[section_no] == p_from_element.next_room && !curr_drill.get_maze_search_element(section_no).is_occupied)
            {
                expand_to_drill(curr_drill, p_from_element, 0);
            }
        }
    }

    /**
     * Tries to expand other layers by inserting a via.
     */
    private void expand_to_other_layers(MazeListElement p_list_element)
    {
        int via_lower_bound = 0;
        int via_upper_bound = -1;
        ExpansionDrill curr_drill = (ExpansionDrill) p_list_element.door;
        int from_layer = curr_drill.first_layer + p_list_element.section_no_of_door;
        boolean smd_attached_on_component_side = false;
        boolean smd_attached_on_solder_side = false;
        boolean room_ripped;
        if (curr_drill.room_arr[p_list_element.section_no_of_door] instanceof ObstacleExpansionRoom)
        {
            // check ripup of an existing via
            if (!this.ctrl.ripup_allowed)
            {
                return;
            }
            Item curr_obstacle_item =
                    ((ObstacleExpansionRoom) curr_drill.room_arr[p_list_element.section_no_of_door]).get_item();
            if (!(curr_obstacle_item instanceof eu.mihosoft.freerouting.board.Via))
            {
                return;
            }
            eu.mihosoft.freerouting.library.Padstack curr_obstacle_padstack = ((eu.mihosoft.freerouting.board.Via) curr_obstacle_item).get_padstack();
            if (!this.ctrl.via_rule.contains_padstack(curr_obstacle_padstack) || curr_obstacle_item.clearance_class_no() != this.ctrl.via_clearance_class)
            {
                return;
            }
            via_lower_bound = curr_obstacle_padstack.from_layer();
            via_upper_bound = curr_obstacle_padstack.to_layer();
            room_ripped = true;
        }
        else
        {
            int[] net_no_arr = new int[1];
            net_no_arr[0] = ctrl.net_no;

            room_ripped = false;
            int via_lower_limit = Math.max(curr_drill.first_layer, ctrl.via_lower_bound);
            int via_upper_limit = Math.min(curr_drill.last_layer, ctrl.via_upper_bound);
            // Calculate the lower bound of possible vias.
            int curr_layer = from_layer;
            for (;;)
            {
                TileShape curr_room_shape = curr_drill.room_arr[curr_layer - curr_drill.first_layer].get_shape();
                eu.mihosoft.freerouting.board.ForcedPadAlgo.CheckDrillResult drill_result =
                        ForcedViaAlgo.check_layer(ctrl.via_radius_arr[curr_layer], ctrl.via_clearance_class,
                        ctrl.attach_smd_allowed, curr_room_shape, curr_drill.location, curr_layer, net_no_arr,
                        ctrl.max_shove_trace_recursion_depth, 0, autoroute_engine.board);
                if (drill_result == eu.mihosoft.freerouting.board.ForcedPadAlgo.CheckDrillResult.NOT_DRILLABLE)
                {
                    via_lower_bound = curr_layer + 1;
                    break;
                }
                else if (drill_result == eu.mihosoft.freerouting.board.ForcedPadAlgo.CheckDrillResult.DRILLABLE_WITH_ATTACH_SMD)
                {
                    if (curr_layer == 0)
                    {
                        smd_attached_on_component_side = true;
                    }
                    else if (curr_layer == ctrl.layer_count - 1)
                    {
                        smd_attached_on_solder_side = true;
                    }
                }
                if (curr_layer <= via_lower_limit)
                {
                    via_lower_bound = via_lower_limit;
                    break;
                }
                --curr_layer;
            }
            if (via_lower_bound > curr_drill.first_layer)
            {
                return;
            }
            curr_layer = from_layer + 1;
            for (;;)
            {
                if (curr_layer > via_upper_limit)
                {
                    via_upper_bound = via_upper_limit;
                    break;
                }
                TileShape curr_room_shape = curr_drill.room_arr[curr_layer - curr_drill.first_layer].get_shape();
                eu.mihosoft.freerouting.board.ForcedPadAlgo.CheckDrillResult drill_result =
                        ForcedViaAlgo.check_layer(ctrl.via_radius_arr[curr_layer], ctrl.via_clearance_class, ctrl.attach_smd_allowed,
                        curr_room_shape, curr_drill.location, curr_layer, net_no_arr,
                        ctrl.max_shove_trace_recursion_depth, 0, autoroute_engine.board);
                if (drill_result == eu.mihosoft.freerouting.board.ForcedPadAlgo.CheckDrillResult.NOT_DRILLABLE)
                {
                    via_upper_bound = curr_layer - 1;
                    break;
                }
                else if (drill_result == eu.mihosoft.freerouting.board.ForcedPadAlgo.CheckDrillResult.DRILLABLE_WITH_ATTACH_SMD)
                {
                    if (curr_layer == ctrl.layer_count - 1)
                    {
                        smd_attached_on_solder_side = true;
                    }
                }
                ++curr_layer;
            }
            if (via_upper_bound < curr_drill.last_layer)
            {
                return;
            }
        }

        for (int to_layer = via_lower_bound; to_layer <= via_upper_bound; ++to_layer)
        {
            if (to_layer == from_layer)
            {
                continue;
            }
            // check, there there is a fitting via mask.
            int curr_first_layer;
            int curr_last_layer;
            if (to_layer < from_layer)
            {
                curr_first_layer = to_layer;
                curr_last_layer = from_layer;
            }
            else
            {
                curr_first_layer = from_layer;
                curr_last_layer = to_layer;
            }
            boolean mask_found = false;
            for (int i = 0; i < ctrl.via_info_arr.length; ++i)
            {
                AutorouteControl.ViaMask curr_via_info = ctrl.via_info_arr[i];
                if (curr_first_layer >= curr_via_info.from_layer && curr_last_layer <= curr_via_info.to_layer && curr_via_info.from_layer >= via_lower_bound && curr_via_info.to_layer <= via_upper_bound)
                {
                    boolean mask_ok = true;
                    if (curr_via_info.from_layer == 0 && smd_attached_on_component_side || curr_via_info.to_layer == ctrl.layer_count - 1 && smd_attached_on_solder_side)
                    {
                        mask_ok = curr_via_info.attach_smd_allowed;
                    }
                    if (mask_ok)
                    {
                        mask_found = true;
                        break;
                    }
                }
            }
            if (!mask_found)
            {
                continue;
            }
            MazeSearchElement curr_drill_layer_info = curr_drill.get_maze_search_element(to_layer - curr_drill.first_layer);
            if (curr_drill_layer_info.is_occupied)
            {
                continue;
            }
            double expansion_value = p_list_element.expansion_value + ctrl.add_via_costs[from_layer].to_layer[to_layer];
            FloatPoint shape_entry_middle = p_list_element.shape_entry.a.middle_point(p_list_element.shape_entry.b);
            double sorting_value = expansion_value + this.destination_distance.calculate(shape_entry_middle, to_layer);
            int curr_room_index = to_layer - curr_drill.first_layer;
            MazeListElement new_element =
                    new MazeListElement(curr_drill, curr_room_index, curr_drill, p_list_element.section_no_of_door,
                    expansion_value, sorting_value, curr_drill.room_arr[curr_room_index], p_list_element.shape_entry,
                    room_ripped, MazeSearchElement.Adjustment.NONE, false);
            this.maze_expansion_list.add(new_element);
        }
    }

    /**
     * Initializes the maze search algorithm.
     * Returns false if the initialisation failed.
     */
    private boolean init(Set<Item> p_start_items, Set<Item> p_destination_items)
    {
        reduce_trace_shapes_at_tie_pins(p_start_items, this.ctrl.net_no, this.search_tree);
        reduce_trace_shapes_at_tie_pins(p_destination_items, this.ctrl.net_no, this.search_tree);
        // process the destination items
        boolean destination_ok = false;
        Iterator<Item> it = p_destination_items.iterator();
        while (it.hasNext())
        {
            if (this.autoroute_engine.is_stop_requested())
            {
                return false;
            }
            Item curr_item = it.next();
            ItemAutorouteInfo curr_info = curr_item.get_autoroute_info();
            curr_info.set_start_info(false);
            for (int i = 0; i < curr_item.tree_shape_count(this.search_tree); ++i)
            {
                TileShape curr_tree_shape = curr_item.get_tree_shape(this.search_tree, i);
                if (curr_tree_shape != null)
                {
                    destination_distance.join(curr_tree_shape.bounding_box(), curr_item.shape_layer(i));
                }
            }
            destination_ok = true;
        }
        if (!destination_ok && this.ctrl.is_fanout)
        {
            // detination set is not needed for fanout
            IntBox board_bounding_box = this.autoroute_engine.board.bounding_box;
            destination_distance.join(board_bounding_box, 0);
            destination_distance.join(board_bounding_box, this.ctrl.layer_count - 1);
            destination_ok = true;
        }

        if (!destination_ok)
        {
            return false;
        }
        // process the start items
        Collection<IncompleteFreeSpaceExpansionRoom> start_rooms = new LinkedList<IncompleteFreeSpaceExpansionRoom>();
        it = p_start_items.iterator();
        while (it.hasNext())
        {
            if (this.autoroute_engine.is_stop_requested())
            {
                return false;
            }
            Item curr_item = it.next();
            ItemAutorouteInfo curr_info = curr_item.get_autoroute_info();
            curr_info.set_start_info(true);
            if (curr_item instanceof Connectable)
            {
                for (int i = 0; i < curr_item.tree_shape_count(search_tree); ++i)
                {
                    TileShape contained_shape =
                            ((Connectable) curr_item).get_trace_connection_shape(search_tree, i);
                    IncompleteFreeSpaceExpansionRoom new_start_room = autoroute_engine.add_incomplete_expansion_room(null, curr_item.shape_layer(i), contained_shape);
                    start_rooms.add(new_start_room);
                }
            }
        }

        // complete the start rooms
        Collection<CompleteFreeSpaceExpansionRoom> completed_start_rooms = new LinkedList<CompleteFreeSpaceExpansionRoom>();


        if (this.autoroute_engine.maintain_database)
        {
            // add the completed start rooms carried over from the last autoroute to the start rooms.
            completed_start_rooms.addAll(this.autoroute_engine.get_rooms_with_target_items(p_start_items));
        }

        for (IncompleteFreeSpaceExpansionRoom curr_room : start_rooms)
        {
            if (this.autoroute_engine.is_stop_requested())
            {
                return false;
            }
            Collection<CompleteFreeSpaceExpansionRoom> curr_completed_rooms =
                    autoroute_engine.complete_expansion_room(curr_room);
            completed_start_rooms.addAll(curr_completed_rooms);
        }

        // Put the ItemExpansionDoors of the completed start rooms into
        // the maze_expansion_list.
        boolean start_ok = false;
        for (CompleteFreeSpaceExpansionRoom curr_room : completed_start_rooms)
        {
            Iterator<TargetItemExpansionDoor> it2 = curr_room.get_target_doors().iterator();
            while (it2.hasNext())
            {
                if (this.autoroute_engine.is_stop_requested())
                {
                    return false;
                }
                TargetItemExpansionDoor curr_door = it2.next();
                if (curr_door.is_destination_door())
                {
                    continue;
                }
                TileShape connection_shape = ((Connectable) curr_door.item).get_trace_connection_shape(search_tree, curr_door.tree_entry_no);
                connection_shape = connection_shape.intersection(curr_door.room.get_shape());
                FloatPoint curr_center = connection_shape.centre_of_gravity();
                FloatLine shape_entry = new FloatLine(curr_center, curr_center);
                double sorting_value = this.destination_distance.calculate(curr_center, curr_room.get_layer());
                MazeListElement new_list_element =
                        new MazeListElement(curr_door, 0, null, 0, 0, sorting_value, curr_room, shape_entry, false,
                        MazeSearchElement.Adjustment.NONE, false);
                maze_expansion_list.add(new_list_element);
                start_ok = true;
            }
        }
        return start_ok;
    }

    /**
     * Looks for pins with more than 1 nets and reduces shapes of thraces of foreign nets,
     * which are already connected to such a pin, so that the pin center is not blocked  for connection.
     */
    private static void reduce_trace_shapes_at_tie_pins(Collection<Item> p_item_list, int p_own_net_no, ShapeSearchTree p_autoroute_tree)
    {
        for (Item curr_item : p_item_list)
        {
            if ((curr_item instanceof eu.mihosoft.freerouting.board.Pin) && curr_item.net_count() > 1)
            {
                Collection<Item> pin_contacts = curr_item.get_normal_contacts();
                eu.mihosoft.freerouting.board.Pin curr_tie_pin = (eu.mihosoft.freerouting.board.Pin) curr_item;
                for (Item curr_contact : pin_contacts)
                {
                    if (!(curr_contact instanceof eu.mihosoft.freerouting.board.PolylineTrace) || curr_contact.contains_net(p_own_net_no))
                    {
                        continue;
                    }
                    p_autoroute_tree.reduce_trace_shape_at_tie_pin(curr_tie_pin, (eu.mihosoft.freerouting.board.PolylineTrace) curr_contact);
                }
            }
        }
    }

    private boolean room_shape_is_thick(ObstacleExpansionRoom p_obstacle_room)
    {
        Item obstacle_item = p_obstacle_room.get_item();
        int layer = p_obstacle_room.get_layer();
        double obstacle_half_width;
        if (obstacle_item instanceof eu.mihosoft.freerouting.board.Trace)
        {
            obstacle_half_width = ((eu.mihosoft.freerouting.board.Trace) obstacle_item).get_half_width()
                    + this.search_tree.clearance_compensation_value(obstacle_item.clearance_class_no(), layer);

        }
        else if (obstacle_item instanceof eu.mihosoft.freerouting.board.Via)
        {
            TileShape via_shape = ((eu.mihosoft.freerouting.board.Via) obstacle_item).get_tree_shape_on_layer(this.search_tree, layer);
            obstacle_half_width = 0.5 * via_shape.max_width();
        }
        else
        {
            FRLogger.warn("MazeSearchAlgo. room_shape_is_thick: unexpected obstacle item");
            obstacle_half_width = 0;
        }
        return obstacle_half_width >= this.ctrl.compensated_trace_half_width[layer];
    }

    /** Checks, if the room can be ripped and returns the rip up costs,
     * which are > 0, if the room is ripped and -1, if no ripup was possible.
     * If the previous room was also ripped and contained the same item
     * or an item of the same connection, the result will be
     * equal to ALREADY_RIPPED_COSTS
     */
    private int check_ripup(MazeListElement p_list_element, Item p_obstacle_item, boolean p_door_is_small)
    {
        if (!p_obstacle_item.is_route())
        {
            return -1;
        }
        if (p_door_is_small)
        {
            // allow entering a via or trace, if its corresponding border segment is smaller than the current trace width

            if (!enter_through_small_door(p_list_element, p_obstacle_item))
            {
                return -1;
            }

        }
        CompleteExpansionRoom previous_room = p_list_element.door.other_room(p_list_element.next_room);
        boolean room_was_shoved = p_list_element.adjustment != MazeSearchElement.Adjustment.NONE;
        Item previous_item = null;
        if (previous_room != null && previous_room instanceof ObstacleExpansionRoom)
        {
            previous_item = ((ObstacleExpansionRoom) previous_room).get_item();
        }
        if (room_was_shoved)
        {
            if (previous_item != null && previous_item != p_obstacle_item && previous_item.shares_net(p_obstacle_item))
            {
                // The ripped trace may start at a fork.
                return -1;
            }
        }
        else if (previous_item == p_obstacle_item)
        {
            return ALREADY_RIPPED_COSTS;
        }

        double fanout_via_cost_factor = 1.0;
        double cost_factor = 1;
        if (p_obstacle_item instanceof eu.mihosoft.freerouting.board.Trace)
        {
            eu.mihosoft.freerouting.board.Trace obstacle_trace = (eu.mihosoft.freerouting.board.Trace) p_obstacle_item;
            cost_factor = obstacle_trace.get_half_width();
            if (!this.ctrl.remove_unconnected_vias)
            {
                // protect traces between SMD-pins and fanout vias
                fanout_via_cost_factor = calc_fanout_via_ripup_cost_factor(obstacle_trace);
            }
        }
        else if (p_obstacle_item instanceof eu.mihosoft.freerouting.board.Via)
        {
            boolean look_if_fanout_via = !this.ctrl.remove_unconnected_vias;
            Collection<Item> contact_list = p_obstacle_item.get_normal_contacts();
            int contact_count = 0;
            for (Item curr_contact : contact_list)
            {
                if (!(curr_contact instanceof eu.mihosoft.freerouting.board.Trace) || curr_contact.is_user_fixed())
                {
                    return -1;
                }
                ++contact_count;
                eu.mihosoft.freerouting.board.Trace obstacle_trace = (eu.mihosoft.freerouting.board.Trace) curr_contact;
                cost_factor = Math.max(cost_factor, obstacle_trace.get_half_width());
                if (look_if_fanout_via)
                {
                    double curr_fanout_via_cost_factor = calc_fanout_via_ripup_cost_factor(obstacle_trace);
                    if (curr_fanout_via_cost_factor > 1)
                    {
                        fanout_via_cost_factor = curr_fanout_via_cost_factor;
                        look_if_fanout_via = false;
                    }
                }
            }
            if (fanout_via_cost_factor <= 1)
            {
                // not a fanout via
                cost_factor *= 0.5 * Math.max(contact_count - 1, 0);
            }
        }

        double ripup_cost = this.ctrl.ripup_costs * cost_factor;
        double detour = 1;
        if (fanout_via_cost_factor <= 1) // p_obstacle_item does not belong to a fanout
        {
            Connection obstacle_connection = Connection.get(p_obstacle_item);
            if (obstacle_connection != null)
            {
                detour = obstacle_connection.get_detour();
            }
        }
        boolean randomize = this.ctrl.ripup_pass_no >= 4 && this.ctrl.ripup_pass_no % 3 != 0;
        if (randomize)
        {
            // shuffle the result to avoid repetitive loops
            double random_number = this.random_generator.nextDouble();
            double random_factor = 0.5 + random_number * random_number;
            detour *= random_factor;
        }
        ripup_cost /= detour;

        ripup_cost *= fanout_via_cost_factor;
        int result = Math.max((int) ripup_cost, 1);
        final int MAX_RIPUP_COSTS = Integer.MAX_VALUE / 100;
        return Math.min(result, MAX_RIPUP_COSTS);
    }

    /**
     *  Return the addditional cost factor for ripping the trace, if it is connected to a fanout via
     *  or 1, if no fanout via was found. 
     */
    private static double calc_fanout_via_ripup_cost_factor(eu.mihosoft.freerouting.board.Trace p_trace)
    {
        final double FANOUT_COST_CONST = 20000;
        Collection<Item> curr_end_contacts;
        for (int i = 0; i < 2; ++i)
        {
            if (i == 0)
            {
                curr_end_contacts = p_trace.get_start_contacts();
            }
            else
            {
                curr_end_contacts = p_trace.get_end_contacts();
            }
            if (curr_end_contacts.size() != 1)
            {
                continue;
            }
            Item curr_trace_contact = curr_end_contacts.iterator().next();
            boolean protect_fanout_via = false;
            if (curr_trace_contact instanceof eu.mihosoft.freerouting.board.Pin && curr_trace_contact.first_layer() == curr_trace_contact.last_layer())
            {
                protect_fanout_via = true;
            }
            else if (curr_trace_contact instanceof PolylineTrace && curr_trace_contact.get_fixed_state() == eu.mihosoft.freerouting.board.FixedState.SHOVE_FIXED)
            {
                // look for shove fixed exit traces of SMD-pins
                PolylineTrace contact_trace = (PolylineTrace) curr_trace_contact;
                if (contact_trace.corner_count() == 2)
                {
                    protect_fanout_via = true;
                }
            }


            if (protect_fanout_via)
            {
                double fanout_via_cost_factor = p_trace.get_half_width() / p_trace.get_length();
                fanout_via_cost_factor *= fanout_via_cost_factor;
                fanout_via_cost_factor *= FANOUT_COST_CONST;
                fanout_via_cost_factor = Math.max(fanout_via_cost_factor, 1);
                return fanout_via_cost_factor;
            }
        }
        return 1;
    }

    /**
     * Shoves a trace room and expands the corresponding doors.
     * Return false, if no door was expanded. In this case occupation of the door_section  by ripup can be delayed
     * to allow shoving the room fromm a different door section
     */
    private boolean shove_trace_room(MazeListElement p_list_element, ObstacleExpansionRoom p_obstacle_room)
    {
        if (p_list_element.section_no_of_door != 0
                && p_list_element.section_no_of_door != p_list_element.door.maze_search_element_count() - 1)
        {
            // No delay of occupation necessesary because inner sections of a door are currently not shoved.
            return true;
        }
        boolean result = false;
        if (p_list_element.adjustment != MazeSearchElement.Adjustment.RIGHT)
        {
            Collection<MazeShoveTraceAlgo.DoorSection> left_to_door_section_list = new java.util.LinkedList<MazeShoveTraceAlgo.DoorSection>();

            if (MazeShoveTraceAlgo.check_shove_trace_line(p_list_element, p_obstacle_room,
                    this.autoroute_engine.board, this.ctrl, false, left_to_door_section_list))
            {
                result = true;
            }

            for (MazeShoveTraceAlgo.DoorSection curr_left_door_section : left_to_door_section_list)
            {
                MazeSearchElement.Adjustment curr_adjustment;
                if (curr_left_door_section.door.dimension == 2)
                {
                    // the door is the link door to the next room
                    curr_adjustment = MazeSearchElement.Adjustment.LEFT;
                }
                else
                {
                    curr_adjustment = MazeSearchElement.Adjustment.NONE;
                }

                expand_to_door_section(curr_left_door_section.door, curr_left_door_section.section_no,
                        curr_left_door_section.section_line, p_list_element,
                        0, curr_adjustment);
            }
        }

        if (p_list_element.adjustment != MazeSearchElement.Adjustment.LEFT)
        {
            Collection<MazeShoveTraceAlgo.DoorSection> right_to_door_section_list = new java.util.LinkedList<MazeShoveTraceAlgo.DoorSection>();

            if (MazeShoveTraceAlgo.check_shove_trace_line(p_list_element, p_obstacle_room,
                    this.autoroute_engine.board, this.ctrl, true, right_to_door_section_list))
            {
                result = true;
            }
            for (MazeShoveTraceAlgo.DoorSection curr_right_door_section : right_to_door_section_list)
            {
                MazeSearchElement.Adjustment curr_adjustment;
                if (curr_right_door_section.door.dimension == 2)
                {
                    // the door is the link door to the next room
                    curr_adjustment = MazeSearchElement.Adjustment.RIGHT;
                }
                else
                {
                    curr_adjustment = MazeSearchElement.Adjustment.NONE;
                }
                expand_to_door_section(curr_right_door_section.door, curr_right_door_section.section_no,
                        curr_right_door_section.section_line, p_list_element,
                        0, curr_adjustment);
            }
        }
        return result;
    }

    /**
     * Checks, if the next roomm contains a destination pin, where evtl. neckdown is necessary.
     * Return the neck down width in this case, or 0, if no such pin waas found,
     */
    private double check_neck_down_at_dest_pin(CompleteExpansionRoom p_room)
    {
        Collection<TargetItemExpansionDoor> target_doors = p_room.get_target_doors();
        for (TargetItemExpansionDoor curr_target_door : target_doors)
        {
            if (curr_target_door.item instanceof eu.mihosoft.freerouting.board.Pin)
            {
                return ((eu.mihosoft.freerouting.board.Pin) curr_target_door.item).get_trace_neckdown_halfwidth(p_room.get_layer());
            }
        }
        return 0;
    }

    /**
     * Returns the perpendicular projection of p_from_segment onto p_to_segment.
     * Returns null, if the projection is empty.
     */
    private static FloatLine segment_projection(FloatLine p_from_segment, FloatLine p_to_segment)
    {
        FloatLine check_segment = p_from_segment.adjust_direction(p_to_segment);
        FloatLine first_projection = p_to_segment.segment_projection(check_segment);
        FloatLine second_projection = p_to_segment.segment_projection_2(check_segment);
        FloatLine result;
        if (first_projection != null && second_projection != null)
        {
            FloatPoint result_a;
            if (first_projection.a == p_to_segment.a || second_projection.a == p_to_segment.a)
            {
                result_a = p_to_segment.a;
            }
            else if (first_projection.a.distance_square(p_to_segment.a) <= second_projection.a.distance_square(p_to_segment.a))
            {
                result_a = first_projection.a;
            }
            else
            {
                result_a = second_projection.a;
            }
            FloatPoint result_b;
            if (first_projection.b == p_to_segment.b || second_projection.b == p_to_segment.b)
            {
                result_b = p_to_segment.b;
            }
            else if (first_projection.b.distance_square(p_to_segment.b) <= second_projection.b.distance_square(p_to_segment.b))
            {
                result_b = first_projection.b;
            }
            else
            {
                result_b = second_projection.b;
            }
            result = new FloatLine(result_a, result_b);
        }
        else if (first_projection != null)
        {
            result = first_projection;
        }
        else
        {
            result = second_projection;
        }
        return result;
    }

    /**
     * Checks, if the next room can be entered if the door of p_list_element is small.
     * If p_ignore_item != null, p_ignore_item and all other items directly connected to p_ignore_item
     * are ignored in the check.
     */
    private boolean enter_through_small_door(MazeListElement p_list_element, Item p_ignore_item)
    {
        if (p_list_element.door.get_dimension() != 1)
        {
            return false;
        }
        TileShape door_shape = p_list_element.door.get_shape();

        // Get the line of the 1 dimensional door.
        Line door_line = null;
        FloatPoint prev_corner = door_shape.corner_approx(0);
        int corner_count = door_shape.border_line_count();
        for (int i = 1; i < corner_count; ++i)
        {
            // skip lines of lenghth 0
            FloatPoint next_corner = door_shape.corner_approx(i);
            if (next_corner.distance_square(prev_corner) > 1)
            {
                door_line = door_shape.border_line(i - 1);
                break;
            }
            prev_corner = next_corner;
        }
        if (door_line == null)
        {
            return false;
        }

        IntPoint door_center = door_shape.centre_of_gravity().round();
        int curr_layer = p_list_element.next_room.get_layer();
        int check_radius =
                this.ctrl.compensated_trace_half_width[curr_layer] + AutorouteEngine.TRACE_WIDTH_TOLERANCE;
        // create a perpendicular line segment of length 2 * check_radius through the door center
        Line[] line_arr = new Line[3];
        line_arr[0] = door_line.translate(check_radius);
        line_arr[1] = new Line(door_center, door_line.direction().turn_45_degree(2));
        line_arr[2] = door_line.translate(-check_radius);

        Polyline check_polyline = new Polyline(line_arr);
        TileShape check_shape = check_polyline.offset_shape(check_radius, 0);
        int[] ignore_net_nos = new int[1];
        ignore_net_nos[0] = this.ctrl.net_no;
        Set<SearchTreeObject> overlapping_objects = new TreeSet<SearchTreeObject>();
        this.autoroute_engine.autoroute_search_tree.overlapping_objects(check_shape, curr_layer,
                ignore_net_nos, overlapping_objects);

        for (SearchTreeObject curr_object : overlapping_objects)
        {
            if (!(curr_object instanceof Item) || curr_object == p_ignore_item)
            {
                continue;
            }
            Item curr_item = (Item) curr_object;
            if (!curr_item.shares_net(p_ignore_item))
            {
                return false;
            }
            Set<Item> curr_contacts = curr_item.get_normal_contacts();
            if (!curr_contacts.contains(p_ignore_item))
            {
                return false;
            }
        }
        return true;
    }

    /**  Checks entering a thick room from a via or trace through a small door (after ripup) */
    private boolean check_leaving_ripped_item(MazeListElement p_list_element)
    {
        if (!(p_list_element.door instanceof ExpansionDoor))
        {
            return false;
        }
        ExpansionDoor curr_door = (ExpansionDoor) p_list_element.door;
        CompleteExpansionRoom from_room = curr_door.other_room(p_list_element.next_room);
        if (!(from_room instanceof ObstacleExpansionRoom))
        {
            return false;
        }
        Item curr_item = ((ObstacleExpansionRoom) from_room).get_item();
        if (!curr_item.is_route())
        {
            return false;
        }
        return enter_through_small_door(p_list_element, curr_item);
    }
    /**
     * The autoroute engine of this expansion algorithm.
     */
    public final AutorouteEngine autoroute_engine;
    final AutorouteControl ctrl;
    /**
     * The seach tree for expanding. It is the tree compensated for the current net.
     */
    private final ShapeSearchTree search_tree;
    /**
     * The queue of of expanded elements used in this search algorithm.
     */
    final java.util.SortedSet<MazeListElement> maze_expansion_list;
    /**
     * Used for calculating of a good lower bound for the distance between a new MazeExpansionElement
     * and the destination set of the expansion.
     */
    final DestinationDistance destination_distance;
    /**
     * The destination door found by the expanding algorithm.
     */
    private ExpandableObject destination_door = null;
    private int section_no_of_destination_door = 0;
    private final java.util.Random random_generator = new java.util.Random();
    private static final int ALREADY_RIPPED_COSTS = 1;

    /**
     *  The result type of MazeSearchAlgo.find_connection
     */
    public static class Result
    {

        Result(ExpandableObject p_destination_door, int p_section_no_of_door)
        {
            destination_door = p_destination_door;
            section_no_of_door = p_section_no_of_door;
        }
        public final ExpandableObject destination_door;
        public final int section_no_of_door;
    }

    /**
     * Used for the result of MazeShoveViaAlgo.check_shove_via and MazeShoveThinRoomAlgo.check_shove_thin_room.
     */
    static class ShoveResult
    {

        /** The opposite door to be expanded */
        final ExpansionDoor opposite_door;
        /** The doors at the adjusted edge of the room shape to be expanded. */
        final Collection<ExpansionDoor> side_doors;
        /** The passing point of a trace through the from_door after adjustment. */
        final FloatPoint from_door_passing_point;
        /** The passing point of a trace through the opposite door after adjustment. */
        final FloatPoint opposite_door_passing_point;

        ShoveResult(ExpansionDoor p_opposite_door, Collection<ExpansionDoor> p_side_doors,
                    FloatPoint p_from_door_passing_point, FloatPoint p_opposite_door_passing_point)
        {
            opposite_door = p_opposite_door;
            side_doors = p_side_doors;
            from_door_passing_point = p_from_door_passing_point;
            opposite_door_passing_point = p_opposite_door_passing_point;
        }
    }
}
