// $Id:
//
// FORESTER -- software libraries and applications
// for evolutionary biology research and applications.
//
// Copyright (C) 2008-2009 Christian M. Zmasek
// Copyright (C) 2008-2009 Burnham Institute for Medical Research
// All rights reserved
//
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public
// License as published by the Free Software Foundation; either
// version 2.1 of the License, or (at your option) any later version.
//
// This library 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
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
//
// Contact: phylosoft @ gmail . com
// WWW: www.phylosoft.org/forester

package org.forester.tools;

import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.forester.phylogeny.Phylogeny;
import org.forester.phylogeny.PhylogenyNode;
import org.forester.phylogeny.data.Confidence;
import org.forester.phylogeny.iterators.PhylogenyNodeIterator;
import org.forester.util.ForesterUtil;

public final class ConfidenceAssessor {

    private ConfidenceAssessor() {
        // Hidden constructor.
    }

    private final static void addExternalNodesToMap( final Map<PhylogenyNode, Set<PhylogenyNode>> node_to_ext_nodes_map,
                                                     final PhylogenyNode node ) {
        final Set<PhylogenyNode> ex_descs = new HashSet<PhylogenyNode>();
        for( final PhylogenyNode n : node.getAllExternalDescendants() ) {
            if ( ex_descs.contains( n ) ) {
                throw new IllegalArgumentException( "node [" + n.toString() + "] of target is not unique" );
            }
            ex_descs.add( n );
        }
        node_to_ext_nodes_map.put( node, ex_descs );
    }

    private final static void checkPreconditions( final String confidence_type,
                                                  final Phylogeny[] evaluators,
                                                  final Phylogeny target,
                                                  final double value,
                                                  final int first,
                                                  final int last ) {
        if ( ( first < 0 ) || ( last < 0 ) ) {
            throw new IllegalArgumentException( "attempt to set first or last evaluator topology to use to a number less than zero" );
        }
        if ( evaluators.length < 1 ) {
            throw new IllegalArgumentException( "need at least one evaluator topology" );
        }
        if ( ForesterUtil.isEmpty( confidence_type ) ) {
            throw new IllegalArgumentException( "attempt to use empty confidence type" );
        }
        if ( value <= 0 ) {
            throw new IllegalArgumentException( "attempt to use zero or negative \'count value\'" );
        }
        if ( ( first != 0 ) || ( last != 0 ) ) {
            if ( ( last >= evaluators.length ) || ( last <= first ) ) {
                throw new IllegalArgumentException( "illegal value for last evaluator topology to use" );
            }
        }
        final Set<PhylogenyNode> nodes = new HashSet<PhylogenyNode>();
        for( final PhylogenyNodeIterator it = target.iteratorPostorder(); it.hasNext(); ) {
            final PhylogenyNode node = it.next();
            if ( nodes.contains( node ) ) {
                throw new IllegalArgumentException( "node [" + node + "] in target is not unique" );
            }
            nodes.add( node );
            final List<Confidence> confidences = node.getBranchData().getConfidences();
            for( final Confidence confidence : confidences ) {
                if ( confidence.getType().equals( confidence_type ) ) {
                    throw new IllegalArgumentException( "confidence [" + confidence_type
                            + "] is already present in target" );
                }
            }
        }
    }

    public final static void evaluate( final String confidence_type,
                                       final Phylogeny[] evaluators,
                                       final Phylogeny target,
                                       final boolean strict,
                                       final double value ) {
        evaluate( confidence_type, evaluators, target, strict, value, 0, 0 );
    }

    public final static void evaluate( final String confidence_type,
                                       final Phylogeny[] evaluators,
                                       final Phylogeny target,
                                       final boolean strict,
                                       final double value,
                                       final int first,
                                       final int last ) {
        try {
            checkPreconditions( confidence_type, evaluators, target, value, first, last );
        }
        catch ( final IllegalArgumentException e ) {
            throw e;
        }
        boolean all = true;
        if ( ( first != 0 ) || ( last != 0 ) ) {
            all = false;
        }
        int counter = 0;
        final Map<PhylogenyNode, Set<PhylogenyNode>> node_to_ext_nodes_map = new HashMap<PhylogenyNode, Set<PhylogenyNode>>();
        for( final Phylogeny evaluator : evaluators ) {
            if ( all || ( ( counter >= first ) && ( counter <= last ) ) ) {
                if ( strict ) {
                    if ( evaluator.getNumberOfExternalNodes() != target.getNumberOfExternalNodes() ) {
                        throw new IllegalArgumentException( "evaluator #" + counter
                                + " does not have the same number of external nodes ["
                                + evaluator.getNumberOfExternalNodes() + "] than the corresponding target ["
                                + target.getNumberOfExternalNodes() + "]" );
                    }
                }
                final TreeSplitMatrix s = new TreeSplitMatrix( evaluator, strict, target );
                for( final PhylogenyNodeIterator it = target.iteratorPostorder(); it.hasNext(); ) {
                    final PhylogenyNode node = it.next();
                    if ( !node.isExternal() && !node.isRoot() ) {
                        if ( node.getParent().isRoot()
                                && ( target.getRoot().getNumberOfDescendants() == 2 )
                                && ( target.getRoot().getChildNode1().isExternal() || target.getRoot().getChildNode2()
                                        .isExternal() ) ) {
                            continue;
                        }
                        if ( !node_to_ext_nodes_map.containsKey( node ) ) {
                            addExternalNodesToMap( node_to_ext_nodes_map, node );
                        }
                        final Set<PhylogenyNode> ex_descs = node_to_ext_nodes_map.get( node );
                        final Confidence c = ConfidenceAssessor.obtainConfidence( node, confidence_type );
                        if ( s.match( ex_descs ) ) {
                            c.setValue( c.getValue() + value );
                        }
                    }
                }
            }
            ++counter;
        }
    }

    private final static Confidence obtainConfidence( final PhylogenyNode n, final String confidence_type ) {
        final List<Confidence> confidences = n.getBranchData().getConfidences();
        Confidence match = null;
        for( final Confidence confidence : confidences ) {
            if ( confidence.getType().equals( confidence_type ) ) {
                if ( match != null ) {
                    throw new IllegalArgumentException( "confidence [" + confidence_type + "] is not unique" );
                }
                match = confidence;
            }
        }
        if ( match == null ) {
            match = new Confidence( 0, confidence_type );
            confidences.add( match );
        }
        return match;
    }
}
