package ac.essex.ooechs.music;

import ac.essex.ooechs.imaging.commons.PixelLoader;
import ac.essex.ooechs.imaging.commons.Pixel;
import ac.essex.ooechs.imaging.commons.util.LineEquation;
import ac.essex.ooechs.music.imaging.MusicParser;
import ac.essex.ooechs.music.util.Key;
import ac.essex.ooechs.music.util.Staff;

import javax.swing.*;
import javax.sound.midi.Synthesizer;
import javax.sound.midi.MidiSystem;
import javax.sound.midi.MidiChannel;
import java.awt.*;
import java.awt.event.MouseMotionAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseAdapter;


/**
 * Takes in an image and plays the music on it.
 *
 * @author Olly Oechsle, University of Essex, Date: 05-Jul-2007
 * @version 1.0
 */
public class MusicPanel extends JPanel {

    public PixelLoader image;

    public MusicParser mp;

    public MidiChannel treblechannel, basschannel;

    protected int currentX = 50;

    protected Pixel currentNote;

    protected JavaMusic owner;

    protected Pixel startPoint;

    protected LineEquation upperLine, lowerLine;

    int lineIndex = 0;

    public MusicPanel(JavaMusic owner) throws Exception {

        this.owner = owner;

        Synthesizer synthesizer = MidiSystem.getSynthesizer();
        synthesizer.open();
        treblechannel = synthesizer.getChannels()[0];
        basschannel = synthesizer.getChannels()[0];

        addMouseMotionListener(new MouseMotionAdapter() {
            public void mouseMoved(MouseEvent e) {
                if (image != null) {
                    if (!playing) {
                        upperLine = mp.lines.elementAt(lineIndex);
                        lowerLine = mp.lines.elementAt(lineIndex + 1);
                        startPoint = new Pixel(e.getX(), e.getY());
                        currentX = e.getX();
                        repaint();
                    }
                }
            }

        });

        addMouseListener(new MouseAdapter() {
            public void mouseClicked(MouseEvent e) {
                if (image != null) {
                    if (!playing) new Player().start();
                    else {
                        playing = false;
                    }
                }
            }
        });

    }

    public void loadImage(PixelLoader image) {
        this.image = image;
        repaint();
        owner.setMessage("Reading Music");
        try {
            mp = new MusicParser(image);
            owner.setSize(image.getWidth() + 40, image.getHeight() + 70);
            repaint();
        } catch (Exception e) {
            alert(e.toString());
            e.printStackTrace();
        }
    }

    private void alert(String message) {
        System.out.println(message);
    }

    public void paintComponent(Graphics g) {
        if (image != null) {
            g.setColor(Color.WHITE);
            g.fillRect(0, 0, getWidth(), getHeight());
        } else {
            g.setColor(Color.BLACK);
            g.fillRect(0, 0, getWidth(), getHeight());
            return;
        }
        g.drawImage(image.getBufferedImage(), 0, 0, null);
        if (image != null && mp != null) {

            for (int i = 0; i < mp.staffs.size(); i++) {
                Staff staff = mp.staffs.elementAt(i);
                g.setColor(staff.type == Staff.TREBLE? Color.GREEN : Color.RED);
                for (int j = 0; j < staff.clefs.length; j++) {
                    LineEquation line = staff.clefs[j];
                    if (line != null) {
                        g.drawLine(mp.startX, (int) line.getY(mp.startX), mp.endX, (int) line.getY(mp.endX));
                    }
                }

                try {
                // draw a box around the start
                g.drawRect(mp.startX, (int) staff.clefs[4].getY(mp.startX), staff.getHeight(), staff.getHeight());
                } catch (Exception e) {}

            }

            g.setColor(Color.LIGHT_GRAY);
            for (int i = 0; i < mp.lines.size(); i++) {
                LineEquation line = mp.lines.elementAt(i);
                g.drawLine(0, (int) line.getY(0), getWidth(), (int) line.getY(getWidth()));
            }

            /*            g.setColor(Color.RED);
            for (int i = 0; i < mp.points.size(); i++) {
                Pixel pixel = mp.points.elementAt(i);
                g.fillOval(pixel.x - 3, pixel.y - 3, 7, 7);
            }*/


            int upperY = 0; // (int) upperLine.getY(currentX);
            int lowerY = getHeight(); //(int) lowerLine.getY(currentX);

            for (int i = 0; i < mp.notes.size(); i++) {
                Pixel pixel = mp.notes.elementAt(i);
                if (pixel.x == currentX && pixel.y > upperY && pixel.y < lowerY) {
                    g.setColor(Color.RED);
                    playNote(pixel);
                } else {
                    g.setColor(Color.CYAN);
                }
                g.fillOval(pixel.x - 4, pixel.y - 4, 9, 9);
            }
            g.setColor(Color.BLACK);
            g.drawLine((int) mp.cursor.getX(0) + currentX, 0, (int) mp.cursor.getX(getHeight()) + currentX, getHeight());
        }
    }

    boolean playing = false;

    int lasttreblenote = -1;
    int lastbassnote = -1;

    public void playNote(Pixel p) {

        // Get the closest clef
        Staff closest = null;
        double smallestDistance = Double.MAX_VALUE;
        for (int i = 0; i < mp.staffs.size(); i++) {
            Staff staff = mp.staffs.elementAt(i);
            double distance = staff.getClosestLine(p);
            if (distance < smallestDistance) {
                closest = staff;
                smallestDistance = distance;
            }
        }

        if (closest.type == Staff.TREBLE) {
            int noteIndex = closest.getNote(p);
            int pitch = Key.getPitch(noteIndex, closest);
            treblechannel.noteOn(pitch, 64);
            if (lasttreblenote != -1) {
                treblechannel.noteOff(lasttreblenote);
            }
            lasttreblenote = pitch;
        } else {
            int noteIndex = closest.getNote(p);
            int pitch = Key.getPitch(noteIndex, closest);
            basschannel.noteOn(pitch, 32);
            if (lastbassnote != -1) {
                basschannel.noteOff(lastbassnote);
            }
            lastbassnote = pitch;
        }

    }

    class Player extends Thread {

        public void run() {
            playing = true;
            while (playing && currentX < mp.endX) {
                currentX++;
                repaint();
                try {
                    Thread.sleep((owner.tempo.getMaximum() - owner.tempo.getValue()) + 1);
                } catch (InterruptedException e) {
                    // do nothing
                }
            }
            currentX = startPoint.x;
            playing = false;
            repaint();
        }

    }


}
