/*
 * 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.Iterator;
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.deidentifier.arx.gui.view.def.IComponent;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.CTabFolder;
import org.eclipse.swt.custom.CTabFolder2Adapter;
import org.eclipse.swt.custom.CTabFolderEvent;
import org.eclipse.swt.custom.CTabItem;
import org.eclipse.swt.events.SelectionAdapter;
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.layout.GridLayout;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Event;
import org.eclipse.swt.widgets.Listener;
import org.eclipse.swt.widgets.ToolBar;
import org.eclipse.swt.widgets.ToolItem;

/**
 * This class implements a titled folder.
 *
 * @author Fabian Prasser
 */
public class ComponentTitledFolder implements IComponent {

    /**
     * An entry in a folder
     * 
     * @author Fabian Prasser
     */
    private class TitledFolderEntry {

        /** Field */
        private String  text;
        /** Field */
        private Control control;
        /** Field */
        private Image   image;
        /** Field */
        private int     index;
        /** Field */
        private boolean hideable;
        
        /**
         * Creates a new instance
         * @param text
         * @param control
         * @param image
         * @param index
         * @param hideable
         */
        public TitledFolderEntry(String text, Control control, Image image, int index, boolean hideable) {
            this.text = text;
            this.control = control;
            this.image = image;
            this.index = index;
            this.hideable = hideable;
        }
    }

    /** Entries*/
    private List<TitledFolderEntry>    entries = new ArrayList<TitledFolderEntry>();

    /** The folder */
    private final CTabFolder           folder;

    /** Flag */
    private final boolean              supportsHidingElements;

    /** Listener */
    private SelectionListener          itemVisibilityListener;

    /**
     * Creates a new instance.
     *
     * @param parent
     * @param controller
     * @param bar
     * @param id
     */
    public ComponentTitledFolder(Composite parent, Controller controller, ComponentTitledFolderButtonBar bar, String id){
        this(parent, controller, bar, id, null, false, false);
    }
    /**
     * Creates a new instance.
     *
     * @param parent
     * @param controller
     * @param bar
     * @param id
     * @param bottom
     */
    public ComponentTitledFolder(Composite parent, 
                                 Controller controller, 
                                 ComponentTitledFolderButtonBar bar, 
                                 String id, 
                                 boolean bottom,
                                 boolean supportsHidingElements){
        this(parent, controller, bar, id, null, bottom, supportsHidingElements);
    }

    /**
     * Creates a new instance.
     *
     * @param parent
     * @param controller
     * @param bar
     * @param id
     * @param helpids
     */
    public ComponentTitledFolder(Composite parent, Controller controller, ComponentTitledFolderButtonBar bar, String id, Map<Composite, String> helpids){
        this(parent, controller, bar, id, helpids, false, false);
    }

    
    /**
     * Creates a new instance.
     *
     * @param parent
     * @param controller
     * @param bar
     * @param id
     * @param bottom
     */
    public ComponentTitledFolder(Composite parent, 
                                 Controller controller, 
                                 ComponentTitledFolderButtonBar bar, 
                                 String id, 
                                 Map<Composite, String> helpids,
                                 boolean bottom,
                                 boolean supportsHidingElements){

        int flags = SWT.BORDER | SWT.FLAT;
        if (bottom) flags |= SWT.BOTTOM;
        else flags |= SWT.TOP;
        
        this.supportsHidingElements = supportsHidingElements;
        
        this.folder = new CTabFolder(parent, flags);
        this.folder.setUnselectedCloseVisible(false);
        this.folder.setSimple(false);
        
        // Create help button
        if (bar == null) SWTUtil.createHelpButton(controller, folder, id, helpids);
        else createBar(controller, folder, bar);

        // Prevent closing
        this.folder.addCTabFolder2Listener(new CTabFolder2Adapter() {
            @Override
            public void close(final CTabFolderEvent event) {
                event.doit = false;
            }
        });
    }
    
    /**
     * @param arg0
     * @param arg1
     * @see org.eclipse.swt.widgets.Widget#addListener(int, org.eclipse.swt.widgets.Listener)
     */
    public void addListener(int arg0, Listener arg1) {
        folder.addListener(arg0, arg1);
    }

    /**
     * Adds a selection listener.
     *
     * @param listener
     */
    public void addSelectionListener(SelectionListener listener) {
        folder.addSelectionListener(listener);
    }
    
    /**
     * Creates a new entry in the folder.
     *
     * @param title
     * @param image
     * @return
     */
    public Composite createItem(String title, Image image){
        return createItem(title, image, getItemCount(), false);
    }

    /**
     * Creates a new entry in the folder.
     *
     * @param title
     * @param image
     * @param hideable
     * @return
     */
    public Composite createItem(String title, Image image, boolean hideable){
        return createItem(title, image, getItemCount(), hideable);
    }

    /**
     * Creates a new entry in the folder.
     *
     * @param title
     * @param image
     * @param index
     * @param hideable
     * @return
     */
    public Composite createItem(String title, Image image, int index, boolean hideable){

        Composite composite = new Composite(folder, SWT.NONE);
        composite.setLayout(new GridLayout());
        
        CTabItem item = new CTabItem(folder, SWT.NULL, index);
        item.setText(title);
        if (image!=null) item.setImage(image);
        item.setShowClose(false);
        item.setControl(composite);
        entries.add(new TitledFolderEntry(title, composite, image, index, hideable));
        return composite;
    }

    /**
     * Disposes the given item.
     *
     * @param text
     */
    public void disposeItem(String text) {
        for (CTabItem item : folder.getItems()) {
            if (item.getText().equals(text)) {
                item.dispose();
            }
        }
    }
    
    /**
     * Returns the button item for the given text.
     *
     * @param text
     * @return
     */
    public ToolItem getButtonItem(String text) {
        Control c = folder.getTopRight();
        if (c == null) return null;
        if (!(c instanceof ToolBar)) return null;
        ToolBar t = (ToolBar)c;
        for (ToolItem i : t.getItems()){
            if (i.getToolTipText().equals(text)) return i;
        }
        return null;
    }

    /**
     * Returns the number of items in the folder.
     *
     * @return
     */
    public int getItemCount() {
        return folder.getItemCount();
    }

    /**
     * Returns the selected control
     * @return
     */
    public Control getSelectedControl() {
        return folder.getSelection().getControl();
    }
    
    /**
     * Returns the currently selected index.
     *
     * @return
     */
    public int getSelectionIndex() {
        return folder.getSelectionIndex();
    }
    
    /**
     * @return
     * @see org.eclipse.swt.widgets.Control#getSize()
     */
    public Point getSize() {
        return folder.getSize();
    }

    /**
     * Returns the tab item for the given text.
     *
     * @param text
     * @return
     */
    public CTabItem getTabItem(String text) {
        for (CTabItem item : folder.getItems()){
            if (item.getText().equals(text)) {
                return item;
            }
        }
        return null;
    }

    /**
     * Returns all visible items
     * @return
     */
    public List<String> getVisibleItems() {
        List<String> result = new ArrayList<String>();
        for (CTabItem item : folder.getItems()) {
            result.add(item.getText());
        }
        return result;
    }

    /**
     * Enables/disables the component.
     *
     * @param b
     */
    public void setEnabled(boolean b) {
        folder.setEnabled(b);
    }

    /**
     * Sets the item visibility listener
     * @param listener
     */
    public void setItemVisibilityListener(SelectionListener listener) {
        this.itemVisibilityListener = listener;
    }

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

    /**
     * Selects the item with the given control
     * @param c
     */
    public void setSelectedControl(Control c) {
        for (CTabItem item : folder.getItems()) {
            if (item.getControl() == c) {
                folder.setSelection(item);
                return;
            }
        }
    }
    
    /**
     * Sets the current selection.
     *
     * @param index
     */
    public void setSelection(int index) {
        folder.setSelection(index);
    }

    /**
     * Sets the given items as visible
     * @param item
     */
    public void setVisibleItems(List<String> items) {
        
        boolean changed = false;
        
        for (String item : getAllHideableItems()) {
            if (items.contains(item)) {
                changed |= setVisible(item, true);
                if (this.folder.getItemCount() == 1) {
                    this.folder.setSelection(0);
                }
            } else {
                changed |= setVisible(item, false);
            }
        }
        
        if (changed && this.itemVisibilityListener != null) {
            Event event = new Event();
            event.widget = this.folder;
            this.itemVisibilityListener.widgetSelected(new SelectionEvent(event));
        }
    }

    /**
     * Creates the bar .
     *
     * @param controller
     * @param folder
     * @param bar
     */
    private void createBar(final Controller controller, final CTabFolder folder, final ComponentTitledFolderButtonBar bar) {
        ToolBar toolbar = new ToolBar(folder, SWT.FLAT);
        folder.setTopRight( toolbar, SWT.RIGHT );

        if (this.supportsHidingElements) {

            ToolItem item = new ToolItem( toolbar, SWT.PUSH );
            item.setImage(controller.getResources().getManagedImage("manage.png"));  //$NON-NLS-1$
            item.setToolTipText(Resources.getMessage("General.1")); //$NON-NLS-1$
            SWTUtil.createDisabledImage(item);
            item.addSelectionListener(new SelectionAdapter(){
                @Override
                public void widgetSelected(SelectionEvent arg0) {
                    List<String> result = controller.actionShowMultiSelectionDialog(folder.getShell(),
                                                                                    Resources.getMessage("ComponentTitledFolder.0"), //$NON-NLS-1$
                                                                                    Resources.getMessage("ComponentTitledFolder.1"), //$NON-NLS-1$
                                                                                    getAllHideableItems(),
                                                                                    getVisibleItems());
                    
                    if (result != null) {
                        setVisibleItems(result);
                    }
                }
            });
        }
        
        for (String title : bar.getTitles()){
            
            final String key = title;
            ToolItem item = null;
            if (bar.isToggle(title)) item = new ToolItem( toolbar, SWT.CHECK);
            else item = new ToolItem( toolbar, SWT.PUSH);
            item.setImage(bar.getImage(key));
            item.setToolTipText(title);
            SWTUtil.createDisabledImage(item);
            item.addSelectionListener(new SelectionAdapter(){
                @Override
                public void widgetSelected(SelectionEvent arg0) {
                    bar.getRunnable(key).run();
                }
            });
        }
        
        ToolItem item = new ToolItem( toolbar, SWT.PUSH );
        item.setImage(controller.getResources().getManagedImage("help.png"));  //$NON-NLS-1$
        item.setToolTipText(Resources.getMessage("General.0")); //$NON-NLS-1$
        SWTUtil.createDisabledImage(item);
        item.addSelectionListener(new SelectionAdapter(){
            @Override
            public void widgetSelected(SelectionEvent arg0) {
                if (bar.getHelpIds() == null || bar.getHelpIds().get(folder.getSelection().getControl()) == null) {
                    controller.actionShowHelpDialog(bar.getHelpId());
                } else {
                    controller.actionShowHelpDialog(bar.getHelpIds().get(folder.getSelection().getControl()));
                }
            }
        });
        
        int height = toolbar.computeSize(SWT.DEFAULT, SWT.DEFAULT).y;
        folder.setTabHeight(Math.max(height, folder.getTabHeight()));
    }

    /**
     * Returns all items
     * @return
     */
    private List<String> getAllHideableItems() {
        List<String> result = new ArrayList<String>();
        for (TitledFolderEntry entry : this.entries) {
            if (entry.hideable) {
                result.add(entry.text);
            }
        }
        return result;
    }
    
    /**
     * Returns a list of all invisible entries
     * @return
     */
    private List<TitledFolderEntry> getInvisibleEntries() {
        List<TitledFolderEntry> result = new ArrayList<TitledFolderEntry>();
        result.addAll(this.entries);
        for (CTabItem item : folder.getItems()){
            Iterator<TitledFolderEntry> iter = result.iterator();
            while (iter.hasNext()) {
                if (item.getText().equals(iter.next().text)) {
                    iter.remove();
                }
            }
        }
        return result;
    }

    /**
     * Sets the given item invisible
     * @param item
     */
    private boolean setInvisible(String text) {
        for (CTabItem item : folder.getItems()){
            label: if (item.getText().equals(text)) {
                for (TitledFolderEntry entry : this.entries) {
                    if (entry.text.equals(text) && !entry.hideable) {
                        break label;
                    }
                }
                item.dispose();
                return true;
            }
        }
        return false;
    }
    
    /**
     * Sets an entry visible
     * @return
     */
    private boolean setVisible(String text) {
        List<TitledFolderEntry> list = getInvisibleEntries();
        
        // Find
        for (TitledFolderEntry entry : list) {
            if (entry.text.equals(text)) {

                // Shift
                int index = entry.index;
                for (TitledFolderEntry other : list) {
                    if (other.index < entry.index) {
                        index--;
                    }
                }
                
                // Show
                CTabItem item = new CTabItem(folder, SWT.NULL, index);
                item.setText(entry.text);
                if (entry.image!=null) item.setImage(entry.image);
                item.setShowClose(false);
                item.setControl(entry.control);
                return true;
            }
        }
        return false;
    }

    /**
     * Sets the according item visible
     * @param item
     * @param visible
     */
    private boolean setVisible(String item, boolean visible) {
        if (!supportsHidingElements) {
            return false;
        }
        if (visible) {
            return this.setVisible(item);
        } else {
            return this.setInvisible(item);
        }
    }
}
