/*
 * 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.ui.KeyboardListener;

import java.util.ArrayList;
import java.util.List;

/**
 * Implements all of the default operations, needed for correct selection
 * functionality.
 *
 * @author Sergey Kozmin
 * @since 30.03.2007
 */
class SelectianableInternalGrid extends InternalDataGrid {
    private static final int NONE_SELECTED = -1;
    /**
     * Indicate last selected row in current data's grid. if it equals to {@link #NONE_SELECTED} it means none record selected.
     * It resets when new data is loaded.
     */
    private int selectedRow = NONE_SELECTED;

    private int highlightRow = NONE_SELECTED;
    /**
     * Is grid in highlight session, and what is the start of the session.
     */
    private int highlightSessionStart = NONE_SELECTED;

    private IntegerSet multiSelectedRows = new IntegerSet();

    /**
     * this class ignore all listeners, and simply implements all listener
     * actions by its own.
     *
     * @param view
     * @param strategy
     * @param gridListeners
     */
    protected SelectianableInternalGrid(GridView view,
                                        GridElementsStrategy strategy,
                                        ArrayList gridListeners) {
        super(view, strategy, new ArrayList());

    }

    public void setupWidgets(GridModel model) {
        //once data is updated we should clear selection data, because the
        // data can be changed, so we can select wrong record
        clearSelection();
        super.setupWidgets(model);
    }

    public void setupRow(GridModel model, int changedRow) {
        //once data is updated we should clear selection data, because the
        // data can be changed, so we can select wrong record
        setRowType(changedRow, NOT_SELECTED_ROW_TYPE, false);
        super.setupRow(model, changedRow);
    }

    protected void onCellClicked(int row, int column, int keyBoardModiriers) {
        switch(keyBoardModiriers) {
            case KeyboardListener.MODIFIER_SHIFT:
                if(selectedRow != NONE_SELECTED) {
                    selectIntervalWithShift(selectedRow, row);
                } else {
                    setRowType(row, SINGLE_SELECTED_ROW_TYPE, true);
                }
                break;
            case KeyboardListener.MODIFIER_CTRL:
                if(selectedRow != NONE_SELECTED && row != selectedRow) {
                    if(!multiSelectedRows.containsValue(row)) {
                        setRowType(row, MULTIPLE_SELECTED_ROWS_TYPE);
                    } else {
                        setRowType(row, NOT_SELECTED_ROW_TYPE);
                    }
                } else {
                    setRowType(row, SINGLE_SELECTED_ROW_TYPE, true);
                }
                break;
            default: {
                selectSingleRow(row);
                break;
            }
        }
    }

    private void selectIntervalWithShift(int intervalStart, int intervalEnd) {
        setRowsType(getSelectedRows(MULTIPLE_SELECTED_ROWS_TYPE),
                NOT_SELECTED_ROW_TYPE);
        setRowsType(Math.min(intervalStart, intervalEnd),
                Math.max(intervalStart, intervalEnd) + 1,
                MULTIPLE_SELECTED_ROWS_TYPE);
    }

    private void selectSingleRow(int row) {
        setRowType(row, SINGLE_SELECTED_ROW_TYPE, true);
    }

    private void scrollGridToRow(int row) {
        Events.SCROLL_TO_ROW.setData(new Integer(row));
        getEventSource().fireEvent(Events.SCROLL_TO_ROW);
    }

    public void onKeyDown(char keyCode, int keyboardModifiers) {
        switch(keyCode) {
            case KeyboardListener.KEY_UP:
                moveUp(keyboardModifiers == KeyboardListener.MODIFIER_SHIFT);
                break;
            case KeyboardListener.KEY_DOWN:
                moveDown(keyboardModifiers == KeyboardListener.MODIFIER_SHIFT);
                break;
            case KeyboardListener.KEY_ENTER:
                selectSingleRow(highlightRow);
                break;
            case KeyboardListener.KEY_DELETE:
                getEventSource().fireEvent(Events.DELETE_KEY_PRESSED);
                break;
        }
    }

    private void moveDown(boolean selectRecs) {
        shiftHiglighting(1, selectRecs);
    }

    private void moveUp(boolean selectRecs) {
        shiftHiglighting(-1, selectRecs);
    }

    private void shiftHiglighting(int step, boolean selectRecs) {
        int currentHighlightRow = highlightRow;//in case of re-writing
        if(currentHighlightRow != NONE_SELECTED) {
            int newHightlight = currentHighlightRow + step;
            if(newHightlight >= 0 && newHightlight < getRowCount()) {
                if(selectRecs) {
                    if(highlightSessionStart == NONE_SELECTED) {
                        highlightSessionStart = currentHighlightRow;
                    }
                    selectIntervalWithShift(highlightSessionStart,
                            newHightlight);
                } else {
                    highlightSessionStart = NONE_SELECTED;
                    setRowsType(getSelectedRows(MULTIPLE_SELECTED_ROWS_TYPE),
                            NOT_SELECTED_ROW_TYPE);
                    setRowType(newHightlight, MULTIPLE_SELECTED_ROWS_TYPE);
                }
                highlightRow = newHightlight;
                scrollGridToRow(highlightRow);
            }
        }
    }

    protected void onMousePositionChanged(int row, boolean in) {
        if(in) {
            setRowType(row, MOUSE_OVER_ROW_TYPE);
        } else {
            setRowType(row, MOUSE_OUT_ROW_TYPE);
        }
    }

    public void setRowType(int row, int selectionType) {
        setRowType(row, selectionType, false);
    }

    protected void setRowType(int row, int selectionType,
                              boolean generatePreSelectionEvents) {
        switch(selectionType) {
            case SINGLE_SELECTED_ROW_TYPE: {
                if(selectedRow != row && row != NONE_SELECTED) {
                    setRowSingleSelected(row, generatePreSelectionEvents);
                }
                break;
            }
            case MULTIPLE_SELECTED_ROWS_TYPE: {
                if(row != selectedRow) {//don't want to rewrite selected row
                    super.setRowType(row, selectionType);
                    multiSelectedRows.add(row);
                }
                break;
            }
            case NOT_SELECTED_ROW_TYPE: {
                super.setRowType(row, selectionType);
                if(selectedRow == row) {
                    selectedRow = NONE_SELECTED;
                }
                if(highlightRow == row) {
                    highlightRow = NONE_SELECTED;
                }
                multiSelectedRows.removeValue(row);
                break;
            }
            /*case CURSOR_SELECTED_ROW_TYPE: {
                if(selectedRow != row) {//don't want to re-write selected row
                    super.setRowType(row, selectionType);
                }
                highlightRow = row;
                break;
            }*/
            case MOUSE_OVER_ROW_TYPE: {
                if(!doesRowHasSelection(row)) {
                    //don't want to
                    // re-write selected and highlighted row
                    super.setRowType(row, selectionType);
                }
                break;
            }
            case MOUSE_OUT_ROW_TYPE: {
                if(!doesRowHasSelection(row)) {
                    //don't want to
                    // re-write selected and highlighted row
                    super.setRowType(row, selectionType);
                }
                break;
            }
        }
    }

    private boolean doesRowHasSelection(int row) {
        return selectedRow == row || highlightRow == row
                || multiSelectedRows.containsValue(row);
    }

    protected void setRowSingleSelected(int row, boolean generateEvents) {
        //deselect currently selected
        if(selectedRow != NONE_SELECTED) {
            super.setRowType(selectedRow, NOT_SELECTED_ROW_TYPE);
        }

        //clear multiselection
        setRowsType(getSelectedRows(MULTIPLE_SELECTED_ROWS_TYPE),
                NOT_SELECTED_ROW_TYPE);

        //select new
        super.setRowType(row, SINGLE_SELECTED_ROW_TYPE);
        selectedRow = row;
        highlightRow = row;

        Events.GRID_ROW_SELECTED.setData(new Integer(row));
        getEventSource().fireEvent(Events.GRID_ROW_SELECTED);
    }

    public void setRowsType(int startIndex, int endIndex, int selectionType) {
        for(int i = startIndex; i < endIndex; i++) {
            setRowType(i, selectionType);
        }
    }

    public void setRowsType(List rows, int selectionType) {
        for(int i = 0; i < rows.size(); i++) {
            Integer integ = (Integer) rows.get(i);
            setRowType(integ.intValue(), selectionType);
        }
    }


    public void clearSelection() {
        //clear view
        super.clearSelection();

        //clear model
        multiSelectedRows.clear();
        selectedRow = NONE_SELECTED;
        highlightRow = NONE_SELECTED;
    }

    public List getSelectedRows() {
        ArrayList selected = new ArrayList();
        selected.addAll(multiSelectedRows);
        if(selectedRow != NONE_SELECTED) {
            selected.add(new Integer(selectedRow));
        }
        return selected;
    }

    public List getSelectedRows(int selectionType) {
        ArrayList selected = new ArrayList();
        if(selectionType == SINGLE_SELECTED_ROW_TYPE) {
            if(selectedRow != NONE_SELECTED) {
                selected.add(new Integer(selectedRow));
            }
        } else if(selectionType == MULTIPLE_SELECTED_ROWS_TYPE) {
            selected.addAll(multiSelectedRows);
        } else if(selectionType == NOT_SELECTED_ROW_TYPE) {
            for(int i = 0; i < getRowCount(); i++) {
                if(!(multiSelectedRows.containsValue(i) || selectedRow == i)) {
                    selected.add(new Integer(i));
                }
            }
        }
        return selected;
    }
}
