/*******************************************************************************
 * Copyright (c) 2017 Pegasystems Inc. All rights reserved.
 *
 * Contributors:
 *     Manu Varghese
 *******************************************************************************/

package com.pega.gcs.tracerviewer;

import java.awt.CardLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Insets;
import java.awt.LayoutManager;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.io.File;
import java.util.HashMap;
import java.util.List;

import javax.swing.BorderFactory;
import javax.swing.Box;
import javax.swing.BoxLayout;
import javax.swing.JButton;
import javax.swing.JComboBox;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.UIManager;
import javax.swing.event.TableModelEvent;
import javax.swing.event.TableModelListener;

import com.pega.gcs.fringecommon.guiutilities.BaseFrame;
import com.pega.gcs.fringecommon.guiutilities.GoToLineDialog;
import com.pega.gcs.fringecommon.guiutilities.Message;
import com.pega.gcs.fringecommon.guiutilities.Message.MessageType;
import com.pega.gcs.fringecommon.guiutilities.ModalProgressMonitor;
import com.pega.gcs.fringecommon.guiutilities.RecentFile;
import com.pega.gcs.fringecommon.guiutilities.RecentFileContainer;
import com.pega.gcs.fringecommon.guiutilities.bookmark.BookmarkContainer;
import com.pega.gcs.fringecommon.guiutilities.bookmark.BookmarkModel;
import com.pega.gcs.fringecommon.guiutilities.search.SearchData;
import com.pega.gcs.fringecommon.log4j2.Log4j2Helper;
import com.pega.gcs.fringecommon.utilities.GeneralUtilities;
import com.pega.gcs.tracerviewer.model.TraceEventKey;
import com.pega.gcs.tracerviewer.report.TracerSimpleReportFrame;
import com.pega.gcs.tracerviewer.view.TracerDataCompareTableView;
import com.pega.gcs.tracerviewer.view.TracerDataSingleTableView;
import com.pega.gcs.tracerviewer.view.TracerDataTreeMergedTableView;
import com.pega.gcs.tracerviewer.view.TracerDataTreeTableView;
import com.pega.gcs.tracerviewer.view.TracerDataView;
import com.pega.gcs.tracerviewer.view.TracerDataViewMode;

public class TracerDataMainPanel extends JPanel {

    private static final long serialVersionUID = 5238167301628481200L;

    private static final Log4j2Helper LOG = new Log4j2Helper(TracerDataMainPanel.class);

    private RecentFileContainer recentFileContainer;

    private TraceTableModel traceTableModel;

    private RecentFile recentFile;

    private HashMap<String, TracerDataView> tracerDataViewMap;

    private JComboBox<TracerDataViewMode> tracerDataViewModeJComboBox;

    private JLabel incompleteTracerJLabel;

    private TracerViewerSetting tracerViewerSetting;

    private JLabel charsetJLabel;

    private JLabel sizeJLabel;

    private JPanel tracerDataViewCardJPanel;

    private JPanel supplementUtilityJPanel;

    private JButton gotoLineJButton;

    private JButton reloadJButton;

    private JButton overviewJButton;

    private TracerSimpleReportFrame tracerSimpleReportFrame;

    private TraceNavigationTableController traceNavigationTableController;

    @SuppressWarnings("unchecked")
    public TracerDataMainPanel(File selectedFile, RecentFileContainer recentFileContainer,
            TracerViewerSetting tracerViewerSetting) {

        super();

        this.recentFileContainer = recentFileContainer;
        this.tracerViewerSetting = tracerViewerSetting;

        String charset = tracerViewerSetting.getCharset();

        this.recentFile = recentFileContainer.getRecentFile(selectedFile, charset);

        SearchData<TraceEventKey> searchData = new SearchData<>(SearchEventType.values());

        this.traceTableModel = new TraceTableModel(recentFile, searchData);

        BookmarkContainer<TraceEventKey> bookmarkContainer;
        bookmarkContainer = (BookmarkContainer<TraceEventKey>) recentFile.getAttribute(RecentFile.KEY_BOOKMARK);

        if (bookmarkContainer == null) {

            bookmarkContainer = new BookmarkContainer<TraceEventKey>();

            recentFile.setAttribute(RecentFile.KEY_BOOKMARK, bookmarkContainer);
        }

        bookmarkContainer = (BookmarkContainer<TraceEventKey>) recentFile.getAttribute(RecentFile.KEY_BOOKMARK);

        BookmarkModel<TraceEventKey> bookmarkModel = new BookmarkModel<TraceEventKey>(bookmarkContainer,
                traceTableModel);

        traceTableModel.setBookmarkModel(bookmarkModel);

        traceNavigationTableController = new TraceNavigationTableController(traceTableModel);

        tracerDataViewMap = new HashMap<String, TracerDataView>();

        traceTableModel.addTableModelListener(new TableModelListener() {

            @Override
            public void tableChanged(TableModelEvent tableModelEvent) {
                updateDisplayJPanel();
            }
        });

        setLayout(new GridBagLayout());

        GridBagConstraints gbc1 = new GridBagConstraints();
        gbc1.gridx = 0;
        gbc1.gridy = 0;
        gbc1.weightx = 1.0D;
        gbc1.weighty = 0.0D;
        gbc1.fill = GridBagConstraints.BOTH;
        gbc1.anchor = GridBagConstraints.NORTHWEST;
        gbc1.insets = new Insets(0, 0, 0, 0);

        GridBagConstraints gbc2 = new GridBagConstraints();
        gbc2.gridx = 0;
        gbc2.gridy = 1;
        gbc2.weightx = 1.0D;
        gbc2.weighty = 1.0D;
        gbc2.fill = GridBagConstraints.BOTH;
        gbc2.anchor = GridBagConstraints.NORTHWEST;
        gbc2.insets = new Insets(0, 0, 0, 0);

        JPanel utilityCompositeJPanel = getUtilityCompositeJPanel();
        JPanel tracerDataViewCardJPanel = getTracerDataViewCardJPanel();

        add(utilityCompositeJPanel, gbc1);
        add(tracerDataViewCardJPanel, gbc2);

        // set default view
        JComboBox<TracerDataViewMode> tracerDataViewModeJComboBox = getTracerDataViewModeJComboBox();

        // http://bugs.java.com/bugdatabase/view_bug.do?bug_id=4699927
        // tree need to be built once the root node has some child nodes. hence
        // setting the default to single table
        tracerDataViewModeJComboBox.setSelectedItem(TracerDataViewMode.SINGLE_TABLE);

        loadFile(traceTableModel, this, false);

    }

    /*
     * (non-Javadoc)
     * 
     * @see javax.swing.JComponent#removeNotify()
     */
    @Override
    public void removeNotify() {
        super.removeNotify();

        clearJDialogList();
    }

    protected TraceTableModel getTraceTableModel() {
        return traceTableModel;
    }

    protected TraceNavigationTableController getTraceNavigationTableController() {
        return traceNavigationTableController;
    }

    private JPanel getUtilityCompositeJPanel() {

        JPanel utilityCompositeJPanel = new JPanel();

        utilityCompositeJPanel.setLayout(new GridBagLayout());

        GridBagConstraints gbc1 = new GridBagConstraints();
        gbc1.gridx = 0;
        gbc1.gridy = 0;
        gbc1.weightx = 0.0D;
        gbc1.weighty = 1.0D;
        gbc1.fill = GridBagConstraints.BOTH;
        gbc1.anchor = GridBagConstraints.NORTHWEST;
        gbc1.insets = new Insets(0, 0, 0, 0);

        GridBagConstraints gbc2 = new GridBagConstraints();
        gbc2.gridx = 1;
        gbc2.gridy = 0;
        gbc2.weightx = 1.0D;
        gbc2.weighty = 1.0D;
        gbc2.fill = GridBagConstraints.BOTH;
        gbc2.anchor = GridBagConstraints.NORTHWEST;
        gbc2.insets = new Insets(0, 0, 0, 0);

        GridBagConstraints gbc3 = new GridBagConstraints();
        gbc3.gridx = 2;
        gbc3.gridy = 0;
        gbc3.weightx = 0.0D;
        gbc3.weighty = 1.0D;
        gbc3.fill = GridBagConstraints.BOTH;
        gbc3.anchor = GridBagConstraints.NORTHWEST;
        gbc3.insets = new Insets(0, 0, 0, 0);

        JPanel utilityJPanel = getUtilityJPanel();
        JPanel supplementUtilityJPanel = getSupplementUtilityJPanel();
        JPanel infoJPanel = getTracerInfoJPanel();

        utilityCompositeJPanel.add(utilityJPanel, gbc1);
        utilityCompositeJPanel.add(supplementUtilityJPanel, gbc2);
        utilityCompositeJPanel.add(infoJPanel, gbc3);

        return utilityCompositeJPanel;
    }

    private JPanel getUtilityJPanel() {

        JPanel utilityJPanel = new JPanel();

        LayoutManager layout = new BoxLayout(utilityJPanel, BoxLayout.LINE_AXIS);
        utilityJPanel.setLayout(layout);

        Dimension spacer = new Dimension(15, 30);
        Dimension endSpacer = new Dimension(10, 30);

        JLabel tracerDataViewModeJLabel = new JLabel("Select view: ");

        JComboBox<TracerDataViewMode> tracerDataViewModeJComboBox = getTracerDataViewModeJComboBox();
        JButton gotoLineJButton = getGotoLineJButton();
        JButton overviewJButton = getOverviewJButton();
        JButton reloadJButton = getReloadJButton();

        utilityJPanel.add(Box.createRigidArea(endSpacer));
        utilityJPanel.add(tracerDataViewModeJLabel);
        utilityJPanel.add(Box.createRigidArea(spacer));
        utilityJPanel.add(tracerDataViewModeJComboBox);
        utilityJPanel.add(Box.createRigidArea(spacer));
        utilityJPanel.add(gotoLineJButton);
        utilityJPanel.add(Box.createRigidArea(spacer));
        utilityJPanel.add(overviewJButton);
        utilityJPanel.add(Box.createRigidArea(spacer));
        utilityJPanel.add(reloadJButton);
        utilityJPanel.add(Box.createRigidArea(spacer));

        utilityJPanel.setBorder(BorderFactory.createLineBorder(Color.LIGHT_GRAY, 1));

        return utilityJPanel;
    }

    private JPanel getTracerInfoJPanel() {

        JPanel infoJPanel = new JPanel();

        LayoutManager layout = new BoxLayout(infoJPanel, BoxLayout.X_AXIS);
        infoJPanel.setLayout(layout);

        // Dimension preferredSize = new Dimension(300, 30);
        // infoJPanel.setPreferredSize(preferredSize);

        JPanel incompleteTracerJPanel = getIncompleteTracerJPanel();
        JPanel charsetJPanel = getCharsetJPanel();
        JPanel sizeJPanel = getSizeJPanel();

        infoJPanel.add(incompleteTracerJPanel);
        infoJPanel.add(charsetJPanel);
        infoJPanel.add(sizeJPanel);

        return infoJPanel;
    }

    private JPanel getIncompleteTracerJPanel() {

        JLabel incompleteTracerJLabel = getIncompleteTracerJLabel();

        JPanel incompleteTracerJPanel = getInfoJPanel(incompleteTracerJLabel);

        Dimension preferredSize = new Dimension(150, 30);
        incompleteTracerJPanel.setPreferredSize(preferredSize);

        return incompleteTracerJPanel;
    }

    private JPanel getCharsetJPanel() {

        JLabel charsetJLabel = getCharsetJLabel();

        JPanel charsetJPanel = getInfoJPanel(charsetJLabel);

        Dimension preferredSize = new Dimension(100, 30);
        charsetJPanel.setPreferredSize(preferredSize);

        return charsetJPanel;
    }

    private JPanel getSizeJPanel() {

        JLabel sizeJLabel = getSizeJLabel();

        JPanel sizeJPanel = getInfoJPanel(sizeJLabel);

        Dimension preferredSize = new Dimension(100, 30);
        sizeJPanel.setPreferredSize(preferredSize);

        return sizeJPanel;
    }

    private JPanel getInfoJPanel(JLabel label) {

        JPanel infoJPanel = new JPanel();

        LayoutManager layout = new BoxLayout(infoJPanel, BoxLayout.X_AXIS);
        infoJPanel.setLayout(layout);

        Dimension dim = new Dimension(1, 30);

        infoJPanel.add(Box.createHorizontalGlue());
        infoJPanel.add(Box.createRigidArea(dim));
        infoJPanel.add(label);
        infoJPanel.add(Box.createHorizontalGlue());

        infoJPanel.setBorder(BorderFactory.createLineBorder(Color.LIGHT_GRAY, 1));

        return infoJPanel;
    }

    protected JPanel getSupplementUtilityJPanel() {

        if (supplementUtilityJPanel == null) {

            supplementUtilityJPanel = new JPanel();
            LayoutManager layout = new BoxLayout(supplementUtilityJPanel, BoxLayout.LINE_AXIS);
            supplementUtilityJPanel.setLayout(layout);
        }

        return supplementUtilityJPanel;
    }

    private JPanel getTracerDataViewCardJPanel() {

        if (tracerDataViewCardJPanel == null) {

            TraceTableModel traceTableModel = getTraceTableModel();

            TraceNavigationTableController traceNavigationTableController = getTraceNavigationTableController();

            JPanel supplementUtilityJPanel = getSupplementUtilityJPanel();

            tracerDataViewCardJPanel = new JPanel(new CardLayout());

            for (TracerDataViewMode tracerDataViewMode : TracerDataViewMode.values()) {

                TracerDataView tracerDataView;

                switch (tracerDataViewMode) {

                case SINGLE_TABLE:
                    tracerDataView = new TracerDataSingleTableView(traceTableModel, supplementUtilityJPanel,
                            traceNavigationTableController);
                    break;

                case SINGLE_TREE:
                    tracerDataView = new TracerDataTreeTableView(traceTableModel, supplementUtilityJPanel,
                            traceNavigationTableController);
                    break;

                case SINGLE_TREE_MERGED:
                    tracerDataView = new TracerDataTreeMergedTableView(traceTableModel, supplementUtilityJPanel,
                            traceNavigationTableController);
                    break;

                case COMPARE_TABLE:
                    tracerDataView = new TracerDataCompareTableView(traceTableModel, supplementUtilityJPanel,
                            traceNavigationTableController, recentFileContainer, tracerViewerSetting);
                    break;

                default:
                    tracerDataView = new TracerDataSingleTableView(traceTableModel, supplementUtilityJPanel,
                            traceNavigationTableController);
                    break;
                }

                String tracerDataViewModeName = tracerDataViewMode.name();

                tracerDataViewMap.put(tracerDataViewModeName, tracerDataView);

                tracerDataViewCardJPanel.add(tracerDataView, tracerDataViewModeName);
            }
        }

        return tracerDataViewCardJPanel;
    }

    private JComboBox<TracerDataViewMode> getTracerDataViewModeJComboBox() {

        if (tracerDataViewModeJComboBox == null) {

            tracerDataViewModeJComboBox = new JComboBox<TracerDataViewMode>(TracerDataViewMode.values());

            Dimension size = new Dimension(150, 20);
            tracerDataViewModeJComboBox.setPreferredSize(size);
            // tracerDataViewModeJComboBox.setMinimumSize(size);
            tracerDataViewModeJComboBox.setMaximumSize(size);

            tracerDataViewModeJComboBox.addActionListener(new ActionListener() {

                @SuppressWarnings("unchecked")
                @Override
                public void actionPerformed(ActionEvent actionEvent) {

                    JComboBox<TracerDataViewMode> tracerDataViewModeJComboBox;
                    tracerDataViewModeJComboBox = (JComboBox<TracerDataViewMode>) actionEvent.getSource();

                    TracerDataViewMode tracerDataViewMode;
                    tracerDataViewMode = (TracerDataViewMode) tracerDataViewModeJComboBox.getSelectedItem();

                    switchTracerDataViewMode(tracerDataViewMode);
                }
            });
        }

        return tracerDataViewModeJComboBox;
    }

    private JLabel getIncompleteTracerJLabel() {

        if (incompleteTracerJLabel == null) {
            incompleteTracerJLabel = new JLabel();
            incompleteTracerJLabel.setForeground(Color.RED);
        }

        return incompleteTracerJLabel;
    }

    private JLabel getCharsetJLabel() {

        if (charsetJLabel == null) {
            charsetJLabel = new JLabel();
        }

        return charsetJLabel;
    }

    private JLabel getSizeJLabel() {

        if (sizeJLabel == null) {
            sizeJLabel = new JLabel();
        }

        return sizeJLabel;
    }

    protected void switchTracerDataViewMode(TracerDataViewMode tracerDataViewMode) {

        String tracerDataViewModeName = tracerDataViewMode.name();

        TracerDataView tracerDataView = tracerDataViewMap.get(tracerDataViewModeName);

        if (tracerDataView != null) {

            tracerDataView.switchToFront();

            JPanel tracerDataViewCardJPanel = getTracerDataViewCardJPanel();
            CardLayout cardLayout = (CardLayout) (tracerDataViewCardJPanel.getLayout());

            cardLayout.show(tracerDataViewCardJPanel, tracerDataViewModeName);

        }
    }

    protected void updateDisplayJPanel() {

        LOG.info("updateDisplayJPanel");
        populateDisplayJPanel();
    }

    protected void populateDisplayJPanel() {

        JLabel incompleteTracerJLabel = getIncompleteTracerJLabel();
        JLabel charsetJLabel = getCharsetJLabel();
        JLabel sizeJLabel = getSizeJLabel();

        boolean incompleteTracerXML = traceTableModel.isIncompletedTracerXML();
        String incompleteTracerStr = (incompleteTracerXML ? "Incomplete Tracer XML" : "");
        String charset = traceTableModel.getCharset();

        Long size = (Long) recentFile.getAttribute(RecentFile.KEY_SIZE);
        String sizeStr = GeneralUtilities.humanReadableSize(size.longValue(), false);

        incompleteTracerJLabel.setText(incompleteTracerStr);
        charsetJLabel.setText(charset);
        sizeJLabel.setText(sizeStr);
    }

    public static void loadFile(TraceTableModel traceTableModel, Component parent, final boolean wait) {

        UIManager.put("ModalProgressMonitor.progressText", "Loading Tracer XML file");

        final ModalProgressMonitor progressMonitor = new ModalProgressMonitor(parent, "", "Loaded 0 trace events (0%)",
                0, 100);

        progressMonitor.setMillisToDecideToPopup(0);
        progressMonitor.setMillisToPopup(0);

        loadFile(traceTableModel, progressMonitor, wait, parent);
    }

    public static void loadFile(TraceTableModel traceTableModel, ModalProgressMonitor progressMonitor, boolean wait,
            Component parent) {

        RecentFile recentFile = traceTableModel.getRecentFile();

        if (recentFile != null) {

            TracerFileLoadTask tflt = new TracerFileLoadTask(progressMonitor, traceTableModel, wait, parent);

            tflt.execute();
            tflt.completeTask();

        } else {
            traceTableModel.setMessage(new Message(MessageType.ERROR, "No file selected for model"));
        }
    }

    private JButton getGotoLineJButton() {

        if (gotoLineJButton == null) {
            gotoLineJButton = new JButton("Go to line");
            Dimension size = new Dimension(90, 20);
            gotoLineJButton.setPreferredSize(size);
            gotoLineJButton.setMaximumSize(size);

            gotoLineJButton.addActionListener(new ActionListener() {

                @Override
                public void actionPerformed(ActionEvent actionEvent) {

                    List<TraceEventKey> traceEventKeyList;
                    traceEventKeyList = getTraceTableModel().getFtmEntryKeyList();

                    // increment by 1 to get the display sequence no.
                    int startIndex = traceEventKeyList.get(0).getId() + 1;
                    int endIndex = traceEventKeyList.get(traceEventKeyList.size() - 1).getId() + 1;

                    GoToLineDialog goToLineDialog = new GoToLineDialog(startIndex, endIndex, BaseFrame.getAppIcon(),
                            TracerDataMainPanel.this);
                    goToLineDialog.setVisible(true);

                    Integer selectedIndex = goToLineDialog.getSelectedInteger();

                    if (selectedIndex != null) {

                        TraceEventKey selectedTraceEventKey = null;

                        for (TraceEventKey traceEventKey : traceEventKeyList) {

                            int traceEventKeyId = traceEventKey.getId();

                            // decrement by 1 to get actual id.
                            if ((selectedIndex.intValue() - 1) == traceEventKeyId) {
                                selectedTraceEventKey = traceEventKey;
                                break;
                            }
                        }

                        if (selectedTraceEventKey != null) {
                            getTraceNavigationTableController().scrollToKey(selectedTraceEventKey);
                            // traverseToKey(selectedTraceEventKey);
                        }
                    }
                }
            });

        }

        return gotoLineJButton;
    }

    private JButton getReloadJButton() {

        if (reloadJButton == null) {
            reloadJButton = new JButton("Reload file");

            Dimension size = new Dimension(90, 20);
            reloadJButton.setPreferredSize(size);
            reloadJButton.setMaximumSize(size);
            reloadJButton.addActionListener(new ActionListener() {

                @Override
                public void actionPerformed(ActionEvent actionEvent) {

                    TraceTableModel traceTableModel = getTraceTableModel();

                    String origCharset = traceTableModel.getCharset();
                    TraceTablePanelSettingDialog traceTablePanelSettingDialog;

                    traceTablePanelSettingDialog = new TraceTablePanelSettingDialog(origCharset, BaseFrame.getAppIcon(),
                            TracerDataMainPanel.this);

                    boolean settingUpdated = traceTablePanelSettingDialog.isSettingUpdated();

                    if (settingUpdated) {

                        String charset = traceTablePanelSettingDialog.getSelectedCharset();
                        traceTableModel.updateRecentFile(charset);

                        if (origCharset.equals(charset)) {
                            populateDisplayJPanel();

                        } else {
                            // charset changed, read/parse the file again

                            // clear and reset the model.
                            traceTableModel.resetModel();

                            // charset changed, read/parse the file again
                            loadFile(traceTableModel, TracerDataMainPanel.this, false);
                        }
                    }
                }
            });
        }

        return reloadJButton;
    }

    private JButton getOverviewJButton() {

        if (overviewJButton == null) {

            overviewJButton = new JButton("Overview");
            Dimension size = new Dimension(90, 20);
            overviewJButton.setPreferredSize(size);
            overviewJButton.setMaximumSize(size);

            overviewJButton.addActionListener(new ActionListener() {

                @Override
                public void actionPerformed(ActionEvent actionEvent) {

                    JFrame tracerSimpleReportFrame = getTracerOverviewFrame();

                    tracerSimpleReportFrame.toFront();
                }
            });
        }

        return overviewJButton;

    }

    protected TracerSimpleReportFrame getTracerOverviewFrame() {

        if (tracerSimpleReportFrame == null) {

            TraceTableModel traceTableModel = getTraceTableModel();
            TraceNavigationTableController traceNavigationTableController = getTraceNavigationTableController();

            tracerSimpleReportFrame = new TracerSimpleReportFrame(traceTableModel, traceNavigationTableController,
                    BaseFrame.getAppIcon(), this);

            tracerSimpleReportFrame.addWindowListener(new WindowAdapter() {

                @Override
                public void windowClosed(WindowEvent windowEvent) {
                    TracerSimpleReportFrame tracerSimpleReportFrame;
                    tracerSimpleReportFrame = getTracerOverviewFrame();

                    tracerSimpleReportFrame.destroyFrame();
                    setTracerSimpleReportFrame(null);
                }

            });
        }

        return tracerSimpleReportFrame;
    }

    protected void setTracerSimpleReportFrame(TracerSimpleReportFrame tracerSimpleReportFrame) {
        this.tracerSimpleReportFrame = tracerSimpleReportFrame;
    }

    private void clearJDialogList() {

        if (tracerSimpleReportFrame != null) {
            tracerSimpleReportFrame.dispose();
            tracerSimpleReportFrame = null;
        }
    }
}