/*******************************************************************************
 * 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
 *     Dirk Fauth <dirk.fauth@googlemail.com> - Bug 455327
 *     Dirk Fauth <dirk.fauth@googlemail.com> - Bug 444839, 444855, 453885
 ******************************************************************************/
package org.eclipse.nebula.widgets.nattable.extension.glazedlists.groupBy;

import java.util.Comparator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

import org.eclipse.nebula.widgets.nattable.data.IColumnAccessor;
import org.eclipse.nebula.widgets.nattable.layer.IUniqueIndexLayer;
import org.eclipse.nebula.widgets.nattable.sort.ISortModel;

import ca.odell.glazedlists.TreeList;

/**
 * The TreeList.Format that is used by the TreeList that is created and used by
 * the GroupByDataLayer. Note that the TreeList created by the GroupByDataLayer
 * is generic for Object because the groupBy functionality will add
 * GroupByObjects to the path for creating the grouping.
 *
 * @param <T>
 *            The type of the base objects carried in the TreeList.
 */
public class GroupByTreeFormat<T> implements TreeList.Format<Object> {

    /**
     * The GroupByModel that carries the information about the groupBy states.
     */
    private final GroupByModel model;
    /**
     * The IColumnAccessor that is used to get the column value for the columns
     * that are grouped by. Needed for compare operations and creating the path
     * in the tree.
     */
    private final IColumnAccessor<T> columnAccessor;
    /**
     * Comparator that is used to sort the TreeList based on the groupBy
     * information.
     */
    private IGroupByComparator<T> groupByComparator;

    /**
     *
     * @param model
     *            The GroupByModel that carries the information about the
     *            groupBy states.
     * @param columnAccessor
     *            The IColumnAccessor that is used to get the column value for
     *            the columns that are grouped by. Needed for compare operations
     *            and creating the path in the tree.
     */
    public GroupByTreeFormat(GroupByModel model, IColumnAccessor<T> columnAccessor) {
        this.model = model;
        this.columnAccessor = columnAccessor;
    }

    @SuppressWarnings("unchecked")
    @Override
    public void getPath(List<Object> path, Object element) {
        List<Integer> groupByColumns = this.model.getGroupByColumnIndexes();
        if (!groupByColumns.isEmpty()) {
            LinkedHashMap<Integer, Object> descriptor = new LinkedHashMap<>();
            for (int columnIndex : groupByColumns) {
                // Build a unique descriptor for the group
                Object columnValue = this.columnAccessor.getDataValue((T) element, columnIndex);
                descriptor.put(columnIndex, columnValue);
                GroupByObject groupByObject = getGroupByObject(columnValue, descriptor);
                path.add(groupByObject);
            }
        }
        path.add(element);
    }

    /**
     *
     * @param columnValue
     *            The column value that is used to create the
     *            {@link GroupByObject}. Specifies the groupBy value.
     * @param descriptor
     *            The descriptor that is used to create the
     *            {@link GroupByObject}. Specifies the groupBy depth.
     * @return The {@link GroupByObject} for the given value and descriptor.
     */
    protected GroupByObject getGroupByObject(Object columnValue, Map<Integer, Object> descriptor) {
        return new GroupByObject(columnValue, new LinkedHashMap<>(descriptor));
    }

    @Override
    public boolean allowsChildren(Object element) {
        return true;
    }

    @Override
    public Comparator<Object> getComparator(int depth) {
        // if there is no grouping we do not provide a comparator for the tree
        if (this.model.getGroupByColumnIndexes().isEmpty()) {
            return null;
        }

        return this.groupByComparator;
    }

    /**
     * Clear the comparator local cache of summary information that is used to
     * increase performance on sorting. Can be called often since the cache is
     * only valid for a sorting operation.
     */
    public void clearComparatorCache() {
        this.groupByComparator.clearCache();
    }

    /**
     *
     * @param model
     *            The {@link ISortModel} that should be set to the
     *            {@link IGroupByComparator}.
     * @see IGroupByComparator#setSortModel(ISortModel)
     */
    public void setSortModel(ISortModel model) {
        this.groupByComparator.setSortModel(model);
    }

    /**
     *
     * @return The {@link ISortModel} that is set to the
     *         {@link IGroupByComparator}.
     * @see IGroupByComparator#getSortModel()
     */
    public ISortModel getSortModel() {
        return this.groupByComparator.getSortModel();
    }

    /**
     *
     * @param treeLayer
     *            The {@link IUniqueIndexLayer} that should be set to the
     *            {@link IGroupByComparator}.
     * @see IGroupByComparator#setTreeLayer(IUniqueIndexLayer)
     */
    void setTreeLayer(IUniqueIndexLayer treeLayer) {
        this.groupByComparator.setTreeLayer(treeLayer);
    }

    /**
     *
     * @param dataLayer
     *            The {@link GroupByDataLayer} that should be set to the
     *            {@link IGroupByComparator}.
     * @see IGroupByComparator#setDataLayer(GroupByDataLayer)
     */
    void setDataLayer(GroupByDataLayer<T> dataLayer) {
        this.groupByComparator.setDataLayer(dataLayer);
    }

    /**
     *
     * @param comparator
     *            The {@link IGroupByComparator} that should be used to sort the
     *            {@link TreeList} in order to be able to build the tree
     *            structure correctly.
     */
    public void setComparator(IGroupByComparator<T> comparator) {
        this.groupByComparator = comparator;
    }

}
