/*
 *   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.
 *
 * OptViaAlgo.java
 *
 * Created on 31. Maerz 2006, 06:58
 *
 */

package eu.mihosoft.freerouting.board;

import java.util.Collection;
import java.util.Iterator;

import eu.mihosoft.freerouting.geometry.planar.Point;
import eu.mihosoft.freerouting.geometry.planar.IntPoint;
import eu.mihosoft.freerouting.geometry.planar.Vector;
import eu.mihosoft.freerouting.geometry.planar.Polyline;
import eu.mihosoft.freerouting.geometry.planar.FloatPoint;
import eu.mihosoft.freerouting.geometry.planar.FloatLine;
import eu.mihosoft.freerouting.geometry.planar.Side;

import eu.mihosoft.freerouting.autoroute.AutorouteControl.ExpansionCostFactor;
import eu.mihosoft.freerouting.logger.FRLogger;

/**
 * Contains functions for optimizing and improving via locations.
 *
 * @author Alfons Wirtz
 */
public class OptViaAlgo
{
    
    /**
     * Optimizes the location of a via connected to at most 2 traces
     * according to the trace costs on the layers of the connected traces
     * If p_trace_cost_arr == null, the horizontal and vertical trace costs will be set to 1.
     * Returns false, if the via was not changed.
     */
    public static boolean opt_via_location(RoutingBoard p_board, Via p_via,
            ExpansionCostFactor[] p_trace_cost_arr, int p_trace_pull_tight_accuracy, int p_max_recursion_depth)
    {
        if (p_via.is_shove_fixed())
        {
            return false;
        }
        if (p_max_recursion_depth <= 0)
        {
            if (p_board.get_test_level().ordinal() >= TestLevel.CRITICAL_DEBUGGING_OUTPUT.ordinal())
            {
                FRLogger.warn("OptViaAlgo.opt_via_location: probably endless loop") ;
            }
            return false;
        }
        Collection<Item> contacts = p_via.get_normal_contacts();
        boolean is_plane_or_fanout_via = (contacts.size() == 1);
        PolylineTrace first_trace = null;
        PolylineTrace second_trace = null;
        if (!is_plane_or_fanout_via)
        {
            if (contacts.size() != 2)
            {
                return false;
            }
            Iterator<Item> it = contacts.iterator();
            Item curr_item = it.next();
            if (curr_item.is_shove_fixed() || !(curr_item instanceof PolylineTrace))
            {
                if (curr_item instanceof ConductionArea)
                {
                    is_plane_or_fanout_via = true;
                }
                else
                {
                    return false;
                }
            }
            else
            {
                first_trace = (PolylineTrace) curr_item;
            }
            curr_item = it.next();
            if (curr_item.is_shove_fixed() || !(curr_item instanceof PolylineTrace))
            {
                if (curr_item instanceof ConductionArea)
                {
                    is_plane_or_fanout_via = true;
                }
                else
                {
                    return false;
                }
            }
            else
            {
                second_trace = (PolylineTrace) curr_item;
            }
        }
        if (is_plane_or_fanout_via)
        {
            return opt_plane_or_fanout_via(p_board, p_via, p_trace_pull_tight_accuracy, p_max_recursion_depth);
        }
        Point via_center = p_via.get_center();
        int first_layer = first_trace.get_layer();
        int second_layer = second_trace.get_layer();
        Point first_trace_from_corner;
        Point second_trace_from_corner;
        
        // calculate first_trace_from_corner and second_trace_from_corner
        
        if (first_trace.first_corner().equals(via_center))
        {
            first_trace_from_corner = first_trace.polyline().corner(1);
        }
        else if (first_trace.last_corner().equals(via_center))
        {
            first_trace_from_corner
                    = first_trace.polyline().corner(first_trace.polyline().corner_count() - 2);
        }
        else
        {
            FRLogger.warn("OptViaAlgo.opt_via_location: incorrect first contact");
            return false;
        }
        
        if (second_trace.first_corner().equals(via_center))
        {
            second_trace_from_corner = second_trace.polyline().corner(1);
        }
        else if (second_trace.last_corner().equals(via_center))
        {
            second_trace_from_corner
                    = second_trace.polyline().corner(second_trace.polyline().corner_count() - 2);
        }
        else
        {
            FRLogger.warn("OptViaAlgo.opt_via_location: incorrect second contact");
            return false;
        }
        
        ExpansionCostFactor first_layer_trace_costs;
        ExpansionCostFactor second_layer_trace_costs;
        if (p_trace_cost_arr != null)
        {
            first_layer_trace_costs = p_trace_cost_arr[first_layer];
            second_layer_trace_costs = p_trace_cost_arr[second_layer];
        }
        else
        {
            first_layer_trace_costs = new ExpansionCostFactor(1, 1);
            second_layer_trace_costs = first_layer_trace_costs;
        }
        
        Point new_location =
                reposition_via(p_board, p_via,
                first_trace.get_half_width(), first_trace.clearance_class_no(), first_trace.get_layer(),
                first_layer_trace_costs, first_trace_from_corner,
                second_trace.get_half_width(), second_trace.clearance_class_no(), second_trace.get_layer(),
                second_layer_trace_costs, second_trace_from_corner);
        if (new_location == null || new_location.equals(via_center))
        {
            return false;
        }
        Vector delta = new_location.difference_by(via_center);
        if (!MoveDrillItemAlgo.insert(p_via, delta, 9, 9, null, p_board))
        {
            FRLogger.warn("OptViaAlgo.opt_via_location: move via failed");
            return false;
        }
        ItemSelectionFilter filter = new ItemSelectionFilter(ItemSelectionFilter.SelectableChoices.TRACES);
        Collection<Item> picked_items = p_board.pick_items(new_location, first_trace.get_layer(), filter);
        for (Item curr_item : picked_items)
        {
            ((PolylineTrace) curr_item).pull_tight(true, p_trace_pull_tight_accuracy, null);
        }
        picked_items = p_board.pick_items(new_location, second_trace.get_layer(), filter);
        for (Item curr_item : picked_items)
        {
            ((PolylineTrace) curr_item).pull_tight(true, p_trace_pull_tight_accuracy, null);
        }
        filter = new ItemSelectionFilter(ItemSelectionFilter.SelectableChoices.VIAS);
        picked_items = p_board.pick_items(new_location, first_trace.get_layer(), filter);
        for (Item curr_item : picked_items)
        {
            opt_via_location(p_board, (Via) curr_item, p_trace_cost_arr,
                    p_trace_pull_tight_accuracy, p_max_recursion_depth - 1);
            break;
        }
        return true;
    }
    
    /**
     * Optimisations for vias with only 1 connected Trace (Plane or Fanout Vias).
     */
    private static boolean opt_plane_or_fanout_via(RoutingBoard p_board, Via p_via,
            int p_trace_pull_tight_accuracy, int p_max_recursion_depth)
    {
        if (p_max_recursion_depth <= 0)
        {
            if (p_board.get_test_level().ordinal() >= TestLevel.CRITICAL_DEBUGGING_OUTPUT.ordinal())
            {
                FRLogger.warn("OptViaAlgo.opt_plane_or_fanout_via: probably endless loop") ;
            }
            return false;
        }
        Collection<Item> contact_list = p_via.get_normal_contacts();
        if (contact_list.isEmpty())
        {
            return false;
        }
        ConductionArea contact_plane = null;
        PolylineTrace contact_trace = null;
        for (Item curr_contact : contact_list)
        {
            if (curr_contact instanceof ConductionArea)
            {
                if (contact_plane != null)
                {
                    return false;
                }
                contact_plane = (ConductionArea) curr_contact;
            }
            else if (curr_contact instanceof PolylineTrace)
            {
                if (curr_contact.is_shove_fixed() || contact_trace != null)
                {
                    return false;
                }
                contact_trace = (PolylineTrace) curr_contact;
            }
            else
            {
                return false;
            }
        }
        if (contact_trace == null)
        {
            return false;
        }
        Point via_center = p_via.get_center();
        boolean at_first_corner;
        if (contact_trace.first_corner().equals(via_center))
        {
            at_first_corner = true;
        }
        else if (contact_trace.last_corner().equals(via_center))
        {
            at_first_corner = false;
        }
        else
        {
            FRLogger.warn("OptViaAlgo.opt_plane_or_fanout_via: unconsistant contact");
            return false;
        }
        Polyline trace_polyline = contact_trace.polyline();
        Point check_corner;
        if (at_first_corner)
        {
            check_corner = trace_polyline.corner(1);
        }
        else
        {
            check_corner = trace_polyline.corner(trace_polyline.corner_count() - 2);
        }
        IntPoint rounded_check_corner = check_corner.to_float().round();
        int trace_half_width = contact_trace.get_half_width();
        int trace_layer = contact_trace.get_layer();
        int trace_cl_class_no = contact_trace.clearance_class_no();
        Point new_via_location =
                reposition_via(p_board, p_via, rounded_check_corner, trace_half_width ,
                trace_layer, trace_cl_class_no);
        if (new_via_location == null && trace_polyline.corner_count() >= 3)
        {
            
            // try to project the via to the previous line
            Point   prev_corner;
            
            if (at_first_corner)
            {
                prev_corner = trace_polyline.corner(2);
            }
            else
            {
                prev_corner = trace_polyline.corner(trace_polyline.corner_count() - 3);
            }
            FloatPoint float_check_corner = check_corner.to_float();
            FloatPoint float_via_center = via_center.to_float();
            FloatPoint float_prev_corner = prev_corner.to_float();
            if (float_check_corner.scalar_product(float_via_center, float_prev_corner) != 0)
            {
                FloatLine curr_line = new FloatLine(float_check_corner, float_prev_corner);
                Point projection = curr_line.perpendicular_projection(float_via_center).round();
                Vector diff_vector = projection.difference_by(via_center);
                boolean projection_ok = true;
                AngleRestriction angle_restriction = p_board.rules.get_trace_angle_restriction();
                if (projection.equals(via_center) ||
                        angle_restriction == AngleRestriction.NINETY_DEGREE && !diff_vector.is_orthogonal() ||
                        angle_restriction == AngleRestriction.FORTYFIVE_DEGREE && !diff_vector.is_multiple_of_45_degree())
                {
                    projection_ok = false;
                }
                if (projection_ok)
                {
                    if(MoveDrillItemAlgo.check(p_via, diff_vector, 0, 0, null, p_board, null))
                    {
                        double ok_length =
                                p_board.check_trace_segment(via_center, projection, trace_layer, p_via.net_no_arr,
                                trace_half_width, trace_cl_class_no, false);
                        if (ok_length >= Integer.MAX_VALUE)
                        {
                            new_via_location = projection;
                        }
                    }
                    
                }
            }
        }
        if (new_via_location == null)
        {
            return false;
        }
        if (contact_plane != null)
        {
            // check, that the new location is inside the contact plane
            ItemSelectionFilter filter = new ItemSelectionFilter(ItemSelectionFilter.SelectableChoices.CONDUCTION);
            Collection<Item> picked_items = p_board.pick_items(new_via_location, contact_plane.get_layer(), filter);
            boolean contact_ok = false;
            for (Item curr_item : picked_items)
            {
                if (curr_item == contact_plane)
                {
                    contact_ok = true;
                    break;
                }
            }
            if (!contact_ok)
            {
                return false;
            }
        }
        Vector diff_vector = new_via_location.difference_by(via_center);
        if (!MoveDrillItemAlgo.insert(p_via, diff_vector, 9, 9, null, p_board))
        {
            FRLogger.warn("OptViaAlgo.opt_plane_or_fanout_via: move via failed");
            return false;
        }
        ItemSelectionFilter filter = new ItemSelectionFilter(ItemSelectionFilter.SelectableChoices.TRACES);
        Collection<Item> picked_items = p_board.pick_items(new_via_location, contact_trace.get_layer(), filter);
        for (Item curr_item : picked_items)
        {
            ((PolylineTrace) curr_item).pull_tight(true, p_trace_pull_tight_accuracy, null);
        }
        if (new_via_location.equals(check_corner))
        {
            opt_plane_or_fanout_via(p_board, p_via, p_trace_pull_tight_accuracy, p_max_recursion_depth - 1);
        }
        return true;
    }
    
    /**
     * Tries to move the via into the direction of p_to_location as far as possible
     * Return the new location of the via, or null, if no move was possible.
     */
    private static Point reposition_via(RoutingBoard p_board, Via p_via,
            IntPoint p_to_location, int p_trace_half_width, int p_trace_layer, int p_trace_cl_class)
            
    {
        Point   from_location = p_via.get_center();
        
        if (from_location.equals(p_to_location))
        {
            return null;
        }
        
        double ok_length = p_board.check_trace_segment(from_location, p_to_location, p_trace_layer, p_via.net_no_arr,
                p_trace_half_width, p_trace_cl_class, false);
        if (ok_length <= 0)
        {
            return null;
        }
        FloatPoint float_from_location = from_location.to_float();
        FloatPoint float_to_location = p_to_location.to_float();
        FloatPoint new_float_to_location;
        if (ok_length >= Integer.MAX_VALUE)
        {
            new_float_to_location = float_to_location;
        }
        else
        {
            new_float_to_location = float_from_location.change_length(float_to_location, ok_length);
        }
        Point new_to_location = new_float_to_location.round();
        Vector delta = new_to_location.difference_by(from_location);
        boolean check_ok =
                MoveDrillItemAlgo.check(p_via, delta, 0, 0, null, p_board, null);
        
        if (check_ok)
        {
            return new_to_location;
        }
        
        final double c_min_length = 0.3 * p_trace_half_width + 1;
        
        ok_length = Math.min(ok_length, float_from_location.distance(float_to_location));
        
        double curr_length = ok_length / 2;
        
        ok_length = 0;
        Point result = null;
        
        while (curr_length >= c_min_length)
        {
            Point check_point = float_from_location.change_length(float_to_location, ok_length + curr_length).round();
            
            delta = check_point.difference_by(from_location);
            if (MoveDrillItemAlgo.check(p_via, delta, 0, 0, null, p_board, null))
            {
                ok_length += curr_length;
                result = check_point;
            }
            curr_length /= 2;
        }
        return result;
    }
    
    private static boolean reposition_via(RoutingBoard p_board, Via p_via,
            IntPoint p_to_location, int p_trace_half_width_1, int p_trace_layer_1, int p_trace_cl_class_1,
            IntPoint p_connect_location, int p_trace_half_width_2, int p_trace_layer_2, int p_trace_cl_class_2)
            
    {
        Point   from_location = p_via.get_center();
        
        if (from_location.equals(p_to_location))
        {
            if (p_board.get_test_level() == TestLevel.ALL_DEBUGGING_OUTPUT)
            {
                FRLogger.warn("OptViaAlgo.reposition_via: from_location equal p_to_location");
            }
            return false;
        }
        
        Vector delta = p_to_location.difference_by(from_location);
        
        if ( p_board.rules.get_trace_angle_restriction() == AngleRestriction.NONE && delta.length_approx() <= 1.5)
        {
            // PullTightAlgoAnyAngle.reduce_corners mmay not be able to remove the new generated overlap
            // because of numerical stability problems
            // That would result in an endless loop with removing the generated acute angle in reposition_via.
            return false;
        }
        
        int[] net_no_arr = p_via.net_no_arr;
        
        double ok_length = p_board.check_trace_segment(from_location, p_to_location, p_trace_layer_1, net_no_arr,
                p_trace_half_width_1, p_trace_cl_class_1, false);
        
        if (ok_length < Integer.MAX_VALUE)
        {
            return false;
        }
        
        ok_length = p_board.check_trace_segment(p_to_location, p_connect_location, p_trace_layer_2, net_no_arr,
                p_trace_half_width_2, p_trace_cl_class_2, false);
        
        if (ok_length < Integer.MAX_VALUE)
        {
            return false;
        }
        if (!MoveDrillItemAlgo.check(p_via, delta, 0, 0, null, p_board, null))
        {
            return false;
        }
        return true;
    }
    
    /**
     * Tries to reposition the via to a better location according to the
     * trace costs. Returns null, if no better location was found.
     */
    private static Point reposition_via(RoutingBoard p_board, Via p_via,
            int p_first_trace_half_width, int p_first_trace_cl_class, int p_first_trace_layer,
            ExpansionCostFactor p_first_trace_costs, Point p_first_trace_from_corner,
            int p_second_trace_half_width, int p_second_trace_cl_class, int p_second_trace_layer,
            ExpansionCostFactor p_second_trace_costs, Point p_second_trace_from_corner)
    {
        Point   via_location = p_via.get_center();
        
        Vector first_delta = p_first_trace_from_corner.difference_by(via_location);
        Vector second_delta = p_second_trace_from_corner.difference_by(via_location);
        double scalar_product = first_delta.scalar_product(second_delta);
        
        FloatPoint float_via_location = via_location.to_float();
        FloatPoint float_first_trace_from_corner = p_first_trace_from_corner.to_float();
        FloatPoint float_second_trace_from_corner = p_second_trace_from_corner.to_float();
        double first_trace_from_corner_distance = float_via_location.distance(float_first_trace_from_corner);
        double second_trace_from_corner_distance = float_via_location.distance(float_second_trace_from_corner);
        IntPoint rounded_first_trace_from_corner = float_first_trace_from_corner.round();
        IntPoint rounded_second_trace_from_corner = float_second_trace_from_corner.round();
        
        // handle case of overlapping lines first
        
        if (via_location.side_of(p_first_trace_from_corner, p_second_trace_from_corner) == Side.COLLINEAR
                && scalar_product > 0)
        {
            if (second_trace_from_corner_distance < first_trace_from_corner_distance)
            {
                return reposition_via(p_board, p_via, rounded_second_trace_from_corner,
                        p_first_trace_half_width, p_first_trace_layer, p_first_trace_cl_class);
            }
            return reposition_via(p_board, p_via, rounded_first_trace_from_corner,
                    p_second_trace_half_width, p_second_trace_layer, p_second_trace_cl_class);
        }
        Point result = null;
        
        double curr_weighted_distance_1 =
                float_via_location.weighted_distance(float_first_trace_from_corner, p_first_trace_costs.horizontal,
                p_first_trace_costs.vertical);
        double curr_weighted_distance_2 =
                float_via_location.weighted_distance(float_first_trace_from_corner, p_second_trace_costs.horizontal,
                p_second_trace_costs.vertical);
        
        if (curr_weighted_distance_1 > curr_weighted_distance_2)
        {
            // try to move the via in direction of p_first_trace_from_corner
            result = reposition_via(p_board, p_via, rounded_first_trace_from_corner,
                    p_second_trace_half_width, p_second_trace_layer, p_second_trace_cl_class);
            if (result != null)
            {
                return result;
            }
        }
        
        curr_weighted_distance_1 =
                float_via_location.weighted_distance(float_second_trace_from_corner, p_second_trace_costs.horizontal,
                p_second_trace_costs.vertical);
        curr_weighted_distance_2 =
                float_via_location.weighted_distance(float_second_trace_from_corner, p_first_trace_costs.horizontal,
                p_first_trace_costs.vertical);
        
        if (curr_weighted_distance_1 > curr_weighted_distance_2)
        {
            // try to move the via in direction of p_second_trace_from_corner
            result = reposition_via(p_board, p_via, rounded_second_trace_from_corner,
                    p_first_trace_half_width, p_first_trace_layer, p_first_trace_cl_class);
            if (result != null)
            {
                return result;
            }
        }
        if (scalar_product > 0 && p_board.rules.get_trace_angle_restriction() != AngleRestriction.NINETY_DEGREE)
        {
            // acute angle
            IntPoint to_point_1;
            IntPoint to_point_2;
            FloatPoint float_to_point_1;
            FloatPoint float_to_point_2;
            if (first_trace_from_corner_distance < second_trace_from_corner_distance)
            {
                to_point_1 = rounded_first_trace_from_corner;
                float_to_point_1 = float_first_trace_from_corner;
                float_to_point_2 =
                        float_via_location.change_length(float_second_trace_from_corner,
                        first_trace_from_corner_distance);
                to_point_2 =  float_to_point_2.round();
            }
            else
            {
                float_to_point_1 =
                        float_via_location.change_length(float_first_trace_from_corner,
                        second_trace_from_corner_distance);
                to_point_1 = float_to_point_1.round();
                to_point_2 = rounded_second_trace_from_corner;
                float_to_point_2 = float_second_trace_from_corner;
            }
            curr_weighted_distance_1 =
                    float_to_point_1.weighted_distance(float_to_point_2, p_first_trace_costs.horizontal,
                    p_first_trace_costs.vertical);
            curr_weighted_distance_2 =
                    float_to_point_1.weighted_distance(float_to_point_2, p_second_trace_costs.horizontal,
                    p_second_trace_costs.vertical);
            
            if (curr_weighted_distance_1 > curr_weighted_distance_2)
            {
                // try moving the via first into the direction of to_point_1
                result =  reposition_via(p_board, p_via, to_point_1,
                        p_second_trace_half_width, p_second_trace_layer, p_second_trace_cl_class);
                if (result == null)
                {
                    result =  reposition_via(p_board, p_via, to_point_2,
                            p_first_trace_half_width, p_first_trace_layer, p_first_trace_cl_class);
                }
            }
            else
            {
                // try moving the via first into the direction of to_point_2
                result =  reposition_via(p_board, p_via, to_point_2,
                        p_first_trace_half_width, p_first_trace_layer, p_first_trace_cl_class);
                if (result == null)
                {
                    result =  reposition_via(p_board, p_via, to_point_1,
                            p_second_trace_half_width, p_second_trace_layer, p_second_trace_cl_class);
                }
            }
            if (result != null)
            {
                return result;
            }
        }
        
        // try decomposition in axisparallel parts
        
        if (!first_delta.is_orthogonal())
        {
            FloatPoint float_check_location = new FloatPoint(float_via_location.x, float_first_trace_from_corner.y);
            
            curr_weighted_distance_1 =
                    float_via_location.weighted_distance(float_first_trace_from_corner, p_first_trace_costs.horizontal,
                    p_first_trace_costs.vertical);
            curr_weighted_distance_2 =
                    float_via_location.weighted_distance(float_check_location, p_second_trace_costs.horizontal,
                    p_second_trace_costs.vertical);
            double curr_weighted_distance_3 =
                    float_check_location.weighted_distance(float_first_trace_from_corner, p_first_trace_costs.horizontal,
                    p_first_trace_costs.vertical);
            
            if (curr_weighted_distance_1 > curr_weighted_distance_2 + curr_weighted_distance_3)
            {
                IntPoint check_location = float_check_location.round();
                boolean check_ok =
                        reposition_via(p_board, p_via, check_location, p_second_trace_half_width,
                        p_second_trace_layer, p_second_trace_cl_class, rounded_first_trace_from_corner,
                        p_first_trace_half_width, p_first_trace_layer, p_first_trace_cl_class);
                if (check_ok)
                {
                    return check_location;
                }
            }
            
            float_check_location = new FloatPoint(float_first_trace_from_corner.x, float_via_location.y);
            
            curr_weighted_distance_2 =
                    float_via_location.weighted_distance(float_check_location, p_second_trace_costs.horizontal,
                    p_second_trace_costs.vertical);
            curr_weighted_distance_3 =
                    float_check_location.weighted_distance(float_first_trace_from_corner, p_first_trace_costs.horizontal,
                    p_first_trace_costs.vertical);
            
            if (curr_weighted_distance_1 > curr_weighted_distance_2 + curr_weighted_distance_3)
            {
                IntPoint check_location = float_check_location.round();
                boolean check_ok =
                        reposition_via(p_board, p_via, check_location, p_second_trace_half_width,
                        p_second_trace_layer, p_second_trace_cl_class, rounded_first_trace_from_corner,
                        p_first_trace_half_width, p_first_trace_layer, p_first_trace_cl_class);
                if (check_ok)
                {
                    return check_location;
                }
            }
        }
        
        if (!second_delta.is_orthogonal())
        {
            FloatPoint float_check_location = new FloatPoint(float_via_location.x, float_second_trace_from_corner.y);
            
            curr_weighted_distance_1 =
                    float_via_location.weighted_distance(float_second_trace_from_corner, p_second_trace_costs.horizontal,
                    p_second_trace_costs.vertical);
            curr_weighted_distance_2 =
                    float_via_location.weighted_distance(float_check_location, p_first_trace_costs.horizontal,
                    p_first_trace_costs.vertical);
            double curr_weighted_distance_3 =
                    float_check_location.weighted_distance(float_second_trace_from_corner, p_second_trace_costs.horizontal,
                    p_second_trace_costs.vertical);
            
            if (curr_weighted_distance_1 > curr_weighted_distance_2 + curr_weighted_distance_3)
            {
                IntPoint check_location = float_check_location.round();
                boolean check_ok =
                        reposition_via(p_board, p_via,  check_location, p_first_trace_half_width,
                        p_first_trace_layer, p_first_trace_cl_class, rounded_second_trace_from_corner,
                        p_second_trace_half_width, p_second_trace_layer, p_second_trace_cl_class);
                if (check_ok)
                {
                    return check_location;
                }
            }
            
            float_check_location = new FloatPoint(float_second_trace_from_corner.x, float_via_location.y);
            
            curr_weighted_distance_2 =
                    float_via_location.weighted_distance(float_check_location, p_first_trace_costs.horizontal,
                    p_first_trace_costs.vertical);
            curr_weighted_distance_3 =
                    float_check_location.weighted_distance(float_second_trace_from_corner, p_second_trace_costs.horizontal,
                    p_second_trace_costs.vertical);
            
            if (curr_weighted_distance_1 > curr_weighted_distance_2 + curr_weighted_distance_3)
            {
                IntPoint check_location = float_check_location.round();
                boolean check_ok =
                        reposition_via(p_board, p_via, check_location, p_first_trace_half_width,
                        p_first_trace_layer, p_first_trace_cl_class, rounded_second_trace_from_corner,
                        p_second_trace_half_width, p_second_trace_layer, p_second_trace_cl_class);
                if (check_ok)
                {
                    return check_location;
                }
            }
        }
        return null;
    }
}
