/*
 * 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.core.client.GWT;
import com.google.gwt.user.client.ui.ClickListener;
import com.google.gwt.user.client.ui.Composite;
import com.google.gwt.user.client.ui.FocusListener;
import com.google.gwt.user.client.ui.FocusPanel;
import com.google.gwt.user.client.ui.ScrollListener;
import com.google.gwt.user.client.ui.VerticalPanel;
import com.google.gwt.user.client.ui.Widget;
import com.queplix.core.client.common.StringUtil;
import com.queplix.core.client.common.event.Event;
import com.queplix.core.client.common.event.EventListener;
import com.queplix.core.client.common.event.EventSource;
import com.queplix.core.client.common.ui.CrossBrowsersAbsolutePanel;
import com.queplix.core.client.common.ui.resizable.Resizable;
import com.queplix.core.client.common.ui.resizable.ResizableScrollPanel;

import java.util.ArrayList;

/**
 * DataGrid, that is able to display data in columns and provides
 * interactivity.
 *
 * @author Sergey Kozmin
 * @author Vasily Mikhailitchenko
 * @since <?>
 */
public class DataGrid extends Composite
        implements Resizable, SourcesGridSortEvents,
        EventListener {
    private static final int SPACE_BEFORE = 7;

    public static final int HEADER_FONT_WIDTH = 7;
    public static final int HEADER_SORT_ICON_WIDTH = 28;

    private GridElementsStrategy strategy = (GridElementsStrategyImpl) GWT
            .create(GridElementsStrategyImpl.class);

    private GridModel model;
    private boolean inPopup;

    private HeaderGrid headerGrid;
    private BaseInternalDataGrid dataGrid;
    private VerticalPanel panel;
    private FocusPanel mainPanel;
    private CrossBrowsersAbsolutePanel headerPanel;
    private ResizableScrollPanel dataGridScrollPanel;//todo move it to internal data grid

    private ArrayList gridListeners = new ArrayList();
    private GridSortListenerCollection sortListeners
            = new GridSortListenerCollection();

    /**
     * Constructor
     *
     * @param model                 grid's model
     * @param view                  grid's view
     * @param inPopup               if client code is going to put the grid in a popup or dialog;
     * @param sendConfirmationEvent should selection be done by internal logic,
     *                              or should to_be_selected event be send instead.
     */
    public DataGrid(GridModel model, GridView view, boolean inPopup,
                    boolean sendConfirmationEvent) {
        this.model = model;

        dataGrid = InternalGridFactory.createInternalGrid(view, strategy,
                gridListeners, true, sendConfirmationEvent);
        headerGrid = new HeaderGrid(model, view, this, strategy, sortListeners);

        dataGrid.getEventSource().addEventListener(this);
        headerGrid.getEventSource().addEventListener(this);

        dataGrid.setHeaderGrid(headerGrid);
        headerGrid.setInternalDataGrid(dataGrid);

        headerGrid.setStyleName(view.getMainGridStyleName());
        dataGrid.setStyleName(view.getMainGridStyleName());
        panel = new VerticalPanel();
        mainPanel = new FocusPanel();
        headerPanel = new CrossBrowsersAbsolutePanel();
        headerPanel.add(headerGrid);

        this.inPopup = inPopup;

        ScrollListener inPopupScrollListener = new ScrollListener() {
            public void onScroll(Widget widget, int scrollLeft, int scrollTop) {
                headerPanel.setWidgetPosition(headerGrid, 0, scrollTop);
            }
        };

        ScrollListener standaloneScrollListener = new ScrollListener() {
            public void onScroll(Widget widget, int scrollLeft, int scrollTop) {
                headerPanel.setWidgetPosition(headerGrid, -scrollLeft, 0);
            }
        };

        if(this.inPopup) {
            headerPanel.add(dataGrid);
            dataGridScrollPanel = new ResizableScrollPanel(headerPanel);
            dataGridScrollPanel.addScrollListener(inPopupScrollListener);
        } else {
            dataGridScrollPanel = new ResizableScrollPanel(dataGrid);
            dataGridScrollPanel.addScrollListener(standaloneScrollListener);
            panel.add(headerPanel);
        }

        panel.add(dataGridScrollPanel);
        mainPanel.setWidget(panel);
        initWidget(mainPanel);

        headerGrid.setupWidgets(model.getDataGridMeta());
        dataGrid.setupWidgets(model);
        subscribeEvents();

        mainPanel.addClickListener(new ClickListener() {
            public void onClick(Widget sender) {
                // Have to set focus explicitly (IE workaround)
                mainPanel.setFocus(true);
            }
        });

        mainPanel.addFocusListener(new FocusListener() {
            public void onFocus(Widget sender) {
                dataGrid.setFocus(true);
            }

            public void onLostFocus(Widget sender) {
                dataGrid.setFocus(false);
            }
        });
    }


    public void setModel(GridModel model) {
        this.model = model;
        dataGrid.setupWidgets(model);
        headerGrid.setupWidgets(model.getDataGridMeta());
        subscribeEvents();
    }

    public void adjustHeaderPosition() {
        headerPanel.setWidgetPosition(headerGrid,
                -dataGridScrollPanel.getHorizontalScrollPosition(), 0);
    }

    /**
     * Scroll the scrollPanel up to the specified row
     *
     * @param row to scroll up to
     */
    public void scrollGridToRow(int row) {
        int rowHeight = dataGrid.getOffsetHeight() / dataGrid.getRowCount();
        int firstVisibleRow = dataGridScrollPanel.getScrollPosition()
                / rowHeight;
        int numOfRowsVisible = dataGridScrollPanel.getOffsetHeight()
                / rowHeight;
        int lastVisibleRow = firstVisibleRow + numOfRowsVisible;

        int scrollDiff = 0;
        if(lastVisibleRow < (row + 1)) {
            scrollDiff = row - lastVisibleRow + 1;
        } else if(firstVisibleRow > (row - 1)) {
            scrollDiff = row - firstVisibleRow - 1;
        }
        if(scrollDiff != 0) {
            dataGridScrollPanel.setScrollPosition(
                    dataGridScrollPanel.getScrollPosition()
                            + scrollDiff * rowHeight);
        }
    }

    private void subscribeEvents() {
        model.addGridModelListener(new GridModelListener() {
            public void gridStructureChanged() {
                headerGrid.setupWidgets(model.getDataGridMeta());
                dataGrid.setupWidgets(model);
            }

            public void gridDataChanged() {
                headerGrid.setupWidgets(model.getDataGridMeta());
                dataGrid.setupWidgets(model);
            }

            public void gridRowDataChanged(int row) {
                dataGrid.setupRow(model, row);
            }

            public void gridRowsWereInserted() {
                int oldRowCount = dataGrid.getRowCount();
                int newRowCount = model.getModelRowCount();
                if(newRowCount > oldRowCount) {
                    dataGrid.setupWidgets(model);
                }
            }
        });
    }

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

    public SelectionController getSelectionController() {
        return dataGrid;
    }

    /**
     * Set the column selected. Deselect column
     *
     * @param column the column
     */
    public void setSelectedColumn(int column) {
        headerGrid.setSelectedColumn(column);
    }

    public int getRowCount() {
        return dataGrid.getRowCount();
    }

    public void addGridListener(GridSelectionListener listener) {
        gridListeners.add(listener);
    }

    public void removeGridListener(GridSelectionListener listener) {
        gridListeners.remove(listener);
    }

    public void addGridSortListener(GridSortListener listener) {
        sortListeners.add(listener);
    }

    public void removeGridSortListener(GridSortListener listener) {
        sortListeners.remove(listener);
    }

    public static int calcColumnWidth(int dataLength) {
        double length = (double) dataLength;
        if(dataLength < 17) {
            return (int) Math.round(
                    length * (HEADER_FONT_WIDTH + 2 * (length / 100)));
        }
        return (int) Math.round(
                length * (HEADER_FONT_WIDTH - 0.4 * (length / 100)));
    }

    public static int calcHeaderColumnWidth(int captionLength) {
        return calcColumnWidth(captionLength) + HEADER_SORT_ICON_WIDTH;
    }

    public void onEvent(Event event, Widget sender) {
        if(event == Events.SCROLL_TO_ROW) {
            Integer value = (Integer) Events.SCROLL_TO_ROW.getData();
            scrollGridToRow(value.intValue());
        } else {
            getEventSource().fireEvent(event);
        }
    }

    public void setSortingEnabled(boolean enabled) {
        headerGrid.setSortingEnabled(enabled);
    }

    // -------------------- public events ------------------------
    public static interface Events
            extends BaseInternalDataGrid.Events, HeaderGrid.Events {
        //todo implement
    }

    private EventSource eventSource = new EventSource(this);

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

    // ======================== Resizable implementation =====================
    private int offsetHeight;
    private int offsetWidth;

    public void setOffsetHeight(int height) {
        if((height < 0) || (offsetHeight == height)) {
            return; // invalid or unchanged value
        }
        offsetHeight = height;
        if(isAttached()) {
            doSetOffsetHeight();
        }
    }

    public void setOffsetWidth(int width) {
        if((width < 0) || (offsetWidth == width)) {
            return; // invalid or unchanged value
        }
        offsetWidth = width;
        if(isAttached()) {
            doSetOffsetWidth();
        }
    }

    private void doSetOffsetHeight() {
        int headerHeight = headerGrid.getOffsetHeight();
        dataGridScrollPanel.setOffsetHeight(
                offsetHeight - (inPopup ? 0:headerHeight));
        offsetHeight = dataGridScrollPanel.getOffsetHeight() + (inPopup ? 0
                :headerHeight);
        panel.setHeight(StringUtil.pixelToSize(offsetHeight));
    }

    private void doSetOffsetWidth() {
        headerPanel.setWidgetPosition(headerGrid, 0, 0);

        if(inPopup) {
            mainPanel.setWidth(offsetWidth - DataGrid.SPACE_BEFORE + "px");
        } else {
            mainPanel.setWidth(offsetWidth - DataGrid.SPACE_BEFORE + "px");
            headerPanel.setWidth(offsetWidth - DataGrid.SPACE_BEFORE + "px");
        }

        dataGridScrollPanel.setOffsetWidth(offsetWidth - DataGrid.SPACE_BEFORE);
        offsetWidth = dataGridScrollPanel.getOffsetWidth();

        panel.setWidth(StringUtil.pixelToSize(offsetWidth));
    }

    protected void onAttach() {
        super.onAttach();
        if(offsetHeight > 0) { // offsetHeight was set
            doSetOffsetHeight();
        }
        if(offsetWidth > 0) { // offsetWidth was set
            doSetOffsetWidth();
        }
    }

    public int getOffsetWidth() {
        int retValue = super.getOffsetWidth();
        retValue = (retValue != 0) ? retValue:offsetWidth;
        return retValue;
    }

    public int getOffsetHeight() {
        int retValue = super.getOffsetHeight();
        retValue = (retValue != 0) ? retValue:offsetHeight;
        return retValue;
    }

    public void setHeight(String height) {
        setOffsetHeight(StringUtil.sizeToPixel(height));
    }

    public void setWidth(String width) {
        setOffsetWidth(StringUtil.sizeToPixel(width));
    }
    // ===================== End of Resizable implementation ==================
}
