package it.unibz.inf.ontop.protege.mapping;

/*
 * #%L
 * ontop-protege
 * %%
 * Copyright (C) 2009 - 2013 KRDB Research Centre. Free University of Bozen Bolzano.
 * %%
 * 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.
 * #L%
 */

import it.unibz.inf.ontop.shaded.com.google.common.collect.ImmutableList;
import it.unibz.inf.ontop.protege.core.*;
import it.unibz.inf.ontop.protege.mapping.worker.ValidationSwingWorker;
import it.unibz.inf.ontop.protege.utils.*;
import it.unibz.inf.ontop.utils.IDGenerator;
import org.protege.editor.owl.OWLEditorKit;

import javax.swing.*;
import javax.swing.event.*;
import java.awt.*;
import java.awt.event.*;
import java.util.List;

import static it.unibz.inf.ontop.protege.utils.DialogUtils.*;
import static java.awt.event.KeyEvent.*;

public class MappingManagerPanel extends JPanel implements OBDAModelManagerListener {

	private static final long serialVersionUID = -486013653814714526L;

	private final OBDAModelManager obdaModelManager;
	private final ColorSettings colorSettings;
	private final OWLEditorKit editorKit;
    private final MappingFilteredListModel model;
    private final JList<TriplesMap> mappingList;

    private final JCheckBox filterCheckbox;
    private final JTextField filterField;

    private static final int GAP = 2;

	public MappingManagerPanel(OWLEditorKit editorKit) {
        this.editorKit = editorKit;
        this.obdaModelManager = OBDAEditorKitSynchronizerPlugin.getOBDAModelManager(editorKit);
        this.colorSettings = OBDAEditorKitSynchronizerPlugin.getColorSettings(editorKit);

        setLayout(new BorderLayout());

        mappingList = new JList<>();
        mappingList.setCellRenderer(new MappingListRenderer(editorKit));
        mappingList.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
        add(new JScrollPane(mappingList,
                ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED,
                ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER), BorderLayout.CENTER);
        mappingList.addListSelectionListener(evt -> setActionEnabled());

        JPanel buttonsPanel = new JPanel(new GridBagLayout());

        buttonsPanel.add(getButton(newAction),
                new GridBagConstraints(0, 0, 1, 1, 0, 0,
                        GridBagConstraints.WEST, GridBagConstraints.NONE,
                        new Insets(GAP, GAP, GAP, GAP), 0, 0));

        buttonsPanel.add(getButton(removeAction),
                new GridBagConstraints(1, 0, 1, 1, 0, 0,
                        GridBagConstraints.WEST, GridBagConstraints.NONE,
                        new Insets(GAP, GAP, GAP, GAP), 0, 0));

        buttonsPanel.add(getButton(copyAction),
                new GridBagConstraints(2, 0, 1, 1, 0, 0,
                        GridBagConstraints.WEST, GridBagConstraints.NONE,
                        new Insets(GAP, GAP, GAP, GAP), 0, 0));

        buttonsPanel.add(getButton(validateAction),
                new GridBagConstraints(3, 0, 1, 1, 0, 0,
                        GridBagConstraints.WEST, GridBagConstraints.NONE,
                        new Insets(GAP, GAP, GAP, GAP), 0, 0));

        buttonsPanel.add(new JPanel(), // stretchable panel
                new GridBagConstraints(4, 0, 1, 1, 1, 0,
                        GridBagConstraints.WEST, GridBagConstraints.HORIZONTAL,
                        new Insets(GAP, GAP, GAP, GAP), 0, 0));

        buttonsPanel.add(getButton(selectAllAction),
                new GridBagConstraints(7, 0, 1, 1, 0, 0,
                        GridBagConstraints.EAST, GridBagConstraints.NONE,
                        new Insets(GAP, GAP, GAP, GAP), 0, 0));

        buttonsPanel.add(getButton(selectNoneAction),
                new GridBagConstraints(8, 0, 1, 1, 0, 0,
                        GridBagConstraints.EAST, GridBagConstraints.NONE,
                        new Insets(GAP, GAP, GAP, GAP), 0, 0));

        add(buttonsPanel, BorderLayout.NORTH);

        JPanel extraButtonsPanel = new JPanel(new GridBagLayout());

        JLabel mappingStatusLabel = new JLabel();
        extraButtonsPanel.add(mappingStatusLabel,
                new GridBagConstraints(0, 0, 1, 1, 0, 0,
                        GridBagConstraints.WEST, GridBagConstraints.NONE,
                        new Insets(GAP, GAP, GAP, GAP), 0, 0));

        extraButtonsPanel.add(new JLabel("Search (any of):"),
                new GridBagConstraints(2, 0, 1, 1, 0, 0,
                        GridBagConstraints.WEST, GridBagConstraints.NONE,
                        new Insets(GAP, 30, GAP, GAP), 0, 0));

        filterField = new JTextField();
        extraButtonsPanel.add(filterField,
                new GridBagConstraints(3, 0, 1, 1, 1, 0,
                        GridBagConstraints.WEST, GridBagConstraints.HORIZONTAL,
                        new Insets(GAP, GAP, GAP, GAP), 0, 0));
        filterField.getDocument().addDocumentListener((SimpleDocumentListener) e ->  applyFilter());

        filterCheckbox = new JCheckBox("Enable filter");
        filterCheckbox.setEnabled(false);
        filterCheckbox.addItemListener(evt -> applyFilter());
        extraButtonsPanel.add(filterCheckbox,
                new GridBagConstraints(4, 0, 1, 1, 0, 0,
                        GridBagConstraints.WEST, GridBagConstraints.NONE,
                        new Insets(GAP, GAP, GAP, GAP), 0, 0));

        filterField.addActionListener(evt -> filterCheckbox.setSelected(true));
        add(extraButtonsPanel, BorderLayout.SOUTH);

        JPopupMenu menu = new JPopupMenu();
        menu.add(getMenuItem("New triples map...", newAction));
        menu.add(getMenuItem("Remove triples maps", removeAction));
        menu.add(getMenuItem("Copy triples maps", copyAction));
        menu.add(getMenuItem(editAction));
        menu.addSeparator();
        menu.add(getMenuItem("Validate triples maps", validateAction));
        menu.add(getMenuItem(executeSQLAction));
        setUpPopUpMenu(mappingList, menu);
        // additional accelerators
        setUpAccelerator(mappingList, removeAction, KeyStroke.getKeyStroke(KeyEvent.VK_DELETE, 0));
        setUpAccelerator(mappingList, editAction, KeyStroke.getKeyStroke(KeyEvent.VK_SPACE, 0));

        setActionEnabled();

        mappingList.addMouseListener(new MouseAdapter() {
            @Override
            public void mouseClicked(MouseEvent e) {
                if (e.getClickCount() == 2)
                    editAction.actionPerformed(
                            new ActionEvent(mappingList, ActionEvent.ACTION_PERFORMED, null));
            }
        });

        model = new MappingFilteredListModel(obdaModelManager);
        model.addListDataListener(new ListDataListener() {
            @Override public void intervalRemoved(ListDataEvent e) { updateMappingSize(); }
            @Override public void intervalAdded(ListDataEvent e) { updateMappingSize(); }
            @Override public void contentsChanged(ListDataEvent e) { updateMappingSize(); }
            private void updateMappingSize() {
                mappingStatusLabel.setText("<html>Mapping size: <b>" +
                        obdaModelManager.getCurrentOBDAModel().getTriplesMapManager().size() + "</b></html>");
            }
        });
        mappingList.setModel(model);
        model.changed(obdaModelManager.getCurrentOBDAModel().getTriplesMapManager());
    }


    TriplesMapManagerListener getTriplesMapManagerListener() { return model; }


    private final OntopAbstractAction newAction = new OntopAbstractAction(
            "New...",
            "plus.png",
            "Create a new triples map",
            KeyStroke.getKeyStroke(KeyEvent.VK_INSERT, 0)) {
        @Override
        public void actionPerformed(ActionEvent e) {
            EditMappingDialog dialog = new EditMappingDialog(
                    obdaModelManager.getCurrentOBDAModel(),
                    colorSettings,
                    IDGenerator.getNextUniqueID("MAPID-"));
            DialogUtils.setLocationRelativeToProtegeAndOpen(editorKit, dialog);
        }
    };

    private final OntopAbstractAction removeAction = new OntopAbstractAction(
            "Remove",
            "minus.png",
            "Remove selected triples maps",
            KeyStroke.getKeyStroke(KeyEvent.VK_BACK_SPACE, 0)) {
        @Override
        public void actionPerformed(ActionEvent e) {
            if (!removeConfirm(mappingList.getSelectedValuesList()))
                return;

            TriplesMapManager triplesMapCollection = obdaModelManager.getCurrentOBDAModel().getTriplesMapManager();
            for (TriplesMap triplesMap : mappingList.getSelectedValuesList())
                triplesMapCollection.remove(triplesMap.getId());

            mappingList.clearSelection();
        }
    };

    private final OntopAbstractAction copyAction = new OntopAbstractAction(
            "Copy",
            "copy.png",
            "Make a duplicate copy of the selected triples maps (with fresh IDs)",
            null) {
        @Override
        public void actionPerformed(ActionEvent e) {
            if (!copyConfirm(mappingList.getSelectedValuesList()))
                return;

            TriplesMapManager triplesMapCollection = obdaModelManager.getCurrentOBDAModel().getTriplesMapManager();
            for (TriplesMap triplesMap : mappingList.getSelectedValuesList())
                triplesMapCollection.duplicate(triplesMap.getId());
        }
    };

    private final OntopAbstractAction editAction = new OntopAbstractAction("Edit triples map...",
            null, null, KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0)) {
        @Override
        public void actionPerformed(ActionEvent e) {
            EditMappingDialog dialog = new EditMappingDialog(
                    obdaModelManager.getCurrentOBDAModel(),
                    colorSettings,
                    mappingList.getSelectedValue());
            DialogUtils.setLocationRelativeToProtegeAndOpen(editorKit, dialog);
        }
    };

    private final OntopAbstractAction validateAction = new OntopAbstractAction(
            "Validate",
            "validate.png",
            "Validate selected triples maps",
            getKeyStrokeWithCtrlMask(VK_V)) {
        @Override
        public void actionPerformed(ActionEvent e) {
            ValidationSwingWorker worker = new ValidationSwingWorker(
                    MappingManagerPanel.this,
                    mappingList.getSelectedValuesList(),
                    obdaModelManager.getCurrentOBDAModel());
            worker.execute();
        }
    };

    private final OntopAbstractAction executeSQLAction = new OntopAbstractAction("Execute source SQL...",
            null, null, null) {
        @Override
        public void actionPerformed(ActionEvent e) {
            SQLQueryDialog dialog = new SQLQueryDialog(
                    obdaModelManager.getCurrentOBDAModel().getDataSource(),
                    mappingList.getSelectedValue().getSqlQuery());
            DialogUtils.setLocationRelativeToProtegeAndOpen(editorKit, dialog);
        }
    };


    private final OntopAbstractAction selectAllAction = new OntopAbstractAction("Select all",
            "select-all.png",
            "Select all triples maps",
            getKeyStrokeWithCtrlMask(VK_A)) {
        @Override
        public void actionPerformed(ActionEvent e) {
            mappingList.setSelectionInterval(0, mappingList.getModel().getSize() - 1);
        }
    };

    private final OntopAbstractAction selectNoneAction = new OntopAbstractAction("Select none",
            "select-none.png",
            "Deselect all triples maps",
            getKeyStrokeWithCtrlMask(VK_N)) {
        @Override
        public void actionPerformed(ActionEvent e) {
            mappingList.clearSelection();
        }
    };

    private void setActionEnabled() {
        List<TriplesMap> selectionList = mappingList.getSelectedValuesList();
        removeAction.setEnabled(!selectionList.isEmpty());
        copyAction.setEnabled(!selectionList.isEmpty());
        validateAction.setEnabled(!selectionList.isEmpty());

        editAction.setEnabled(selectionList.size() == 1);
        executeSQLAction.setEnabled(selectionList.size() == 1);
    }

    @Override
    public void activeOntologyChanged(OBDAModel obdaModel) {
        setFilter("");
        applyFilter(); // forced update
    }
    /**
     * any change of the filter will trigger the model update via processFilterAction()
     * @param filter
     */
    public void setFilter(String filter) {
        filterField.setText(filter);
    }

    private void applyFilter() {
        String filterText = filterField.getText().trim();
        filterCheckbox.setEnabled(!filterText.isEmpty());

        model.setFilter(filterCheckbox.isSelected() && filterCheckbox.isEnabled()
                ? getFilter(filterText)
                : ImmutableList.of());
    }

    private ImmutableList<String> getFilter(String filterText) {
        ImmutableList.Builder<String> builder = ImmutableList.builder();
        for (String c : filterText.split("\\s+")) {
            String filter = c.endsWith(";") ? c.substring(0, c.length() - 1).trim() : c;
            builder.add(filter);
        }
        return builder.build();
    }

    private boolean copyConfirm(List<TriplesMap> selection) {
        return DialogUtils.confirmation(this,
                "<html>This will create a <b>copy</b> of the selected <b>" + selection.size() +
                        " triples map" + (selection.size() == 1 ? "" : "s") + "</b>.<br><br>" +
                        "Do you wish to <b>continue</b>?<br></html>",
                "Copy triples maps confirmation");
    }

    private boolean removeConfirm(List<TriplesMap> selection) {
        return DialogUtils.confirmation(this,
                "<html>This will <b>remove</b> the selected <b>" + selection.size() +
                        " triples map" + (selection.size() == 1 ? "" : "s") + "</b>.<br><br>" +
                        "Do you wish to <b>continue</b>?.<br></html>",
                "Remove triples maps confirmation");
    }
}
