/*
 *   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.
 *
 * PullTight90.java
 *
 * Created on 19. Juli 2003, 18:59
 */
package eu.mihosoft.freerouting.board;

import eu.mihosoft.freerouting.datastructures.Stoppable;

import eu.mihosoft.freerouting.geometry.planar.Direction;
import eu.mihosoft.freerouting.geometry.planar.FloatPoint;
import eu.mihosoft.freerouting.geometry.planar.IntPoint;
import eu.mihosoft.freerouting.geometry.planar.Limits;
import eu.mihosoft.freerouting.geometry.planar.Line;
import eu.mihosoft.freerouting.geometry.planar.Point;
import eu.mihosoft.freerouting.geometry.planar.Polyline;
import eu.mihosoft.freerouting.geometry.planar.Side;
import eu.mihosoft.freerouting.geometry.planar.TileShape;
import eu.mihosoft.freerouting.geometry.planar.Vector;
import eu.mihosoft.freerouting.datastructures.Signum;

/**
 *
 * @author Alfons Wirtz
 */
class PullTightAlgo45 extends PullTightAlgo
{

    /** Creates a new instance of PullTight90 */
    public PullTightAlgo45(RoutingBoard p_board, int[] p_only_net_no_arr, Stoppable p_stoppable_thread, int p_time_limit,
            Point p_keep_point, int p_keep_point_layer)
    {
        super(p_board, p_only_net_no_arr, p_stoppable_thread, p_time_limit, p_keep_point, p_keep_point_layer);
    }

    Polyline pull_tight(Polyline p_polyline)
    {
        Polyline new_result = avoid_acid_traps(p_polyline);
        Polyline prev_result = null;
        while (new_result != prev_result)
        {
            if (this.is_stop_requested())
            {
                break;
            }
            prev_result = new_result;
            Polyline tmp1 = reduce_corners(prev_result);
            Polyline tmp2 = smoothen_corners(tmp1);
            new_result = reposition_lines(tmp2);
        }
        return new_result;
    }

    AngleRestriction get_angle_restriction()
    {
        return AngleRestriction.FORTYFIVE_DEGREE;
    }

    /**
     * Tries to reduce the amount of corners of p_polyline.
     * Return p_polyline, if nothing was changed.
     */
    private Polyline reduce_corners(Polyline p_polyline)
    {
        if (p_polyline.arr.length <= 4)
        {
            return p_polyline;
        }
        int new_corner_count = 1;
        Point[] curr_corner = new Point[4];
        for (int i = 0; i < 4; ++i)
        {
            curr_corner[i] = p_polyline.corner(i);
            if (!(curr_corner[i] instanceof IntPoint))
            {
                return p_polyline;
            }
        }
        boolean[] curr_corner_in_clip_shape = new boolean[4];

        for (int i = 0; i < 4; ++i)
        {
            if (curr_clip_shape == null)
            {
                curr_corner_in_clip_shape[i] = true;
            }
            else
            {
                curr_corner_in_clip_shape[i] = !curr_clip_shape.is_outside(curr_corner[i]);
            }
        }

        boolean polyline_changed = false;
        Point[] new_corners = new Point[p_polyline.arr.length - 3];
        new_corners[0] = curr_corner[0];
        Point[] curr_check_points = new Point[2];
        Point new_corner = null;
        int corner_no = 3;
        while (corner_no < p_polyline.arr.length - 1)
        {
            boolean corner_removed = false;
            curr_corner[3] = p_polyline.corner(corner_no);
            if (!(curr_corner[3] instanceof IntPoint))
            {
                return p_polyline;
            }
            if (curr_corner[1].equals(curr_corner[2]) ||
                    corner_no < p_polyline.arr.length - 2 &&
                    curr_corner[3].side_of(curr_corner[1], curr_corner[2]) == Side.COLLINEAR)
            {
                // corners in the middle af a line can be skipped
                ++corner_no;
                curr_corner[2] = curr_corner[3];
                curr_corner_in_clip_shape[2] = curr_corner_in_clip_shape[3];
                if (corner_no < p_polyline.arr.length - 1)
                {
                    curr_corner[3] = p_polyline.corner(corner_no);
                    if (!(curr_corner[3] instanceof IntPoint))
                    {
                        return p_polyline;
                    }
                }
                polyline_changed = true;
            }
            curr_corner_in_clip_shape[3] = curr_clip_shape == null || !curr_clip_shape.is_outside(curr_corner[3]);
            if (curr_corner_in_clip_shape[1] && curr_corner_in_clip_shape[2] && curr_corner_in_clip_shape[3])
            {
                // translate the line from curr_corner[2] to curr_corner[1] to curr_corner[3]
                Vector delta = curr_corner[3].difference_by(curr_corner[2]);
                new_corner = curr_corner[1].translate_by(delta);
                if (curr_corner[3].equals(curr_corner[2]))
                {
                    // just remove multiple corner
                    corner_removed = true;
                }
                else if (new_corner.side_of(curr_corner[0], curr_corner[1]) == Side.COLLINEAR)
                {
                    curr_check_points[0] = new_corner;
                    curr_check_points[1] = curr_corner[1];
                    Polyline check_polyline = new Polyline(curr_check_points);
                    if (check_polyline.arr.length == 3)
                    {
                        TileShape shape_to_check = check_polyline.offset_shape(curr_half_width, 0);
                        if (board.check_trace_shape(shape_to_check, curr_layer, curr_net_no_arr,
                                curr_cl_type, this.contact_pins))
                        {
                            curr_check_points[1] = curr_corner[3];
                            if (curr_check_points[0].equals(curr_check_points[1]))
                            {
                                corner_removed = true;
                            }
                            else
                            {
                                check_polyline = new Polyline(curr_check_points);
                                if (check_polyline.arr.length == 3)
                                {
                                    shape_to_check = check_polyline.offset_shape(curr_half_width, 0);
                                    corner_removed = board.check_trace_shape(shape_to_check, curr_layer, curr_net_no_arr,
                                            curr_cl_type, this.contact_pins);
                                }
                                else
                                {
                                    corner_removed = true;
                                }
                            }
                        }
                    }
                    else
                    {
                        corner_removed = true;
                    }
                }
            }
            if (!corner_removed && curr_corner_in_clip_shape[0] && curr_corner_in_clip_shape[1] && curr_corner_in_clip_shape[2])
            {
                // the first try has failed. Try to translate the line from
                // corner_2 to corner_1 to corner_0
                Vector delta = curr_corner[0].difference_by(curr_corner[1]);
                new_corner = curr_corner[2].translate_by(delta);
                if (curr_corner[0].equals(curr_corner[1]))
                {
                    // just remove multiple corner
                    corner_removed = true;
                }
                else if (new_corner.side_of(curr_corner[2], curr_corner[3]) == Side.COLLINEAR)
                {
                    curr_check_points[0] = new_corner;
                    curr_check_points[1] = curr_corner[0];
                    Polyline check_polyline = new Polyline(curr_check_points);
                    if (check_polyline.arr.length == 3)
                    {
                        TileShape shape_to_check = check_polyline.offset_shape(curr_half_width, 0);
                        if (board.check_trace_shape(shape_to_check, curr_layer, curr_net_no_arr,
                                curr_cl_type, this.contact_pins))
                        {
                            curr_check_points[1] = curr_corner[2];
                            check_polyline = new Polyline(curr_check_points);
                            if (check_polyline.arr.length == 3)
                            {
                                shape_to_check = check_polyline.offset_shape(curr_half_width, 0);
                                corner_removed = board.check_trace_shape(shape_to_check, curr_layer, curr_net_no_arr,
                                        curr_cl_type, this.contact_pins);
                            }
                            else
                            {
                                corner_removed = true;
                            }
                        }
                    }
                    else
                    {
                        corner_removed = true;
                    }
                }
            }
            if (corner_removed)
            {
                polyline_changed = true;
                curr_corner[1] = new_corner;
                curr_corner_in_clip_shape[1] =
                        curr_clip_shape == null || !curr_clip_shape.is_outside(curr_corner[1]);
                if (board.changed_area != null)
                {
                    board.changed_area.join(new_corner.to_float(), curr_layer);
                    board.changed_area.join(curr_corner[1].to_float(), curr_layer);
                    board.changed_area.join(curr_corner[2].to_float(), curr_layer);
                }
            }
            else
            {
                new_corners[new_corner_count] = curr_corner[1];
                ++new_corner_count;
                curr_corner[0] = curr_corner[1];
                curr_corner[1] = curr_corner[2];
                curr_corner_in_clip_shape[0] = curr_corner_in_clip_shape[1];
                curr_corner_in_clip_shape[1] = curr_corner_in_clip_shape[2];
            }
            curr_corner[2] = curr_corner[3];
            curr_corner_in_clip_shape[2] = curr_corner_in_clip_shape[3];
            ++corner_no;
        }
        if (!polyline_changed)
        {
            return p_polyline;
        }
        Point adjusted_corners[] = new Point[new_corner_count + 2];
        for (int i = 0; i < new_corner_count; ++i)
        {
            adjusted_corners[i] = new_corners[i];
        }
        adjusted_corners[new_corner_count] = curr_corner[1];
        adjusted_corners[new_corner_count + 1] = curr_corner[2];
        Polyline result = new Polyline(adjusted_corners);
        return result;
    }

    /**
     * Smoothens the 90 degree corners of p_polyline to 45 degree
     * by cutting of the 90 degree corner. The cutting of is so small,
     * that no check is needed
     */
    private Polyline smoothen_corners(Polyline p_polyline)
    {
        Polyline result = p_polyline;
        boolean polyline_changed = true;
        while (polyline_changed)
        {
            if (result.arr.length < 4)
            {
                return result;
            }
            polyline_changed = false;
            Line[] line_arr = new Line[result.arr.length];
            System.arraycopy(result.arr, 0, line_arr, 0, line_arr.length);

            for (int i = 1; i < line_arr.length - 2; ++i)
            {
                Direction d1 = line_arr[i].direction();
                Direction d2 = line_arr[i + 1].direction();
                if (d1.is_multiple_of_45_degree() && d2.is_multiple_of_45_degree() && d1.projection(d2) != Signum.POSITIVE)
                {
                    // there is a 90 degree or sharper angle
                    Line new_line = smoothen_corner(line_arr, i);
                    if (new_line == null)
                    {
                        //the greedy smoothening couldn't change the polyline
                        new_line = smoothen_sharp_corner(line_arr, i);
                    }
                    if (new_line != null)
                    {
                        polyline_changed = true;
                        // add the new line into the line array
                        Line[] tmp_lines = new Line[line_arr.length + 1];
                        System.arraycopy(line_arr, 0, tmp_lines, 0, i + 1);
                        tmp_lines[i + 1] = new_line;
                        System.arraycopy(line_arr, i + 1, tmp_lines, i + 2,
                                tmp_lines.length - (i + 2));
                        line_arr = tmp_lines;
                        ++i;
                    }
                }
            }
            if (polyline_changed)
            {
                result = new Polyline(line_arr);
            }
        }
        return result;
    }

    /**
     * adds a line between at p_no  to smoothen a
     * 90 degree corner between p_line_1 and p_line_2 to 45 degree.
     * The distance of the new line to the corner will be so small
     * that no clearance check is necessary.
     */
    private Line smoothen_sharp_corner(Line[] p_line_arr, int p_no)
    {
        FloatPoint curr_corner = p_line_arr[p_no].intersection_approx(p_line_arr[p_no + 1]);
        if (curr_corner.x != (int) curr_corner.x)
        {
            // intersection of 2 diagonal lines is not integer
            Line result = smoothen_non_integer_corner(p_line_arr, p_no);
            {
                if (result != null)
                {
                    return result;
                }
            }
        }
        FloatPoint prev_corner = p_line_arr[p_no].intersection_approx(p_line_arr[p_no - 1]);
        FloatPoint next_corner = p_line_arr[p_no + 1].intersection_approx(p_line_arr[p_no + 2]);

        Direction prev_dir = p_line_arr[p_no].direction();
        Direction next_dir = p_line_arr[p_no + 1].direction();
        Direction new_line_dir = Direction.get_instance(prev_dir.get_vector().add(next_dir.get_vector()));
        Line translate_line = Line.get_instance(curr_corner.round(), new_line_dir);
        double translate_dist = (Limits.sqrt2 - 1) * this.curr_half_width;
        double prev_dist = Math.abs(translate_line.signed_distance(prev_corner));
        double next_dist = Math.abs(translate_line.signed_distance(next_corner));
        translate_dist = Math.min(translate_dist, prev_dist);
        translate_dist = Math.min(translate_dist, next_dist);
        if (translate_dist < 0.99)
        {
            return null;
        }
        translate_dist = Math.max(translate_dist - 1, 1);
        if (translate_line.side_of(next_corner) == Side.ON_THE_LEFT)
        {
            translate_dist = -translate_dist;
        }
        Line result = translate_line.translate(translate_dist);
        if (board.changed_area != null)
        {
            board.changed_area.join(curr_corner, curr_layer);
        }
        return result;
    }

    /**
     *  Smoothens with a short axis parrallel line to remove a non integer corner
     *  of two intersecting diagonal lines. 
     *  Returns null, if that is not possible.
     */
    private Line smoothen_non_integer_corner(Line[] p_line_arr, int p_no)
    {
        Line prev_line = p_line_arr[p_no];
        Line next_line = p_line_arr[p_no + 1];
        if (prev_line.is_equal_or_opposite(next_line))
        {
            return null;
        }
        if (!(prev_line.is_diagonal() && next_line.is_diagonal()))
        {
            return null;
        }
        FloatPoint curr_corner = prev_line.intersection_approx(next_line);
        FloatPoint prev_corner = prev_line.intersection_approx(p_line_arr[p_no - 1]);
        FloatPoint next_corner = next_line.intersection_approx(p_line_arr[p_no + 2]);
        Line result = null;
        int new_x = 0;
        int new_y = 0;
        boolean new_line_is_vertical = false;
        boolean new_line_is_horizontal = false;
        if (prev_corner.x > curr_corner.x && next_corner.x > curr_corner.x)
        {
            new_x = (int) Math.ceil(curr_corner.x);
            new_y = (int) Math.ceil(curr_corner.y);
            new_line_is_vertical = true;
        }
        else if (prev_corner.x < curr_corner.x && next_corner.x < curr_corner.x)
        {
            new_x = (int) Math.floor(curr_corner.x);
            new_y = (int) Math.floor(curr_corner.y);
            new_line_is_vertical = true;
        }
        else if (prev_corner.y > curr_corner.y && next_corner.y > curr_corner.y)
        {
            new_x = (int) Math.ceil(curr_corner.x);
            new_y = (int) Math.ceil(curr_corner.y);
            new_line_is_horizontal = true;
        }
        else if (prev_corner.y < curr_corner.y && next_corner.y < curr_corner.y)
        {
            new_x = (int) Math.floor(curr_corner.x);
            new_y = (int) Math.floor(curr_corner.y);
            new_line_is_horizontal = true;
        }
        Direction new_line_dir = null;
        if (new_line_is_vertical)
        {
            if (prev_corner.y < next_corner.y)
            {
                new_line_dir = Direction.UP;
            }
            else
            {
                new_line_dir = Direction.DOWN;
            }
        }
        else if (new_line_is_horizontal)
        {
            if (prev_corner.x < next_corner.x)
            {
                new_line_dir = Direction.RIGHT;
            }
            else
            {
                new_line_dir = Direction.LEFT;
            }
        }
        else
        {
            return null;
        }
        
        Point line_a = new IntPoint(new_x, new_y);
        result = new Line(line_a, new_line_dir);
        return result;
    }

    /**
     * adds a line between at p_no  to smoothen a
     * 90 degree corner between p_line_1 and p_line_2 to 45 degree.
     * The distance of the new line to the corner will be so big
     * that a clearance check is necessary.
     */
    private Line smoothen_corner(Line[] p_line_arr, int p_no)
    {
        FloatPoint prev_corner = p_line_arr[p_no].intersection_approx(p_line_arr[p_no - 1]);
        FloatPoint curr_corner = p_line_arr[p_no].intersection_approx(p_line_arr[p_no + 1]);
        FloatPoint next_corner = p_line_arr[p_no + 1].intersection_approx(p_line_arr[p_no + 2]);

        Direction prev_dir = p_line_arr[p_no].direction();
        Direction next_dir = p_line_arr[p_no + 1].direction();
        Direction new_line_dir = Direction.get_instance(prev_dir.get_vector().add(next_dir.get_vector()));
        Line translate_line = Line.get_instance(curr_corner.round(), new_line_dir);
        double prev_dist = Math.abs(translate_line.signed_distance(prev_corner));
        double next_dist = Math.abs(translate_line.signed_distance(next_corner));
        if (prev_dist == 0 || next_dist == 0)
        {
            return null;
        }
        double max_translate_dist;
        FloatPoint nearest_corner;
        if (prev_dist <= next_dist)
        {
            max_translate_dist = prev_dist;
            nearest_corner = prev_corner;
        }
        else
        {
            max_translate_dist = next_dist;
            nearest_corner = next_corner;
        }
        if (max_translate_dist < 1)
        {
            return null;
        }
        max_translate_dist = Math.max(max_translate_dist - 1, 1);
        if (translate_line.side_of(next_corner) == Side.ON_THE_LEFT)
        {
            max_translate_dist = -max_translate_dist;
        }
        Line[] check_lines = new Line[3];
        check_lines[0] = p_line_arr[p_no];
        check_lines[2] = p_line_arr[p_no + 1];
        double translate_dist = max_translate_dist;
        double delta_dist = max_translate_dist;
        Side side_of_nearest_corner = translate_line.side_of(nearest_corner);
        int sign = Signum.as_int(max_translate_dist);
        Line result = null;
        while (Math.abs(delta_dist) > this.min_translate_dist)
        {
            boolean check_ok = false;
            Line new_line = translate_line.translate(translate_dist);
            Side new_line_side_of_nearest_corner = new_line.side_of(nearest_corner);
            if (new_line_side_of_nearest_corner == side_of_nearest_corner || new_line_side_of_nearest_corner == Side.COLLINEAR)
            {
                check_lines[1] = new_line;
                Polyline tmp = new Polyline(check_lines);

                if (tmp.arr.length == 3)
                {
                    TileShape shape_to_check =
                            tmp.offset_shape(curr_half_width, 0);
                    check_ok = board.check_trace_shape(shape_to_check,
                            curr_layer, curr_net_no_arr, curr_cl_type, this.contact_pins);

                }
                delta_dist /= 2;
                if (check_ok)
                {
                    result = check_lines[1];
                    if (translate_dist == max_translate_dist)
                    {
                        // biggest possible change
                        break;
                    }
                    translate_dist += delta_dist;
                }
                else
                {
                    translate_dist -= delta_dist;
                }
            }
            else
            // moved a little bit to far at the first time
            // because of numerical inaccuracy
            {
                double shorten_value = sign * 0.5;
                max_translate_dist -= shorten_value;
                translate_dist -= shorten_value;
                delta_dist -= shorten_value;
            }
        }
        if (result != null && board.changed_area != null)
        {
            FloatPoint new_prev_corner =
                    check_lines[0].intersection_approx(result);
            FloatPoint new_next_corner =
                    check_lines[2].intersection_approx(result);
            board.changed_area.join(new_prev_corner, curr_layer);
            board.changed_area.join(new_next_corner, curr_layer);
            board.changed_area.join(curr_corner, curr_layer);
        }
        return result;
    }

    Polyline smoothen_start_corner_at_trace(PolylineTrace p_trace)
    {
        boolean acute_angle = false;
        boolean bend = false;
        FloatPoint other_trace_corner_approx = null;
        Line other_trace_line = null;
        Line other_prev_trace_line = null;
        Polyline trace_polyline = p_trace.polyline();
        Point curr_end_corner = trace_polyline.corner(0);

        if (this.curr_clip_shape != null && this.curr_clip_shape.is_outside(curr_end_corner))
        {
            return null;
        }

        Point curr_prev_end_corner = trace_polyline.corner(1);
        Side prev_corner_side = null;
        Direction line_direction = trace_polyline.arr[1].direction();
        Direction prev_line_direction = trace_polyline.arr[2].direction();

        java.util.Collection<Item> contact_list = p_trace.get_start_contacts();
        for (Item curr_contact : contact_list)
        {
            if (curr_contact instanceof PolylineTrace && !curr_contact.is_shove_fixed())
            {
                Polyline contact_trace_polyline = ((PolylineTrace) curr_contact).polyline();
                FloatPoint curr_other_trace_corner_approx;
                Line curr_other_trace_line;
                Line curr_other_prev_trace_line;
                if (contact_trace_polyline.first_corner().equals(curr_end_corner))
                {
                    curr_other_trace_corner_approx = contact_trace_polyline.corner_approx(1);
                    curr_other_trace_line = contact_trace_polyline.arr[1];
                    curr_other_prev_trace_line = contact_trace_polyline.arr[2];
                }
                else
                {
                    int curr_corner_no = contact_trace_polyline.corner_count() - 2;
                    curr_other_trace_corner_approx = contact_trace_polyline.corner_approx(curr_corner_no);
                    curr_other_trace_line = contact_trace_polyline.arr[curr_corner_no + 1].opposite();
                    curr_other_prev_trace_line = contact_trace_polyline.arr[curr_corner_no];
                }
                Side curr_prev_corner_side = curr_prev_end_corner.side_of(curr_other_trace_line);
                Signum curr_projection = line_direction.projection(curr_other_trace_line.direction());
                boolean other_trace_found = false;
                if (curr_projection == Signum.POSITIVE && curr_prev_corner_side != Side.COLLINEAR)
                {
                    if (curr_other_trace_line.direction().is_orthogonal())
                    {
                        acute_angle = true;
                        other_trace_found = true;
                    }
                }
                else if (curr_projection == Signum.ZERO && trace_polyline.corner_count() > 2)
                {
                    if (prev_line_direction.projection(curr_other_trace_line.direction()) == Signum.POSITIVE)
                    {
                        bend = true;
                        other_trace_found = true;
                    }
                }
                if (other_trace_found)
                {
                    other_trace_corner_approx = curr_other_trace_corner_approx;
                    other_trace_line = curr_other_trace_line;
                    prev_corner_side = curr_prev_corner_side;
                    other_prev_trace_line = curr_other_prev_trace_line;
                }
            }
            else
            {
                return null;
            }
        }

        if (acute_angle)
        {
            Direction new_line_dir;
            if (prev_corner_side == Side.ON_THE_LEFT)
            {
                new_line_dir = other_trace_line.direction().turn_45_degree(2);
            }
            else
            {
                new_line_dir = other_trace_line.direction().turn_45_degree(6);
            }
            Line translate_line = Line.get_instance(curr_end_corner.to_float().round(), new_line_dir);
            double translate_dist = (Limits.sqrt2 - 1) * this.curr_half_width;
            double prev_corner_dist = Math.abs(translate_line.signed_distance(curr_prev_end_corner.to_float()));
            double other_dist = Math.abs(translate_line.signed_distance(other_trace_corner_approx));
            translate_dist = Math.min(translate_dist, prev_corner_dist);
            translate_dist = Math.min(translate_dist, other_dist);
            if (translate_dist >= 0.99)
            {

                translate_dist = Math.max(translate_dist - 1, 1);
                if (translate_line.side_of(curr_prev_end_corner) == Side.ON_THE_LEFT)
                {
                    translate_dist = -translate_dist;
                }
                Line add_line = translate_line.translate(translate_dist);
                // constract the new trace polyline.
                Line[] new_lines = new Line[trace_polyline.arr.length + 1];
                new_lines[0] = other_trace_line;
                new_lines[1] = add_line;
                for (int i = 2; i < new_lines.length; ++i)
                {
                    new_lines[i] = trace_polyline.arr[i - 1];
                }
                return new Polyline(new_lines);
            }
        }
        else if (bend)
        {
            Line[] check_line_arr = new Line[trace_polyline.arr.length + 1];
            check_line_arr[0] = other_prev_trace_line;
            check_line_arr[1] = other_trace_line;
            for (int i = 2; i < check_line_arr.length; ++i)
            {
                check_line_arr[i] = trace_polyline.arr[i - 1];
            }
            Line new_line = reposition_line(check_line_arr, 2);
            if (new_line != null)
            {
                Line[] new_lines = new Line[trace_polyline.arr.length];
                new_lines[0] = other_trace_line;
                new_lines[1] = new_line;
                for (int i = 2; i < new_lines.length; ++i)
                {
                    new_lines[i] = trace_polyline.arr[i];
                }
                return new Polyline(new_lines);
            }
        }
        return null;
    }

    Polyline smoothen_end_corner_at_trace(PolylineTrace p_trace)
    {
        boolean acute_angle = false;
        boolean bend = false;
        FloatPoint other_trace_corner_approx = null;
        Line other_trace_line = null;
        Line other_prev_trace_line = null;
        Polyline trace_polyline = p_trace.polyline();
        Point curr_end_corner = trace_polyline.last_corner();

        if (this.curr_clip_shape != null && this.curr_clip_shape.is_outside(curr_end_corner))
        {
            return null;
        }

        Point curr_prev_end_corner = trace_polyline.corner(trace_polyline.corner_count() - 2);
        Side prev_corner_side = null;
        Direction line_direction = trace_polyline.arr[trace_polyline.arr.length - 2].direction().opposite();
        Direction prev_line_direction = trace_polyline.arr[trace_polyline.arr.length - 3].direction().opposite();

        java.util.Collection<Item> contact_list = p_trace.get_end_contacts();
        for (Item curr_contact : contact_list)
        {
            if (curr_contact instanceof PolylineTrace && !curr_contact.is_shove_fixed())
            {
                Polyline contact_trace_polyline = ((PolylineTrace) curr_contact).polyline();
                FloatPoint curr_other_trace_corner_approx;
                Line curr_other_trace_line;
                Line curr_other_prev_trace_line;
                if (contact_trace_polyline.first_corner().equals(curr_end_corner))
                {
                    curr_other_trace_corner_approx = contact_trace_polyline.corner_approx(1);
                    curr_other_trace_line = contact_trace_polyline.arr[1];
                    curr_other_prev_trace_line = contact_trace_polyline.arr[2];
                }
                else
                {
                    int curr_corner_no = contact_trace_polyline.corner_count() - 2;
                    curr_other_trace_corner_approx = contact_trace_polyline.corner_approx(curr_corner_no);
                    curr_other_trace_line = contact_trace_polyline.arr[curr_corner_no + 1].opposite();
                    curr_other_prev_trace_line = contact_trace_polyline.arr[curr_corner_no];
                }
                Side curr_prev_corner_side = curr_prev_end_corner.side_of(curr_other_trace_line);
                Signum curr_projection = line_direction.projection(curr_other_trace_line.direction());
                boolean other_trace_found = false;
                if (curr_projection == Signum.POSITIVE && curr_prev_corner_side != Side.COLLINEAR)
                {
                    if (curr_other_trace_line.direction().is_orthogonal())
                    {
                        acute_angle = true;
                        other_trace_found = true;
                    }
                }
                else if (curr_projection == Signum.ZERO && trace_polyline.corner_count() > 2)
                {
                    if (prev_line_direction.projection(curr_other_trace_line.direction()) == Signum.POSITIVE)
                    {
                        bend = true;
                        other_trace_found = true;
                    }
                }
                if (other_trace_found)
                {
                    other_trace_corner_approx = curr_other_trace_corner_approx;
                    other_trace_line = curr_other_trace_line;
                    prev_corner_side = curr_prev_corner_side;
                    other_prev_trace_line = curr_other_prev_trace_line;
                }
            }
            else
            {
                return null;
            }
        }

        if (acute_angle)
        {
            Direction new_line_dir;
            if (prev_corner_side == Side.ON_THE_LEFT)
            {
                new_line_dir = other_trace_line.direction().turn_45_degree(6);
            }
            else
            {
                new_line_dir = other_trace_line.direction().turn_45_degree(2);
            }
            Line translate_line = Line.get_instance(curr_end_corner.to_float().round(), new_line_dir);
            double translate_dist = (Limits.sqrt2 - 1) * this.curr_half_width;
            double prev_corner_dist = Math.abs(translate_line.signed_distance(curr_prev_end_corner.to_float()));
            double other_dist = Math.abs(translate_line.signed_distance(other_trace_corner_approx));
            translate_dist = Math.min(translate_dist, prev_corner_dist);
            translate_dist = Math.min(translate_dist, other_dist);
            if (translate_dist >= 0.99)
            {

                translate_dist = Math.max(translate_dist - 1, 1);
                if (translate_line.side_of(curr_prev_end_corner) == Side.ON_THE_LEFT)
                {
                    translate_dist = -translate_dist;
                }
                Line add_line = translate_line.translate(translate_dist);
                // constract the new trace polyline.
                Line[] new_lines = new Line[trace_polyline.arr.length + 1];
                for (int i = 0; i < trace_polyline.arr.length - 1; ++i)
                {
                    new_lines[i] = trace_polyline.arr[i];
                }
                new_lines[new_lines.length - 2] = add_line;
                new_lines[new_lines.length - 1] = other_trace_line;
                return new Polyline(new_lines);
            }
        }
        else if (bend)
        {
            Line[] check_line_arr = new Line[trace_polyline.arr.length + 1];
            for (int i = 0; i < trace_polyline.arr.length - 1; ++i)
            {
                check_line_arr[i] = trace_polyline.arr[i];
            }
            check_line_arr[check_line_arr.length - 2] = other_trace_line;
            check_line_arr[check_line_arr.length - 1] = other_prev_trace_line;
            Line new_line = reposition_line(check_line_arr, trace_polyline.arr.length - 2);
            if (new_line != null)
            {
                Line[] new_lines = new Line[trace_polyline.arr.length];
                for (int i = 0; i < new_lines.length - 2; ++i)
                {
                    new_lines[i] = trace_polyline.arr[i];
                }
                new_lines[new_lines.length - 2] = new_line;
                new_lines[new_lines.length - 1] = other_trace_line;
                return new Polyline(new_lines);
            }
        }
        return null;
    }
}
