/*
 * Copyright (c) 2011, 2012, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Oracle designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Oracle in the LICENSE file that accompanied this code.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */

package com.apple.laf;

import java.awt.*;
import java.util.Enumeration;

import javax.swing.*;
import javax.swing.border.Border;
import javax.swing.plaf.*;
import javax.swing.plaf.basic.BasicTableHeaderUI;
import javax.swing.table.*;
import com.apple.laf.ClientPropertyApplicator;
import com.apple.laf.ClientPropertyApplicator.Property;
import com.apple.laf.AquaUtils.RecyclableSingleton;

public class AquaTableHeaderUI extends BasicTableHeaderUI {
    private int originalHeaderAlignment;
    protected int sortColumn;
    protected int sortOrder;

    public static ComponentUI createUI(final JComponent c) {
        return new AquaTableHeaderUI();
    }

    public void installDefaults() {
        super.installDefaults();

        final TableCellRenderer renderer = header.getDefaultRenderer();
        if (renderer instanceof UIResource && renderer instanceof DefaultTableCellRenderer) {
            final DefaultTableCellRenderer defaultRenderer = (DefaultTableCellRenderer)renderer;
            originalHeaderAlignment = defaultRenderer.getHorizontalAlignment();
            defaultRenderer.setHorizontalAlignment(SwingConstants.LEADING);
        }
    }

    public void uninstallDefaults() {
        final TableCellRenderer renderer = header.getDefaultRenderer();
        if (renderer instanceof UIResource && renderer instanceof DefaultTableCellRenderer) {
            final DefaultTableCellRenderer defaultRenderer = (DefaultTableCellRenderer)renderer;
            defaultRenderer.setHorizontalAlignment(originalHeaderAlignment);
        }

        super.uninstallDefaults();
    }

    final static RecyclableSingleton<ClientPropertyApplicator<JTableHeader, JTableHeader>> TABLE_HEADER_APPLICATORS = new RecyclableSingleton<ClientPropertyApplicator<JTableHeader, JTableHeader>>() {
        @Override
        protected ClientPropertyApplicator<JTableHeader, JTableHeader> getInstance() {
            return new ClientPropertyApplicator<JTableHeader, JTableHeader>(
                    new Property<JTableHeader>("JTableHeader.selectedColumn") {
                        public void applyProperty(final JTableHeader target, final Object value) {
                            tickle(target, value, target.getClientProperty("JTableHeader.sortDirection"));
                        }
                    },
                    new Property<JTableHeader>("JTableHeader.sortDirection") {
                        public void applyProperty(final JTableHeader target, final Object value) {
                            tickle(target, target.getClientProperty("JTableHeader.selectedColumn"), value);
                        }
                    }
            );
        }
    };
    static ClientPropertyApplicator<JTableHeader, JTableHeader> getTableHeaderApplicators() {
        return TABLE_HEADER_APPLICATORS.get();
    }

    static void tickle(final JTableHeader target, final Object selectedColumn, final Object direction) {
        final TableColumn tableColumn = getTableColumn(target, selectedColumn);
        if (tableColumn == null) return;

        int sortDirection = 0;
        if ("ascending".equalsIgnoreCase(direction+"")) {
            sortDirection = 1;
        } else if ("descending".equalsIgnoreCase(direction+"")) {
            sortDirection = -1;
        } else if ("decending".equalsIgnoreCase(direction+"")) {
            sortDirection = -1; // stupid misspelling that GM'ed in 10.5.0
        }

        final TableHeaderUI headerUI = target.getUI();
        if (headerUI == null || !(headerUI instanceof AquaTableHeaderUI)) return;

        final AquaTableHeaderUI aquaHeaderUI = (AquaTableHeaderUI)headerUI;
        aquaHeaderUI.sortColumn = tableColumn.getModelIndex();
        aquaHeaderUI.sortOrder = sortDirection;
        final AquaTableCellRenderer renderer = aquaHeaderUI.new AquaTableCellRenderer();
        tableColumn.setHeaderRenderer(renderer);
    }

    class AquaTableCellRenderer extends DefaultTableCellRenderer implements UIResource {
        public Component getTableCellRendererComponent(final JTable localTable, final Object value, final boolean isSelected, final boolean hasFocus, final int row, final int column) {
            if (localTable != null) {
                if (header != null) {
                    setForeground(header.getForeground());
                    setBackground(header.getBackground());
                    setFont(UIManager.getFont("TableHeader.font"));
                }
            }

            setText((value == null) ? "" : value.toString());

            // Modify the table "border" to draw smaller, and with the titles in the right position
            // and sort indicators, just like an NSSave/Open panel.
            final AquaTableHeaderBorder cellBorder = AquaTableHeaderBorder.getListHeaderBorder();
            final boolean thisColumnSelected = localTable.getColumnModel().getColumn(column).getModelIndex() == sortColumn;

            cellBorder.setSelected(thisColumnSelected);
            if (thisColumnSelected) {
                cellBorder.setSortOrder(sortOrder);
            } else {
                cellBorder.setSortOrder(AquaTableHeaderBorder.SORT_NONE);
            }
            setBorder(cellBorder);
            return this;
        }
    }

    protected static TableColumn getTableColumn(final JTableHeader target, final Object value) {
        if (value == null || !(value instanceof Integer)) return null;
        final int columnIndex = ((Integer)value).intValue();

        final TableColumnModel columnModel = target.getColumnModel();
        if (columnIndex < 0 || columnIndex >= columnModel.getColumnCount()) return null;

        return columnModel.getColumn(columnIndex);
    }

    protected static AquaTableHeaderBorder getAquaBorderFrom(final JTableHeader header, final TableColumn column) {
        final TableCellRenderer renderer = column.getHeaderRenderer();
        if (renderer == null) return null;

        final Component c = renderer.getTableCellRendererComponent(header.getTable(), column.getHeaderValue(), false, false, -1, column.getModelIndex());
        if (!(c instanceof JComponent)) return null;

        final Border border = ((JComponent)c).getBorder();
        if (!(border instanceof AquaTableHeaderBorder)) return null;

        return (AquaTableHeaderBorder)border;
    }

    protected void installListeners() {
        super.installListeners();
        getTableHeaderApplicators().attachAndApplyClientProperties(header);
    }

    protected void uninstallListeners() {
        getTableHeaderApplicators().removeFrom(header);
        super.uninstallListeners();
    }

    private int getHeaderHeightAqua() {
        int height = 0;
        boolean accomodatedDefault = false;

        final TableColumnModel columnModel = header.getColumnModel();
        for (int column = 0; column < columnModel.getColumnCount(); column++) {
            final TableColumn aColumn = columnModel.getColumn(column);
            // Configuring the header renderer to calculate its preferred size is expensive.
            // Optimise this by assuming the default renderer always has the same height.
            if (aColumn.getHeaderRenderer() != null || !accomodatedDefault) {
                final Component comp = getHeaderRendererAqua(column);
                final int rendererHeight = comp.getPreferredSize().height;
                height = Math.max(height, rendererHeight);
                // If the header value is empty (== "") in the
                // first column (and this column is set up
                // to use the default renderer) we will
                // return zero from this routine and the header
                // will disappear altogether. Avoiding the calculation
                // of the preferred size is such a performance win for
                // most applications that we will continue to
                // use this cheaper calculation, handling these
                // issues as `edge cases'.

                // Mac OS X Change - since we have a border on our renderers
                // it is possible the height of an empty header could be > 0,
                // so we chose the relatively safe number of 4 to handle this case.
                // Now if we get a size of 4 or less we assume it is empty and measure
                // a different header.
                if (rendererHeight > 4) {
                    accomodatedDefault = true;
                }
            }
        }
        return height;
    }

    private Component getHeaderRendererAqua(final int columnIndex) {
        final TableColumn aColumn = header.getColumnModel().getColumn(columnIndex);
        TableCellRenderer renderer = aColumn.getHeaderRenderer();
        if (renderer == null) {
            renderer = header.getDefaultRenderer();
        }
        return renderer.getTableCellRendererComponent(header.getTable(), aColumn.getHeaderValue(), false, false, -1, columnIndex);
    }

    private Dimension createHeaderSizeAqua(long width) {
        // None of the callers include the intercell spacing, do it here.
        if (width > Integer.MAX_VALUE) {
            width = Integer.MAX_VALUE;
        }
        return new Dimension((int)width, getHeaderHeightAqua());
    }

    
Return the minimum size of the header. The minimum width is the sum of the minimum widths of each column (plus inter-cell spacing).
/** * Return the minimum size of the header. The minimum width is the sum of the minimum widths of each column (plus * inter-cell spacing). */
public Dimension getMinimumSize(final JComponent c) { long width = 0; final Enumeration<TableColumn> enumeration = header.getColumnModel().getColumns(); while (enumeration.hasMoreElements()) { final TableColumn aColumn = enumeration.nextElement(); width = width + aColumn.getMinWidth(); } return createHeaderSizeAqua(width); }
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 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). */
public Dimension getPreferredSize(final JComponent c) { long width = 0; final Enumeration<TableColumn> enumeration = header.getColumnModel().getColumns(); while (enumeration.hasMoreElements()) { final TableColumn aColumn = enumeration.nextElement(); width = width + aColumn.getPreferredWidth(); } return createHeaderSizeAqua(width); } }