package info.ata4.util.gui;

import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

public class ListTableModel extends RowTableModel<List> {

    /**
     * Initialize the List with null values. This will set the size of the List
     * and will prevent an IndexOutOfBoundsException when trying to access an
     * Element in the List.
     *
     * @param size the number of Elements to add to the List
     *
     */
    protected static List<String> newList(int size) {
        ArrayList<String> list = new ArrayList<>(size);

        for (int i = 0; i < size; i++) {
            list.add(null);
        }

        return list;
    }

    /**
     * Constructs an empty
     * <code>ListTableModel</code> with default column names for the specified
     * number of
     * <code>columns</code>.
     *
     * @param columns the number of columns the table holds
     */
    public ListTableModel(int columns) {
        super(newList(columns));
        setRowClass(List.class);
    }

    /**
     * Constructs an empty
     * <code>ListTableModel</code> with customized column names. The number of
     * columns is determined bye the number of items in the
     * <code>columnNames</code> List.
     *
     * @param columnNames
     * <code>List</code> containing the names of the new columns
     */
    public ListTableModel(List<String> columnNames) {
        super(columnNames);
        setRowClass(List.class);
    }

    /**
     * Constructs a
     * <code>ListTableModel</code> with the specified number of rows. Default
     * column names will be used for the specified number of
     * <code>columns</code>.
     *
     * @param rows  the number of initially empty rows to create
     * @param columns the number of columns the table holds
     */
    public ListTableModel(int rows, int columns) {
        super(newList(columns));
        setRowClass(List.class);

        List<List> data = new ArrayList<>(rows);

        for (int i = 0; i < rows; i++) {
            data.add(new ArrayList(columns));
        }

        insertRows(0, data);
    }

    /**
     * Constructs a
     * <code>ListTableModel</code> with initial data and customized column
     * names.
     *
     * Each item in the
     * <code>modelData</code> List must also be a List Object containing items
     * for each column of the row.
     *
     * Each column's name will be taken from the
     * <code>columnNames</code> List and the number of colums is determined by
     * thenumber of items in the
     * <code>columnNames</code> List.
     *
     * @param modelData the data of the table
     * @param columnNames
     * <code>List</code> containing the names of the new columns
     */
    public ListTableModel(List<List> modelData, List<String> columnNames) {
        super(modelData, columnNames);
        setRowClass(List.class);
    }
//
//  Implement the TableModel interface
//

    /**
     * Returns an attribute value for the cell at
     * <code>row</code> and
     * <code>column</code>.
     *
     * @param row   the row whose value is to be queried
     * @param column    the column whose value is to be queried
     * @return  the value Object at the specified cell
     * @exception IndexOutOfBoundsException if an invalid row or column was
     * given
     */
    @Override
    public Object getValueAt(int row, int column) {
        List rowData = getRow(row);
        return rowData.get(column);
    }

    /**
     * Sets the object value for the cell at
     * <code>column</code> and
     * <code>row</code>.
     * <code>value</code> is the new value. This method will generate a
     * <code>tableChanged</code> notification.
     *
     * @param value the new value; this can be null
     * @param row   the row whose value is to be changed
     * @param column    the column whose value is to be changed
     * @exception IndexOutOfBoundsException if an invalid row or column was
     * given
     */
    @SuppressWarnings("unchecked")
    @Override
    public void setValueAt(Object value, int row, int column) {
        List rowData = getRow(row);
        rowData.set(column, value);
        fireTableCellUpdated(row, column);
    }

    /**
     * Insert a row of data at the
     * <code>row</code> location in the model. Notification of the row being
     * added will be generated.
     *
     * @param row   row in the model where the data will be inserted
     * @param rowData data of the row being added
     */
    @Override
    public void insertRow(int row, List rowData) {
        justifyRow(rowData);
        super.insertRow(row, rowData);
    }

    /**
     * Insert multiple rows of data at the
     * <code>row</code> location in the model. Notification of the row being
     * added will be generated.
     *
     * @param row   row in the model where the data will be inserted
     * @param rowList each item in the list is a separate row of data
     */
    @Override
    public void insertRows(int row, List<List> rowList) {
        for (List rowData : rowList) {
            justifyRow(rowData);
        }

        super.insertRows(row, rowList);
    }

    /*
     * Make sure each List row contains the required number of columns.
     */
    @SuppressWarnings("unchecked")
    private void justifyRow(List rowData) {
        for (int i = rowData.size(); i < getColumnCount(); i++) {
            rowData.add(null);
        }
    }

    /**
     * Adds a row of data to the end of the model. Notification of the row being
     * added will be generated.
     *
     * @param rowData   data of the row being added
     */
    public void addRow(Object[] rowData) {
        insertRow(getRowCount(), rowData);
    }

    /**
     * Insert a row of data at the
     * <code>row</code> location in the model. Notification of the row being
     * added will be generated.
     *
     * @param row   row in the model where the data will be inserted
     * @param rowData data of the row being added
     */
    public void insertRow(int row, Object[] rowData) {
        insertRow(row, copyToList(rowData));
    }

    /**
     * Insert multiple rows of data at the
     * <code>row</code> location in the model. Notification of the row being
     * added will be generated.
     *
     * @param row   row in the model where the data will be inserted
     * @param rowArray each item in the Array is a separate row of data
     */
    public void insertRows(int row, Object[][] rowArray) {
        List<List> data = new ArrayList<>(rowArray.length);

        for (int i = 0; i < rowArray.length; i++) {
            data.add(copyToList(rowArray[i]));
        }

        insertRows(row, data);
    }

    /*
     * Copy the information in the Array to a List so a List can be added to the
     * model
     */
    private List copyToList(Object[] rowData) {
        List<Object> row = new ArrayList<>(rowData.length);
        row.addAll(Arrays.asList(rowData));
        return row;
    }

    /**
     * Create a ListTableModel given a specific ResultSet.
     *
     * The column names and class type will be retrieved from the
     * ResultSetMetaData. The data is retrieved from each row found in the
     * ResultSet. The class of
     *
     * @param resultSet ResultSet containing results of a database query
     * @return  a newly created ListTableModel
     * @exception SQLException when an SQL error is encountered
     */
    public static ListTableModel createModelFromResultSet(ResultSet resultSet)
            throws SQLException {
        ResultSetMetaData metaData = resultSet.getMetaData();
        int columns = metaData.getColumnCount();

        //  Create empty model using the column names

        ArrayList<String> columnNames = new ArrayList<>();

        for (int i = 1; i <= columns; i++) {
            String columnName = metaData.getColumnName(i);
            String columnLabel = metaData.getColumnLabel(i);

            if (columnLabel.equals(columnName)) {
                columnNames.add(formatColumnName(columnName));
            } else {
                columnNames.add(columnLabel);
            }
        }

        ListTableModel model = new ListTableModel(columnNames);
        model.setModelEditable(false);

        //  Assign the class of each column

        for (int i = 1; i <= columns; i++) {
            try {
                String className = metaData.getColumnClassName(i);
                model.setColumnClass(i - 1, Class.forName(className));
            } catch (Exception exception) {
            }
        }

        //  Get row data

        ArrayList<List> data = new ArrayList<>();

        while (resultSet.next()) {
            ArrayList<Object> row = new ArrayList<>(columns);

            for (int i = 1; i <= columns; i++) {
                Object o = resultSet.getObject(i);
                row.add(o);
            }

            data.add(row);
        }

        model.insertRows(0, data);

        return model;
    }
}
