/*
 * Copyright (c) 2008-2016 Haulmont.
 *
 * 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 com.haulmont.cuba.desktop.sys.vcl;

import com.haulmont.cuba.desktop.gui.components.DesktopComponentsHelper;
//import sun.awt.CausedFocusEvent;

import javax.swing.*;
import javax.swing.event.ChangeEvent;
import java.awt.*;
import java.awt.event.FocusEvent;
import java.awt.event.KeyEvent;
import java.util.Set;

/**
 * <p>Handle focus traversing in table</p>
 * <p>Supports focus forward, backward, up and down navigation</p>
 * Keys:
 * <ul>
 * <li>TAB - next cell/control</li>
 * <li>SHIFT+TAB - previous cell/control</li>
 * <li>CTRL+TAB - next component after table</li>
 * <li>CTRL+SHIFT+TAB - previous component before table</li>
 * </ul>
 */
public class TableFocusManager {

    protected JTable impl;

    public TableFocusManager(JTable impl) {
        this.impl = impl;
    }

    public boolean isDisabledKeys(KeyEvent e) {
        return (e.getModifiers() & KeyEvent.CTRL_MASK) > 0 && (e.getModifiers() & KeyEvent.SHIFT_MASK) > 0;
    }

    public boolean processKeyBinding(KeyStroke ks, KeyEvent e, int condition, boolean pressed) {
        Set<AWTKeyStroke> forwardKeys = KeyboardFocusManager.getCurrentKeyboardFocusManager().getDefaultFocusTraversalKeys(
                KeyboardFocusManager.FORWARD_TRAVERSAL_KEYS);
        Set<AWTKeyStroke> backwardKeys = KeyboardFocusManager.getCurrentKeyboardFocusManager().getDefaultFocusTraversalKeys(
                KeyboardFocusManager.BACKWARD_TRAVERSAL_KEYS);
        if (forwardKeys.contains(ks)) {
            if ((e.getModifiers() & KeyEvent.CTRL_MASK) > 0)
                moveFocusToNextControl();
            else
                nextFocusElement();
            return true;
        } else if (backwardKeys.contains(ks)) {
            if ((e.getModifiers() & KeyEvent.CTRL_MASK) > 0)
                moveFocusToPrevControl();
            else
                prevFocusElement();
            return true;
        } else if (e.getModifiers() == 0) {
//            if (e.getKeyCode() == KeyEvent.VK_DOWN || e.getKeyCode() == KeyEvent.VK_UP ) {
//                impl.getSelectionModel().setValueIsAdjusting(pressed);
//            }

            return processExtraKeyBinding(ks, e, condition, pressed);
        }

        return false;
    }

    protected boolean processExtraKeyBinding(KeyStroke ks, KeyEvent e, int condition, boolean pressed) {
        if (e.getKeyCode() == KeyEvent.VK_UP && pressed) {
            nextUpElement();
            return true;
        } else if (e.getKeyCode() == KeyEvent.VK_DOWN && pressed) {
            nextDownElement();
            return true;
        } else if (e.getKeyCode() == KeyEvent.VK_LEFT && pressed) {
            prevFocusElement();
            return true;
        } else if (e.getKeyCode() == KeyEvent.VK_RIGHT && pressed) {
            nextFocusElement();
            return true;
        } else if (e.getKeyCode() == KeyEvent.VK_ESCAPE && pressed) {
            requestFocusIfWindowActive(impl);
            impl.editingCanceled(new ChangeEvent(this));
            // allow handle ESCAPE in window
            return false;
        } else {
            return false;
        }
    }

    public void processFocusEvent(FocusEvent e) {
//TODO: CUBA 7 Rework TableFocusManager for Java 9
//https://github.com/cuba-platform/cuba/issues/893
//        if (e.getID() == FocusEvent.FOCUS_GAINED) {
//            if (e instanceof CausedFocusEvent) {
//                if (((CausedFocusEvent) e).getCause() == CausedFocusEvent.Cause.TRAVERSAL_FORWARD) {
//                    if (impl.getModel().getRowCount() > 0) {
//                        // if focus from cell editor
//                        if (e.getSource() == impl && impl.getSelectedRow() >= 0) {
//                            int selectedColumn = impl.getSelectedColumn();
//                            focusTo(impl.getSelectedRow(), selectedColumn >= 0 ? selectedColumn : 0);
//                        } else
//                            moveToStart(0, 0);
//                    } else
//                        impl.transferFocus();
//
//                } else if (((CausedFocusEvent) e).getCause() == CausedFocusEvent.Cause.TRAVERSAL_BACKWARD) {
//                    if (impl.getModel().getRowCount() > 0) {
//                        moveToEnd(impl.getRowCount() - 1, impl.getColumnCount() - 1);
//                    } else
//                        impl.transferFocusBackward();
//                }
//            }
//        }
    }

    /**
     * Navigate down throw Table rows
     */
    protected void nextDownElement() {
        int editingColumn = getActiveColumn();
        int editingRow = getActiveRow();
        int nextRow = editingRow + 1;
        if (editingRow == -1) {
            return;
        }
        if (editingColumn == -1) {
            editingColumn = 0;
        }
        if (nextRow > impl.getRowCount() - 1) {
            nextRow = 0;
        }
        moveToStart(nextRow, editingColumn);
    }

    /**
     * Navigate up throw Table rows
     */
    protected void nextUpElement() {
        int editingColumn = getActiveColumn();
        int editingRow = getActiveRow();
        int nextRow = editingRow - 1;
        if (editingRow == -1) {
            return;
        }
        if (editingColumn == -1) {
            editingColumn = 0;
        }
        if (nextRow == -1) {
            nextRow = impl.getRowCount() - 1;
        }
        moveToStart(nextRow, editingColumn);
    }

    /**
     * Navigate to prev active control or cell in table
     */
    public void prevFocusElement() {
        int selectedColumn = getActiveColumn();
        int selectedRow = getActiveRow();
        int prevColumn = selectedColumn - 1;
        int prevRow = selectedRow;
        if (selectedColumn == -1) {
            selectedColumn = 0;
        }

        if (selectedRow == -1) {
            if (impl.getModel().getRowCount() > 0) {
                moveToEnd(impl.getRowCount() - 1, impl.getColumnCount() - 1);
            } else
                moveFocusToPrevControl();
            return;
        }

        if (selectedColumn == 0) {
            prevColumn = impl.getColumnCount() - 1;
            prevRow = selectedRow - 1;
        }

        JComponent activeComponent = getActiveComponent();
        boolean wasMoved = false;
        if (activeComponent != null) {
            wasMoved = moveFocusPrevIntoComponent(activeComponent);
        }

        if (!wasMoved) {
            if (prevRow < 0)
                impl.transferFocusBackward();
            else
                moveToEnd(prevRow, prevColumn);
        }
    }

    /**
     * Navigate to previous active control or cell in table
     */
    public void nextFocusElement() {
        int selectedColumn = getActiveColumn();
        int selectedRow = getActiveRow();
        int nextColumn = selectedColumn + 1;
        int nextRow = selectedRow;
        if (selectedColumn == -1) {
            selectedColumn = 0;
        }

        if (selectedRow == -1) {
            if (impl.getModel().getRowCount() > 0) {
                moveToStart(0, 0);
            } else
                moveFocusToNextControl();

            return;
        }
        if (selectedColumn == impl.getColumnCount() - 1) {
            nextColumn = 0;
            nextRow = selectedRow + 1;
        }

        JComponent activeComponent = getActiveComponent();
        boolean wasMoved = false;
        if (activeComponent != null) {
            wasMoved = moveFocusNextIntoComponent(activeComponent);
        }

        if (!wasMoved) {
            if (nextRow > impl.getRowCount() - 1)
                impl.transferFocus();
            else
                moveToStart(nextRow, nextColumn);
        }
    }

    /**
     * Focus first cell in specified row
     *
     * @param selectedRow Focused row
     */
    public void focusSelectedRow(int selectedRow) {
        if (impl.getModel().getRowCount() > 0) {
            focusTo(selectedRow, 0);
        } else {
            moveFocusToNextControl();
        }
    }

    /**
     * Scroll to first cell in specified row
     *
     * @param selectedRow row
     */
    public void scrollToSelectedRow(int selectedRow) {
        if (impl.getModel().getRowCount() > 0) {
            scrollTo(selectedRow, 0);
        }
    }

    protected void moveTo(int row, int col) {
        Component editorComp = impl.getEditorComponent();

        if (editorComp != null) {
            editorComp.dispatchEvent(new FocusEvent(editorComp, FocusEvent.FOCUS_LOST, false, impl));
        }
        impl.scrollRectToVisible(impl.getCellRect(row, col, true));

        if (row >= 0 && col >= 0)
            requestFocusIfWindowActive(impl);

        impl.getSelectionModel().setSelectionInterval(row, row);
        impl.getColumnModel().getSelectionModel().setSelectionInterval(col, col);
        impl.editCellAt(
                impl.getSelectedRow(),
                impl.getSelectedColumn()
        );
    }

    protected void focusTo(int row, int col) {
        if (row >= 0) {
            requestFocusIfWindowActive(impl);

            impl.getSelectionModel().setSelectionInterval(row, row);
            impl.getColumnModel().getSelectionModel().setSelectionInterval(col, col);

            scrollTo(row, col);
        }
    }

    protected void scrollTo(int row, int col) {
        if (row >= 0) {
            Rectangle cellRect = impl.getCellRect(row, col, true);
            impl.scrollRectToVisible(cellRect);
        }
    }

    protected void moveToStart(int row, int col) {
        moveTo(row, col);
        JComponent newEditorComp = (JComponent) impl.getEditorComponent();

        if (newEditorComp != null) {
            if (DesktopComponentsHelper.canRequestFocus(newEditorComp)) {
                newEditorComp.requestFocusInWindow();
            }
            KeyboardFocusManager focusManager = KeyboardFocusManager.getCurrentKeyboardFocusManager();
            FocusTraversalPolicy defaultFocusTraversalPolicy = focusManager.getDefaultFocusTraversalPolicy();
            Component component = defaultFocusTraversalPolicy.getFirstComponent(newEditorComp);

            if (component != null) {
                requestFocusIfWindowActive(component);
            }
        }
    }

    protected void moveToEnd(int row, int col) {
        moveTo(row, col);
        JComponent newEditorComp = (JComponent) impl.getEditorComponent();

        if (newEditorComp != null) {
            newEditorComp.requestFocusInWindow();
            KeyboardFocusManager focusManager = KeyboardFocusManager.getCurrentKeyboardFocusManager();
            FocusTraversalPolicy defaultFocusTraversalPolicy = focusManager.getDefaultFocusTraversalPolicy();
            Component component = defaultFocusTraversalPolicy.getLastComponent(newEditorComp);

            if (component != null) {
                requestFocusIfWindowActive(component);
            }
        }
    }

    protected JComponent getActiveComponent() {
        return (JComponent) impl.getEditorComponent();
    }

    protected int getActiveColumn() {
        int editingColumn = impl.getEditingColumn();
        int selectedColumn = impl.getSelectedColumn();
        if (editingColumn < 0)
            return selectedColumn;
        else
            return editingColumn;
    }

    protected int getActiveRow() {
        int editingRow = impl.getEditingColumn() == -1 ? -1 : impl.getEditingRow();
        int selectedRow = impl.getSelectedRow();
        if (editingRow < 0)
            return selectedRow;
        else
            return editingRow;
    }

    protected boolean moveFocusNextIntoComponent(Container activeComponent) {
        KeyboardFocusManager focusManager = KeyboardFocusManager.getCurrentKeyboardFocusManager();
        Component focusOwner = focusManager.getFocusOwner();
        FocusTraversalPolicy defaultFocusTraversalPolicy = focusManager.getDefaultFocusTraversalPolicy();
        Component lastComponent = defaultFocusTraversalPolicy.getLastComponent(activeComponent);
        if (focusOwner != null &&
                lastComponent != null &&
                lastComponent != focusOwner) {
            if (focusOwner == impl) {
                Component component = defaultFocusTraversalPolicy.getFirstComponent(activeComponent);
                if (component != null)
                    requestFocusIfWindowActive(component);
                else
                    moveFocusToNextControl();
            } else {
                moveFocusToNextControl();
            }

            return true;
        }

        return false;
    }

    protected boolean moveFocusPrevIntoComponent(JComponent activeComponent) {
        KeyboardFocusManager focusManager = KeyboardFocusManager.getCurrentKeyboardFocusManager();
        Component focusOwner = focusManager.getFocusOwner();
        FocusTraversalPolicy defaultFocusTraversalPolicy = focusManager.getDefaultFocusTraversalPolicy();
        Component firstComponent = defaultFocusTraversalPolicy.getFirstComponent(activeComponent);
        if (focusOwner != null &&
                firstComponent != null &&
                firstComponent != focusOwner) {
            if (focusOwner == impl) {
                Component component = defaultFocusTraversalPolicy.getLastComponent(activeComponent);
                if (component != null)
                    component.requestFocus();
                else
                    moveFocusToPrevControl();
            } else
                moveFocusToPrevControl();

            return true;
        }

        return false;
    }

    protected void moveFocusToNextControl() {
        FocusHelper.moveFocusToNextControl();
    }

    protected void moveFocusToPrevControl() {
        FocusHelper.moveFocusToPrevControl();
    }

    protected void requestFocusIfWindowActive(Component impl) {
        if (DesktopComponentsHelper.canRequestFocus(impl)) {
            impl.requestFocus();
        }
    }
}