/*
 * Copyright (c) 2002 and later by MH Software-Entwicklung. All Rights Reserved.
 *  
 * JTattoo is multiple licensed. If your are an open source developer you can use
 * it under the terms and conditions of the GNU General Public License version 2.0
 * or later as published by the Free Software Foundation.
 *  
 * see: gpl-2.0.txt
 * 
 * If you pay for a license you will become a registered user who could use the
 * software under the terms and conditions of the GNU Lesser General Public License
 * version 2.0 or later with classpath exception as published by the Free Software
 * Foundation.
 * 
 * see: lgpl-2.0.txt
 * see: classpath-exception.txt
 * 
 * Registered users could also use JTattoo under the terms and conditions of the 
 * Apache License, Version 2.0 as published by the Apache Software Foundation.
 *  
 * see: APACHE-LICENSE-2.0.txt
 */

package com.jtattoo.plaf;

import java.awt.*;
import java.awt.event.*;
import java.util.Enumeration;
import java.util.List;
import javax.swing.*;
import javax.swing.plaf.ColorUIResource;
import javax.swing.plaf.ComponentUI;
import javax.swing.plaf.basic.BasicTableHeaderUI;
import javax.swing.table.*;

/**
 *
 * @author Michael Hagen
 */
public class BaseTableHeaderUI extends BasicTableHeaderUI {

    private TableCellRenderer originalHeaderRenderer;
    protected MouseAdapter myMouseAdapter = null;
    protected MouseMotionAdapter myMouseMotionAdapter = null;
    protected int rolloverCol = -1;

    public static ComponentUI createUI(JComponent h) {
        return new BaseTableHeaderUI();
    }

    public void installUI(JComponent c) {
        super.installUI(c);
        if ((header != null) && header.getTable() != null) {
            originalHeaderRenderer = header.getDefaultRenderer();
            if ((originalHeaderRenderer != null)
                    && "sun.swing.table.DefaultTableCellHeaderRenderer".equals(originalHeaderRenderer.getClass().getName())) {
                header.setDefaultRenderer(new BaseDefaultHeaderRenderer());
            }
        }
    }

    public void uninstallUI(JComponent c) {
        if ((header != null) && (header.getTable() != null)) {
            if (header.getDefaultRenderer() instanceof BaseDefaultHeaderRenderer) {
                header.setDefaultRenderer(originalHeaderRenderer);
            }
        }
        super.uninstallUI(c);
    }

    public void installListeners() {
        super.installListeners();
        myMouseAdapter = new MouseAdapter() {
            
            public void mouseReleased(MouseEvent e) {
                if ((header == null) || (header.getTable() == null)) {
                    return;
                }
                boolean rolloverEnabled = Boolean.TRUE.equals(header.getClientProperty("rolloverEnabled"));
                boolean sortingAllowed = false;
                if (JTattooUtilities.getJavaVersion() >= 1.6) {
                    sortingAllowed = header.getTable().getRowSorter() != null;
                }
                if (rolloverEnabled || sortingAllowed || header.getReorderingAllowed()) {
                    if (header.getBounds().contains(e.getPoint())) {
                        int oldRolloverCol = rolloverCol;
                        rolloverCol = header.getTable().columnAtPoint(e.getPoint());
                        updateRolloverColumn(oldRolloverCol, rolloverCol);
                    } else {
                        int oldRolloverCol = rolloverCol;
                        rolloverCol = -1;
                        updateRolloverColumn(oldRolloverCol, rolloverCol);
                    }
                }
            }

            public void mouseEntered(MouseEvent e) {
                if ((header == null) || (header.getTable() == null)) {
                    return;
                }
                boolean rolloverEnabled = Boolean.TRUE.equals(header.getClientProperty("rolloverEnabled"));
                boolean sortingAllowed = false;
                if (JTattooUtilities.getJavaVersion() >= 1.6) {
                    sortingAllowed = header.getTable().getRowSorter() != null;
                }
                if (rolloverEnabled || sortingAllowed || header.getReorderingAllowed()) {
                    int oldRolloverCol = rolloverCol;
                    rolloverCol = header.getTable().columnAtPoint(e.getPoint());
                    updateRolloverColumn(oldRolloverCol, rolloverCol);
                }
            }

            public void mouseExited(MouseEvent e) {
                if ((header == null) || (header.getTable() == null)) {
                    return;
                }
                boolean rolloverEnabled = Boolean.TRUE.equals(header.getClientProperty("rolloverEnabled"));
                boolean sortingAllowed = false;
                if (JTattooUtilities.getJavaVersion() >= 1.6) {
                    sortingAllowed = header.getTable().getRowSorter() != null;
                }
                if (rolloverEnabled || sortingAllowed || header.getReorderingAllowed()) {
                    int oldRolloverCol = rolloverCol;
                    rolloverCol = -1;
                    updateRolloverColumn(oldRolloverCol, rolloverCol);
                }
            }
        };
        myMouseMotionAdapter = new MouseMotionAdapter() {
            
            public void mouseMoved(MouseEvent e) {
                if ((header == null) || (header.getTable() == null)) {
                    return;
                }
                boolean rolloverEnabled = Boolean.TRUE.equals(header.getClientProperty("rolloverEnabled"));
                boolean sortingAllowed = false;
                if (JTattooUtilities.getJavaVersion() >= 1.6) {
                    sortingAllowed = header.getTable().getRowSorter() != null;
                }
                if (rolloverEnabled || sortingAllowed || header.getReorderingAllowed()) {
                    if (header.getDraggedColumn() == null) {
                        int oldRolloverCol = rolloverCol;
                        rolloverCol = header.getTable().columnAtPoint(e.getPoint());
                        updateRolloverColumn(oldRolloverCol, rolloverCol);
                    }
                }
            }

            public void mouseDragged(MouseEvent e) {
                if ((header == null) || (header.getTable() == null)) {
                    return;
                }
                boolean rolloverEnabled = Boolean.TRUE.equals(header.getClientProperty("rolloverEnabled"));
                boolean sortingAllowed = false;
                if (JTattooUtilities.getJavaVersion() >= 1.6) {
                    sortingAllowed = header.getTable().getRowSorter() != null;
                }
                if (rolloverEnabled || sortingAllowed || header.getReorderingAllowed()) {
                    if (header.getDraggedColumn() != null && header.getDraggedColumn().getIdentifier() != null) {
                        rolloverCol = header.getColumnModel().getColumnIndex(header.getDraggedColumn().getIdentifier());
                    } else if (header.getResizingColumn() != null) {
                        rolloverCol = -1;
                    }
                }
            }
        };
        header.addMouseListener(myMouseAdapter);
        header.addMouseMotionListener(myMouseMotionAdapter);
    }

    public void uninstallListeners() {
        header.removeMouseListener(myMouseAdapter);
        header.removeMouseMotionListener(myMouseMotionAdapter);
        super.uninstallListeners();
    }

    protected boolean drawAlwaysActive() {
        return false;
    }

    protected boolean drawRolloverBar() {
        return false;
    }

    protected Component getHeaderRenderer(int col) {
        if ((header == null) || (header.getTable() == null)) {
            return null;
        }
        TableColumn tabCol = header.getColumnModel().getColumn(col);
        TableCellRenderer renderer = tabCol.getHeaderRenderer();
        if (renderer == null) {
            renderer = header.getDefaultRenderer();
        }
        return renderer.getTableCellRendererComponent(header.getTable(), tabCol.getHeaderValue(), false, false, -1, col);
    }

    private int getHeaderHeight() {
        if ((header == null) || (header.getTable() == null)) {
            return 0;
        }
        int height = 0;
        boolean accomodatedDefault = false;
        TableColumnModel columnModel = header.getColumnModel();
        for (int column = 0; column < columnModel.getColumnCount(); column++) {
            TableColumn aColumn = columnModel.getColumn(column);
            boolean isDefault = (aColumn.getHeaderRenderer() == null);

            if (!isDefault || !accomodatedDefault) {
                Component comp = getHeaderRenderer(column);
                int rendererHeight = comp.getPreferredSize().height;
                height = Math.max(height, rendererHeight);

                // Configuring the header renderer to calculate its preferred size
                // is expensive. Optimise this by assuming the default renderer
                // always has the same height as the first non-zero height that
                // it returns for a non-null/non-empty value.
                if (isDefault && rendererHeight > 0) {
                    Object headerValue = aColumn.getHeaderValue();
                    if (headerValue != null) {
                        headerValue = headerValue.toString();

                        if (headerValue != null && !headerValue.equals("")) {
                            accomodatedDefault = true;
                        }
                    }
                }
            }
        }
        return height + 2;
    }

    /**
     * Return the preferred size of the header. The preferred height is the maximum of the preferred heights of all of
     * the components provided by the header renderers. The preferred width is the sum of the preferred widths of each
     * column (plus inter-cell spacing).
     * 
     * @return the preferredSize
     */
    public Dimension getPreferredSize(JComponent c) {
        if ((header == null) || (header.getTable() == null)) {
            return new Dimension(0, 0);
        }
        long width = 0;
        Enumeration enumeration = header.getColumnModel().getColumns();
        while (enumeration.hasMoreElements()) {
            TableColumn aColumn = (TableColumn) enumeration.nextElement();
            width = width + aColumn.getPreferredWidth();
        }
        if (width > Integer.MAX_VALUE) {
            width = Integer.MAX_VALUE;
        }
        return new Dimension((int) width, getHeaderHeight());
    }

    protected void updateRolloverColumn(int oldColumn, int newColumn) {
        if ((header == null) || (header.getTable() == null)) {
            return;
        }
        header.repaint(header.getHeaderRect(oldColumn));
        header.repaint(header.getHeaderRect(newColumn));
    }

    protected void rolloverColumnUpdated(int oldColumn, int newColumn) {
        // Empty to avoid multiple paints
    }

    public void paint(Graphics g, JComponent c) {
        if ((header == null) || (header.getTable() == null) || header.getColumnModel().getColumnCount() <= 0) {
            return;
        }
        
        boolean ltr = header.getComponentOrientation().isLeftToRight();
        Rectangle clip = g.getClipBounds();
        Point left = clip.getLocation();
        Point right = new Point(clip.x + clip.width - 1, clip.y);
        TableColumnModel cm = header.getColumnModel();
        int cMin = header.columnAtPoint(ltr ? left : right);
        int cMax = header.columnAtPoint(ltr ? right : left);
        // This should never happen.
        if (cMin == -1) {
            cMin = 0;
        }
        // If the table does not have enough columns to fill the view we'll get -1.
        // Replace this with the index of the last column.
        if (cMax == -1) {
            cMax = cm.getColumnCount() - 1;
        }

        TableColumn draggedColumn = header.getDraggedColumn();
        Rectangle cellRect = header.getHeaderRect(ltr ? cMin : cMax);
        int columnWidth;
        TableColumn aColumn;
        if (ltr) {
            for (int column = cMin; column <= cMax; column++) {
                aColumn = cm.getColumn(column);
                columnWidth = aColumn.getWidth();
                cellRect.width = columnWidth;
                if (aColumn != draggedColumn) {
                    paintCell(g, cellRect, column);
                }
                cellRect.x += columnWidth;
            }
        } else {
            for (int column = cMax; column >= cMin; column--) {
                aColumn = cm.getColumn(column);
                columnWidth = aColumn.getWidth();
                cellRect.width = columnWidth;
                if (aColumn != draggedColumn) {
                    paintCell(g, cellRect, column);
                }
                cellRect.x += columnWidth;
            }
        }

        // Paint the dragged column if we are dragging.
        if (draggedColumn != null) {
            int draggedColumnIndex = viewIndexForColumn(draggedColumn);
            Rectangle draggedCellRect = header.getHeaderRect(draggedColumnIndex);
            // Draw a gray well in place of the moving column.
            g.setColor(header.getParent().getBackground());
            g.fillRect(draggedCellRect.x, draggedCellRect.y, draggedCellRect.width, draggedCellRect.height);
            draggedCellRect.x += header.getDraggedDistance();

            // Fill the background.
            g.setColor(header.getBackground());
            g.fillRect(draggedCellRect.x, draggedCellRect.y, draggedCellRect.width, draggedCellRect.height);
            paintCell(g, draggedCellRect, draggedColumnIndex);
        }

        // Remove all components in the rendererPane.
        rendererPane.removeAll();
    }

    protected void paintBackground(Graphics g, Rectangle cellRect, int col) {
        Component component = getHeaderRenderer(col);
        int x = cellRect.x;
        int y = cellRect.y;
        int w = cellRect.width;
        int h = cellRect.height;
        if (header.getBackground() instanceof ColorUIResource) {
            if ((col == rolloverCol) && (component != null) && component.isEnabled()) {
                JTattooUtilities.fillHorGradient(g, AbstractLookAndFeel.getTheme().getRolloverColors(), x, y, w, h);
            } else if (drawAlwaysActive() || JTattooUtilities.isFrameActive(header)) {
                JTattooUtilities.fillHorGradient(g, AbstractLookAndFeel.getTheme().getColHeaderColors(), x, y, w, h);
            } else {
                JTattooUtilities.fillHorGradient(g, AbstractLookAndFeel.getTheme().getInActiveColors(), x, y, w, h);
            }
        } else {
            g.setColor(header.getBackground());
            g.fillRect(x, y, w, h);
        }
    }

    protected void paintCell(Graphics g, Rectangle cellRect, int col) {
        if ((header == null) || (header.getTable() == null)) {
            return;
        }
        Component component = getHeaderRenderer(col);
        if (!(component instanceof BaseDefaultHeaderRenderer)) {
            paintBackground(g, cellRect, col);
        }
        rendererPane.paintComponent(g, component, header, cellRect.x, cellRect.y, cellRect.width, cellRect.height, true);
    }

    private int viewIndexForColumn(TableColumn aColumn) {
        if ((header == null) || (header.getTable() == null)) {
            return -1;
        }
        TableColumnModel cm = header.getColumnModel();
        for (int column = 0; column < cm.getColumnCount(); column++) {
            if (cm.getColumn(column) == aColumn) {
                return column;
            }
        }
        return -1;
    }

//----------------------------------------------------------------------------------------------------------------------
// inner classes
//----------------------------------------------------------------------------------------------------------------------
    private class BaseDefaultHeaderRenderer extends DefaultTableCellRenderer {

        public BaseDefaultHeaderRenderer() {
            super();
        }

        public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
            return new MyRenderComponent(table, value, isSelected, hasFocus, row, column);
        }
    }

    private class MyRenderComponent extends JLabel {

        private JTable table = null;
        private int col = 0;

        public MyRenderComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int col) {
            super();
            this.table = table;
            this.col = col;
            if (value != null) {
                setText(value.toString());
            } else {
                setText("");
            }
            setOpaque(false);
            if (table.getClientProperty("TableHeader.font") != null) {
                setFont((Font)table.getClientProperty("TableHeader.font"));
            } else {
                setFont(UIManager.getFont("TableHeader.font"));
            }
            if (col == rolloverCol) {
                setForeground(AbstractLookAndFeel.getTheme().getRolloverForegroundColor());
            } else {
                setForeground(UIManager.getColor("TableHeader.foreground"));
            }
            setHorizontalAlignment(JLabel.CENTER);
            setHorizontalTextPosition(SwingConstants.LEADING);
            setBorder(UIManager.getBorder("TableHeader.cellBorder"));
            if ((JTattooUtilities.getJavaVersion() >= 1.6) && (UIManager.getLookAndFeel() instanceof AbstractLookAndFeel)) {
                RowSorter rowSorter = table.getRowSorter();
                List keyList = rowSorter == null ? null : rowSorter.getSortKeys();
                if ((keyList != null) && (keyList.size() > 0)) {
                    RowSorter.SortKey sortKey = (RowSorter.SortKey) keyList.get(0);
                    if (sortKey.getColumn() == table.convertColumnIndexToModel(col)) {
                        AbstractIconFactory iconFactory = ((AbstractLookAndFeel) UIManager.getLookAndFeel()).getIconFactory();
                        if (sortKey.getSortOrder().equals(SortOrder.ASCENDING)) {
                            setIcon(iconFactory.getUpArrowIcon());
                        } else if (sortKey.getSortOrder().equals(SortOrder.DESCENDING)) {
                            setIcon(iconFactory.getDownArrowIcon());
                        }
                    }
                }
            }
        }

        protected void paintBackground(Graphics g) {
            // BugFix: 12.12.2013
            // Currently I don't know why header is sometimes null, but if it's null it's better to ignore
            // the background instead of throwing a NPE.
            if (header == null) {
                JTattooUtilities.fillHorGradient(g, AbstractLookAndFeel.getTheme().getColHeaderColors(), 0, 0, getWidth(), getHeight());
                return;
            }
            int draggedColumn = -1;
            if (header.getTable() != null && header.getDraggedColumn() != null) {
                draggedColumn = header.getColumnModel().getColumnIndex(header.getDraggedColumn().getIdentifier());
            }
            int w = getWidth();
            int h = getHeight();
            if ((table != null) && table.isEnabled() && (col == rolloverCol || col == draggedColumn)) {
                JTattooUtilities.fillHorGradient(g, AbstractLookAndFeel.getTheme().getRolloverColors(), 0, 0, w, h);
                if (drawRolloverBar()) {
                    g.setColor(AbstractLookAndFeel.getFocusColor());
                    g.drawLine(0, 0, w - 1, 0);
                    g.drawLine(0, 1, w - 1, 1);
                    g.drawLine(0, 2, w - 1, 2);
                }
            } else if (drawAlwaysActive() || JTattooUtilities.isFrameActive(header)) {
                if (header.getBackground() instanceof ColorUIResource) {
                    JTattooUtilities.fillHorGradient(g, AbstractLookAndFeel.getTheme().getColHeaderColors(), 0, 0, w, h);
                } else {
                    g.setColor(header.getBackground());
                    g.fillRect(0, 0, w, h);
                }
            } else {
                if (header.getBackground() instanceof ColorUIResource) {
                    JTattooUtilities.fillHorGradient(g, AbstractLookAndFeel.getTheme().getInActiveColors(), 0, 0, w, h);
                } else {
                    g.setColor(header.getBackground());
                    g.fillRect(0, 0, w, h);
                }
            }
        }

        public void paint(Graphics g) {
            paintBackground(g);
            super.paint(g);
        }
    }
}
