/*
 *   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.
 */

package eu.mihosoft.freerouting.rules;

import eu.mihosoft.freerouting.logger.FRLogger;

/**
 *
 * NxN Matrix describing the spacing restrictions
 * between N clearance classes on a fixed set of layers.
 *
 *
 * @author Alfons Wirtz
 */

public class ClearanceMatrix implements java.io.Serializable
{
    /**
     * Creates a new instance with the 2 clearance classes "none"and "default"
     * ans initializes it with p_default_value.
     */
    public static ClearanceMatrix get_default_instance(eu.mihosoft.freerouting.board.LayerStructure p_layer_structure, int p_default_value)
    {
        String [] name_arr = new String [2];
        name_arr[0] = "null";
        name_arr[1] = "default";
        ClearanceMatrix result = new ClearanceMatrix(2, p_layer_structure, name_arr);
        result.set_default_value(p_default_value);
        return result;
    }
    
    /**
     * Creates a new instance for p_class_count clearance classes on
     * p_layer_count layers.
     * p_names is an array of dimension p_class_count;
     */
    public ClearanceMatrix(int p_class_count, eu.mihosoft.freerouting.board.LayerStructure p_layer_structure, String [] p_name_arr)
    {
        class_count = Math.max(p_class_count, 1);
        layer_structure = p_layer_structure;
        row = new Row [class_count];
        for (int i = 0; i < class_count; ++i)
        {
            row[i] = new Row(p_name_arr[i]);
        }
        this.max_value_on_layer = new int[layer_structure.arr.length];
    }
    
    /**
     * Returns the number of the clearance class with the input name,
     * or -1, if no such clearance class exists.
     */
    public int get_no(String p_name)
    {
        for (int i = 0; i < class_count; ++i)
        {
            if (row[i].name.compareToIgnoreCase(p_name) == 0)
            {
                return i;
            }
        }
        return -1;
    }
    
    /**
     * Gets the name of the clearance class with the input number.
     */
    public String get_name(int p_cl_class)
    {
        if (p_cl_class < 0 || p_cl_class >= row.length)
        {
            FRLogger.warn("CleatranceMatrix.get_name: p_cl_class out of range");
            return null;
        }
        return row[p_cl_class].name;
    }
    
    /**
     * Sets the value of all clearance classes with number {@literal >}= 1
     * to p_value on all layers.
     */
    public void set_default_value(int p_value)
    {
        for (int i = 0; i < layer_structure.arr.length; ++i)
        {
            set_default_value(i, p_value);
        }
    }
    
    /**
     * Sets the value of all clearance classes with number {@literal >}= 1
     * to p_value on p_layer.
     */
    public void set_default_value(int p_layer, int p_value)
    {
        for (int i = 1; i < class_count; ++i)
        {
            for (int j = 1; j < class_count; ++j)
            {
                
                set_value(i, j, p_layer,  p_value);
            }
        }
    }
    
    /**
     * Sets the value of an entry in the clearance matrix to p_value
     * on all layers.
     */
    public void set_value(int p_i, int p_j, int p_value)
    {
        for (int layer = 0; layer < layer_structure.arr.length; ++layer)
        {
            set_value(p_i, p_j, layer, p_value);
        }
    }
    
    /**
     * Sets the value of an entry in the clearance matrix to p_value
     * on all inner layers.
     */
    public void set_inner_value(int p_i, int p_j, int p_value)
    {
        for (int layer = 1; layer < layer_structure.arr.length - 1; ++layer)
        {
            set_value(p_i, p_j, layer, p_value);
        }
    }
    
    /**
     * Sets the value of an entry in the clearance matrix to p_value.
     */
    public void set_value(int p_i, int p_j, int p_layer, int p_value)
    {
        Row curr_row = row[p_j];
        MatrixEntry curr_entry = curr_row.column[p_i];
        // assure, that the clearance value is even
        int value = Math.max(p_value, 0);
        value += value % 2;
        curr_entry.layer[p_layer] = value;
        curr_row.max_value[p_layer] = Math.max(curr_row.max_value[p_layer], p_value);
        this.max_value_on_layer[p_layer] = Math.max(this.max_value_on_layer[p_layer], p_value);
    }
    
    /**
     * Cets the required spacing of clearance classes with index p_i and p_j
     * on p_layer. This value will be always an even integer.
     */
    public int value(int p_i, int p_j, int p_layer)
    {
        if (p_i < 0 || p_i >= class_count || p_j < 0 || p_j >= class_count
                || p_layer < 0 || p_layer >= layer_structure.arr.length)
        {
            return 0;
        }
        return row[p_j].column[p_i].layer[p_layer];
    }
    
    /**
     * Returns the maximal required spacing of clearance class with
     * index p_i to all other clearance classes on layer p_layer.
     */
    public int max_value(int p_i, int p_layer)
    {
        int i = Math.max(p_i, 0);
        i = Math.min(i, class_count - 1);
        int layer = Math.max(p_layer, 0);
        layer = Math.min(layer, layer_structure.arr.length - 1);
        return row[i].max_value[layer];
    }
    
    public int max_value(int p_layer)
    {
        int layer = Math.max(p_layer, 0);
        layer = Math.min(layer, layer_structure.arr.length - 1);
        return this.max_value_on_layer[layer];
    }
    
    /**
     * Returns true, if the values of the clearance matrix
     * in the p_i-th column and the p_j-th row are not equal on all layers.
     */
    public boolean is_layer_dependent(int p_i, int p_j)
    {
        int compare_value = row[p_j].column[p_i].layer[0];
        for (int l = 1; l < layer_structure.arr.length; ++l)
        {
            if (row[p_j].column[p_i].layer[l] != compare_value)
            {
                return true;
            }
        }
        return false;
    }
    
    /**
     * Returns true, if the values of the clearance matrix
     * in the p_i-th column and the p_j-th row are not equal on all inner layers.
     */
    public boolean is_inner_layer_dependent(int p_i, int p_j)
    {
        if (layer_structure.arr.length <= 2)
        {
            return false; // no inner layers
        }
        int compare_value = row[p_j].column[p_i].layer[1];
        for (int l = 2; l < layer_structure.arr.length - 1; ++l)
        {
            if (row[p_j].column[p_i].layer[l] != compare_value)
            {
                return true;
            }
        }
        return false;
    }
    
    /**
     * Returns the row with index p_no
     */
    public Row get_row(int p_no)
    {
        if (p_no < 0 || p_no >= this.row.length)
        {
            FRLogger.warn("ClearanceMatrix.get_row: p_no out of range");
            return null;
        }
        return this.row[p_no];
    }
    
    public int get_class_count()
    {
        return this.class_count;
    }
    
    /**
     * Return the layer count of this clearance matrix;#
     */
    public int get_layer_count()
    {
        return layer_structure.arr.length;
    }
    
    /**
     * Return the clearance compensation value of p_clearance_class_no on layer p_layer.
     */
    public int clearance_compensation_value(int p_clearance_class_no, int p_layer)
    {
        return (this.value(p_clearance_class_no,  p_clearance_class_no, p_layer) + 1)/ 2;
    }
    
    /**
     * Appends a new clearance class to the clearence matrix and
     * initializes it with the values of the default class.
     * Returns false, oif a clearance class with name p_class_name is already existing.
     */
    public boolean append_class(String p_class_name)
    {
        if (this.get_no(p_class_name) >= 0)
        {
            return false;
        }
        int old_class_count = this.class_count;
        ++this.class_count;
        
        Row [] new_row = new Row [this.class_count];
        
        // append a matrix entry to each old row
        for (int i = 0; i < old_class_count; ++i)
        {
            Row curr_old_row = this.row[i];
            new_row[i] = new Row(curr_old_row.name);
            Row curr_new_row = new_row[i];
            curr_new_row.max_value = curr_old_row.max_value;
            for (int j = 0; j < old_class_count; ++j)
            {
                curr_new_row.column[j] = curr_old_row.column[j];
            }
            
            curr_new_row.column[old_class_count] = new MatrixEntry();
        }
        
        // append the new row
        
        new_row [old_class_count] = new Row(p_class_name);
        
        this.row = new_row;
        
        // Set the new matrix elements to default values.
        
        for (int i = 0; i < old_class_count; ++i)
        {
            for (int j = 0; j < this.layer_structure.arr.length; ++j)
            {
                int default_value = this.value(1, i, j);
                this.set_value(old_class_count, i, j, default_value);
                this.set_value(i, old_class_count, j, default_value);
            }
        }
        
        for (int j = 0; j < this.layer_structure.arr.length; ++j)
        {
            int default_value = this.value(1, 1, j);
            this.set_value(old_class_count, old_class_count, j, default_value);
        }
        return true;
    }
    
    /**
     * Removes the class with index p_index from the clearance matrix.
     */
    void remove_class(int p_index)
    {
        int old_class_count = this.class_count;
        --this.class_count;
        
        Row [] new_row = new Row [this.class_count];
        
        // remove the  matrix entry with inded p_index in to each old row
        int new_row_index = 0;
        for (int i = 0; i < old_class_count; ++i)
        {
            if (i == p_index)
            {
                continue;
            }
            Row curr_old_row = this.row[i];
            new_row[new_row_index] = new Row(curr_old_row.name);
            Row curr_new_row = new_row[new_row_index];
            
            int new_column_index = 0;
            for (int j = 0; j < old_class_count; ++j)
            {
                if (j == p_index)
                {
                    continue;
                }
                curr_new_row.column[new_column_index] = curr_old_row.column[j];
                ++new_column_index;
            }
            ++new_row_index;
        }
        this.row = new_row;
    }
    
    /**
     * Returns true, if all clearance values of the class with index p_1 are equal to
     * the clearance values of index p_2.
     */
    public boolean is_equal(int p_1, int p_2)
    {
        if (p_1 == p_2)
        {
            return true;
        }
        if (p_1 < 0 || p_2 < 0 || p_1 >= this.class_count || p_2 >= this.class_count)
        {
            return false;
        }
        Row row_1 = this.row[p_1];
        Row row_2 = this.row[p_2];
        for (int i = 1; i < class_count; ++i)
        {
            if (!row_1.column[i].equals(row_2.column[i]))
            {
                return false;
            }
        }
        return true;
    }
    
    /**
     * count of clearance classes
     */
    private int class_count;
    
    private final eu.mihosoft.freerouting.board.LayerStructure layer_structure;
    private Row [] row; // vector of class_count rows of the clearance matrix
    private int [] max_value_on_layer; //  maximum clearance value for each layer
    
    
    /**
     * contains a row of entries of the clearance matrix
     */
    private class Row implements eu.mihosoft.freerouting.board.ObjectInfoPanel.Printable, java.io.Serializable
    {
        private Row(String p_name)
        {
            name = p_name;
            column = new MatrixEntry [class_count];
            for (int i = 0; i < class_count; ++i)
            {
                column[i] = new MatrixEntry();
            }
            max_value = new int[layer_structure.arr.length];
        }
        
        public void print_info(eu.mihosoft.freerouting.board.ObjectInfoPanel p_window, java.util.Locale p_locale)
        {
            java.util.ResourceBundle resources = 
                    java.util.ResourceBundle.getBundle("eu.mihosoft.freerouting.board.ObjectInfoPanel", p_locale);
            p_window.append_bold(resources.getString("spacing_from_clearance_class") + " ");
            p_window.append_bold(this.name);
            for (int i = 1; i < this.column.length; ++i)
            {
                p_window.newline();
                p_window.indent();
                p_window.append(" " + resources.getString("to_class") + " ");
                p_window.append(row[i].name);
                MatrixEntry curr_column = this.column[i];
                if (curr_column.is_layer_dependent())
                {
                    p_window.append(" " + resources.getString("on_layer") + " ");
                    for (int j = 0; j < layer_structure.arr.length; ++j)
                    {
                        p_window.newline();
                        p_window.indent();
                        p_window.indent();
                        p_window.append(layer_structure.arr[j].name);
                        p_window.append(" = ");
                        p_window.append(curr_column.layer[j]);
                    }
                }
                else
                {
                    p_window.append(" = ");
                    p_window.append(curr_column.layer[0]);
                }
            }
        }
        
        final String name;
        
        int [] max_value;
        final MatrixEntry [] column;
    }
    
    /**
     * a single entry of the clearance matrix
     */
    private class MatrixEntry implements java.io.Serializable
    {
        private MatrixEntry()
        {
            layer = new int [layer_structure.arr.length];
            for (int i = 0; i < layer_structure.arr.length; ++i)
            {
                layer[i] = 0;
            }
        }
        
        /**
         * Returns thrue of all clearances values of this and p_other are equal.
         */
        boolean equals(MatrixEntry p_other)
        {
            for (int i = 0; i < layer_structure.arr.length; ++i)
            {
                if (this.layer[i] != p_other.layer[i])
                {
                    return false;
                }
            }
            return true;
        }
        
        /**
         * Return true, if not all layer values are equal.
         */
        boolean is_layer_dependent()
        {
            int compare_value = layer[0];
            for (int i = 1; i < layer_structure.arr.length; ++i)
            {
                if (layer[i] != compare_value)
                {
                    return true;
                }
            }
            return false;
        }
        int [] layer;
    }
}