/*
 * Copyright 2006-2021 DLR, Germany
 * 
 * SPDX-License-Identifier: EPL-1.0
 * 
 * https://rcenvironment.de/
 */
 
package de.rcenvironment.core.gui.cluster.view.internal;

import java.io.IOException;
import java.util.Timer;
import java.util.TimerTask;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.jface.action.Action;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.jface.resource.ImageDescriptor;
import org.eclipse.jface.viewers.TableViewer;
import org.eclipse.jface.viewers.TableViewerColumn;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.BusyIndicator;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.events.SelectionListener;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.layout.RowData;
import org.eclipse.swt.layout.RowLayout;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Combo;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Menu;
import org.eclipse.swt.widgets.MenuItem;
import org.eclipse.swt.widgets.Table;
import org.eclipse.swt.widgets.TableColumn;
import org.eclipse.swt.widgets.Text;
import org.eclipse.ui.part.ViewPart;

import de.rcenvironment.core.gui.cluster.configuration.internal.ClusterConnectionConfigurationDialogsController;
import de.rcenvironment.core.gui.cluster.internal.ErrorMessageDialogFactory;
import de.rcenvironment.core.utils.cluster.ClusterService;
import de.rcenvironment.core.utils.common.StringUtils;

/**
 * View monitoring information about cluster jobs.
 * 
 * @author Doreen Seider
 */
public class ClusterJobMonitorView extends ViewPart {

    private static final String CONNECTING_TO_CLUSTER_FAILED = "Connecting to cluster failed";
    
    private static final String FETCHING_CLUSTER_JOB_INFORMATION_FAILED = "Fetching cluster job information failed";

    private static final int COLUMN_WIDTH = 120;

    private static final int COLUMN_WIDTH_JOBSTATE = 100;

    private static final int NO_SPACE = 0;

    private static final int PLATFORM_WIDTH = 250;

    private static final int TEXT_WIDTH = PLATFORM_WIDTH;
    
    private static final int DELAY = 30000;

    private static final Log LOGGER = LogFactory.getLog(ClusterJobMonitorView.class);

    private Button othersCheckbox;

    private Button runningCheckbox;

    private Button queuedCheckbox;

    private Combo connectedConfigurationNameCombo;

    private ClusterJobInformationTableFilter tableRowFilter;

    private ClusterJobInformationTableColumnSorter tableColumnSorter;

    private TableViewer jobInformationTableViewer;

    private Text searchText;
    
    private Composite parent;
   
    private Timer getUpdateTimer;
    
    private volatile boolean getUpdateTimerTaskScheduled = false;
    
    private final Action connectAction = new Action(Messages.connectToolTip, ImageDescriptor.createFromURL(
        ClusterJobMonitorView.class.getResource("/resources/icons/connect16.png"))) {

        @Override
        public void run() {
            final ClusterConnectionConfigurationDialogsController controller = new ClusterConnectionConfigurationDialogsController(parent);
            final ClusterConnectionInformation connectionInformation = controller.openClusterConnectionSelectionDialog();
            
            if (connectionInformation != null) {
                
                BusyIndicator.showWhile(jobInformationTableViewer.getTable().getDisplay(), new Runnable() {
                    
                    @Override
                    public void run() {
                        String clusterConfigurationName = controller.getClusterConfigurationName();
                        ClusterService clusterInformationService = controller.getClusterJobInformationService();
                        
                        if (updateModelForFirstTime(clusterConfigurationName, clusterInformationService, connectionInformation)) {
                            refreshView(clusterConfigurationName);                            
                            scheduleGetUpdateOfClusterJobInformationTimerIfNeeded();                            
                        }
                        
                    }
                });
                
            }
        }
    };
    
    private final Action disconnectAction = new Action(Messages.disconnectToolTip, ImageDescriptor.createFromURL(
        ClusterJobMonitorView.class.getResource("/resources/icons/disconnect16.png"))) {

        @Override
        public void run() {
            
            String clusterConfigurationName = connectedConfigurationNameCombo.getItem(connectedConfigurationNameCombo.getSelectionIndex());

            ClusterJobInformationModel model = ClusterJobInformationModel.getInstance();
            model.removeConnectedCluster(clusterConfigurationName);
            
            refreshConnectedClusterComboAndToolBar(null);
            
            clusterConfigurationName = connectedConfigurationNameCombo.getItem(connectedConfigurationNameCombo.getSelectionIndex());
            
            model.setSelectedConnectedConfigurationName(clusterConfigurationName);
            
            refreshClusterJobInformationTableViewer();

            cancelUpdateClusterJobInformationTimerIfNeeded();
        }
    };
    
    private final Action refreshAction = new Action(Messages.refreshToolTip, ImageDescriptor.createFromURL(
        ClusterJobMonitorView.class.getResource("/resources/icons/refresh.gif"))) {

        @Override
        public void run() {
            BusyIndicator.showWhile(jobInformationTableViewer.getTable().getDisplay(), new Runnable() {
                @Override
                public void run() {
                    getUpdateOfClusterJobInformation();
                    refreshClusterJobInformationTableViewer();
                }
            });
        }
    };
    
    private final Action informationAction = new Action(Messages.informationToolTip, ImageDescriptor.createFromURL(
        ClusterJobMonitorView.class.getResource("/resources/icons/information.gif"))) {

        @Override
        public void run() {
            ClusterJobInformationModel model = ClusterJobInformationModel.getInstance();
            String connectedConfigurationName = connectedConfigurationNameCombo
                .getItem(connectedConfigurationNameCombo.getSelectionIndex());
            ClusterConnectionInformation connectionInformation = model.getClusterConnectionInformation(connectedConfigurationName);
            MessageDialog dialog = new MessageDialog(parent.getShell(), Messages.informationDialogTitle, null,
                StringUtils.format(Messages.informationDialogMessage,
                    connectionInformation.getHost(), connectionInformation.getUsername(), connectionInformation.getConnectedDate(),
                    connectionInformation.getLastUpdateDate(),
                    connectionInformation.getUpdateInterval()),
                MessageDialog.INFORMATION, new String[] { Messages.ok }, 0);
            dialog.open();
        }
    };

    @Override
    public void createPartControl(Composite aParent) {
        this.parent = aParent;
        
        aParent.setLayout(new GridLayout(1, false));

        // filter = cluster selection, state selection, and search text field
        Composite filterComposite = new Composite(aParent, SWT.NONE);
        filterComposite.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false));
        filterComposite.setLayout(new RowLayout());

        createConnectedClusterListingArrangement(filterComposite);
        createLevelArrangement(filterComposite);
        createSearchArrangement(filterComposite);

        createTableArrangement(aParent);

        tableColumnSorter = new ClusterJobInformationTableColumnSorter();
        jobInformationTableViewer.setSorter(tableColumnSorter);

        tableRowFilter = new ClusterJobInformationTableFilter(this, jobInformationTableViewer);

        searchText.addKeyListener(tableRowFilter);

        queuedCheckbox.addSelectionListener(tableRowFilter);
        runningCheckbox.addSelectionListener(tableRowFilter);
        othersCheckbox.addSelectionListener(tableRowFilter);
        connectedConfigurationNameCombo.addSelectionListener(tableRowFilter);

        jobInformationTableViewer.addFilter(tableRowFilter);
        
        for (Action action : createToolbarActions()) {
            getViewSite().getActionBars().getToolBarManager().add(action);
        }
        
        String[] connectedConfigurationNames = ClusterJobInformationModel.getInstance().getConnectedConfigurationNames();
        boolean enabled = connectedConfigurationNames.length > 0
            && !connectedConfigurationNames[0].equals(ClusterJobInformationModel.NOT_CONNECTED);
        disconnectAction.setEnabled(enabled);
        refreshAction.setEnabled(enabled);
        informationAction.setEnabled(enabled);
        
    }

    @Override
    public void setFocus() {
        jobInformationTableViewer.getControl().setFocus();
        scheduleGetUpdateOfClusterJobInformationTimerIfNeeded();
    }
    
    @Override
    public void dispose() {
        cancelUpdateClusterJobInformationTimerIfNeeded();
    }
    
    public boolean getQueuedSelection() {
        return queuedCheckbox.getSelection();
    }

    public boolean getRunningSelection() {
        return runningCheckbox.getSelection();
    }

    public boolean getOthersSelection() {
        return othersCheckbox.getSelection();
    }

    public String getSelectedConnectedConfigurationName() {
        return connectedConfigurationNameCombo.getItem(connectedConfigurationNameCombo.getSelectionIndex());
    }

    public String getSearchText() {
        return searchText.getText();
    }
    
    private Action[] createToolbarActions() {
        return new Action[] {
            connectAction, disconnectAction, refreshAction, informationAction
        };
    }
    
    private void createLevelArrangement(Composite filterComposite) {
        RowLayout rowLayout = new RowLayout();
        rowLayout.spacing = NO_SPACE;
        filterComposite.setLayout(rowLayout);

        queuedCheckbox = new Button(filterComposite, SWT.CHECK);
        queuedCheckbox.setText(Messages.queuedFilter);
        queuedCheckbox.setSelection(true);

        runningCheckbox = new Button(filterComposite, SWT.CHECK);
        runningCheckbox.setText(Messages.runningFilter);
        runningCheckbox.setSelection(true);

        othersCheckbox = new Button(filterComposite, SWT.CHECK);
        othersCheckbox.setText(Messages.othersFilter);
        othersCheckbox.setSelection(true);

    }

    private void createConnectedClusterListingArrangement(Composite connectedClusterComposite) {
        RowLayout rowLayout = new RowLayout();
        rowLayout.spacing = NO_SPACE;
        connectedClusterComposite.setLayout(rowLayout);

        connectedConfigurationNameCombo = new Combo(connectedClusterComposite, SWT.DROP_DOWN | SWT.READ_ONLY);
        connectedConfigurationNameCombo.setLayoutData(new RowData(PLATFORM_WIDTH, SWT.DEFAULT));
        connectedConfigurationNameCombo.addSelectionListener(new SelectionListener() {
            
            @Override
            public void widgetSelected(SelectionEvent event) {
                ClusterJobInformationModel.getInstance().setSelectedConnectedConfigurationName(
                    connectedConfigurationNameCombo.getItem(connectedConfigurationNameCombo.getSelectionIndex()));
                refreshClusterJobInformationTableViewer();
            }
            
            @Override
            public void widgetDefaultSelected(SelectionEvent event) {
                widgetSelected(event);
            }
        });
        refreshConnectedClusterComboAndToolBar(null);
    }

    private void createSearchArrangement(Composite searchComposite) {
        RowLayout rowLayout = new RowLayout();
        rowLayout.spacing = 7;
        searchComposite.setLayout(rowLayout);

        searchText = new Text(searchComposite, SWT.SEARCH);
        searchText.setMessage(Messages.searchFilter);
        searchText.setSize(TEXT_WIDTH, SWT.DEFAULT);
        searchText.setLayoutData(new RowData(TEXT_WIDTH, SWT.DEFAULT));
    }

    private void createTableArrangement(Composite tableComposite) {
        tableComposite.setLayout(new GridLayout());

        jobInformationTableViewer = new TableViewer(tableComposite, SWT.H_SCROLL | SWT.V_SCROLL | SWT.FULL_SELECTION | SWT.MULTI);
        jobInformationTableViewer.getControl().setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));

        // create table header and column styles
        String[] columnTitles = new String[] {
            Messages.columnJobId,
            Messages.columnJobName,
            Messages.columnUser,
            Messages.columnQueue,
            Messages.columnRemainingTime,
            Messages.columnStartTime,
            Messages.columnQueueTime,
            Messages.columnJobState };
        int[] bounds = { COLUMN_WIDTH, COLUMN_WIDTH, COLUMN_WIDTH, COLUMN_WIDTH, COLUMN_WIDTH,
            COLUMN_WIDTH, COLUMN_WIDTH, COLUMN_WIDTH_JOBSTATE };

        for (int i = 0; i < columnTitles.length; i++) {
            // for all columns

            final int index = i;
            final TableViewerColumn viewerColumn = new TableViewerColumn(jobInformationTableViewer, SWT.NONE);
            final TableColumn column = viewerColumn.getColumn();

            // set column properties
            column.setText(columnTitles[i]);
            column.setWidth(bounds[i]);
            column.setResizable(true);
            column.setMoveable(true);

            column.addSelectionListener(new SelectionAdapter() {

                @Override
                public void widgetSelected(SelectionEvent e) {
                    tableColumnSorter.setColumn(index);
                    int direction = jobInformationTableViewer.getTable().getSortDirection();

                    if (jobInformationTableViewer.getTable().getSortColumn() == column) {
                        if (direction == SWT.UP) {
                            direction = SWT.DOWN;
                        } else {
                            direction = SWT.UP;
                        }
                    } else {
                        direction = SWT.UP;
                    }
                    jobInformationTableViewer.getTable().setSortDirection(direction);
                    jobInformationTableViewer.getTable().setSortColumn(column);

                    refreshClusterJobInformationTableViewer();
                }
            });
        }

        jobInformationTableViewer.setContentProvider(new ClusterJobInformationContentProvider());
        jobInformationTableViewer.setLabelProvider(new ClusterJobInformationLabelProvider());
        jobInformationTableViewer.setInput(ClusterJobInformationModel.getInstance());

        // set table layout data
        final Table table = jobInformationTableViewer.getTable();
        table.setHeaderVisible(true);
        table.setLinesVisible(true);
        table.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));

        Menu contextMenu = new Menu(tableComposite);
        final MenuItem killJobItem = new MenuItem(contextMenu, SWT.PUSH);
        killJobItem.setText(Messages.cancelJob);
        killJobItem.setEnabled(table.getSelectionCount() > 0);
        killJobItem.addSelectionListener(new KillClusterJobListenerListener(jobInformationTableViewer, refreshAction));
        jobInformationTableViewer.getControl().setMenu(contextMenu);

        table.addSelectionListener(new SelectionListener() {
            
            @Override
            public void widgetSelected(SelectionEvent event) {
                killJobItem.setEnabled(table.getSelectionCount() > 0);
            }
            
            @Override
            public void widgetDefaultSelected(SelectionEvent event) {
                widgetSelected(event);
            }
        });
        

    }
        
    private synchronized void getUpdateOfClusterJobInformation() {
        try {
            ClusterJobInformationModel.getInstance().getUpdateFromCluster();
        } catch (final IOException e) {
            jobInformationTableViewer.getTable().getDisplay().asyncExec(new Runnable() {

                @Override
                public void run() {
                    LOGGER.error(FETCHING_CLUSTER_JOB_INFORMATION_FAILED, e);
                    disconnectAction.run();
                    ErrorMessageDialogFactory.createMessageDialogForFetchingFailure(parent).open();
                }
            });
            
        }
    }
    
    private boolean updateModelForFirstTime(String connectedClusterConfigurationName,  ClusterService clusterInformationService,
        ClusterConnectionInformation connectionInformation) {
        ClusterJobInformationModel model = ClusterJobInformationModel.getInstance();
        model.addConnectedCluster(connectedClusterConfigurationName, clusterInformationService);
        final int convertToSecondsQuotient = 1000;
        connectionInformation.setUpdateInterval(ClusterService.FETCH_INTERVAL / convertToSecondsQuotient);
        model.addClusterConnectionInformation(connectedClusterConfigurationName, connectionInformation);
        model.setSelectedConnectedConfigurationName(connectedClusterConfigurationName);
        try {
            model.getUpdateFromCluster();
        } catch (IOException e) {
            ErrorMessageDialogFactory.createMessageDialogForConnectionFailure(parent, e).open();
            LOGGER.error(CONNECTING_TO_CLUSTER_FAILED, e);
            model.removeConnectedCluster(connectedClusterConfigurationName);
            return false;
        }
        return true;
    }

    private void refreshView(String connectedClusterConfigurationName) {
        refreshConnectedClusterComboAndToolBar(connectedClusterConfigurationName);
        refreshClusterJobInformationTableViewer();
    }
    
    private void refreshConnectedClusterComboAndToolBar(String connectedClusterConfigurationName) {
        connectedConfigurationNameCombo.setItems(ClusterJobInformationModel.getInstance().getConnectedConfigurationNames());
        
        if (connectedClusterConfigurationName != null && !connectedClusterConfigurationName.isEmpty()) {
            connectedConfigurationNameCombo.select(connectedConfigurationNameCombo.indexOf(connectedClusterConfigurationName));   
            disconnectAction.setEnabled(true);
            refreshAction.setEnabled(true);
            informationAction.setEnabled(true);
        } else {
            connectedConfigurationNameCombo.select(0);
            if (!isConnectedToAtLeastOneCluster()) {
                disconnectAction.setEnabled(false);
                refreshAction.setEnabled(false);
                informationAction.setEnabled(false);
            }
        }
    }

    private void refreshClusterJobInformationTableViewer() {
        jobInformationTableViewer.getTable().clearAll();
        jobInformationTableViewer.refresh();
    }
    
    private void scheduleGetUpdateOfClusterJobInformationTimerIfNeeded() {
        
        if (!getUpdateTimerTaskScheduled && isConnectedToAtLeastOneCluster()) {
            TimerTask getUpdateTimerTask = new TimerTask() {
                
                @Override
                public void run() {
                    Job job = new Job(Messages.updateJobTitle) {
                        @Override
                        protected IStatus run(IProgressMonitor monitor) {
                            try {
                                monitor.beginTask(Messages.updateJobMessage, 3);
                                monitor.worked(1);
                                getUpdateOfClusterJobInformation();
                                monitor.worked(2);
                                if (!jobInformationTableViewer.getTable().isDisposed()) {
                                    jobInformationTableViewer.getTable().getDisplay().asyncExec(new Runnable() {

                                        @Override
                                        public void run() {
                                            refreshClusterJobInformationTableViewer();
                                        }
                                    });                                    
                                }
                            } finally {
                                monitor.done();
                            }
                            return Status.OK_STATUS;
                        };
                    };
                    job.setUser(false);
                    job.schedule();
                    
                }
            };

            getUpdateTimer = new Timer("Get Update (Cluster Job Information) Timer", true);
            getUpdateTimer.schedule(getUpdateTimerTask, DELAY, ClusterService.FETCH_INTERVAL);
            getUpdateTimerTaskScheduled = true;
        }
    }
    
    private void cancelUpdateClusterJobInformationTimerIfNeeded() {
        if (getUpdateTimerTaskScheduled && !isConnectedToAtLeastOneCluster()) {
            getUpdateTimer.cancel();
            getUpdateTimerTaskScheduled = false;
        }
    }
    
    private boolean isConnectedToAtLeastOneCluster() {
        return !connectedConfigurationNameCombo.getItem(connectedConfigurationNameCombo.getSelectionIndex())
            .equals(ClusterJobInformationModel.NOT_CONNECTED);
    }
    
}
