/*******************************************************************************
 * Copyright (c) 2012, 2020 Original authors and others.
 *
 * This program and the accompanying materials are made
 * available under the terms of the Eclipse Public License 2.0
 * which is available at https://www.eclipse.org/legal/epl-2.0/
 *
 * SPDX-License-Identifier: EPL-2.0
 *
 * Contributors:
 *     Original authors and others - initial API and implementation
 ******************************************************************************/
package org.eclipse.nebula.widgets.nattable.data;

import java.util.List;

/**
 * Enables the use of a {@link List} containing POJO(s) as a backing data
 * source.
 *
 * By default a bean at position 'X' in the list is displayed in row 'X' in the
 * table. The properties of the bean are used to populate the columns. A
 * {@link IColumnPropertyResolver} is used to retrieve column data from the bean
 * properties.
 *
 * By implementing filter logic within
 * {@link AbstractFilterListDataProvider#show(Object)} it is possible to create
 * a static filter. All data access methods will skip invisible items within the
 * wrapped list and delegate access to the visible items.
 *
 * NOTE: This way of static filtering can cause performance issues for huge data
 * sets where a lot of items are filtered, because data access will always
 * calculate the visible row position. Trying to use a caching mechanism would
 * create some issues for deleting or inserting new data to the wrapped list.
 *
 * TODO add caching that reacts on insert/delete actions on the wrapped list
 *
 * @param <T>
 *            type of the Objects in the backing list.
 * @see IColumnPropertyResolver
 */
public abstract class AbstractFilterListDataProvider<T> extends ListDataProvider<T> {

    public AbstractFilterListDataProvider(List<T> list, IColumnAccessor<T> columnAccessor) {
        super(list, columnAccessor);
    }

    /**
     * Iterates over the whole list of data objects and checks the visibility
     * for every object. The number of non visible items will be subtracted from
     * the size of the wrapped list to return the number of visible items.
     */
    @Override
    public int getRowCount() {
        int numberOfInvisible = 0;
        for (T object : this.list) {
            if (!show(object)) {
                numberOfInvisible++;
            }
        }
        return this.list.size() - numberOfInvisible;
    }

    /**
     * Get the data value for the columnIndex and the visible rowIndex.
     */
    @Override
    public Object getDataValue(int columnIndex, int rowIndex) {
        T rowObj = getRowObject(rowIndex);
        return this.columnAccessor.getDataValue(rowObj, columnIndex);
    }

    /**
     * Set the data value for the columnIndex and the visible rowIndex.
     */
    @Override
    public void setDataValue(int columnIndex, int rowIndex, Object newValue) {
        T rowObj = getRowObject(rowIndex);
        this.columnAccessor.setDataValue(rowObj, columnIndex, newValue);
    }

    /**
     * Returns the object for the visible rowIndex. To do this it is iterated
     * over the wrapped list, taking the invisible items into account, so the
     * real row index for the given visible row index is calculated.
     */
    @Override
    public T getRowObject(int rowIndex) {
        T object = null;
        int count = 0;
        int realRowIndex = 0;
        while (count <= rowIndex) {
            object = this.list.get(realRowIndex);
            if (show(object)) {
                count++;
            }
            realRowIndex++;
        }

        return object;
    }

    /**
     * Returns the visible rowIndex for the given object. To do this the real
     * row index for the object within the wrapped list is searched and then all
     * invisible items are subtracted from the real row index to calculate the
     * visible row index.
     */
    @Override
    public int indexOfRowObject(T rowObject) {
        int realRowIndex = this.list.indexOf(rowObject);
        int filteredIndex = realRowIndex;
        // now find number of not visible items
        T vf = null;
        for (int i = 0; i <= realRowIndex; i++) {
            vf = this.list.get(i);
            if (!show(vf)) {
                filteredIndex--;
            }
        }
        return filteredIndex;
    }

    /**
     * Within this method the filter logic should be applied. Return false if
     * the object should not be visible within the grid. Return true if it
     * should be visible.
     *
     * @param object
     *            The object that should be checked.
     * @return true if the object should be visible, false if not
     */
    protected abstract boolean show(T object);
}
