/*
 *   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.
 *
 * AutorouteControl.java
 *
 * Created on 25. Januar 2004, 09:38
 */
package eu.mihosoft.freerouting.autoroute;

import eu.mihosoft.freerouting.geometry.planar.ConvexShape;

import eu.mihosoft.freerouting.rules.ViaInfo;
import eu.mihosoft.freerouting.rules.ViaRule;

import eu.mihosoft.freerouting.board.RoutingBoard;

/**
 * Structure for controlling the autoroute algorithm.
 *
 * @author Alfons Wirtz
 */
public class AutorouteControl
{

    /** Creates a new instance of AutorouteControl for the input net */
    public AutorouteControl(RoutingBoard p_board, int p_net_no, eu.mihosoft.freerouting.interactive.Settings p_settings)
    {
        this(p_board, p_settings, p_settings.autoroute_settings.get_trace_cost_arr());
        init_net(p_net_no, p_board, p_settings.autoroute_settings.get_via_costs());
    }

    /** Creates a new instance of AutorouteControl for the input net */
    public AutorouteControl(RoutingBoard p_board, int p_net_no, eu.mihosoft.freerouting.interactive.Settings p_settings, int p_via_costs, ExpansionCostFactor[] p_trace_cost_arr)
    {
        this(p_board, p_settings, p_trace_cost_arr);
        init_net(p_net_no, p_board, p_via_costs);
    }

    /** Creates a new instance of AutorouteControl */
    private AutorouteControl(RoutingBoard p_board, eu.mihosoft.freerouting.interactive.Settings p_settings,
                             ExpansionCostFactor[] p_trace_costs_arr)
    {
        layer_count = p_board.get_layer_count();
        trace_half_width = new int[layer_count];
        compensated_trace_half_width = new int[layer_count];
        layer_active = new boolean[layer_count];
        vias_allowed = p_settings.autoroute_settings.get_vias_allowed();
        via_radius_arr = new double[layer_count];
        add_via_costs = new ViaCost[layer_count];

        for (int i = 0; i < layer_count; ++i)
        {
            add_via_costs[i] = new ViaCost(layer_count);
            layer_active[i] = p_settings.autoroute_settings.get_layer_active(i);
        }
        is_fanout = false;
        remove_unconnected_vias = true;
        with_neckdown = p_settings.get_automatic_neckdown();
        tidy_region_width = Integer.MAX_VALUE;
        pull_tight_accuracy = 500;
        max_shove_trace_recursion_depth = 20;
        max_shove_via_recursion_depth = 5;
        max_spring_over_recursion_depth = 5;
        for (int i = 0; i < layer_count; ++i)
        {
            for (int j = 0; j < layer_count; ++j)
            {
                add_via_costs[i].to_layer[j] = 0;
            }
        }
        trace_costs = p_trace_costs_arr;
        attach_smd_allowed = false;
        via_lower_bound = 0;
        via_upper_bound = layer_count;

        ripup_allowed = false;
        ripup_costs = 1000;
        ripup_pass_no = 1;
    }

    private void init_net(int p_net_no, RoutingBoard p_board, int p_via_costs)
    {
        net_no = p_net_no;
        eu.mihosoft.freerouting.rules.Net curr_net = p_board.rules.nets.get(p_net_no);
        eu.mihosoft.freerouting.rules.NetClass curr_net_class;
        if (curr_net != null)
        {
            curr_net_class = curr_net.get_class();
            trace_clearance_class_no = curr_net_class.get_trace_clearance_class();
            via_rule = curr_net_class.get_via_rule();
        }
        else
        {
            trace_clearance_class_no = 1;
            via_rule = p_board.rules.via_rules.firstElement();
            curr_net_class = null;
        }
        for (int i = 0; i < layer_count; ++i)
        {
            if (net_no > 0)
            {
                trace_half_width[i] = p_board.rules.get_trace_half_width(net_no, i);
            }
            else
            {
                trace_half_width[i] = p_board.rules.get_trace_half_width(1, i);
            }
            compensated_trace_half_width[i] = trace_half_width[i] + p_board.rules.clearance_matrix.clearance_compensation_value(trace_clearance_class_no, i);
            if (curr_net_class != null && !curr_net_class.is_active_routing_layer(i))
            {
                layer_active[i] = false;
            }
        }
        if (via_rule.via_count() > 0)
        {
            this.via_clearance_class = via_rule.get_via(0).get_clearance_class();
        }
        else
        {
            this.via_clearance_class = 1;
        }
        this.via_info_arr = new ViaMask[via_rule.via_count()];
        for (int i = 0; i < via_rule.via_count(); ++i)
        {
            ViaInfo curr_via = via_rule.get_via(i);
            if (curr_via.attach_smd_allowed())
            {
                this.attach_smd_allowed = true;
            }
            eu.mihosoft.freerouting.library.Padstack curr_via_padstack = curr_via.get_padstack();
            int from_layer = curr_via_padstack.from_layer();
            int to_layer = curr_via_padstack.to_layer();
            for (int j = from_layer; j <= to_layer; ++j)
            {
                ConvexShape curr_shape = curr_via_padstack.get_shape(j);
                double curr_radius;
                if( curr_shape != null)
                {
                   curr_radius = 0.5 * curr_shape.max_width();
                }
                else
                {
                    curr_radius = 0;
                }
                this.via_radius_arr[j] = Math.max(this.via_radius_arr[j], curr_radius);
            }
            via_info_arr[i] = new ViaMask(from_layer, to_layer, curr_via.attach_smd_allowed());
        }
        for (int j = 0; j < this.layer_count; ++j)
        {
            this.via_radius_arr[j] = Math.max(this.via_radius_arr[j], trace_half_width[j]);
            this.max_via_radius = Math.max(this.max_via_radius, this.via_radius_arr[j]);
        }
        double via_cost_factor = this.max_via_radius;
        via_cost_factor = Math.max(via_cost_factor, 1);
        min_normal_via_cost = p_via_costs * via_cost_factor;
        min_cheap_via_cost = 0.8 * min_normal_via_cost;
    }
    final int layer_count;
    /** The horizontal and vertical trace costs on each layer */
    public final ExpansionCostFactor[] trace_costs;
    /** Defines for each layer, if it may used for routing. */
    final boolean[] layer_active;
    /** The currently used net number in the autoroute algorithm */
    int net_no;
    /** The currently used trace half widths in the autoroute algorithm on each layer */
    final int[] trace_half_width;
    /**
     * The currently used compensated trace half widths in the autoroute algorithm on each layer.
     * Equal to trace_half_width if no clearance compensation is used.
     */
    final int[] compensated_trace_half_width;
    /** The currently used clearance class for traces in the autoroute algorithm */
    public int trace_clearance_class_no;
    /** The currently used clearance class for vias in the autoroute algorithm */
    int via_clearance_class;
    /** The possible (partial) vias, which can be used by the autorouter */
    ViaRule via_rule;
    /** The array of possible via ranges used bei the autorouter */
    ViaMask[] via_info_arr;
    /** The lower bound for the first layer of vias */
    int via_lower_bound;
    /** The upper bound for the last layer of vias */
    int via_upper_bound;
    final double[] via_radius_arr;
    double max_via_radius;
    /** The width of the region around changed traces, where traces are pulled tight */
    int tidy_region_width;
    /** The pull tight accuracy of traces*/
    int pull_tight_accuracy;
    /** The maximum recursion depth for shoving traces */
    int max_shove_trace_recursion_depth;
    /** The maximum recursion depth for shoving obstacles */
    int max_shove_via_recursion_depth;
    /** The maximum recursion depth for traces springing over obstacles */
    int max_spring_over_recursion_depth;
    /** True, if layer change by inserting of vias is allowed */
    public boolean vias_allowed;
    /** True, if vias may drill to the pad of SMD pins */
    public boolean attach_smd_allowed;
    /** the additiomal costs to min_normal via_cost for inserting a via between 2 layers */
    final ViaCost[] add_via_costs;
    /** The minimum cost valua of all normal vias */
    public double min_normal_via_cost;
    /** The minimal cost value of all cheap vias */
    double min_cheap_via_cost;
    public boolean ripup_allowed;
    public int ripup_costs;
    public int ripup_pass_no;
    public final boolean with_neckdown;
    /** If true, the autoroute algorithm completes after the first drill */
    public boolean is_fanout;
    /**
     *  Normally true, if the autorouter contains no fanout pass
     */
    public boolean remove_unconnected_vias;

    /** horizontal and vertical costs for traces on a board layer */
    public static class ExpansionCostFactor
    {

        public ExpansionCostFactor(double p_horizontal, double p_vertical)
        {
            horizontal = p_horizontal;
            vertical = p_vertical;
        }
        /** The horizontal expansion cost factor on a layer of the board */
        public final double horizontal;
        /** The verical expansion cost factor on a layer of the board */
        public final double vertical;
    }

    /** Array of via costs from one layer to the other layers */
    static class ViaCost
    {

        private ViaCost(int p_layer_count)
        {
            to_layer = new int[p_layer_count];
        }
        public int[] to_layer;
    }

    static class ViaMask
    {

        ViaMask(int p_from_layer, int p_to_layer, boolean p_attach_smd_allowed)
        {
            from_layer = p_from_layer;
            to_layer = p_to_layer;
            attach_smd_allowed = p_attach_smd_allowed;
        }
        final int from_layer;
        final int to_layer;
        final boolean attach_smd_allowed;
    }
}
