/*
 * TransmissionStatistic.java
 *
 * Copyright (c) 2002-2015 Alexei Drummond, Andrew Rambaut and Marc Suchard
 *
 * This file is part of BEAST.
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership and licensing.
 *
 * BEAST 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
 * of the License, or (at your option) any later version.
 *
 *  BEAST 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 BEAST; if not, write to the
 * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
 * Boston, MA  02110-1301  USA
 */

package dr.evomodel.transmission;

import dr.evolution.tree.NodeRef;
import dr.evolution.tree.Tree;
import dr.evolution.util.Taxon;
import dr.evomodel.tree.TreeStatistic;
import dr.inference.model.BooleanStatistic;
import dr.inference.model.Statistic;
import dr.xml.*;

import java.util.HashSet;
import java.util.Set;


/**
 * A statistic for the compatibility of a viruses tree with a transmission
 * history. The transmission history consists of a number of
 * hosts with known history of transmission. The viruses tree should have tip
 * attributes specifying which host they are from (host="").
 *
 * @author Andrew Rambaut
 * @author Alexei Drummond
 * @version $Id: TransmissionStatistic.java,v 1.11 2005/06/27 21:19:15 rambaut Exp $
 */
public class TransmissionStatistic extends BooleanStatistic implements TreeStatistic {

    // PUBLIC STUFF

    public static final String TRANSMISSION_STATISTIC = "transmissionStatistic";

    public TransmissionStatistic(String name, TransmissionHistoryModel transmissionHistoryModel, Tree virusTree) {

        super(name);

        this.transmissionHistoryModel = transmissionHistoryModel;
        this.virusTree = virusTree;

        setupHosts();
    }

    public TransmissionStatistic(String name, Tree hostTree, Tree virusTree) {

        super(name);

        this.hostTree = hostTree;
        this.virusTree = virusTree;

        setupHosts();
    }

    private void setupHosts() {

        if (transmissionHistoryModel != null) {
            hostCount = transmissionHistoryModel.getHostCount();

        } else {
            hostCount = hostTree.getTaxonCount();
        }

        donorHost = new int[hostCount];
        donorHost[0] = -1;
        transmissionTime = new double[hostCount];
        transmissionTime[0] = java.lang.Double.POSITIVE_INFINITY;

        if (transmissionHistoryModel != null) {
            for (int i = 0; i < transmissionHistoryModel.getTransmissionEventCount(); i++)
            {
                TransmissionHistoryModel.TransmissionEvent event = transmissionHistoryModel.getTransmissionEvent(i);

                int host1 = transmissionHistoryModel.getHostIndex(event.getDonor());
                int host2 = transmissionHistoryModel.getHostIndex(event.getRecipient());

                donorHost[host2] = host1;
                transmissionTime[host2] = event.getTransmissionTime();
            }
        } else {
            setupHostsTree(hostTree.getRoot());
        }
    }

    private int setupHostsTree(NodeRef node) {

        int host;

        if (hostTree.isExternal(node)) {
            host = node.getNumber();
        } else {

            // This traversal assumes that the first child is the donor
            // and the second is the recipient

            int host1 = setupHostsTree(hostTree.getChild(node, 0));
            int host2 = setupHostsTree(hostTree.getChild(node, 1));

            donorHost[host2] = host1;
            transmissionTime[host2] = hostTree.getNodeHeight(node);

            host = host1;
        }

        return host;
    }

    public void setTree(Tree tree) {
        this.virusTree = tree;
    }

    public Tree getTree() {
        return virusTree;
    }

    public String getDimensionName(int dim) {
        String recipient = transmissionHistoryModel.getHost(dim).getId();

        String donor = (donorHost[dim] == -1 ? "" : transmissionHistoryModel.getHost(donorHost[dim]).getId() + "->");
        return "transmission(" + donor + recipient + ")";
    }

    public int getDimension() {
        return hostCount;
    }

    /**
     * @return true if the population tree is compatible with the species tree
     */
    public boolean getBoolean(int dim) {
        Set<Integer> incompatibleSet = new HashSet<Integer>();
        setupHosts();
        isCompatible(virusTree.getRoot(), incompatibleSet);

        return !incompatibleSet.contains(dim);
    }

    private int isCompatible(NodeRef node, Set<Integer> incompatibleSet) {

        double height = virusTree.getNodeHeight(node);
        int host;

        if (virusTree.isExternal(node)) {
            Taxon hostTaxon = (Taxon) virusTree.getTaxonAttribute(node.getNumber(), "host");
            if (transmissionHistoryModel != null) {
                host = transmissionHistoryModel.getHostIndex(hostTaxon);
            } else {
                host = hostTree.getTaxonIndex(hostTaxon);
            }

            if (host != -1 && height > transmissionTime[host]) {
                // This means that the sequence was sampled
                // before the host was infected so we should probably flag
                // this as an error before we get to this point...
                throw new RuntimeException("Sequence " + virusTree.getNodeTaxon(node) + ", was sampled ("+height+") before host, " + hostTaxon + ", was infected ("+transmissionTime[host]+")");
            }

        } else {

            // Tree should be bifurcating...
            int host1 = isCompatible(virusTree.getChild(node, 0), incompatibleSet);

            int host2 = isCompatible(virusTree.getChild(node, 1), incompatibleSet);

            if (host1 == host2) {

                host = host1;
                while (height > transmissionTime[host]) {
                    host = donorHost[host];
                }

            } else {
                while (height > transmissionTime[host1]) {
                    host1 = donorHost[host1];
                }

                while (height > transmissionTime[host2]) {
                    host2 = donorHost[host2];
                }

                if (host1 != host2) {
                    if (transmissionTime[host1] < transmissionTime[host2]) {
                        incompatibleSet.add(host1);
                        host = host2;
                    } else {
                        incompatibleSet.add(host2);
                        host = host1;
                    }
                } else {
                    host = host1;
                }
            }
        }

        return host;
    }

    // ****************************************************************
    // Private and protected stuff
    // ****************************************************************

    public static XMLObjectParser PARSER = new AbstractXMLObjectParser() {

        public String getParserName() {
            return TRANSMISSION_STATISTIC;
        }

        public Object parseXMLObject(XMLObject xo) throws XMLParseException {

            String name = xo.getStringAttribute("name");
            Tree virusTree = (Tree) xo.getElementFirstChild("parasiteTree");

            if (xo.getChild(TransmissionHistoryModel.class) != null) {
                TransmissionHistoryModel history = (TransmissionHistoryModel) xo.getChild(TransmissionHistoryModel.class);
                return new TransmissionStatistic(name, history, virusTree);
            } else {
                Tree hostTree = (Tree) xo.getElementFirstChild("hostTree");
                return new TransmissionStatistic(name, hostTree, virusTree);
            }

        }

        public String getParserDescription() {
            return "A statistic that returns true if the given parasite tree is compatible with the host tree.";
        }

        public Class getReturnType() {
            return Statistic.class;
        }

        public XMLSyntaxRule[] getSyntaxRules() {
            return rules;
        }

        private XMLSyntaxRule[] rules = new XMLSyntaxRule[]{
                new StringAttributeRule("name", "A name for this statistic for the purpose of logging"),
                new XORRule(
                        new ElementRule("hostTree",
                                new XMLSyntaxRule[]{new ElementRule(Tree.class)}),
                        new ElementRule(TransmissionHistoryModel.class,
                                "This describes the transmission history of the patients.")
                ),
                new ElementRule("parasiteTree",
                        new XMLSyntaxRule[]{new ElementRule(Tree.class)})
        };
    };

    /**
     * The host tree.
     */
    private Tree hostTree = null;

    private TransmissionHistoryModel transmissionHistoryModel = null;

    /**
     * The viruses tree.
     */
    private Tree virusTree = null;

    /**
     * The number of hosts.
     */
    private int hostCount;

    /**
     * The donor host for each recipient host (-1 for initial host).
     */
    private int[] donorHost;

    /**
     * The time of transmission into this host (POSITIVE_INFINITY for initial host).
     */
    private double[] transmissionTime;
}