package vermilion;

// TODO: Make all imports specific, no awt.*.

// TODO: Introduce static constant on SystemComposer for 'Patterns'
// and one on SystemRouter for 'Machines' (plus methods to get them
// rather than direct access through document).

import java.awt.BorderLayout;
import relative.*;

import java.awt.Color;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.Font;
import java.awt.Rectangle;
import java.awt.event.KeyEvent;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
import java.util.ArrayList;
import javax.swing.DefaultListModel;
import javax.swing.JButton;
import javax.swing.JLabel;
import javax.swing.JList;
import javax.swing.JMenu;
import javax.swing.JMenuItem;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.KeyStroke;
import javax.swing.SwingUtilities;

/**
 * A panel that displays a tracker interface. This includes machine and pattern
 * lists as well as the 'piano roll' event cueing control.
 */
public class JTrackerPanel extends JPanel implements Runnable
{
    // The system composer which owns this panel.
    protected SystemComposerPatternData composerPatterns;
    protected SystemComposerPlayback composerPlayback;

    // The piano roll tracker interface control.
    private JPatternTable patternTable;

    // The list of machines that are in the composition.
    private DefaultListModel machineList;
    protected JList machineListC;
    
    // The list of patterns for a chosen machine, add/rem buttons.
    private DefaultListModel patternList;
    protected JList patternListC;
    protected JButton newPatternButton;
    protected JButton delPatternButton;

    // The list of tracks for a chosen machine/pattern, add/rem buttons.
    private DefaultListModel trackList;
    protected JList trackListC;
    protected JButton newTrackButton;
    protected JButton delTrackButton;

    // Play/stop buttons for panel and menu for main title bar.
    protected JMenu playbackMenu;
    protected JMenuItem playItem;
    protected JButton playButton;
    protected JMenuItem stopItem;
    protected JButton stopButton;

    // Pattern information label and pattern properties button
    private JLabel infoLabel;
    protected JButton patternPropsButton;

    // Cursor position(s) for playback, displayed at regular intervals.
    JPatternCursor[] cursors;

    /**
     * Constructs tracker interface the panel, keeping a reference to the
     * composer.
     */
    public JTrackerPanel(SystemComposerPatternData patterns, SystemComposerPlayback playback)
    {
        composerPatterns = patterns;
        composerPlayback = playback;

        ComposerListener composerListener = new ComposerListener(playback, this);
        ComposerSelectListener selectListener = new ComposerSelectListener(this);

        RelativeLayout theLayout = new RelativeLayout(null);
        setLayout(theLayout);





        // Main panel for the tracker, info panel and properties button.
        JPanel panel = new JPanel(new BorderLayout());

        cursors = new JPatternCursor[SystemComposerPlayback.numCursors];
        for (int x = 0; x < SystemComposerPlayback.numCursors; x ++)
        {
            cursors[x] = new JPatternCursor();
            //cursors[x].setDoubleBuffered(false);
            panel.add(cursors[x]);
        }

        patternTable = new JPatternTable(composerPatterns, this, panel);
        theLayout.registerForFontSizeChanges(patternTable, 1.2f, "Courier New", Font.PLAIN);
        theLayout.registerForFontSizeChanges(patternTable.getTableHeader(), 0.8f);

        JPanel tablePanel = new JPanel(new BorderLayout());
        tablePanel.add(patternTable, BorderLayout.CENTER);
        tablePanel.add(patternTable.getTableHeader(), BorderLayout.NORTH);

        panel.add(tablePanel, BorderLayout.WEST);

        JScrollPane scrollPane = new JScrollPane(panel);
        scrollPane.getViewport().setBackground(Color.WHITE);
        this.add(scrollPane, new RelativePlacement(0, 0, 800, 950));

        infoLabel = new JLabel("Information", JLabel.CENTER);
        theLayout.registerForFontSizeChanges(infoLabel);
        this.add(infoLabel, new RelativePlacement(0, 950, 650, 50));

        patternPropsButton = new JButton("Properties");
        theLayout.registerForFontSizeChanges(patternPropsButton);
        patternPropsButton.addActionListener(composerListener);
        this.add(patternPropsButton, new RelativePlacement(650, 950, 150, 50));





        // Play/Stop buttons.
        playButton = new JButton("Play");
        theLayout.registerForFontSizeChanges(playButton);
        this.add(playButton, new RelativePlacement(800, 0, 100, 50));
        playButton.addActionListener(composerListener);

        stopButton = new JButton("Stop");
        theLayout.registerForFontSizeChanges(stopButton);
        this.add(stopButton, new RelativePlacement(900, 0, 100, 50));
        stopButton.addActionListener(composerListener);





        // Machine list, for choosing target plugin.
        JLabel machineListLabel = new JLabel("Machines", JLabel.CENTER);
        theLayout.registerForFontSizeChanges(machineListLabel);
        this.add(machineListLabel, new RelativePlacement(800, 50, 200, 50));

        machineList = new DefaultListModel();
        machineListC = new JList(machineList);
        theLayout.registerForFontSizeChanges(machineListC);
        machineListC.addListSelectionListener(selectListener);
        this.add(machineListC, new RelativePlacement(800, 100, 200, 250));





        // Pattern list, for selecing/creating patterns.
        JLabel patternListLabel = new JLabel("Patterns", JLabel.CENTER);
        theLayout.registerForFontSizeChanges(patternListLabel);
        this.add(patternListLabel, new RelativePlacement(800, 350, 200, 50));

        newPatternButton = new JButton("Create");
        theLayout.registerForFontSizeChanges(newPatternButton);
        newPatternButton.addActionListener(composerListener);
        this.add(newPatternButton, new RelativePlacement(800, 400, 100, 50));

        delPatternButton = new JButton("Delete");
        theLayout.registerForFontSizeChanges(delPatternButton);
        delPatternButton.addActionListener(composerListener);
        this.add(delPatternButton, new RelativePlacement(900, 400, 100, 50));

        patternList = new DefaultListModel();
        patternListC = new JList(patternList);
        theLayout.registerForFontSizeChanges(patternListC);
        patternListC.addListSelectionListener(selectListener);
        this.add(patternListC, new RelativePlacement(800, 450, 200, 250));





        // Track list for adding/removing channels.
        JLabel trackListLabel = new JLabel("Tracks", JLabel.CENTER);
        theLayout.registerForFontSizeChanges(trackListLabel);
        this.add(trackListLabel, new RelativePlacement(800, 700, 200, 50));

        newTrackButton = new JButton("Add");
        theLayout.registerForFontSizeChanges(newTrackButton);
        this.add(newTrackButton, new RelativePlacement(800, 750, 100, 50));
        newTrackButton.addActionListener(composerListener);

        delTrackButton = new JButton("Remove");
        theLayout.registerForFontSizeChanges(delTrackButton);
        this.add(delTrackButton, new RelativePlacement(900, 750, 100, 50));
        delTrackButton.addActionListener(composerListener);

        trackList = new DefaultListModel();
        trackListC = new JList(trackList);
        theLayout.registerForFontSizeChanges(trackListC);
        this.add(trackListC, new RelativePlacement(800, 800, 200, 200));





        // Menu items for playback control.
        playbackMenu = new JMenu("Playback");
        theLayout.registerForFontSizeChanges(playbackMenu);

        playItem = new JMenuItem("Play");
        playItem.addActionListener(composerListener);
        playItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_F11, 0));
        theLayout.registerForFontSizeChanges(playItem);
        playbackMenu.add(playItem);

        stopItem = new JMenuItem("Stop");
        stopItem.addActionListener(composerListener);
        stopItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_F12, 0));
        theLayout.registerForFontSizeChanges(stopItem);
        playbackMenu.add(stopItem);

        Main.window.addMenu(playbackMenu);

        // This is the playback cursor update timer.
        Thread timer = new Thread(this);
        timer.setPriority(Thread.MIN_PRIORITY);
        timer.start();
    }

    /**
     * Removes the menu we created, for when we reinitialise or close.
     */
    public void removeMenu()
    {
        Main.window.removeMenu(playbackMenu);
    }

    /**
     * Reloads the items in the machine list when the document is loaded or
     * if a machine is added or deleted (and maybe later renamed). Clears the
     * pattern list and editor area.
     */
    public void reloadMachines()
    {
        patternTable.showPattern(null, null);

        machineList.clear();
        patternList.clear();
        trackList.clear();

        ArrayList<UsefulFileObject> machines = Main.document.getChildren("Machines");

        if (machines != null)
        {
            for (UsefulFileObject obj : machines)
            {
                MachineNode node = (MachineNode) obj;
                if (node != null) machineList.addElement(node);
            }
        }
    }

    /**
     * Passes through a command to save changes to the currently showing pattern
     * when we do a save.
     */
    public void documentSaving()
    {
        patternTable.saveCurrentPattern();
    }

    /**
     * Called to clear the interface when a document is loaded. This is to
     * avoid the usual saving checks we do when we change the pattern.
     */
    public void clearWithoutSaving()
    {
        machineList.clear();
        patternList.clear();
        trackList.clear();
        patternTable.clearData();
    }

    /**
     * Refreshes the pattern currently being shown, for when we add/remove tracks.
     */
    public void refreshPattern()
    {
        patternTable.refreshPattern();
    }

    /**
     * Returns the name of the track where the cursor currently sits, for when
     * the user wants to remove a track.
     */
    public String getCurrentTrackName()
    {
        if (patternTable.gridX == 0) return null;
        return patternTable.columns[patternTable.gridX].columnName;
    }

    /**
     * Fired when the user selects a machine, to update the tracker display,
     * lists and pointers that we use.
     */
    protected void showMachine()
    {
        // Clear the pattern list.
        patternList.clear();

        MachineNode selected = (MachineNode) machineListC.getSelectedValue();
        if (selected == null) return;

        // Populate the list of patterns.
        ArrayList<UsefulFileObject> patterns = composerPatterns.getPatterns(selected.machineName);
        if (patterns != null)
        {
            for (UsefulFileObject obj : patterns)
            {
                PatternNode node = (PatternNode) obj;
                patternList.addElement(node);
            }
        }

        // If the machine is the master, add the master pattern to the list.
        if (selected.isMaster)
        {
            // We do grab a reference to the master pattern of the document (or
            // create it if it doesn't exist yet).
            PatternNode masterPattern = composerPatterns.getPattern(SystemComposerPlayback.MASTER_PATTERN_NAME, SystemComposerPlayback.MASTER_PATTERN_NAME);
            patternList.addElement(masterPattern);
        }

        // TODO: Sel last pattern.
    }

    /**
     * Fired when the user selects a pattern, to update the tracker display,
     * lists and pointers that we use.
     */
    protected void showPattern()
    {
        // Update the current pointer.
        MachineNode selMach = (MachineNode) machineListC.getSelectedValue();
        PatternNode selected = (PatternNode) patternListC.getSelectedValue();

        // Clear the track list.
        trackList.clear();

        if (selected == null)
        {
            patternTable.showPattern(null, null);
            return;
        }

        // Populate the track list.
        if (selected.patternID.equals(SystemComposerPlayback.MASTER_PATTERN_NAME))
        {
            ArrayList<UsefulFileObject> machines = Main.document.getChildren("Machines");
            for (int x = 0; x < machines.size(); x++)
            {
                trackList.addElement(new ParameterPattern(((MachineNode) machines.get(x)).machineID, ""));
            }
        }
        else
        {
            // TODO: The cache can also store the last loaded pattern.
            MachineBase mach = composerPatterns.getMachInstance(selMach.machineName);
            boolean hasTracks = false;
            for (ParameterBase param : mach.parameters)
            {
                if (param.isTrackParam == false) trackList.addElement(param);
                else hasTracks = true;
            }

            // If there is a track structure, show a track name allowing addition of
            // the tracks as a group.
            if (hasTracks)
            {
                trackList.addElement(new ParameterCommand("TRACK GROUP", true));
            }
        }

        // Show the pattern.
        patternTable.showPattern(selected, selMach);

        patternTable.grabFocus();
    }

    /**
     * Called when the cursor is moved to update the helper/info display for
     * the pattern table.
     */
    protected void setCursorDescription(String cursorDescription)
    {
        infoLabel.setText(cursorDescription);
    }

    /**
     * Shows a dialog box where the user can change a pattern's name, length
     * and time signature.
     */
    protected void showPatternProperties()
    {
        PatternNode node = (PatternNode) patternListC.getSelectedValue();
        if (node != null && !node.patternID.equals(SystemComposerPlayback.MASTER_PATTERN_NAME))
        {
            JPatternPropertiesFrame f = new JPatternPropertiesFrame(node);
            f.setVisible(true);

            if (f.saveChanges)
            {
                patternTable.currPatternChanged = true;
                patternTable.saveCurrentPattern();
            }
        }

        patternTable.refreshPattern();
    }

    /**
     * Set the position of the playback cursor. This is called on a timer loop
     * 30 times a second.
     */
    public void setPlaybackCursorLines()
    {
        if (patternTable == null) return;

        double[] playbackPositions = composerPlayback.getPlaybackCursors((PatternNode) patternListC.getSelectedValue());
        int numPositions = 0;

        int rowHeight = patternTable.getRowHeight();
        int headerHeight = patternTable.getTableHeader().getHeight();

        if (playbackPositions != null)
        {
            numPositions = playbackPositions.length;
            for (int x = 0; x < numPositions; x++)
            {
                int h = (int) (playbackPositions[x] * rowHeight + headerHeight);
                Rectangle r = new Rectangle(0, h, getWidth(), 1);

                if (x < SystemComposerPlayback.numCursors)
                {
                    cursors[x].setBounds(r);
                }
            }
        }

        Rectangle r = new Rectangle(0, -1, getWidth(), 1);
        for (int x = numPositions; x < SystemComposerPlayback.numCursors; x++)
        {
            cursors[x].setBounds(r);
        }
    }

    @Override
    public void run()
    {
        PlaybackCursorTask updateCursorTask = new PlaybackCursorTask(this);
        while (Main.window.isVisible())
        {
            try 
            {
                Thread.sleep(80);
                if (isVisible()) SwingUtilities.invokeAndWait(updateCursorTask);
            }
            catch (Exception ex) {}
            
        }
    }

    void createPattern()
    {
       MachineNode mach = (MachineNode) machineListC.getSelectedValue();
       if (mach == null) return;
       composerPatterns.createPattern(mach);
       showMachine();
    }

    void deletePattern()
    {
       MachineNode mach = (MachineNode) machineListC.getSelectedValue();
       PatternNode patt = (PatternNode) patternListC.getSelectedValue();

       if (mach == null || patt == null) return;

       composerPatterns.deletePattern(mach, patt);
       showMachine();
    }

    void addTrack()
    {
       MachineNode mach = (MachineNode) machineListC.getSelectedValue();
       PatternNode patt = (PatternNode) patternListC.getSelectedValue();
       ParameterBase param = (ParameterBase) trackListC.getSelectedValue();

       if (param != null)
       {
           if (param.isTrackParam)
           {
               composerPatterns.createTrackGroup(mach, patt);
           }
           composerPatterns.createTrack(mach, patt, param);
       }

       patternTable.refreshPattern();
    }

    void deleteTrack()
    {
       MachineNode mach = (MachineNode) machineListC.getSelectedValue();
       PatternNode patt = (PatternNode) patternListC.getSelectedValue();
       composerPatterns.deleteTrack(mach, patt, patternTable.columns[patternTable.gridX].columnName);

       patternTable.refreshPattern();
    }
}


/**
 * Calls to the table on a regular basis to update the playback cursor positions.
 */
class PlaybackCursorTask extends Thread
{
    private JTrackerPanel pTable;

    public PlaybackCursorTask(JTrackerPanel table)
    {
        pTable = table;
    }

    @Override
    public void run()
    {
        pTable.setPlaybackCursorLines();
    }
}


/**
 * A listener for the buttons and menu that handles starting and stopping
 * playback, as well as pattern creation and other operations.
 */
class ComposerListener implements ActionListener
{
    private SystemComposerPlayback sysComposer;
    private JTrackerPanel trackerPanel;

    /**
     * Constructs the composer listener with reference to composer and panel.
     */
    public ComposerListener(SystemComposerPlayback owner, JTrackerPanel container)
    {
        sysComposer = owner;
        trackerPanel = container;
    }

    /**
     * Handles actions from the menu and buttons for the composer interface.
     */
    @Override
    public void actionPerformed(ActionEvent e)
    {
        if (e.getSource() == trackerPanel.playItem
            || e.getSource() == trackerPanel.playButton)
        {
            // TODO: Getting playback cursor pos.
            sysComposer.seek(0);
        }
        else if (e.getSource() == trackerPanel.stopItem
            || e.getSource() == trackerPanel.stopButton)
        {
            sysComposer.stop();
        }
        else if (e.getSource() == trackerPanel.newPatternButton)
        {
            trackerPanel.createPattern();
        }
        else if (e.getSource() == trackerPanel.delPatternButton)
        {
            trackerPanel.deletePattern();
        }
        else if (e.getSource() == trackerPanel.patternPropsButton)
        {
            trackerPanel.showPatternProperties();
        }
        else if (e.getSource() == trackerPanel.newTrackButton)
        {
            trackerPanel.addTrack();
        }
        else if (e.getSource() == trackerPanel.delTrackButton)
        {
            trackerPanel.deleteTrack();
        }
    }
}

/**
 * A listener for the buttons and menu that handles starting and stopping
 * playback, as well as pattern creation and other operations.
 */
class ComposerSelectListener implements ListSelectionListener
{
    private JTrackerPanel trackerPanel;

    /**
     * Construct the select listener with reference to composer and panel.
     */
    public ComposerSelectListener(JTrackerPanel container)
    {
        trackerPanel = container;
    }

    /**
     * Handles actions from the list controls on the tracker panel.
     */
    @Override
    public void valueChanged(ListSelectionEvent e)
    {
        if (e.getSource() == trackerPanel.patternListC)
        {
            trackerPanel.showPattern();
        }
        else if (e.getSource() == trackerPanel.machineListC)
        {
            trackerPanel.showMachine();
        }
    }
}