/*
 * Copyright 2011 JBoss, by Red Hat, Inc
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *  
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.jboss.errai.tools.monitoring;

import org.jboss.errai.bus.client.api.MessageCallback;
import org.jboss.errai.bus.client.api.base.RuleDelegateMessageCallback;
import org.jboss.errai.bus.client.framework.MessageBus;
import org.jboss.errai.bus.client.util.BusTools;
import org.jboss.errai.bus.server.api.ServerMessageBus;
import org.jboss.errai.bus.server.io.RemoteServiceCallback;
import org.jboss.errai.bus.server.security.auth.rules.RolesRequiredRule;
import org.mvel2.util.StringAppender;

import javax.swing.*;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.DefaultTreeModel;
import javax.swing.tree.TreeSelectionModel;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

import static java.lang.String.valueOf;
import static javax.swing.SwingUtilities.invokeLater;
import static org.jboss.errai.tools.monitoring.UiHelper.getSwIcon;

public class ServerMonitorPanel implements Attachable {
  private MainMonitorGUI mainMonitorGUI;
  private MessageBus messageBus;
  private String busId;

  private JList busServices;
  private JTree serviceExplorer;

  private final DefaultListModel busServicesModel;

  private JPanel rootPanel;

  private String currentlySelectedService;

  private ServerLogPanel logPanel;

  private ActivityProcessor processor;

  private Map<String, ServiceActivityMonitor> monitors = new HashMap<String, ServiceActivityMonitor>();

  public ServerMonitorPanel(MainMonitorGUI gui, MessageBus bus, String busId) {
    this.mainMonitorGUI = gui;
    this.messageBus = bus;
    this.busId = busId;

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

    JButton activityConsoleButton = new JButton("Activity Console");
    JButton monitorButton = new JButton("Monitor Service...");
    JButton conversationsButton = new JButton("Conversations ...");

    busServices = new JList();
    busServices.setSelectionMode(ListSelectionModel.SINGLE_INTERVAL_SELECTION);
    busServices.setModel(busServicesModel = new DefaultListModel());
    busServices.setCellRenderer(new ServicesListCellRender());

    serviceExplorer = new JTree();

    JSplitPane splitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT,
            new JScrollPane(busServices), new JScrollPane(serviceExplorer));
    splitPane.setDividerLocation(150);

    rootPanel.add(splitPane, BorderLayout.CENTER);

    JPanel southPanel = new JPanel();
    southPanel.setLayout(new BorderLayout());
    rootPanel.add(southPanel, BorderLayout.SOUTH);

    southPanel.add(activityConsoleButton, BorderLayout.WEST);

    JPanel southEastPanel = new JPanel();
    southEastPanel.setLayout(new FlowLayout());
    southEastPanel.add(conversationsButton);
    southEastPanel.add(monitorButton);

    southPanel.add(southEastPanel, BorderLayout.EAST);

    busServices.addListSelectionListener(new ListSelectionListener() {
      public void valueChanged(ListSelectionEvent e) {
        currentlySelectedService = getCurrentServiceSelection();
        generateServiceExplorer();
      }
    });

    busServices.addMouseListener(new MouseAdapter() {
      public void mouseClicked(MouseEvent e) {
        if (e.getButton() != MouseEvent.BUTTON1 || e.getClickCount() != 2) return;
        openActivityMonitor();
      }
    });

    monitorButton.addActionListener(new ActionListener() {
      public void actionPerformed(ActionEvent e) {
        openActivityMonitor();
      }
    });

    conversationsButton.addActionListener(new ActionListener() {
      public void actionPerformed(ActionEvent e) {
        openConversationMonitor();
      }
    });

    activityConsoleButton.addActionListener(new ActionListener() {
      public void actionPerformed(ActionEvent e) {
        openServerLog();
      }
    });

    DefaultTreeModel model = (DefaultTreeModel) serviceExplorer.getModel();
    ((DefaultMutableTreeNode) serviceExplorer.getModel().getRoot()).removeAllChildren();
    serviceExplorer.setRootVisible(false);

    serviceExplorer.setCellRenderer(new MonitorTreeCellRenderer());

    serviceExplorer.getSelectionModel().setSelectionMode(TreeSelectionModel.DISCONTIGUOUS_TREE_SELECTION);

    model.reload();
  }

  public void attach(ActivityProcessor proc) {
    this.processor = proc;
  }

  private void openActivityMonitor() {
    if (monitors.containsKey(getCurrentServiceSelection())) {
      monitors.get(currentlySelectedService).toFront();
    }
    else {
      ServiceActivityMonitor sam = new ServiceActivityMonitor(this, busId, currentlySelectedService);
      sam.attach(processor);
      monitors.put(currentlySelectedService, sam);
    }
  }

  public void openConversationMonitor() {
    String key = currentlySelectedService + ":Conversations";

    if (monitors.containsKey(key)) {
      monitors.get(key).toFront();
    }
    else {
      ServiceActivityMonitor sam = new ConversationActivityMonitor(this, busId, currentlySelectedService);
      sam.attach(processor);
      monitors.put(key, sam);
    }
  }

  private void openServerLog() {
    if (this.logPanel != null && this.logPanel.isVisible()) {
      return;
    }
    this.logPanel = new ServerLogPanel(mainMonitorGUI);
    this.logPanel.attach(processor);
  }

  void stopMonitor(String service) {
    monitors.remove(service);
  }

  private String getCurrentServiceSelection() {
    return valueOf(busServicesModel.get(busServices.getSelectedIndex()));
  }

  public void addServiceName(final String serviceName) {
    synchronized (busServicesModel) {
      if (busServicesModel.contains(serviceName)) return;

      invokeLater(new Runnable() {
        public void run() {
          busServicesModel.addElement(serviceName);
        }
      });
    }
  }

  public void removeServiceName(final String serviceName) {
    synchronized (busServicesModel) {
      if (!busServicesModel.contains(serviceName)) return;

      invokeLater(new Runnable() {
        public void run() {
          busServicesModel.removeElement(serviceName);
        }
      });
    }
  }

  public JPanel getPanel() {
    return rootPanel;
  }

  private void generateServiceExplorer() {
    DefaultMutableTreeNode node = (DefaultMutableTreeNode) serviceExplorer.getModel().getRoot();

    node.setUserObject(new JLabel(currentlySelectedService + (BusTools.isReservedName(currentlySelectedService) ? " (Built-in)" : ""), getSwIcon("service.png"), SwingConstants.LEFT));
    node.removeAllChildren();

    serviceExplorer.setRootVisible(true);

    DefaultTreeModel model = (DefaultTreeModel) serviceExplorer.getModel();


    if (messageBus instanceof ServerMessageBus) {
      // this is the serverside bus.
      ServerMessageBus smb = (ServerMessageBus) messageBus;
      List<MessageCallback> receivers = smb.getReceivers(currentlySelectedService);

      DefaultMutableTreeNode receiversNode
              = new DefaultMutableTreeNode("Receivers (" + receivers.size() + ")", true);

      for (MessageCallback mc : receivers) {
        receiversNode.add(new DefaultMutableTreeNode(mc.getClass().getName()));

        if (mc instanceof RuleDelegateMessageCallback) {
          RuleDelegateMessageCallback ruleDelegate = (RuleDelegateMessageCallback) mc;
          DefaultMutableTreeNode securityNode =
                  new DefaultMutableTreeNode("Security");

          if (ruleDelegate.getRoutingRule() instanceof RolesRequiredRule) {
            RolesRequiredRule rule = (RolesRequiredRule) ruleDelegate.getRoutingRule();

            DefaultMutableTreeNode rolesNode =
                    new DefaultMutableTreeNode(rule.getRoles().isEmpty() ? "Requires Authentication" : "Roles Required");

            for (Object o : rule.getRoles()) {
              //     DefaultMutableTreeNode roleNode = new DefaultMutableTreeNode(String.valueOf(o));

              rolesNode.add(UiHelper.createIconEntry("key.png", valueOf(o)));
            }

            securityNode.add(rolesNode);
          }

          node.add(securityNode);
        }
        else if (mc instanceof RemoteServiceCallback) {
          RemoteServiceCallback remCB = (RemoteServiceCallback) mc;

          Set<String> endpoints = remCB.getEndpoints();

          DefaultMutableTreeNode remoteCPs =
                  new DefaultMutableTreeNode("Callpoints (" + endpoints.size() + ")");

          for (String endpoint : endpoints) {
            String[] epParts = endpoint.split(":");

            StringAppender appender = new StringAppender(epParts[0]).append('(');
            for (int i = 1; i < epParts.length; i++) {
              appender.append(epParts[i]);
              if ((i + 1) < epParts.length) appender.append(", ");
            }

            remoteCPs.add(UiHelper.createIconEntry("database_connect.png", appender.append(')').toString()));
          }

          node.add(remoteCPs);
        }
      }

      node.add(receiversNode);
    }

    model.reload();

    for (int i = 0; i < serviceExplorer.getRowCount(); i++) {
      serviceExplorer.expandRow(i);
    }
  }

  public MainMonitorGUI getMainMonitorGUI() {
    return mainMonitorGUI;
  }

  public class ServicesListCellRender extends DefaultListCellRenderer {
    @Override
    public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) {
      super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus);
      String v = valueOf(value);
      if (v.endsWith(":RPC")) {
        setIcon(getSwIcon("database_connect.png"));
      }
      else {
        setIcon(BusTools.isReservedName(v) ? getSwIcon("database_key.png") : getSwIcon("database.png"));
      }
      setToolTipText(v);
      return this;
    }
  }
}
