/*
 * To change this license header, choose License Headers in Project Properties.
 * To change this template file, choose Tools | Templates
 * and open the template in the editor.
 */
package br.com.ctecinf.table;

import br.com.ctecinf.orm.Column;
import br.com.ctecinf.orm.Model;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import javax.swing.table.AbstractTableModel;

/**
 *
 * @author Cássio Conceição
 * @param <T>
 * @since 24/09/2019
 * @version 1909
 * @see http://ctecinf.com.br/
 */
public class TableModel<T> extends AbstractTableModel {

    protected List<br.com.ctecinf.table.TableColumn> columns;
    protected List data;

    public TableModel() {
        this.data = Collections.synchronizedList(new ArrayList());
        this.columns = Collections.synchronizedList(new ArrayList());
    }

    public TableModel(List<br.com.ctecinf.table.TableColumn> columns) {
        this.data = Collections.synchronizedList(new ArrayList());
        this.columns = Collections.synchronizedList(new ArrayList(columns));
    }

    public TableModel(List<br.com.ctecinf.table.TableColumn> columns, List<T> data) {
        this.data = Collections.synchronizedList(new ArrayList(data));
        this.columns = Collections.synchronizedList(new ArrayList(columns));
    }

    public List<br.com.ctecinf.table.TableColumn> getColumns() {

        if (this.columns == null) {
            this.columns = Collections.synchronizedList(new ArrayList());
        }

        return this.columns;
    }

    public void setColumns(List<br.com.ctecinf.table.TableColumn> columns) {
        this.columns = columns == null ? Collections.synchronizedList(new ArrayList()) : Collections.synchronizedList(new ArrayList(columns));
        fireTableStructureChanged();
    }

    public List<T> getData() {

        if (this.data == null) {
            this.data = Collections.synchronizedList(new ArrayList());
        }

        return this.data;
    }

    public void setData(List<T> data) {
        this.data = data == null ? Collections.synchronizedList(new ArrayList()) : Collections.synchronizedList(new ArrayList(data));
        fireTableDataChanged();
    }

    public void addColumn(String name) {
        int index = getColumnCount();
        getColumns().add(new TableColumn(index, name));
    }

    public void addColumn(String name, String label) {
        int index = getColumnCount();
        getColumns().add(new TableColumn(index, name, label));
    }

    public void addColumn(String name, Class<?> type) {
        int index = getColumnCount();
        getColumns().add(new TableColumn(index, name, name, type));
    }

    public void addColumn(String name, String label, Class<?> type) {
        int index = getColumnCount();
        getColumns().add(new TableColumn(index, name, label, type));
    }

    public void addColumn(String name, String label, Class<?> type, int width) {
        int index = getColumnCount();
        getColumns().add(new TableColumn(index, name, label, type, width));
    }

    public void addRow(T row) {

        int index = getRowCount();
        getData().add(row);

        if (row instanceof Object[]) {

            Object[] obj = (Object[]) row;

            for (int i = 0; i < obj.length; i++) {
                if (obj[i] != null && obj[i].toString().length() > getColumns().get(i).getWidth()) {
                    getColumns().get(i).setWidth(obj[i].toString().length());
                }
            }
        } else if (row instanceof Model) {

            try {

                for (Field field : row.getClass().getDeclaredFields()) {

                    if (field.isAnnotationPresent(Column.class) && field.getAnnotation(Column.class).tableDisplay()) {

                        field.setAccessible(true);
                        Object vl = field.get(row);

                        TableColumn tableColumn = null;

                        if (vl != null && tableColumn != null && vl.toString().length() > tableColumn.getWidth()) {
                            tableColumn.setWidth(vl.toString().length());
                        }
                    }
                }

            } catch (SecurityException | IllegalArgumentException | IllegalAccessException ex) {
            }
        }

        fireTableRowsInserted(index, index);
    }

    public void updateRow(int rowIndex, T row) {
        if (getRowCount() > 0 && rowIndex >= 0 && rowIndex < getRowCount()) {
            getData().set(rowIndex, row);
            fireTableRowsUpdated(rowIndex, rowIndex);
        }
    }

    public void removeRow(int rowIndex) {
        if (getRowCount() > 0 && rowIndex >= 0 && rowIndex < getRowCount() && getData().remove(getData().remove(rowIndex))) {
            fireTableRowsDeleted(rowIndex, rowIndex);
        }
    }

    public void removeAllRows() {
        if (getRowCount() > 0) {
            int size = getRowCount() - 1;
            getData().clear();
            fireTableRowsDeleted(0, size);
        }
    }

    @Override
    public int getRowCount() {
        return this.data == null ? 0 : this.data.size();
    }

    @Override
    public int getColumnCount() {
        return this.columns == null ? 0 : this.columns.size();
    }

    @Override
    public Class<?> getColumnClass(int columnIndex) {
        return getColumns() == null ? Object.class : getColumns().get(columnIndex).getType();
    }

    @Override
    public String getColumnName(int column) {
        return getColumns() == null ? "" : getColumns().get(column).getLabel();
    }

    @Override
    public Object getValueAt(int rowIndex, int columnIndex) {

        if (getRowCount() > 0 && rowIndex >= 0 && rowIndex < getRowCount()) {

            Object obj = getData().get(rowIndex);

            if (obj instanceof Object[]) {

                Object[] row = (Object[]) obj;

                return row[columnIndex];

            } else if (obj instanceof Model) {

                if (columnIndex == -1) {
                    return obj;
                }

                try {

                    Field field;

                    if (getColumns().get(columnIndex) != null) {

                        if (columnIndex == 0) {
                            field = obj.getClass().getSuperclass().getDeclaredField(getColumns().get(columnIndex).getName());
                        } else {
                            field = obj.getClass().getDeclaredField(getColumns().get(columnIndex).getName());
                        }

                        if (field != null) {
                            field.setAccessible(true);
                            return field.get(obj);
                        }
                    }

                    return null;

                } catch (NoSuchFieldException | SecurityException | IllegalArgumentException | IllegalAccessException ex) {
                    return null;
                }
            }
        }

        return null;
    }

    @Override
    public void setValueAt(Object aValue, int rowIndex, int columnIndex) {

        if (getRowCount() > 0 && rowIndex >= 0 && rowIndex < getRowCount()) {

            Object obj = getData().get(rowIndex);

            if (obj == null) {
                return;
            }

            if (obj instanceof Object[]) {
                ((Object[]) obj)[columnIndex] = aValue;
            } else if (obj instanceof Model) {

                try {
                    Field field = obj.getClass().getDeclaredField(getColumns().get(columnIndex).getName());
                    field.setAccessible(true);
                    field.set(obj, aValue);
                } catch (NoSuchFieldException | SecurityException | IllegalArgumentException | IllegalAccessException ex) {
                }
            }

            fireTableRowsUpdated(rowIndex, rowIndex);
        }
    }
}
