package vermilion;

import java.awt.Color;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.font.FontRenderContext;
import java.util.ArrayList;
import javax.swing.JTable;
import javax.swing.table.TableColumn;
import javax.swing.table.TableColumnModel;

/**
 * Table for patterns, a heavily modified JTable made to work like a tracker.
 */
public class JPatternTable extends JTable
{
    // A cursor position for playback. TODO: Move with playback, and allow
    // dragging with the mouse to set seek position.
    // private int playbackCursorYPos;

    // A cell renderer for displaying tracker column cells, which are just text.
    private JPatternTableCellRenderer cellRenderer;

    // The composer system component that owns this control.
    private SystemComposer sysComposer;

    // Indicates that something has been changed in the currently showing pattern.
    protected boolean currPatternChanged = false;

    // Since we may get out of sync during update operations, we have our own
    // current machine and pattern pointers.
    private PatternNode currPattern = null;
    private String currMachine = null;

    // Model for the table, which provides the link to the column objects.
    private JPatternTableModel table = new JPatternTableModel(1, 1);

    /**
     * The JTrackerColumns currently being displayed.
     */
    public JTrackerColumn[] columns;

    /**
     * The cursor position within the current column, from 0 to
     * columns[gridX].columnWidth-1.
     */
    public int columnPosition = 0;

    /**
     * The X grid position of the cursor, from 0 to columns.length-1.
     */
    public int gridX = 0;

    /**
     * The Y grid position of the cursor, from 0 to currPattern.length-1.
     */
    public int gridY = 0;

    /**
     * The constructor accepts a reference to the system composer.
     * @param composer
     */
    public JPatternTable(SystemComposer composer)
    {
        sysComposer = composer;

        // Set the table model to our customised class.
        setModel(table);

        // Customise the appearance.
        showHorizontalLines = false;
        showVerticalLines = false;
        setIntercellSpacing(new Dimension(0,0));

        // Customise resizing behaviour.
        setAutoResizeMode(AUTO_RESIZE_OFF);
        getTableHeader().setResizingAllowed(false);

        // Set the cell renderer to our custom class.
        cellRenderer = new JPatternTableCellRenderer();
        setDefaultRenderer(Object.class, cellRenderer);

        // Enable mouse selection.
        cellSelectionEnabled = true;

        // Clear the table for first use.
        clearData();

        // Add our custom listener for 'editing' cells.
        addKeyListener(new PatternKeyListener(this));

        // This is part of playback position display which is incomplete.
        //playbackCursorYPos = 3;
    }

    /**
     * Set the position of the playback cursor. This is part of playback
     * position display which is incomplete.
     */
    public void setPlaybackCursorLine(int cursorLine)
    {
        // TODO: Figure out pixel position of cursor line, including scroll
        // offset.
        //this.playbackCursorYPos = cursorLine;
        repaint();
    }

    /**
     * Draws the cursor line over the top of the control, incomplete.
     */
    @Override
    protected void paintChildren(Graphics g)
    {
        super.paintChildren(g);
        g.setColor(Color.GREEN);
        //g.drawLine(0, playbackCursorYPos, getWidth(), playbackCursorYPos);
    }

    @Override
    public void setFont(Font f)
    {
        super.setFont(f);
        scaleColumns();
    }

    /**
     * Shows a given pattern. The machine name is also required, for locating
     * the pattern. If there is a pattern showing which needs to be saved, it
     * is saved. 
     */
    public void showPattern(PatternNode patt, String machName)
    {
        saveCurrentPattern();

        clearData();

        currPatternChanged = false;
        currPattern = patt;
        currMachine = machName;

        if (patt == null) return;
        
        table.setRowCount(currPattern.patternLength);
        setupColumns();

        setColumnSelectionInterval(0, 0);
        setRowSelectionInterval(0, 0);
    }

    // Reads the tracks that exist for the current pattern and sets up columns
    // to match them.
    private void setupColumns()
    {
        ArrayList<UsefulFileObject> currColumns = null;

        if (currPattern == sysComposer.masterPattern)
        {
            currColumns = Main.document.getChildren("Patterns/" + SystemComposer.MASTER_PATTERN_NAME);
            if (currColumns == null)
            {
                // This could happen if the master pattern wasn't initialised
                // yet, but shouldn't happen normally. TODO: Check this some
                // time since if this did happen there could be a sync problem.
                currColumns = new ArrayList<UsefulFileObject>();
            }
        }
        else
        {
            currColumns = Main.document.getChildren("Patterns/" + currMachine + "/" + currPattern.patternID);
            if (currColumns == null)
            {
                // This shouldn't happen - we create tracks when we create
                // patterns and the getChildren should only return an empty list
                // anyway.
                currColumns = new ArrayList<UsefulFileObject>();
            }
        }

        int numColumns = currColumns.size();
        columns = new JTrackerColumn[numColumns + 1];

        for (int x = 0; x < numColumns; x++)
        {
            PatternTrackNode node = (PatternTrackNode) currColumns.get(x);
            JTrackerColumn column = null;
            if (node.paramType == ParameterBase.PARAMETER_TYPE_PATTERN)
            {
                ArrayList<UsefulFileObject> machList = Main.document.getChildren("Machines");
                if (machList != null)
                {
                    for (UsefulFileObject obj : machList)
                    {
                        MachineNode mNode = (MachineNode) obj;
                        if (mNode.machineID.equals(node.paramName)) column = new JTrackerColumnMachine(node.columnName, node.paramName, currPattern, -1, node, mNode.machineName);
                    }
                }

            }
            else if (node.paramType == ParameterBase.PARAMETER_TYPE_SLIDER) column = new JTrackerColumnSlider(node.columnName, node.paramName, currPattern, -1, node);
            else if (node.paramType == ParameterBase.PARAMETER_TYPE_NOTE) column = new JTrackerColumnNote(node.columnName, node.paramName, currPattern, -1, node);

            columns[x+1] = column;
        }

        columns[0] = new JTrackerColumnRowNum();

        table.columns = columns;
        table.setColumnCount(columns.length);
        table.setColumnIdentifiers(columns);
        scaleColumns();
    }

    private void scaleColumns()
    {
        if (columns == null) return;

        TableColumnModel model = getColumnModel();
        FontRenderContext frc = new FontRenderContext(null, false, false);
        int fontWidth = (int) (this.getFont().getStringBounds("00000", frc).getWidth()/5.0);
        for (int x = 0; x < columns.length; x++) {
            TableColumn col = model.getColumn(x);
            col.setPreferredWidth((columns[x].columnWidth + 3) * fontWidth);
        }
    }

    // Checks if the pattern being shown hs been modified and if so writes the
    // tracks and commands back to the composition.
    void saveCurrentPattern()
    {
        if (currPattern == null) return;

        // TODO: Work out why this is fired twice on selection and FOUR times
        // on deselection (by changing machine).
        if (currPatternChanged == false) return;

        System.out.println("Saving commands to " + currPattern.patternID);
        if (currPattern.patternID.equals(SystemComposer.MASTER_PATTERN_NAME))
        {
            Main.document.setValue("Patterns/" + SystemComposer.MASTER_PATTERN_NAME, currPattern);

            for (int x = 0; x < columns.length; x++)
            {
                JTrackerColumn col = columns[x];
                if (col.commandList != null) Main.document.setValue("Patterns/" + SystemComposer.MASTER_PATTERN_NAME + "/" + col.columnName, col.commandList);
            }
        }
        else
        {
            Main.document.setValue("Patterns/" + currMachine + "/" + currPattern.patternID, currPattern);

            for (int x = 0; x < columns.length; x++)
            {
                JTrackerColumn col = columns[x];
                if (col.commandList != null) Main.document.setValue("Patterns/" + currMachine + "/" + currPattern.patternID + "/" + col.columnName, col.commandList);
            }
        }

        currPatternChanged = false;
    }

    /**
     * Empties the table data, without saving.
     */
    public void clearData()
    {
        currMachine = null;
        currPattern = null;
        columns = new JTrackerColumn[0];

        table.setRowCount(0);
        table.setColumnCount(0);
        table.columns = new JTrackerColumn[0];

        gridX = 0;
        gridY = 0;
        columnPosition = 0;
    }

    /**
     * Updates the display *using the system composer's current pattern*. This
     * is used when tracks are added or removed, so a refresh from the composition
     * is necessary.
     */
    public void refreshPattern()
    {
        if (sysComposer.currMachine == null || sysComposer.currPattern == null) showPattern(null, null);
        else showPattern(sysComposer.currPattern, sysComposer.currMachine.machineName);
    }
}

/**
 * Listens for key events on the tracker interface and passes them to columns
 * for interpretation. The table itself handles rerendering.
 */
class PatternKeyListener implements KeyListener
{
    private JPatternTable table;

    public PatternKeyListener(JPatternTable t)
    {
        table = t;
    }

    @Override
    public void keyTyped(KeyEvent e) { }

    @Override
    public void keyPressed(KeyEvent e)
    {
        int colNum = table.gridX;
        int rowNum = table.gridY;

        // We let these keys go through to the default
        // handler, which does things like selection.
        if (   e.getKeyCode() == KeyEvent.VK_SHIFT
            || e.getKeyCode() == KeyEvent.VK_DOWN
            || e.getKeyCode() == KeyEvent.VK_PAGE_DOWN
            || e.getKeyCode() == KeyEvent.VK_UP
            || e.getKeyCode() == KeyEvent.VK_PAGE_UP
            || e.getKeyCode() == KeyEvent.VK_LEFT
            || e.getKeyCode() == KeyEvent.VK_RIGHT)
        {
            // We handle left/right ourselves so that the cursor moves within
            // columns.
            if (e.getKeyCode() == KeyEvent.VK_LEFT)
            {
                table.columnPosition--;
                if (table.columnPosition < 0)
                {
                    if (colNum > 0)
                    {
                        table.columnPosition = table.columns[colNum-1].columnWidth-1;
                    }
                    else table.columnPosition = 0;
                }
                else
                {
                    e.consume();
                }

                table.repaint();
            }
            else if (e.getKeyCode() == KeyEvent.VK_RIGHT)
            {
                table.columnPosition++;
                if (table.columnPosition >= table.columns[colNum].columnWidth)
                {
                    if (colNum < table.columns.length-1)
                    {
                        table.columnPosition = 0;
                    }
                    else table.columnPosition--;
                }
                else
                {
                    e.consume();
                }

                table.repaint();
            }
        }
        // All other keypresses are sent to column handlers.
        else
        {
            //System.out.println("KEY " + KeyEvent.getKeyText(e.getKeyCode()));
            // Pass to column - keycode, row, columnPosition. If something
            // happens we do some work moving the cursor etc.
            if (table.columns[colNum].setEntry(rowNum, table.columnPosition, e))
            {
                rowNum++;
                if (rowNum >= table.getRowCount()) rowNum--;
                table.setRowSelectionInterval(rowNum, rowNum);
                table.repaint();
                table.currPatternChanged = true;
            }

            e.consume();
        }

        // Ensures that the viewport scrolls with the cursor.
        table.scrollRectToVisible(table.getCellRect(rowNum, colNum, false));
    }

    @Override
    public void keyReleased(KeyEvent e) { }
}