/*
 *   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.
 *
 * AutorouteSettings.java
 *
 * Created on 27. Juli 2006, 09:16
 *
 */
package eu.mihosoft.freerouting.interactive;

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

/**
 * Contains the interactive settings for the autorouter.
 *
 * @author Alfons Wirtz
 */
public class AutorouteSettings implements java.io.Serializable
{

    /** Creates a new instance of AutorouteSettings */
    public AutorouteSettings(int p_layer_count)
    {
        layer_active_arr = new boolean[p_layer_count];
        preferred_direction_is_horizontal_arr = new boolean[p_layer_count];
        preferred_direction_trace_cost_arr = new double[p_layer_count];
        against_preferred_direction_trace_cost_arr = new double[p_layer_count];
    }

    /** Creates a new instance of AutorouteSettings */
    public AutorouteSettings(RoutingBoard p_board)
    {
        this(p_board.get_layer_count());

        // set default values

        start_ripup_costs = 100;
        start_pass_no = 1;
        vias_allowed = true;
        with_fanout = false;
        with_autoroute = true;
        with_postroute = true;
        via_costs = 50;
        plane_via_costs = 5;

        double horizontal_width = p_board.bounding_box.width();
        double vertical_width = p_board.bounding_box.height();

        int layer_count = p_board.get_layer_count();

        // additional costs against  preferred direction with 1 digit behind the decimal point.
        double horizontal_add_costs_against_preferred_dir = 0.1 * Math.round(10 * horizontal_width / vertical_width);

        double vertical_add_costs_against_preferred_dir = 0.1 * Math.round(10 * vertical_width / horizontal_width);

        // make more horizontal preferred direction, if the board is horizontal.

        boolean curr_preferred_direction_is_horizontal = horizontal_width < vertical_width;
        for (int i = 0; i < layer_count; ++i)
        {
            layer_active_arr[i] = p_board.layer_structure.arr[i].is_signal;
            if (p_board.layer_structure.arr[i].is_signal)
            {
                curr_preferred_direction_is_horizontal = !curr_preferred_direction_is_horizontal;
            }
            preferred_direction_is_horizontal_arr[i] = curr_preferred_direction_is_horizontal;
            preferred_direction_trace_cost_arr[i] = 1;
            against_preferred_direction_trace_cost_arr[i] = 1;
            if (curr_preferred_direction_is_horizontal)
            {
                against_preferred_direction_trace_cost_arr[i] += horizontal_add_costs_against_preferred_dir;
            }
            else
            {
                against_preferred_direction_trace_cost_arr[i] += vertical_add_costs_against_preferred_dir;
            }
        }
        int signal_layer_count = p_board.layer_structure.signal_layer_count();
        if (signal_layer_count > 2)
        {
            double outer_add_costs = 0.2 * signal_layer_count;
            // increase costs on the outer layers.
            preferred_direction_trace_cost_arr[0] += outer_add_costs;
            preferred_direction_trace_cost_arr[layer_count - 1] += outer_add_costs;
            against_preferred_direction_trace_cost_arr[0] += outer_add_costs;
            against_preferred_direction_trace_cost_arr[layer_count - 1] += outer_add_costs;
        }
    }

    /**
     * Copy constructor
     */
    public AutorouteSettings(AutorouteSettings p_settings)
    {
        start_ripup_costs = p_settings.start_ripup_costs;
        start_pass_no = p_settings.start_pass_no;
        via_costs = p_settings.via_costs;
        plane_via_costs = p_settings.plane_via_costs;
        layer_active_arr = new boolean[p_settings.layer_active_arr.length];
        System.arraycopy(p_settings.layer_active_arr, 0, this.layer_active_arr, 0, layer_active_arr.length);
        preferred_direction_is_horizontal_arr = new boolean[p_settings.preferred_direction_is_horizontal_arr.length];
        System.arraycopy(p_settings.preferred_direction_is_horizontal_arr, 0, this.preferred_direction_is_horizontal_arr, 0,
                preferred_direction_is_horizontal_arr.length);
        preferred_direction_trace_cost_arr = new double[p_settings.preferred_direction_trace_cost_arr.length];
        System.arraycopy(p_settings.preferred_direction_trace_cost_arr, 0, preferred_direction_trace_cost_arr, 0,
                preferred_direction_trace_cost_arr.length);
        against_preferred_direction_trace_cost_arr = new double[p_settings.against_preferred_direction_trace_cost_arr.length];
        System.arraycopy(p_settings.against_preferred_direction_trace_cost_arr, 0, against_preferred_direction_trace_cost_arr, 0,
                against_preferred_direction_trace_cost_arr.length);
    }

    public void set_start_ripup_costs(int p_value)
    {
        start_ripup_costs = Math.max(p_value, 1);
    }

    public int get_start_ripup_costs()
    {
        return start_ripup_costs;
    }

    public void set_start_pass_no(int p_value)
    {
        start_pass_no = Math.max(p_value, 1);
        start_pass_no = Math.min(start_pass_no, 99999);
    }

    public int get_start_pass_no()
    {
        return start_pass_no;
    }

    public void set_stop_pass_no(int p_value)
    {
        stop_pass_no = Math.max(p_value, start_pass_no);
        stop_pass_no = Math.min(stop_pass_no, 99999);
    }

    public int get_stop_pass_no()
    {
        return stop_pass_no;
    }

    public void increment_pass_no()
    {
        ++start_pass_no;
    }

    public void set_with_fanout(boolean p_value)
    {
        with_fanout = p_value;
    }

    public boolean get_with_fanout()
    {
        return with_fanout;
    }

    public void set_with_autoroute(boolean p_value)
    {
        with_autoroute = p_value;
    }

    public boolean get_with_autoroute()
    {
        return with_autoroute;
    }

    public void set_with_postroute(boolean p_value)
    {
        with_postroute = p_value;
    }

    public boolean get_with_postroute()
    {
        return with_postroute;
    }

    public void set_vias_allowed(boolean p_value)
    {
        vias_allowed = p_value;
    }

    public boolean get_vias_allowed()
    {
        return vias_allowed;
    }

    public void set_via_costs(int p_value)
    {
        via_costs = Math.max(p_value, 1);
    }

    public int get_via_costs()
    {
        return via_costs;
    }

    public void set_plane_via_costs(int p_value)
    {
        plane_via_costs = Math.max(p_value, 1);
    }

    public int get_plane_via_costs()
    {
        return plane_via_costs;
    }

    public void set_layer_active(int p_layer, boolean p_value)
    {
        if (p_layer < 0 || p_layer >= layer_active_arr.length)
        {
            FRLogger.warn("AutorouteSettings.set_layer_active: p_layer out of range");
            return;
        }
        layer_active_arr[p_layer] = p_value;
    }

    public boolean get_layer_active(int p_layer)
    {
        if (p_layer < 0 || p_layer >= layer_active_arr.length)
        {
            FRLogger.warn("AutorouteSettings.get_layer_active: p_layer out of range");
            return false;
        }
        return layer_active_arr[p_layer];
    }

    public void set_preferred_direction_is_horizontal(int p_layer, boolean p_value)
    {
        if (p_layer < 0 || p_layer >= layer_active_arr.length)
        {
            FRLogger.warn("AutorouteSettings.set_preferred_direction_is_horizontal: p_layer out of range");
            return;
        }
        preferred_direction_is_horizontal_arr[p_layer] = p_value;
    }

    public boolean get_preferred_direction_is_horizontal(int p_layer)
    {
        if (p_layer < 0 || p_layer >= layer_active_arr.length)
        {
            FRLogger.warn("AutorouteSettings.get_preferred_direction_is_horizontal: p_layer out of range");
            return false;
        }
        return preferred_direction_is_horizontal_arr[p_layer];
    }

    public void set_preferred_direction_trace_costs(int p_layer, double p_value)
    {
        if (p_layer < 0 || p_layer >= layer_active_arr.length)
        {
            FRLogger.warn("AutorouteSettings.set_preferred_direction_trace_costs: p_layer out of range");
            return;
        }
        preferred_direction_trace_cost_arr[p_layer] = Math.max(p_value, 0.1);
    }

    public double get_preferred_direction_trace_costs(int p_layer)
    {
        if (p_layer < 0 || p_layer >= layer_active_arr.length)
        {
            FRLogger.warn("AutorouteSettings.get_preferred_direction_trace_costs: p_layer out of range");
            return 0;
        }
        return preferred_direction_trace_cost_arr[p_layer];
    }

    public double get_against_preferred_direction_trace_costs(int p_layer)
    {
        if (p_layer < 0 || p_layer >= layer_active_arr.length)
        {
            FRLogger.warn("AutorouteSettings.get_against_preferred_direction_trace_costs: p_layer out of range");
            return 0;
        }
        return against_preferred_direction_trace_cost_arr[p_layer];
    }

    public double get_horizontal_trace_costs(int p_layer)
    {
        if (p_layer < 0 || p_layer >= layer_active_arr.length)
        {
            FRLogger.warn("AutorouteSettings.get_preferred_direction_trace_costs: p_layer out of range");
            return 0;
        }
        double result;
        if (preferred_direction_is_horizontal_arr[p_layer])
        {
            result = preferred_direction_trace_cost_arr[p_layer];
        }
        else
        {
            result = against_preferred_direction_trace_cost_arr[p_layer];
        }
        return result;
    }

    public void set_against_preferred_direction_trace_costs(int p_layer, double p_value)
    {
        if (p_layer < 0 || p_layer >= layer_active_arr.length)
        {
            FRLogger.warn("AutorouteSettings.set_against_preferred_direction_trace_costs: p_layer out of range");
            return;
        }
        against_preferred_direction_trace_cost_arr[p_layer] = Math.max(p_value, 0.1);
    }

    public double get_vertical_trace_costs(int p_layer)
    {
        if (p_layer < 0 || p_layer >= layer_active_arr.length)
        {
            FRLogger.warn("AutorouteSettings.get_against_preferred_direction_trace_costs: p_layer out of range");
            return 0;
        }
        double result;
        if (preferred_direction_is_horizontal_arr[p_layer])
        {
            result = against_preferred_direction_trace_cost_arr[p_layer];
        }
        else
        {
            result = preferred_direction_trace_cost_arr[p_layer];
        }
        return result;
    }

    public ExpansionCostFactor[] get_trace_cost_arr()
    {
        ExpansionCostFactor[] result = new ExpansionCostFactor[preferred_direction_trace_cost_arr.length];
        for (int i = 0; i < result.length; ++i)
        {
            result[i] = new ExpansionCostFactor(get_horizontal_trace_costs(i), get_vertical_trace_costs(i));
        }
        return result;
    }
    
    private boolean with_fanout;
    private boolean with_autoroute;
    private boolean with_postroute;
    private boolean vias_allowed;
    private int via_costs;
    private int plane_via_costs;
    private int start_ripup_costs;
    private int start_pass_no;
    private int stop_pass_no;
    private final boolean[] layer_active_arr;
    private final boolean[] preferred_direction_is_horizontal_arr;
    private final double[] preferred_direction_trace_cost_arr;
    private final double[] against_preferred_direction_trace_cost_arr;
}
