/*******************************************************************************
 * Copyright (c) 2009, 2018 STMicroelectronics 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:
 *    Marzia Maugeri <marzia.maugeri@st.com> - initial API and implementation
 *******************************************************************************/
package org.eclipse.linuxtools.dataviewers.abstractviewers;

import java.util.Comparator;

import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.jface.dialogs.IDialogSettings;
import org.eclipse.jface.viewers.Viewer;
import org.eclipse.jface.viewers.ViewerComparator;
import org.eclipse.linuxtools.dataviewers.STDataViewersActivator;
import org.eclipse.swt.widgets.Item;

/**
 * This comparator is used to reorder the elements provided by the content provider.
 */
public class STDataViewersComparator extends ViewerComparator implements Comparator<Object> {

    /** Maximum number of sorters */
    public static final int MAX_DEPTH = 4;

    /** Tag for ascending direction when sorting */
    public static final int ASCENDING = 1;

    /** Tag for reverse direction when sorting */
    public static final int DESCENDING = -1;

    protected final Item[] columns;

    protected int[] priorities;

    protected int[] directions;

    /**
     * Copy Constructor
     *
     * @param other The comparator to initialize from.
     */
    public STDataViewersComparator(STDataViewersComparator other) {
        this(other.getColumns(), other.getPriorities(), other.getDirections());
    }

    /**
     * Constructor
     *
     * @param columns The columns properties of the viewer.
     */
    public STDataViewersComparator(Item[] columns) {
        this(columns, null, null);
    }

    /**
     * Constructor
     *
     * @param columns
     * @param priorities
     * @param directions
     */
    private STDataViewersComparator(Item[] columns, int[] priorities, int[] directions) {
        this.columns = columns;
        if (priorities == null || directions == null) {
            this.priorities = new int[columns.length];
            this.directions = new int[columns.length];
            resetState();
        } else {
            if (priorities.length == columns.length && directions.length == columns.length) {
                this.priorities = priorities;
                this.directions = directions;
            } else {
                STDataViewersActivator
                        .getDefault()
                        .getLog()
                        .log(new Status(IStatus.WARNING, STDataViewersActivator.PLUGIN_ID, "Invalid parameters:"
                                + " priorities and/or directions number don't match with"
                                + " viewer's columns count. Applying defaults settings."));
                resetState();
            }
        }
    }

    /**
     * Reset the priorities to the default ones
     */
    private void resetPriorites() {
        for (int i = 0; i < this.priorities.length; i++) {
            this.priorities[i] = i;
        }
    }

    /**
     * Reset the directions to the default ones
     */
    private void resetDirections() {
        for (int i = 0; i < this.directions.length; i++) {
            this.directions[i] = getField(this.columns[i]).getDefaultDirection();
        }
    }

    /**
     * Resets the directions and priorities of the sorters
     */
    public void resetState() {
        resetDirections();
        resetPriorites();
    }

    /**
     * Change the direction of the first sorter
     */
    public void reverseTopPriority() {
        directions[priorities[0]] *= -1;
    }

    /**
     * Sets the top-level sorter.
     *
     * @param column The column to make top priority.
     * @param field The field to set priority for.
     */
    public void setTopPriority(final Item column, final ISTDataViewersField field) {
        for (int i = 0; i < columns.length; i++) {
            if (columns[i].equals(column)) {
                setTopPriority(i, field);
            }
        }
    }

    /**
     * Sets the top-level sorter.
     *
     * @param priority The new top priority.
     * @param field The field to set priority for.
     */
    private void setTopPriority(final int priority, final ISTDataViewersField field) {
        if (priority < 0 || priority >= priorities.length) {
            return;
        }
        int index = -1;
        for (int i = 0; i < priorities.length; i++) {
            if (priorities[i] == priority) {
                index = i;
            }
        }
        if (index == -1) {
            resetState();
            return;
        }
        // shift the array
        for (int i = index; i > 0; i--) {
            priorities[i] = priorities[i - 1];
        }
        priorities[0] = priority;
        directions[priority] = field.getDefaultDirection();
    }

    /**
     * Changes the direction of the top-priority sorter.
     *
     * @param direction The direction of sorting - ascending or descending.
     */
    public void setTopPriorityDirection(int direction) {
        if (direction == ASCENDING || direction == DESCENDING) {
            directions[priorities[0]] = direction;
        }
    }

    /**
     * @return the direction of the top-level sorter
     */
    public int getTopPriorityDirection() {
        return directions[priorities[0]];
    }

    /**
     * Return the field at the top priority.
     *
     * @return IField
     */
    public Item getTopColumn() {
        return columns[priorities[0]];
    }

    /**
     * NOTE: defensive programming: return a copy of the array
     *
     * @return the current priorities
     */
    public int[] getPriorities() {
        int[] copy = new int[priorities.length];
        System.arraycopy(priorities, 0, copy, 0, copy.length);
        return copy;
    }

    /**
     * NOTE: defensive programming: return a copy of the array
     *
     * @return the current directions
     */
    public int[] getDirections() {
        int[] copy = new int[directions.length];
        System.arraycopy(directions, 0, copy, 0, copy.length);
        return copy;
    }

    @Override
    public int compare(Object o1, Object o2) {
        return compare(o1, o2, 0, true);
    }

    @Override
    public int compare(Viewer viewer, Object e1, Object e2) {
        return compare(e1, e2, 0, true);
    }

    /**
     * Compare obj1 and obj2 at depth. If continueSearching continue searching below depth to continue the comparison.
     *
     * @param obj1
     * @param obj2
     * @param depth
     * @param continueSearching
     * @return int
     */
    private int compare(Object obj1, Object obj2, int depth, boolean continueSearching) {
        if (depth >= priorities.length) {
            return 0;
        }

        int column = priorities[depth];
        ISTDataViewersField property = getField(columns[column]);

        int result;
        if (directions[column] >= 0) {
            result = property.compare(obj1, obj2);
        } else {
            result = property.compare(obj2, obj1);
        }

        if (result == 0 && continueSearching) {
            return compare(obj1, obj2, depth + 1, continueSearching);
        }

        return result;
    }

    protected ISTDataViewersField getField(Item column) {
        return (ISTDataViewersField) column.getData();
    }

    /**
     * @return IField[] an array of fields
     */
    public Item[] getColumns() {
        return columns;
    }

    /**
     * Saves the sort order preferences of the user in the given {@link IDialogSettings}
     *
     * @param dialogSettings The setting to save into.
     */
    public void saveState(IDialogSettings dialogSettings) {
        if (dialogSettings == null) {
            return;
        }
        IDialogSettings settings = dialogSettings.getSection(STDataViewersSettings.TAG_SECTION_SORTER);
        if (settings == null) {
            settings = dialogSettings.addNewSection(STDataViewersSettings.TAG_SECTION_SORTER);
        }
        for (int i = 0; i < priorities.length; i++) {
            settings.put(STDataViewersSettings.TAG_SORTER_PRIORITY_ + i, priorities[i]);
            settings.put(STDataViewersSettings.TAG_SORTER_DIRECTION_ + i, directions[i]);
        }
    }

    /**
     * Restore the sort order preferences of the user from the given {@link IDialogSettings}
     *
     * @param dialogSettings The settings to restore from.
     */
    public void restoreState(IDialogSettings dialogSettings) {
        if (dialogSettings == null) {
            // no settings section
            resetState();
            return;
        }
        IDialogSettings settings = dialogSettings.getSection(STDataViewersSettings.TAG_SECTION_SORTER);
        if (settings == null) {
            // no settings saved
            resetState();
            return;
        }
        try {
            for (int i = 0; i < priorities.length; i++) {
                String priority = settings.get(STDataViewersSettings.TAG_SORTER_PRIORITY_ + i);
                if (priority == null) {
                    // no priority data
                    resetState();
                    return;
                }

                int colIndex = Integer.parseInt(priority);
                // Make sure it is not old data from a different sized array
                if (colIndex < columns.length) {
                    priorities[i] = colIndex;
                } else {
                    // data from a different sized array
                    resetState();
                    return;
                }
                String direction = settings.get(STDataViewersSettings.TAG_SORTER_DIRECTION_ + i);
                if (direction == null) {
                    // no direction data
                    resetState();
                    return;
                }
                directions[i] = Integer.parseInt(direction);
            }
        } catch (NumberFormatException e) {
            // invalid entry
            resetState();
            return;
        }
    }

}
