/*
 * Copyright 2006-2007 Queplix Corp.
 *
 * Licensed under the Queplix Public License, Version 1.1.1 (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.queplix.com/solutions/commercial-open-source/queplix-public-license/
 *
 * 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 com.queplix.core.client.common.ui.grid;

import com.google.gwt.user.client.Command;
import com.google.gwt.user.client.DOM;
import com.google.gwt.user.client.DeferredCommand;
import com.google.gwt.user.client.Element;
import com.google.gwt.user.client.Event;
import com.google.gwt.user.client.ui.Grid;
import com.google.gwt.user.client.ui.SourcesTableEvents;
import com.google.gwt.user.client.ui.TableListener;
import com.queplix.core.client.common.StringUtil;
import com.queplix.core.client.common.event.EventSource;

/**
 * Header of DataGrid
 *
 * @author Vasily Mikhailitchenko
 * @since 19 Dec 2006
 */
class HeaderGrid extends Grid implements TableListener {
    /**
     * None of cells selected.
     */
    private static final int COLUMN_UNSELECTED = -1;

    private static final int RESIZEABLE_AREA_OFFSET = 4;

    /**
     * Needed this for mouse events tracing
     */
    private int[] columnOffsets;
    /**
     * Current grid header captions
     */
    private String[] captions;
    /**
     * Current columns' sort orders
     */
    private Boolean[] sortOrders;
    /**
     * Is column resizing at the moment.
     */
    private boolean columnResizing;
    /**
     * Is cursor has resizing style (grid can turn to resizable state if user clicks mouse button).
     */
    private boolean cursorResizing;
    /**
     * Column to be resized
     */
    private int columnToResize;
    private int originalResizePosition;

    private Boolean initialSortOrder = null;
    private Boolean currentSortOrder;
    private int lastCellClicked = -1;

    private int lastColumnSelected = COLUMN_UNSELECTED;

    private boolean isColumnsFake = false;

    private GridModel model;
    private GridView view;

    private BaseInternalDataGrid dataGrid;
    private DataGrid parent;
    private GridElementsStrategy strategy;
    private GridSortListenerCollection sortListeners;
    private boolean sortingEnabled = true;

    public HeaderGrid(GridModel model,
                      GridView view,
                      DataGrid parent,
                      GridElementsStrategy strategy,
                      GridSortListenerCollection sortListeners) {

        // initialization
        this.model = model;
        this.view = view;
        this.parent = parent;
        this.strategy = strategy;
        this.sortListeners = sortListeners;

        resizeRows(1);
        sinkEvents(Event.MOUSEEVENTS);
        sinkEvents(Event.ONDBLCLICK);

        currentSortOrder = initialSortOrder;
        strategy.addTableListener(this);
        addTableListener(this);
    }

    public void setInternalDataGrid(BaseInternalDataGrid dataGrid) {
        this.dataGrid = dataGrid;
    }

    public void resizeColumns(int columns) {
        if(columnOffsets == null || columnOffsets.length != columns) {
            columnOffsets = new int[columns];
            captions = new String[columns];
            sortOrders = new Boolean[columns];
        }
        super.resizeColumns(columns);
    }

    public void setupWidgets(final DataGridMeta dataGridMeta) {
        boolean performUpdate = false;
        if(dataGridMeta == null || dataGridMeta.getColumnsCount() < 1) {
            if(!isColumnsFake) {
                resizeColumns(1);
                setupFakeEmptyData();
                isColumnsFake = true;
                performUpdate = true;
            }
        } else {
            int columnsCount = dataGridMeta.getColumnsCount();
            if(getColumnCount() != columnsCount) {
                resizeColumns(columnsCount);
            }
            setupHeaders(dataGridMeta);
            isColumnsFake = false;
            performUpdate = true;
        }
        if(performUpdate) {
            removeColumnSelection();
            DeferredCommand.add(new Command() {
                public void execute() {
                    saveColumnsWidthAndOffsets();
                    //                adaptGridSize();
                }
            });
        }
    }

    private void setupFakeEmptyData() {
        setCaption(0, "", null);
        getCellFormatter().setStyleName(0, 0, view.getHeaderCellStyleName());
        getRowFormatter().setStyleName(0, view.getHeaderRowStyleName());
    }

    private void setupHeaders(DataGridMeta dataGridMeta) {
        SortColumn sortColumn = model.getSortColumn();
        for(int column = 0; column < dataGridMeta.getColumnsCount(); column++) {
            Boolean sortBy = null;
            DataGridColumn dataGridColumn = dataGridMeta.getColumn(column);
            int startSize = dataGridColumn.getWidth();
            if(startSize == DataGridColumn.SIZE_UNSPECIFIED) {
                startSize = DataGrid.calcHeaderColumnWidth(
                        dataGridColumn.getCaption().length());
            }
            if(startSize < DataGridColumn
                    .MINIMAL_WIDTH) {//initialize start site on attach event
                startSize = DataGridColumn.MINIMAL_WIDTH;
            }
            if(sortColumn != null) {
                if(column == sortColumn.getColumnIndex()) {
                    sortBy = sortColumn.getAscending();
                }
            }

            String newCaption = dataGridMeta.getCaption(column);
            if(!equalToCurrentCaption(newCaption, column, sortBy)) {
                setCaption(column, newCaption, sortBy);
                strategy.setHeaderColumnWidth(this, column, startSize, 0);
                getCellFormatter().setStyleName(0, column,
                        view.getHeaderCellStyleName());
            }
            dataGrid.resizeColumn(column, startSize, 0);
        }
        getRowFormatter().addStyleName(0, view.getHeaderRowStyleName());
        saveColumnsWidthAndOffsets();
        setWidth(StringUtil.pixelToSize(dataGrid.getOffsetWidth()));
//        adaptGridSize();
    }

    private void setCaption(int column, String newCaption, Boolean sortBy) {
        captions[column] = newCaption;
        sortOrders[column] = sortBy;
        strategy.setHeaderCellValue(this, 0, column, newCaption, sortBy);
    }

    private boolean equalToCurrentCaption(String newCaption, int column,
                                          Boolean sortBy) {
        String oldCaption = captions[column];
        Boolean oldSortBy = sortOrders[column];

        if(oldSortBy == null) {
            return (oldCaption != null) && oldCaption.equalsIgnoreCase(
                    newCaption) && (sortBy == null);
        } else {
            return (oldCaption != null) && oldCaption.equalsIgnoreCase(
                    newCaption) && oldSortBy.equals(sortBy);
        }
    }

    /*private void adaptGridSize() {
        String size = StringUtil.pixelToSize(columnOffsets[columnOffsets.length - 1]);
        DialogHelper.showMessageDialog("Size: " + size);
        setWidth(size);
        dataGrid.setWidth(size);
    }
*/
    public void saveColumnsWidthAndOffsets() {
        int offset = 0;
        for(int column = 0; column < columnOffsets.length; column++) {
            offset += getHeaderCellOffsetWidth(column);
            columnOffsets[column] = offset;
        }
        DataGridMeta dataGridMeta = model.getDataGridMeta();
        for(int i = 0, n = dataGridMeta.getColumnsCount(); i < n; i++) {
            if(dataGridMeta.getColumn(i).isResized()) {
                dataGridMeta.getColumn(i).setWidth(getHeaderCellWidth(i));
            }
        }
    }

    public int getHeaderCellWidth(int column) {
        Element tr = DOM.getChild(getBodyElement(), 0);
        Element td = DOM.getChild(tr, column);
        return DOM.getIntAttribute(td, "width");
    }

    private int getHeaderCellOffsetWidth(int column) {
        Element tr = DOM.getChild(getBodyElement(), 0);
        Element td = DOM.getChild(tr, column);
        return DOM.getIntAttribute(td, "offsetWidth");
    }

    public void onBrowserEvent(Event event) {
        int x = DOM.eventGetClientX(event) - DOM.getAbsoluteLeft(getElement());
        switch(DOM.eventGetType(event)) {
            case Event.ONMOUSEDOWN:
                if(DOM.eventGetButton(event) == Event.BUTTON_RIGHT) {
                    invertSelection(x);
                } else {
                    startColumnResize(x);
                }
                break;
            case Event.ONDBLCLICK:
                autoresizeColumn(x);
                break;
            case Event.ONMOUSEUP:
                stopColumnResize();
                break;
            case Event.ONMOUSEMOVE:
                setupCursor(x);
                performColumnResize(x);
                break;
        }
    }

    /**
     * Invert column selection with the given coordinate. If column tuns to selected state and another column is already selected, then deselect it.
     *
     * @param x column width coordinate
     */
    private void invertSelection(int x) {
        if(!isColumnsFake) {
            int column = getColumnNumber(x);
            invertColumnSelection(column);
        }
    }

    /**
     * Invert the given column selection. If column tuns to selected state and another column is already selected, then deselect it.
     *
     * @param column column number
     */
    private void invertColumnSelection(int column) {
        if(lastColumnSelected == column) {
            removeColumnSelection();
        } else {
            setSelectedColumn(column);
        }
    }

    /**
     * Set the given column selected.
     *
     * @param column given column
     */
    public void setSelectedColumn(int column) {
        if(column != lastColumnSelected) {
            deselectColumn(lastColumnSelected);
            selectColumn(column);
            lastColumnSelected = column;
            fireColumnSelected(COLUMN_UNSELECTED);
        }
    }

    /**
     * Removes selection from currently selected column
     */
    public void removeColumnSelection() {
        deselectColumn(lastColumnSelected);
        lastColumnSelected = COLUMN_UNSELECTED;
        fireColumnSelected(COLUMN_UNSELECTED);
    }

    /**
     * Makes header column selected.
     *
     * @param column column number
     */
    private void selectColumn(int column) {
        if(column >= 0 && column < getColumnCount()) {
            getCellFormatter().addStyleName(0, column,
                    view.getSelectedHeaderCellStyleName());
        }
    }

    /**
     * Makes header column not selected.
     *
     * @param column column number
     */
    private void deselectColumn(int column) {
        if(column >= 0 && column < getColumnCount()) {
            getCellFormatter().removeStyleName(0, column,
                    view.getSelectedHeaderCellStyleName());
        }
    }

    /**
     * Returns the column number by its x position.
     *
     * @param x width position
     * @return column number
     */
    private int getColumnNumber(int x) {
        int columnNum = COLUMN_UNSELECTED;
        for(int column = 0; column < columnOffsets.length; column++) {
            if(x < columnOffsets[column]) {
                columnNum = column;
                break;
            }
        }
        return columnNum;
    }

    /**
     * Last selected column. If at least one column selected, then value will be positive, otherwise negative.
     *
     * @return Last selected column.
     */
    public int getSelectedColumn() {
        return lastColumnSelected;
    }

    protected void fireColumnSelected(int column) {
        Events.COLUMN_SELECTED.setData(new Integer(column));
        getEventSource().fireEvent(Events.COLUMN_SELECTED);
    }

    public void handleMouseUpEvent(int x) {
        stopColumnResize();
    }

    public void autoresizeParticularColumn(int column, boolean saveToMeta) {
        int newWidth = findMaximumRowWidth(column);
        int delta = newWidth - getHeaderCellWidth(column);
        strategy.setHeaderColumnWidth(this, column, newWidth, delta);
        dataGrid.resizeColumn(column, newWidth, delta);
        model.getDataGridMeta().getColumn(column).setResized(true);

        if(saveToMeta) {
            saveColumnsWidthAndOffsets();
//            adaptGridSize();
        }
    }

    private void autoresizeColumn(int x) {
        if(!isColumnsFake) {
            for(int column = 0; column < columnOffsets.length; column++) {
                if(x > (columnOffsets[column] - RESIZEABLE_AREA_OFFSET) && x < (
                        columnOffsets[column] + RESIZEABLE_AREA_OFFSET)) {
                    cursorResizing = true;
                    columnToResize = column;
                    autoresizeParticularColumn(columnToResize, true);
                    parent.adjustHeaderPosition();
                }
            }
        }
    }

    private int findMaximumRowWidth(int column) {
        int longest = 0;
        for(int i = 0, n = dataGrid.getRowCount(); i < n; i++) {
            int txtLength = dataGrid.getText(i, column).length();
            if(txtLength > longest) {
                longest = txtLength;
            }
        }
        int captionLength = model.getDataGridMeta().getCaption(column).length();
        return Math.max(DataGrid.calcHeaderColumnWidth(captionLength),
                DataGrid.calcColumnWidth(longest));
    }

    private void setupCursor(int x) {
        if(!columnResizing) {
            String columnResizeStyleName = view.getColumnResizeStyleName();
            if(cursorResizing) {
                if(x < (columnOffsets[columnToResize] - RESIZEABLE_AREA_OFFSET)
                        || x > (columnOffsets[columnToResize]
                        + RESIZEABLE_AREA_OFFSET)) {
                    cursorResizing = false;
                    removeStyleName(columnResizeStyleName);
                }
            } else {
                for(int column = 0; column < columnOffsets.length; column++) {
                    if(x > (columnOffsets[column] - RESIZEABLE_AREA_OFFSET)
                            && x < (columnOffsets[column]
                            + RESIZEABLE_AREA_OFFSET)) {
                        cursorResizing = true;
                        columnToResize = column;
                        addStyleName(columnResizeStyleName);
                        break;
                    }
                    if(x < (columnOffsets[column]
                            + 2 * RESIZEABLE_AREA_OFFSET)) {
                        break;
                    }
                }
            }
        }
    }

    public void performColumnResize(int x) {
        if(columnResizing) {
            int delta = x - originalResizePosition;
            int width = getHeaderCellWidth(columnToResize) + delta;
            if(width > DataGridColumn.MINIMAL_WIDTH) {
                strategy.setHeaderColumnWidth(this, columnToResize, width,
                        delta);
                dataGrid.resizeColumn(columnToResize, width, delta);
                originalResizePosition = x;

                parent.adjustHeaderPosition();
            }
        }
    }

    private void startColumnResize(int x) {
        if(cursorResizing) {
            columnResizing = true;
            originalResizePosition = x;
            DOM.setCapture(getElement()); // to capture last column resize
        }
    }

    private void stopColumnResize() {
        if(columnResizing) {
            DOM.releaseCapture(getElement());
            columnResizing = false;
            if(!isColumnsFake) {
                model.getDataGridMeta().getColumn(columnToResize).setResized(
                        true);
            }
            saveColumnsWidthAndOffsets();
        }
    }

    public void onCellClicked(SourcesTableEvents sender, int row, int cell) {
        if(!isColumnsFake && sortingEnabled) {
            if(cell != lastCellClicked) {
                currentSortOrder = SortColumnImpl.getNextSortOrder(
                        initialSortOrder);
            } else {
                currentSortOrder = SortColumnImpl.getNextSortOrder(
                        currentSortOrder);
            }
            sortListeners.fireEvent(parent, new SortColumnImpl(cell,
                    currentSortOrder));
            lastCellClicked = cell;
        }
    }

    public void setSortingEnabled(boolean enabled) {
        sortingEnabled = enabled;
    }

    // -------------------- public events ------------------------
    public static interface Events {
        com.queplix.core.client.common.event.Event/*]<Integer>[*/ COLUMN_SELECTED
                = new com.queplix.core.client.common.event.Event/*]<Integer>[*/();
    }

    private EventSource eventSource = new EventSource(this);

    public EventSource getEventSource() {
        return eventSource;
    }
    // ----------------- end of public events --------------------
}
