/*
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
*/
package jpa.tools.swing;

import java.awt.Color;
import java.awt.Component;
import java.awt.Dimension;
import java.util.List;

import jakarta.persistence.EntityManagerFactory;
import jakarta.persistence.PersistenceUnitUtil;
import jakarta.persistence.metamodel.Attribute;
import javax.swing.DefaultCellEditor;
import javax.swing.JComboBox;
import javax.swing.JTable;
import javax.swing.ListSelectionModel;
import javax.swing.SwingConstants;
import javax.swing.table.DefaultTableCellRenderer;
import javax.swing.table.TableCellEditor;
import javax.swing.table.TableCellRenderer;
import javax.swing.table.TableColumn;
import javax.swing.table.TableModel;

import org.apache.openjpa.enhance.PersistenceCapable;
import org.apache.openjpa.enhance.StateManager;


/**
 * A specialized Swing table to display a list of persistent entity instances.
 * The data for the table is supplied via a specialized {@linkplain EntityDataModel data model}
 * that is supplied at construction.
 * <br>
 * The table view uses specialized cell renderer to display single-valued and multi-valued
 * association.
 *
 *
 * @author Pinaki Poddar
 *
 */
public class EntityTable<T> extends JTable {
    private static final long serialVersionUID = 1L;
    private InstanceCellRenderer instanceCellRenderer;
    private CollectionCellRenderer collectionCellRenderer;

    public EntityTable(Class<T> cls, List<T> data, int styleBits, EntityManagerFactory unit) {
        super();
        instanceCellRenderer   = new InstanceCellRenderer(unit.getPersistenceUnitUtil());
        collectionCellRenderer = new CollectionCellRenderer();
        setSelectionMode(ListSelectionModel.SINGLE_SELECTION);

        setModel(new EntityDataModel<>(cls, data,  unit.getMetamodel(), styleBits));
        getModel().addTableModelListener(this);
        initColumnSizes();
        setFillsViewportHeight(true);
    }


    public InstanceCellRenderer getInstanceRenderer() {
        return instanceCellRenderer;
    }

    /**
     * Gets the special renderer for single- and multi-valued association.
     * Otherwise uses the super classes' renderer defined by the field type.
     */
    @Override
    public TableCellRenderer getCellRenderer(int row, int column) {
        Attribute<?,?> attr = ((EntityDataModel)getModel()).getAttribute(column);
        TableCellRenderer renderer = null;
        if (attr == null) {
            renderer = super.getCellRenderer(row, column);
        } else if (attr.isAssociation()) {
            renderer = instanceCellRenderer;
        } else if (attr.isCollection()) {
            renderer = collectionCellRenderer;
        } else {
            renderer = super.getCellRenderer(row, column);
        }
        if (renderer instanceof DefaultTableCellRenderer) {
            ((DefaultTableCellRenderer)renderer).setHorizontalAlignment(SwingConstants.CENTER);
        }

        return renderer;
    }

    @Override
    public TableCellEditor getCellEditor(int row, int column) {
        Attribute<?,?> attr = ((EntityDataModel)getModel()).getAttribute(column);
        if (attr == null)
            return super.getCellEditor(row, column);
        if (attr.isCollection())
            return new DefaultCellEditor((JComboBox)getCellRenderer(row, column));
        return super.getCellEditor(row, column);
    }

    /**
     * Picks good column sizes.
     * If all column heads are wider than the column's cells'
     * contents, then you can just use column.sizeWidthToFit().
     */
    private void initColumnSizes() {
        TableModel model   = getModel();
        TableColumn column = null;
        Component comp     = null;
        int headerWidth    = 0;

        TableCellRenderer headerRenderer =  getTableHeader().getDefaultRenderer();

        for (int columnIndex = 0; columnIndex < model.getColumnCount(); columnIndex++) {
            int cellWidth = 0;
            column = getColumnModel().getColumn(columnIndex);

            comp = headerRenderer.getTableCellRendererComponent(
                                 null, column.getHeaderValue(),
                                 false, false, 0, 0);
            headerWidth = comp.getPreferredSize().width;
            TableCellRenderer renderer = getCellRenderer(0, columnIndex);
            int rowCount = Math.min(model.getRowCount(), 10);
            for (int rowIndex = 0; rowIndex < rowCount; rowIndex++) {
                Object value = model.getValueAt(rowIndex, columnIndex);
                comp = renderer.getTableCellRendererComponent(
                                 this, value,
                                 false, false, rowIndex, columnIndex);
                cellWidth = Math.max(comp.getPreferredSize().width, cellWidth);
            }
            column.setPreferredWidth(Math.max(headerWidth, cellWidth));
        }
    }

    /**
     * Renders the value of a persistent entity in a table column as the persistent identifier.
     * The persistent identifier is extracted by the new {@link PersistenceUnitUtil utility} feature
     * of JPA 2.0 API.
     *
     * @author Pinaki Poddar
     *
     */
    public class InstanceCellRenderer extends DefaultTableCellRenderer {
        
        private static final long serialVersionUID = 1L;
        private final PersistenceUnitUtil util;

        public InstanceCellRenderer(PersistenceUnitUtil util) {
            super();
            this.util = util;
        }

        /**
         * Gets the stringified persistence identifier of the given instance.
         *
         */
        public String renderAsString(Object instance) {
            if (instance == null) {
                return "null";
            }
            Object id = util.getIdentifier(instance);
            if (id != null)
                return id.toString();
            if (instance instanceof PersistenceCapable) {
                PersistenceCapable pc = (PersistenceCapable)instance;
                StateManager sm = pc.pcGetStateManager();
                id = sm == null ? null : sm.fetchObjectId();
            }
            return id == null ? "null" : id.toString();
        }

        @Override
        public void setValue(Object instance) {
            setForeground(Color.BLUE);
            setText(renderAsString(instance));
        }

    }


    /**
     * Renders a many-valued attribute as simply three dots.
     *
     * @author Pinaki Poddar
     *
     */
    public class CollectionCellRenderer extends DefaultTableCellRenderer {
        
        private static final long serialVersionUID = 1L;
        public CollectionCellRenderer() {
            setPreferredSize(new Dimension(10,20));
        }
        @Override
        public Component getTableCellRendererComponent(JTable table, Object value,
                boolean isSelected, boolean hasFocus, int row, int column) {
            return super.getTableCellRendererComponent(table, value == null ? null : "...",
                    isSelected, hasFocus, row, column);
        }

    }




}
