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

package com.pega.gcs.tracerviewer.report;

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.LayoutManager;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.List;
import java.util.Map;
import java.util.TreeSet;
import java.util.concurrent.atomic.AtomicInteger;

import javax.swing.BorderFactory;
import javax.swing.Box;
import javax.swing.BoxLayout;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTabbedPane;
import javax.swing.JTable;
import javax.swing.ListSelectionModel;
import javax.swing.SwingConstants;
import javax.swing.border.EmptyBorder;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
import javax.swing.event.TableModelEvent;
import javax.swing.event.TableModelListener;
import javax.swing.table.DefaultTableCellRenderer;
import javax.swing.table.DefaultTableColumnModel;
import javax.swing.table.JTableHeader;
import javax.swing.table.TableCellRenderer;
import javax.swing.table.TableColumn;
import javax.swing.table.TableColumnModel;

import com.pega.gcs.fringecommon.guiutilities.FilterTableModel;
import com.pega.gcs.fringecommon.guiutilities.NavigationTableController;
import com.pega.gcs.fringecommon.guiutilities.bookmark.BookmarkContainerPanel;
import com.pega.gcs.fringecommon.guiutilities.bookmark.BookmarkModel;
import com.pega.gcs.fringecommon.guiutilities.search.SearchModel;
import com.pega.gcs.fringecommon.log4j2.Log4j2Helper;
import com.pega.gcs.tracerviewer.TraceEventRule;
import com.pega.gcs.tracerviewer.TraceEventRuleset;
import com.pega.gcs.tracerviewer.TraceTableModel;
import com.pega.gcs.tracerviewer.TraceTableModelColumn;
import com.pega.gcs.tracerviewer.model.TraceEvent;
import com.pega.gcs.tracerviewer.model.TraceEventKey;

public class TracerSimpleReportFrame extends JFrame implements TableModelListener {

    private static final long serialVersionUID = -3013669038509576223L;

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

    private TraceTableModel traceTableModel;

    private NavigationTableController<TraceEventKey> navigationTableController;

    private JButton refreshJButton;

    private JTabbedPane tracerReportTabbedPane;

    private AtomicInteger selectedTab;

    private boolean removeAction;

    public TracerSimpleReportFrame(TraceTableModel traceTableModel,
            NavigationTableController<TraceEventKey> navigationTableController, ImageIcon appIcon, Component parent) {

        super();

        this.traceTableModel = traceTableModel;
        this.navigationTableController = navigationTableController;
        this.selectedTab = new AtomicInteger(0);
        this.removeAction = false;

        traceTableModel.addTableModelListener(this);

        setTitle(traceTableModel.getModelName());

        setIconImage(appIcon.getImage());

        setPreferredSize(new Dimension(1150, 600));

        setDefaultCloseOperation(javax.swing.WindowConstants.DISPOSE_ON_CLOSE);

        setContentPane(getMainJPanel());

        pack();

        setLocationRelativeTo(parent);

        // visible should be the last step
        setVisible(true);

    }

    protected TraceTableModel getTraceTableModel() {
        return traceTableModel;
    }

    protected NavigationTableController<TraceEventKey> getNavigationTableController() {
        return navigationTableController;
    }

    @Override
    public void tableChanged(TableModelEvent tableModelEvent) {

        if (tableModelEvent.getType() == TableModelEvent.UPDATE) {
            LOG.info("TracerSimpleReportFrame tableChanged");
            rebuildOverview();
        }
    }

    private void rebuildOverview() {
        JTabbedPane tracerReportTabbedPane = getTracerReportTabbedPane();

        removeAction = true;
        tracerReportTabbedPane.removeAll();
        buildTabs();
        removeAction = false;

        tracerReportTabbedPane.setSelectedIndex(selectedTab.get());

        validate();
        repaint();
    }

    public void destroyFrame() {
        traceTableModel.removeTableModelListener(this);
        setVisible(false);
    }

    private JButton getRefreshJButton() {

        if (refreshJButton == null) {
            refreshJButton = new JButton("Refresh Overview");
            refreshJButton.addActionListener(new ActionListener() {

                @Override
                public void actionPerformed(ActionEvent actionEvent) {
                    rebuildOverview();
                }
            });
        }

        return refreshJButton;
    }

    private JTabbedPane getTracerReportTabbedPane() {

        if (tracerReportTabbedPane == null) {
            tracerReportTabbedPane = new JTabbedPane();

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

            ChangeListener tabChangeListener = new ChangeListener() {

                @Override
                public void stateChanged(ChangeEvent changeEvent) {

                    if (!removeAction) {
                        JTabbedPane sourceTabbedPane = (JTabbedPane) changeEvent.getSource();
                        int index = sourceTabbedPane.getSelectedIndex();
                        selectedTab.set(index);
                    }
                }
            };

            tracerReportTabbedPane.addChangeListener(tabChangeListener);
        }

        return tracerReportTabbedPane;
    }

    private JPanel getMainJPanel() {

        JPanel mainJPanel = new JPanel();
        mainJPanel.setLayout(new BorderLayout());

        JPanel refreshButtonJPanel = getRefreshButtonJPanel();
        JTabbedPane tracerReportTabbedPane = getTracerReportTabbedPane();

        mainJPanel.add(refreshButtonJPanel, BorderLayout.NORTH);
        mainJPanel.add(tracerReportTabbedPane, BorderLayout.CENTER);

        buildTabs();

        return mainJPanel;
    }

    private JPanel getRefreshButtonJPanel() {

        JPanel refreshButtonJPanel = new JPanel();

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

        Dimension spacer = new Dimension(5, 35);

        JButton refreshJButton = getRefreshJButton();

        refreshButtonJPanel.add(Box.createHorizontalGlue());
        refreshButtonJPanel.add(Box.createRigidArea(spacer));
        refreshButtonJPanel.add(refreshJButton);
        refreshButtonJPanel.add(Box.createRigidArea(spacer));
        refreshButtonJPanel.add(Box.createHorizontalGlue());

        return refreshButtonJPanel;

    }

    private void buildTabs() {
        int tabIndex = 0;

        BookmarkModel<TraceEventKey> bookmarkModel = traceTableModel.getBookmarkModel();

        List<TraceEventKey> reportFailedEventList = traceTableModel.getReportFailedEventKeyList();
        List<TraceEventKey> reportExceptionEventList = traceTableModel.getReportExceptionEventKeyList();
        List<TraceEventKey> reportAlertEventList = traceTableModel.getReportAlertEventKeyList();
        List<TraceEventKey> reportNoStartEventList = traceTableModel.getReportNoStartEventKeyList();
        List<TraceEventKey> reportNoEndEventList = traceTableModel.getReportNoEndEventKeyList();
        List<TraceEventKey> reportOwnElapsedEventList = traceTableModel.getReportOwnElapsedEventKeyList();

        Object searchStrObj = traceTableModel.getSearchModel().getSearchStrObj();

        boolean containsBookmark = bookmarkModel.getMarkerCount() > 0;

        boolean containsFailure = (reportFailedEventList.size() > 0) ? true : false;

        boolean containsException = (reportExceptionEventList.size() > 0) ? true : false;

        boolean containsAlerts = (reportAlertEventList.size() > 0) ? true : false;

        boolean containsNoStartEvent = (reportNoStartEventList.size() > 0) ? true : false;

        boolean containsNoEndEvent = (reportNoEndEventList.size() > 0) ? true : false;

        boolean containsOwnElapsed = (reportOwnElapsedEventList.size() > 0) ? true : false;

        boolean containsSearch = (searchStrObj != null) ? true : false;

        if (containsFailure) {
            JPanel failedEventJPanel = getFailedEventJPanel();
            String tabText = "Failed Events";
            addTab(tabText, failedEventJPanel, tabIndex);
            tabIndex++;
        }

        if (containsException) {
            JPanel exceptionEventJPanel = getExceptionEventJPanel();
            String tabText = "Exception Events";
            addTab(tabText, exceptionEventJPanel, tabIndex);
            tabIndex++;
        }

        if (containsAlerts) {
            JPanel alertEventJPanel = getAlertEventJPanel();
            String tabText = "Alert Events";
            addTab(tabText, alertEventJPanel, tabIndex);
            tabIndex++;
        }

        if (containsNoStartEvent) {
            JPanel noStartEventJPanel = getNoStartEventJPanel();
            String tabText = "No Start Events";
            addTab(tabText, noStartEventJPanel, tabIndex);
            tabIndex++;
        }

        if (containsNoEndEvent) {
            JPanel noEndEventJPanel = getNoEndEventJPanel();
            String tabText = "No End Events";
            addTab(tabText, noEndEventJPanel, tabIndex);
            tabIndex++;
        }

        if (containsOwnElapsed) {
            JPanel ownElapsedEventJPanel = getOwnElapsedEventJPanel();
            String tabText = "Own Elapsed Time";
            addTab(tabText, ownElapsedEventJPanel, tabIndex);
            tabIndex++;
        }

        if (containsSearch) {
            JPanel searchEventJPanel = getSearchEventJPanel();
            String tabText = "Search Results";
            addTab(tabText, searchEventJPanel, tabIndex);
            tabIndex++;
        }

        if (containsBookmark) {

            JPanel bookmarkContainerPanel = getBookmarkContainerPanel();

            String tabText = "Bookmarks";
            addTab(tabText, bookmarkContainerPanel, tabIndex);
            tabIndex++;

        }

        JPanel rulesInvokedJPanel = getRulesInvokedJPanel();
        String tabText = "Rules invoked";
        addTab(tabText, rulesInvokedJPanel, tabIndex);
        tabIndex++;

    }

    private void addTab(String tabText, JPanel panel, int tabIndex) {

        JTabbedPane tracerReportTabbedPane = getTracerReportTabbedPane();

        JLabel tabLabel = new JLabel(tabText);
        Font labelFont = tabLabel.getFont();
        Font tabFont = labelFont.deriveFont(Font.BOLD, 12);
        Dimension dim = new Dimension(140, 26);
        tabLabel.setFont(tabFont);
        tabLabel.setSize(dim);
        tabLabel.setPreferredSize(dim);
        tabLabel.setHorizontalAlignment(SwingConstants.CENTER);

        tracerReportTabbedPane.addTab(tabText, panel);
        tracerReportTabbedPane.setTabComponentAt(tabIndex, tabLabel);
    }

    private BookmarkContainerPanel<TraceEventKey> getBookmarkContainerPanel() {

        BookmarkModel<TraceEventKey> bookmarkModel = traceTableModel.getBookmarkModel();

        BookmarkContainerPanel<TraceEventKey> bookmarkContainerPanel;

        bookmarkContainerPanel = new BookmarkContainerPanel<TraceEventKey>(bookmarkModel, navigationTableController) {

            private static final long serialVersionUID = 5672957295689747776L;

            @Override
            public FilterTableModel<TraceEventKey> getFilterTableModel() {
                return getTraceTableModel();
            }

        };

        return bookmarkContainerPanel;
    }

    private JPanel getFailedEventJPanel() {

        String description = "List of innermost trace events that has failed status. Select an entry to select the record on the main table.";

        List<TraceEventKey> reportFailedEventList = traceTableModel.getReportFailedEventKeyList();

        JPanel failedEventJPanel = getTraceReportJPanel(description, reportFailedEventList);

        return failedEventJPanel;
    }

    private JPanel getExceptionEventJPanel() {

        String description = "List of innermost trace events that has failed with an exception. Select an entry to "
                + "select the record on the main table.";

        List<TraceEventKey> reportExceptionEventList = traceTableModel.getReportExceptionEventKeyList();

        JPanel exceptionEventJPanel = getTraceReportJPanel(description, reportExceptionEventList);

        return exceptionEventJPanel;
    }

    private JPanel getAlertEventJPanel() {

        String description = "List of Alert trace events. Select an entry to select the record on the main table.";

        List<TraceEventKey> reportAlertEventList = traceTableModel.getReportAlertEventKeyList();

        JPanel alertEventJPanel = getTraceReportJPanel(description, reportAlertEventList);

        return alertEventJPanel;
    }

    private JPanel getNoStartEventJPanel() {

        String description = "List of trace events which doesnt have corresponding 'Begin' event. Select an entry to "
                + "select the record on the main table.";
        List<TraceEventKey> reportNoStartEventList = traceTableModel.getReportNoStartEventKeyList();

        JPanel noStartEventJPanel = getTraceReportJPanel(description, reportNoStartEventList);

        return noStartEventJPanel;
    }

    private JPanel getNoEndEventJPanel() {

        String description = "List of trace events which doesnt have corresponding 'End' event. Select an entry to "
                + "select the record on the main table.";
        List<TraceEventKey> reportNoEndEventList = traceTableModel.getReportNoEndEventKeyList();

        JPanel noEndEventJPanel = getTraceReportJPanel(description, reportNoEndEventList);

        return noEndEventJPanel;
    }

    private JPanel getOwnElapsedEventJPanel() {

        String description = "List of innermost trace events sorted by 'own elapsed time' in decending order. Select "
                + "an entry to select the record on the main table.";
        List<TraceEventKey> reportOwnElapsedEventList = traceTableModel.getReportOwnElapsedEventKeyList();

        JPanel ownElapsedEventJPanel = getTraceReportJPanel(description, reportOwnElapsedEventList);

        return ownElapsedEventJPanel;
    }

    private JPanel getSearchEventJPanel() {

        String description = "List of current search results. Select an entry to select the record on the main table.";
        SearchModel<TraceEventKey> searchModel = traceTableModel.getSearchModel();
        Object searchStrObj = searchModel.getSearchStrObj();
        List<TraceEventKey> searchEventList = searchModel.getSearchResultList(searchStrObj);

        JPanel searchEventJPanel = getTraceReportJPanel(description, searchEventList);

        return searchEventJPanel;
    }

    private JPanel getRulesInvokedJPanel() {

        JPanel rulesInvokedJPanel = new JPanel(new BorderLayout());

        String text = "List all the rules invoked during the trace capture, sorted on total own elapsed time.";
        JPanel labelJPanel = getLabelJPanel(text);

        JTable rulesInvokedJTable = getRulesInvokedJTable();

        JScrollPane ownElapsedEventJTableScrollPane = new JScrollPane(rulesInvokedJTable);

        rulesInvokedJPanel.add(labelJPanel, BorderLayout.NORTH);
        rulesInvokedJPanel.add(ownElapsedEventJTableScrollPane, BorderLayout.CENTER);

        return rulesInvokedJPanel;
    }

    private TracerReportRulesTable getRulesInvokedJTable() {

        Map<TraceEventRuleset, TreeSet<TraceEventRule>> reportRulesInvokedMap = traceTableModel.getReportRulesInvokedMap();

        TracerReportRulesTableModel tracerReportRulesTableModel = new TracerReportRulesTableModel(reportRulesInvokedMap);

        TracerReportRulesTable tracerReportRulesTable;
        tracerReportRulesTable = new TracerReportRulesTable(tracerReportRulesTableModel);

        return tracerReportRulesTable;

    }

    private JPanel getTraceReportJPanel(String description, List<TraceEventKey> traceEventKeyList) {

        JPanel traceReportJPanel = new JPanel(new BorderLayout());

        JPanel labelJPanel = getLabelJPanel(description);

        JTable traceReportJTable = getTraceReportJTable(traceEventKeyList);

        JScrollPane traceReportJTableScrollPane = new JScrollPane(traceReportJTable);

        traceReportJPanel.add(labelJPanel, BorderLayout.NORTH);
        traceReportJPanel.add(traceReportJTableScrollPane, BorderLayout.CENTER);

        return traceReportJPanel;
    }

    private JTable getTraceReportJTable(List<TraceEventKey> traceEventKeyList) {

        TracerReportTableModel tracerReportTableModel;

        tracerReportTableModel = new TracerReportTableModel(traceEventKeyList, traceTableModel);

        JTable traceReportJTable = new JTable(tracerReportTableModel);

        traceReportJTable.setRowHeight(20);
        traceReportJTable.setFillsViewportHeight(true);
        traceReportJTable.setAutoResizeMode(JTable.AUTO_RESIZE_OFF);
        traceReportJTable.setRowSelectionAllowed(true);
        traceReportJTable.setAutoCreateColumnsFromModel(false);

        TableColumnModel tableColumnModel = getTraceReportTableColumnModel(tracerReportTableModel);

        traceReportJTable.setColumnModel(tableColumnModel);

        // setup header
        JTableHeader tableHeader = traceReportJTable.getTableHeader();

        tableHeader.setReorderingAllowed(false);

        final TableCellRenderer origTableCellRenderer = tableHeader.getDefaultRenderer();

        DefaultTableCellRenderer dtcr = new DefaultTableCellRenderer() {

            private static final long serialVersionUID = -5411641633512120668L;

            @Override
            public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected,
                    boolean hasFocus, int row, int column) {

                JLabel origComponent = (JLabel) origTableCellRenderer.getTableCellRendererComponent(table, value,
                        isSelected, hasFocus, row, column);

                origComponent.setHorizontalAlignment(CENTER);

                // set header height
                Dimension dim = origComponent.getPreferredSize();
                dim.setSize(dim.getWidth(), 30);
                origComponent.setPreferredSize(dim);

                return origComponent;
            }

        };
        tableHeader.setDefaultRenderer(dtcr);

        // bold the header
        Font existingFont = tableHeader.getFont();
        String existingFontName = existingFont.getName();
        int existFontSize = existingFont.getSize();
        Font newFont = new Font(existingFontName, Font.BOLD, existFontSize);
        tableHeader.setFont(newFont);

        ListSelectionModel listSelectionModel = traceReportJTable.getSelectionModel();
        listSelectionModel.addListSelectionListener(new ListSelectionListener() {

            @Override
            public void valueChanged(ListSelectionEvent listSelectionEvent) {
                if (!listSelectionEvent.getValueIsAdjusting()) {

                    int row = traceReportJTable.getSelectedRow();

                    TracerReportTableModel tracerReportTableModel;
                    tracerReportTableModel = (TracerReportTableModel) traceReportJTable.getModel();

                    TraceEventKey traceEventKey = tracerReportTableModel.getTraceEventKey(row);

                    NavigationTableController<TraceEventKey> navigationTableController;
                    navigationTableController = getNavigationTableController();

                    navigationTableController.scrollToKey(traceEventKey);
                }

            }
        });

        return traceReportJTable;
    }

    private TableColumnModel getTraceReportTableColumnModel(TracerReportTableModel tracerReportTableModel) {

        TableColumnModel tableColumnModel = new DefaultTableColumnModel();

        for (int i = 0; i < tracerReportTableModel.getColumnCount(); i++) {

            TableColumn tableColumn = new TableColumn(i);

            String text = tracerReportTableModel.getColumnName(i);

            tableColumn.setHeaderValue(text);

            DefaultTableCellRenderer dtcr = new DefaultTableCellRenderer() {

                private static final long serialVersionUID = 5731474707446644101L;

                @Override
                public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected,
                        boolean hasFocus, int row, int column) {

                    String text = null;

                    if ((value != null) && (value instanceof TraceEvent)) {

                        TraceEvent traceEvent = (TraceEvent) value;

                        TracerReportTableModel tracerReportTableModel;

                        tracerReportTableModel = (TracerReportTableModel) table.getModel();

                        text = tracerReportTableModel.getColumnValue(traceEvent, column);

                        if (!table.isRowSelected(row)) {
                            setBackground(traceEvent.getColumnBackground(0));
                        }

                        setHorizontalAlignment(CENTER);
                    }

                    super.getTableCellRendererComponent(table, text, isSelected, hasFocus, row, column);

                    return this;
                }

            };

            dtcr.setBorder(new EmptyBorder(1, 3, 1, 1));

            tableColumn.setCellRenderer(dtcr);

            TraceTableModelColumn trtc = tracerReportTableModel.getColumn(i);

            int colWidth = trtc.getPrefColumnWidth();
            tableColumn.setPreferredWidth(colWidth);
            tableColumn.setMinWidth(colWidth);
            tableColumn.setWidth(colWidth);
            tableColumn.setResizable(true);

            tableColumnModel.addColumn(tableColumn);
        }

        return tableColumnModel;
    }

    private JPanel getLabelJPanel(String text) {

        JPanel labelJPanel = new JPanel();

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

        JLabel label = new JLabel(text);

        int height = 30;

        Dimension spacer = new Dimension(10, height);
        labelJPanel.add(Box.createRigidArea(spacer));
        labelJPanel.add(label);
        labelJPanel.add(Box.createHorizontalGlue());

        labelJPanel.setBorder(BorderFactory.createLineBorder(Color.LIGHT_GRAY));
        return labelJPanel;

    }

}