/*
 * #%L
 * gitools-core
 * %%
 * Copyright (C) 2013 Universitat Pompeu Fabra - Biomedical Genomics group
 * %%
 * 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 3 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, see
 * <http://www.gnu.org/licenses/gpl-3.0.html>.
 * #L%
 */
/*******************************************************************************
 * Copyright 2013 Lars Behnke
 *
 * 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.gitools.heatmap.header;

import com.google.common.base.Joiner;
import com.google.common.base.Strings;
import org.gitools.api.analysis.Clusters;

import javax.xml.bind.annotation.*;
import java.util.*;

@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(propOrder = {"name", "parentName", "leaves", "distance", "weight", "color", "children"})
public class HierarchicalCluster implements Clusters, Comparable<HierarchicalCluster> {

    private String name;

    private String parentName;

    private int leaves;

    private Double distance;

    private Double weight = 0.0;

    private int color;

    @XmlElementWrapper(name = "children")
    @XmlElement(name = "child")
    private List<HierarchicalCluster> children;

    public HierarchicalCluster() {
        //JAXB requirement
    }

    public HierarchicalCluster(String... identifiers) {
        leaves = identifiers.length;
        if (identifiers.length == 1) {
            name = identifiers[0];
        }
    }

    public HierarchicalCluster(Set<String> leftIds, Set<String> rightIds) {
        leaves = leftIds.size() + rightIds.size();
    }

    public Double getDistance() {
        return distance;
    }

    public void setDistance(Double distance) {
        this.distance = distance;
    }

    public List<HierarchicalCluster> getChildren() {
        if (children == null) {
            children = new ArrayList<>();
        }

        return children;
    }

    public void setChildren(List<HierarchicalCluster> children) {
        this.children = children;
    }

    public void setParent(HierarchicalCluster parent) {
        this.parentName = parent.getName();
    }

    public Set<String> getIdentifiers(Set<String> idSet) {
        if (isLeaf()) {
            idSet.add(name);
        } else {
            for (HierarchicalCluster child : children) {
                child.getIdentifiers(idSet);
            }
        }
        return idSet;
    }

    public Map<String, String> getMaxLevel(Map<String, String> idSet) {
        if (isLeaf()) {
            idSet.put(name, parentName);
        } else {
            for (HierarchicalCluster child : children) {
                child.getMaxLevel(idSet);
            }
        }
        return idSet;
    }

    public Set<String> getIdentifiers() {
        return getIdentifiers(getIdentifiers(new HashSet<String>()));
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
        for (HierarchicalCluster child : children) {
            child.setParentName(name);
        }
    }

    public void addChild(HierarchicalCluster cluster) {
        getChildren().add(cluster);
    }

    public Double getWeight() {
        return weight;
    }

    public void setWeight(Double weight) {
        this.weight = (weight == null ? 0.0 : weight);
    }

    public boolean contains(HierarchicalCluster cluster) {
        return getChildren().contains(cluster);
    }

    @Override
    public String toString() {
        return name + "-" + leaves;
    }

    public boolean isLeaf() {
        return leaves == 1 || children == null || children.size() == 0;
    }

    public int countLeafs() {
        return countLeafs(this, 0);
    }

    public int countLeafs(HierarchicalCluster node, int count) {
        if (node.isLeaf()) count++;
        for (HierarchicalCluster child : node.getChildren()) {
            count += child.countLeafs();
        }
        return count;
    }

    public void toConsole(int indent) {
        for (int i = 0; i < indent; i++) {
            System.out.print("  ");

        }
        String name = Joiner.on('&').join(getIdentifiers()) + (isLeaf() ? " (leaf)" : "") + (distance != null ? "  distance: " + distance : "");
        System.out.println(name);
        for (HierarchicalCluster child : getChildren()) {
            child.toConsole(indent + 1);
        }
    }

    public double getTotalDistance() {
        double dist = getDistance() == null ? 0 : getDistance();
        if (getChildren().size() > 0) {
            dist += children.get(0).getTotalDistance();
        }
        return dist;

    }

    public int getLeaves() {
        return leaves;
    }

    public void setLeaves(int leaves) {
        this.leaves = leaves;
    }

    public int getColor() {
        return color;
    }

    public void setColor(int color) {
        this.color = color;
    }

    @Override
    public Collection<String> getClusters() {
        throw new UnsupportedOperationException();
    }

    @Override
    public Set<String> getItems(String cluster) {
        throw new UnsupportedOperationException();
    }

    @Override
    public Map<String, Set<String>> getClustersMap() {
        throw new UnsupportedOperationException();
    }

    @Override
    public int compareTo(HierarchicalCluster o) {

        int result;
        if (o == null || o.getWeight() == null) {
            result = -1;
        } else if (getWeight() == null) {
            result = 1;
        } else {
            Double w1 = getWeight() / leaves;
            Double w2 = o.getWeight() / o.getIdentifiers().size();
            result = w1.compareTo(w2);
        }

        return result;
    }

    public String getParentName() {
        return parentName;
    }

    public void setParentName(String parentName) {
        this.parentName = parentName;
    }

    public HierarchicalCluster getHierarchicalSubCluster(String clusterName) {

        HierarchicalCluster foundCluster = null;

        if (Strings.isNullOrEmpty(clusterName)) {
            return this;
        }

        for (HierarchicalCluster cluster : this.getChildren()) {
            if (cluster.getName().equals(clusterName)) {
                foundCluster = cluster;
                break;
            }
        }

        if (foundCluster == null) {
            for (HierarchicalCluster cluster : this.getChildren()) {
                foundCluster = cluster.getHierarchicalSubCluster(clusterName);
                if (foundCluster != null) {
                    break;
                }
            }
        }


        return foundCluster;
    }
}
