/*
 * JBoss, Home of Professional Open Source.
 * Copyright 2010, Red Hat, Inc., and individual contributors
 * as indicated by the @author tags. See the copyright.txt file in the
 * distribution for a full listing of individual contributors.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 */
package org.jboss.as.console.client.shared.runtime.logging.viewer;

import com.google.gwt.core.client.Scheduler;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.event.logical.shared.AttachEvent;
import com.google.gwt.event.logical.shared.ResizeEvent;
import com.google.gwt.event.logical.shared.ResizeHandler;
import com.google.gwt.event.shared.HandlerRegistration;
import com.google.gwt.safehtml.shared.SafeHtmlUtils;
import com.google.gwt.user.client.Window;
import com.google.gwt.user.client.ui.*;
import edu.ycp.cs.dh.acegwt.client.ace.AceEditor;
import org.jboss.as.console.client.shared.runtime.logging.store.*;
import org.jboss.ballroom.client.widgets.tools.ToolButton;
import org.jboss.ballroom.client.widgets.tools.ToolStrip;
import org.jboss.gwt.circuit.Action;
import org.jboss.gwt.circuit.Dispatcher;

import static com.google.gwt.dom.client.Style.Unit.EM;
import static com.google.gwt.dom.client.Style.Unit.PX;
import static com.google.gwt.dom.client.Style.VerticalAlign.MIDDLE;
import static org.jboss.as.console.client.shared.runtime.logging.viewer.Direction.*;
import static org.jboss.as.console.client.shared.util.IdHelper.setId;

/**
 * Shows a log file in a read-only ACE editor.
 *
 * @author Harald Pehl
 */
public class LogFilePanel extends Composite implements LogViewerId {

    private final static int HEADER_HEIGHT = 48; // no search + 35;
    private final static int TOOLS_HEIGHT = 32;
    private final static int MARGIN_BOTTOM = 50;

    private final Dispatcher circuit;
    private final String name;
    private final VerticalPanel panel;
    private final AceEditor editor;
    private final LogFileIndicator indicator;
    private final HandlerRegistration resizeHandler;
    private final CheckBox follow;
    private final ToolButton head;
    private final ToolButton prev;
    private final ToolButton next;
    private final ToolButton tail;
    private int visibleLines;

    public LogFilePanel(final Dispatcher circuit, final LogFile logFile) {
        this.circuit = circuit;
        this.name = logFile.getName();

        panel = new VerticalPanel();
        panel.setStyleName("rhs-content-panel");
        panel.add(new HTML("<h3>" + logFile.getName() + "</h3>"));
        // No search box: The search works only inside the currently displayed lines, not over the whole
        // log file. It's better to disable search than having a search which is ambiguous to the user.
//        panel.add(new SearchBox());

        editor = new AceEditor();
        editor.addAttachHandler(new AttachEvent.Handler() {
            @Override
            public void onAttachOrDetach(AttachEvent event) {
                if (event.isAttached()) {
                    Scheduler.get().scheduleDeferred(
                            new Scheduler.ScheduledCommand() {
                                @Override
                                public void execute() {
                                    editor.startEditor();
                                    editor.setReadOnly(true);
                                    editor.setShowGutter(false);
                                    editor.setShowPrintMargin(false);
                                    editor.setModeByName("logfile");
                                    editor.setThemeByName("logfile");
                                    editor.setText(logFile.getContent());
                                    editor.setFontSize("11px");
                                    editor.setVScrollBarVisible(false);
                                }
                            }
                    );
                }
            }
        });
        indicator = new LogFileIndicator();
        HorizontalPanel editorPanel = new HorizontalPanel();
        editorPanel.setStyleName("fill-layout-width");
        editorPanel.add(indicator);
        editorPanel.add(editor);
        indicator.getElement().getParentElement().getStyle().setWidth(15, PX);
        indicator.getElement().getParentElement().getStyle().setBackgroundColor("#030303");
        panel.add(editorPanel);

        follow = new CheckBox("Auto Refresh");
        follow.addClickHandler(new ClickHandler() {
            @Override
            public void onClick(ClickEvent event) {
                if (follow.getValue()) {
                    circuit.dispatch(new FollowLogFile());
                } else {
                    circuit.dispatch(new UnFollowLogFile());
                }
            }
        });
        follow.getElement().getStyle().setMarginLeft(1, EM);
        setId(follow, BASE_ID, "auto_refresh");

        head = new ToolButton("Head", new ClickHandler() {
            @Override
            public void onClick(ClickEvent event) {
                onNavigate(HEAD);
            }
        });
        head.setOperationAddress("/{selected.host}/{selected.server}/subsystem=logging", "read-log-file");
        setId(head, BASE_ID, "head");

        prev = new ToolButton("<i class=\"icon-angle-up\"></i>", new ClickHandler() {
            @Override
            public void onClick(ClickEvent event) {
                onNavigate(PREVIOUS);
            }
        });
        prev.setOperationAddress("/{selected.host}/{selected.server}/subsystem=logging", "read-log-file");
        setId(prev, BASE_ID, "prev");

        next = new ToolButton("<i class=\"icon-angle-down\"></i>", new ClickHandler() {
            @Override
            public void onClick(ClickEvent event) {
                onNavigate(NEXT);
            }
        });
        next.setOperationAddress("/{selected.host}/{selected.server}/subsystem=logging", "read-log-file");
        setId(next, BASE_ID, "next");

        tail = new ToolButton("Tail", new ClickHandler() {
            @Override
            public void onClick(ClickEvent event) {
                onNavigate(TAIL);
            }
        });
        tail.setOperationAddress("/{selected.host}/{selected.server}/subsystem=logging", "read-log-file");
        setId(tail, BASE_ID, "tail");

        ToolStrip navigationTools = new ToolStrip();
        navigationTools.addToolButton(head);
        navigationTools.addToolButton(prev);
        navigationTools.addToolButton(next);
        navigationTools.addToolButton(tail);
        navigationTools.addToolWidget(follow);
        panel.add(navigationTools);

        resizeHandler = Window.addResizeHandler(new ResizeHandler() {
            @Override
            public void onResize(ResizeEvent event) {
                LogFilePanel.this.onResize();
            }
        });
        initWidget(panel);
        setStyleName("rhs-content-panel");
    }

    public void refresh(LogFile logFile, Action action) {
        editor.setText(logFile.getContent());
        indicator.refresh(logFile, action);
        follow.setValue(logFile.isFollow());
        if (logFile.getLines().size() < visibleLines) {
            head.setEnabled(false);
            prev.setEnabled(false);
            next.setEnabled(false);
            tail.setEnabled(false);
        } else {
            head.setEnabled(!logFile.isHead());
            prev.setEnabled(!logFile.isHead());
            next.setEnabled(!logFile.isTail());
            tail.setEnabled(!logFile.isTail());
        }
    }

    private void onNavigate(Direction direction) {
        if (direction == Direction.HEAD || direction == Direction.PREVIOUS) {
            circuit.dispatch(new UnFollowLogFile());
        }
        circuit.dispatch(new NavigateInLogFile(direction));
    }

    @Override
    protected void onUnload() {
        editor.destroy();
        resizeHandler.removeHandler();
    }

    public void onResize() {
        Scheduler.get().scheduleDeferred(new Scheduler.ScheduledCommand() {
            @Override
            public void execute() {
                int panelHeight = panel.getElement().getParentElement().getOffsetHeight();
                int editorHeight = panelHeight - HEADER_HEIGHT - TOOLS_HEIGHT - MARGIN_BOTTOM;

                if (panelHeight > 0) {
                    editor.setHeight(editorHeight + "px");
                    visibleLines = editorHeight / 17;
                    circuit.dispatch(new ChangePageSize(visibleLines));
                }
            }
        });
    }

    public String getName() {
        return name;
    }


    /**
     * Simulates AceEditor's build in search box
     */
    @SuppressWarnings("UnusedDeclaration")
    private class SearchBox extends Composite {

        public SearchBox() {

            // first part: setup the visible widgets
            final TextBox findTextBox = new TextBox();
            findTextBox.addStyleName("ace_search_field");
            findTextBox.getElement().setAttribute("placeholder", "Find");
            setId(findTextBox, BASE_ID, "find_input");

            ToolButton findButton = new ToolButton("Find", new ClickHandler() {
                @Override
                public void onClick(ClickEvent event) {
                    editor.search(findTextBox.getValue());
                }
            });
            setId(findButton, BASE_ID, "find");

            Button findPrev = new Button(SafeHtmlUtils.fromSafeConstant("<i class=\"icon-angle-left\"></i>"));
            findPrev.addStyleName("toolstrip-button");
            findPrev.getElement().setAttribute("action", "findPrev"); // AceEditor action wiring
            setId(findPrev, BASE_ID, "prev_match");

            Button findNext = new Button(SafeHtmlUtils.fromSafeConstant("<i class=\"icon-angle-right\"></i>"));
            findNext.addStyleName("toolstrip-button");
            findNext.getElement().setAttribute("action", "findNext"); // AceEditor action wiring
            setId(findNext, BASE_ID, "next_match");

            ToolStrip searchTools = new ToolStrip();
            searchTools.addToolWidget(findTextBox);
            searchTools.addToolButton(findButton);
            searchTools.addToolWidget(findPrev);
            searchTools.addToolWidget(findNext);
            findTextBox.getElement().getStyle().setWidth(20, EM);
            findTextBox.getElement().getStyle().setMarginBottom(0, PX);
            findTextBox.getElement().getParentElement().getStyle().setVerticalAlign(MIDDLE);
            findButton.getElement().getParentElement().getStyle().setVerticalAlign(MIDDLE);
            findPrev.getElement().getParentElement().getStyle().setVerticalAlign(MIDDLE);
            findNext.getElement().getParentElement().getStyle().setVerticalAlign(MIDDLE);

            // next part: rebuild the original search box
            FlowPanel searchForm = div("ace_search_form", false);
            searchForm.add(searchTools);

            FlowPanel replaceForm = div("ace_replace_form", true);
            replaceForm.add(hiddenTextBox("ace_search_field"));
            replaceForm.add(hiddenButton("replaceAndFindNext", "ace_replacebtn"));
            replaceForm.add(hiddenButton("replaceAll", "ace_replacebtn"));

            FlowPanel searchOptions = div("ace_search_options", true);
            searchOptions.add(hiddenButton("toggleRegexpMode", "ace_button"));
            searchOptions.add(hiddenButton("toggleCaseSensitive", "ace_button"));
            searchOptions.add(hiddenButton("toggleWholeWords", "ace_button"));

            FlowPanel searchBox = div("ace_search_log_viewer", false);
            searchBox.add(hiddenButton("close", "ace_searchbtn_close"));
            searchBox.add(searchForm);
            searchBox.add(replaceForm);
            searchBox.add(searchOptions);

            initWidget(searchBox);
        }

        private FlowPanel div(String styleName, boolean hidden) {
            FlowPanel div = new FlowPanel();
            div.setStyleName(styleName);
            if (hidden) {
                div.setVisible(false);
            }
            return div;
        }

        private Button hiddenButton(String action, String styleName) {
            Button button = new Button();
            button.setStyleName(styleName);
            button.getElement().setAttribute("action", action);
            button.setVisible(false);
            return button;
        }

        private TextBox hiddenTextBox(String styleName) {
            TextBox textBox = new TextBox();
            textBox.setStyleName(styleName);
            textBox.setVisible(false);
            return textBox;
        }
    }
}
