/*
 * LinkageGroupSwap.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.operators;

import dr.evolution.alignment.Alignment;
import dr.evolution.util.Taxon;
import dr.evomodel.tree.HiddenLinkageModel;
import dr.inference.operators.OperatorFailedException;
import dr.inference.operators.SimpleMCMCOperator;
import dr.math.MathUtils;
import dr.xml.*;

import java.util.ArrayList;
import java.util.HashSet;

/**
 * @author Aaron Darling
 */
public class LinkageGroupSwap extends SimpleMCMCOperator {

	HiddenLinkageModel hlm;	
	int groupCount;
	int columnCount;

	public LinkageGroupSwap(HiddenLinkageModel hlm, double weight){
		this.hlm = hlm;
		groupCount = hlm.getLinkageGroupCount();
		columnCount = hlm.getData().getAlignment().getSiteCount();
        setWeight(weight);
	}

	@Override
	public double doOperation() throws OperatorFailedException {
		if(MathUtils.nextBoolean()){
			// swap all taxa in one group to a new group
			int A = MathUtils.nextInt(groupCount);
			int B = MathUtils.nextInt(groupCount);
			HashSet<Taxon> aTaxa = new HashSet<Taxon>(hlm.getGroup(A));
			HashSet<Taxon> bTaxa = new HashSet<Taxon>(hlm.getGroup(B));
			for(Taxon taxon : aTaxa){
				hlm.moveReadGroup(taxon, A, B);
			}
			for(Taxon taxon : bTaxa){
				hlm.moveReadGroup(taxon, B, A);
			}
		}else{
			// pick two linkage groups uniformly A, B
			// pick an alignment column uniformly X
			// move all reads in group A that are defined in column X to group B, and
			// vice versa from B to A.
			// pick a read uniformly at random, add it to a linkage group uniformly at random
			ArrayList<Taxon> aTaxa = new ArrayList<Taxon>();
			ArrayList<Taxon> bTaxa = new ArrayList<Taxon>();
			int A=0, B=0;
	
			// iterate until we actually find something to move
			// this could be done more efficiently, e.g. by limiting
			// choice of X and Y to groups that have something in col X.
			while(aTaxa.size()==0&&bTaxa.size()==0)
			{
				int X = MathUtils.nextInt(columnCount);
		
				A = MathUtils.nextInt(groupCount);
				B = MathUtils.nextInt(groupCount);
				if(A==B)
					continue;	// nothing to do.
		
				// find all reads intersecting column X
				Alignment aln = hlm.getData().getAlignment();
				for(int i=0; i<aln.getTaxonCount(); i++){
					int state = aln.getPatternState(i, X);
					if(state==hlm.getDataType().getGapState() || state==hlm.getDataType().getUnknownState())
						continue;	// seq undefined in this column
					if(hlm.getGroup(A).contains(aln.getTaxon(i))){
						aTaxa.add(aln.getTaxon(i));
					}
					if(hlm.getGroup(B).contains(aln.getTaxon(i))){
						bTaxa.add(aln.getTaxon(i));
					}
				}
			}
	
			// move taxa from A to B and from B to A
			for(Taxon taxon : aTaxa){
				hlm.moveReadGroup(taxon, A, B);
			}
			for(Taxon taxon : bTaxa){
				hlm.moveReadGroup(taxon, B, A);
			}
		}
		return 0;
	}

	public String getOperatorName() {
		return "linkageGroupSwap";
	}

	public String getPerformanceSuggestion() {
		return "Ask Aaron Darling to write a better operator";
	}

    public static XMLObjectParser LINKAGE_GROUP_SWAP_PARSER = new AbstractXMLObjectParser() {

        public String getParserName() {
            return "linkageGroupSwap";
        }

        public Object parseXMLObject(XMLObject xo) throws XMLParseException {

        	HiddenLinkageModel hlm = (HiddenLinkageModel)xo.getChild(HiddenLinkageModel.class);
            double weight = xo.getDoubleAttribute("weight");

            return new LinkageGroupSwap(hlm, weight);
        }

        //************************************************************************
        // AbstractXMLObjectParser implementation
        //************************************************************************

        public String getParserDescription() {
            return "This element represents an operator that swaps taxa among two linkage groups. ";
        }

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

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

        private final XMLSyntaxRule[] rules = {
                AttributeRule.newDoubleRule("weight"),
                new ElementRule(HiddenLinkageModel.class)
        };

    };
}
