

import java.awt.Component;
import java.awt.Dimension;
import java.util.HashMap;
import java.util.Set;
import java.util.Vector;
import java.util.Map.Entry;

import javax.media.j3d.BoundingBox;
import javax.swing.JCheckBox;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.vecmath.Matrix3f;
import javax.vecmath.Point3d;

import org.cellmicrocosmos.cm2.membranepackingalgorithm.Lipid;
import org.cellmicrocosmos.cm2.membranepackingalgorithm.MembranePackingAlgorithm;
import org.cellmicrocosmos.cm2.membranepackingalgorithm.MembranePackingAlgorithmGUI;



/**
 * This tool algorithm lists many information about the
 * dimensions of the lipids.
 * 
 * @author Bjoern Sommer bjoern@CELLmicrocosmos.org
 *
 */
public class DimensionLister extends MembranePackingAlgorithm {

	// indicates if lipids should be deleted if intersect
	boolean listLipidTypes;
	boolean listLipids;
	
	private static int SAMPLELIPID = 0;
	private static int MEMBRANELIPID = 1;
	
	HashMap<String,Double> lipidTypeDimensions = new HashMap<String,Double>();

	// a flag that can be toggled to abort the algorithm
	boolean stop = false;
	
	int userSelectedDomain = -1;

	/*
	 * This method gets invoked before the fillWithLipids() process is started
	 * This is the point where you should present your GUI, like i'm doing here
	 */
	public boolean buildAndShowGui() {

		// check if the user selected a membrane
		if (this.getSelectedArea() != -1) {

			int val = JOptionPane
					.showConfirmDialog(
							null,
							"You have currently selected a particular area.\nDo you want to consider this area only?\n",
							"Selected Area", JOptionPane.YES_NO_OPTION,
							JOptionPane.QUESTION_MESSAGE);

			if (val == JOptionPane.YES_OPTION) {
				userSelectedDomain = this.getSelectedArea();
			}

		}
		
		// building up the options panel for the GUI
		final JPanel controlPanel = new JPanel();
		final JLabel infoLipids = new JLabel("List Dimension of ");
		final JCheckBox checkBoxLipidTypes = new JCheckBox("Lipid Types");
		checkBoxLipidTypes.setSelected(true);
		final JCheckBox checkBoxLipids = new JCheckBox("Membrane Lipids");
		checkBoxLipids.setSelected(true);

		controlPanel.add(infoLipids);
		controlPanel.add(checkBoxLipidTypes);
		controlPanel.add(checkBoxLipids);

		/*
		 * showing the GUI to the user. The returnvalue indicates if the user
		 * confirmed or cancelled the dialog so far
		 */
		final boolean returnValue = MembranePackingAlgorithmGUI.popUpGUI(controlPanel,
				"Dimension Lister", "Options");

		// returning false if all should be cancelled
		if (returnValue == false)
			return false;

		this.listLipidTypes = checkBoxLipidTypes.isSelected();
		this.listLipids = checkBoxLipids.isSelected();

		/*
		 * the user settings are ok plus the user must have confirmed if the
		 * thread reaches this code, so true gets returned in order to proceed
		 */
		return true;

	}

	/*
	 * This method gets called when the algorithm gets started. Remember: The
	 * algorithm finishes when the thread leaves this method
	 */
	public void fillWithLipids() throws Exception {

		int counter = 0;
		
		Lipid [] lipArray = null;
		
		Vector<Lipid> lips = null;
		
		String dimensionInfo = "";
		
		this.setDisplayedProgress(0);
		
		/*
		 * starting a loop that runs until the terminal condition is reached or
		 * the stop flag has been set on true
		 */
		while (this.stop == false) {

			if (lipArray != null)
				for (int i=0;i<lipArray.length;i++)
					dimensionInfo += this.getDimensionInfo(lipArray[i], SAMPLELIPID);
			
			if (lips != null) 
				for (Lipid l : lips)
					dimensionInfo += this.getDimensionInfo(l, MEMBRANELIPID);
			
			if (counter == 0 && listLipidTypes) {
				this.displayInformation("List Lipid Type Dimensions Extracellular.");
				lipArray = this.getAllSampleLipids_ExtraCellular(this.getCurrentLayerID(), userSelectedDomain);
				dimensionInfo += "********* SAMPLE LIPIDS EXTRA *********";
				this.setDisplayedProgress(25);
			} else if (counter ==1 && listLipidTypes) {
				this.displayInformation("List Lipid Type Dimensions Intracellular.");
				lipArray = this.getAllSampleLipids_IntraCellular(this.getCurrentLayerID(), userSelectedDomain);
				this.setDisplayedProgress(50);
				dimensionInfo += "\n\n\n********* SAMPLE LIPIDS INTRA *********";
			} else if (counter ==2 && listLipids) {
				lipArray = null;
				this.displayInformation("List Lipid Dimensions Extracellular.");
				lips = this.getAllLipidsInDomain(userSelectedDomain, MembranePackingAlgorithm.EXTRA);
				this.setDisplayedProgress(75);
				dimensionInfo += "\n\n\n********* MEMBRANE LIPIDS EXTRA *********";
			} else if (counter ==3 && listLipids) {
				lipArray = null;
				lips.clear();
				this.displayInformation("List Lipid Dimensions Intracellular.");
				lips = this.getAllLipidsInDomain(userSelectedDomain, MembranePackingAlgorithm.INTRA);
				this.setDisplayedProgress(100);
				dimensionInfo += "\n\n\n********* MEMBRANE LIPIDS INTRA *********";
			} else if (counter ==4) {
				dimensionInfo += this.getMembraneInfo(this.getCurrentLayerID(), userSelectedDomain);
			}
			
			counter ++;
			if (counter==5)
				this.stop = true;
			
		}

		this.showOptionScrollPane(dimensionInfo);
		
		this.displayInformation("Finished.");

	}

	/**
	 * Add the Dimension Info to the given String.
	 * @param l lipid to get information
	 * @param lipidInfo additional info which should be added to the String
	 * @return
	 */
	public String getDimensionInfo(Lipid l, int lipidType) {
		
		Matrix3f storeMatrix = l.getRotationMatrix();
		
		l.setRotationMatrix(new Matrix3f(	1,0,0,
											0,1,0,
											0,0,1));
		
		BoundingBox bb = l.getBounds();
		
		Point3d lower = new Point3d();
		bb.getLower(lower);

		Point3d upper = new Point3d();
		bb.getUpper(upper);
		
		String dimensionInfo = "\n\nDimension Info for " +l.getType()+" "+((l.getMembraneSide()==EXTRA)?"EXTRA":"INTRA")+
				" "+(lipidType==SAMPLELIPID?" *SAMPLELIPID*":"")+
				"\nX-Length: "+l.getXZBounds().width+
				"\nZ-Length: "+l.getXZBounds().height+
				"\nY-Length: "+Math.abs(upper.y - lower.y)+
				"\nArea: "+l.getXZBounds().width*l.getXZBounds().height+
				"\nMaxRadius_XZ: "+l.getMaxRadius_XZ()+
				"\nRadius: "+l.calculateRadius();
		
		if (lipidType==MEMBRANELIPID) {
			if (lipidTypeDimensions.get(l.getType())!=null) {
				double actualValue = lipidTypeDimensions.get(l.getType());
				lipidTypeDimensions.put(l.getType(), l.getXZBounds().width*l.getXZBounds().height+actualValue);
			} else {
				lipidTypeDimensions.put(l.getType(), l.getXZBounds().width*l.getXZBounds().height);
			}
			
			if (lipidTypeDimensions.get("ALL")!=null) {
				double actualValue = lipidTypeDimensions.get("ALL");
				lipidTypeDimensions.put("ALL", l.getXZBounds().width*l.getXZBounds().height+actualValue);
			} else {
				lipidTypeDimensions.put("ALL", l.getXZBounds().width*l.getXZBounds().height);
			}
		}
		
		l.setRotationMatrix(storeMatrix);
		
		return dimensionInfo;
	}
	
	/**
	 * Add the Dimension Info to the given String.
	 * @param l lipid to get information
	 * @param lipidInfo additional info which should be added to the String
	 * @return
	 */
	public String getMembraneInfo(int layerID, int selectedDomain) {

		String dimensionInfo = "";
		double dimensionValue = 0;
		int layerDivisor = (this.hasMembraneAnyExtracellularLipids()&&this.hasMembraneAnyIntracellularLipids())?2:1;
		
		if (selectedDomain==-1 || (selectedDomain==0 && this.getMicroDomainCount()==0)) {
			dimensionValue = this.getMembraneArea();
			dimensionInfo = "\n\n******Dimension Info for Membrane******"+
							"\nX-Length: "+this.getMembraneSize().height+
							"\nZ-Length: "+this.getMembraneSize().width+
							"\nArea: "+dimensionValue;
		} else if (selectedDomain==0) {
			double areaOfAllMicrodomains = 0d;
			for (int i=0;i<this.getMicroDomainCount();i++) {
				areaOfAllMicrodomains += this.getMicroDomainAt(i).getArea();
			}
			dimensionValue = this.getMembraneArea()-areaOfAllMicrodomains;
			dimensionInfo += "\n\n******Dimension Info for MicroDomain "+selectedDomain+"******"+
							"\nX-Length: "+this.getMembraneSize().height+
							"\nZ-Length: "+this.getMembraneSize().width+
							"\nArea: "+dimensionValue;
		} else {
			dimensionValue = this.getMicroDomainAt(selectedDomain-1).getArea();
			dimensionInfo += "\n\n******Dimension Info for Selected Domain"+selectedDomain+"******"+
							"\nX-Length: "+this.getMicroDomainAt(selectedDomain-1).getBounds().width+
							"\nZ-Length: "+this.getMicroDomainAt(selectedDomain-1).getBounds().height+
							"\nArea: "+dimensionValue;
		}
		
		dimensionInfo += "\n\n******Overall Area of all Lipids"+selectedDomain+"******";
		
		Set<Entry<String, Double>> setOfLipidTypeDimensions = lipidTypeDimensions.entrySet();
		for(Entry<String, Double> entry : setOfLipidTypeDimensions) {
			dimensionInfo +="\n"+entry.getKey()+" "+entry.getValue()+" ("+
							( ((entry.getValue()*100)/dimensionValue)/layerDivisor )+
							"%)";
		}
		
		return dimensionInfo;
	}
	
	/**
	 * Shows a ScrollPane with an OptionPane and the given text as dialog.
	 * @param text
	 */
	public void showOptionScrollPane(String text) {
		
		JFrame frame = new JFrame();
		JTextArea textArea = new JTextArea(text);
		JScrollPane scrollPane = new JScrollPane(textArea);
		scrollPane.setMaximumSize(new Dimension(frame.getWidth()/2,frame.getHeight()/2));
		scrollPane.setEnabled(false);
		JPanel panel = new JPanel();
		scrollPane.setPreferredSize(new Dimension(scrollPane.getPreferredSize().width+100, 400));
		panel.add(scrollPane);
		JOptionPane.showMessageDialog(	frame,
										panel,
										"Dimension Lister",
										JOptionPane.INFORMATION_MESSAGE);
		
	}
	
	public String getFullInformation() {

		// this string is getting displayed in the textarea were the algorithms
		// are selected
		return	"<html>This algorithm lists information about the <br>" +
				"dimensions of the sample and membrane lipids.<br><br>" +
				"Author: Bj&ouml;rn Sommer<br>" +
				"Version: 1.0</html>";
	}

	public String getName() {

		// the chosen name for this algorithm. It has to return a unique name in
		// order to get accepted
		return "Dimension Lister Tool";
	}

	public Component getRuntimeControlPanel() {

		// we don't provide any runtime controls for this simple algorithm
		return null;
	}

	public String getShortInformation() {

		// you can leave out some information if you want
		return "Lists dimension of lipids.";
	}

	public String getStringRepresentationOfSetupParameters() {

		// returning a String that describes the users setup
		return "List Dimensions extracellular lipids: " + this.listLipidTypes
				+ "\n" + "List Dimensions intracellular lipids: "
				+ this.listLipids;
	}

	public boolean overrideExistingLipids() {

		// existing lipids may remain in this case
		return false;
	}

	public void stopAlgorithm() {

		// setting the stop flag on true, forcing the algorithm to stop at the
		// next loop
		this.stop = true;
	}

	public boolean supportsAbsoluteValues() {
		// this distribution mode is not important for this algorithm, 
		// therefore this is set to true
		return true;
	}
	
	public boolean supportsMicrodomains() {
		// this algorithm provides a full microdomain support
		return true;
	}
	
}
