/* -*- Mode: java; indent-tabs-mode: nil; c-basic-offset: 2 -*-
 *
 * The contents of this file are subject to the Mozilla Public License
 * Version 1.0 (the "License"); you may not use this file except in
 * compliance with the License.  You may obtain a copy of the License at
 * http://www.mozilla.org/MPL/
 *
 * Software distributed under the License is distributed on an "AS IS"
 * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied.  See
 * the License for the specific language governing rights and limitations
 * under the License.
 *
 * The Original Code is the Grendel mail/news client.
 *
 * The Initial Developer of the Original Code is Netscape Communications
 * Corporation.  Portions created by Netscape are Copyright (C) 1997
 * Netscape Communications Corporation.  All Rights Reserved.
 *
 * Created: Will Scullin <scullin@netscape.com>, 21 Aug 1997.
 *
 * Contributors: Jeff Galyan <talisman@anamorphic.com>
 */

package grendel.widgets;

import java.awt.Color;
import java.awt.Component;
import java.awt.Cursor;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;

import java.util.Enumeration;

import javax.swing.CellRendererPane;
import javax.swing.JComponent;
import javax.swing.UIManager;
import javax.swing.event.ChangeEvent;

public class ColumnHeader extends JComponent implements ColumnModelListener {
  ColumnModel fColumnModel;
  CellRendererPane fCellRendererPane = new CellRendererPane();

  static final int RESIZE_MARGIN = 4;
  static final int DRAG_THRESHOLD = 4;

  boolean fDynamicUpdate = true;

  // Set by hit test
  boolean fOverResize;
  int     fHitOffset;
  Column  fPressedColumn;

  HeaderMouseListener fMouseListener;

  //
  // HeaderMouseListener
  //

  class HeaderMouseListener implements MouseMotionListener, MouseListener {
    boolean fResizing;
    int     fOverColumn;
    int     fDragOverColumn;
    int     fHitX;
    int     fHitColumn;
    int     fOldSize;
    int     fOldDragX = -1;
    int     fDragOffset;
    boolean fDragging;

    // MouseMotionListener Interface

    public void mouseMoved(MouseEvent aEvent) {
      fOverColumn = hitTest(aEvent.getX());
      if (fOverResize) {
        setCursor(Cursor.getPredefinedCursor(Cursor.E_RESIZE_CURSOR));
      } else {
        setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
      }
    }

    public void mouseDragged(MouseEvent aEvent) {
      if (fHitColumn < 0) {
        return;
      }
      int x = aEvent.getX();
      if (fResizing) {
        if (fDynamicUpdate) {
          updateColumnSize(x);
        } else {
          paintResizeLine(x);
        }
      } else if (Math.abs(x - fHitX) > DRAG_THRESHOLD) {
        fDragging = true;
        fDragOverColumn = hitTest(x);
        if (fDynamicUpdate) {
          int threshhold = 0;
          if (fDragOverColumn != -1) {
            if (fDragOverColumn < fHitColumn) {
              threshhold = getColumnX(fDragOverColumn) +
                           fColumnModel.getColumn(fHitColumn).getWidth();
            } else {
              threshhold = getColumnX(fDragOverColumn) +
                           fColumnModel.getColumn(fDragOverColumn).getWidth() -
                           fColumnModel.getColumn(fHitColumn).getWidth();
            }
          }

          if (fDragOverColumn != -1 &&
              ((fDragOverColumn < fHitColumn &&
                aEvent.getX() < threshhold) ||
                (fDragOverColumn > fHitColumn &&
                aEvent.getX() > threshhold))) {
            paintDraggedHeader(-1);
            fColumnModel.moveColumn(fHitColumn, fDragOverColumn);
            fHitColumn = fDragOverColumn;
          } else {
            paintDraggedHeader(aEvent.getX() + fDragOffset);
          }
        } else {
          paintDraggedHeader(aEvent.getX() + fDragOffset);
        }
      }
    }

    // MouseListener Interface

    public void mouseEntered(MouseEvent aEvent) {

    }

    public void mouseExited(MouseEvent aEvent) {

    }

    public void mouseClicked(MouseEvent aEvent) {
    }

    public void mousePressed(MouseEvent aEvent) {
      fHitX = aEvent.getX();
      fHitColumn = fOverColumn;
      if (fHitColumn > -1) {
        Column column = fColumnModel.getColumn(fHitColumn);
        if (fOverResize) {
          fResizing = true;
          fOldSize = column.getWidth();
        } else {
          fDragOffset = fHitOffset;
          if (column.isSelectable()) {
            fPressedColumn = column;
            repaint();
          }
        }
      }
    }

    public void mouseReleased(MouseEvent aEvent) {
      if (fResizing && !fDynamicUpdate) {
        paintResizeLine(-1);
        updateColumnSize(aEvent.getX());
      } else if (fDragging) {
        fDragging = false;
        paintDraggedHeader(-1);
        if (!fDynamicUpdate) {
          if (fDragOverColumn != fHitColumn && fDragOverColumn != -1) {
            fColumnModel.moveColumn(fHitColumn, fDragOverColumn);
          }
        }
      } else {
        if (fPressedColumn != null) {
          fColumnModel.setSelectedColumn(fPressedColumn);
        }
      }

      fHitX = -1;
      fHitColumn = -1;
      fResizing = false;
      fPressedColumn = null;

      repaint();
    }

    void updateColumnSize(int aMouseX) {
      Column column = fColumnModel.getColumn(fHitColumn);
      int newSize = fOldSize + (aMouseX - fHitX);

      if (newSize >= column.getMinWidth() &&
          newSize <= column.getMaxWidth()) {
        column.setWidth(newSize);
      }
    }

    void paintResizeLine(int aMouseX) {
      Graphics g = getGraphics();
      g.setXORMode(Color.white);
      g.setColor(Color.black);

      if (fOldDragX > -1) {
        g.drawLine(fOldDragX, 0, fOldDragX, getHeight());
      }
      if (aMouseX > -1) {
        g.drawLine(aMouseX, 0, aMouseX, getHeight());
      }
      g.setPaintMode();
      fOldDragX = aMouseX;
    }

    void paintDraggedHeader(int aMouseX) {
      Graphics g = getGraphics();
      g.setXORMode(Color.white);

      Column column = fColumnModel.getColumn(fHitColumn);

      int w, h;

      w = column.getWidth();
      h = getHeight();

      HeaderRenderer renderer = column.getHeaderRenderer();
      renderer.setValue(column, HeaderRenderer.DRAGGING);
      Component component = renderer.getComponent();

      if (fOldDragX > -1) {
        paintComponent(g, component, fOldDragX, 0, w, h);
      }
      if (aMouseX > -1) {
        paintComponent(g, component, aMouseX, 0, w, h);
      }

      g.setPaintMode();
      fOldDragX = aMouseX;
    }

    int getColumnX(int aIdx) {
      int res = 0;
      for (int i = 0; i < aIdx; i++) {
        res += fColumnModel.getColumn(i).getWidth();
      }
      return res;
    }
  }

  //
  // Constructor
  //

  /**
   * Takes a ColumnModel. The ColumnModel is generally shared with whatever
   * is the column header for.
   */

  public ColumnHeader(ColumnModel aColumnModel) {
    add(fCellRendererPane);

    fColumnModel = aColumnModel;
    fColumnModel.addColumnModelListener(this);

    updateUI();

    fMouseListener = new HeaderMouseListener();
    addMouseListener(fMouseListener);
    addMouseMotionListener(fMouseListener);
  }

  /**
   * Sets whether the UI repaints all columns as a column is
   * being resized or dragged. Set to false for better performance.
   */

  public void setDynamicUpdate(boolean aDynamic) {
    fDynamicUpdate = aDynamic;
  }

  /**
   * @return Whether the UI is repainting the columns as a column
   *         is being sized or dragged.
   */

  public boolean getDynamicUpdate() {
    return fDynamicUpdate;
  }

  //
  // Utility methods
  //

  int hitTest(int aX) {
    if (aX < 0) {
      return -1;
    }

    int index = 0;
    int w = 0;

    fOverResize = false;

    Enumeration columns = fColumnModel.getColumns();
    while (columns.hasMoreElements()) {
      Column column = (Column) columns.nextElement();
      w += column.getWidth();
      if (aX < w) {
        if (aX > w - RESIZE_MARGIN) {
          fOverResize = true;
        }
        fHitOffset = w - column.getWidth() - aX;
        return index;
      }
      index++;
    }

    return -1;
  }

  void paintComponent(Graphics g, Component component,
                      int x, int y, int w, int h) {
    fCellRendererPane.paintComponent(g, component, this, x, y, w, h);
  }

  //
  // ColumnModelListener Interface
  //

  public void columnAdded(ColumnModelEvent e) {
    resizeAndRepaint();
  }

  public void columnRemoved(ColumnModelEvent e) {
    resizeAndRepaint();
  }

  public void columnMoved(ColumnModelEvent e) {
    repaint();
  }

  public void columnMarginChanged(ChangeEvent e) {
    resizeAndRepaint();
  }

  public void columnWidthChanged(ColumnModelEvent e) {
    resizeAndRepaint();
  }

  public void columnSelectionChanged(ChangeEvent e) {
    repaint();
  }

  void resizeAndRepaint() {
    setSize(getPreferredSize());
    repaint();
  }

  //
  // Component Overloads
  //

  public void paint(Graphics g) {
    int margin = fColumnModel.getColumnMargin();
    int x = 0, y = 0;
    int w = 0, h = getHeight();

    Color bg = getBackground();
    for (Enumeration columnEnum = fColumnModel.getColumns();
         columnEnum.hasMoreElements(); ) {
      Column column = (Column) columnEnum.nextElement();

      // Easy Part
      w = column.getWidth() + margin;

      if (bg != null) {
        g.setColor(bg);
        g.fillRect(x, y, w, h);
      }

      // Hard Part
      HeaderRenderer renderer = column.getHeaderRenderer();
      if (column == fPressedColumn) {
        renderer.setValue(column, HeaderRenderer.DEPRESSED);
      } else if (column == fColumnModel.getSelectedColumn()) {
        renderer.setValue(column, HeaderRenderer.SELECTED);
      } else {
        renderer.setValue(column, HeaderRenderer.NORMAL);
      }
      Component component = renderer.getComponent();
      fCellRendererPane.paintComponent(g, component, this, x, y, w, h);

      x += w;
    }
    if (x < getWidth()) {
      if (bg != null) {
        g.setColor(bg);
        g.fillRect(x, y, getWidth() - x, h);
      }
    }
  }

  public Dimension getPreferredSize() {
    Dimension res = new Dimension();
    int margin = fColumnModel.getColumnMargin();
    for (Enumeration columnEnum = fColumnModel.getColumns();
         columnEnum.hasMoreElements(); ) {
      Column column = (Column) columnEnum.nextElement();

      // Easy Part
      res.width += column.getWidth() + margin;

      // Hard Part
      HeaderRenderer renderer = column.getHeaderRenderer();
      renderer.setValue(column, HeaderRenderer.NORMAL);
      Component component = renderer.getComponent();
      Dimension dim = component.getPreferredSize();
      if (dim.height > res.height) {
        res.height = dim.height;
      }
    }
    return res;
  }

  public Dimension getMinimumSize() {
    return getPreferredSize();
  }

  public Dimension getMaximumSize() {
    return getPreferredSize();
  }

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

    setBackground(UIManager.getColor("control"));
  }
}
