import java.awt.Dimension;
import java.awt.Font;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.AdjustmentEvent;
import java.awt.event.AdjustmentListener;
import java.awt.event.FocusEvent;
import java.awt.event.FocusListener;
import java.awt.event.MouseWheelEvent;
import java.awt.event.MouseWheelListener;

import javax.swing.Box;
import javax.swing.BoxLayout;
import javax.swing.DefaultListModel;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollBar;
import javax.swing.JScrollPane;
import javax.swing.JSlider;
import javax.swing.JTextField;
import javax.swing.ListSelectionModel;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;

public class TimeLineJPanel extends JPanel implements MouseWheelListener, FocusListener, ActionListener, ChangeListener, AdjustmentListener {
	private static final long serialVersionUID = 1L;
	private static final Font titleFont = new Font(new JLabel().getFont().getName(), Font.BOLD, 14);

	private static final int SCROLL_SPEED_HORIZONTAL = 30;
	private static final int SCROLL_SPEED_VERTICAL = 30;
	
	private DefaultListModel<Music> listModel;
	private double zoomLevel = 1;
	
	private JScrollPane scrollPane;
	private MusicRenderer musicRenderer;
	private Interface interf;
	private JTextField currentTick;
	private JLabel maxTicks;
	private JPanel scrollPanel;

	public TimeLineJPanel(Interface interf) {
		this.interf = interf;
		setLayout(new BoxLayout(this, BoxLayout.PAGE_AXIS));
		
		// timing infos
		JPanel timingPanel = new JPanel();
		timingPanel.setLayout(new BoxLayout(timingPanel, BoxLayout.LINE_AXIS));
		
		JLabel title = new JLabel("Timeline : ");
		title.setFont(titleFont);
		timingPanel.add(title);
		
		currentTick = new JTextField("00:00.000", 8);
		currentTick.setMaximumSize( currentTick.getPreferredSize() );
		currentTick.addFocusListener(this);
		currentTick.addActionListener(this);
		timingPanel.add(currentTick);
		
		maxTicks = new JLabel(" / 00:00.0000 (MM:ss:ms)");
		timingPanel.add(maxTicks);
		
		timingPanel.add(Box.createHorizontalGlue());
		
		add(timingPanel);
		
		// scroll pane
		
		scrollPanel = new JPanel();
		scrollPanel.setLayout(new BoxLayout(scrollPanel, BoxLayout.PAGE_AXIS));
		scrollPane = new JScrollPane(scrollPanel, JScrollPane.VERTICAL_SCROLLBAR_ALWAYS, JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED);
		scrollPane.addMouseWheelListener(this);
		scrollPane.setWheelScrollingEnabled(false);
		scrollPane.getHorizontalScrollBar().addAdjustmentListener(this);
		scrollPane.setPreferredSize(new Dimension(800, 400));
		add(scrollPane);
		
		// slider
		
		interf.timeSlider = new JSlider(JSlider.HORIZONTAL, 0, 0, 0);
		interf.timeSlider.setMajorTickSpacing(1000);
		interf.timeSlider.setMinorTickSpacing(500);
		interf.timeSlider.setPaintTicks(true);
		interf.timeSlider.setAlignmentX(LEFT_ALIGNMENT);
		interf.timeSlider.setEnabled(false);
		interf.timeSlider.setUI(new CustomSliderUI(interf.timeSlider));
		interf.timeSlider.addChangeListener(this);
		scrollPanel.add(interf.timeSlider);
		
		// timeline
		
		listModel = new DefaultListModel<>();
		for(Music music : interf.musicList.getList())
			listModel.addElement(music);
		interf.timeLineList = new TimeLineJList(listModel, interf);
		musicRenderer = new MusicRenderer(this);
		interf.timeLineList.setCellRenderer(musicRenderer);
		interf.timeLineList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
		interf.timeLineList.setAlignmentX(LEFT_ALIGNMENT);
		scrollPanel.add(interf.timeLineList);
	}

	public double getZoomLevel() { return zoomLevel; }
	
	public void updateList() {
		final MusicList musicList = interf.musicList;
		
		listModel.clear();
		for(Music music : musicList.getList())
			listModel.addElement(music);
		
		// compute max slider
		long maxTick = 0;
		for(Music music : musicList.getList()) {
			if(music.getMusicPattern().getStop() > maxTick)
				maxTick = music.getMusicPattern().getStop();
		}
		interf.timeSlider.setMaximum((int) maxTick);

		// update need a delay and have to be call with another thread (note sure why...)
		new Thread(new Runnable() {
			@Override
			public void run() {
				try {
					Thread.sleep(10);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
				if(musicList.getList().size() != 0)
					updateTimeLine(0);
			}
		}).start();
	}
	
	public void updateTick() {
		currentTick.setText(Util.tickToText(interf.tick));
		long maxTick = 0;
		for(PatternJPanel pattern : musicRenderer.patterns) {
			pattern.refreshPreferedSize();
			long tick = pattern.getMusic().getMusicPattern().getStop();
			if(tick > maxTick)
				maxTick = tick;
		}
		maxTicks.setText(Util.tickToText(maxTick) + " (mm:ss:ms)");
	}
	
	public void mouseWheelMoved(MouseWheelEvent e) {
		if(e.getSource() == scrollPane) {
			if (e.isControlDown()) {
				if(interf.musicList.getList().size() != 0)
					updateTimeLine(e.getWheelRotation());
			} else if(e.isShiftDown()) {
				JScrollBar sb = scrollPane.getHorizontalScrollBar();
				sb.setValue(sb.getValue() + e.getWheelRotation() * SCROLL_SPEED_HORIZONTAL);
			} else {
				JScrollBar sb = scrollPane.getVerticalScrollBar();
				sb.setValue(sb.getValue() + e.getWheelRotation() * SCROLL_SPEED_VERTICAL);
			}
		}
	}
	
	public void updateTimeLine(int wheelRot) {
		if(wheelRot < 0)
			zoomLevel *= (double) wheelRot * -1.1;
		else if (wheelRot > 0)
			zoomLevel *= (double) wheelRot / 1.1;

		interf.timeSlider.setEnabled(musicRenderer.patterns.size() != 0);
		int maxWidth = 0;
		if(musicRenderer.patterns.size() == 0) {
			interf.timeSlider.setValue(0);
			maxWidth = (int) (scrollPane.getPreferredSize().width - new JScrollBar().getPreferredSize().getWidth()) - 1;
		} else {
			for(PatternJPanel pattern : musicRenderer.patterns) {
				pattern.refreshPreferedSize();
				int width = pattern.getEnd();
				if(width > maxWidth)
					maxWidth = width;
			}
		}
		
		interf.timeSlider.setMaximumSize(new Dimension(maxWidth, interf.timeSlider.getPreferredSize().height));
		interf.timeLineList.setMaximumSize(new Dimension(maxWidth, PatternJPanel.PATTERN_HEIGHT * musicRenderer.patterns.size()));
		interf.timeLineList.setPreferredSize(new Dimension(maxWidth, PatternJPanel.PATTERN_HEIGHT * musicRenderer.patterns.size()));
		scrollPanel.setPreferredSize(new Dimension(maxWidth, PatternJPanel.PATTERN_HEIGHT * musicRenderer.patterns.size()));
		scrollPane.repaint();
		scrollPane.getViewport().revalidate();
		scrollPanel.revalidate();
	}

	@Override
	public void focusLost(FocusEvent e) {
		if(e.getSource() == currentTick) {
			interf.tick = Util.textToTick(currentTick.getText());
			currentTick.setText(Util.tickToText(interf.tick));
			interf.computeTick();
		}
	}
	@Override
	public void focusGained(FocusEvent e) {
		if(e.getSource() == currentTick)
			currentTick.setCaretPosition(currentTick.getDocument().getLength());
	}

	@Override
	public void actionPerformed(ActionEvent e) {
		if(e.getSource() == currentTick)
			interf.getContentPane().requestFocus();
	}

	@Override
	public void stateChanged(ChangeEvent e) {
		if(e.getSource() == interf.timeSlider) {
			interf.tick = interf.timeSlider.getValue();
			interf.computeTick();
		}
	}

	@Override
	public void adjustmentValueChanged(AdjustmentEvent e) {
		if(e.getSource() == scrollPane.getHorizontalScrollBar()) {
			musicRenderer.setInfoPadding(scrollPane.getHorizontalScrollBar().getValue());
			scrollPane.repaint();
			scrollPane.getViewport().revalidate();
			scrollPanel.revalidate();
		}
	}
}
