/*
 *   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.
 */
package eu.mihosoft.freerouting.interactive;

import eu.mihosoft.freerouting.datastructures.TimeLimit;

import eu.mihosoft.freerouting.geometry.planar.Area;
import eu.mihosoft.freerouting.geometry.planar.FloatPoint;
import eu.mihosoft.freerouting.geometry.planar.IntBox;
import eu.mihosoft.freerouting.geometry.planar.IntOctagon;
import eu.mihosoft.freerouting.geometry.planar.IntPoint;
import eu.mihosoft.freerouting.geometry.planar.Vector;
import eu.mihosoft.freerouting.geometry.planar.Point;
import eu.mihosoft.freerouting.geometry.planar.Polyline;
import eu.mihosoft.freerouting.geometry.planar.Ellipse;

import java.awt.Graphics;
import java.util.Collection;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.Set;

import eu.mihosoft.freerouting.library.Padstack;

import eu.mihosoft.freerouting.logger.FRLogger;
import eu.mihosoft.freerouting.rules.ViaRule;
import eu.mihosoft.freerouting.rules.ViaInfo;
import eu.mihosoft.freerouting.rules.Net;

import eu.mihosoft.freerouting.board.AngleRestriction;
import eu.mihosoft.freerouting.board.Trace;
import eu.mihosoft.freerouting.board.ConductionArea;
import eu.mihosoft.freerouting.board.DrillItem;
import eu.mihosoft.freerouting.board.Item;
import eu.mihosoft.freerouting.board.PolylineTrace;
import eu.mihosoft.freerouting.board.RoutingBoard;
import eu.mihosoft.freerouting.board.ItemSelectionFilter;
import eu.mihosoft.freerouting.board.TestLevel;
import eu.mihosoft.freerouting.board.Unit;

import eu.mihosoft.freerouting.boardgraphics.GraphicsContext;

/**
 *
 * Functionality for interactive routing.
 *
 * @author Alfons Wirtz
 */
public class Route
{

    /**
     * Starts routing a connection.
     * p_pen_half_width_arr is provided because it may be different from
     * the half width array in p_board.rules.
     */
    public Route(Point p_start_corner, int p_layer, int[] p_pen_half_width_arr, boolean[] p_layer_active_arr, int[] p_net_no_arr,
            int p_clearance_class, ViaRule p_via_rule, boolean p_push_enabled,
            int p_trace_tidy_width, int p_pull_tight_accuracy, Item p_start_item, Set<Item> p_target_set,
            RoutingBoard p_board, boolean p_is_stitch_mode, boolean p_with_neckdown, boolean p_via_snap_to_smd_center,
            boolean p_hilight_shove_failing_obstacle)
    {
        board = p_board;
        layer = p_layer;
        if (p_push_enabled)
        {
            max_shove_trace_recursion_depth = 20;
            max_shove_via_recursion_depth = 8;
            max_spring_over_recursion_depth = 5;
        }
        else
        {
            max_shove_trace_recursion_depth = 0;
            max_shove_via_recursion_depth = 0;
            max_spring_over_recursion_depth = 0;
        }
        trace_tidy_width = p_trace_tidy_width;
        pull_tight_accuracy = p_pull_tight_accuracy;
        prev_corner = p_start_corner;
        net_no_arr = p_net_no_arr;
        pen_half_width_arr = p_pen_half_width_arr;
        layer_active = p_layer_active_arr;
        clearance_class = p_clearance_class;
        via_rule = p_via_rule;
        start_item = p_start_item;
        target_set = p_target_set;
        is_stitch_mode = p_is_stitch_mode;
        with_neckdown = p_with_neckdown;
        via_snap_to_smd_center = p_via_snap_to_smd_center;
        hilight_shove_failing_obstacle = p_hilight_shove_failing_obstacle;
        if (p_board.get_test_level() == TestLevel.RELEASE_VERSION)
        {
            this.pull_tight_time_limit = PULL_TIGHT_TIME_LIMIT;
        }
        else
        {
            this.pull_tight_time_limit = 0;
        }
        calculate_target_points_and_areas();
        swap_pin_infos = calculate_swap_pin_infos();
    }

    /**
     * Append a line to the trace routed so far.
     * Return true, if the route is completed by connecting
     * to a target.
     */
    public boolean next_corner(FloatPoint p_corner)
    {
        if (!this.layer_active[this.layer])
        {
            return false;
        }
        IntPoint curr_corner = p_corner.round();
        if (!(board.contains(prev_corner) && board.contains(curr_corner) && board.layer_structure.arr[this.layer].is_signal))
        {
            return false;
        }

        if (curr_corner.equals(prev_corner))
        {
            return false;
        }
        if (nearest_target_item instanceof DrillItem)
        {
            DrillItem target = (DrillItem) nearest_target_item;
            if (this.prev_corner.equals(target.get_center()))
            {
                return true; // connection already completed at prev_corner.
            }
        }
        this.shove_failing_obstacle = null;
        AngleRestriction angle_restriction = this.board.rules.get_trace_angle_restriction();
        if (angle_restriction != AngleRestriction.NONE && !(prev_corner instanceof IntPoint))
        {
            return false;
        }
        if (angle_restriction == AngleRestriction.NINETY_DEGREE)
        {
            curr_corner = curr_corner.orthogonal_projection((IntPoint) prev_corner);
        }
        else if (angle_restriction == AngleRestriction.FORTYFIVE_DEGREE)
        {
            curr_corner = curr_corner.fortyfive_degree_projection((IntPoint) prev_corner);
        }
        Item end_routing_item = board.pick_nearest_routing_item(prev_corner, this.layer, null);
        // look for a nearby item of this net, which is not connected to end_routing_item.
        nearest_target_item = board.pick_nearest_routing_item(curr_corner, this.layer, end_routing_item);
        TimeLimit check_forced_trace_time_limit;
        if (is_stitch_mode || this.board.get_test_level() != TestLevel.RELEASE_VERSION)
        {
            // because no check before inserting in this case
            check_forced_trace_time_limit = null;
        }
        else
        {
            check_forced_trace_time_limit = new TimeLimit(CHECK_FORCED_TRACE_TIME_LIMIT);
        }


        // eu.mihosoft.freerouting.tests.Validate.check("before insert", eu.mihosoft.freerouting.board);
        Point ok_point = board.insert_forced_trace_segment(prev_corner,
                curr_corner, pen_half_width_arr[layer], layer, net_no_arr, clearance_class,
                max_shove_trace_recursion_depth, max_shove_via_recursion_depth, max_spring_over_recursion_depth,
                trace_tidy_width, pull_tight_accuracy, !is_stitch_mode, check_forced_trace_time_limit);
        // eu.mihosoft.freerouting.tests.Validate.check("after insert", eu.mihosoft.freerouting.board);
        if (ok_point == prev_corner && this.with_neckdown)
        {
            ok_point = try_neckdown_at_start(curr_corner);
        }
        if (ok_point == prev_corner && this.with_neckdown)
        {
            ok_point = try_neckdown_at_end(this.prev_corner, curr_corner);
        }
        if (ok_point == null)
        {
            // database may be damaged, restore previous situation
            board.undo(null);
            // end routing in case it is dynamic
            return (!is_stitch_mode);
        }

        if (ok_point == prev_corner)
        {
            set_shove_failing_obstacle(board.get_shove_failing_obstacle());
            return false;
        }
        this.prev_corner = ok_point;
        // check, if a target is reached
        boolean route_completed = false;
        if (ok_point == curr_corner)
        {
            route_completed = connect_to_target(curr_corner);
        }

        IntOctagon tidy_clip_shape;
        if (trace_tidy_width == Integer.MAX_VALUE)
        {
            tidy_clip_shape = null;
        }
        else if (trace_tidy_width == 0)
        {
            tidy_clip_shape = IntOctagon.EMPTY;
        }
        else
        {
            tidy_clip_shape = ok_point.surrounding_octagon().enlarge(trace_tidy_width);
        }
        int[] opt_net_no_arr;
        if (max_shove_trace_recursion_depth <= 0)
        {
            opt_net_no_arr = net_no_arr;
        }
        else
        {
            opt_net_no_arr = new int[0];
        }
        if (route_completed)
        {
            this.board.reduce_nets_of_route_items();
            for (int curr_net_no : this.net_no_arr)
            {
                this.board.combine_traces(curr_net_no);
            }
        }
        else
        {
            calc_nearest_target_point(this.prev_corner.to_float());
        }
        board.opt_changed_area(opt_net_no_arr, tidy_clip_shape, pull_tight_accuracy,
                null, null, pull_tight_time_limit, ok_point, layer);
        return route_completed;
    }

    /**
     * Changing the layer in interactive route and inserting a via.
     * Returns false, if changing the layer was not possible.
     */
    public boolean change_layer(int p_to_layer)
    {
        if (this.layer == p_to_layer)
        {
            return true;
        }
        if (p_to_layer < 0 || p_to_layer >= this.layer_active.length)
        {
            FRLogger.warn("Route.change_layer: p_to_layer out of range");
            return false;
        }
        if (!this.layer_active[p_to_layer])
        {
            return false;
        }
        if (this.via_rule == null)
        {
            return false;
        }
        this.shove_failing_obstacle = null;
        if (this.via_snap_to_smd_center)
        {
            boolean snapped_to_smd_center = snap_to_smd_center(p_to_layer);
            if (!snapped_to_smd_center)
            {
                snap_to_smd_center(this.layer);
            }
        }
        boolean result = true;
        int min_layer = Math.min(this.layer, p_to_layer);
        int max_layer = Math.max(this.layer, p_to_layer);
        boolean via_found = false;
        for (int i = 0; i < this.via_rule.via_count(); ++i)
        {
            ViaInfo curr_via_info = this.via_rule.get_via(i);
            Padstack curr_via_padstack = curr_via_info.get_padstack();
            if (min_layer < curr_via_padstack.from_layer() || max_layer > curr_via_padstack.to_layer())
            {
                continue;
            }
            // make the current situation restorable by undo
            board.generate_snapshot();
            result = board.forced_via(curr_via_info, this.prev_corner, this.net_no_arr, clearance_class,
                    pen_half_width_arr, max_shove_trace_recursion_depth,
                    0, this.trace_tidy_width, this.pull_tight_accuracy, pull_tight_time_limit);
            if (result)
            {
                via_found = true;
                break;
            }
            set_shove_failing_obstacle(board.get_shove_failing_obstacle());
            board.undo(null);
        }
        if (via_found)
        {
            this.layer = p_to_layer;
        }
        return result;
    }

    /**
     * Snaps to the center of an smd pin, if the location location on p_layer is inside an smd pin of the own net,
     */
    private boolean snap_to_smd_center(int p_layer)
    {
        ItemSelectionFilter selection_filter = new ItemSelectionFilter(ItemSelectionFilter.SelectableChoices.PINS);
        java.util.Collection<Item> picked_items = board.pick_items(this.prev_corner, p_layer, selection_filter);
        eu.mihosoft.freerouting.board.Pin found_smd_pin = null;
        for (Item curr_item : picked_items)
        {
            if (curr_item instanceof eu.mihosoft.freerouting.board.Pin && curr_item.shares_net_no(this.net_no_arr))
            {
                eu.mihosoft.freerouting.board.Pin curr_pin = (eu.mihosoft.freerouting.board.Pin) curr_item;
                if (curr_pin.first_layer() == p_layer && curr_pin.last_layer() == p_layer)
                {
                    found_smd_pin = curr_pin;
                    break;
                }
            }
        }
        if (found_smd_pin == null)
        {
            return false;
        }
        Point pin_center = found_smd_pin.get_center();
        if (!(pin_center instanceof IntPoint))
        {
            return false;
        }
        IntPoint to_corner = (IntPoint) pin_center;
        if (this.connect(this.prev_corner, to_corner))
        {
            this.prev_corner = to_corner;
        }
        return true;
    }

    /**
     * If p_from_point is already on a target item, a connection
     * to the target is made and true returned.
     */
    private boolean connect_to_target(IntPoint p_from_point)
    {
        if (nearest_target_item != null && target_set != null && !target_set.contains(nearest_target_item))
        {
            nearest_target_item = null;
        }
        if (nearest_target_item == null || !nearest_target_item.shares_net_no(this.net_no_arr))
        {
            return false;
        }
        boolean route_completed = false;
        Point connection_point = null;
        if (nearest_target_item instanceof DrillItem)
        {
            DrillItem target = (DrillItem) nearest_target_item;
            connection_point = target.get_center();
        }
        else if (nearest_target_item instanceof PolylineTrace)
        {
            return board.connect_to_trace(p_from_point, (PolylineTrace) nearest_target_item,
                    this.pen_half_width_arr[layer], this.clearance_class);
        }
        else if (nearest_target_item instanceof ConductionArea)
        {
            connection_point = p_from_point;
        }
        if (connection_point != null && connection_point instanceof IntPoint)
        {
            route_completed = connect(p_from_point, (IntPoint) connection_point);
        }
        return route_completed;
    }

    /**
     * Tries to make a trace connection from p_from_point to p_to_point according to the angle restriction.
     * Returns true, if the connection succeeded.
     */
    private boolean connect(Point p_from_point, IntPoint p_to_point)
    {
        Point[] corners = angled_connection(p_from_point, p_to_point);
        boolean connection_succeeded = true;
        for (int i = 1; i < corners.length; ++i)
        {
            Point from_corner = corners[i - 1];
            Point to_corner = corners[i];
            TimeLimit time_limit = new TimeLimit(CHECK_FORCED_TRACE_TIME_LIMIT);
            while (!from_corner.equals(to_corner))
            {
                Point curr_ok_point = board.insert_forced_trace_segment(from_corner,
                        to_corner, pen_half_width_arr[layer], this.layer, net_no_arr,
                        clearance_class, max_shove_trace_recursion_depth,
                        max_shove_via_recursion_depth, max_spring_over_recursion_depth,
                        trace_tidy_width, pull_tight_accuracy, !is_stitch_mode, time_limit);
                if (curr_ok_point == null)
                {
                    // database may be damaged, restore previous situation
                    board.undo(null);
                    return true;
                }
                if (curr_ok_point.equals(from_corner) && this.with_neckdown)
                {
                    curr_ok_point = try_neckdown_at_end(from_corner, to_corner);
                }
                if (curr_ok_point.equals(from_corner))
                {
                    this.prev_corner = from_corner;
                    connection_succeeded = false;
                    break;
                }
                from_corner = curr_ok_point;
            }
        }
        return connection_succeeded;
    }

    /** Calculates the nearest layer of the nearest target item
     * to this.layer.
     */
    public int nearest_target_layer()
    {
        if (nearest_target_item == null)
        {
            return this.layer;
        }
        int result;
        int first_layer = nearest_target_item.first_layer();
        int last_layer = nearest_target_item.last_layer();
        if (this.layer < first_layer)
        {
            result = first_layer;
        }
        else if (this.layer > last_layer)
        {
            result = last_layer;
        }
        else
        {
            result = this.layer;
        }
        return result;
    }

    /**
     * Returns all pins, which can be reached by a pin swap from a srtart or target pin.
     */
    private Set<SwapPinInfo> calculate_swap_pin_infos()
    {
        Set<SwapPinInfo> result = new java.util.TreeSet<SwapPinInfo>();
        if (this.target_set == null)
        {
            return result;
        }
        for (Item curr_item : this.target_set)
        {
            if (curr_item instanceof eu.mihosoft.freerouting.board.Pin)
            {
                Collection<eu.mihosoft.freerouting.board.Pin> curr_swapppable_pins = ((eu.mihosoft.freerouting.board.Pin) curr_item).get_swappable_pins();
                for (eu.mihosoft.freerouting.board.Pin curr_swappable_pin : curr_swapppable_pins)
                {
                    result.add(new SwapPinInfo(curr_swappable_pin));
                }
            }
        }
        // add the from item, if it is a pin
        ItemSelectionFilter selection_filter = new ItemSelectionFilter(ItemSelectionFilter.SelectableChoices.PINS);
        java.util.Collection<Item> picked_items = board.pick_items(this.prev_corner, this.layer, selection_filter);
        for (Item curr_item : picked_items)
        {
            if (curr_item instanceof eu.mihosoft.freerouting.board.Pin)
            {
                Collection<eu.mihosoft.freerouting.board.Pin> curr_swapppable_pins = ((eu.mihosoft.freerouting.board.Pin) curr_item).get_swappable_pins();
                for (eu.mihosoft.freerouting.board.Pin curr_swappable_pin : curr_swapppable_pins)
                {
                    result.add(new SwapPinInfo(curr_swappable_pin));
                }
            }
        }
        return result;
    }

    /**
     * Hilights the targets and draws the incomplete.
     */
    public void draw(Graphics p_graphics, GraphicsContext p_graphics_context)
    {
        if (this.hilight_shove_failing_obstacle && this.shove_failing_obstacle != null)
        {
            this.shove_failing_obstacle.draw(p_graphics, p_graphics_context, p_graphics_context.get_violations_color(), 1);
        }
        if (target_set == null || net_no_arr.length < 1)
        {
            return;
        }
        Net curr_net = board.rules.nets.get(net_no_arr[0]);
        if (curr_net == null)
        {
            return;
        }
        java.awt.Color highlight_color = p_graphics_context.get_hilight_color();
        double highligt_color_intensity = p_graphics_context.get_hilight_color_intensity();


        // hilight the swapppable pins and their incompletes
        for (SwapPinInfo curr_info : this.swap_pin_infos)
        {
            curr_info.pin.draw(p_graphics, p_graphics_context, highlight_color, 0.3 * highligt_color_intensity);
            if (curr_info.incomplete != null)
            {
                // draw the swap pin incomplete
                FloatPoint[] draw_points = new FloatPoint[2];
                draw_points[0] = curr_info.incomplete.a;
                draw_points[1] = curr_info.incomplete.b;
                java.awt.Color draw_color = p_graphics_context.get_incomplete_color();
                p_graphics_context.draw(draw_points, 1, draw_color, p_graphics, highligt_color_intensity);
            }
        }

        // hilight the target set
        for (Item curr_item : target_set)
        {
            if (!(curr_item instanceof ConductionArea))
            {
                curr_item.draw(p_graphics, p_graphics_context, highlight_color, highligt_color_intensity);
            }
        }
        FloatPoint from_corner = this.prev_corner.to_float();
        if (nearest_target_point != null && prev_corner != null)
        {
            boolean curr_length_matching_ok = true; // used for drawing the incomplete as violation
            double max_trace_length = curr_net.get_class().get_maximum_trace_length();
            double min_trace_length = curr_net.get_class().get_minimum_trace_length();
            double length_matching_color_intensity = p_graphics_context.get_length_matching_area_color_intensity();
            if (max_trace_length > 0 || min_trace_length > 0 && length_matching_color_intensity > 0)
            {

                // draw the length matching area
                double trace_length_add = from_corner.distance(this.prev_corner.to_float());
                // trace_length_add is != 0 only in stitching mode.
                if (max_trace_length <= 0)
                {
                    // max_trace_length not provided. Create an ellipse containing the whole board.
                    max_trace_length = 0.3 * eu.mihosoft.freerouting.geometry.planar.Limits.CRIT_INT;
                }
                double curr_max_trace_length = max_trace_length - (curr_net.get_trace_length() + trace_length_add);
                double curr_min_trace_length = min_trace_length - (curr_net.get_trace_length() + trace_length_add);
                double incomplete_length = nearest_target_point.distance(from_corner);
                if (incomplete_length < curr_max_trace_length && min_trace_length <= max_trace_length)
                {
                    Vector delta = nearest_target_point.round().difference_by(prev_corner);
                    double rotation = delta.angle_approx();
                    FloatPoint center = from_corner.middle_point(nearest_target_point);
                    double bigger_radius = 0.5 * curr_max_trace_length;
                    // dist_focus_to_center^2 = bigger_radius^2 - smaller_radius^2
                    double smaller_radius = 0.5 * Math.sqrt(curr_max_trace_length * curr_max_trace_length - incomplete_length * incomplete_length);
                    int ellipse_count;
                    if (min_trace_length <= 0 || incomplete_length >= curr_min_trace_length)
                    {
                        ellipse_count = 1;
                    }
                    else
                    {
                        // display an ellipse ring.
                        ellipse_count = 2;
                    }
                    Ellipse[] ellipse_arr = new Ellipse[ellipse_count];
                    ellipse_arr[0] = new Ellipse(center, rotation, bigger_radius, smaller_radius);
                    IntBox bounding_box = new IntBox(prev_corner.to_float().round(), nearest_target_point.round());
                    bounding_box = bounding_box.offset(curr_max_trace_length - incomplete_length);
                    board.join_graphics_update_box(bounding_box);
                    if (ellipse_count == 2)
                    {
                        bigger_radius = 0.5 * curr_min_trace_length;
                        smaller_radius = 0.5 * Math.sqrt(curr_min_trace_length * curr_min_trace_length - incomplete_length * incomplete_length);
                        ellipse_arr[1] = new Ellipse(center, rotation, bigger_radius, smaller_radius);
                    }
                    p_graphics_context.fill_ellipse_arr(ellipse_arr, p_graphics,
                            p_graphics_context.get_length_matching_area_color(), length_matching_color_intensity);
                }
                else
                {
                    curr_length_matching_ok = false;
                }
            }

            // draw the incomplete
            FloatPoint[] draw_points = new FloatPoint[2];
            draw_points[0] = from_corner;
            draw_points[1] = nearest_target_point;
            java.awt.Color draw_color = p_graphics_context.get_incomplete_color();
            double draw_width = Math.min (this.board.communication.get_resolution(Unit.MIL), 100);  // problem with low resolution on Kicad
            if (!curr_length_matching_ok)
            {
                draw_color = p_graphics_context.get_violations_color();
                draw_width *= 3;
            }
            p_graphics_context.draw(draw_points, draw_width, draw_color, p_graphics, highligt_color_intensity);
            if (this.nearest_target_item != null && !this.nearest_target_item.is_on_layer(this.layer))
            {
                // draw a marker to indicate the layer change.
                NetIncompletes.draw_layer_change_marker(draw_points[0], 4 * pen_half_width_arr[0], p_graphics, p_graphics_context);
            }
        }

    }

    /**
     * Makes a connection polygon from p_from_point to p_to_point
     * whose lines fulfill the angle restriction.
     */
    private Point[] angled_connection(Point p_from_point, Point p_to_point)
    {
        IntPoint add_corner = null;
        if (p_from_point instanceof IntPoint && p_to_point instanceof IntPoint)
        {
            AngleRestriction angle_restriction = this.board.rules.get_trace_angle_restriction();
            if (angle_restriction == AngleRestriction.NINETY_DEGREE)
            {
                add_corner = ((IntPoint) p_from_point).ninety_degree_corner((IntPoint) p_to_point, true);
            }
            else if (angle_restriction == AngleRestriction.FORTYFIVE_DEGREE)
            {
                add_corner = ((IntPoint) p_from_point).fortyfive_degree_corner((IntPoint) p_to_point, true);
            }
        }
        int new_corner_count = 2;
        if (add_corner != null)
        {
            ++new_corner_count;
        }
        Point[] result = new Point[new_corner_count];
        result[0] = p_from_point;
        if (add_corner != null)
        {
            result[1] = add_corner;
        }
        result[result.length - 1] = p_to_point;
        return result;
    }

    /**
     * Calculates a list of the center points of DrillItems,
     * end points of traces and areas of ConductionAreas in the target set.
     */
    private void calculate_target_points_and_areas()
    {
        target_points = new LinkedList<TargetPoint>();
        target_traces_and_areas = new LinkedList<Item>();
        if (target_set == null)
        {
            return;
        }
        Iterator<Item> it = target_set.iterator();
        while (it.hasNext())
        {
            Item curr_ob = it.next();
            if (curr_ob instanceof DrillItem)
            {
                Point curr_point = ((DrillItem) curr_ob).get_center();
                target_points.add(new TargetPoint(curr_point.to_float(), curr_ob));
            }
            else if (curr_ob instanceof Trace || curr_ob instanceof ConductionArea)
            {
                target_traces_and_areas.add(curr_ob);
            }
        }
    }

    public Point get_last_corner()
    {
        return prev_corner;
    }

    public boolean is_layer_active(int p_layer)
    {
        if (p_layer < 0 || p_layer >= layer_active.length)
        {
            return false;
        }
        return layer_active[p_layer];
    }

    /**
     * The nearest point is used for drowing the incomplete
     */
    void calc_nearest_target_point(FloatPoint p_from_point)
    {
        double min_dist = Double.MAX_VALUE;
        FloatPoint nearest_point = null;
        Item nearest_item = null;
        for (TargetPoint curr_target_point : target_points)
        {
            double curr_dist = p_from_point.distance(curr_target_point.location);
            if (curr_dist < min_dist)
            {
                min_dist = curr_dist;
                nearest_point = curr_target_point.location;
                nearest_item = curr_target_point.item;
            }
        }
        Iterator<Item> it = target_traces_and_areas.iterator();
        while (it.hasNext())
        {
            Item curr_item = it.next();
            if (curr_item instanceof PolylineTrace)
            {
                PolylineTrace curr_trace = (PolylineTrace) curr_item;
                Polyline curr_polyline = curr_trace.polyline();
                if (curr_polyline.bounding_box().distance(p_from_point) < min_dist)
                {
                    FloatPoint curr_nearest_point = curr_polyline.nearest_point_approx(p_from_point);
                    double curr_dist = p_from_point.distance(curr_nearest_point);
                    if (curr_dist < min_dist)
                    {
                        min_dist = curr_dist;
                        nearest_point = curr_nearest_point;
                        nearest_item = curr_trace;
                    }
                }
            }
            else if (curr_item instanceof ConductionArea && curr_item.tile_shape_count() > 0)
            {
                ConductionArea curr_conduction_area = (ConductionArea) curr_item;
                Area curr_area = curr_conduction_area.get_area();
                if (curr_area.bounding_box().distance(p_from_point) < min_dist)
                {
                    FloatPoint curr_nearest_point = curr_area.nearest_point_approx(p_from_point);
                    double curr_dist = p_from_point.distance(curr_nearest_point);
                    if (curr_dist < min_dist)
                    {
                        min_dist = curr_dist;
                        nearest_point = curr_nearest_point;
                        nearest_item = curr_conduction_area;
                    }
                }
            }
        }
        if (nearest_point == null)
        {
            return; // target set is empty
        }
        nearest_target_point = nearest_point;
        nearest_target_item = nearest_item;
        // join the graphics update box by the nearest item, so that the incomplete
        // is completely displayed.
        board.join_graphics_update_box(nearest_item.bounding_box());
    }

    private void set_shove_failing_obstacle(Item p_item)
    {
        this.shove_failing_obstacle = p_item;
        if (p_item != null)
        {
            this.board.join_graphics_update_box(p_item.bounding_box());
        }
    }

    /**
     * If the routed starts at a pin and the route failed with the normal trace width,
     * another try with the smalllest pin width is done.
     * Returns the ok_point of the try, which is this.prev_point, if the try failed.
     */
    private Point try_neckdown_at_start(IntPoint p_to_corner)
    {
        if (!(this.start_item instanceof eu.mihosoft.freerouting.board.Pin))
        {
            return this.prev_corner;
        }
        eu.mihosoft.freerouting.board.Pin start_pin = (eu.mihosoft.freerouting.board.Pin) this.start_item;
        if (!start_pin.is_on_layer(this.layer))
        {
            return this.prev_corner;
        }
        FloatPoint pin_center = start_pin.get_center().to_float();
        double curr_clearance =
                this.board.rules.clearance_matrix.value(this.clearance_class, start_pin.clearance_class_no(), this.layer);
        double pin_neck_down_distance =
                2 * (0.5 * start_pin.get_max_width(this.layer) + curr_clearance);
        if (pin_center.distance(this.prev_corner.to_float()) >= pin_neck_down_distance)
        {
            return this.prev_corner;
        }

        int neck_down_halfwidth = start_pin.get_trace_neckdown_halfwidth(this.layer);
        if (neck_down_halfwidth >= this.pen_half_width_arr[this.layer])
        {
            return this.prev_corner;
        }

        // check, that the neck_down started inside the pin shape
        if (!this.prev_corner.equals(start_pin.get_center()))
        {
            Item picked_item = this.board.pick_nearest_routing_item(this.prev_corner, this.layer, null);
            if (picked_item instanceof Trace)
            {
                if (((Trace) picked_item).get_half_width() > neck_down_halfwidth)
                {
                    return this.prev_corner;
                }
            }
        }
        TimeLimit time_limit = new TimeLimit(CHECK_FORCED_TRACE_TIME_LIMIT);
        Point ok_point = board.insert_forced_trace_segment(prev_corner,
                p_to_corner, neck_down_halfwidth, layer, net_no_arr, clearance_class, max_shove_trace_recursion_depth,
                max_shove_via_recursion_depth, max_spring_over_recursion_depth, trace_tidy_width,
                pull_tight_accuracy, !is_stitch_mode, time_limit);
        return ok_point;
    }

    /**
     * If the routed ends at a pin and the route failed with the normal trace width,
     * another try with the smalllest pin width is done.
     * Returns the ok_point of the try, which is p_from_corner, if the try failed.
     */
    private Point try_neckdown_at_end(Point p_from_corner, Point p_to_corner)
    {
        if (!(this.nearest_target_item instanceof eu.mihosoft.freerouting.board.Pin))
        {
            return p_from_corner;
        }
        eu.mihosoft.freerouting.board.Pin target_pin = (eu.mihosoft.freerouting.board.Pin) this.nearest_target_item;
        if (!target_pin.is_on_layer(this.layer))
        {
            return p_from_corner;
        }
        FloatPoint pin_center = target_pin.get_center().to_float();
        double curr_clearance =
                this.board.rules.clearance_matrix.value(this.clearance_class, target_pin.clearance_class_no(), this.layer);
        double pin_neck_down_distance =
                2 * (0.5 * target_pin.get_max_width(this.layer) + curr_clearance);
        if (pin_center.distance(p_from_corner.to_float()) >= pin_neck_down_distance)
        {
            return p_from_corner;
        }
        int neck_down_halfwidth = target_pin.get_trace_neckdown_halfwidth(this.layer);
        if (neck_down_halfwidth >= this.pen_half_width_arr[this.layer])
        {
            return p_from_corner;
        }
        TimeLimit time_limit = new TimeLimit(CHECK_FORCED_TRACE_TIME_LIMIT);
        Point ok_point = board.insert_forced_trace_segment(p_from_corner,
                p_to_corner, neck_down_halfwidth, layer, net_no_arr, clearance_class,
                max_shove_trace_recursion_depth, max_shove_via_recursion_depth,
                max_spring_over_recursion_depth, trace_tidy_width,
                pull_tight_accuracy, !is_stitch_mode, time_limit);
        return ok_point;
    }
    /** The net numbers used for routing */
    final int[] net_no_arr;
    private Point prev_corner;
    private int layer;
    private final Item start_item;
    private final Set<Item> target_set;
    /** Pins, which can be reached by a pin swap by a target pin. */
    private final Set<SwapPinInfo> swap_pin_infos;
    private Collection<TargetPoint> target_points; // from drill_items
    private Collection<Item> target_traces_and_areas; // from traces and conduction areas
    private FloatPoint nearest_target_point;
    private Item nearest_target_item;
    private final int[] pen_half_width_arr;
    private final boolean[] layer_active;
    private final int clearance_class;
    private final ViaRule via_rule;
    private final int max_shove_trace_recursion_depth;
    private final int max_shove_via_recursion_depth;
    private final int max_spring_over_recursion_depth;
    private final int trace_tidy_width;
    private final int pull_tight_accuracy;
    private final RoutingBoard board;
    private final boolean is_stitch_mode;
    private final boolean with_neckdown;
    private final boolean via_snap_to_smd_center;
    private final boolean hilight_shove_failing_obstacle;
    private final int pull_tight_time_limit;
    private Item shove_failing_obstacle = null;
    /** The time limit in milliseconds for the pull tight algorithm */
    private static final int CHECK_FORCED_TRACE_TIME_LIMIT = 3000;
    /** The time limit in milliseconds for the pull tight algorithm */
    private static final int PULL_TIGHT_TIME_LIMIT = 2000;

    private static class TargetPoint
    {

        TargetPoint(FloatPoint p_location, Item p_item)
        {
            location = p_location;
            item = p_item;
        }
        final FloatPoint location;
        final Item item;
    }

    private class SwapPinInfo implements Comparable<SwapPinInfo>
    {

        SwapPinInfo(eu.mihosoft.freerouting.board.Pin p_pin)
        {
            pin = p_pin;
            incomplete = null;
            if (p_pin.is_connected() || p_pin.net_count() != 1)
            {
                return;
            }
            // calculate the incomplete of p_pin
            FloatPoint pin_center = p_pin.get_center().to_float();
            double min_dist = Double.MAX_VALUE;
            FloatPoint nearest_point = null;
            Collection<Item> net_items = board.get_connectable_items(p_pin.get_net_no(0));
            for (Item curr_item : net_items)
            {
                if (curr_item == this.pin || !(curr_item instanceof DrillItem))
                {
                    continue;
                }
                FloatPoint curr_point = ((DrillItem) curr_item).get_center().to_float();
                double curr_dist = pin_center.distance_square(curr_point);
                if (curr_dist < min_dist)
                {
                    min_dist = curr_dist;
                    nearest_point = curr_point;
                }
            }
            if (nearest_point != null)
            {
                incomplete = new eu.mihosoft.freerouting.geometry.planar.FloatLine(pin_center, nearest_point);
            }
        }

        public int compareTo(SwapPinInfo p_other)
        {
            return this.pin.compareTo(p_other.pin);
        }
        final eu.mihosoft.freerouting.board.Pin pin;
        eu.mihosoft.freerouting.geometry.planar.FloatLine incomplete;
    }
}