/**
 * Copyright © CENRA (2016)
 *
 * remi.clement@espaces-naturels.fr
 * 
 * This software is a multi-platform and portable application based on 
 * the scientifical toolbox to monitor wetlands. With this toolbox it’s 
 * possible to calculate indicators «RhoMéO» to monitor quality, 
 * fonctionalities and urban and agricultural pressures of wetlands. 
 * Indicators are calculated using naturalist data entered and/or imported,
 * and geographic data downloaded from a geographic server.
 * 
 * This software is governed by the CeCILL license under French law and
 * abiding by the rules of distribution of free software.  You can  use, 
 * modify and/ or redistribute the software under the terms of the CeCILL
 * license as circulated by CEA, CNRS and INRIA at the following URL
 * "http://www.cecill.info". 
 * 
 * As a counterpart to the access to the source code and  rights to copy,
 * modify and redistribute granted by the license, users are provided only
 * with a limited warranty  and the software's author,  the holder of the
 * economic rights,  and the successive licensors  have only  limited
 * liability. 
 * 
 * In this respect, the user's attention is drawn to the risks associated
 * with loading,  using,  modifying and/or developing or reproducing the
 * software by the user in light of its specific status of free software,
 * that may mean  that it is complicated to manipulate,  and  that  also
 * therefore means  that it is reserved for developers  and  experienced
 * professionals having in-depth computer knowledge. Users are therefore
 * encouraged to load and test the software's suitability as regards their
 * requirements in conditions enabling the security of their systems and/or 
 * data to be ensured and,  more generally, to use and operate it in the 
 * same conditions as regards security. 
 * 
 * The fact that you are presently reading this means that you have had
 * knowledge of the CeCILL license and that you accept its terms.
*/
package fr.cenra.rhomeo.fx.edition;

import fr.cenra.rhomeo.api.InternationalResource;
import fr.cenra.rhomeo.core.RhomeoCore;
import java.beans.BeanInfo;
import java.beans.IntrospectionException;
import java.beans.Introspector;
import java.beans.MethodDescriptor;
import java.beans.PropertyDescriptor;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.logging.Level;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import org.apache.sis.util.ArgumentChecks;

/**
 *
 * @author Alexis Manin (Geomatys)
 */
public class ObjectTable<T> extends TableView<T> {

    private final Class<T> dataType;
    private final BeanInfo beanInfo;

    /**
     * Create a new table to expose data type properties.
     *
     * @param objectType Type of row item objects.
     * @param blackList A set of properties to ignore when building table columns.
     *
     * @throws IntrospectionException
     */
    public ObjectTable(final Class objectType, String... blackList) throws IntrospectionException {
        ArgumentChecks.ensureNonNull("Data type", objectType);
        dataType = objectType;
        beanInfo = Introspector.getBeanInfo(objectType, Object.class);

        final Set<String> toIgnore;
        if (blackList == null || blackList.length <= 0) {
            toIgnore = Collections.EMPTY_SET;
        } else {
            toIgnore = new HashSet(Arrays.asList(blackList));
        }

        // Index methods by name to find them easily
        final MethodDescriptor[] mds = beanInfo.getMethodDescriptors();
        final Map<String, Method> methods = new HashMap<>(mds.length);
        for (final MethodDescriptor md : mds) {
            methods.put(md.getName(), md.getMethod());
        }

        final boolean bundleAvailable = InternationalResource.class.isAssignableFrom(dataType);
        boolean editable = false;
        for (final PropertyDescriptor desc : beanInfo.getPropertyDescriptors()) {
            if (toIgnore.contains(desc.getName()))
                continue;

            try {
                if (bundleAvailable) {
                    try {
                        desc.setDisplayName(InternationalResource.getResourceString((Class) dataType, desc.getName(), "label"));
                        desc.setShortDescription(InternationalResource.getResourceString((Class) dataType, desc.getName(), "tooltip"));
                    } catch (Exception e) {
                        RhomeoCore.LOGGER.log(Level.FINE, "No bundle available for property ".concat(desc.getName()), e);
                    }
                }
                final PropertyColumn<T> col = new PropertyColumn<>(desc, methods.get(desc.getName().concat("Property")));
                if (col.isEditable())
                    editable = true;
                getColumns().add(col);
            } catch (IllegalArgumentException e) {
                RhomeoCore.LOGGER.log(Level.WARNING, "Cannot create a table column for property ".concat(desc.getName()), e);
            }
        }

        setEditable(editable);
        setColumnResizePolicy(TableView.UNCONSTRAINED_RESIZE_POLICY);
    }

    /**
     * Try to sort table columns according to the names given in input list. Names
     * in input list must be a property name, or (only for columns not working with object properties)
     * the text of the column. Order in the list will define the order of the columns.
     *
     * @param orderedNames Order to apply on this table columns.
     */
    public void sortColumns(final String... orderedNames) {
        if (orderedNames == null || orderedNames.length < 1)
            return;
        sortColumns(Arrays.asList(orderedNames));
    }

    public void sortColumns(final List<String> orderedNames) {
        if (orderedNames == null || orderedNames.isEmpty())
            return;

        getColumns().sort((TableColumn<T, ?> o1, TableColumn<T, ?> o2) -> {
            final String firstName, secondName;
            if (o1 instanceof PropertyColumn) {
                firstName = ((PropertyColumn)o1).descriptor.getName();
            } else {
                firstName = o1.getText();
            }
            if (o2 instanceof PropertyColumn) {
                secondName = ((PropertyColumn)o2).descriptor.getName();
            } else {
                secondName = o2.getText();
            }

            final int firstOccur = orderedNames.indexOf(firstName);
            final int secondOccur = orderedNames.indexOf(secondName);
            // Second property not in the given list. No change in ordering.
            if (secondOccur < 0)
                return -1;
            // First property not found. Moved to the back.
            else if (firstOccur < 0)
                return 1;
            else
                return orderedNames.indexOf(firstName) - orderedNames.indexOf(secondName);
        });
    }

    public Class<T> getDataType() {
        return dataType;
    }

    public void focusOn(T item, String propertyName) {
        final int rowIndex = getItems().indexOf(item);
        if (rowIndex >= 0) {
            if (propertyName == null || (propertyName = propertyName.trim()).isEmpty()) {
                getSelectionModel().select(item);// No property given. Select row.
            } else {
                // Try to find the column of the property, to focus / edit queried cell.
                for (final TableColumn col : getColumns()) {
                    if (col instanceof PropertyColumn && ((PropertyColumn) col).descriptor.getName().equals(propertyName)) {
                        if (col.isEditable()) {
                            edit(rowIndex, col);
                        } else {
                            getFocusModel().focus(rowIndex, col);
                        }
                        return;
                    }
                }
            }
        }
    }
}
