// $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
// Copyright (C) 2000-2001 Washington University School of Medicine
// and Howard Hughes Medical Institute
// 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.sdi;

import org.forester.phylogeny.Phylogeny;
import org.forester.phylogeny.PhylogenyNode;
import org.forester.phylogeny.data.Event;

/*
 * Implements our algorithm for speciation - duplication inference (SDI). <p>
 * Reference: </p> <ul> <li>Zmasek, C.M. and Eddy, S.R. (2001) "A simple
 * algorithm to infer gene duplication and speciation events on a gene tree".
 * Bioinformatics, in press. </ul> <p> The initialization is accomplished by:
 * </p> <ul> <li>method "linkExtNodesOfG()" of class SDI: setting the links for
 * the external nodes of the gene tree <li>"preorderReID(int)" from class
 * Phylogeny: numbering of nodes of the species tree in preorder <li>the
 * optional stripping of the species tree is accomplished by method
 * "stripTree(Phylogeny,Phylogeny)" of class Phylogeny </ul> <p> The recursion
 * part is accomplished by this class' method
 * "geneTreePostOrderTraversal(PhylogenyNode)". <p> Requires JDK 1.2 or greater.
 * 
 * @see SDI#linkNodesOfG()
 * 
 * @see Phylogeny#preorderReID(int)
 * 
 * @see
 * PhylogenyMethods#taxonomyBasedDeletionOfExternalNodes(Phylogeny,Phylogeny)
 * 
 * @see #geneTreePostOrderTraversal(PhylogenyNode)
 * 
 * @author Christian M. Zmasek
 * 
 * @version 1.102 -- last modified: 10/02/01
 */
public class SDIse extends SDI {

    /**
     * Constructor which sets the gene tree and the species tree to be compared.
     * species_tree is the species tree to which the gene tree gene_tree will be
     * compared to - with method "infer(boolean)". Both Trees must be completely
     * binary and rooted. The actual inference is accomplished with method
     * "infer(boolean)". The mapping cost L can then be calculated with method
     * "computeMappingCost()".
     * <p>
     * (Last modified: 01/11/01)
     * 
     * @see #infer(boolean)
     * @see SDI#computeMappingCostL()
     * @param gene_tree
     *            reference to a rooted binary gene Phylogeny to which assign
     *            duplication vs speciation, must have species names in the
     *            species name fields for all external nodes
     * @param species_tree
     *            reference to a rooted binary species Phylogeny which might get
     *            stripped in the process, must have species names in the
     *            species name fields for all external nodes
     * @throws SDIException 
     */
    public SDIse( final Phylogeny gene_tree, final Phylogeny species_tree ) throws SDIException {
        super( gene_tree, species_tree );
        _duplications_sum = 0;
        getSpeciesTree().preOrderReId();
        linkNodesOfG();
        geneTreePostOrderTraversal( getGeneTree().getRoot() );
    }

    // Helper method for updateM( boolean, PhylogenyNode, PhylogenyNode )
    // Calculates M for PhylogenyNode n, given that M for the two children
    // of n has been calculated.
    // (Last modified: 10/02/01)
    private void calculateMforNode( final PhylogenyNode n ) {
        if ( !n.isExternal() ) {
            final boolean was_duplication = n.isDuplication();
            PhylogenyNode a = n.getChildNode1().getLink();
            PhylogenyNode b = n.getChildNode2().getLink();
            while ( a != b ) {
                if ( a.getId() > b.getId() ) {
                    a = a.getParent();
                }
                else {
                    b = b.getParent();
                }
            }
            n.setLink( a );
            Event event = null;
            if ( ( a == n.getChildNode1().getLink() ) || ( a == n.getChildNode2().getLink() ) ) {
                event = Event.createSingleDuplicationEvent();
                if ( !was_duplication ) {
                    ++_duplications_sum;
                }
            }
            else {
                event = Event.createSingleSpeciationEvent();
                if ( was_duplication ) {
                    --_duplications_sum;
                }
            }
            n.getNodeData().setEvent( event );
        }
    } // calculateMforNode( PhylogenyNode )

    /**
     * Traverses the subtree of PhylogenyNode g in postorder, calculating the
     * mapping function M, and determines which nodes represent speciation
     * events and which ones duplication events.
     * <p>
     * Preconditions: Mapping M for external nodes must have been calculated and
     * the species tree must be labelled in preorder.
     * <p>
     * (Last modified: 01/11/01)
     * 
     * @param g
     *            starting node of a gene tree - normally the root
     */
    void geneTreePostOrderTraversal( final PhylogenyNode g ) {
        PhylogenyNode a, b;
        if ( !g.isExternal() ) {
            geneTreePostOrderTraversal( g.getChildNode( 0 ) );
            geneTreePostOrderTraversal( g.getChildNode( 1 ) );
            a = g.getChildNode( 0 ).getLink();
            b = g.getChildNode( 1 ).getLink();
            while ( a != b ) {
                if ( a.getId() > b.getId() ) {
                    a = a.getParent();
                }
                else {
                    b = b.getParent();
                }
            }
            g.setLink( a );
            // Determines whether dup. or spec.
            Event event = null;
            if ( ( a == g.getChildNode( 0 ).getLink() ) || ( a == g.getChildNode( 1 ).getLink() ) ) {
                event = Event.createSingleDuplicationEvent();
                ++_duplications_sum;
            }
            else {
                event = Event.createSingleSpeciationEvent();
            }
            g.getNodeData().setEvent( event );
        }
    } // geneTreePostOrderTraversal( PhylogenyNode )

    /**
     * Updates the mapping function M after the root of the gene tree has been
     * moved by one branch. It calculates M for the root of the gene tree and
     * one of its two children.
     * <p>
     * To be used ONLY by method "SDIunrooted.fastInfer(Phylogeny,Phylogeny)".
     * <p>
     * (Last modfied: 10/02/01)
     * 
     * @param prev_root_was_dup
     *            true if the previous root was a duplication, false otherwise
     * @param prev_root_c1
     *            child 1 of the previous root
     * @param prev_root_c2
     *            child 2 of the previous root
     * @return number of duplications which have been assigned in gene tree
     */
    int updateM( final boolean prev_root_was_dup, final PhylogenyNode prev_root_c1, final PhylogenyNode prev_root_c2 ) {
        final PhylogenyNode root = getGeneTree().getRoot();
        if ( ( root.getChildNode1() == prev_root_c1 ) || ( root.getChildNode2() == prev_root_c1 ) ) {
            calculateMforNode( prev_root_c1 );
        }
        else {
            calculateMforNode( prev_root_c2 );
        }
        Event event = null;
        if ( prev_root_was_dup ) {
            event = Event.createSingleDuplicationEvent();
        }
        else {
            event = Event.createSingleSpeciationEvent();
        }
        root.getNodeData().setEvent( event );
        calculateMforNode( root );
        return getDuplicationsSum();
    } // updateM( boolean, PhylogenyNode, PhylogenyNode )
} // End of class SDIse.
