/*
 * ARX Data Anonymization Tool
 * Copyright 2012 - 2024 Fabian Prasser and contributors
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 * http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.deidentifier.arx.gui.view.impl.common;

import java.util.Arrays;

import org.deidentifier.arx.AttributeType.Hierarchy;
import org.deidentifier.arx.gui.resources.Resources;
import org.deidentifier.arx.gui.view.SWTUtil;
import org.deidentifier.arx.gui.view.impl.common.table.CTConfiguration;
import org.eclipse.nebula.widgets.nattable.data.IDataProvider;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.ModifyEvent;
import org.eclipse.swt.events.ModifyListener;
import org.eclipse.swt.events.MouseListener;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Event;

/**
 * This class implements an editor for generalization hierarchies.
 * 
 * @author Fabian Prasser
 * @author Florian Kohlmayer
 * @author Ledian Xhani
 * @author Ljubomir Dshevlekov
 * 
 */
public class ComponentHierarchy {
    
    /**
     * A data provider for hierarchies.
     *
     * @author Fabian Prasser
     */
    private class HierarchyDataProvider implements IDataProvider {
        
        @Override
        public int getColumnCount() {
            if (hierarchy == null) return 0;
            else if (hierarchy.length == 0) return 0;
            else if (hierarchy[0] == null) return 0;
            else return hierarchy[0].length;
        }
        
        @Override
        public Object getDataValue(int columnIndex, int rowIndex) {
            return hierarchy[rowIndex][columnIndex];
        }
        
        @Override
        public int getRowCount() {
            if (hierarchy == null) return 0;
            else return hierarchy.length;
        }
        
        @Override
        public void setDataValue(int columnIndex, int rowIndex, Object newValue) {
            // Ignore
        }
    }
    
    /**
     * A header data provider for hierarchies.
     *
     * @author Fabian Prasser
     */
    private class HierarchyHeaderDataProvider extends HierarchyDataProvider {
        
        @Override
        public Object getDataValue(int columnIndex, int rowIndex) {
            return Resources.getMessage("ViewHierarchy.0") + columnIndex; //$NON-NLS-1$
        }
        
        @Override
        public int getRowCount() {
            return 1;
        }
    }

    /** Widget. */
    private Composite            base;

    /** Widget. */
    private ComponentTable       table     = null;

    /** Model. */
    private String[][]           hierarchy = new String[][] { new String[] {} };

    /** Listener */
    private final ModifyListener listener;
    
    /**
     * Constructor for non-editable views.
     *
     * @param parent
     */
    public ComponentHierarchy(final Composite parent) {
        this.listener = null;
        this.createTable(parent);
    }
    
    /**
     * Constructor for editable views.
     *
     * @param parent
     * @param listener
     */
    public ComponentHierarchy(final Composite parent,
                         final ModifyListener listener) {
        this.listener = listener;
        this.createTable(parent);
    }

    /**
     * Clears the hierarchy.
     */
    public void actionClear() {
        this.setHierarchy(null);
        this.triggerChangedEvent();
    }
    
    /**
     * Deletes a column.
     */
    public void actionDeleteColumn() {
        
        if (table.getSelectedColumn() == null) {
            return;
        }
        
        int selected = table.getSelectedColumn();
        int columns = hierarchy[0].length - 1;
        int rows = hierarchy.length;
        String[][] temp = new String[rows][columns];
        for (int i = 0; i < rows; i++) {
            String[] row = new String[columns];
            System.arraycopy(hierarchy[i], 0, row, 0, selected);
            System.arraycopy(hierarchy[i], selected + 1, row, selected, columns - selected);
            temp[i] = row;
        }
        
        if (columns == 0) {
            actionClear();
        } else {
            this.hierarchy = temp;
            this.table.refresh();
        }
        
        this.triggerChangedEvent();
    }
    
    /**
     * Deletes a row.
     */
    public void actionDeleteRow() {
        
        if (table.getSelectedRow() == null) {
            return;
        }
        
        int selected = table.getSelectedRow();
        int columns = hierarchy[0].length;
        int rows = hierarchy.length - 1;
        String[][] temp = new String[rows][columns];
        
        System.arraycopy(hierarchy, 0, temp, 0, selected);
        System.arraycopy(hierarchy, selected + 1, temp, selected, rows - selected);
        
        if (rows == 0) {
            actionClear();
        } else {
            this.hierarchy = temp;
            this.table.refresh();
        }
        
        this.triggerChangedEvent();
    }
    
    /**
     * Renames an item.
     */
    public void actionEditItem(String newValue) {
        
        if (table.getSelectedRow() == null ||
            table.getSelectedColumn() == null) {
            return;
        }
        
        int selectedRow = table.getSelectedRow();
        int selectedColumn = table.getSelectedColumn();
        
        if (newValue != null) {
            hierarchy[selectedRow][selectedColumn] = newValue;
        }
        
        this.table.refresh();
        this.triggerChangedEvent();
    }
    
    /**
     * Inserts a column.
     */
    public void actionInsertColumn() {
        
        if (table.getSelectedColumn() == null) {
            return;
        }
        
        int selected = table.getSelectedColumn();
        int columns = hierarchy[0].length + 1;
        int rows = hierarchy.length;
        String[][] temp = new String[rows][columns];
        for (int i = 0; i < rows; i++) {
            String[] row = new String[columns];
            System.arraycopy(hierarchy[i], 0, row, 0, selected + 1);
            System.arraycopy(hierarchy[i], selected + 1, row, selected + 2, columns - selected - 2);
            row[selected + 1] = ""; //$NON-NLS-1$
            temp[i] = row;
        }
        
        this.hierarchy = temp;
        this.table.refresh();
        this.triggerChangedEvent();
    }
    
    /**
     * Inserts a row.
     */
    public void actionInsertRow() {
        
        if (table.getSelectedRow() == null) {
            return;
        }
        
        int selected = table.getSelectedRow();
        int columns = hierarchy[0].length;
        int rows = hierarchy.length + 1;
        String[][] temp = new String[rows][columns];
        
        System.arraycopy(hierarchy, 0, temp, 0, selected + 1);
        System.arraycopy(hierarchy, selected + 1, temp, selected + 2, rows - selected - 2);
        temp[selected + 1] = new String[columns];
        Arrays.fill(temp[selected + 1], ""); //$NON-NLS-1$
        
        this.hierarchy = temp;
        this.table.refresh();
        this.triggerChangedEvent();
    }
    
    /**
     * Moves an element down.
     */
    public void actionMoveRowDown() {
        
        if (table.getSelectedRow() == null) {
            return;
        }
        
        int selected = table.getSelectedRow();
        
        if (selected == hierarchy.length - 1) {
            return;
        }
        
        String[] temp = hierarchy[selected + 1];
        hierarchy[selected + 1] = hierarchy[selected];
        hierarchy[selected] = temp;
        
        this.table.refresh();
        this.triggerChangedEvent();
    }
    
    /**
     * Moves an element up.
     */
    public void actionMoveRowUp() {
        
        if (table.getSelectedRow() == null) {
            return;
        }
        
        int selected = table.getSelectedRow();
        
        if (selected <= 0) {
            return;
        }
        
        String[] temp = hierarchy[selected - 1];
        hierarchy[selected - 1] = hierarchy[selected];
        hierarchy[selected] = temp;
        
        this.table.refresh();
        this.triggerChangedEvent();
    }
    
    /**
     * Renames an item.
     */
    public void actionRenameItem(String newValue) {
        
        if (table.getSelectedRow() == null ||
            table.getSelectedColumn() == null) {
            return;
        }
        
        int selectedRow = table.getSelectedRow();
        int selectedColumn = table.getSelectedColumn();
        
        final String oldValue = hierarchy[selectedRow][selectedColumn];
       
        if (newValue != null) {
            for (int i = 0; i < hierarchy.length; i++) {
                if (hierarchy[i][selectedColumn].equals(oldValue)) {
                    hierarchy[i][selectedColumn] = newValue;
                }
            }
        }
        
        this.table.refresh();
        this.triggerChangedEvent();
    }
    
    /**
     * Clears the hierarchy.
     */
    public void actionReset() {
        this.setHierarchy(null);
    }
    
    /**
     * Adds a mouse listener
     * @param listener
     */
    public void addMouseListener(MouseListener listener) {
        this.table.addMouseListener(listener);
        this.table.getControl().addMouseListener(listener);
    }
    
    /**
     * Returns the underlying control
     * @return
     */
    public Control getControl() {
        return table.getControl();
    }
    
    /**
     * Returns the selected value. Returns null if no value is selected.
     * @return
     */
    public String getSelectedValue() {

        if (table.getSelectedRow() == null ||
            table.getSelectedColumn() == null) {
            return null;
        }
        int selectedRow = table.getSelectedRow();
        int selectedColumn = table.getSelectedColumn();
        return hierarchy[selectedRow][selectedColumn];
    }
    
    /**
     * Returns whether a cell is selected
     * @return
     */
    public boolean isCellSelected() {
        return table.getSelectedColumn() != null && table.getSelectedRow() != null;
    }
    
    /**
     * Returns whether a column is selected
     * @return
     */
    public boolean isColumnSelected() {
        return !isCellSelected() && table.getSelectedColumn() != null;
    }
    
    /**
     * Returns whether this component is empty
     * @return
     */
    public boolean isEmpty() {
        return  hierarchy == null ||
                hierarchy.length == 0 ||
                hierarchy[0] == null ||
                hierarchy[0].length == 0;
    }
    
    /**
     * Returns whether a row is selected
     * @return
     */
    public boolean isRowSelected() {
        return !isCellSelected() && table.getSelectedRow() != null;
    }
    
    /**
     * Sets the hierarchy displayed by this component. Call with null to reset.
     *
     * @param hierarchy
     */
    public void setHierarchy(final Hierarchy hierarchy) {
        this.hierarchy = (hierarchy == null ? null : hierarchy.getHierarchy());
        this.table.refresh();
    }

    /**
     * Sets the layout data.
     *
     * @param d
     */
    public void setLayoutData(final Object d) {
        base.setLayoutData(d);
    }

    /**
     * Creates the control.
     *
     * @param parent
     */
    private void createTable(final Composite parent) {
        
        // Create base composite
        this.base = new Composite(parent, SWT.NONE);
        GridData bottomLayoutData = SWTUtil.createFillGridData();
        bottomLayoutData.grabExcessVerticalSpace = true;
        GridLayout bottomLayout = new GridLayout();
        bottomLayout.numColumns = 1;
        this.base.setLayout(bottomLayout);
        this.base.setLayoutData(bottomLayoutData);
        
        // Configure table
        CTConfiguration config = new CTConfiguration(parent, CTConfiguration.STYLE_TABLE);
        config.setHorizontalAlignment(SWT.LEFT);
        config.setCellSelectionEnabled(true);
        config.setColumnSelectionEnabled(true);
        config.setRowSelectionEnabled(false);
        config.setColumnHeaderLayout(CTConfiguration.COLUMN_HEADER_LAYOUT_FILL);
        config.setRowHeaderLayout(CTConfiguration.ROW_HEADER_LAYOUT_FILL);
        
        // Create table
        this.table = new ComponentTable(base, SWT.BORDER, config);
        this.table.getControl().setLayoutData(SWTUtil.createFillGridData());
        this.table.setData(new HierarchyDataProvider(), new HierarchyHeaderDataProvider());
    }
    
    /**
     * Triggers a change event. New hierarchy is passed as event.data
     */
    private void triggerChangedEvent() {
        if (listener != null) {
            Event event = new Event();
            event.data = Hierarchy.create(hierarchy);
            event.widget = table.getControl();
            listener.modifyText(new ModifyEvent(event));
        }
    }
}
