/* -*- tab-width: 4 -*-
 *
 * Electric(tm) VLSI Design System
 *
 * File: Table2D.java
 * Written by Jonathan Gainsley, Sun Microsystems.
 *
 * Copyright (c) 2009, Oracle and/or its affiliates. All rights reserved.
 *
 * Electric(tm) 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.
 *
 * Electric(tm) 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 for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
package com.sun.electric.tool.simulation.sctiming;

import java.util.Map;
import java.util.HashMap;

/**
 * Table2D stores data in a more intiuitive fashion than TableData for results
 * that are a function of two parameters being swept in the spice run.
 * The row and col indices are these two parameters. Because there
 * are several other measures, each 2D-array of data is assigned a key,
 * which is the name of the measure.  Thus a Table2D is really a 3D table,
 * but it is represented as a set of 2D tables.
 * <P>
 * Note that this table converts all keys to lower case to deal with
 * Spice, which converts everything to lower case.
 * User: gainsley
 * Date: Nov 16, 2006
 */
public class Table2D {

    private double [] rowVals;
    private double [] colVals;
    private String rowName;
    private String colName;
    private Map<String,double [][]> valuesMap;

    /**
     * Create a new Table2D.
     * Note: size is fixed once created.
     * @param rowVals row index values
     * @param rowName name of the row index param
     * @param colVals column index values
     * @param colName name of the column index param
     */
    public Table2D(double [] rowVals, String rowName, double [] colVals, String colName) {
        this.rowVals = rowVals;
        this.colVals = colVals;
        this.rowName = rowName.toLowerCase();
        this.colName = colName.toLowerCase();
        //Arrays.sort(this.rowVals);
        //Arrays.sort(this.colVals);
        valuesMap = new HashMap<String,double[][]>();
    }

    /**
     * Get the name of the row header name
     * @return name of the row header
     */
    public String getRowName() { return rowName; }

    /**
     * Get the name of the column header name
     * @return name of the column header
     */
    public String getColName() { return colName; }

    /**
     * Get the number of rows in the table
     * @return number of rows in the table
     */
    public int getNumRows() { return rowVals.length; }

    /**
     * Get the number of columns in the table
     * @return number of columns in the table
     */
    public int getNumCols() { return colVals.length; }

    /**
     * Get the index values for the columns
     * @return index values for the columns
     */
    public double [] getColIndexVals() { return colVals; }

    /**
     * Get the index values for the rows
     * @return index values for the rows
     */
    public double [] getRowIndexVals() { return rowVals; }

    /**
     * Set the value for a given row and column value. Table2D can
     * hold multiple sets of values for the same row and column values,
     * so a key is used to access a particular set of 2D data.
     * @param rowVal the row index value
     * @param colVal the column index value
     * @param dataKey the key for the data set
     * @param value the value at the row and column value
     */
    public void setValue(double rowVal, double colVal, String dataKey, double value) {
        dataKey = dataKey.toLowerCase();
        double [][] values = valuesMap.get(dataKey);
        if (values == null) {
            values = new double[rowVals.length][colVals.length];
            valuesMap.put(dataKey, values);
        }

        int i1, i2;
        for (i1=0; i1<rowVals.length; i1++) {
            if (rowVals[i1] == rowVal) break;
        }
        if (i1 == rowVals.length) {
            System.out.println("Cannot find value "+rowVal +" in row values");
            return;
        }
        for (i2=0; i2<colVals.length; i2++) {
            if (colVals[i2] == colVal) break;
        }
        if (i2 == colVals.length) {
            System.out.println("Cannot find value "+colVal +" in column values");
            return;
        }

        values[i1][i2] = value;
    }

    /**
     * Get the value for a given row and column value. Table2D can
     * hold multiple sets of values for the same row and column values, so
     * a key is used to access a particular set of 2D data.
     * @param rowVal the row index value
     * @param colVal the column index value
     * @param dataKey the key for the data set
     * @return the value at the row and column value
     */
    public double getValue(double rowVal, double colVal, String dataKey) {
        dataKey = dataKey.toLowerCase();
        double [][] values = valuesMap.get(dataKey);
        if (values == null) {
            System.out.println("Data key "+dataKey+" not found");
            return Double.MIN_VALUE;
        }

        int i1, i2;
        for (i1=0; i1<rowVals.length; i1++) {
            if (rowVals[i1] == rowVal) break;
        }
        if (i1 == rowVals.length) {
            System.out.println("Cannot find value "+rowVal +" in row values");
            return Double.MIN_VALUE;
        }
        for (i2=0; i2<colVals.length; i2++) {
            if (colVals[i2] == colVal) break;
        }
        if (i2 == colVals.length) {
            System.out.println("Cannot find value "+colVal +" in column values");
            return Double.MIN_VALUE;
        }

        return values[i1][i2];
    }

    /**
     * Set the data for a given data key.  The Data must size rows x cols.
     * This will replace any previous data stored under that key.
     * @param dataKey the data key
     * @param data the data
     */
    public void setData(String dataKey, double [][] data) {
        dataKey = dataKey.toLowerCase();
        if (data.length != rowVals.length) return;
        for (int i=0; i<data.length; i++) {
            if (data[i].length != colVals.length) return;
        }
        valuesMap.put(dataKey, data);
    }

    /**
     * Get the values in a column for a specific set of data.
     * @param key denotes the set of data to use
     * @param colIndex which column to get data
     * @return an array of values, the 0th index being from row 0,
     * and the Nth index from row N.
     */
    public double [] getColumnValues(String key, int colIndex) {
        key = key.toLowerCase();
        double [][] values = valuesMap.get(key);
        if (values == null) return null;
        double [] col = new double[values.length];
        for (int i=0; i<values.length; i++) {
            col[i] = values[i][colIndex];
        }
        return col;
    }

    /**
     * Get the values in a row for a specific set of data.
     * @param key denotes the set of data to use
     * @param rowIndex which row to get
     * @return an array of values, the 0th index being from col 0,
     * and the Nth index from col N.
     */
    public double [] getRowValues(String key, int rowIndex) {
        key = key.toLowerCase();
        double [][] values = valuesMap.get(key);
        if (values == null) return null;
        return values[rowIndex];
    }

    /**
     * Get the average (mean) of a set of values
     * @param vals the values
     * @return the mean
     */
    public static double getAverage(double [] vals) {
        double total = 0;
        for (int i=0; i<vals.length; i++) {
            total += vals[i];
        }
        return total/vals.length;
    }

    /**
     * Get the standard deviation of a set of values
     * @param vals the values
     * @return the mean
     */
    public static double getStandardDeviation(double [] vals) {
        double avg = getAverage(vals);
        double total = 0;
        for (int i=0; i<vals.length; i++) {
            double diff = avg - vals[i];
            total += diff*diff;
        }
        return Math.sqrt(total/vals.length);
    }

    /**
     * Get the average (mean) of all values
     * @param vals the values
     * @return the mean
     */
    public static double getAverage(double [][] vals) {
        double total = 0;
        int count = 0;
        for (int i=0; i<vals.length; i++) {
            double [] row = vals[i];
            for (int j=0; j<row.length; j++) {
                total += row[j];
                count++;
            }
        }
        return total/count;
    }

    /**
     * Get the standard deviation of a set of values
     * @param vals the values
     * @return the mean
     */
    public static double getStandardDeviation(double [][] vals) {
        double avg = getAverage(vals);
        double total = 0;
        int count = 0;
        for (int i=0; i<vals.length; i++) {
            double [] row = vals[i];
            for (int j=0; j<row.length; j++) {
                double diff = avg - row[j];
                total += diff*diff;
                count++;
            }
        }
        return Math.sqrt(total/count);
    }

    /**
     * Get the average (mean) value for a particular column.
     * Returns zero if no such data for the key is found.
     * @param key the data key
     * @param colIndex the column index (not column value)
     * @return the mean
     */
    public double getAvgColumnValue(String key, int colIndex) {
        key = key.toLowerCase();
        double [][] values = valuesMap.get(key);
        if (values == null) return 0;
        double total = 0;
        for (int i=0; i<values.length; i++) {
            double [] row = values[i];
            total += row[colIndex];
        }
        return total/values.length;
    }

    /**
     * Get the average (mean) values for columns.
     * Returns zero in all entries if no such data for the key is found.
     * @param key the data key
     * @return the means
     */
    public double [] getAvgColumnValues(String key) {
        key = key.toLowerCase();
        double [] avgvals = new double[colVals.length];
        for (int i=0; i<colVals.length; i++) {
            avgvals[i] = getAvgColumnValue(key, i);
        }
        return avgvals;
    }

    /**
     * Get the average (mean) value for a particular row.
     * Returns zero if no such data for the key is found.
     * @param key the data key
     * @param rowIndex the row index (not row value)
     * @return the mean
     */
    public double getAvgRowValue(String key, int rowIndex) {
        key = key.toLowerCase();
        double [][] values = valuesMap.get(key);
        if (values == null) return 0;
        double [] row = values[rowIndex];
        return getAverage(row);
    }

    /**
     * Get the average (mean) values for rows.
     * Returns zero in all entries if no such data for the key is found.
     * @param key the data key
     * @return the means
     */
    public double [] getAvgRowValues(String key) {
        key = key.toLowerCase();
        double [] avgvals = new double[rowVals.length];
        for (int i=0; i<rowVals.length; i++) {
            avgvals[i] = getAvgRowValue(key, i);
        }
        return avgvals;
    }

    /**
     * Get values[rows][cols] for the specified key
     * @param key which data set
     * @return values array
     */
    public double [][] getValues(String key) {
        key = key.toLowerCase();
        return valuesMap.get(key);
    }

    /**
     * Print to System.out a nicely formatted ASCII table
     * of all data in this Table2D.
     */
    public void print() {
        for (String s : valuesMap.keySet())
            print(s);
    }

    /**
     * Print a nicely formatted ASCII table of the data
     * for the given key
     * @param key the key
     */
    public void print(String key) {
        key = key.toLowerCase();
        System.out.println("---------------------------------------------------");
        System.out.println(key+":");
        System.out.println("---------------------------------------------------");
        double [][] values = valuesMap.get(key);
        if (values == null) return;

        System.out.println("\t"+colName);
        System.out.println(rowName);
        System.out.print("\t");
        for (int i=0; i<colVals.length; i++) {
            System.out.print(colVals[i]+"\t");
        }
        System.out.println();
        for (int i=0; i<rowVals.length; i++) {
            System.out.print(rowVals[i]+"\t");
            double [] vals = values[i];
            for (int i2=0; i2<colVals.length; i2++) {
                System.out.print(vals[i2]+"\t");
            }
            System.out.println();
        }
        System.out.println();
    }

}
