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

import org.deidentifier.arx.aggregates.AggregateFunction;
import org.deidentifier.arx.gui.resources.Resources;
import org.deidentifier.arx.gui.view.SWTUtil;
import org.deidentifier.arx.gui.view.impl.wizard.HierarchyWizard.HierarchyWizardView;
import org.deidentifier.arx.gui.view.impl.wizard.HierarchyWizardEditorFunction.IHierarchyFunctionEditorParent;
import org.deidentifier.arx.gui.view.impl.wizard.HierarchyWizardEditorRenderer.RenderedComponent;
import org.deidentifier.arx.gui.view.impl.wizard.HierarchyWizardEditorRenderer.RenderedGroup;
import org.deidentifier.arx.gui.view.impl.wizard.HierarchyWizardEditorRenderer.RenderedInterval;
import org.deidentifier.arx.gui.view.impl.wizard.HierarchyWizardModelGrouping.HierarchyWizardGroupingGroup;
import org.deidentifier.arx.gui.view.impl.wizard.HierarchyWizardModelGrouping.HierarchyWizardGroupingInterval;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.CTabFolder;
import org.eclipse.swt.custom.CTabItem;
import org.eclipse.swt.custom.ScrolledComposite;
import org.eclipse.swt.events.MouseAdapter;
import org.eclipse.swt.events.MouseEvent;
import org.eclipse.swt.events.PaintEvent;
import org.eclipse.swt.events.PaintListener;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.GC;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.widgets.Composite;

/**
 * The general editor for hierarchies.
 *
 * @author Fabian Prasser
 * @param <T>
 */
public class HierarchyWizardEditor<T> implements HierarchyWizardView, IHierarchyFunctionEditorParent<T> {

    /** Var. */
    private HierarchyWizardModelGrouping<T>  model;
    
    /** Var. */
    private Composite                        composite;
    
    /** Var. */
    private Composite                        canvascomposite;
    
    /** Var. */
    private ScrolledComposite                scrolledcomposite;
    
    /** Var. */
    private CTabFolder                       folder;
    
    /** Var. */
    private HierarchyWizardEditorMenu<T>     menu;
    
    /** Var. */
    private HierarchyWizardEditorFunction<T> editor;
    
    /**
     * Creates a new instance.
     *
     * @param parent
     * @param model
     */
    public HierarchyWizardEditor(Composite parent, HierarchyWizardModelGrouping<T> model) {
        
        this.model = model;
        this.model.register(this);

        this.composite = new Composite(parent, SWT.NONE);
        this.composite.setLayoutData(SWTUtil.createFillGridData());
        this.composite.setLayout(SWTUtil.createGridLayout(1));
        
        this.scrolledcomposite = new ScrolledComposite(composite, SWT.H_SCROLL | SWT.V_SCROLL | SWT.BORDER);
        this.scrolledcomposite.setMinSize(200, 200);
        this.scrolledcomposite.setExpandHorizontal(true);
        this.scrolledcomposite.setExpandVertical(true);
        this.scrolledcomposite.setLayoutData(SWTUtil.createFillGridData());
        
        this.canvascomposite = new Composite(scrolledcomposite, SWT.NONE);
        this.scrolledcomposite.setContent(canvascomposite);
        
        this.canvascomposite.addPaintListener(new PaintListener(){
            @Override
            public void paintControl(PaintEvent e){
                paint(e.gc);
            }
        }); 
        
        this.canvascomposite.addMouseListener(new MouseAdapter(){
            @Override public void mouseUp(MouseEvent arg0) {
                
                if (HierarchyWizardEditor.this.model.getRenderer().select(arg0.x, arg0.y)){
                    updateSelectedElement();
                }

                if ((arg0.stateMask & SWT.BUTTON3) != 0 && HierarchyWizardEditor.this.model.getSelectedElement() != null){
                    menu.show(arg0.x, arg0.y);
                }
            }
        });
        
        this.menu = new HierarchyWizardEditorMenu<T>(canvascomposite, model);
        this.menu.addAddRightListener(new SelectionAdapter(){
            @Override public void widgetSelected(SelectionEvent arg0) {
                model.addRight(model.getSelectedElement());
                updateSelectedElement();
            }
        });
        this.menu.addMergeUpListener(new SelectionAdapter(){
            @Override public void widgetSelected(SelectionEvent arg0) {
                model.mergeUp(model.getSelectedElement());
                updateSelectedElement();
            }
        });
        this.menu.addAddAfterListener(new SelectionAdapter(){
            @Override public void widgetSelected(SelectionEvent arg0) {
                model.addAfter(model.getSelectedElement());
                updateSelectedElement();
            }
        });
        this.menu.addRemoveListener(new SelectionAdapter(){
            @Override public void widgetSelected(SelectionEvent arg0) {
                model.remove(model.getSelectedElement());
                updateSelectedElement();
            }
        });
        this.menu.addAddBeforeListener(new SelectionAdapter(){
            @Override public void widgetSelected(SelectionEvent arg0) {
                model.addBefore(model.getSelectedElement());
                updateSelectedElement();
            }
        });
        this.menu.addMergeDownListener(new SelectionAdapter(){
            @Override public void widgetSelected(SelectionEvent arg0) {
                model.mergeDown(model.getSelectedElement());
                updateSelectedElement();
            }
        });
        
        this.folder = new CTabFolder(composite, SWT.BORDER);
        this.folder.setSimple(false);
        this.folder.setLayoutData(SWTUtil.createFillHorizontallyGridData());
        this.createGeneralTab(folder);
        if (model.isShowIntervals()) this.createRangeTab(folder);
        if (model.isShowIntervals()) this.createIntervalTab(folder);
        this.createGroupTab(folder);
        this.folder.setSelection(0);
        
        this.model.update();
    }
    
    @Override
    public void setFunction(AggregateFunction<T> function) {
        model.setDefaultFunction(function);
        editor.setFunction(function);
        model.update();
    }

    /**
     * Set the controls layout data.
     *
     * @param object
     */
    public void setLayoutData(Object object){
        this.composite.setLayoutData(object);
    }

    /**
     * Update to show the selected element
     */
    public void updateSelectedElement() {
        HierarchyWizardEditor.this.model.updateUI(HierarchyWizardEditor.this);
        canvascomposite.redraw();
        Object selected = HierarchyWizardEditor.this.model.getSelectedElement();
        if (selected instanceof HierarchyWizardGroupingInterval){
            if (HierarchyWizardEditor.this.model.isShowIntervals()) folder.setSelection(2);
        } else if (selected instanceof HierarchyWizardGroupingGroup){
            if (HierarchyWizardEditor.this.model.isShowIntervals()) folder.setSelection(3);
            else folder.setSelection(1);
        }
    }
    
    @Override
    public void update() {
        this.canvascomposite.redraw();
        this.editor.update();
    }

    /**
     * Create a tab.
     *
     * @param tabFolder
     */
    private void createGeneralTab(CTabFolder tabFolder) {
    	CTabItem tabItem1 = new CTabItem(tabFolder, SWT.NULL);
        tabItem1.setText(Resources.getMessage("HierarchyWizardEditor.0")); //$NON-NLS-1$
        Composite parent = new Composite(tabFolder, SWT.NULL);
        parent.setLayout(SWTUtil.createGridLayout(2, false));
        parent.setLayoutData(SWTUtil.createFillHorizontallyGridData());
        editor = new HierarchyWizardEditorFunction<T>(this, model, parent, true);
        editor.setFunction(model.getDefaultFunction());
        tabItem1.setControl(parent);
    }

    /**
     * Create a tab.
     *
     * @param tabFolder
     */
    private void createGroupTab(CTabFolder tabFolder) {
    	CTabItem tabItem3 = new CTabItem(tabFolder, SWT.NULL);
        tabItem3.setText(Resources.getMessage("HierarchyWizardEditor.1")); //$NON-NLS-1$
        Composite parent = new Composite(tabFolder, SWT.NULL);
        parent.setLayout(SWTUtil.createGridLayout(1, false));
        new HierarchyWizardEditorGroup<T>(parent, model);
        tabItem3.setControl(parent);
        
    }

    /**
     * Create a tab.
     *
     * @param tabFolder
     */
    private void createIntervalTab(CTabFolder tabFolder) {
        CTabItem tabItem2 = new CTabItem(tabFolder, SWT.NULL);
        tabItem2.setText(Resources.getMessage("HierarchyWizardEditor.2")); //$NON-NLS-1$
        Composite parent = new Composite(tabFolder, SWT.NULL);
        parent.setLayout(SWTUtil.createGridLayout(2, false));
        new HierarchyWizardEditorInterval<T>(parent, model);
        tabItem2.setControl(parent);
    }
    
    /**
     * Create a tab.
     *
     * @param tabFolder
     */
    private void createRangeTab(CTabFolder tabFolder) {
    	CTabItem tabItem4 = new CTabItem(tabFolder, SWT.NULL);
        tabItem4.setText(Resources.getMessage("HierarchyWizardEditor.3")); //$NON-NLS-1$
        Composite parent = new Composite(tabFolder, SWT.NULL);
        parent.setLayout(SWTUtil.createGridLayout(2, false));
        new HierarchyWizardEditorRange<T>(parent, model, true);
        new HierarchyWizardEditorRange<T>(parent, model, false);
        tabItem4.setControl(parent);
    }

    /**
     * Draws a string.
     *
     * @param gc
     * @param string
     * @param r
     */
    private void drawString(GC gc, String string, Rectangle r) {
        gc.setFont(HierarchyWizardEditorRenderer.FONT);
        Point extent = gc.textExtent(string);
        int xx = r.x + (r.width - extent.x) / 2;
        int yy = r.y + (r.height - extent.y) / 2;
        gc.drawText(string, xx, yy, true);
    }

    /**
     * Is the component selected.
     *
     * @param component
     * @return
     */
    private boolean isSelected(RenderedComponent<T> component) {
        if (model.getSelectedElement() == null) return false;
        if (component instanceof RenderedInterval) {
            return ((RenderedInterval<T>)component).interval.equals(model.getSelectedElement());
        } else {
            return ((RenderedGroup<T>)component).group.equals(model.getSelectedElement());
        }
    }

    /**
     * Paints the intervals and fanouts.
     *
     * @param gc
     */
    protected void paint(GC gc) {
        
        model.getRenderer().update(gc);
        
        gc.setBackground(HierarchyWizardEditorRenderer.WIDGET_BACKGROUND);
        Point size = canvascomposite.getSize();
        gc.fillRectangle(0, 0, size.x, size.y);
        
        for (RenderedComponent<T> component : model.getRenderer().getComponents()) {
            
            Color foreground = HierarchyWizardEditorRenderer.NORMAL_FOREGROUND;
            Color alternative = HierarchyWizardEditorRenderer.ALTERNATIVE_FOREGROUND;
            Color background = HierarchyWizardEditorRenderer.NORMAL_BACKGROUND;
            
            if (isSelected(component)) {
                background = HierarchyWizardEditorRenderer.SELECTED_BACKGROUND;
                alternative = background;
            }
            
            if (!component.enabled) {
                foreground = HierarchyWizardEditorRenderer.DISABLED_FOREGROUND;
                background = HierarchyWizardEditorRenderer.DISABLED_BACKGROUND;
                alternative = background;
            }

            gc.setBackground(background);
            gc.fillRectangle(component.rectangle1);
            gc.setForeground(foreground);
            gc.drawRectangle(component.rectangle1);
            drawString(gc, component.bounds, component.rectangle1);

            gc.setBackground(foreground);
            gc.fillRectangle(component.rectangle2);
            gc.drawRectangle(component.rectangle2);
            gc.setForeground(alternative);
            drawString(gc, component.label, component.rectangle2);
        }
        scrolledcomposite.setMinSize(model.getRenderer().getMinSize());
    }
}
