/*
 * ARX: Powerful Data Anonymization
 * Copyright 2012 - 2017 Fabian Prasser, Florian Kohlmayer 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.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.deidentifier.arx.gui.Controller;
import org.deidentifier.arx.gui.resources.Resources;
import org.deidentifier.arx.gui.view.SWTUtil;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.MouseAdapter;
import org.eclipse.swt.events.MouseEvent;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.events.SelectionListener;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Event;
import org.eclipse.swt.widgets.Table;
import org.eclipse.swt.widgets.TableColumn;
import org.eclipse.swt.widgets.TableItem;

/**
 * This class implements a table, in which properties can be filtered.
 * 
 * @author Fabian Prasser
 */
public class ComponentFilterTable {

    /** Constant. */
    private static final int                  LABEL_WIDTH      = 100;

    /** Constant. */
    private static final int                  CHECKBOX_WIDTH   = 20;

    /** Image. */
    private final Image                       IMAGE_ENABLED;

    /** Image. */
    private final Image                       IMAGE_DISABLED;

    /** Widget. */
    private final Table                       table;

    /** Widgets. */
    private Map<String, TableItem>            items;

    /** The registered listeners. */
    private List<SelectionListener>           listeners;

    /** The selection map. */
    private Map<String, Map<String, Boolean>> selected;

    /** The list of properties. */
    private Map<String, List<String>>         itemProperties;

    /** The list of properties. */
    private List<String>                      properties;

    /** The list of entries. */
    private List<String>                      entries;

    /** Selected entry. */
    private String                            selectedEntry    = null;

    /** Selected property. */
    private String                            selectedProperty = null;

    /**
     * Creates a new instance.
     * 
     * @param parent
     * @param controller
     * @param properties
     */
    public ComponentFilterTable(Composite parent, 
                                Controller controller, 
                                List<String> properties) {
        
        IMAGE_ENABLED = controller.getResources().getManagedImage("tick.png"); //$NON-NLS-1$
        IMAGE_DISABLED = controller.getResources().getManagedImage("cross.png"); //$NON-NLS-1$
        
        this.listeners = new ArrayList<SelectionListener>();
        this.selected = new HashMap<String, Map<String, Boolean>>();
        this.properties = new ArrayList<String>(properties);
        this.entries = new ArrayList<String>();
        this.items = new HashMap<String, TableItem>();
        this.itemProperties = new HashMap<String, List<String>>();
        this.table = SWTUtil.createTable(parent, SWT.BORDER | SWT.H_SCROLL | SWT.V_SCROLL);
        this.table.setHeaderVisible(true);
        this.setProperties(properties);
        
        table.addMouseListener(new MouseAdapter(){
            public void mouseDown(MouseEvent arg0) {
                int row = getItemRowAt(arg0.x, arg0.y);
                int column = getItemColumnAt(arg0.x, arg0.y);
                if (row != -1 && column > 0 && column <= ComponentFilterTable.this.properties.size()) {
                    String property = ComponentFilterTable.this.properties.get(column-1);
                    String entry = ComponentFilterTable.this.entries.get(row);
                    if (itemProperties.get(entry).contains(property)) {
                        selectedProperty = property;
                        selectedEntry = entry;
                    } else {
                        selectedProperty = null;
                        selectedEntry = null;
                    }
                } else {
                    selectedProperty = null;
                    selectedEntry = null;
                }
            }
        });
        
        table.addMouseListener(new MouseAdapter(){
            public void mouseDown(MouseEvent arg0) {
                if (selectedProperty != null && selectedEntry != null) {
                    boolean selected = isSelected(selectedEntry, selectedProperty);
                    setSelected(selectedEntry,
                                selectedProperty,
                                !selected);
                    fireSelectionEvent();
                }
            }
        });
    }

    /**
     * Adds a new entry, i.e., a row in the table
     * 
     * @param entry
     * @param properties
     */
    public void addEntry(String entry, List<String> properties) {

        if (!this.properties.containsAll(properties)) {
            throw new RuntimeException(Resources.getMessage("ComponentFilterTable.2")); //$NON-NLS-1$
        }

        TableItem item = new TableItem(table, SWT.NONE);
        for (int i = 0; i < this.properties.size(); i++) {
            if (properties.contains(this.properties.get(i))) {
                item.setImage(i + 1, IMAGE_DISABLED);
            }
        }
        item.setImage(0, null);
        item.setText(0, entry);
        this.items.put(entry, item);
        this.itemProperties.put(entry, properties);
        this.entries.add(entry);
        table.redraw();

        for (TableColumn c : table.getColumns()) {
            c.pack();
        }
    }

    /**
     * Adds a selection listener.
     * 
     * @param listener
     */
    public void addSelectionListener(SelectionListener listener) {
        this.listeners.add(listener);
    }

    /**
     * Clears the table.
     */
    public void clear() {
        this.table.setRedraw(false);
        for (TableItem item : table.getItems()) {
            item.dispose();
        }
        for (TableColumn column : table.getColumns()) {
            column.dispose();
        }
        this.table.removeAll();
        this.table.setRedraw(true);
        this.table.redraw();
        this.items.clear();
        this.itemProperties.clear();
        this.properties.clear();
        this.entries.clear();
        this.selected.clear();
    }

    /**
     * Disposes this widget.
     */
    public void dispose() {
        this.table.dispose();
    }

    /**
     * Returns the entries.
     * 
     * @return
     */
    public List<String> getEntries() {
        return entries;
    }

    /**
     * Returns the properties.
     * 
     * @return
     */
    public List<String> getProperties() {
        return properties;
    }

    /**
     * Returns the currently selected entry.
     * 
     * @return
     */
    public String getSelectedEntry() {
        return selectedEntry;
    }

    /**
     * Returns the currently selected property.
     * 
     * @return
     */
    public String getSelectedProperty() {
        return selectedProperty;
    }

    /**
     * Returns whether the given property is selected for the given entry.
     * 
     * @param entry
     * @param property
     * @return
     */
    public boolean isSelected(String entry, String property) {
        if (!this.entries.contains(entry)) {
            throw new RuntimeException(Resources.getMessage("ComponentFilterTable.3")); //$NON-NLS-1$
        }
        if (!this.properties.contains(property)) {
            throw new RuntimeException(Resources.getMessage("ComponentFilterTable.4")); //$NON-NLS-1$
        }
        Map<String, Boolean> map = selected.get(entry);
        if (map == null) {
            return false;
        }
        else {
            Boolean b = map.get(property);
            return b == null ? false : b;
        }
    }

    /**
     * Removes a selection listener.
     * 
     * @param listener
     */
    public void removeSelectionListener(SelectionListener listener) {
        this.listeners.remove(listener);
    }

    /**
     * Enable/disable.
     * 
     * @param enabled
     */
    public void setEnabled(boolean enabled) {
        this.table.setEnabled(enabled);
    }

    /**
     * Sets layout data.
     * 
     * @param layoutData
     */
    public void setLayoutData(Object layoutData) {
        this.table.setLayoutData(layoutData);
    }

    /**
     * Sets new properties. Clears the table
     * @param properties
     */
    public void setProperties(List<String> properties) {
        this.clear();
        this.properties = new ArrayList<String>(properties);

        TableColumn column = new TableColumn(table, SWT.LEFT);
        column.setWidth(LABEL_WIDTH);
        column.setText(""); //$NON-NLS-1$
        for (String property : properties) {
            column = new TableColumn(table, SWT.CENTER);
            column.setText(property);
            column.setWidth(CHECKBOX_WIDTH);
        }
        column = new TableColumn(table, SWT.LEFT);
        column.setText(""); //$NON-NLS-1$
    }

    /**
     * Sets the given property selected for the given entry .
     * 
     * @param entry
     * @param property
     * @param selected
     */
    public void setSelected(String entry, String property, boolean selected) {
        if (!this.entries.contains(entry)) {
            return;
        }
        if (!this.properties.contains(property)) {
            return;
        }
        if (!this.selected.containsKey(entry)) {
            this.selected.put(entry, new HashMap<String, Boolean>());
        }
        if (this.itemProperties.get(entry).contains(property)) {
            this.selected.get(entry).put(property, selected);
            int index = properties.indexOf(property);
            this.items.get(entry).setImage(index + 1, selected ? IMAGE_ENABLED : IMAGE_DISABLED);
            table.redraw();
        }
    }

    /**
     * Fires a new event.
     */
    private void fireSelectionEvent() {
        Event event = new Event();
        event.display = table.getDisplay();
        event.item = table;
        event.widget = table;
        SelectionEvent sEvent = new SelectionEvent(event);
        for (SelectionListener listener : listeners) {
            listener.widgetSelected(sEvent);
        }
    }

    /**
     * Returns the item at the given location.
     * 
     * @param x
     * @param y
     * @return
     */
    private int getItemColumnAt(int x, int y) {
        Point pt = new Point(x, y);
        int index = table.getTopIndex();
        while (index < table.getItemCount()) {
            final TableItem item = table.getItem(index);
            for (int i = 0; i < table.getColumns().length; i++) {
                final Rectangle rect = item.getBounds(i);
                if (rect.contains(pt)) {
                    return i;
                }
            }
            index++;
        }
        return -1;
    }

    /**
     * Returns the item at the given location.
     * 
     * @param x
     * @param y
     * @return
     */
    private int getItemRowAt(int x, int y) {
        Point pt = new Point(x, y);
        int index = table.getTopIndex();
        while (index < table.getItemCount()) {
            final TableItem item = table.getItem(index);
            for (int i = 0; i < table.getColumns().length; i++) {
                final Rectangle rect = item.getBounds(i);
                if (rect.contains(pt)) {
                    return index;
                }
            }
            index++;
        }
        return -1;
    }
}
