/*
 * AttributeComboHelper.java
 *
 * Copyright (C) 2006-2014 Andrew Rambaut
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 */

package figtree.treeviewer.painters;

import figtree.treeviewer.TreeViewer;
import figtree.treeviewer.TreeViewerListener;
import figtree.treeviewer.decorators.ColourDecorator;
import jebl.evolution.graphs.Node;
import jebl.evolution.taxa.Taxon;
import jebl.evolution.trees.Tree;
import jebl.util.Attributable;

import javax.swing.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.*;

/**
 * @author Andrew Rambaut
 * @version $Id$
 *
 * $HeadURL$
 *
 * $LastChangedBy$
 * $LastChangedDate$
 * $LastChangedRevision$
 */
public class AttributeComboHelper {

    public AttributeComboHelper(final JComboBox attributeComboBox, final TreeViewer treeViewer) {

        this(attributeComboBox, treeViewer, null, null, false, false);

    }

    public AttributeComboHelper(final JComboBox attributeComboBox, final TreeViewer treeViewer,
                                final String defaultOption) {

        this(attributeComboBox, treeViewer, defaultOption, null, false, false);

    }

    public AttributeComboHelper(final JComboBox attributeComboBox, final TreeViewer treeViewer,
                                final String defaultOption, final LabelPainter.PainterIntent intent) {

        this(attributeComboBox, treeViewer, defaultOption, intent, false, false);

    }

    public AttributeComboHelper(final JComboBox attributeComboBox, final TreeViewer treeViewer,
                                final String defaultOption,
                                final boolean numericalOnly,
                                final boolean includeLineageColourings) {

        this(attributeComboBox, treeViewer, defaultOption, null, numericalOnly, includeLineageColourings);

    }

    public AttributeComboHelper(final JComboBox attributeComboBox, final TreeViewer treeViewer,
                                final LabelPainter.PainterIntent intent) {

        this(attributeComboBox, treeViewer, null, intent, false, false);

    }

    public AttributeComboHelper(final JComboBox attributeComboBox, final TreeViewer treeViewer,
                                final LabelPainter.PainterIntent intent,
                                final boolean numericalOnly) {

        this(attributeComboBox, treeViewer, null, intent, numericalOnly, false);
    }

    public AttributeComboHelper(final JComboBox attributeComboBox, final TreeViewer treeViewer,
                                final String defaultOption,
                                final LabelPainter.PainterIntent intent,
                                final boolean numericalOnly,
                                final boolean includeLineageColourings) {

        treeViewer.addTreeViewerListener(new TreeViewerListener() {
            public void treeChanged() {
                List<Tree> trees = treeViewer.getTrees();

                Object selected = attributeComboBox.getSelectedItem();

                addingItems = true;

                attributeComboBox.removeAllItems();

                if (defaultOption != null) {
                    attributeComboBox.addItem(defaultOption);
                }

                if (trees == null) {
                    return;
                }
                List<String> names = new ArrayList<String>();
                if (intent != null || numericalOnly) {
                    getAttributeNames(names, trees, intent, numericalOnly);
                } else {
                    getAttributeNames(names, trees, includeLineageColourings);
                }

                for (String name : names) {
                    attributeComboBox.addItem(name);
                }

                addingItems = false;

                if (selected != null) {
                    attributeComboBox.setSelectedItem(selected);
                }
            }

            public void treeSettingsChanged() {
                // nothing to do
            }
        });

        attributeComboBox.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent actionEvent) {
                if (!addingItems) {
                    fireAttributeSelectionChanged();
                }
            }
        });
    }

    public static void getAttributeNames(List<String> attributeNames, Collection<? extends Tree> trees,
                                         LabelPainter.PainterIntent intent,
                                         boolean numericalOnly) {

        Set<String> nodeAttributes = new TreeSet<String>();
        if (trees != null) {
            for (Tree tree : trees) {
                if (intent == LabelPainter.PainterIntent.TIP) {
                    for (Node node : tree.getExternalNodes()) {
                        nodeAttributes.addAll(node.getAttributeNames());
                    }
                    for (Taxon taxon : tree.getTaxa()) {
                        nodeAttributes.addAll(taxon.getAttributeNames());
                    }
                } else if (intent == LabelPainter.PainterIntent.NODE) {
                    for (Node node : tree.getInternalNodes()) {
                        nodeAttributes.addAll(node.getAttributeNames());
                    }
                } else if (intent == LabelPainter.PainterIntent.RANGE) {
                    for (Node node : tree.getInternalNodes()) {
                        for (String name : node.getAttributeNames()) {
                            Object attr = node.getAttribute(name);
                            if (attr instanceof Object[]) {
                                Object[] array = (Object[])attr;
                                if (array.length == 2 &&
                                        array[0] instanceof Number &&
                                        array[1] instanceof Number) {
                                    nodeAttributes.add(name);
                                }
                            }
                        }
                    }
                } else {
                    for (Node node : tree.getNodes()) {
                        for (String key : node.getAttributeMap().keySet()) {
                            nodeAttributes.add(key);
                        }
                    }
                }
            }
        }

        if (numericalOnly) {
            Set<String> continuousNodeAttributes = new TreeSet<String>();
            for (String nodeAttributeName : nodeAttributes) {
                boolean isNumerical = true;
                for (Tree tree : trees) {
                    if (!ColourDecorator.isNumerical(nodeAttributeName, tree.getNodes())) {
                        isNumerical = false;
                    }
                }
                if (isNumerical) {
                    continuousNodeAttributes.add(nodeAttributeName);
                }
            }
            nodeAttributes = continuousNodeAttributes;
        }

        if (intent != null) {
            switch( intent ) {
                case TIP: {
                    attributeNames.add(LabelPainter.NAMES);
                    attributeNames.add(LabelPainter.NODE_AGES);
                    attributeNames.add(LabelPainter.NODE_HEIGHTS);
                    attributeNames.add(LabelPainter.BRANCH_TIMES);
                    attributeNames.add(LabelPainter.BRANCH_LENGTHS);
                    break;
                }
                case NODE: {
                    if (nodeAttributes.contains("!name")) {
                        attributeNames.add(LabelPainter.NAMES);
                    }
                    attributeNames.add(LabelPainter.NODE_AGES);
                    attributeNames.add(LabelPainter.NODE_HEIGHTS);
                    attributeNames.add(LabelPainter.BRANCH_TIMES);
                    attributeNames.add(LabelPainter.BRANCH_LENGTHS);
                    break;
                }
                case BRANCH: {
                    if (nodeAttributes.contains("!name")) {
                        attributeNames.add(LabelPainter.NAMES);
                    }
                    attributeNames.add(LabelPainter.BRANCH_TIMES);
                    attributeNames.add(LabelPainter.BRANCH_LENGTHS);
                    attributeNames.add(LabelPainter.NODE_AGES);
                    attributeNames.add(LabelPainter.NODE_HEIGHTS);
                    break;
                }
            }
        }

        for (String attributeName : nodeAttributes) {
            if (!attributeName.startsWith("!")) {
                attributeNames.add(attributeName);
            }
        }
    }

    private static void getAttributeNames(List<String> attributeNames, Collection<? extends Tree> trees, final boolean includeLineageColourings) {
        for (Tree tree : trees) {
            Set<String> attributes = new TreeSet<String>();

            for (Attributable item : tree.getNodes()) {
                for (String name : item.getAttributeNames()) {
                    if (!name.startsWith("!")) {
                        Object attr = item.getAttribute(name);
                        if (!(attr instanceof Object[])) {
                            attributes.add(name);
                        } else {
                            boolean isColouring = true;

                            Object[] array = (Object[])attr;
                            boolean isIndex = true;
                            for (Object element : array) {
                                if (isIndex && !(element instanceof Integer) ||
                                        !isIndex && !(element instanceof Double)) {
                                    isColouring = false;
                                    break;
                                }
                                isIndex = !isIndex;
                            }

                            if (isIndex) {
                                // a colouring should finish on an index (which means isIndex should be false)...
                                isColouring = false;
                            }

                            if (isColouring && includeLineageColourings) {
                                attributes.add(name + " *");
                            }

                        }
                    }
                }
            }

            attributeNames.addAll(attributes);
        }
    }

    public void addListener(AttributeComboHelperListener listener) {
        listeners.add(listener);
    }

    private void fireAttributeSelectionChanged() {
        for (AttributeComboHelperListener listener : listeners) {
            listener.attributeComboChanged();
        }
    }

    private final List<AttributeComboHelperListener> listeners = new ArrayList<AttributeComboHelperListener>();
    private boolean addingItems = false;
}
