/*
 * Copyright 2011 JBoss Inc
 * 
 * Licensed under the Apache License, Version 2.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.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations under
 * the License.
 */
package org.drools.guvnor.client.widgets.decoratedgrid;

import org.drools.guvnor.client.messages.Constants;
import org.drools.guvnor.client.resources.DecisionTableResources;
import org.drools.guvnor.client.resources.DecisionTableResources.DecisionTableStyle;

import com.google.gwt.core.client.GWT;
import com.google.gwt.dom.client.DivElement;
import com.google.gwt.dom.client.Style.Overflow;
import com.google.gwt.dom.client.Style.Position;
import com.google.gwt.dom.client.Style.Unit;
import com.google.gwt.dom.client.Style.Visibility;
import com.google.gwt.dom.client.TableElement;
import com.google.gwt.event.dom.client.MouseDownEvent;
import com.google.gwt.event.dom.client.MouseDownHandler;
import com.google.gwt.event.dom.client.MouseMoveEvent;
import com.google.gwt.event.dom.client.MouseMoveHandler;
import com.google.gwt.event.dom.client.MouseOutEvent;
import com.google.gwt.event.dom.client.MouseOutHandler;
import com.google.gwt.event.dom.client.MouseUpEvent;
import com.google.gwt.event.dom.client.MouseUpHandler;
import com.google.gwt.event.logical.shared.HasResizeHandlers;
import com.google.gwt.event.logical.shared.ResizeEvent;
import com.google.gwt.event.logical.shared.ResizeHandler;
import com.google.gwt.event.shared.HandlerRegistration;
import com.google.gwt.user.client.DOM;
import com.google.gwt.user.client.Element;
import com.google.gwt.user.client.ui.CellPanel;
import com.google.gwt.user.client.ui.Panel;
import com.google.gwt.user.client.ui.ScrollPanel;
import com.google.gwt.user.client.ui.Widget;

/**
 * An abstract "Header" widget to decorate a <code>DecoratedGridWidget</code>
 * 
 * @param <T>
 *            The type of domain columns represented by the Header
 */
public abstract class DecoratedGridHeaderWidget<T> extends CellPanel
    implements
        HasResizeHandlers,
    HasColumnResizeHandlers {

    /**
     * Container class for information relating to re-size operations
     */
    public class ResizerInformation {

        private boolean          isResizePrimed    = false;
        private boolean          isResizing        = false;
        private int              resizeColumnLeft  = 0;
        private DynamicColumn<T> resizeColumn      = null;
        private int              resizeColumnWidth = 0;

        /**
         * @param resizeColumn
         *            the resizeColumn to set
         */
        public void setResizeColumn(DynamicColumn<T> resizeColumn) {
            this.resizeColumn = resizeColumn;
        }

        /**
         * @param resizeColumnLeft
         *            the resizeColumnLeft to set
         */
        public void setResizeColumnLeft(int resizeColumnLeft) {
            this.resizeColumnLeft = resizeColumnLeft;
        }

        /**
         * @param isResizePrimed
         *            the bResizePrimed to set
         */
        public void setResizePrimed(boolean isResizePrimed) {
            this.isResizePrimed = isResizePrimed;
        }

    }

    private static final int                      MIN_COLUMN_WIDTH = 16;

    protected Panel                               panel;
    protected DecoratedGridWidget<T>              grid;

    // Resources
    protected static final DecisionTableResources resource         = GWT
                                                                            .create( DecisionTableResources.class );
    protected static final DecisionTableStyle     style            = resource.cellTableStyle();
    protected static final Constants              constants        = GWT.create( Constants.class );

    // Column resizing
    private ResizerInformation                    resizerInfo      = new ResizerInformation();
    private DivElement                            resizer;

    /**
     * Construct a "Header" for the provided DecoratedGridWidget
     * 
     * @param grid
     */
    public DecoratedGridHeaderWidget(DecoratedGridWidget<T> grid) {
        if ( grid == null ) {
            throw new IllegalArgumentException( "grid cannot be null" );
        }
        this.grid = grid;

        // Container DIV in which the components will live
        Element tre = DOM.createTR();
        Element tce = DOM.createTD();
        Element div = DOM.createDiv();
        div.getStyle().setPosition( Position.RELATIVE );
        getBody().getParentElement().<TableElement> cast().setCellSpacing( 0 );
        getBody().getParentElement().<TableElement> cast().setCellPadding( 0 );
        tce.appendChild( div );
        tre.appendChild( tce );
        getBody().appendChild( tre );

        // Widgets within the container
        panel = new ScrollPanel();

        // We don't want scroll bars on the ScrollPanel so hide any overflow
        panel.getElement().getStyle().setOverflow( Overflow.HIDDEN );
        panel.add( getHeaderWidget() );
        add( panel,
             div );

        // Column resizing
        resizer = DOM.createDiv().<DivElement> cast();
        resizer.addClassName( style.headerResizer() );
        resizer.getStyle().setTop( 0,
                                   Unit.PX );

        // Add the resizer to the outer most container, otherwise it gets
        // truncated by the ScrollPanel as it hides any overflow
        div.appendChild( resizer );

        addDomHandler( new MouseMoveHandler() {

                           public void onMouseMove(MouseMoveEvent event) {
                               int mx = event.getClientX();
                               if ( resizerInfo.isResizing ) {
                                   if ( mx - resizerInfo.resizeColumnLeft < MIN_COLUMN_WIDTH ) {
                                       event.preventDefault();
                                       return;
                                   }
                                   setResizerDimensions( event.getX() );
                                   resizerInfo.resizeColumnWidth = mx - resizerInfo.resizeColumnLeft;
                                   resizeColumn( resizerInfo.resizeColumn,
                                                 resizerInfo.resizeColumnWidth );

                                   // Second call to set dimensions as a column
                                   // resize can add (or remove) a scroll bar to
                                   // (or from) the Decision Table and our
                                   // resizer needs to be redrawn accordingly.
                                   // Just having the call to set dimensions
                                   // after the column has been resized added
                                   // excess flicker to movement of the resizer.
                                   setResizerDimensions( event.getX() );
                                   event.preventDefault();
                               } else {
                                   resizerInfo = getResizerInformation( mx );
                               }
                           }

                       },
                       MouseMoveEvent.getType() );

        addDomHandler( new MouseDownHandler() {

                           public void onMouseDown(MouseDownEvent event) {
                               if ( !resizerInfo.isResizePrimed ) {
                                   return;
                               }
                               int mx = event.getX();
                               resizerInfo.isResizing = true;
                               setResizerDimensions( mx );
                               resizer.getStyle().setVisibility( Visibility.VISIBLE );
                               event.preventDefault();
                           }

                       },
                       MouseDownEvent.getType() );

        addDomHandler( new MouseUpHandler() {

                           public void onMouseUp(MouseUpEvent event) {
                               if ( !resizerInfo.isResizing ) {
                                   return;
                               }
                               resizerInfo.isResizing = false;
                               resizerInfo.isResizePrimed = false;
                               resizer.getStyle().setVisibility( Visibility.HIDDEN );
                               event.preventDefault();
                           }

                       },
                       MouseUpEvent.getType() );

        addDomHandler( new MouseOutHandler() {

                           public void onMouseOut(MouseOutEvent event) {
                               if ( !resizerInfo.isResizing ) {
                                   return;
                               }
                               resizerInfo.isResizing = false;
                               resizerInfo.isResizePrimed = false;
                               resizer.getStyle().setVisibility( Visibility.HIDDEN );
                               event.preventDefault();
                           }

                       },
                       MouseOutEvent.getType() );

    }

    public HandlerRegistration addColumnResizeHandler(
                                                      ColumnResizeHandler handler) {
        if ( handler == null ) {
            throw new IllegalArgumentException( "handler cannot be null" );
        }

        return addHandler( handler,
                           ColumnResizeEvent.getType() );
    }

    public HandlerRegistration addResizeHandler(ResizeHandler handler) {
        if ( handler == null ) {
            throw new IllegalArgumentException( "handler cannot be null" );
        }

        return addHandler( handler,
                           ResizeEvent.getType() );
    }

    /**
     * Redraw entire header
     */
    public abstract void redraw();

    /**
     * Set scroll position to enable some degree of synchronisation between
     * DecisionTable and DecisionTableHeader
     * 
     * @param position
     */
    public abstract void setScrollPosition(int position);

    @Override
    public void setWidth(String width) {
        // Set the width of our ScrollPanel too; to prevent the containing
        // DIV from extending it's width to accommodate the increase in size
        super.setWidth( width );
        panel.setWidth( width );
    }

    // Bit of a hack to ensure the resizer is the correct size. The
    // Decision Table itself could be contained in an outer most DIV
    // that hides any overflow however the horizontal scrollbar
    // would be rendered inside the DIV and hence still be covered
    // by the resizer.
    private void setResizerDimensions(int position) {
        resizer.getStyle().setHeight(
                                      grid.getSidebarWidget().getOffsetHeight(),
                                      Unit.PX );
        resizer.getStyle().setLeft( position,
                                    Unit.PX );
    }

    /**
     * Get the Widget that should be wrapped by the scroll panel and resize
     * handlers. The widget renders the actual "header" embedded within the
     * decorations provided by this class: scroll-bars and resizing support.
     * 
     * @return
     */
    protected abstract Widget getHeaderWidget();

    /**
     * Given the X-coordinate check whether resizing of any column should be
     * enabled. The ResizerInformation return value contains necessary
     * information for this decorating class to perform column-resizing.
     * 
     * @param mx
     *            the MouseMoveEvent.event.getClientX() coordinate
     * @return
     */
    protected abstract ResizerInformation getResizerInformation(int mx);

    /**
     * Resize the Header column
     * 
     * @param resizeColumn
     * @param resizeColumnWidth
     */
    protected abstract void resizeColumn(DynamicColumn<T> resizeColumn,
                                         int resizeColumnWidth);

}
