/* GraphPropertiesTableComponent.java
 * =========================================================================
 * This file is part of the GrInvIn project - http://www.grinvin.org
 * 
 * Copyright (C) 2005-2008 Universiteit Gent
 * 
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or (at
 * your option) any later version.
 * 
 * This program 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 for more details.
 * 
 * A copy of the GNU General Public License can be found in the file
 * LICENSE.txt provided with the source distribution of this program (see
 * the META-INF directory in the source jar). This license can also be
 * found on the GNU website at http://www.gnu.org/licenses/gpl.html.
 * 
 * If you did not receive a copy of the GNU General Public License along
 * with this program, contact the lead developer, or write to the Free
 * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
 * 02110-1301, USA.
 */

package org.grinvin.gui.components;

import be.ugent.caagt.swirl.dnd.LocalTransferHandler;

import java.awt.Color;
import java.awt.Container;
import java.awt.Graphics;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.event.HierarchyEvent;
import java.awt.event.HierarchyListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.util.ResourceBundle;
import javax.help.CSH;
import javax.swing.JTable;
import javax.swing.JViewport;

import org.grinvin.worksheet.HasRequestedCell;
import org.grinvin.graphs.GraphBundleView;
import org.grinvin.gui.dnd.InvariantDropHandler;
import org.grinvin.gui.dnd.InvariantFactoryDropHandler;
import org.grinvin.gui.WatermarkPainter;
import org.grinvin.help.GraphPropertiesTableHelpManager;
import org.grinvin.help.HelpManager;
import org.grinvin.invariants.Invariant;
import org.grinvin.invariants.InvariantValue;
import org.grinvin.invariants.InvariantValueDelegate;
import org.grinvin.list.graphs.GraphListElement;
import org.grinvin.list.GraphPropertiesTableModel;
import org.grinvin.list.GraphPropertiesTableModelListener;
import org.grinvin.gui.components.render.InvariantTableCellRenderer;
import org.grinvin.gui.components.render.InvariantValueTableCellRenderer;
import org.grinvin.list.actions.InvariantValuePopupMenu;

/**
 * Table to display a GraphPropertiesTableModel.
 */
public class GraphPropertiesTableComponent extends JTable implements AcceptsInvariant, MouseListener, GraphPropertiesTableModelListener, HasRequestedCell {
    
    //
    private static final String BUNDLE_NAME = "org.grinvin.worksheet.resources";
    
    // shared watermark painter
    private static final WatermarkPainter WATERMARK_PAINTER = new WatermarkPainter(
            20,
            ResourceBundle.getBundle(BUNDLE_NAME).getString("InvariantList.emptytext"),
            new Color(245, 237, 237)
            );
    
    // shared transfer handler
    private static final LocalTransferHandler TRANSFER_HANDLER;
    
    static {
        TRANSFER_HANDLER = new LocalTransferHandler();
        TRANSFER_HANDLER.addDropHandler(InvariantDropHandler.getInstance());
        TRANSFER_HANDLER.addDropHandler(InvariantFactoryDropHandler.getInstance());
    }
    
    //
    private final InvariantValuePopupMenu popupMenu
            = new InvariantValuePopupMenu();

    //
    private GraphListElement gle;
    
    /** Creates a new instance of GraphPropertiesTable */
    public GraphPropertiesTableComponent(final GraphPropertiesTableModel model) {
        super(model);
        setElement(model.getGraphListElement());
        setTransferHandler(TRANSFER_HANDLER);
        setDefaultRenderer(Invariant.class, InvariantTableCellRenderer.getInstance());
        setDefaultRenderer(InvariantValue.class, InvariantValueTableCellRenderer.getInstance());
        setOpaque(false); // we paint our own background
        setShowGrid(false); //hide grid
        HelpManager.enableHelpKey(this, "intro");
        CSH.addManager(new GraphPropertiesTableHelpManager(this));

        addHierarchyListener(new HierarchyListener() {
            public void hierarchyChanged(HierarchyEvent e) {
                if ((e.getChangeFlags() & HierarchyEvent.SHOWING_CHANGED) == HierarchyEvent.SHOWING_CHANGED) {
                    if (GraphPropertiesTableComponent.this.isShowing()) {
                        model.addGraphPropertiesTableModelListener(GraphPropertiesTableComponent.this);
                    } else {
                        model.removeGraphPropertiesTableModelListener(GraphPropertiesTableComponent.this);
                    }
                }
            }
        });
        
        // mouse interaction
        addMouseListener(this);
    }
    
    private void setElement(GraphListElement element) {
        gle = element;
    }
    
    /**
     * Adds a watermark to the list.
     */
    @Override
    protected void paintComponent(Graphics g) {
        g.setColor(getBackground());
        g.fillRect(0, 0, getWidth(), getHeight());
        WATERMARK_PAINTER.paint(this, g);
        super.paintComponent(g);
    }
    

    public Reason addInvariant(Invariant invariant) {
        gle.getInvariantNow(invariant);
        return Reason.SUCCESS;
    }
    
    public Reason acceptsInvariant(Invariant invariant) {
        GraphBundleView bundle = gle.getBundle();
        if (bundle != null) {
            return (bundle.getCachedInvariantValue(invariant) == null) ? Reason.SUCCESS : Reason.DUPLICATE;
        } else {
            return Reason.SUCCESS;
        }
    }
    
    public AcceptsInvariant.Reason acceptsInvariant(Class<? extends InvariantValue> clazz) {
        return Reason.SUCCESS;
    }

    public void graphListElementChanged() {
        setElement(((GraphPropertiesTableModel)getModel()).getGraphListElement());
        setRequestedCell(-1,-1);
    }
    
    // allow dropping below the table,
    // solution from http://weblogs.java.net/blog/shan_man/archive/2006/01/enable_dropping.html
    @Override
    public boolean getScrollableTracksViewportHeight() {
        // fetch the table's parent
        Container viewport = getParent();
        
        // if the parent is not a viewport, calling this isn't useful
        if (!(viewport instanceof JViewport)) {
            return false;
        }
        
        // return true if the table's preferred height is smaller
        // than the viewport height, else false
        return getPreferredSize().height < viewport.getHeight();
    }
    
    //
    private InvariantValue getInvariantValueAtPoint(Point point) {
        
        int row = rowAtPoint(point);
        if (row < 0)
            return null;
        else
            return (InvariantValue)getModel().getValueAt(row, 1);
    }
    
    public void mouseClicked(MouseEvent e) {
        // TODO: remove common code with GraphTableComponent (GTC)
        
        if (e.getClickCount() != 2)
            return;
        
        Point point = e.getPoint();
        int row = rowAtPoint (point);
        int column = columnAtPoint(point);
        if (row < 0 || column != 1)  //[!] different from GTC
            return ;
        column = convertColumnIndexToModel(column);
        InvariantValue ivalue = (InvariantValue)getModel().getValueAt(row, column);
        if (ivalue != null) {
            InvariantValueDelegate delegate = ivalue.getType().getDelegate();
            if (delegate.hasDetails(ivalue)) {
                delegate.showDetails(ivalue, gle, this, e.getX(), e.getY() );
            }
        }
    }

    public void mousePressed(MouseEvent e) {
        //set requested cell
        setRequestedCell(rowAtPoint(e.getPoint()), 1); //[!] different from GTC
        
        if (e.isPopupTrigger()) {
            InvariantValue iv = getInvariantValueAtPoint(e.getPoint());
            if (gle != null)
                popupMenu.show(gle, iv, this, e.getX(), e.getY());
        }
    }

    public void mouseReleased(MouseEvent e) {
        if (e.isPopupTrigger()) {
            InvariantValue iv = getInvariantValueAtPoint(e.getPoint());
            if (gle != null)
                popupMenu.show(gle, iv, this, e.getX(), e.getY());
        }
    }

    public void mouseEntered(MouseEvent e) {
        // intentionally empty
    }

    public void mouseExited(MouseEvent e) {
        // intentionally empty
    }
    
    private int requestedRow = -1;
    private int requestedColumn = -1;
    
    public boolean isRequestedCell(int row, int column){
        return row == requestedRow && column == requestedColumn;
    }
    
    public void setRequestedCell(int row, int column){
        Rectangle oldRect = getCellRect(requestedRow, requestedColumn, false);
        requestedRow = row;
        requestedColumn = column;
        Rectangle newRect = getCellRect(row, column, false);
        repaint(oldRect);
        repaint(newRect);
    }

}
