/*
 * Created on Mar 5, 2005
 *
 * Copyright Alok Saldnaha, all rights reserved.
 */
package edu.stanford.genetics.treeview.model;

import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.Observable;
import java.util.Observer;

import edu.stanford.genetics.treeview.*;

/**
 * 
 * This class produces a reordered version of the parent DataModel
 * It can a subset, if the integer arrays passed in have fewer members, or a 
 * superset, if the arrays passed in have more members.
 * Gaps can be introduced between genes or arrays by inserting "-1" at an index.
 * 
 * @author aloksaldanha
 *
 */
public class ReorderedDataModel extends Observable implements DataModel {
	/**
	 * @author aloksaldanha
	 *
	 */
	private class SubDataMatrix implements DataMatrix {
		public double getValue(int col, int row) {
			if (geneIndex != null) row = geneIndex[row];
			if (arrayIndex != null) col = arrayIndex[col];
			if ((row == -1) || (col == -1)) {
				return DataModel.EMPTY;
			} else {
				return parent.getDataMatrix().getValue(col, row);
			}
		}

		public void setValue(double value, int col, int row) {
			if (geneIndex != null) row = geneIndex[row];
			if (arrayIndex != null) col = arrayIndex[col];
			if ((row == -1) || (col == -1)) {
				return;
			} else {
				LogBuffer.getSingleton().log("Error: cannot modifiy reordered data model");
				return;
			}
		}

		public int getNumRow() {
			if (geneIndex != null) 
				return geneIndex.length;
			else
				return parent.getDataMatrix().getNumRow();
		}

		public int getNumCol() {
			if (arrayIndex != null) 
				return arrayIndex.length;
			else
				return parent.getDataMatrix().getNumCol();
		}

		public int getNumUnappendedCol() {
			return parent.getDataMatrix().getNumUnappendedCol();
		}

		@Override
		public boolean getModified() {
			return false;
		}

		@Override
		public void setModified(boolean b) {
			// TODO Auto-generated method stub
			
		}

	}
	

	/**
	 * 
	 * Represents reordered HeaderInfo of parent.
	 * In this case, it is the genes/arrays that are reordered, not the headers
	 * 
	 */
	private class ReorderedHeaderInfo implements HeaderInfo {
		private HeaderInfo parentHeaderInfo;
		int [] reorderedIndex;
		private ReorderedHeaderInfo(HeaderInfo hi, int [] ri) {
			parentHeaderInfo = hi;
			reorderedIndex = ri;
		}
		public String[] getHeader(int i) {
			int index = reorderedIndex[i];
			if (index == -1)
				return null;
			return parentHeaderInfo.getHeader(index);
		}

		public String getHeader(int i, String name) {
			int index = reorderedIndex[i];
			if (index == -1)
				return null;
			return parentHeaderInfo.getHeader(index, name);
		}
		public String getHeader(int rowIndex, int columnIndex) {
			String [] header = getHeader(rowIndex);
			if (header != null)
				return header[columnIndex];
			else 
				return "";
		}

		public String[] getNames() {
			return parentHeaderInfo.getNames();
		}

		public int getNumNames() {
			return parentHeaderInfo.getNumNames();
		}

		public int getNumHeaders() {
			return reorderedIndex.length;
		}

		public int getIndex(String name) {
			return parentHeaderInfo.getIndex(name);
		}

		public int getHeaderIndex(String id) {
			int parentIndex = parentHeaderInfo.getHeaderIndex(id);
			if (reorderedIndex[parentIndex] == parentIndex) 
				return parentIndex;
			else {
				for (int i = 0; i < reorderedIndex.length; i++)
					if (reorderedIndex[i] == parentIndex)
						return i;
			}
			return -1;
		}
		public void addObserver(Observer o) {
			parentHeaderInfo.addObserver(o);
		}
		public void deleteObserver(Observer o) {
			parentHeaderInfo.deleteObserver(o);
		}
		public boolean addName(String name, int location) {return false;}
		public boolean setHeader(int i, String name, String value) {return false;}
		public boolean getModified() {return false;}
		public void setModified(boolean mod) {}		
	}
	
	/**
	 * 
	 * Represents reordered HeaderInfo of parent.
	 * In this case, it is the headers that are reordered, not the genes/arrays
	 * 
	 */
	private class ReorderedNameHeaderInfo implements HeaderInfo {
		final private HeaderInfo parentHeaderInfo;
		final int [] reorderedIndex;
		final String [] names;
		private Map<String, Integer> nameToIndex = new HashMap<String, Integer>();
		/**
		 * 
		 * @param hi the base header info
		 * @param ri the list of indexes into the name of the base header info that we want to retain.
		 */
		private ReorderedNameHeaderInfo(HeaderInfo hi, int [] ri) {
			parentHeaderInfo = hi;
			reorderedIndex = ri;
			String [] origNames = hi.getNames();
			names = makeSubset(origNames);
		}
		private String[] makeSubset(String[] origNames) {
			String[] headers = new String[reorderedIndex.length];
			for (int i =0; i < headers.length;i++ ) {
				headers[i] = origNames[reorderedIndex[i]]; 
			}
			return headers;
		}
		public String[] getHeader(int i) {
			return makeSubset(parentHeaderInfo.getHeader(i));
		}

		public String getHeader(int i, String name) {
			int index = getIndex(name);
			if (index == -1)
				return null;
			else
				return getHeader(i,index);
		}
		public String getHeader(int rowIndex, int columnIndex) {
			String [] header = getHeader(rowIndex);
			if (header != null)
				return header[columnIndex];
			else 
				return "";
		}

		public String[] getNames() {
			return names;
		}

		public int getNumNames() {
			return names.length;
		}

		public int getNumHeaders() {
			return parentHeaderInfo.getNumHeaders();
		}

		public int getIndex(String name) {
			for (int i = 0; i < names.length; i++)
				if (name.equalsIgnoreCase(names[i]))
					return i;
			return -1;
		}

		public int getHeaderIndex(String id) {
			return  parentHeaderInfo.getHeaderIndex(id);
		}
		public void addObserver(Observer o) {
			parentHeaderInfo.addObserver(o);
		}
		public void deleteObserver(Observer o) {
			parentHeaderInfo.deleteObserver(o);
		}
		public boolean addName(String name, int location) {return false;}
		public boolean setHeader(int i, String name, String value) {return false;}
		public boolean getModified() {return false;}
		public void setModified(boolean mod) {}		
	}
	
	/**
	 * 
	 * Represents renamed HeaderInfo of parent.
	 * In this case, it is the genes/arrays that are reordered, not the headers
	 * 
	 */
	private class RenamedHeaderInfo implements HeaderInfo {
		final private HeaderInfo parentHeaderInfo;
		final int nameIndex;
		final String newName;
		private RenamedHeaderInfo(HeaderInfo hi, int ni, String nn) {
			parentHeaderInfo = hi;
			nameIndex = ni;
			newName = nn;
		}
		public String[] getHeader(int i) {
			return parentHeaderInfo.getHeader(i);
		}

		public String getHeader(int i, String name) {
			int index = getIndex(name);
			if (index == -1)
				return null;
			else
				return parentHeaderInfo.getHeader(i, index);
		}
		public String getHeader(int rowIndex, int columnIndex) {
			return parentHeaderInfo.getHeader(rowIndex, columnIndex);
		}

		public String[] getNames() {
			String [] names = parentHeaderInfo.getNames();
			names[nameIndex] = newName;
			return names;
		}

		public int getNumNames() {
			return parentHeaderInfo.getNumNames();
		}

		public int getNumHeaders() {
			return parentHeaderInfo.getNumHeaders();
		}

		public int getIndex(String name) {
			if (newName.equalsIgnoreCase(name)) {
				return nameIndex;
			} else if (parentHeaderInfo.getIndex(name) == nameIndex) {
				return -1;
			} else {
				return parentHeaderInfo.getIndex(name);
			}
		}

		public int getHeaderIndex(String id) {
			return parentHeaderInfo.getHeaderIndex(id);
		}
		public void addObserver(Observer o) {
			parentHeaderInfo.addObserver(o);
		}
		public void deleteObserver(Observer o) {
			parentHeaderInfo.deleteObserver(o);
		}
		public boolean addName(String name, int location) {return false;}
		public boolean setHeader(int i, String name, String value) {return false;}
		public boolean getModified() {return false;}
		public void setModified(boolean mod) {}		
	}

	/**
	 * Builds data model which corresponds to a reordered version of the source datamodel, 
	 * as specified by geneIndex
	 * 
	 * @param source
	 * @param geneIndex
	 */
	public ReorderedDataModel(DataModel source, int [] geneIndex) {
		this(source, geneIndex, null);
	}
	/**
	 * Builds data model which corresponds to a reordered version of the source datamodel, 
	 * as specified by geneIndex and arrayIndex.
	 * 
	 * @param source
	 * @param geneIndex
	 */
	public ReorderedDataModel(DataModel source, int [] geneIndex, int [] arrayIndex) {
		this.geneIndex = geneIndex;
		this.arrayIndex = arrayIndex;
		if (geneIndex != null) {
			GtrHeaderInfo = new ReorderedHeaderInfo(source.getGtrHeaderInfo(), geneIndex);
			this.gidFound = source.gidFound() && (source.getGtrHeaderInfo().getNumHeaders() == GtrHeaderInfo.getNumHeaders());
			GeneHeaderInfo = new ReorderedHeaderInfo(source.getGeneHeaderInfo(), geneIndex);
		} else {
			// the geneheaderinfo and gtrheaerinfo flow through here.
			this.gidFound  = source.gidFound();
		}
		if (arrayIndex != null) {
			AtrHeaderInfo = new ReorderedHeaderInfo(source.getAtrHeaderInfo(), arrayIndex);
			this.aidFound = source.aidFound() && (source.getAtrHeaderInfo().getNumHeaders() == AtrHeaderInfo.getNumHeaders());
			ArrayHeaderInfo = new ReorderedHeaderInfo(source.getArrayHeaderInfo(), arrayIndex);
		} else {
			this.aidFound  = source.aidFound();
		}
		
		this.parent =source;
		if (!gidFound) {
			int gidGeneIndex = getGeneHeaderInfo().getIndex("GID");
			if (gidGeneIndex != -1) {
				// two things:
				// 1) get rid of gid column from the gene header info
				int[] keptGeneHeaders = new int[getGeneHeaderInfo().getNames().length -1];
				int j = 0;
				for (int i = 0; i < getGeneHeaderInfo().getNames().length; i++)
					if (i != gidGeneIndex)
						keptGeneHeaders[j++] = i;
				GeneHeaderInfo = new ReorderedNameHeaderInfo(getGeneHeaderInfo(), keptGeneHeaders);
				// 2) rename first header of arrayHeaderInfo to the first header of gene header info
				ArrayHeaderInfo = new RenamedHeaderInfo(getArrayHeaderInfo(), 0, GeneHeaderInfo.getNames()[0]);
			}
		}
		this.source = "Subset " +parent.getSource();
		this.name = "Subset of " +parent.getName();
		// this should really be set to a clone of the parent's document config.
		// should also massage the selected gene annotations to match the new ones.
		documentConfig = new DummyConfigNode("DocumentConfig");
		source.getDocumentConfigRoot().deepCopyTo(documentConfig);
		//documentConfig = source.getDocumentConfigRoot();
	}
	private HeaderInfo GtrHeaderInfo;
	private HeaderInfo GeneHeaderInfo;
	private HeaderInfo AtrHeaderInfo;
	private HeaderInfo ArrayHeaderInfo;
	private DataMatrix subDataMatrix = new SubDataMatrix();
	private DataModel parent;
	private int [] geneIndex;
	private int [] arrayIndex;
	private ConfigNode documentConfig = new DummyConfigNode("SubDataModel");
	
	/* (non-Javadoc)
	 * @see edu.stanford.genetics.treeview.DataModel#getDocumentConfig()
	 */
	public ConfigNode getDocumentConfigRoot() {
		return documentConfig;
	}

	String source;
	/* (non-Javadoc)
	 * @see edu.stanford.genetics.treeview.DataModel#getSource()
	 */
	public String getSource() {
		return source;
	}
	public void setSource(String string) {
		source = string;
	}
	String name;
	private boolean aidFound;
	private boolean gidFound;
	public String getName() {
		return name;
	}
	public void setName(String string) {
		name = string;
	}

	/* (non-Javadoc)
	 * @see edu.stanford.genetics.treeview.DataModel#setModelForCompare(edu.stanford.genetics.treeview.DataModel)
	 */
	public void setModelForCompare(DataModel dm) {
		// TODO Auto-generated method stub

	}

	/* (non-Javadoc)
	 * @see edu.stanford.genetics.treeview.DataModel#getFileSet()
	 */
	public FileSet getFileSet() {
		// TODO Auto-generated method stub
		return null;
	}
	@Override
	public void clearFileSetListeners() {
	}
	@Override
	public void addFileSetListener(FileSetListener listener) {
	}

	/* (non-Javadoc)
	 * @see edu.stanford.genetics.treeview.DataModel#getGeneHeaderInfo()
	 */
	public HeaderInfo getGeneHeaderInfo() {
		if (GeneHeaderInfo == null)
			return parent.getGeneHeaderInfo();
		else
			return GeneHeaderInfo;
	}

	/* (non-Javadoc)
	 * @see edu.stanford.genetics.treeview.DataModel#getArrayHeaderInfo()
	 */
	public HeaderInfo getArrayHeaderInfo() {
		if (ArrayHeaderInfo == null)
			return parent.getArrayHeaderInfo();
		else
			return ArrayHeaderInfo;
	}

	/* (non-Javadoc)
	 * @see edu.stanford.genetics.treeview.DataModel#getGtrHeaderInfo()
	 */
	public HeaderInfo getGtrHeaderInfo() {
		if (GtrHeaderInfo == null)
			return parent.getGtrHeaderInfo();
		else
			return GtrHeaderInfo;
	}

	/* (non-Javadoc)
	 * @see edu.stanford.genetics.treeview.DataModel#getAtrHeaderInfo()
	 */
	public HeaderInfo getAtrHeaderInfo() {
		if (AtrHeaderInfo == null)
			return parent.getAtrHeaderInfo();
		else
			return AtrHeaderInfo;
	}

	/* (non-Javadoc)
	 * @see edu.stanford.genetics.treeview.DataModel#getType()
	 */
	public String getType() {
		return "ReorderedDataModel";
	}

	/* (non-Javadoc)
	 * @see edu.stanford.genetics.treeview.DataModel#getDataMatrix()
	 */
	public DataMatrix getDataMatrix() {
		return subDataMatrix;
	}

	/* (non-Javadoc)
	 * @see edu.stanford.genetics.treeview.DataModel#append(edu.stanford.genetics.treeview.DataModel)
	 */
	public void append(DataModel m) {
		// TODO Auto-generated method stub

	}

	/* (non-Javadoc)
	 * @see edu.stanford.genetics.treeview.DataModel#removeAppended()
	 */
	public void removeAppended() {
		// TODO Auto-generated method stub

	}

	public boolean aidFound() {
		return aidFound;
	}

	public boolean gidFound() {
		// the following causes a mismatch if not all genes were selected.
		return gidFound;
	}
	public boolean getModified() {
		return false;
	}
	public boolean isLoaded() {
		return true;
	}
}
