/*
 * RHQ Management Platform
 * Copyright (C) 2005-2010 Red Hat, Inc.
 * All rights reserved.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation version 2 of the License.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */
package org.rhq.enterprise.gui.coregui.client.util.message;

import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;

import com.allen_sauer.gwt.log.client.Log;
import com.google.gwt.user.client.Timer;
import com.smartgwt.client.data.SortSpecifier;
import com.smartgwt.client.types.Alignment;
import com.smartgwt.client.types.ListGridFieldType;
import com.smartgwt.client.types.SelectionStyle;
import com.smartgwt.client.types.SortDirection;
import com.smartgwt.client.types.TimeFormatter;
import com.smartgwt.client.types.VerticalAlignment;
import com.smartgwt.client.widgets.Window;
import com.smartgwt.client.widgets.events.CloseClickHandler;
import com.smartgwt.client.widgets.events.CloseClientEvent;
import com.smartgwt.client.widgets.events.DoubleClickEvent;
import com.smartgwt.client.widgets.events.DoubleClickHandler;
import com.smartgwt.client.widgets.form.DynamicForm;
import com.smartgwt.client.widgets.form.fields.FormItemIcon;
import com.smartgwt.client.widgets.form.fields.StaticTextItem;
import com.smartgwt.client.widgets.grid.HoverCustomizer;
import com.smartgwt.client.widgets.grid.ListGrid;
import com.smartgwt.client.widgets.grid.ListGridField;
import com.smartgwt.client.widgets.grid.ListGridRecord;
import com.smartgwt.client.widgets.grid.SortNormalizer;

import org.rhq.enterprise.gui.coregui.client.CoreGUI;
import org.rhq.enterprise.gui.coregui.client.components.table.AbstractTableAction;
import org.rhq.enterprise.gui.coregui.client.components.table.Table;
import org.rhq.enterprise.gui.coregui.client.components.table.TableActionEnablement;
import org.rhq.enterprise.gui.coregui.client.components.table.TimestampCellFormatter;
import org.rhq.enterprise.gui.coregui.client.util.message.Message.Severity;
import org.rhq.enterprise.gui.coregui.client.util.selenium.LocatableDynamicForm;
import org.rhq.enterprise.gui.coregui.client.util.selenium.LocatableWindow;

/**
 * Message Center view that shows the latest messages generated by the app.
 * This is a Table and can therefore be a member to any layout. However, it can
 * also be displayed in its own non-modal dialog window. If you want this table
 * shown in a dialog window, call {@link #showMessageCenterWindow()}.
 * 
 * Note: this class has to be very careful about catching any and all exceptions.
 * Otherwise, an uncaught exception will cause a flooding of global exception
 * messages (since the unhandled exception handler in CoreGUI will recursively
 * call into this message center).
 *
 * @author John Mazzitelli
 */
@SuppressWarnings("unchecked")
public class MessageCenterView extends Table implements MessageCenter.MessageListener {

    public static final String LOCATOR_ID = "MessageCenter";
    public static final String TABLE_TITLE = MSG.view_messageCenter_messageTitle();

    private static final String FIELD_TIME = "time";
    private static final String FIELD_SEVERITY = "severity";
    private static final String FIELD_CONCISEMESSAGE = "conciseMessage";
    private static final String FIELD_OBJECT = "object";

    private MessageCenterWindow window;

    public MessageCenterView(String locatorId) {
        super(locatorId, MSG.view_messageCenter_messageTitle(), null, new SortSpecifier[] { new SortSpecifier(
            FIELD_TIME, SortDirection.DESCENDING) }, null, false);
        CoreGUI.getMessageCenter().addMessageListener(this);
    }

    /**
     * This will popup a non-modal window with the messages in a list.
     */
    public void showMessageCenterWindow() {
        try {
            if (window == null) {
                window = new MessageCenterWindow("MessageCenterViewWindow");
                window.addItem(this); // Use addItem(), not addMember(), when adding a widget to a Window!
                window.addCloseClickHandler(new CloseClickHandler() {
                    @Override
                    public void onCloseClick(CloseClientEvent event) {
                        try {
                            window.hide();
                        } catch (Throwable e) {
                            Log.warn("Cannot hide message center", e);
                        }
                    }
                });
            }

            window.show();
            markForRedraw(); // need this to ensure the list grid rows are selectable
        } catch (Throwable e) {
            Log.error("Cannot show message center window", e);
        }
    }

    @Override
    public void onMessage(final Message message) {
        try {
            if (!message.isTransient()) {
                refresh();
                if (window != null) {
                    window.blink();
                }
            }
        } catch (Throwable e) {
            Log.error("Cannot process message", e);
        }
    }

    public void reset() {
        if (window != null) {
            window.hide();
        }
        refresh();
    }

    @Override
    protected void configureTable() {
        getListGrid().setEmptyMessage(MSG.view_messageCenter_noRecentMessages());

        updateTitleCanvas(MSG.view_messageCenter_lastNMessages(String.valueOf(CoreGUI.getMessageCenter().getMaxMessages())));

        ListGridField severityField = new ListGridField(FIELD_SEVERITY);
        severityField.setType(ListGridFieldType.ICON);
        severityField.setAlign(Alignment.CENTER);
        severityField.setShowValueIconOnly(true);
        HashMap<String, String> severityIcons = new HashMap<String, String>(5);
        severityIcons.put(Severity.Blank.name(), getSeverityIcon(Severity.Blank));
        severityIcons.put(Severity.Info.name(), getSeverityIcon(Severity.Info));
        severityIcons.put(Severity.Warning.name(), getSeverityIcon(Severity.Warning));
        severityIcons.put(Severity.Error.name(), getSeverityIcon(Severity.Error));
        severityIcons.put(Severity.Fatal.name(), getSeverityIcon(Severity.Fatal));
        severityField.setValueIcons(severityIcons);
        severityField.setShowHover(true);
        severityField.setHoverCustomizer(new HoverCustomizer() {
            @Override
            public String hoverHTML(Object value, ListGridRecord record, int rowNum, int colNum) {
                try {
                    Severity severity = ((Message) record.getAttributeAsObject(FIELD_OBJECT)).getSeverity();
                    switch (severity) {
                    case Info:
                        return MSG.common_severity_info();
                    case Warning:
                        return MSG.common_severity_warn();
                    case Error:
                        return MSG.common_severity_error();
                    case Fatal:
                        return MSG.common_severity_fatal();
                    }
                } catch (Throwable e) {
                    Log.error("Cannot get severity hover", e);
                }
                return null;
            }
        });
        severityField.setSortNormalizer(new SortNormalizer() {
            @Override
            public Object normalize(ListGridRecord record, String fieldName) {
                try {
                    Severity severity = ((Message) record.getAttributeAsObject(FIELD_OBJECT)).getSeverity();
                    return Integer.valueOf(severity.ordinal());
                } catch (Throwable e) {
                    Log.error("Cannot get sort nomalizer", e);
                }
                return Integer.valueOf(0);
            }
        });

        ListGridField timeField = new ListGridField(FIELD_TIME, MSG.view_messageCenter_messageTime());
        timeField.setType(ListGridFieldType.TIME);
        timeField.setAttribute("displayFormat", TimeFormatter.TOPADDEDTIME);
        timeField.setAlign(Alignment.LEFT);
        timeField.setShowHover(true);
        timeField.setHoverCustomizer(TimestampCellFormatter.getHoverCustomizer(FIELD_TIME));

        ListGridField messageField = new ListGridField(FIELD_CONCISEMESSAGE, MSG.common_title_message());

        severityField.setWidth(25);
        timeField.setWidth("15%");
        messageField.setWidth("*");

        getListGrid().setFields(severityField, timeField, messageField);

        setListGridDoubleClickHandler(new DoubleClickHandler() {
            @Override
            public void onDoubleClick(DoubleClickEvent event) {
                try {
                    ListGrid listGrid = (ListGrid) event.getSource();
                    ListGridRecord[] selectedRows = listGrid.getSelection();
                    if (selectedRows != null && selectedRows.length > 0) {
                        Message message = (Message) selectedRows[0].getAttributeAsObject(FIELD_OBJECT); // show the first selected
                        showDetails(message);
                    }
                } catch (Throwable e) {
                    Log.error("Cannot show details for message", e);
                }
            }
        });

        addTableAction(extendLocatorId("delete"), MSG.common_button_delete(), MSG.common_msg_areYouSure(),
            new AbstractTableAction(TableActionEnablement.ANY) {
                @Override
                public void executeAction(ListGridRecord[] selection, Object actionValue) {
                    try {
                        for (ListGridRecord record : selection) {
                            Object doomed = record.getAttributeAsObject(FIELD_OBJECT);
                            CoreGUI.getMessageCenter().getMessages().remove(doomed);
                        }
                        refresh();
                    } catch (Throwable e) {
                        Log.error("Cannot delete messages", e);
                    }
                }
            });

        addTableAction(extendLocatorId("deleteAll"), MSG.common_button_delete_all(), MSG.common_msg_areYouSure(),
            new AbstractTableAction(TableActionEnablement.ALWAYS) {
                @Override
                public void executeAction(ListGridRecord[] selection, Object actionValue) {
                    try {
                        CoreGUI.getMessageCenter().getMessages().clear();
                        refresh();
                    } catch (Throwable e) {
                        Log.error("Cannot delete all messages", e);
                    }
                }
            });

        LinkedHashMap<String, Integer> maxMessagesMap = new LinkedHashMap<String, Integer>();
        maxMessagesMap.put("10", Integer.valueOf("10"));
        maxMessagesMap.put("25", Integer.valueOf("25"));
        maxMessagesMap.put("50", Integer.valueOf("50"));
        maxMessagesMap.put("100", Integer.valueOf("100"));
        maxMessagesMap.put("200", Integer.valueOf("200"));
        addTableAction(extendLocatorId("maxMessageMenu"), MSG.view_messageCenter_maxMessages(), null, maxMessagesMap,
            new AbstractTableAction(TableActionEnablement.ALWAYS) {
                @Override
                public void executeAction(ListGridRecord[] selection, Object actionValue) {
                    try {
                        Integer maxSize = (Integer) actionValue;
                        CoreGUI.getMessageCenter().setMaxMessages(maxSize.intValue());
                        updateTitleCanvas(MSG.view_messageCenter_lastNMessages(maxSize.toString()));
                        refresh();
                    } catch (Throwable e) {
                        Log.error("Cannot set max messages", e);
                    }
                }
            });

        /*
        // TODO only for testing, remove this when done testing
        addTableAction(extendLocatorId("test"), "TEST MSG", null,
            new AbstractTableAction(TableActionEnablement.ALWAYS) {
                @Override
                public void executeAction(ListGridRecord[] selection, Object actionValue) {
                    for (Severity severity : java.util.EnumSet.allOf(Severity.class)) {
                        Message m = new Message(severity.name() + ':' + System.currentTimeMillis(), severity);
                        CoreGUI.getMessageCenter().notify(m);
                    }
                }
            });
        */

        // initial population of the list with current messages
        try {
            refresh();
        } catch (Throwable e) {
            Log.error("Cannot perform initial refresh", e);
        }
    }

    @Override
    protected SelectionStyle getDefaultSelectionStyle() {
        return SelectionStyle.MULTIPLE;
    }

    @Override
    public void refresh() {
        try {
            super.refresh();
            if ((getListGrid() != null) && (CoreGUI.getMessageCenter().getMessages() != null)) {
                getListGrid().setRecords(transform(CoreGUI.getMessageCenter().getMessages()));
            }
            refreshTableInfo();
        } catch (Throwable e) {
            Log.error("Cannot refresh messages", e);
        }
    }

    private ListGridRecord[] transform(List<Message> list) {
        ListGridRecord[] results = new ListGridRecord[list.size()];
        for (int i = 0; i < list.size(); i++) {
            results[i] = transform(list.get(i));
        }
        return results;
    }

    private ListGridRecord transform(Message msg) {
        ListGridRecord record = new ListGridRecord();
        record.setAttribute(FIELD_TIME, msg.fired);
        record.setAttribute(FIELD_SEVERITY, (msg.severity != null) ? msg.severity.name() : Severity.Info.name());
        record.setAttribute(FIELD_CONCISEMESSAGE, msg.conciseMessage);
        record.setAttribute(FIELD_OBJECT, msg);
        return record;
    }

    /**
     * This is a static utility method that is package protected so the message center view
     * and the message bar can pop up a dialog showing a message's details.
     * 
     * @param message the message whose details are to be shown
     */
    static void showDetails(Message message) {
        if (message == null) {
            return;
        }

        DynamicForm form = new LocatableDynamicForm("MessageCenterDetailsForm");
        form.setWrapItemTitles(false);
        form.setAlign(Alignment.LEFT);

        StaticTextItem title = new StaticTextItem("theMessage", MSG.common_title_message());
        title.setValue(message.conciseMessage);

        StaticTextItem severity = new StaticTextItem("severity", MSG.view_messageCenter_messageSeverity());
        FormItemIcon severityIcon = new FormItemIcon();
        severityIcon.setSrc(getSeverityIcon(message.severity));
        severity.setIcons(severityIcon);
        severity.setValue(message.severity.name());

        StaticTextItem date = new StaticTextItem("time", MSG.view_messageCenter_messageTime());
        date.setValue(TimestampCellFormatter.format(message.fired, TimestampCellFormatter.DATE_TIME_FORMAT_FULL));

        StaticTextItem detail = new StaticTextItem("detail", MSG.view_messageCenter_messageDetail());
        detail.setTitleVAlign(VerticalAlignment.TOP);
        detail.setValue(message.detailedMessage);

        form.setItems(title, severity, date, detail);

        final Window dialogWin = new LocatableWindow("MessageCenterDetailsWindow");
        dialogWin.setTitle(MSG.common_title_message());
        dialogWin.setWidth(600);
        dialogWin.setHeight(400);
        dialogWin.setIsModal(true);
        dialogWin.setShowModalMask(true);
        dialogWin.setCanDragResize(true);
        dialogWin.setShowMaximizeButton(true);
        dialogWin.setShowMinimizeButton(false);
        dialogWin.centerInPage();
        dialogWin.addItem(form);
        dialogWin.show();
        dialogWin.addCloseClickHandler(new CloseClickHandler() {
            @Override
            public void onCloseClick(CloseClientEvent event) {
                dialogWin.destroy();
            }
        });
    }

    private static String getSeverityIcon(Message.Severity severity) {
        if (severity == null) {
            severity = Severity.Blank;
        }
        return severity.getIcon();
    }

    static class MessageCenterWindow extends LocatableWindow {
        private Timer blinkTimer;

        public MessageCenterWindow(String locatorId) {
            super(locatorId);
            setTitle(TABLE_TITLE);
            setShowMinimizeButton(true);
            setShowMaximizeButton(true);
            setShowCloseButton(true);
            setIsModal(false);
            setShowModalMask(false);
            setWidth(700);
            setHeight(300);
            setShowResizer(true);
            setCanDragResize(true);
            centerInPage();

            final String origColor = getBodyColor();
            blinkTimer = new Timer() {
                @Override
                public void run() {
                    try {
                        setBodyColor(origColor);
                        setTitle(TABLE_TITLE);
                        redraw();
                    } catch (Throwable e) {
                        Log.error("Blink timer failed", e);
                    }
                }
            };
        }

        public void blink() {
            try {
                // window.flash() isn' t working so do it ourselves
                if (getMinimized()) {
                    setTitle(TABLE_TITLE + " *");
                } else {
                    setBodyColor(getHiliteBodyColor());
                }
                redraw();
                blinkTimer.schedule(250);
            } catch (Throwable e) {
                Log.error("Cannot blink message center window", e);
            }
        }
    }

}
