/*****************************************************************
 *   Licensed to the Apache Software Foundation (ASF) under one
 *  or more contributor license agreements.  See the NOTICE file
 *  distributed with this work for additional information
 *  regarding copyright ownership.  The ASF licenses this file
 *  to you 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
 *
 *    https://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.apache.cayenne.modeler.editor;

import org.apache.cayenne.configuration.DataChannelDescriptor;
import org.apache.cayenne.configuration.event.ProcedureEvent;
import org.apache.cayenne.configuration.event.ProcedureParameterEvent;
import org.apache.cayenne.configuration.event.ProcedureParameterListener;
import org.apache.cayenne.dba.TypesMapping;
import org.apache.cayenne.map.Procedure;
import org.apache.cayenne.map.ProcedureParameter;
import org.apache.cayenne.map.event.MapEvent;
import org.apache.cayenne.modeler.Application;
import org.apache.cayenne.modeler.ProjectController;
import org.apache.cayenne.modeler.action.ActionManager;
import org.apache.cayenne.modeler.action.CopyProcedureParameterAction;
import org.apache.cayenne.modeler.action.CreateProcedureParameterAction;
import org.apache.cayenne.modeler.action.CutProcedureParameterAction;
import org.apache.cayenne.modeler.action.PasteAction;
import org.apache.cayenne.modeler.action.RemoveProcedureParameterAction;
import org.apache.cayenne.modeler.event.ProcedureDisplayEvent;
import org.apache.cayenne.modeler.event.ProcedureDisplayListener;
import org.apache.cayenne.modeler.event.ProcedureParameterDisplayEvent;
import org.apache.cayenne.modeler.event.TablePopupHandler;
import org.apache.cayenne.modeler.pref.TableColumnPreferences;
import org.apache.cayenne.modeler.util.CayenneAction;
import org.apache.cayenne.modeler.util.CayenneCellEditor;
import org.apache.cayenne.modeler.util.CayenneTable;
import org.apache.cayenne.modeler.util.ModelerUtil;
import org.apache.cayenne.modeler.util.PanelFactory;
import org.apache.cayenne.modeler.util.UIUtil;
import org.apache.cayenne.modeler.util.combo.AutoCompletion;
import org.apache.cayenne.swing.components.LimitedTextField;
import org.apache.cayenne.swing.components.image.FilteredIconFactory;

import javax.swing.Icon;
import javax.swing.JButton;
import javax.swing.JComboBox;
import javax.swing.JMenuItem;
import javax.swing.JPanel;
import javax.swing.JPopupMenu;
import javax.swing.JToolBar;
import javax.swing.SwingConstants;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
import javax.swing.table.DefaultTableCellRenderer;
import javax.swing.table.TableColumn;
import java.awt.BorderLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.EventObject;
import java.util.List;

public class ProcedureParameterTab extends JPanel implements ProcedureParameterListener,
        ProcedureDisplayListener, ExistingSelectionProcessor, ActionListener {

    protected ProjectController eventController;

    protected CayenneTable table;
    protected TableColumnPreferences tablePreferences;
    protected JButton removeParameterButton;
    protected JButton moveUp;
    protected JButton moveDown;

    /**
     * By now popup menu items are made similar to toolbar button. (i.e. all
     * functionality is here) This should be probably refactored as Action.
     */
    protected JMenuItem removeParameterMenu;
    protected JMenuItem moveUpMenu;
    protected JMenuItem moveDownMenu;

    public ProcedureParameterTab(ProjectController eventController) {
        this.eventController = eventController;

        init();

        eventController.addProcedureDisplayListener(this);
        eventController.addProcedureParameterListener(this);

        table.getSelectionModel().addListSelectionListener(new ListSelectionListener() {

            public void valueChanged(ListSelectionEvent e) {
                processExistingSelection(e);
            }
        });

        moveDown.addActionListener(this);
        moveUp.addActionListener(this);

        moveDownMenu.addActionListener(this);
        moveUpMenu.addActionListener(this);
    }

    protected void init() {
        setLayout(new BorderLayout());

        JToolBar toolBar = new JToolBar();
        toolBar.setFloatable(false);

        ActionManager actionManager = Application.getInstance().getActionManager();
        toolBar.add(actionManager.getAction(CreateProcedureParameterAction.class).buildButton(1));
        removeParameterButton = actionManager.getAction(RemoveProcedureParameterAction.class).buildButton(3);
        toolBar.add(removeParameterButton);
        toolBar.addSeparator();

        Icon up = ModelerUtil.buildIcon("icon-up.png");
        Icon down = ModelerUtil.buildIcon("icon-down.png");

        moveUp = new CayenneAction.CayenneToolbarButton(null, 1);
        moveUp.setIcon(up);
        moveUp.setDisabledIcon(FilteredIconFactory.createDisabledIcon(up));
        moveUp.setToolTipText("Move Parameter Up");
        toolBar.add(moveUp);

        moveDown = new CayenneAction.CayenneToolbarButton(null, 3);
        moveDown.setIcon(down);
        moveDown.setDisabledIcon(FilteredIconFactory.createDisabledIcon(down));
        moveDown.setToolTipText("Move Parameter Down");
        toolBar.add(moveDown);

        toolBar.addSeparator();
        toolBar.add(actionManager.getAction(CutProcedureParameterAction.class).buildButton(1));
        toolBar.add(actionManager.getAction(CopyProcedureParameterAction.class).buildButton(2));
        toolBar.add(actionManager.getAction(PasteAction.class).buildButton(3));

        add(toolBar, BorderLayout.NORTH);

        // Create table with two columns and no rows.
        table = new CayenneTable();

        tablePreferences = new TableColumnPreferences(
                this.getClass(),
                "procedure/parameterTable");

        // Create and install a popup
        JPopupMenu popup = new JPopupMenu();

        removeParameterMenu = actionManager.getAction(
                RemoveProcedureParameterAction.class).buildMenu();

        popup.add(removeParameterMenu);
        popup.addSeparator();

        moveUpMenu = new JMenuItem("Move Parameter Up", up);
        moveDownMenu = new JMenuItem("Move Parameter Down", down);

        popup.add(moveUpMenu);
        popup.add(moveDownMenu);

        popup.addSeparator();
        popup.add(actionManager.getAction(CutProcedureParameterAction.class).buildMenu());
        popup
                .add(actionManager
                        .getAction(CopyProcedureParameterAction.class)
                        .buildMenu());
        popup.add(actionManager.getAction(PasteAction.class).buildMenu());

        TablePopupHandler.install(table, popup);

        add(PanelFactory.createTablePanel(table, null), BorderLayout.CENTER);

        actionManager.setupCutCopyPaste(
                table,
                CutProcedureParameterAction.class,
                CopyProcedureParameterAction.class);
    }

    public void processExistingSelection(EventObject e) {
        if (e instanceof ChangeEvent) {
            table.clearSelection();
        }

        ProcedureParameter[] parameters = new ProcedureParameter[0];
        boolean enableUp = false;
        boolean enableDown = false;
        boolean enableRemoveButton = false;

        int selectedRow = table.getSelectedRow();
        if (selectedRow >= 0) {
            enableRemoveButton = true;
            ProcedureParameterTableModel model = (ProcedureParameterTableModel) table.getModel();

            int[] sel = table.getSelectedRows();
            parameters = new ProcedureParameter[sel.length];

            for (int i = 0; i < sel.length; i++) {
                parameters[i] = model.getParameter(sel[i]);
            }

            if (sel.length == 1) {
                // scroll table
                UIUtil.scrollToSelectedRow(table);

                int rowCount = table.getRowCount();
                if (rowCount > 1) {
                    if (selectedRow > 0) {
                        enableUp = true;
                    }
                    if (selectedRow < (rowCount - 1)) {
                        enableDown = true;
                    }
                }
            }
        }

        removeParameterButton.setEnabled(enableRemoveButton);
        moveUp.setEnabled(enableUp);
        moveDown.setEnabled(enableDown);

        syncButtons();

        ProcedureParameterDisplayEvent ppde = new ProcedureParameterDisplayEvent(
                this,
                parameters,
                eventController.getCurrentProcedure(),
                eventController.getCurrentDataMap(),
                (DataChannelDescriptor) eventController.getProject().getRootNode());
        eventController.fireProcedureParameterDisplayEvent(ppde);
    }

    /**
     * Invoked when currently selected Procedure object is changed.
     */
    public void currentProcedureChanged(ProcedureDisplayEvent e) {
        Procedure procedure = e.getProcedure();
        if (procedure != null && e.isProcedureChanged()) {
            rebuildTable(procedure);
        }
    }

    /**
     * Selects a specified parameters.
     */
    public void selectParameters(ProcedureParameter[] parameters) {
        ModelerUtil.updateActions(
                parameters.length,
                RemoveProcedureParameterAction.class,
                CutProcedureParameterAction.class,
                CopyProcedureParameterAction.class
        );

        ProcedureParameterTableModel model = (ProcedureParameterTableModel) table.getModel();

        List<ProcedureParameter> listAttrs = model.getObjectList();
        int[] newSel = new int[parameters.length];

        for (int i = 0; i < parameters.length; i++) {
            newSel[i] = listAttrs.indexOf(parameters[i]);
        }

        table.select(newSel);
    }

    protected void rebuildTable(Procedure procedure) {
        ProcedureParameterTableModel model = new ProcedureParameterTableModel(
                procedure,
                eventController,
                this);

        table.setModel(model);
        table.setRowHeight(25);
        table.setRowMargin(3);

        // number column tweaking
        TableColumn numberColumn = table.getColumnModel()
                .getColumn(ProcedureParameterTableModel.PARAMETER_NUMBER);
        DefaultTableCellRenderer renderer = new DefaultTableCellRenderer();
        renderer.setHorizontalAlignment(SwingConstants.CENTER);
        numberColumn.setCellRenderer(renderer);

        TableColumn typesColumn = table.getColumnModel()
                .getColumn(ProcedureParameterTableModel.PARAMETER_TYPE);
        JComboBox typesEditor = Application.getWidgetFactory()
                .createComboBox(TypesMapping.getDatabaseTypes(), true);
        AutoCompletion.enable(typesEditor);
        typesColumn.setCellEditor(Application.getWidgetFactory()
                .createCellEditor(typesEditor));

        // direction column tweaking
        TableColumn directionColumn = table.getColumnModel()
                .getColumn(ProcedureParameterTableModel.PARAMETER_DIRECTION);
        JComboBox directionEditor = Application.getWidgetFactory()
                .createComboBox(ProcedureParameterTableModel.PARAMETER_DIRECTION_NAMES, false);
        directionEditor.setEditable(false);
        directionColumn.setCellEditor(new CayenneCellEditor(directionEditor));

        TableColumn precisionColumn = table.getColumnModel().getColumn(ProcedureParameterTableModel.PARAMETER_PRECISION);
        LimitedTextField limitedPrecisionField = new LimitedTextField(10);
        precisionColumn.setCellEditor(Application.getWidgetFactory().createCellEditor(limitedPrecisionField));

        TableColumn lengthColumn = table.getColumnModel().getColumn(ProcedureParameterTableModel.PARAMETER_LENGTH);
        LimitedTextField limitedLengthField = new LimitedTextField(10);
        lengthColumn.setCellEditor(Application.getWidgetFactory().createCellEditor(limitedLengthField));

        moveUp.setEnabled(false);
        moveDown.setEnabled(false);

        tablePreferences.bind(table, null, null, null);
    }

    public void procedureParameterAdded(ProcedureParameterEvent e) {
        rebuildTable(e.getParameter().getProcedure());
        table.select(e.getParameter());
    }

    public void procedureParameterChanged(ProcedureParameterEvent e) {
        table.select(e.getParameter());
    }

    public void procedureParameterRemoved(ProcedureParameterEvent e) {
        ProcedureParameterTableModel model = (ProcedureParameterTableModel) table.getModel();
        int ind = model.getObjectList().indexOf(e.getParameter());
        model.removeRow(e.getParameter());
        table.select(ind);
    }

    public void actionPerformed(ActionEvent e) {
        ProcedureParameterTableModel model = (ProcedureParameterTableModel) table.getModel();
        ProcedureParameter parameter = model.getParameter(table.getSelectedRow());

        int index = -1;

        if (e.getSource() == moveUp || e.getSource() == moveUpMenu) {
            index = model.moveRowUp(parameter);
        } else if (e.getSource() == moveDown || e.getSource() == moveDownMenu) {
            index = model.moveRowDown(parameter);
        }

        if (index >= 0) {
            table.select(index);

            // note that 'setCallParameters' is donw by copy internally
            parameter.getProcedure().setCallParameters(model.getObjectList());
            eventController.fireProcedureEvent(
                    new ProcedureEvent(this, parameter.getProcedure(), MapEvent.CHANGE)
            );
        }
    }

    /**
     * Synchronizes state of toolbar and popup menu buttons
     */
    private void syncButtons() {
        removeParameterMenu.setEnabled(removeParameterButton.isEnabled());
        moveUpMenu.setEnabled(moveUp.isEnabled());
        moveDownMenu.setEnabled(moveDown.isEnabled());
    }
}
