/*
 * ObsoleteARGNewEventOperator.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
 */

/*
 * AddRemoveSubtreeOperator.java
 *
 * (c) 2002-2005 BEAST Development Core Team
 *
 * This package may be distributed under the
 * Lesser Gnu Public Licence (LGPL)
 */
package dr.evomodel.arg.operators;

import dr.evolution.tree.NodeRef;
import dr.evolution.tree.Tree;
import dr.evomodel.arg.ARGModel;
import dr.evomodel.arg.ARGModel.Node;
import dr.evomodelxml.tree.TreeModelParser;
import dr.inference.model.CompoundParameter;
import dr.inference.model.Parameter;
import dr.inference.operators.*;
import dr.math.MathUtils;
import dr.math.functionEval.GammaFunction;
import dr.xml.*;

import java.util.ArrayList;

/**
 * Implements the subtree slide move.
 *
 * @author Marc Suchard
 * @version $Id: ObsoleteARGAddRemoveEventOperator.java,v 1.18.2.4 2006/11/06 01:38:30 msuchard Exp $
 */

public class ObsoleteARGNewEventOperator extends AbstractCoercableOperator {
//		SimpleMCMCOperator implements CoercableMCMCOperator {

    public static final String SUBTREE_SLIDE = "newARGEvent";
    public static final String SWAP_RATES = "swapRates";
    public static final String SWAP_TRAITS = "swapTraits";
    public static final String MAX_VALUE = "maxTips";
    public static final String SINGLE_PARTITION = "singlePartitionProbability";
    public static final String IS_RECOMBINATION = "isRecombination";

    public static final String JUST_INTERNAL = "justInternalNodes";
    public static final String INTERNAL_AND_ROOT = "internalAndRootNodes";
    public static final String NODE_RATES = TreeModelParser.NODE_RATES;

    private ARGModel arg = null;
    private double size = 1.0;
    private boolean gaussian = false;
    private double singlePartitionProbability = 0.0;
    private boolean isRecombination = false;
    //   private boolean swapRates;
    //   private boolean swapTraits;
    //	private int mode = CoercableMCMCOperator.DEFAULT;
    private CompoundParameter internalNodeParameters;
    private CompoundParameter internalAndRootNodeParameters;
    private CompoundParameter nodeRates;
//	private int maxTips = 1;

    public ObsoleteARGNewEventOperator(ARGModel arg, int weight, double size, boolean gaussian,
                                       boolean swapRates, boolean swapTraits, CoercionMode mode,
                                       CompoundParameter param1,
                                       CompoundParameter param2,
                                       CompoundParameter param3,
                                       double singlePartitionProbability, boolean isRecombination) {
        super(mode);
        this.arg = arg;
        setWeight(weight);
//		this.maxTips = maxTips;
        this.size = size;
        this.gaussian = gaussian;
//        this.swapRates = swapRates;
//        this.swapTraits = swapTraits;
        this.internalNodeParameters = param1;
        this.internalAndRootNodeParameters = param2;
        this.nodeRates = param3;
        this.singlePartitionProbability = singlePartitionProbability;
        this.isRecombination = isRecombination;

//		this.mode = mode;
    }


    /**
     * Do a add/remove reassortment node operation
     *
     * @return the log-transformed hastings ratio
     */
    public double doOperation() throws OperatorFailedException {
//		System.err.println("Starting AddRemove Operation");

        double logq = 0;
        try {
            if (MathUtils.nextDouble() < 0.5)
                logq = AddOperation();
            else
                logq = RemoveOperation();
        } catch (Exception ofe) {
//			System.err.println(ofe);
            if (ofe.getMessage().compareTo("No reassortment nodes to remove.") != 0) {
                System.err.println("Catch: " + ofe.getMessage());
                System.exit(-1);
            }
        }
//        if (arg.isBifurcationDoublyLinked(arg.getRoot()))
//            throw new OperatorFailedException("trouble with double-rooted root");
//	    System.err.println("logq = "+logq);
        return logq;
    }

    private int findPotentialNodesToRemove(ArrayList<NodeRef> list) {
        int count = 0;
        int n = arg.getNodeCount();
//	    int max = arg.getReassortmentNodeCount();
        Node root = (Node) arg.getRoot();
        for (int i = 0; i < n; i++) {
            Node node = (Node) arg.getNode(i);
            if (node.isReassortment()
                    && ((node.leftParent != root && node.leftParent.isBifurcation()) ||
                    (node.rightParent != root && node.rightParent.isBifurcation()))
//	                && !(
//		                    (node.leftParent == root && node.rightParent.isReassortment()) ||
//				            (node.rightParent== root && node.leftParent.isReassortment()))
//		            && !(node.leftParent == root && node.rightParent == root)
//		            && (node.leftParent.isBifurcation() || node.rightParent.isBifurcation())
                    ) {
                if (list != null)
                    list.add(node);
                count++;
            }
        }
//	    System.err.printf("max = %d, available = %d\n",max,count);
        return count;
    }

    private double RemoveOperation() throws OperatorFailedException {
        double logq = 0;

//	    System.err.println("Starting remove ARG operation.");
//	    System.err.println("Before remove:\n"+arg.toGraphString());

        // 1. Draw reassortment node uniform randomly

        ArrayList<NodeRef> potentialNodes = new ArrayList<NodeRef>();

        int totalPotentials = findPotentialNodesToRemove(potentialNodes);
        if (totalPotentials == 0)
            throw new OperatorFailedException("No reassortment nodes to remove.");
//	    System.err.println("potentials exist!");
        Node recNode = (Node) potentialNodes.get(MathUtils.nextInt(totalPotentials));
//        logq += Math.log(totalPotentials);

        double reverseReassortmentHeight = 0;
        double reverseBifurcationHeight = 0;

        double treeHeight = arg.getNodeHeight(arg.getRoot());

//	    System.err.println("RecNode to remove = "+recNode.number);
        reverseReassortmentHeight = arg.getNodeHeight(recNode);

        arg.beginTreeEdit();
        boolean doneSomething = false;
        Node recParent = recNode.leftParent;
        Node recChild = recNode.leftChild;
        if (recNode.leftParent == recNode.rightParent) { // Doubly linked.
            Node recGrandParent = recParent.leftParent;
//	        reverseBifurcationHeight = arg.getNodeHeight(recParent);

//            reverseReassortmentSpan = arg.getNodeHeight(recParent) - arg.getNodeHeight(recChild);
//            reverseBifurcationSpan = arg.getNodeHeight(recGrandParent) - arg.getNodeHeight(recChild);
//            if (arg.isRoot(recParent)) { // This case should never happen as double links
//                arg.setRoot(recChild);    // to root can not be added or removed.
//	            System.err.println("doubled link root?");
//	            System.exit(-1);
//            } else {
            //Node recGrandParent = recParent1.leftParent; // And currently recParent must be a bifurcatio
            //System.err.println("recGrand   ="+recGrandParent.number);
            arg.doubleRemoveChild(recGrandParent, recParent);
            arg.doubleRemoveChild(recNode, recChild);
            if (recGrandParent.bifurcation)
                arg.singleAddChild(recGrandParent, recChild);
            else
                arg.doubleAddChild(recGrandParent, recChild);
//            }
            doneSomething = true;
            // There are not left/right choices to be made for doubly linked removals.
            // End doubly linked.
        } else { // Two different parents.
            Node recParent1 = recNode.leftParent;
            Node recParent2 = recNode.rightParent;
            if (!recParent1.bifurcation || recParent1.isRoot()) { // One orientation is valid
                recParent1 = recNode.rightParent;
                recParent2 = recNode.leftParent;
            } else if (recParent2.bifurcation && !recParent2.isRoot()) { // Both orientations are valid
                if (MathUtils.nextDouble() < 0.5) { // choose equally likely
                    recParent1 = recNode.rightParent;
                    recParent2 = recNode.leftParent;
                }
                logq += Math.log(2);
            }

            Node recGrandParent = recParent1.leftParent; // And currently recParent must be a bifurcatio

            Node otherChild = recParent1.leftChild;
            if (otherChild == recNode)
                otherChild = recParent1.rightChild;

            if (recGrandParent.bifurcation)
                arg.singleRemoveChild(recGrandParent, recParent1);
            else
                arg.doubleRemoveChild(recGrandParent, recParent1);

            arg.singleRemoveChild(recParent1, otherChild);
            if (recParent2.bifurcation)
                arg.singleRemoveChild(recParent2, recNode);
            else
                arg.doubleRemoveChild(recParent2, recNode);
            arg.doubleRemoveChild(recNode, recChild);
            if (otherChild != recChild) {
                if (recGrandParent.bifurcation)
                    arg.singleAddChild(recGrandParent, otherChild);
                else
                    arg.doubleAddChild(recGrandParent, otherChild);
                if (recParent2.bifurcation)
                    arg.singleAddChild(recParent2, recChild);
                else
                    arg.doubleAddChild(recParent2, recChild);
            } else {
                if (recGrandParent.bifurcation)
                    arg.singleAddChildWithOneParent(recGrandParent, otherChild);
                else
                    arg.doubleAddChildWithOneParent(recGrandParent, otherChild);
                if (recParent2.bifurcation)
                    arg.singleAddChildWithOneParent(recParent2, recChild);
                else
                    arg.doubleAddChildWithOneParent(recParent2, recChild);
            }
//			System.err.println("Sanity check in Remove Operator");
//			sanityCheck();
//			System.err.println("End Remove Operator in sanity check");
            doneSomething = true;

            recParent = recParent1;
        }

        reverseBifurcationHeight = arg.getNodeHeight(recParent);

        if (doneSomething) {
            try {
                arg.contractARGWithRecombinant(recParent, recNode,
                        internalNodeParameters, internalAndRootNodeParameters, nodeRates);
            } catch (Exception e) {
                System.err.println("here");
                System.err.println(e);

            }
        }

        arg.pushTreeSizeChangedEvent();
        arg.endTreeEdit();
//        try {
//            arg.checkTreeIsValid();
//        } catch (MutableTree.InvalidTreeException ite) {
//            throw new RuntimeException(ite.toString() + "\n" + arg.toString()
//                    + "\n" + Tree.Utils.uniqueNewick(arg, arg.getRoot()));
//        }

//	    double d1 = findPotentialAttachmentPoints(reverseBifurcationHeight,null);
//	    double d2 = findPotentialAttachmentPoints(reverseReassortmentHeight,null);
//	    System.err.printf("d1 = %5.4f, d2 = %5.4f\n",d1,d2);

//	    logq -= Math.log(findPotentialAttachmentPoints(reverseBifurcationHeight,null));
//	    logq -= Math.log(findPotentialAttachmentPoints(reverseReassortmentHeight,null));

//        int nodes = arg.getInternalNodeCount() - 1;
//
//        logq += 3*Math.log(2);
//
//        logq = 100;

//	    System.err.println("logq remove = "+logq);
        logq = 0;
//	    logq -= 10;
//	    logq += Math.log(totalPotentials);
        return logq; // 1 / total potentials * 1 / 2 (if valid) * length1 * length2 * attachmentSisters
    }

    private static double lnGamma(double x) {
        if (x == 1 || x == 2)
            return 0.0;
        return GammaFunction.logGamma(x);
    }

    private void checkAllHeights() {
        int len = arg.getInternalNodeCount();
        System.err.println("# internal nodes = " + len);
        int n = internalNodeParameters.getParameterCount();
        System.err.println("VSCP (" + n + ")");
        for (int i = 0; i < n; i++) {
            System.err.println(internalNodeParameters.getParameterValue(i));
        }
        n = arg.getInternalNodeCount();
        System.err.println("Checking all internal nodes (" + n + ") via tree:");
        for (int i = 0; i < n; i++) {
            NodeRef node = arg.getInternalNode(i);
            System.err.print(Tree.Utils.uniqueNewick(arg, node) + " ");
            System.err.println(((Node) node).getHeight());
        }
    }

    private int findPotentialAttachmentPoints(double time, ArrayList<NodeRef> list) {
        int count = 0;
        for (int i = 0, n = arg.getNodeCount(); i < n; i++) {
            NodeRef nr = arg.getNode(i);
            if (!arg.isRoot(nr) && arg.getNodeHeight(nr) < time) {
                if (arg.getNodeHeight(arg.getParent(nr, 0)) > time) {
                    if (list != null)
                        list.add(nr);
                    count++;
                }
                if (arg.isReassortment(nr) && arg.getNodeHeight(arg.getParent(nr, 1)) > time) {
                    if (list != null)
                        list.add(nr);
                    count++;
                }
            }
        }
        return count;
    }

    private double drawRandomPartitioning(Parameter partitioning) {
        double logq = 0;
        int len = arg.getNumberOfPartitions();
        if (len == 2) {
//            boolean first = MathUtils.nextBoolean();
            if (partitioning != null) {
                if (MathUtils.nextBoolean())
                    partitioning.setParameterValueQuietly(0, 1.0);
                else
                    partitioning.setParameterValueQuietly(1, 1.0);
            }
            return Math.log(2);
        }
        if (isRecombination) {
            logq += drawRandomRecombination(partitioning);
        } else {
            logq += drawRandomReassortment(partitioning);
        }
        return logq;
    }


    /* Draws a new partitioning.
      * With probability singlePartitionProbability, one bit is set;
      * otherwise, all bits are selected via a random permutation
      *
      */
    private double drawRandomReassortment(Parameter partitioning) {
        int len = arg.getNumberOfPartitions();
        double logq = 0;
        if (MathUtils.nextDouble() < singlePartitionProbability) {
            if (partitioning != null)
                partitioning.setParameterValueQuietly(MathUtils.nextInt(len), 1.0);
            return Math.log(len);
        }
        int[] permutation = MathUtils.permuted(len);
        int cut = MathUtils.nextInt(len - 1);
        for (int i = 0; i < len; i++) {
            logq += Math.log(i + 1);
            if (i > cut && partitioning != null)
                partitioning.setParameterValueQuietly(permutation[i], 1.0);
        }
        logq += Math.log(len - 1);
        return logq;
    }

    /* Draws a new partitioning.
      * A break-pt is drawn uniformly
      *
      */
    private double drawRandomRecombination(Parameter partitioning) {
        int len = arg.getNumberOfPartitions();
        double logq = 0;
        double leftValue = MathUtils.nextInt(2);
        double rightValue = 1.0 - leftValue;
        logq += Math.log(2);
        if (partitioning != null) {
            int cut = MathUtils.nextInt(len - 1);
            for (int i = 0; i <= cut; i++)
                partitioning.setParameterValueQuietly(i, leftValue);
            for (int i = cut + 1; i < len; i++)
                partitioning.setParameterValueQuietly(i, rightValue);
        }
        logq += Math.log(len - 1);
        return logq;
    }


    private double AddOperation() throws OperatorFailedException {

        double logq = 0;

        // Draw attachment point for new bifurcation

//	    System.err.println("Starting add operation.");

        ArrayList<NodeRef> potentialBifurcationChildren = new ArrayList<NodeRef>();
        ArrayList<NodeRef> potentialReassortmentChildren = new ArrayList<NodeRef>();

        double treeHeight = arg.getNodeHeight(arg.getRoot());

        double newBifurcationHeight = treeHeight * MathUtils.nextDouble();
        double newReassortmentHeight = treeHeight * MathUtils.nextDouble();
        if (newReassortmentHeight > newBifurcationHeight) {
            double temp = newReassortmentHeight;
            newReassortmentHeight = newBifurcationHeight;
            newBifurcationHeight = temp;
        }

//	    logq += 2.0 * Math.log(treeHeight); // Uniform before sorting

        int totalPotentialBifurcationChildren = findPotentialAttachmentPoints(
                newBifurcationHeight, potentialBifurcationChildren);
        int totalPotentialReassortmentChildren = findPotentialAttachmentPoints(
                newReassortmentHeight, potentialReassortmentChildren);

        if (totalPotentialBifurcationChildren == 0 || totalPotentialReassortmentChildren == 0) {
            throw new RuntimeException("Unable to find attachment points.");
        }

        Node recNode = (Node) potentialReassortmentChildren.get(MathUtils
                .nextInt(totalPotentialReassortmentChildren));
//	    logq += Math.log(totalPotentialReassortmentChildren);

        Node recParentL = recNode.leftParent;
        Node recParentR = recNode.rightParent;
        Node recParent = recParentL;
        if (recParentL != recParentR) {
            if (arg.getNodeHeight(recParentL) < newReassortmentHeight)
                recParent = recParentR;
            else if (arg.getNodeHeight(recParentR) > newReassortmentHeight
                    && MathUtils.nextDouble() > 0.5)
                recParent = recParentR;
        }


        Node sisNode = (Node) potentialBifurcationChildren.get(MathUtils
                .nextInt(totalPotentialBifurcationChildren));
//	    logq += Math.log(totalPotentialBifurcationChildren);

        Node sisParentL = sisNode.leftParent;
        Node sisParentR = sisNode.rightParent;
        Node sisParent = sisParentL;
        if (sisParentL != sisParentR) {
            if (arg.getNodeHeight(sisParentL) < newBifurcationHeight)
                sisParent = sisParentR;
            else if (arg.getNodeHeight(sisParentR) > newBifurcationHeight
                    && MathUtils.nextDouble() > 0.5)
                sisParent = sisParentR;
        }

        double newBifurcationRateCategory = 1.0;
        double newReassortmentRateCategory = 1.0;

        Node newBifurcation = arg.new Node();
        newBifurcation.heightParameter = new Parameter.Default(newBifurcationHeight);
        newBifurcation.rateParameter = new Parameter.Default(newBifurcationRateCategory);
        newBifurcation.setupHeightBounds();

        Node newReassortment = arg.new Node();
        newReassortment.bifurcation = false;
        newReassortment.heightParameter = new Parameter.Default(newReassortmentHeight);
        newReassortment.rateParameter = new Parameter.Default(newReassortmentRateCategory);
        newReassortment.setupHeightBounds();

        arg.beginTreeEdit();
        if (sisParent.bifurcation)
            arg.singleRemoveChild(sisParent, sisNode);
        else
            arg.doubleRemoveChild(sisParent, sisNode);
        if (sisNode != recNode) {
            if (recParent.bifurcation)
                arg.singleRemoveChild(recParent, recNode);
            else
                arg.doubleRemoveChild(recParent, recNode);
        }
        if (sisParent.bifurcation)
            arg.singleAddChild(sisParent, newBifurcation);
        else
            arg.doubleAddChild(sisParent, newBifurcation);
        if (sisNode != recNode)
            arg.singleAddChild(newBifurcation, sisNode);
        arg.doubleAddChild(newReassortment, recNode);

        Parameter partitioning = new Parameter.Default(arg.getNumberOfPartitions());

//        logq +=
        drawRandomPartitioning(partitioning);
//		System.err.println("point 1");

        if (sisNode != recNode) {
            arg.addChildAsRecombinant(newBifurcation, recParent,
                    newReassortment, partitioning);
        } else {
            arg.addChildAsRecombinant(newBifurcation, newBifurcation,
                    newReassortment, partitioning);
        }

        arg.expandARGWithRecombinant(newBifurcation, newReassortment,
                internalNodeParameters,
                internalAndRootNodeParameters,
                nodeRates);

//		System.err.println("point 2");

        arg.pushTreeSizeChangedEvent();

        arg.endTreeEdit();
//        try {
//            arg.checkTreeIsValid();
//        } catch (MutableTree.InvalidTreeException ite) {
//            throw new RuntimeException(ite.toString() + "\n" + arg.toString()
//                    + "\n" + Tree.Utils.uniqueNewick(arg, arg.getRoot()));
//        }

//	    double d1 = findPotentialNodesToRemove(null);
//	    System.err.printf("d1 = %5.4f\n",d1);

//        logq -= Math.log(findPotentialNodesToRemove(null));
//        if (!(recNode.leftParent.isBifurcation() && recNode.rightParent.isRoot()) &&
//                !(recNode.rightParent.isBifurcation() && recNode.leftParent.isRoot()))
//            logq -= Math.log(2.0);

//	    System.err.println("End add ARG operation.");

//        int nodes = arg.getInternalNodeCount() - 1;

//
/*	    int i = findPotentialNodesToRemove(null);
	    if (i==0) {
		    System.err.println("why can't i remove this one?");
		    System.err.println("graph:"+arg.toGraphString());
		    System.exit(1);
		    return Double.NEGATIVE_INFINITY ;
	    }*/
        logq = 0;
//	    logq -= Math.log(findPotentialNodesToRemove(null));
//	    System.err.println("logq add = "+logq);
//	    System.err.println("After add:\n"+arg.toGraphString());
        return logq;
    }

//	if( !recParent1.bifurcation || recParent1.isRoot() ) { // One orientation is valid


    public void sanityCheck() {
        int len = arg.getNodeCount();
        for (int i = 0; i < len; i++) {
            Node node = (Node) arg.getNode(i);
            if (node.bifurcation) {
                boolean equalChild = (node.leftChild == node.rightChild);
                if ((equalChild && node.leftChild != null)) {
                    if (!node.leftChild.bifurcation && ((node.leftChild).leftParent == node))
                        ;
                    else {
                        System.err.println("Node " + (i + 1) + " is insane.");
                        System.err.println(arg.toGraphString());
                        System.exit(-1);
                    }
                }
            } else {
                if ((node.leftChild != node.rightChild)) {
                    System.err.println("Node " + (i + 1) + " is insane.");
                    System.err.println(arg.toGraphString());
                    System.exit(-1);
                }
            }
            if (!node.isRoot()) {
                double d;
                d = node.getHeight();
            }
        }
    }

    private int times = 0;

    private double getDelta() {
        if (!gaussian) {
            return (MathUtils.nextDouble() * size) - (size / 2.0);
        } else {
            return MathUtils.nextGaussian() * size;
        }
    }


    public double getSize() {
        return size;
    }

    public void setSize(double size) {
        this.size = size;
    }

    public double getCoercableParameter() {
        return Math.log(getSize());
    }

    public void setCoercableParameter(double value) {
        setSize(Math.exp(value));
    }

    public double getRawParameter() {
        return getSize();
    }

//	public int getMode() {
//		return mode;
//	}

    public double getTargetAcceptanceProbability() {
        return 0.234;
    }


    public String getPerformanceSuggestion() {
        double prob = MCMCOperator.Utils.getAcceptanceProbability(this);
        double targetProb = getTargetAcceptanceProbability();

        double ws = OperatorUtils.optimizeWindowSize(getSize(), Double.MAX_VALUE, prob, targetProb);

        if (prob < getMinimumGoodAcceptanceLevel()) {
            return "Try decreasing size to about " + ws;
        } else if (prob > getMaximumGoodAcceptanceLevel()) {
            return "Try increasing size to about " + ws;
        } else return "";
    }

    public String getOperatorName() {
        return SUBTREE_SLIDE;
    }

    public static dr.xml.XMLObjectParser PARSER = new dr.xml.AbstractXMLObjectParser() {

        public String getParserName() {
            return SUBTREE_SLIDE;
        }

        public Object parseXMLObject(XMLObject xo) throws XMLParseException {

            boolean swapRates = false;
            boolean swapTraits = false;

            double singlePartitionProbability = 0.0;
            boolean isRecombination = false;

            CoercionMode mode = CoercionMode.parseMode(xo);

//			int mode = CoercableMCMCOperator.DEFAULT;
//			if (xo.hasAttribute(AUTO_OPTIMIZE)) {
//				if (xo.getBooleanAttribute(AUTO_OPTIMIZE)) {
//					mode = CoercableMCMCOperator.COERCION_ON;
//				} else {
//					mode = CoercableMCMCOperator.COERCION_OFF;
//				}
//			}

            if (xo.hasAttribute(SINGLE_PARTITION)) {
                singlePartitionProbability = xo.getDoubleAttribute(SINGLE_PARTITION);
            }

            if (xo.hasAttribute(IS_RECOMBINATION)) {
                isRecombination = xo.getBooleanAttribute(IS_RECOMBINATION);

            }

            if (xo.hasAttribute(SWAP_RATES)) {
                swapRates = xo.getBooleanAttribute(SWAP_RATES);
            }
            if (xo.hasAttribute(SWAP_TRAITS)) {
                swapTraits = xo.getBooleanAttribute(SWAP_TRAITS);
            }
            Object obj = xo.getChild(ARGModel.class);
            //TreeModel tmp = (TreeModel)xo.getChild(TreeModel.class);
            ARGModel treeModel = null;
            //if( (tmp.TREE_MODEL).compareTo(VariableSizeTreeModel.TREE_MODEL) == 0) {
            if (obj instanceof ARGModel) {
                //System.err.println("Found VSTM");
                treeModel = (ARGModel) obj;
            } else {
                System.err.println("Must specify a variable size tree model to use the AddRemoveSubtreeOperators");
                System.exit(-1);
            }

               // bug, getChild(String) returns an xmlObject;   not in XML rules
            CompoundParameter parameter1 = null; //(CompoundParameter) xo.getChild(JUST_INTERNAL);
            CompoundParameter parameter2 = null; //CompoundParameter) xo.getChild(INTERNAL_AND_ROOT);
            CompoundParameter parameter3 = null; //(CompoundParameter) xo.getChild(NODE_RATES);

            int weight = xo.getIntegerAttribute("weight");
//            int maxTips = xo.getIntegerAttribute(MAX_VALUE);
            double size = xo.getDoubleAttribute("size");
            boolean gaussian = xo.getBooleanAttribute("gaussian");
            return new ObsoleteARGNewEventOperator(treeModel, weight, size, gaussian, swapRates, swapTraits,
                    mode, parameter1, parameter2, parameter3, singlePartitionProbability, isRecombination);
        }

        public String getParserDescription() {
            return "An operator that slides a subarg.";
        }

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

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

        private final XMLSyntaxRule[] rules = {
                AttributeRule.newIntegerRule("weight"),
//                AttributeRule.newIntegerRule(MAX_VALUE),
                AttributeRule.newDoubleRule("size"),
                AttributeRule.newBooleanRule("gaussian"),
                AttributeRule.newBooleanRule(SWAP_RATES, true),
                AttributeRule.newBooleanRule(SWAP_TRAITS, true),
                AttributeRule.newBooleanRule(AUTO_OPTIMIZE, true),
                new ElementRule(ARGModel.class)//,
//			new ElementRule(Parameter.class)
        };
    };

}
