package kr.gui.kreventgraph;

import java.awt.Color;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics2D;
import java.awt.geom.Rectangle2D;
import java.util.HashMap;
import java.util.Map;

import javax.swing.JPopupMenu;

import kr.gui.GuiModel;
import kr.gui.midigraph.MidiGraph;
import kr.gui.midigraph.MidiTextGraphController;
import kr.gui.util.SimplePopupMenu;
import kr.miditunemodel.Event;
import kr.miditunemodel.KREvent;
import kr.miditunemodel.MidiTuneModel;
import kr.miditunemodel.NoteEvent;

public class KREventGraph extends MidiGraph
{
	private Map<KREvent.Type, Integer> eventLengths = new HashMap<KREvent.Type, Integer>();
	private MidiTuneModel midiModel;
	private static final int MAX_TRACKS = 6;
	
	private static final Font TEXT_FONT = new Font("Arial", Font.PLAIN, 12);
	
	private final int DOT_SIZE = 3;
	private final int SELECTED_DOT_SIZE = 4;

	private FontMetrics fm;
	
	private SimplePopupMenu eventTypeEdit;

	public KREventGraph(GuiModel guiModel, MidiTuneModel midiModel,
			KREventGraphController controller)
	{
		super(guiModel);
		setFocusable(true);
		
		eventTypeEdit = new SimplePopupMenu();
		
		for(KREvent.Type type : KREvent.Type.values())
		{
			eventTypeEdit.add(type.getText());
		}
		
		eventTypeEdit.setVisible(false);
		this.add(eventTypeEdit);
		
		this.midiModel = midiModel;

		controller.init(guiModel, midiModel, this,  guiModel.getKREventGuiModel());

		
		fm = this.getFontMetrics(TEXT_FONT);

		//figure out the length for each event
		for(KREvent.Type type : KREvent.Type.values())
		{
			Rectangle2D r = fm.getStringBounds(type.toString(), getGraphics());
			eventLengths.put(type, (int)r.getWidth());
		}
		
		//
		// find minimum size
		//
		Dimension size = new Dimension(20, fm.getHeight() * MAX_TRACKS);
		setMinimumSize(size);
		setPreferredSize(size);
	}
	
	protected void doScrollingPaint(Graphics2D g, int startX, int endX) {
		// TODO: PERF, only redraw stuff that has been written over. If the
		// slider is moved,
		// slide over the bits and only redraw the new stuff

		int w = getWidth();
		int h = getHeight();

		g.setClip(startX, 0, endX-startX, h);
        g.setColor(Color.WHITE);
		g.fillRect(startX, 0, endX-startX, h);

		int nowX = guiModel.calcNowX(w);

		long nowMicros = guiModel.getCurrMicros();

		//
		// Draw items
		//
		long startMicros = guiModel.pixelsToMicros(startX, w);
		long endMicros = guiModel.pixelsToMicros(endX, w);
		
		//long microsEventLength = pixelsToRelMicros(eventLength);

        //get all notes that are within the screen
        //PERF specify a starting point
        KREvent k = midiModel.getKREventGroup().getAtOrBeforeMicros(null, 0, startMicros);
        if(k == null) k = midiModel.getKREventGroup().getEvents().getFirst();
        
        KREvent [] tracksFilled = new KREvent[MAX_TRACKS]; 
        
        // find the earliest event that may take up a track
        if(k != null)
        	while(k.getPrevious() != null && ((Event) k.getPrevious()).getMicros() + getMicrosEventLength((KREvent)k.getPrevious()) > k.getMicros())
        		k = (KREvent) k.getPrevious();
        
        //now go forward filling up tracks
        while(k != null && k.getMicros() < endMicros)
        {
        	int i = 0;
        	while(i < MAX_TRACKS && tracksFilled[i] != null && 
        			tracksFilled[i].getMicros() + getMicrosEventLength(tracksFilled[i]) > k.getMicros() ) i++;
        	
        	if(i != MAX_TRACKS)
        	{
        		tracksFilled[i] = k;
        		if(k.getMicros() + getMicrosEventLength(k) > startMicros)
        		{
                	//System.out.println("nowX: "+guiModel.microsToAbsPixels(nowMicros)+" x: "+guiModel.microsToPixels(w, nowMicros, k.getMicros())+" text: "+k.getKRType().getText());
        			drawEvent(g, nowMicros, k.getMicros(), k.getKRType().getText(), i,
        					guiModel.getKREventGuiModel().isSelectedEvent(k));
        		}
        	}
			
        	k = (KREvent)k.getNext();
		}
		g.setClip(0, 0, w, h);
	}

	private long getMicrosEventLength(KREvent n) {
		return pixelsToRelMicros(eventLengths.get(n.getKRType()));
	}

	private void drawEvent(Graphics2D g, long nowMicros, long micros, String text, int track, boolean selected) 
	{
		int w = getWidth();
		int h = getHeight();

		int x = guiModel.microsToPixels(w, nowMicros, micros);
		int y = fm.getAscent() + track * (fm.getHeight());

		int myDotSize;
		if(selected)
		{
			myDotSize = SELECTED_DOT_SIZE ;
			g.setColor(Color.BLACK);
		}
		else
		{
			g.setColor(Color.BLUE);
			myDotSize = DOT_SIZE;
		}
		
		g.fillRect(x, y - fm.getAscent()/2, myDotSize, myDotSize);

		g.setColor(Color.BLUE);
		g.setFont(TEXT_FONT);
		g.drawString(text, x+myDotSize+1, y);
	}

	public KREvent getEvent(int x, int y) {
		//TODO: Most of this is duplicated in the drawing code
		
		long startMicros = guiModel.pixelsToMicros(x, getWidth());
		//long microsEventLength = pixelsToRelMicros(eventLength); 
		
		int chosenTrack = getChosenTrack(y);

        //get all notes that are within the current hill of events
        KREvent n = midiModel.getKREventGroup().getAtOrBeforeMicros(null, 0, startMicros);
        if(n == null) n = midiModel.getKREventGroup().getEvents().getFirst();
        
        KREvent [] tracksFilled = new KREvent[MAX_TRACKS]; 
        
        // find the earliest event that may take up a track
        if(n != null)
        	while(n.getPrevious() != null && ((Event) n.getPrevious()).getMicros() + getMicrosEventLength((KREvent)n.getPrevious()) > n.getMicros())
        		n = (KREvent) n.getPrevious();
        
        //now go forward filling up tracks
        while(n != null && n.getMicros() <= startMicros)
        {
        	int i = 0;
        	while(tracksFilled[i] != null && tracksFilled[i].getMicros() + getMicrosEventLength(tracksFilled[i]) > n.getMicros() && i < MAX_TRACKS) i++;
        	
        	if(i != MAX_TRACKS)
        	{
        		tracksFilled[i] = n;
        		if(i == chosenTrack && n.getMicros() < startMicros && n.getMicros() + getMicrosEventLength(tracksFilled[i]) > startMicros)
        			return n;
        	}
			
        	n = (KREvent)n.getNext();
        }
        
		return null;
	}

	private int getChosenTrack(int y) {
		return y / fm.getHeight();
	}

	public void editEvent(int x, int y) {
		eventTypeEdit.show(this, x, y);
	}
	
	public JPopupMenu getEventTypeEditComponent()
	{
		return eventTypeEdit;
	}

	
}
