/*
 *  Copyright 2006-2007 Columbia University.
 *
 *  This file is part of MEAPsoft.
 *
 *  MEAPsoft is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License version 2 as
 *  published by the Free Software Foundation.
 *
 *  MEAPsoft 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
 *  General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with MEAPsoft; if not, write to the Free Software
 *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 *  02110-1301 USA
 *
 *  See the file "COPYING" for the text of the license.
 */


package com.meapsoft.visualizer;

import java.awt.Color;
import java.awt.Graphics;
import java.awt.Rectangle;
import java.awt.event.MouseListener;
import java.text.DecimalFormat;
import java.util.Iterator;
import java.util.Vector;

import javax.swing.JOptionPane;
import javax.swing.JPanel;

import com.meapsoft.FeatChunk;
import com.meapsoft.FeatFile;
import com.meapsoft.MEAPUtil;
import com.meapsoft.gui.ColorMap;

/**
 * displays the a single feature
 * meant to be stacked in various ways to create multi-file visualizations  
 *
 * @author Douglas Repetto (douglas@music.columbia.edu)
 * and the MEAP team
 */

public abstract class SingleFeaturePanel extends JPanel implements MouseListener
{
	FeatFile featFile;
	String shortFileName;
	String featureName;
	String slash = MEAPUtil.slash;
	//featureNumbers -1 -2 -3 etc are special cases for Length, StartTime, Segments Only, etc.
	//featureNumber -1000 is an error condition
	int featureNumber = -1000;
	//how many data points are in one chunk of this feature
	int featureSize = -1;
	
	//can we draw multi-dimensional features?
	//set max features, or -1 for don't care
	int numDrawableFeatures = 1;
	
	//FeatFile featInputFile;
	
	Vector events = new Vector();
	
	double featureData[][];
	int numChunks;
	double highestValue = 0.0d;
	double lowestValue = 0.0d;
	double featureRange = 0.0;
	double firstEventTime = 0.0;
	double lastEventTime = 0.0;
	double endTime = 0.0;
	double timeRange = 0.0;
	
	Color bgColor = Color.WHITE;
	Color fGColor = null;
	Color outlineColor = Color.black;
	Color segmentTicksColor = Color.gray;
	Color panelInfoColor = Color.gray;
	Color timelineColor = Color.yellow;
	
	boolean showScale = true;
	//boolean showSegmentTicks = true;
	boolean showPanelInfo = true;
	boolean selected = false;
	
	static final int NO_TIMELINE = 0;
	static final int TENTHS_TIMELINE = 1;
	static final int SECS_TIMELINE = 2;
	static final int MINS_TIMELINE = 3;
	
	static final int NO_SEG_TICKS = 0;
	static final int SHORT_SEG_TICKS = 1;
	static final int FULL_SEG_TICKS = 2;
	
	int segTickType = SHORT_SEG_TICKS;
	int timelineType = NO_TIMELINE;
	
	double zoomLevel = 1.0;
	int firstChunkToDraw = 0;
	
	//DecimalFormat oneTwoNumberFormat = new java.text.DecimalFormat(); 
	DecimalFormat oneTwoNumberFormat = new java.text.DecimalFormat(); 
	DecimalFormat anyZeroNumberFormat = new java.text.DecimalFormat(); 
	
	public SingleFeaturePanel(FeatFile featFile, String featureName) 
	{
		setSize(600, 400);
		
		this.featFile = featFile;
		this.featureName = featureName;
		
		//System.out.println("running with: " + featFile.filename + " " + featureName);
		addMouseListener(this);
		
		fGColor = new Color((int)(Math.random() * 127.0) + 100, (int)(Math.random() * 127.0) + 100, 
				(int)(Math.random() * 127.0) + 100);
		
		oneTwoNumberFormat.setMaximumIntegerDigits(1);
		oneTwoNumberFormat.setMaximumFractionDigits(2);
		oneTwoNumberFormat.setMinimumFractionDigits(1);
		
		anyZeroNumberFormat.setMinimumIntegerDigits(1);
		anyZeroNumberFormat.setMaximumFractionDigits(0);

	}
	
	int initialize()
	{		
		highestValue = 0.0d;
		lowestValue = 0.0d;
		featureRange = 0.0;
		firstEventTime = 0.0;
		lastEventTime = 0.0;
		endTime = 0.0;
		timeRange = 0.0;
		
		//check to see if our feature is in the input file
		featureNumber = verifyFeature(featureName);
		if (featureNumber == -1000)
		{
			System.out.println("hmm, I don't find that feature! Bye.");
			return -1;
		}
		//System.out.println("using feature number: " + featureNumber);
		
		int elementsPerFeature[] = {0};

		if (featureNumber >= 0)
		{
			elementsPerFeature = featFile.getFeatureLengths();
			featureSize = elementsPerFeature[featureNumber];
		}
		else
			featureSize = 1;
		
		//System.out.println("featureSize: " + featureSize);
		
		//bail if we can't draw the feature
		if (featureSize > numDrawableFeatures && numDrawableFeatures != -1)
		{
			String message = "Sorry, " + getDisplayType() + " display can only " +
					"display features having " + numDrawableFeatures + " feature(s).";
			JOptionPane.showMessageDialog(null, message, "whoops", JOptionPane.ERROR_MESSAGE);

			//System.out.println("I can't draw that feature, too many dimensions!");
			return -1;
		}
		
		events = featFile.chunks;
	
		//extract feature data, find highest/lowest feature values
		numChunks = events.size();		
		featureData = new double[numChunks][featureSize];
		
		if (featureNumber >= 0)
		{
			for (int i = 0; i < numChunks; i++)
			{			
				for (int j = 0; j < featureSize; j++)
				{
					FeatChunk fC = (FeatChunk)events.elementAt(i);
					int[] dim = {featureNumber + j};
					double feature = fC.getFeatures(dim)[0];
					featureData[i][j] = feature;
					
					if (feature < lowestValue)
						lowestValue = feature;
				
					if (feature > highestValue)
						highestValue = feature;
					
					if (fC.startTime < firstEventTime)
						firstEventTime = fC.startTime;
					
					if (fC.startTime > lastEventTime)
						lastEventTime = fC.startTime;
					
					if (fC.startTime + fC.length > endTime)
						endTime = fC.startTime + fC.length;
				}
			}
		}
		//do Length & StartTime features
		else
		{
			for (int i = 0; i < numChunks; i++)
			{			
				FeatChunk fC = (FeatChunk)events.elementAt(i);
				double feature = 0.0;
				
				//Length
				if (featureNumber == -1)
					feature = fC.length;
				else if (featureNumber == -2)
					feature = fC.startTime;
				
				featureData[i][0] = feature;
				
				if (feature < lowestValue)
					lowestValue = feature;
			
				if (feature > highestValue)
					highestValue = feature;
				
				if (fC.startTime < firstEventTime)
					firstEventTime = fC.startTime;
				
				if (fC.startTime > lastEventTime)
					lastEventTime = fC.startTime;
				
				if (fC.startTime + fC.length > endTime)
					endTime = fC.startTime + fC.length;
			}
		}
		//System.out.println("feature parsed -- highestValue: " + highestValue +
		//		" lowestValue: " + lowestValue);
		
		featureRange = highestValue - lowestValue;
		timeRange = endTime - firstEventTime;
		
		String fileNameParts[] = featFile.filename.split(slash);
		shortFileName = fileNameParts[fileNameParts.length - 1];

		return 1;
	}
	
	void setShowPanelInfo(boolean sST)
	{
		showPanelInfo = sST;
	}
	
	boolean getShowPanelInfo()
	{
		return showPanelInfo;
	}

	void setShowTimeline(int tT)
	{
		timelineType = tT;
	}
	
	int getShowTimeline()
	{
		return timelineType;
	}


	void setSegTickType(int sTT)
	{
		segTickType = sTT;
	}
	
	int getSegTickType()
	{
		return timelineType;
	}
/*
	void setShowSegmentTicks(boolean sST)
	{
		showSegmentTicks = sST;
	}
	
	boolean getShowSegmentTicks()
	{
		return showSegmentTicks;
	}
*/
	void setShowScale(boolean show)
	{
		showScale = show;
	}
	
	boolean getShowScale()
	{
		return showScale;
	}
	
	void setSelected(boolean selected)
	{
		this.selected = selected;
		//System.out.println(featureName + ": selected = " + selected);
	}
	
	void toggleSelected()
	{
		selected = !selected;
	}
	
	boolean isSelected()
	{
		return selected;
	}
	
	int setFeatureName(String fN)
	{
		String oldFeatureName = featureName;
		int oldFeatureNumber = featureNumber;
		
		featureName = fN;
		
		int error = initialize();
		
		if (error == -1)
		{
			featureName = oldFeatureName;
			featureNumber = oldFeatureNumber;
			return error;
		}
		else
			return 0;
	}
	
	int verifyFeature(String fN)
	{
		int numFeats = featFile.featureDescriptions.size();
		Vector featDescs = featFile.featureDescriptions;
		
		for (int i = 0; i < numFeats; i++)
		{
			String featDesc = (String)featDescs.elementAt(i);
			//System.out.println("checking: " + featDesc);

			//dumb 1.4.2 way of telling if a string contains another string
			if (featDesc.indexOf(featureName) > 0)
			{
				//System.out.println("matched on: " + featureName + "!");
				return i;
			}
		}
		
		if (fN.equals("Length"))
		{
			//System.out.println("matched on: Length!");
			return -1;
		}
		else if (fN.equals("StartTime"))
		{
			//System.out.println("matched on: StartTime!");
			return -2;
		}
		else if (fN.equals("SegmentsOnly"))
		{
			return -3;
		}
		//we can't find that feature!
		return -1000;
	}
	
	protected void paintComponent(Graphics g)
	{
		if (featureSize <= numDrawableFeatures || numDrawableFeatures == -1)
			drawData(g);
		else
			errorDrawer(g, "I can't draw that feature, too many dimensions!");
		
		
		if (segTickType != NO_SEG_TICKS)
			drawSegmentTicks(g);
		
		if (timelineType != NO_TIMELINE)
			drawTimeline(g);
		
		if (showPanelInfo)
			drawPanelInfo(g);
		
		if (showScale)
			drawScale(g);
		
		if (selected)
			drawBorder(g);
	}
	
	public abstract void drawData(Graphics g);
	
	//	weird drawing sFPs may want to override this to draw file info in a different way/place
	void drawPanelInfo(Graphics g)
	{
		int yOffset = (int)(this.getHeight() * 0.2);
		
		g.setColor(panelInfoColor);
		
		g.drawString(shortFileName + " - " + featureName + " - zoom:" + zoomLevel, 20, yOffset);
	}
	
	//	weird drawing sFPs may want to override this to draw scale in a different way/place
	void drawScale(Graphics g)
	{
		g.setColor(Color.BLACK);
		g.drawString("max: " + highestValue, 5, 15);
		//g.drawString("" + (highestValue + lowestValue)/2.0, 5, (this.getHeight() - 10)/2);
		g.drawString("min: " + lowestValue, 5, this.getHeight() - 10);
	}

	//weird drawing sFPs may want to override this to draw timeline in a different way/place	
	void drawTimeline(Graphics g)
	{	
		double tickTimeIncr = 1.0;
		double localFirstEventTime = ((FeatChunk)events.elementAt(firstChunkToDraw)).startTime;		
		
		double firstTickTime = localFirstEventTime;//(int)Math.ceil(localFirstEventTime);
		
		switch(timelineType)
		{
			case TENTHS_TIMELINE:
				tickTimeIncr = 0.1;
				//need to shift around so that we only draw ticks on exactly
				//.1s increments
				firstTickTime *= 10;
				firstTickTime = (int)Math.ceil(firstTickTime);
				firstTickTime *= 0.1;
				break;
			case SECS_TIMELINE:
				tickTimeIncr = 1.0;
				firstTickTime = (int)Math.ceil(localFirstEventTime);
				break;
			case MINS_TIMELINE:
				tickTimeIncr = 60.0;
				if (localFirstEventTime % 60 != 0)
				{
					int offset = 60 - (int)(localFirstEventTime % 60);
					firstTickTime += offset;
					firstTickTime = (int)Math.floor(firstTickTime);				
				}
				break;
		}

		int w = (int)(getWidth() * zoomLevel);
		int h = this.getHeight();
		//height of ticks
		int ySize = (int)(h * 0.1);
		
		double xScaler = w/timeRange;
		
		g.setColor(timelineColor);
		
		//System.out.println("localFirstEventTime: " + localFirstEventTime + " firstTickTime: " + firstTickTime);
		
		double tickTime = firstTickTime;
		
		int x = 0;
		
		while (tickTime <= lastEventTime && x < getWidth())
		{
			String timeLabel = "" + tickTime;
			
			switch(timelineType)
			{
				case TENTHS_TIMELINE:
					timeLabel = oneTwoNumberFormat.format(tickTime);
					break;
				case SECS_TIMELINE:
				case MINS_TIMELINE:
					timeLabel = anyZeroNumberFormat.format(tickTime);
					break;
			}
			
			double drawTime = tickTime - localFirstEventTime;
			x = (int)(drawTime * xScaler);
			g.drawLine(x, h, x, h - ySize);
			g.drawString(timeLabel, x, h - ySize);
			tickTime += tickTimeIncr;
		}
		//System.out.println("bailed at: " + x + " tickTime: " + tickTime);
	}
	
	//weird drawing sFPs may want to override this to draw segment ticks in a different way/place	
	void drawSegmentTicks(Graphics g)
	{
		int w = (int)(this.getWidth() * zoomLevel);
		int h = this.getHeight();
		//size of ticks
		int ySize = (int)(h * 0.05);
		
		double xScaler = w/timeRange;
		
		g.setColor(segmentTicksColor);
		
		double localFirstEventTime = ((FeatChunk)events.elementAt(firstChunkToDraw)).startTime;
		
		Iterator it = events.iterator();
		
		for (int i = 0; i < firstChunkToDraw; i++)
			it.next();
		
		int x = 0;
		
		while (it.hasNext () && x < getWidth())
		{
			FeatChunk fC = (FeatChunk)it.next();
			double startTime = fC.startTime - localFirstEventTime;
			
			x = (int)(startTime * xScaler);
			
			if (segTickType == SHORT_SEG_TICKS)
			{
				g.drawLine(x, 0, x, ySize);
				g.drawLine(x, h, x, h - ySize);
			}
			else
			{
				g.drawLine(x, 0, x, h);
			}
		}
	}
	
	void drawBorder(Graphics g)
	{
		g.setColor(outlineColor);
		
		Rectangle r = getLocalBounds();
		g.drawRect(r.x, r.y, r.width - 1 , r.height - 1);
		g.drawRect(r.x + 1, r.y + 1, r.width - 3, r.height - 3);
	}
	
	void errorDrawer(Graphics g, String error)
	{
		int w = this.getWidth();
		int h = this.getHeight();
		
		g.setColor(bgColor);
		g.fillRect(0, 0, w, h);
		
		g.setColor(fGColor);
		g.drawString(error, 10, 20);
	}

	Rectangle getLocalBounds()
	{
		Rectangle bounds = getBounds();
		
		return new Rectangle(0, 0, bounds.width, bounds.height);
	}
	
	public abstract String getDisplayType();
	
	void setBackgroundColor(Color c)
	{
		bgColor = c;
		repaint();
	}
	
	void setForegroundColor(Color c)
	{
		fGColor = c;
	}
	
	void zoomIn()
	{
		zoomLevel++;// += zoomLevel * .25;
	}
	
	void zoomOut()
	{
		zoomLevel--;// -= zoomLevel * .25;
		if (zoomLevel < 1.0)
			zoomLevel = 1.0;
	}
	
	void resetZoom()
	{
		zoomLevel = 1.0;
		firstChunkToDraw = 0;
	}
	
	void setZoomLevel(double zL)
	{
		if (zL >= 1.0)
			zoomLevel = zL;
	}
	
	double getZoomLevel()
	{
		return zoomLevel;
	}
	
	void incrFirstChunkToDraw()
	{
		firstChunkToDraw++;
		
		if (firstChunkToDraw > events.size() - 1)
			firstChunkToDraw = events.size() - 1;
	}
	
	void decrFirstChunkToDraw()
	{
		firstChunkToDraw--;
		
		if (firstChunkToDraw < 0)
			firstChunkToDraw = 0;
	}
	
	void setFirstChunkToDraw(int fCTD)
	{
		firstChunkToDraw = fCTD;
		
		if (firstChunkToDraw > events.size() - 1)
			firstChunkToDraw = events.size() - 1;
		
		if (firstChunkToDraw < 0)
			firstChunkToDraw = 0;
	}
	
	int getFirstChunkToDraw()
	{
		return firstChunkToDraw;
	}
	
}