/*
 * XMLGenerator.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.app.bss;

import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.util.ArrayList;

import dr.app.beagle.evomodel.parsers.MG94CodonModelParser;
import dr.app.beagle.tools.parsers.BeagleSequenceSimulatorParser;
import dr.app.beagle.tools.parsers.PartitionParser;
import dr.app.beauti.util.XMLWriter;
import dr.evolution.datatype.AminoAcids;
import dr.evolution.datatype.DataType;
import dr.evolution.tree.Tree;
import dr.evolution.util.Taxa;
import dr.evolution.util.Taxon;
import dr.evomodel.sitemodel.SiteModel;
import dr.evomodel.substmodel.AminoAcidModelType;
import dr.evomodel.substmodel.NucModelType;
import dr.evomodel.tree.TreeModel;
import dr.evomodelxml.branchratemodel.DiscretizedBranchRatesParser;
import dr.evomodelxml.branchratemodel.StrictClockBranchRatesParser;
import dr.evomodelxml.coalescent.CoalescentSimulatorParser;
import dr.evomodelxml.coalescent.ConstantPopulationModelParser;
import dr.evomodelxml.coalescent.ExponentialGrowthModelParser;
import dr.evomodelxml.sitemodel.GammaSiteModelParser;
import dr.evomodelxml.substmodel.EmpiricalAminoAcidModelParser;
import dr.evomodelxml.substmodel.FrequencyModelParser;
import dr.evomodelxml.substmodel.GTRParser;
import dr.evomodelxml.substmodel.HKYParser;
import dr.evomodelxml.substmodel.TN93Parser;
import dr.evomodelxml.substmodel.YangCodonModelParser;
import dr.evomodelxml.tree.TreeModelParser;
import dr.evoxml.NewickParser;
import dr.evoxml.SequenceParser;
import dr.evoxml.TaxaParser;
import dr.evoxml.TaxonParser;
import dr.evoxml.util.XMLUnits;
import dr.inference.distribution.ExponentialDistributionModel;
import dr.inference.model.ParameterParser;
import dr.inferencexml.distribution.DistributionModelParser;
import dr.inferencexml.distribution.InverseGaussianDistributionModelParser;
import dr.inferencexml.distribution.LogNormalDistributionModelParser;
import dr.util.Attribute;
import dr.xml.Report;
import dr.xml.XMLParser;

/**
 * @author Filip Bielejec
 * @version $Id$
 */
public class XMLGenerator {

	private PartitionDataList dataList;

	public XMLGenerator(PartitionDataList dataList) {

		this.dataList = dataList;

	}// END: Constructor

	public void generateXML(File file) throws IOException {

		XMLWriter writer = new XMLWriter(new BufferedWriter(
				new FileWriter(file)));

		// //////////////
		// ---header---//
		// //////////////

		writer.writeText("<?xml version=\"1.0\" standalone=\"yes\"?>");
		writer.writeComment("Generated by "
				+ BeagleSequenceSimulatorApp.LONG_NAME + " "
				+ BeagleSequenceSimulatorApp.VERSION);

		writer.writeOpenTag("beast");
		writer.writeBlankLine();

		// ////////////////////
		// ---taxa element---//
		// ////////////////////

		try {

			int suffix = 1;
			ArrayList<Taxa> taxaList = new ArrayList<Taxa>();
			for (PartitionData data : dataList) {

				if (data.record == null) {

					throw new RuntimeException("Set Tree Model in Partitions tab for " + suffix + " partition.");

				} else {

					Taxa taxa = null;
					if (data.demographicModelIndex == 0 && data.record.isTreeSet()) {
						
						 taxa = new Taxa(data.record.getTree().asList()); 
						
					} else if (data.demographicModelIndex == 0 && data.record.isTaxaSet()) { 
					
						throw new RuntimeException("Data and demographic model incompatible for partition " + suffix);
						
					} else if( (data.demographicModelIndex > 0 && data.demographicModelIndex <= PartitionData.lastImplementedIndex) && data.record.isTreeSet()) {
						
						taxa = new Taxa(data.record.getTree().asList()); 
						
					} else if((data.demographicModelIndex > 0 && data.demographicModelIndex <= PartitionData.lastImplementedIndex) && data.record.isTaxaSet()) {
						
						 taxa = data.record.getTaxa();
						
					} else {
						
						//
						
					}// END: demo model check
					
						if (taxaList.size() == 0 
								| !Utils.isTaxaInList(taxa, taxaList)
								) {

							data.taxaIdref += suffix;

							writeTaxa(taxa, writer, String.valueOf(suffix));
							writer.writeBlankLine();

							taxaList.add(taxa);

//							 System.out.println("NOT IN LIST");

						} else {

							int index = Utils.taxaIsIdenticalWith(taxa, taxaList) + 1;
							data.taxaIdref += index;

//							 System.out.println("IDENTICAL WITH " + index);

						}

				}// END: treeModel set check

				suffix++;

			}// END: partition loop

		} catch (Exception e) {

			throw new RuntimeException("Taxa generation has failed:\n"
					+ e.getMessage());

		}// END: try-catch block

		// ////////////////////////
		// ---topology element---//
		// ////////////////////////
		
		try {	
			
			int suffix = 1;
			ArrayList<TreeModel> treeModelList = new ArrayList<TreeModel>();
			for (PartitionData data : dataList) {
		
				if (data.demographicModelIndex == 0) {
				
					TreeModel treeModel = data.createTreeModel();

					if (treeModelList.size() == 0 | !Utils.isTreeModelInList(treeModel, treeModelList)) {

						data.treeModelIdref += suffix;

						writeNewick(treeModel, writer, String.valueOf(suffix));
						writer.writeBlankLine();

						treeModelList.add(treeModel);

					} else {

						int index = Utils.treeModelIsIdenticalWith(treeModel, treeModelList) + 1;
						data.treeModelIdref += index;

					}// END: list check
				
				} else if(data.demographicModelIndex > 0 && data.demographicModelIndex <= PartitionData.lastImplementedIndex){
					
						data.demographicModelIdref += suffix;
						data.treeModelIdref += suffix;
						
						writeDemographicModel(data, writer, String.valueOf(suffix));
						writer.writeBlankLine();
					
				} else {
					
					// throw exception
					
				}// END: demo model check
			
				suffix++;
			}// END: partition loop
			
		} catch (Exception e) {

			throw new RuntimeException("Topology generation has failed:\n"
					+ e.getMessage());

		}// END: try-catch block	
		
		// //////////////////////////
		// ---tree model element---//
		// //////////////////////////

		try {

			int suffix = 1;
			ArrayList<TreesTableRecord> recordsList = new ArrayList<TreesTableRecord>();
			for (PartitionData data : dataList) {

				TreesTableRecord record = data.record;
				if (recordsList.size() == 0 | !Utils.isRecordInList(record, recordsList)) {

					writeTreeModel(writer, String.valueOf(suffix));
					writer.writeBlankLine();

					recordsList.add(record);
					
				}

				suffix++;

			}// END: partition loop
			
		} catch (Exception e) {

			throw new RuntimeException("Tree model generation has failed:\n"
					+ e.getMessage());

		}// END: try-catch block

		// //////////////////////////////////
		// ---branch rates model element---//
		// //////////////////////////////////

		try {

			int suffix = 1;
			ArrayList<PartitionData> partitionList = new ArrayList<PartitionData>();
			for (PartitionData data : dataList) {

				if (partitionList.size() == 0
						| !Utils.isElementInList(data, partitionList,
								Utils.BRANCH_RATE_MODEL_ELEMENT)) {

					data.clockModelIdref += suffix;

					writeBranchRatesModel(data, writer, String.valueOf(suffix));
					writer.writeBlankLine();
					partitionList.add(data);

					// System.out.println("NOT IN LIST");

				} else {

					int index = Utils.isIdenticalWith(data, partitionList,
							Utils.BRANCH_RATE_MODEL_ELEMENT) + 1;
					data.clockModelIdref += index;

					// System.out.println("IDENTICAL WITH " + index);

				}

				suffix++;

			}// END: partition loop

		} catch (Exception e) {

			throw new RuntimeException("Clock model generation has failed:\n"
					+ e.getMessage());

		}// END: try-catch block

		// ///////////////////////////////
		// ---frequency model element---//
		// ///////////////////////////////

		try {

			int suffix = 1;
			ArrayList<PartitionData> partitionList = new ArrayList<PartitionData>();
			for (PartitionData data : dataList) {

				if (partitionList.size() == 0
						| !Utils.isElementInList(data, partitionList,
								Utils.FREQUENCY_MODEL_ELEMENT)) {

					data.frequencyModelIdref += suffix;

					writeFrequencyModel(data, writer);
					writer.writeBlankLine();
					partitionList.add(data);

//					System.out.println("NOT IN LIST");

				} else {

					int index = Utils.isIdenticalWith(data, partitionList,
							Utils.FREQUENCY_MODEL_ELEMENT) + 1;
					data.frequencyModelIdref += index;

//					System.out.println("IDENTICAL WITH " + index);

				}

				suffix++;

			}// END: partition loop

		} catch (Exception e) {

			throw new RuntimeException(
					"Frequency model generation has failed:\n" + e.getMessage());

		}// END: try-catch block

		// ////////////////////////////
		// ---branch model element---//
		// ////////////////////////////

		try {

			int suffix = 1;
			ArrayList<PartitionData> partitionList = new ArrayList<PartitionData>();
			for (PartitionData data : dataList) {

				if (partitionList.size() == 0
						| !Utils.isElementInList(data, partitionList,
								Utils.BRANCH_MODEL_ELEMENT)) {

					data.substitutionModelIdref += suffix;

					writeBranchModel(data, writer, String.valueOf(suffix));
					writer.writeBlankLine();
					partitionList.add(data);

				} else {

					int index = Utils.isIdenticalWith(data, partitionList,
							Utils.BRANCH_MODEL_ELEMENT) + 1;
					data.substitutionModelIdref += index;

				}

				suffix++;

			}// END: partition loop

		} catch (Exception e) {

			throw new RuntimeException("Branch model generation has failed:\n"
					+ e.getMessage());

		}// END: try-catch block

		// ///////////////////////////////
		// ---site rate model element---//
		// ///////////////////////////////

		try {

			int suffix = 1;
			ArrayList<PartitionData> partitionList = new ArrayList<PartitionData>();
			for (PartitionData data : dataList) {

				if (partitionList.size() == 0
						| !Utils.isElementInList(data, partitionList,
								Utils.SITE_RATE_MODEL_ELEMENT)) {

					data.siteRateModelIdref += suffix;

					writeSiteRateModel(data, writer, suffix);
					writer.writeBlankLine();
					partitionList.add(data);

				} else {

					int index = Utils.isIdenticalWith(data, partitionList,
							Utils.SITE_RATE_MODEL_ELEMENT) + 1;
					data.siteRateModelIdref += index;

				}

				suffix++;

			}// END: partition loop

		} catch (Exception e) {

			System.err.println(e);
			throw new RuntimeException(
					"Site rate model generation has failed:\n" + e.getMessage());

		}// END: try-catch block

		// /////////////////////////////////////////
		// ---beagle sequence simulator element---//
		// /////////////////////////////////////////

		try {

			writeBeagleSequenceSimulator(writer);
			writer.writeBlankLine();
			
		} catch (Exception e) {

			throw new RuntimeException(
					"Beagle Sequence Simulator element generation has failed:\n"
							+ e.getMessage());

		}// END: try-catch block

		// //////////////////////
		// ---report element---//
		// //////////////////////

		try {

			writeReport(writer);
			writer.writeBlankLine();

		} catch (Exception e) {

			System.err.println(e);
			throw new RuntimeException(
					"Report element generation has failed:\n" + e.getMessage());

		}// END: try-catch block

		writer.writeCloseTag("beast");
		writer.flush();
		writer.close();

		// reset all idrefs
		for (PartitionData data : dataList) {
			data.resetIdrefs();
		}

	}// END: generateXML

	private void writeBeagleSequenceSimulator(XMLWriter writer) {

		writer.writeOpenTag(
				BeagleSequenceSimulatorParser.BEAGLE_SEQUENCE_SIMULATOR,
				new Attribute[] {
						new Attribute.Default<String>(XMLParser.ID, "simulator"), //
						new Attribute.Default<String>(
								BeagleSequenceSimulatorParser.PARALLEL, String
										.valueOf(dataList.useParallel)), //
										new Attribute.Default<String>(
												BeagleSequenceSimulatorParser.OUTPUT, String
														.valueOf(dataList.outputFormat))						
				});

		int suffix = 1;
		for (PartitionData data : dataList) {

			writer.writeOpenTag(
					PartitionParser.PARTITION,
					new Attribute[] {
							new Attribute.Default<String>(PartitionParser.FROM,
									String.valueOf(data.from)),
							new Attribute.Default<String>(PartitionParser.TO,
									String.valueOf(data.to)),
							new Attribute.Default<String>(
									PartitionParser.EVERY, String
											.valueOf(data.every)) });

			writer.writeIDref(TreeModel.TREE_MODEL, data.treeModelIdref);

			int substitutionModelIndex = data.substitutionModelIndex;
			switch (substitutionModelIndex) {

			case 0: // HKY

				writer.writeIDref(NucModelType.HKY.getXMLName(),
						data.substitutionModelIdref);
				break;

			case 1: // GTR

				writer.writeIDref(GTRParser.GTR_MODEL,
						data.substitutionModelIdref);
				break;

			case 2: // TN93

				writer.writeIDref(NucModelType.TN93.getXMLName(),
						data.substitutionModelIdref);
				break;

			case 3: // Yang Codon Model

				writer.writeIDref(YangCodonModelParser.YANG_CODON_MODEL,
						data.substitutionModelIdref);
				break;

			case 4: // Blosum62

				writer.writeIDref(EmpiricalAminoAcidModelParser.EMPIRICAL_AMINO_ACID_MODEL,
						data.substitutionModelIdref);
				break;	
				
			case 5: // CPREV

				writer.writeIDref(EmpiricalAminoAcidModelParser.EMPIRICAL_AMINO_ACID_MODEL,
						data.substitutionModelIdref);
				break;

			case 6: // Dayhoff

				writer.writeIDref(EmpiricalAminoAcidModelParser.EMPIRICAL_AMINO_ACID_MODEL,
						data.substitutionModelIdref);
				break;
				
			case 7: // FLU

				writer.writeIDref(EmpiricalAminoAcidModelParser.EMPIRICAL_AMINO_ACID_MODEL,
						data.substitutionModelIdref);
				break;
				
			case 8: // JTT

				writer.writeIDref(EmpiricalAminoAcidModelParser.EMPIRICAL_AMINO_ACID_MODEL,
						data.substitutionModelIdref);
				break;
				
			case 9: // LG

				writer.writeIDref(EmpiricalAminoAcidModelParser.EMPIRICAL_AMINO_ACID_MODEL,
						data.substitutionModelIdref);
				break;
				
			case 10: // MTREV

				writer.writeIDref(EmpiricalAminoAcidModelParser.EMPIRICAL_AMINO_ACID_MODEL,
						data.substitutionModelIdref);
				break;
				
			case 11: // WAG

				writer.writeIDref(EmpiricalAminoAcidModelParser.EMPIRICAL_AMINO_ACID_MODEL,
						data.substitutionModelIdref);
				break;
				
			}// END: switch

			writer.writeIDref(SiteModel.SITE_MODEL, data.siteRateModelIdref);

			int clockModel = data.clockModelIndex;
			switch (clockModel) {

			case 0: // StrictClock

				writer.writeIDref(
						StrictClockBranchRatesParser.STRICT_CLOCK_BRANCH_RATES,
						data.clockModelIdref);
				break;

			case 1: // LognormalRelaxedClock

				writer.writeIDref(
						DiscretizedBranchRatesParser.DISCRETIZED_BRANCH_RATES,
						data.clockModelIdref);
				break;

			case 2: // ExponentialRelaxedClock

				writer.writeIDref(
						DiscretizedBranchRatesParser.DISCRETIZED_BRANCH_RATES,
						data.clockModelIdref);
				break;

			}// END: switch

			writer.writeIDref(FrequencyModelParser.FREQUENCY_MODEL,
					data.frequencyModelIdref);

			if (data.ancestralSequenceString != null) {

				writeAncestralSequence(data, writer, suffix);

			}

			writer.writeCloseTag(PartitionParser.PARTITION);

			suffix++;
		}// END: partitions loop

		writer.writeCloseTag(BeagleSequenceSimulatorParser.BEAGLE_SEQUENCE_SIMULATOR);

	}// END: writeBeagleSequenceSimulator

	private void writeAncestralSequence(PartitionData data, XMLWriter writer, int suffix) {
		
		writer.writeOpenTag(SequenceParser.SEQUENCE);
		
		writer.writeTag(
				TaxonParser.TAXON, // tagname
				new Attribute[] { // attributes[]
				new Attribute.Default<String>(XMLParser.ID, Utils.ANCESTRAL_SEQUENCE + suffix) },
				true // close
		);
		
		writer.write (data.ancestralSequenceString + "\n");
		
		writer.writeCloseTag(SequenceParser.SEQUENCE);
		
	}//END: writeAncestralSequence
	
	private void writeNewick(TreeModel tree, XMLWriter writer,
			String suffix) {

		writer.writeOpenTag(NewickParser.NEWICK,
				new Attribute[] { new Attribute.Default<String>(XMLParser.ID,
						Utils.TOPOLOGY + suffix), //
						new Attribute.Default<String>(NewickParser.USING_DATES,
								"false"), //
								new Attribute.Default<String>(NewickParser.USING_HEIGHTS,
										"true") //
		});

		writer.writeText(Tree.Utils.newick(tree));

		writer.writeCloseTag(NewickParser.NEWICK);

	}// END: writeNewick
	
	private void writeDemographicModel(PartitionData data, XMLWriter writer,
			String suffix) {

		int demographicModel = data.demographicModelIndex;

		switch (demographicModel) {

		case 0: // NoModel

			// do nothing

			break;

		case 1: // Constant Population

			writer.writeOpenTag(
					ConstantPopulationModelParser.CONSTANT_POPULATION_MODEL,
					new Attribute[] {
							new Attribute.Default<String>(XMLParser.ID,
									Utils.DEMOGRAPHIC_MODEL + suffix
//									data.demographicModelIdref
									),
							new Attribute.Default<String>(XMLUnits.UNITS,
									XMLUnits.YEARS) });
			
			writeParameter(ConstantPopulationModelParser.POPULATION_SIZE,
					"constant.popSize" + suffix, 1,
					String.valueOf(data.demographicParameterValues[0]), writer);
			
			writer.writeCloseTag(ConstantPopulationModelParser.CONSTANT_POPULATION_MODEL);
			
			writer.writeBlankLine();
			
			writer.writeOpenTag(
					CoalescentSimulatorParser.COALESCENT_SIMULATOR,
					new Attribute[] {
							new Attribute.Default<String>(XMLParser.ID,
									Utils.TOPOLOGY + suffix) });
		
			writer.writeIDref(TaxaParser.TAXA, data.taxaIdref);
			writer.writeIDref(ConstantPopulationModelParser.CONSTANT_POPULATION_MODEL, data.demographicModelIdref);
			
			writer.writeCloseTag(CoalescentSimulatorParser.COALESCENT_SIMULATOR);
			
			break;

		case 2: // Exponential Growth (Growth Rate)

			writer.writeOpenTag(
					ExponentialGrowthModelParser.EXPONENTIAL_GROWTH_MODEL,
					new Attribute[] {
							new Attribute.Default<String>(XMLParser.ID,
									data.demographicModelIdref),
							new Attribute.Default<String>(XMLUnits.UNITS,
									XMLUnits.YEARS) });
			
			writeParameter(ExponentialGrowthModelParser.POPULATION_SIZE,
					"exponential.popSize" + suffix, 1,
					String.valueOf(data.demographicParameterValues[1]), writer);

			writeParameter(ExponentialGrowthModelParser.GROWTH_RATE,
					"exponential.growthRate" + suffix, 1,
					String.valueOf(data.demographicParameterValues[2]), writer);
			
			writer.writeCloseTag(ExponentialGrowthModelParser.EXPONENTIAL_GROWTH_MODEL);
			
			writer.writeBlankLine();
			
			writer.writeOpenTag(
					CoalescentSimulatorParser.COALESCENT_SIMULATOR,
					new Attribute[] {
							new Attribute.Default<String>(XMLParser.ID,
									Utils.TOPOLOGY + suffix) });
		
			writer.writeIDref(TaxaParser.TAXA, TaxaParser.TAXA);
			writer.writeIDref(ExponentialGrowthModelParser.EXPONENTIAL_GROWTH_MODEL, data.demographicModelIdref);
			
			writer.writeCloseTag(CoalescentSimulatorParser.COALESCENT_SIMULATOR);
			
			break;

		case 3: // Exponential Growth (Doubling Time)

			writer.writeOpenTag(
					ExponentialGrowthModelParser.EXPONENTIAL_GROWTH_MODEL,
					new Attribute[] {
							new Attribute.Default<String>(XMLParser.ID,
									data.demographicModelIdref),
							new Attribute.Default<String>(XMLUnits.UNITS,
									XMLUnits.YEARS) });
			
			writeParameter(ExponentialGrowthModelParser.POPULATION_SIZE,
					"exponential.popSize" + suffix, 1,
					String.valueOf(data.demographicParameterValues[3]), writer);

			writeParameter(ExponentialGrowthModelParser.DOUBLING_TIME,
					"exponential.doublingTime" + suffix, 1,
					String.valueOf(data.demographicParameterValues[4]), writer);
			
			writer.writeCloseTag(ExponentialGrowthModelParser.EXPONENTIAL_GROWTH_MODEL);
			
			writer.writeBlankLine();
			
			writer.writeOpenTag(
					CoalescentSimulatorParser.COALESCENT_SIMULATOR,
					new Attribute[] {
							new Attribute.Default<String>(XMLParser.ID,
									Utils.TOPOLOGY + suffix) });
		
			writer.writeIDref(TaxaParser.TAXA, TaxaParser.TAXA);
			writer.writeIDref(ExponentialGrowthModelParser.EXPONENTIAL_GROWTH_MODEL, data.demographicModelIdref);
			
			writer.writeCloseTag(CoalescentSimulatorParser.COALESCENT_SIMULATOR);
			
			break;

			
//		case 4: // Logistic Growth (Growth Rate)
			
		}// END: switch

	}// END: writeDemographicModel
	
	private void writeBranchRatesModel(PartitionData data, XMLWriter writer,
			String suffix) {

		double numberOfBranches = 0;
		int clockModel = data.clockModelIndex;
		
		switch (clockModel) {

		case 0: // StrictClock

			writer.writeOpenTag(
					StrictClockBranchRatesParser.STRICT_CLOCK_BRANCH_RATES,
					new Attribute[] { new Attribute.Default<String>(
							XMLParser.ID, data.clockModelIdref) });

			writeParameter(StrictClockBranchRatesParser.RATE,
					PartitionData.clockParameterNames[0] + suffix, 1,
					String.valueOf(data.clockParameterValues[0]), writer);

			writer.writeCloseTag(StrictClockBranchRatesParser.STRICT_CLOCK_BRANCH_RATES);

			break;

		case 1: // Lognormal relaxed clock

			writer.writeOpenTag(
					DiscretizedBranchRatesParser.DISCRETIZED_BRANCH_RATES,
					new Attribute[] {
							new Attribute.Default<String>(XMLParser.ID,
									data.clockModelIdref),
							new Attribute.Default<String>(
									DiscretizedBranchRatesParser.RANDOMIZE_RATES,
									"true") }

			);

			writer.writeIDref(TreeModel.TREE_MODEL, data.treeModelIdref);

			writer.writeOpenTag(DiscretizedBranchRatesParser.DISTRIBUTION);

			writer.writeOpenTag(
					LogNormalDistributionModelParser.LOGNORMAL_DISTRIBUTION_MODEL,
					new Attribute[] {
							new Attribute.Default<String>(
									LogNormalDistributionModelParser.OFFSET,
									String.valueOf(data.clockParameterValues[3]) ),
							new Attribute.Default<String>(
									LogNormalDistributionModelParser.MEAN_IN_REAL_SPACE,
									"false"),
							new Attribute.Default<String>(
									LogNormalDistributionModelParser.STDEV_IN_REAL_SPACE,
									"false") });

			writeParameter(LogNormalDistributionModelParser.MEAN,
					PartitionData.clockParameterNames[1] + suffix, 1,
					String.valueOf(data.clockParameterValues[1]), writer);

			writeParameter(LogNormalDistributionModelParser.STDEV,
					PartitionData.clockParameterNames[2] + suffix, 1,
					String.valueOf(data.clockParameterValues[2]), writer);

			writer.writeCloseTag(LogNormalDistributionModelParser.LOGNORMAL_DISTRIBUTION_MODEL);

			writer.writeCloseTag(DiscretizedBranchRatesParser.DISTRIBUTION);

			numberOfBranches = 2 * (data.createTreeModel().getTaxonCount() - 1);
			writeParameter(DiscretizedBranchRatesParser.RATE_CATEGORIES,
					"branchRates.categories" + suffix, (int) numberOfBranches, null, writer);

			writer.writeCloseTag(DiscretizedBranchRatesParser.DISCRETIZED_BRANCH_RATES);

			break;

		case 2: // Exponential relaxed clock

			writer.writeOpenTag(
					DiscretizedBranchRatesParser.DISCRETIZED_BRANCH_RATES,
					new Attribute[] {
							new Attribute.Default<String>(XMLParser.ID,
									data.clockModelIdref),
							new Attribute.Default<String>(
									DiscretizedBranchRatesParser.RANDOMIZE_RATES,
									"true") }

			);

			writer.writeIDref(TreeModel.TREE_MODEL, data.treeModelIdref);

			writer.writeOpenTag(DiscretizedBranchRatesParser.DISTRIBUTION);

			writer.writeOpenTag(
					ExponentialDistributionModel.EXPONENTIAL_DISTRIBUTION_MODEL,
					new Attribute[] { 
							new Attribute.Default<String>(
									LogNormalDistributionModelParser.OFFSET,
									String.valueOf(data.clockParameterValues[5]) ),
							new Attribute.Default<String>(
							LogNormalDistributionModelParser.MEAN_IN_REAL_SPACE,
							"true") });

			writeParameter(DistributionModelParser.MEAN,
					PartitionData.clockParameterNames[4] + suffix, 1,
					String.valueOf(data.clockParameterValues[4]), writer);

			writer.writeCloseTag(ExponentialDistributionModel.EXPONENTIAL_DISTRIBUTION_MODEL);

			writer.writeCloseTag(DiscretizedBranchRatesParser.DISTRIBUTION);

			numberOfBranches = 2 * (data.createTreeModel().getTaxonCount() - 1);
			writeParameter(DiscretizedBranchRatesParser.RATE_CATEGORIES,
					"branchRates.categories" + suffix, (int) numberOfBranches, null, writer);

			writer.writeCloseTag(DiscretizedBranchRatesParser.DISCRETIZED_BRANCH_RATES);

			break;
			
		case 3: // Inverse Gaussian
			
			writer.writeOpenTag(
					DiscretizedBranchRatesParser.DISCRETIZED_BRANCH_RATES,
					new Attribute[] {
							new Attribute.Default<String>(XMLParser.ID,
									data.clockModelIdref),
							new Attribute.Default<String>(
									DiscretizedBranchRatesParser.RANDOMIZE_RATES,
									"true") }

			);

			writer.writeIDref(TreeModel.TREE_MODEL, data.treeModelIdref);

			writer.writeOpenTag(DiscretizedBranchRatesParser.DISTRIBUTION);
			
			writer.writeOpenTag(
					InverseGaussianDistributionModelParser.INVERSEGAUSSIAN_DISTRIBUTION_MODEL,
					new Attribute[] { new Attribute.Default<String>(
							InverseGaussianDistributionModelParser.OFFSET,
							String.valueOf(data.clockParameterValues[8])) });
			
			writeParameter(InverseGaussianDistributionModelParser.MEAN,
					PartitionData.clockParameterNames[7] + suffix, 1,
					String.valueOf(data.clockParameterValues[7]), writer);
			
			writeParameter(InverseGaussianDistributionModelParser.STDEV,
					PartitionData.clockParameterNames[6] + suffix, 1,
					String.valueOf(data.clockParameterValues[6]), writer);
			
			writer.writeCloseTag(InverseGaussianDistributionModelParser.INVERSEGAUSSIAN_DISTRIBUTION_MODEL);
			
			writer.writeCloseTag(DiscretizedBranchRatesParser.DISTRIBUTION);

			numberOfBranches = 2 * (data.createTreeModel().getTaxonCount() - 1);
			writeParameter(DiscretizedBranchRatesParser.RATE_CATEGORIES,
					"branchRates.categories" + suffix, (int) numberOfBranches, null, writer);

			writer.writeCloseTag(DiscretizedBranchRatesParser.DISCRETIZED_BRANCH_RATES);
			
		}// END: switch

	}// END: writeBranchRatesModel

	private void writeTaxa(Taxa taxa, XMLWriter writer, String suffix) {

		writer.writeOpenTag(TaxaParser.TAXA, // tagname
				new Attribute[] { // attributes[]
				new Attribute.Default<String>(XMLParser.ID, TaxaParser.TAXA + suffix) });

		for (int i = 0; i < taxa.getTaxonCount(); i++) {

			Taxon taxon = taxa.getTaxon(i);

			writer.writeTag(
					TaxonParser.TAXON, // tagname
					new Attribute[] { // attributes[]
					new Attribute.Default<String>(XMLParser.ID, taxon.getId()) },
					true // close
			);

		}// END: i loop

		writer.writeCloseTag(TaxaParser.TAXA);
	}// END: writeTaxa

	private void writeTreeModel(
//			TreeModel tree, 
			XMLWriter writer, String suffix) {

		final String treeModelName = TreeModel.TREE_MODEL + suffix;

		writer.writeTag(TreeModel.TREE_MODEL, new Attribute.Default<String>(
				XMLParser.ID, treeModelName), false);

		writer.writeIDref("tree", Utils.TOPOLOGY + suffix);

		writeParameter(TreeModelParser.ROOT_HEIGHT, treeModelName + "."
				+ TreeModelParser.ROOT_HEIGHT, 1, null, writer);

		writer.writeOpenTag(TreeModelParser.NODE_HEIGHTS,
				new Attribute.Default<String>(TreeModelParser.INTERNAL_NODES,
						"true"));

		writeParameter(null, treeModelName + "." + "internalNodeHeights", 1,
				null, writer);

		writer.writeCloseTag(TreeModelParser.NODE_HEIGHTS);

		writer.writeOpenTag(TreeModelParser.NODE_HEIGHTS,
				new Attribute[] {
						new Attribute.Default<String>(
								TreeModelParser.INTERNAL_NODES, "true"),
						new Attribute.Default<String>(
								TreeModelParser.ROOT_NODE, "true") });

		writeParameter(null, treeModelName + "." + "allInternalNodeHeights", 1,
				null, writer);

		writer.writeCloseTag(TreeModelParser.NODE_HEIGHTS);

		writer.writeCloseTag(TreeModel.TREE_MODEL);

	}// END: writeTreeModel

	private void writeReport(XMLWriter writer) {

		writer.writeOpenTag(
				Report.REPORT,
				new Attribute[] { new Attribute.Default<String>(
						Report.FILENAME, "sequences."
								+ String.valueOf(dataList.outputFormat)
										.toLowerCase()) });

		writer.writeIDref(
				BeagleSequenceSimulatorParser.BEAGLE_SEQUENCE_SIMULATOR,
				"simulator");

		writer.writeCloseTag(Report.REPORT);

	}// END: writeReport

	private void writeSiteRateModel(PartitionData data, XMLWriter writer, int suffix) {

		writer.writeOpenTag(SiteModel.SITE_MODEL,
				new Attribute[] { new Attribute.Default<String>(XMLParser.ID,
						data.siteRateModelIdref) });

		writer.writeOpenTag(GammaSiteModelParser.SUBSTITUTION_MODEL);

		int substitutionModelIndex = data.substitutionModelIndex;
		switch (substitutionModelIndex) {

		case 0: // HKY

			writer.writeIDref(NucModelType.HKY.getXMLName(),
					data.substitutionModelIdref);
			break;

		case 1: // GTR

			writer.writeIDref(GTRParser.GTR_MODEL, data.substitutionModelIdref);
			break;

		case 2: // TN93

			writer.writeIDref(NucModelType.TN93.getXMLName(),
					data.substitutionModelIdref);
			break;

		case 3: // GY94CodonModel

			writer.writeIDref(YangCodonModelParser.YANG_CODON_MODEL,
					data.substitutionModelIdref);
			break;
			
		case 4: // MG94CodonModel

			writer.writeIDref(MG94CodonModelParser.MUSE_CODON_MODEL,
					data.substitutionModelIdref);
			break;
			
		case 5: // Blosum62

			writer.writeIDref(EmpiricalAminoAcidModelParser.EMPIRICAL_AMINO_ACID_MODEL,
					data.substitutionModelIdref);
			break;	
			
		case 6: // CPREV

			writer.writeIDref(EmpiricalAminoAcidModelParser.EMPIRICAL_AMINO_ACID_MODEL,
					data.substitutionModelIdref);
			break;

		case 7: // Dayhoff

			writer.writeIDref(EmpiricalAminoAcidModelParser.EMPIRICAL_AMINO_ACID_MODEL,
					data.substitutionModelIdref);
			break;
			
		case 8: // FLU

			writer.writeIDref(EmpiricalAminoAcidModelParser.EMPIRICAL_AMINO_ACID_MODEL,
					data.substitutionModelIdref);
			break;
			
		case 9: // JTT

			writer.writeIDref(EmpiricalAminoAcidModelParser.EMPIRICAL_AMINO_ACID_MODEL,
					data.substitutionModelIdref);
			break;
			
		case 10: // LG

			writer.writeIDref(EmpiricalAminoAcidModelParser.EMPIRICAL_AMINO_ACID_MODEL,
					data.substitutionModelIdref);
			break;
			
		case 11: // MTREV

			writer.writeIDref(EmpiricalAminoAcidModelParser.EMPIRICAL_AMINO_ACID_MODEL,
					data.substitutionModelIdref);
			break;
			
		case 12: // WAG

			writer.writeIDref(EmpiricalAminoAcidModelParser.EMPIRICAL_AMINO_ACID_MODEL,
					data.substitutionModelIdref);
			break;
			
		}// END: switch

		writer.writeCloseTag(GammaSiteModelParser.SUBSTITUTION_MODEL);

		int siteRateModelIndex = data.siteRateModelIndex;
		switch (siteRateModelIndex) {

		case 0: // no model

			// do nothing

			break;

		case 1: // GammaSiteRateModel
			
			writer.writeOpenTag(
					GammaSiteModelParser.GAMMA_SHAPE,
					new Attribute.Default<String>(
							GammaSiteModelParser.GAMMA_CATEGORIES,
							String.valueOf((int) data.siteRateModelParameterValues[0])));

			//TODO
			writeParameter(null, "alpha" + suffix, 1,
					String.valueOf(data.siteRateModelParameterValues[1]),
					writer);

			writer.writeCloseTag(GammaSiteModelParser.GAMMA_SHAPE);

			if (data.siteRateModelParameterValues[2] > 0.0) {
				writeParameter(GammaSiteModelParser.PROPORTION_INVARIANT, "pInv", 1,
						String.valueOf(data.siteRateModelParameterValues[2]),
						writer);
			}
			
			break;
		}// END: switch

		writer.writeCloseTag(SiteModel.SITE_MODEL);

	}// END: writeSiteModel

	private void writeBranchModel(PartitionData data, XMLWriter writer,
			String suffix) {

		int substitutionModelIndex = data.substitutionModelIndex;

		switch (substitutionModelIndex) {

		case 0: // HKY

			writer.writeOpenTag(NucModelType.HKY.getXMLName(),
					new Attribute[] { new Attribute.Default<String>(
							XMLParser.ID, data.substitutionModelIdref) });

			writer.writeOpenTag(FrequencyModelParser.FREQUENCIES);

			writer.writeIDref(FrequencyModelParser.FREQUENCY_MODEL,
					data.frequencyModelIdref);

			writer.writeCloseTag(FrequencyModelParser.FREQUENCIES);

			writeParameter(HKYParser.KAPPA, HKYParser.KAPPA + suffix, 1,
					String.valueOf(data.substitutionParameterValues[0]), writer);

			writer.writeCloseTag(NucModelType.HKY.getXMLName());

			break;

		case 1: // GTR

			writer.writeOpenTag(GTRParser.GTR_MODEL,
					new Attribute[] { new Attribute.Default<String>(
							XMLParser.ID, data.substitutionModelIdref) });

			writer.writeOpenTag(FrequencyModelParser.FREQUENCIES);

			writer.writeIDref(FrequencyModelParser.FREQUENCY_MODEL,
					data.frequencyModelIdref);

			writer.writeCloseTag(FrequencyModelParser.FREQUENCIES);

			writeParameter(GTRParser.A_TO_C, "ac" + suffix, 1,
					String.valueOf(data.substitutionParameterValues[1]), writer);
			writeParameter(GTRParser.A_TO_G, "ag" + suffix, 1,
					String.valueOf(data.substitutionParameterValues[2]), writer);
			writeParameter(GTRParser.A_TO_T, "at" + suffix, 1,
					String.valueOf(data.substitutionParameterValues[3]), writer);
			writeParameter(GTRParser.C_TO_G, "cg" + suffix, 1,
					String.valueOf(data.substitutionParameterValues[4]), writer);
			writeParameter(GTRParser.C_TO_T, "ct" + suffix, 1,
					String.valueOf(data.substitutionParameterValues[5]), writer);
//			writeParameter(GTRParser.G_TO_T, "gt" + suffix, 1,
//					String.valueOf(data.substitutionParameterValues[6]), writer);

			writer.writeCloseTag(GTRParser.GTR_MODEL);

			break;

		case 2: // TN93

			writer.writeOpenTag(NucModelType.TN93.getXMLName(),
					new Attribute[] { new Attribute.Default<String>(
							XMLParser.ID, data.substitutionModelIdref) });

			writer.writeOpenTag(FrequencyModelParser.FREQUENCIES);

			writer.writeIDref(FrequencyModelParser.FREQUENCY_MODEL,
					data.frequencyModelIdref);

			writer.writeCloseTag(FrequencyModelParser.FREQUENCIES);

			writeParameter(TN93Parser.KAPPA1, TN93Parser.KAPPA1 + suffix, 1,
					String.valueOf(data.substitutionParameterValues[7]), writer);

			writeParameter(TN93Parser.KAPPA2, TN93Parser.KAPPA2 + suffix, 1,
					String.valueOf(data.substitutionParameterValues[8]), writer);

			writer.writeCloseTag(NucModelType.TN93.getXMLName());

			break;

		case 3: // Yang Codon Model

			writer.writeOpenTag(YangCodonModelParser.YANG_CODON_MODEL,
					new Attribute[] { new Attribute.Default<String>(
							XMLParser.ID, data.substitutionModelIdref) });

			writer.writeIDref(FrequencyModelParser.FREQUENCY_MODEL,
					data.frequencyModelIdref);

			writeParameter(YangCodonModelParser.OMEGA,
					YangCodonModelParser.OMEGA + suffix, 1,
					String.valueOf(data.substitutionParameterValues[9]), writer);

			writeParameter(YangCodonModelParser.KAPPA,
					YangCodonModelParser.KAPPA + suffix, 1,
					String.valueOf(data.substitutionParameterValues[10]),
					writer);

			writer.writeCloseTag(YangCodonModelParser.YANG_CODON_MODEL);

			break;

		case 4: // 		
			
			writer.writeOpenTag(MG94CodonModelParser.MUSE_CODON_MODEL,
					new Attribute[] { new Attribute.Default<String>(
							XMLParser.ID, data.substitutionModelIdref) });
			
			writer.writeIDref(FrequencyModelParser.FREQUENCY_MODEL,
					data.frequencyModelIdref);
			
			
			writeParameter(MG94CodonModelParser.ALPHA,
					MG94CodonModelParser.ALPHA + suffix, 1,
					String.valueOf(data.substitutionParameterValues[11]), writer);

			
			writeParameter(MG94CodonModelParser.BETA,
					MG94CodonModelParser.BETA + suffix, 1,
					String.valueOf(data.substitutionParameterValues[12]), writer);
			
			writeParameter(MG94CodonModelParser.KAPPA,
					MG94CodonModelParser.KAPPA + suffix, 1,
					String.valueOf(data.substitutionParameterValues[13]), writer);
			
			writer.writeCloseTag(MG94CodonModelParser.MUSE_CODON_MODEL);
			
			break;
			
		case 5: // Blosum62

			writer.writeOpenTag(
					EmpiricalAminoAcidModelParser.EMPIRICAL_AMINO_ACID_MODEL,
					new Attribute[] {
							new Attribute.Default<String>(XMLParser.ID,
									data.substitutionModelIdref), //
							new Attribute.Default<String>(
									EmpiricalAminoAcidModelParser.TYPE,
									AminoAcidModelType.BLOSUM_62.getXMLName()) 
									});
			
			writer.writeOpenTag(FrequencyModelParser.FREQUENCIES);

			writer.writeIDref(FrequencyModelParser.FREQUENCY_MODEL,
					data.frequencyModelIdref);

			writer.writeCloseTag(FrequencyModelParser.FREQUENCIES);
			
			writer.writeCloseTag(EmpiricalAminoAcidModelParser.EMPIRICAL_AMINO_ACID_MODEL);
			
			break;

		case 6: // CPREV

			writer.writeOpenTag(
					EmpiricalAminoAcidModelParser.EMPIRICAL_AMINO_ACID_MODEL,
					new Attribute[] {
							new Attribute.Default<String>(XMLParser.ID,
									data.substitutionModelIdref), //
							new Attribute.Default<String>(
									EmpiricalAminoAcidModelParser.TYPE,
									AminoAcidModelType.CP_REV_45.getXMLName()) 
									});
			
			writer.writeOpenTag(FrequencyModelParser.FREQUENCIES);

			writer.writeIDref(FrequencyModelParser.FREQUENCY_MODEL,
					data.frequencyModelIdref);

			writer.writeCloseTag(FrequencyModelParser.FREQUENCIES);
			
			writer.writeCloseTag(EmpiricalAminoAcidModelParser.EMPIRICAL_AMINO_ACID_MODEL);
			
			break;

		case 7: // Dayhoff

			writer.writeOpenTag(
					EmpiricalAminoAcidModelParser.EMPIRICAL_AMINO_ACID_MODEL,
					new Attribute[] {
							new Attribute.Default<String>(XMLParser.ID,
									data.substitutionModelIdref), //
							new Attribute.Default<String>(
									EmpiricalAminoAcidModelParser.TYPE,
									AminoAcidModelType.DAYHOFF.getXMLName()) 
									});
			
			writer.writeOpenTag(FrequencyModelParser.FREQUENCIES);

			writer.writeIDref(FrequencyModelParser.FREQUENCY_MODEL,
					data.frequencyModelIdref);

			writer.writeCloseTag(FrequencyModelParser.FREQUENCIES);
			
			writer.writeCloseTag(EmpiricalAminoAcidModelParser.EMPIRICAL_AMINO_ACID_MODEL);
			
			break;

		case 8: // FLU

			writer.writeOpenTag(
					EmpiricalAminoAcidModelParser.EMPIRICAL_AMINO_ACID_MODEL,
					new Attribute[] {
							new Attribute.Default<String>(XMLParser.ID,
									data.substitutionModelIdref), //
							new Attribute.Default<String>(
									EmpiricalAminoAcidModelParser.TYPE,
									AminoAcidModelType.FLU.getXMLName()) 
									});
			
			writer.writeOpenTag(FrequencyModelParser.FREQUENCIES);

			writer.writeIDref(FrequencyModelParser.FREQUENCY_MODEL,
					data.frequencyModelIdref);

			writer.writeCloseTag(FrequencyModelParser.FREQUENCIES);
			
			writer.writeCloseTag(EmpiricalAminoAcidModelParser.EMPIRICAL_AMINO_ACID_MODEL);
			
			break;

		case 9: // JTT

			writer.writeOpenTag(
					EmpiricalAminoAcidModelParser.EMPIRICAL_AMINO_ACID_MODEL,
					new Attribute[] {
							new Attribute.Default<String>(XMLParser.ID,
									data.substitutionModelIdref), //
							new Attribute.Default<String>(
									EmpiricalAminoAcidModelParser.TYPE,
									AminoAcidModelType.JTT.getXMLName()) 
									});
			
			writer.writeOpenTag(FrequencyModelParser.FREQUENCIES);

			writer.writeIDref(FrequencyModelParser.FREQUENCY_MODEL,
					data.frequencyModelIdref);

			writer.writeCloseTag(FrequencyModelParser.FREQUENCIES);
			
			writer.writeCloseTag(EmpiricalAminoAcidModelParser.EMPIRICAL_AMINO_ACID_MODEL);
			
			break;

		case 10: // LG

			writer.writeOpenTag(
					EmpiricalAminoAcidModelParser.EMPIRICAL_AMINO_ACID_MODEL,
					new Attribute[] {
							new Attribute.Default<String>(XMLParser.ID,
									data.substitutionModelIdref), //
							new Attribute.Default<String>(
									EmpiricalAminoAcidModelParser.TYPE,
									AminoAcidModelType.LG.getXMLName()) 
									});
			
			writer.writeOpenTag(FrequencyModelParser.FREQUENCIES);

			writer.writeIDref(FrequencyModelParser.FREQUENCY_MODEL,
					data.frequencyModelIdref);

			writer.writeCloseTag(FrequencyModelParser.FREQUENCIES);
			
			writer.writeCloseTag(EmpiricalAminoAcidModelParser.EMPIRICAL_AMINO_ACID_MODEL);
			
			break;

		case 11: // MTREV

			writer.writeOpenTag(
					EmpiricalAminoAcidModelParser.EMPIRICAL_AMINO_ACID_MODEL,
					new Attribute[] {
							new Attribute.Default<String>(XMLParser.ID,
									data.substitutionModelIdref), //
							new Attribute.Default<String>(
									EmpiricalAminoAcidModelParser.TYPE,
									AminoAcidModelType.MT_REV_24.getXMLName()) 
									});
			
			writer.writeOpenTag(FrequencyModelParser.FREQUENCIES);

			writer.writeIDref(FrequencyModelParser.FREQUENCY_MODEL,
					data.frequencyModelIdref);

			writer.writeCloseTag(FrequencyModelParser.FREQUENCIES);
			
			writer.writeCloseTag(EmpiricalAminoAcidModelParser.EMPIRICAL_AMINO_ACID_MODEL);	
			
			break;
			
		case 12: // WAG

			writer.writeOpenTag(
					EmpiricalAminoAcidModelParser.EMPIRICAL_AMINO_ACID_MODEL,
					new Attribute[] {
							new Attribute.Default<String>(XMLParser.ID,
									data.substitutionModelIdref), //
							new Attribute.Default<String>(
									EmpiricalAminoAcidModelParser.TYPE,
									AminoAcidModelType.WAG.getXMLName()) 
									});
			
			writer.writeOpenTag(FrequencyModelParser.FREQUENCIES);

			writer.writeIDref(FrequencyModelParser.FREQUENCY_MODEL,
					data.frequencyModelIdref);

			writer.writeCloseTag(FrequencyModelParser.FREQUENCIES);
			
			writer.writeCloseTag(EmpiricalAminoAcidModelParser.EMPIRICAL_AMINO_ACID_MODEL);
			
			break;
			
		}// END: switch

	}// END: writeBranchModel

	private void writeFrequencyModel(PartitionData data, XMLWriter writer) {

		DataType dataType = null;
		String frequencies = null;
//		int dataTypeIndex = data.dataTypeIndex;
		int frequencyModelIndex = data.frequencyModelIndex;
		
		switch (frequencyModelIndex) {

		case 0: // Nucleotide

			dataType = data.createDataType();
//			dataType = Nucleotides.INSTANCE;

			frequencies = data.frequencyParameterValues[0] + "";
			for (int i = 1; i < 4; i++) {
				frequencies += " " + data.frequencyParameterValues[i];
			}

			writer.writeOpenTag(FrequencyModelParser.FREQUENCY_MODEL, // tagname
					new Attribute[] { // attributes[]
							new Attribute.Default<String>(XMLParser.ID,
									data.frequencyModelIdref), // id
							new Attribute.Default<String>(DataType.DATA_TYPE,
									dataType.getDescription()) // dataType
					});

			writeParameter(FrequencyModelParser.FREQUENCIES, null,
					dataType.getStateCount(), frequencies, writer);

			writer.writeCloseTag(FrequencyModelParser.FREQUENCY_MODEL);

			break;

		case 1: // Codon

			dataType = data.createDataType();
//			dataType = Codons.UNIVERSAL;

			frequencies = data.frequencyParameterValues[4] + "";
			for (int i = 5; i < 65; i++) {
				frequencies += " " + data.frequencyParameterValues[i];
			}

			writer.writeOpenTag(FrequencyModelParser.FREQUENCY_MODEL, // tagname
					new Attribute[] { // attributes[]
							new Attribute.Default<String>(XMLParser.ID,
									data.frequencyModelIdref), // id
							new Attribute.Default<String>(DataType.DATA_TYPE,
//									dataType.getDescription()
									Utils.CODON_UNIVERSAL
									) // dataType
					});

			writeParameter(FrequencyModelParser.FREQUENCIES, null,
					dataType.getStateCount(), frequencies, writer);

			writer.writeCloseTag(FrequencyModelParser.FREQUENCY_MODEL);

			break;

		case 2: // Amino acid
			
			dataType = data.createDataType();
//			dataType = AminoAcids.INSTANCE;
			
			frequencies = data.frequencyParameterValues[65] + "";
			for (int i = 66; i < 85; i++) {
				frequencies += " " + data.frequencyParameterValues[i];
			}
			
			writer.writeOpenTag(FrequencyModelParser.FREQUENCY_MODEL, // tagname
					new Attribute[] { // attributes[]
							new Attribute.Default<String>(XMLParser.ID,
									data.frequencyModelIdref), // id
							new Attribute.Default<String>(DataType.DATA_TYPE,
//									dataType.getDescription()
									AminoAcids.DESCRIPTION
									) // dataType
					});
			
			writeParameter(FrequencyModelParser.FREQUENCIES, null,
					dataType.getStateCount(), frequencies, writer);

			writer.writeCloseTag(FrequencyModelParser.FREQUENCY_MODEL);
			
			break;
			
		}// END: switch
		
	}// END: writeFrequencyModel

	@SuppressWarnings("rawtypes")
	private void writeParameter(String wrapper, String id, int dimension,
			String value, XMLWriter writer) {

		if (wrapper != null) {
			writer.writeOpenTag(wrapper);
		}

		ArrayList<Attribute.Default> attributes = new ArrayList<Attribute.Default>();

		if (id != null) {
			attributes.add(new Attribute.Default<String>(XMLParser.ID, id));
		}

		if (dimension > 1) {
			attributes.add(new Attribute.Default<String>(
					ParameterParser.DIMENSION, String.valueOf(dimension)));
		}

		if (value != null) {
			attributes.add(new Attribute.Default<String>(ParameterParser.VALUE,
					value));
		}

		Attribute[] attrArray = new Attribute[attributes.size()];
		for (int i = 0; i < attrArray.length; i++) {
			attrArray[i] = attributes.get(i);
		}

		writer.writeTag(ParameterParser.PARAMETER, attrArray, true);

		if (wrapper != null) {
			writer.writeCloseTag(wrapper);
		}

	}// END: writeParameter
	
}// END: class
