/*
 *   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.
 *
 * ForcedViaAlgo.java
 *
 * Created on 25. April 2004, 09:55
 */

package eu.mihosoft.freerouting.board;

import eu.mihosoft.freerouting.geometry.planar.ConvexShape;
import eu.mihosoft.freerouting.geometry.planar.IntPoint;
import eu.mihosoft.freerouting.geometry.planar.Point;
import eu.mihosoft.freerouting.geometry.planar.FloatPoint;
import eu.mihosoft.freerouting.geometry.planar.Shape;
import eu.mihosoft.freerouting.geometry.planar.TileShape;
import eu.mihosoft.freerouting.geometry.planar.Simplex;
import eu.mihosoft.freerouting.geometry.planar.IntBox;
import eu.mihosoft.freerouting.geometry.planar.Circle;
import eu.mihosoft.freerouting.geometry.planar.Vector;
import eu.mihosoft.freerouting.geometry.planar.Limits;
import eu.mihosoft.freerouting.rules.ViaInfo;
import eu.mihosoft.freerouting.library.Padstack;


/**
 * Class with static functions for checking and inserting forced vias.
 *
 * @author Alfons Wirtz
 */
public class ForcedViaAlgo
{
    /**
     * Checks, if a Via is possible at the input layer after evtl. shoving aside obstacle traces.
     * p_room_shape is used for calculating the from_side.
     */
    public static ForcedPadAlgo.CheckDrillResult check_layer(double p_via_radius, int p_cl_class, boolean p_attach_smd_allowed,
            TileShape p_room_shape, Point p_location, int p_layer,
            int[] p_net_no_arr, int p_max_recursion_depth,
            int p_max_via_recursion_depth, RoutingBoard p_board)
    {
        if (p_via_radius <= 0)
        {
            return ForcedPadAlgo.CheckDrillResult.DRILLABLE;
        }
        ForcedPadAlgo forced_pad_algo = new ForcedPadAlgo(p_board);
        if (!(p_location instanceof IntPoint))
        {
            return ForcedPadAlgo.CheckDrillResult.NOT_DRILLABLE;
        }
        ConvexShape via_shape =  new Circle((IntPoint) p_location, (int) Math.ceil(p_via_radius));
        
        double check_radius =
                p_via_radius + 0.5 * p_board.clearance_value(p_cl_class, p_cl_class, p_layer)
                + p_board.get_min_trace_half_width();
        
        TileShape tile_shape;
        boolean is_90_degree;
        if (p_board.rules.get_trace_angle_restriction() == AngleRestriction.NINETY_DEGREE)
        {
            tile_shape = via_shape.bounding_box();
            is_90_degree = true;
        }
        else
        {
            tile_shape = via_shape.bounding_octagon();
            is_90_degree = false;
        }
        
        CalcFromSide from_side = calculate_from_side(p_location.to_float(), tile_shape, p_room_shape.to_Simplex(), check_radius, is_90_degree);
        if (from_side == null)
        {
            return ForcedPadAlgo.CheckDrillResult.NOT_DRILLABLE;
        }
        
        ForcedPadAlgo.CheckDrillResult result = forced_pad_algo.check_forced_pad(tile_shape, from_side, p_layer, p_net_no_arr,
                p_cl_class, p_attach_smd_allowed, null, p_max_recursion_depth, p_max_via_recursion_depth, false, null);
        return result;
    }
    
    /**
     * Checks, if a Via is possible with the input parameter after evtl. shoving aside obstacle traces.
     */
    public static boolean check(ViaInfo p_via_info, Point p_location, int[] p_net_no_arr, int p_max_recursion_depth,
            int p_max_via_recursion_depth, RoutingBoard p_board)
    {
        Vector translate_vector = p_location.difference_by(Point.ZERO);
        int calc_from_side_offset = p_board.get_min_trace_half_width();
        ForcedPadAlgo forced_pad_algo = new ForcedPadAlgo(p_board);
        Padstack via_padstack = p_via_info.get_padstack();
        for (int i = via_padstack.from_layer(); i <= via_padstack.to_layer(); ++i)
        {
            Shape curr_pad_shape = via_padstack.get_shape(i);
            if (curr_pad_shape == null)
            {
                continue;
            }
            curr_pad_shape = (Shape) curr_pad_shape.translate_by(translate_vector);
            TileShape tile_shape;
            if (p_board.rules.get_trace_angle_restriction() == AngleRestriction.NINETY_DEGREE)
            {
                tile_shape = curr_pad_shape.bounding_box();
            }
            else
            {
                tile_shape = curr_pad_shape.bounding_octagon();
            }
            CalcFromSide from_side
                    = forced_pad_algo.calc_from_side(tile_shape, p_location, i, calc_from_side_offset,p_via_info.get_clearance_class());
            if (forced_pad_algo.check_forced_pad(tile_shape, from_side, i, p_net_no_arr, p_via_info.get_clearance_class(),
                    p_via_info.attach_smd_allowed(), null, p_max_recursion_depth, p_max_via_recursion_depth, false, null)
                    == ForcedPadAlgo.CheckDrillResult.NOT_DRILLABLE)
            {
                p_board.set_shove_failing_layer(i);
                return false;
            }
        }
        return true;
    }
    
    /**
     * Shoves aside traces, so that a via with the input parameters can be
     * inserted without clearance violations. If the shove failed, the database may be damaged, so that an undo
     * becomes necessesary.
     * p_trace_clearance_class_no and p_trace_pen_halfwidth_arr is provided to make space for starting a trace
     * in case the trace width is bigger than the via shape.
     * Returns false, if the forced via failed.
     */
    public static boolean insert( ViaInfo p_via_info, Point p_location, int[] p_net_no_arr,
            int p_trace_clearance_class_no, int [] p_trace_pen_halfwidth_arr, int p_max_recursion_depth,
            int p_max_via_recursion_depth, RoutingBoard p_board)
    {
        Vector translate_vector = p_location.difference_by(Point.ZERO);
        int calc_from_side_offset = p_board.get_min_trace_half_width();
        ForcedPadAlgo forced_pad_algo = new ForcedPadAlgo(p_board);
        Padstack via_padstack = p_via_info.get_padstack();
        for (int i = via_padstack.from_layer(); i <= via_padstack.to_layer(); ++i)
        {
            Shape curr_pad_shape = via_padstack.get_shape(i);
            if (curr_pad_shape == null)
            {
                continue;
            }
            curr_pad_shape = (Shape) curr_pad_shape.translate_by(translate_vector);
            TileShape tile_shape;
            Circle start_trace_circle;
            if (p_trace_pen_halfwidth_arr[i] > 0 && p_location instanceof IntPoint)
            {
                start_trace_circle = new Circle((IntPoint) p_location, p_trace_pen_halfwidth_arr[i]);
            }
            else
            {
                start_trace_circle = null;
            }
            TileShape start_trace_shape = null;
            if (p_board.rules.get_trace_angle_restriction() == AngleRestriction.NINETY_DEGREE)
            {
                tile_shape = curr_pad_shape.bounding_box();
                if (start_trace_circle != null)
                {
                    start_trace_shape = start_trace_circle.bounding_box();
                }
            }
            else
            {
                tile_shape = curr_pad_shape.bounding_octagon();
                if (start_trace_circle != null)
                {
                    start_trace_shape = start_trace_circle.bounding_octagon();
                }
            }
            CalcFromSide from_side
                    = forced_pad_algo.calc_from_side(tile_shape, p_location, i, calc_from_side_offset,p_via_info.get_clearance_class());
            if (!forced_pad_algo.forced_pad(tile_shape, from_side, i, p_net_no_arr, p_via_info.get_clearance_class(),
                    p_via_info.attach_smd_allowed(), null, p_max_recursion_depth, p_max_via_recursion_depth))
            {
                p_board.set_shove_failing_layer(i);
                return false;
            }
            if (start_trace_shape != null)
            {
                // necessesary in case strart_trace_shape is bigger than tile_shape
                if (!forced_pad_algo.forced_pad(start_trace_shape, from_side, i, p_net_no_arr, p_trace_clearance_class_no,
                        true, null, p_max_recursion_depth, p_max_via_recursion_depth))
                {
                    p_board.set_shove_failing_layer(i);
                    return false;
                }
            }
        }
        p_board.insert_via(via_padstack, p_location, p_net_no_arr, p_via_info.get_clearance_class(),
                FixedState.UNFIXED, p_via_info.attach_smd_allowed());
        return true;
    }
    
    static private CalcFromSide calculate_from_side(FloatPoint p_via_location, TileShape p_via_shape, Simplex p_room_shape, double p_dist, boolean is_90_degree)
    {
        IntBox via_box = p_via_shape.bounding_box();
        for (int i = 0; i < 4; ++i)
        {
            FloatPoint check_point;
            double border_x;
            double border_y;
            if (i == 0)
            {
                check_point = new FloatPoint(p_via_location.x, p_via_location.y - p_dist);
                border_x = p_via_location.x;
                border_y =  via_box.ll.y;
            }
            else if (i == 1)
            {
                check_point = new FloatPoint(p_via_location.x + p_dist, p_via_location.y);
                border_x = via_box.ur.x;
                border_y = p_via_location.y;
            }
            else if (i == 2)
            {
                check_point = new FloatPoint(p_via_location.x, p_via_location.y + p_dist);
                border_x = p_via_location.x;
                border_y =  via_box.ur.y;
            }
            else // i == 3
            {
                check_point = new FloatPoint(p_via_location.x - p_dist, p_via_location.y);
                border_x = via_box.ll.x;
                border_y =  p_via_location.y;
            }
            if (p_room_shape.contains(check_point))
            {
                int from_side_no;
                if (is_90_degree)
                {
                    from_side_no = i;
                }
                else
                {
                    from_side_no = 2 * i;
                }
                FloatPoint curr_border_point = new FloatPoint(border_x, border_y);
                return new CalcFromSide(from_side_no, curr_border_point);
            }
        }
        if (is_90_degree)
        {
            return null;
        }
        // try the diagonal drections
        double dist = p_dist /  Limits.sqrt2;
        double border_dist = via_box.max_width() / (2 * Limits.sqrt2);
        for (int i = 0; i < 4; ++i)
        {
            FloatPoint check_point;
            double border_x;
            double border_y;
            if (i == 0)
            {
                check_point = new FloatPoint(p_via_location.x + dist, p_via_location.y - dist);
                border_x = p_via_location.x + border_dist;
                border_y = p_via_location.y - border_dist;
            }
            else if (i == 1)
            {
                check_point = new FloatPoint(p_via_location.x + dist, p_via_location.y + dist);
                border_x = p_via_location.x + border_dist;
                border_y = p_via_location.y + border_dist;
            }
            else if (i == 2)
            {
                check_point = new FloatPoint(p_via_location.x - dist, p_via_location.y + dist);
                border_x = p_via_location.x - border_dist;
                border_y = p_via_location.y + border_dist;
            }
            else // i == 3
            {
                check_point = new FloatPoint(p_via_location.x - dist, p_via_location.y - dist);
                border_x = p_via_location.x - border_dist;
                border_y = p_via_location.y - border_dist;
            }
            if (p_room_shape.contains(check_point))
            {
                
                int from_side_no = 2 * i + 1;
                FloatPoint curr_border_point = new FloatPoint(border_x, border_y);
                return new CalcFromSide(from_side_no, curr_border_point);
            }
        }
        return null;
    }
}
