/*******************************************************************************
 * 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.columnRename;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.TreeMap;

import org.eclipse.nebula.widgets.nattable.coordinate.Range;
import org.eclipse.nebula.widgets.nattable.grid.layer.ColumnHeaderLayer;
import org.eclipse.nebula.widgets.nattable.layer.event.StructuralDiff;
import org.eclipse.nebula.widgets.nattable.layer.event.StructuralDiff.DiffTypeEnum;
import org.eclipse.nebula.widgets.nattable.persistence.IPersistable;
import org.eclipse.nebula.widgets.nattable.util.PersistenceUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * Helper class for handling column renaming by user interactions on the column
 * header.
 */
public class RenameColumnHelper implements IPersistable {

    private static final Logger LOG = LoggerFactory.getLogger(RenameColumnHelper.class);

    public static final String PERSISTENCE_KEY_RENAMED_COLUMN_HEADERS = ".renamedColumnHeaders"; //$NON-NLS-1$

    private final ColumnHeaderLayer columnHeaderLayer;

    /**
     * Tracks the renamed labels provided by the user.
     */
    protected Map<Integer, String> renamedColumnsLabelsByIndex = new TreeMap<>();

    /**
     *
     * @param columnHeaderLayer
     *            The {@link ColumnHeaderLayer} this helper is connected to.
     */
    public RenameColumnHelper(ColumnHeaderLayer columnHeaderLayer) {
        this.columnHeaderLayer = columnHeaderLayer;
    }

    /**
     * Rename the column at the given position. Note: This does not change the
     * underlying column name.
     *
     * @param columnPosition
     *            The column position of the column that should be renamed.
     * @param customColumnName
     *            The name that should used as the user defined column name.
     *
     * @return <code>true</code> if the column at the given position was
     *         successfully changed.
     */
    public boolean renameColumnPosition(int columnPosition, String customColumnName) {
        int index = this.columnHeaderLayer.getColumnIndexByPosition(columnPosition);
        return renameColumnIndex(index, customColumnName);
    }

    /**
     * Rename the column at the given index. Note: This does not change the
     * underlying column name.
     *
     * @param index
     *            The column index of the column that should be renamed.
     * @param customColumnName
     *            The name that should used as the user defined column name.
     *
     * @return <code>true</code> if the column at the given index was
     *         successfully changed.
     */
    public boolean renameColumnIndex(int index, String customColumnName) {
        if (index >= 0) {
            if (customColumnName == null) {
                this.renamedColumnsLabelsByIndex.remove(index);
            } else {
                this.renamedColumnsLabelsByIndex.put(index, customColumnName);
            }
            return true;
        }
        return false;
    }

    /**
     * @param columnIndex
     *            the column index
     * @return the custom label for this column as specified by the user Null if
     *         the columns is not renamed
     */
    public String getRenamedColumnLabel(int columnIndex) {
        return this.renamedColumnsLabelsByIndex.get(columnIndex);
    }

    /**
     * @param columnIndex
     *            the column index
     * @return <code>true</code> if the column at the specified index was
     *         renamed by a user.
     */
    public boolean isColumnRenamed(int columnIndex) {
        return this.renamedColumnsLabelsByIndex.get(columnIndex) != null;
    }

    /**
     * @return <code>true</code> if a user renamed any column.
     */
    public boolean isAnyColumnRenamed() {
        return this.renamedColumnsLabelsByIndex.size() > 0;
    }

    /**
     * Handle the given collection of {@link StructuralDiff} objects to update
     * the indexes of the renamed column labels.
     *
     * @param columnDiffs
     *            The {@link StructuralDiff}s to handle
     * @since 1.4
     */
    public void handleStructuralChanges(Collection<StructuralDiff> columnDiffs) {
        // the number of all deleted columns that don't have a corresponding
        // index anymore (last column cases)
        List<Integer> toRemove = new ArrayList<>();
        for (StructuralDiff columnDiff : columnDiffs) {
            if (columnDiff.getDiffType() != null
                    && columnDiff.getDiffType().equals(DiffTypeEnum.DELETE)) {
                Range beforePositionRange = columnDiff.getBeforePositionRange();
                for (int i = beforePositionRange.start; i < beforePositionRange.end; i++) {
                    int index = i;
                    if (index >= 0)
                        toRemove.add(index);
                }
            }
        }
        // remove the column indexes that are deleted
        for (Integer r : toRemove) {
            this.renamedColumnsLabelsByIndex.remove(r);
        }

        // modify column indexes regarding the deleted columns
        List<Integer> indices = new ArrayList<>(this.renamedColumnsLabelsByIndex.keySet());
        Collections.sort(indices);

        Map<Integer, String> modified = new TreeMap<>();
        for (Integer column : indices) {
            // check number of removed indexes that are lower than the current
            // one
            int deletedBefore = 0;
            for (Integer removed : toRemove) {
                if (removed < column) {
                    deletedBefore++;
                }
            }
            int modColumn = column - deletedBefore;
            if (modColumn >= 0)
                modified.put(modColumn, this.renamedColumnsLabelsByIndex.get(column));
        }
        this.renamedColumnsLabelsByIndex.clear();
        this.renamedColumnsLabelsByIndex.putAll(modified);

        for (StructuralDiff columnDiff : columnDiffs) {
            if (columnDiff.getDiffType() != null
                    && columnDiff.getDiffType().equals(DiffTypeEnum.ADD)) {

                indices = new ArrayList<>(this.renamedColumnsLabelsByIndex.keySet());
                Collections.sort(indices);

                Range beforePositionRange = columnDiff.getBeforePositionRange();
                Range afterPositionRange = columnDiff.getAfterPositionRange();
                Map<Integer, String> modifiedColumns = new TreeMap<>();
                int beforeIndex = this.columnHeaderLayer.getColumnIndexByPosition(beforePositionRange.start);
                for (Integer column : indices) {
                    if (column >= beforeIndex) {
                        modifiedColumns.put(column + (afterPositionRange.end - afterPositionRange.start), this.renamedColumnsLabelsByIndex.get(column));
                    } else {
                        modifiedColumns.put(column, this.renamedColumnsLabelsByIndex.get(column));
                    }
                }

                this.renamedColumnsLabelsByIndex.clear();
                this.renamedColumnsLabelsByIndex.putAll(modifiedColumns);
            }
        }
    }

    @Override
    public void loadState(String prefix, Properties properties) {
        Object property = properties.get(prefix + PERSISTENCE_KEY_RENAMED_COLUMN_HEADERS);

        try {
            this.renamedColumnsLabelsByIndex = PersistenceUtils.parseString(property);
        } catch (Exception e) {
            LOG.error("Error while restoring renamed column headers: {}", e.getMessage()); //$NON-NLS-1$
            LOG.error("Skipping restore."); //$NON-NLS-1$
            this.renamedColumnsLabelsByIndex.clear();
        }
    }

    @Override
    public void saveState(String prefix, Properties properties) {
        String string = PersistenceUtils.mapAsString(this.renamedColumnsLabelsByIndex);
        if (string != null && string.length() > 0) {
            properties.put(prefix + PERSISTENCE_KEY_RENAMED_COLUMN_HEADERS, string);
        }
    }
}
