/*
 *   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.
 *
 * ShoveViaAlgo.java
 *
 * Created on 12. Dezember 2005, 06:48
 *
 */

package eu.mihosoft.freerouting.board;

import java.util.Collection;

import eu.mihosoft.freerouting.datastructures.TimeLimit;

import eu.mihosoft.freerouting.geometry.planar.TileShape;
import eu.mihosoft.freerouting.geometry.planar.ConvexShape;
import eu.mihosoft.freerouting.geometry.planar.IntOctagon;
import eu.mihosoft.freerouting.geometry.planar.IntBox;
import eu.mihosoft.freerouting.geometry.planar.IntPoint;
import eu.mihosoft.freerouting.geometry.planar.Point;
import eu.mihosoft.freerouting.geometry.planar.Vector;
import eu.mihosoft.freerouting.geometry.planar.FloatPoint;


/**
 *
 * Contains internal auxiliary functions of class RoutingBoard
 * for shoving vias and pins
 *
 * @author Alfons Wirtz
 */
public class MoveDrillItemAlgo
{
    
    /**
     * checks, if p_drill_item can be translated by p_vector by shoving obstacle
     * traces and vias aside, so that no clearance violations occur.
     */
    public static boolean check(DrillItem p_drill_item, Vector p_vector, int p_max_recursion_depth,
            int p_max_via_recursion_depth, Collection<Item> p_ignore_items,
            RoutingBoard p_board, TimeLimit p_time_limit)
    {
        
        if (p_time_limit != null && p_time_limit.limit_exceeded())
        {
            return false;
        }
        if (p_drill_item.is_shove_fixed())
        {
            return false;
        }
        
        // Check, that p_drillitem is only connected to traces.
        Collection<Item> contact_list = p_drill_item.get_normal_contacts();
        for (Item curr_contact : contact_list)
        {
            if (!(curr_contact instanceof Trace || curr_contact instanceof ConductionArea))
            {
                return false;
            }
        }
        Collection<Item> ignore_items;
        if (p_ignore_items == null)
        {
            ignore_items = new java.util.LinkedList<Item>();
        }
        else
        {
            ignore_items = p_ignore_items;
        }
        ignore_items.add(p_drill_item);
        ForcedPadAlgo forced_pad_algo = new ForcedPadAlgo(p_board);
        boolean attach_allowed = false;
        if (p_drill_item instanceof Via)
        {
            attach_allowed = ((Via)p_drill_item).attach_allowed;
        }
        ShapeSearchTree search_tree = p_board.search_tree_manager.get_default_tree();
        for (int curr_layer = p_drill_item.first_layer(); curr_layer <= p_drill_item.last_layer(); ++curr_layer)
        {
            int curr_ind = curr_layer - p_drill_item.first_layer();
            TileShape curr_shape = p_drill_item.get_tree_shape(search_tree, curr_ind);
            if (curr_shape == null)
            {
                continue;
            }
            ConvexShape new_shape = (ConvexShape) curr_shape.translate_by(p_vector);
            TileShape curr_tile_shape;
            if (p_board.rules.get_trace_angle_restriction() == AngleRestriction.NINETY_DEGREE)
            {
                curr_tile_shape = new_shape.bounding_box();
            }
            else
            {
                curr_tile_shape = new_shape.bounding_octagon();
            }
            CalcFromSide from_side = new CalcFromSide(p_drill_item.get_center(), curr_tile_shape);
            if (forced_pad_algo.check_forced_pad(curr_tile_shape, from_side, curr_layer,
                    p_drill_item.net_no_arr, p_drill_item.clearance_class_no(), attach_allowed,
                    ignore_items, p_max_recursion_depth, p_max_via_recursion_depth, true, p_time_limit)
                    == ForcedPadAlgo.CheckDrillResult.NOT_DRILLABLE)
            {
                return false;
            }
        }
        return true;
    }
    
    /**
     * Translates p_drill_item by p_vector by shoving obstacle
     * traces and vias aside, so that no clearance violations occur.
     * If p_tidy_region != null, it will be joined by the bounding octagons of the translated shapes.
     */
    static boolean insert(DrillItem p_drill_item, Vector p_vector,
            int p_max_recursion_depth, int p_max_via_recursion_depth, IntOctagon p_tidy_region,
            RoutingBoard p_board)
    {
        if (p_drill_item.is_shove_fixed())
        {
            return false;
        }

        boolean attach_allowed = false;
        if (p_drill_item instanceof Via)
        {
            attach_allowed = ((Via)p_drill_item).attach_allowed;
        }
        ForcedPadAlgo forced_pad_algo = new ForcedPadAlgo(p_board);
        Collection<Item> ignore_items = new java.util.LinkedList<Item>();
        ignore_items.add(p_drill_item);
        ShapeSearchTree search_tree = p_board.search_tree_manager.get_default_tree();
        for (int curr_layer = p_drill_item.first_layer(); curr_layer <= p_drill_item.last_layer(); ++curr_layer)
        {
            int curr_ind = curr_layer - p_drill_item.first_layer();
            TileShape curr_shape = p_drill_item.get_tree_shape(search_tree, curr_ind);
            if (curr_shape == null)
            {
                continue;
            }
            ConvexShape new_shape = (ConvexShape) curr_shape.translate_by(p_vector);
            TileShape curr_tile_shape;
            if (p_board.rules.get_trace_angle_restriction() == AngleRestriction.NINETY_DEGREE)
            {
                curr_tile_shape = new_shape.bounding_box();
            }
            else
            {
                curr_tile_shape = new_shape.bounding_octagon();
            }
            if (p_tidy_region != null)
            {
                p_tidy_region = p_tidy_region.union(curr_tile_shape.bounding_octagon());
            }
            CalcFromSide from_side = new CalcFromSide(p_drill_item.get_center(), curr_tile_shape);
            if (!forced_pad_algo.forced_pad(curr_tile_shape, from_side, curr_layer, p_drill_item.net_no_arr,
                    p_drill_item.clearance_class_no(), attach_allowed,
                    ignore_items, p_max_recursion_depth, p_max_via_recursion_depth))
            {
                return false;
            }
            IntBox curr_bounding_box = curr_shape.bounding_box();
            for (int j = 0; j < 4; ++j)
            {
                p_board.join_changed_area( curr_bounding_box.corner_approx(j), curr_layer);
            }
        }
        p_drill_item.move_by(p_vector);
        return true;
    }
    
    /**
     * Shoves vias out of p_obstacle_shape. Returns false, if the database is damaged, so that an undo is necessary afterwards.
     */
    static boolean shove_vias(TileShape p_obstacle_shape, CalcFromSide p_from_side, int p_layer, int[] p_net_no_arr,
            int p_cl_type, Collection<Item> p_ignore_items, int p_max_recursion_depth,
            int p_max_via_recursion_depth, boolean p_copper_sharing_allowed,
            RoutingBoard p_board)
    {
        ShapeSearchTree search_tree = p_board.search_tree_manager.get_default_tree();
        ShapeTraceEntries shape_entries =
                new ShapeTraceEntries(p_obstacle_shape, p_layer, p_net_no_arr, p_cl_type, p_from_side, p_board);
        Collection<Item> obstacles =
                search_tree.overlapping_items_with_clearance(p_obstacle_shape, p_layer, new int[0], p_cl_type);
        
        if (!shape_entries.store_items(obstacles, false, p_copper_sharing_allowed))
        {
            return true;
        }
        if (p_ignore_items != null)
        {
            shape_entries.shove_via_list.removeAll(p_ignore_items);
        }
        if (shape_entries.shove_via_list.isEmpty())
        {
            return true;
        }
        double shape_radius = 0.5 * p_obstacle_shape.bounding_box().min_width();
        for (Via curr_via : shape_entries.shove_via_list)
        {
            if (curr_via.shares_net_no(p_net_no_arr))
            {
                continue;
            }
            if (p_max_via_recursion_depth <= 0)
            {
                return true;
            }
            IntPoint [] try_via_centers =
                    try_shove_via_points(p_obstacle_shape, p_layer, curr_via, p_cl_type, true, p_board);
            IntPoint new_via_center = null;
            double max_dist = 0.5 * curr_via.get_shape_on_layer(p_layer).bounding_box().max_width() + shape_radius;
            double max_dist_square = max_dist * max_dist;
            IntPoint curr_via_center = (IntPoint) curr_via.get_center();
            FloatPoint check_via_center = curr_via_center.to_float();
            Vector rel_coor = null;
            for (int i = 0; i < try_via_centers.length; ++i)
            {
                if (i == 0 || check_via_center.distance_square(try_via_centers[i].to_float()) <= max_dist_square)
                {
                    Collection<Item> ignore_items = new java.util.LinkedList<Item>();
                    if (p_ignore_items != null)
                    {
                        ignore_items.addAll(p_ignore_items);
                    }
                    rel_coor = try_via_centers[i].difference_by(curr_via_center);
                    // No time limit here because the item database is already changed.
                    boolean shove_ok = check(curr_via, rel_coor, p_max_recursion_depth, 
                            p_max_via_recursion_depth - 1, ignore_items, p_board, null);
                    if (shove_ok)
                    {
                        new_via_center = try_via_centers[i];
                        break;
                    }
                }
            }
            if (new_via_center == null)
            {
                continue;
            }
            if (!insert(curr_via, rel_coor, p_max_recursion_depth, p_max_via_recursion_depth - 1, null, p_board))
            {
                return false;
            }
        }
        return true;
    }
    
    /**
     * Calculates possible new location for a via to shove outside p_obstacle_shape.
     * if p_extended_check is true, more than 1 possible new locations are calculated.
     * The function isused here and in ShoveTraceAlgo.check.
     */
    static IntPoint[] try_shove_via_points(TileShape p_obstacle_shape, int p_layer, Via p_via, int  p_cl_class_no,
            boolean p_extended_check, RoutingBoard p_board)
    {
        ShapeSearchTree search_tree = p_board.search_tree_manager.get_default_tree();
        TileShape curr_via_shape = p_via.get_tree_shape_on_layer(search_tree, p_layer);
        if (curr_via_shape == null)
        {
            return new IntPoint [0];
        }
        boolean is_int_octagon = p_obstacle_shape.is_IntOctagon();
        double clearance_value = p_board.clearance_value(p_cl_class_no, p_via.clearance_class_no(), p_layer);
        double shove_distance;
        if (p_board.rules.get_trace_angle_restriction() == AngleRestriction.NINETY_DEGREE ||is_int_octagon )
        {
            shove_distance =  0.5 * curr_via_shape.bounding_box().max_width();
            if (!search_tree.is_clearance_compensation_used())
            {
                shove_distance += clearance_value;
            }
        }
        else
        {
            // a different algorithm is used for calculating the new via centers
            shove_distance = 0;
            if (!search_tree.is_clearance_compensation_used())
            {
                // enlarge p_obstacle_shape and curr_via_shape by half of the clearance value to syncronize
                // with the check algorithm in ShapeSearchTree.overlapping_tree_entries_with_clearance
                shove_distance += 0.5 * clearance_value;
            }
        }
        
        // The additional constant 2 is an empirical value for the tolerance in case of diagonal shoving.
        shove_distance  += 2;
        
        IntPoint curr_via_center = (IntPoint) p_via.get_center();
        IntPoint [] try_via_centers;
        int try_count = 1;
        if (p_board.rules.get_trace_angle_restriction() == AngleRestriction.NINETY_DEGREE)
        {
            IntBox curr_offset_box = p_obstacle_shape.bounding_box().offset(shove_distance);
            if (p_extended_check)
            {
                try_count = 2;
            }
            try_via_centers = curr_offset_box.nearest_border_projections(curr_via_center, try_count);
        }
        else if (is_int_octagon)
        {
            IntOctagon curr_offset_octagon = p_obstacle_shape.bounding_octagon().enlarge(shove_distance);
            if (p_extended_check)
            {
                try_count = 4;
            }
            
            try_via_centers = curr_offset_octagon.nearest_border_projections(curr_via_center, try_count);
        }
        else
        {
            TileShape curr_offset_shape = (TileShape) p_obstacle_shape.enlarge(shove_distance);
            if (!search_tree.is_clearance_compensation_used())
            {
                curr_via_shape = (TileShape) curr_via_shape.enlarge(0.5 * clearance_value);
            }
            if (p_extended_check)
            {
                try_count = 4;
            }
            FloatPoint[] shove_deltas =  curr_offset_shape.nearest_relative_outside_locations(curr_via_shape, try_count);
            try_via_centers = new IntPoint[shove_deltas.length];
            for (int i = 0; i < try_via_centers.length; ++i)
            {
                Vector curr_delta = shove_deltas[i].round().difference_by(Point.ZERO);
                try_via_centers[i] = (IntPoint) curr_via_center.translate_by(curr_delta);
            }
        }
        return try_via_centers;
    }
}
