/* 
 *  SupervisorDataGUI.java
 * 
 *  Copyright 2016 Avaya Inc. All Rights Reserved.
 * 
 *  Usage of this source is bound to the terms described
 *  in AvayaLicenseSDK.rtf.
 * 
 *  Avaya - Confidential & Proprietary. Use pursuant to your signed agreement
 *  or Avaya Policy
 * 
 */
package com.avaya.ccs.javafxrefclient;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import com.avaya.ccs.api.enums.ContactType;

import javafx.beans.property.ReadOnlyObjectWrapper;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.event.ActionEvent;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.CheckBox;
import javafx.scene.control.ComboBox;
import javafx.scene.control.Label;
import javafx.scene.control.SplitPane;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.control.TextField;
import javafx.scene.layout.AnchorPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
import javafx.util.Callback;

/**
 * This class is responsible for all the supervisor related GUI components
 */
public class SupervisorDataGUI {

    private static final String BLANKMONITORDATA = "No supervised agents to display";
    private static final String BLANKCONTACTINFO = "There are currently no contacts associated with the monitored agent(s).";
    private static final Logger LOG = Logger.getLogger(SupervisorDataGUI.class);

    private final MonitoredExecutor monitoredExe;
    private final InteractionExecutor interactionExe;

    // GUI components and object lists
    private final Stage supervisorStage = new Stage();
    private TableView<MonitoredUserData> monitorTable = new TableView<>();
    private final TableView<MonitoredInteractionData> monitoredInteractionTable = new TableView<>();
    private final ObservableList<UserData> userItems = FXCollections.observableArrayList();
    private ObservableList<MonitoredUserData> monitoredUserItems = FXCollections.observableArrayList();
    private final ObservableList<MonitoredInteractionData> monitoredInteractionItems = FXCollections
            .observableArrayList();
    private final ObservableList<InteractionData> interactionItems = FXCollections.observableArrayList();
    private final ComboBox<String> superCombo = new ComboBox<>();
    private Button buttonForceReady, buttonForceNotReady, buttonForceLogout;
    private Button buttonObserve, buttonBargein, buttonWhisper, buttonHold, buttonEnd;
    private final SupervisorSideBar sideBar;
    private CheckBox allCheckBox;
    private final TextField conStatusField;

    // Variables to store current subscriptions so we can cancel them when
    // selections change
    private boolean shouldMonitor = false;
    private String supervisorUserId = null;
    private boolean areMonitoringSupervisor = false;
    private List<String> monitoredUserIds = new ArrayList<>();
    private boolean areMonitoringUsers = false;

    private final String forceReadyButtonText = "Ready";
    private final String forceNotReadyButtonText = "Not Ready";
    private final String forceLogoutButtonText = "Logout";
    private final String observeButtonText = "Observe";
    private final String bargeinButtonText = "Barge-In";
    private final String whisperButtonText = "Whisper";
    private final String holdButtonText = "Hold";
    private final String unHoldButtonText = "Unhold";
    private final String endButtonText = "End";

    private final int supervisorSceneWidth = 1400;
    private final int supervisorSceneHeight = 800;

    private String css;
	private Map<String, WebchatForm> webchatForms= new HashMap<String, WebchatForm>();
    
    public SupervisorDataGUI(MonitoredExecutor monitoredExe, InteractionExecutor interactionExe, String css) {

        this.monitoredExe = monitoredExe;
        this.interactionExe = interactionExe;
        this.sideBar = new SupervisorSideBar(interactionExe, this);
        this.css=css;
        
        // Build supervisor stage
        SplitPane sp = new SplitPane();
        VBox selectionVbox = new VBox();
        VBox interactionVbox = new VBox();
        sp.getStyleClass().add("root");

        // Left Pane
        Label superLabel = new Label("Supervisor");
        Label monitorLabel = new Label("Monitored Agents");
        allCheckBox = new CheckBox("All");
        allCheckBox.setAllowIndeterminate(false);
        allCheckBox.setSelected(false);
        allCheckBox.selectedProperty().addListener(new ChangeListener<Boolean>() {
            @Override
            public void changed(ObservableValue<? extends Boolean> observable, Boolean oldValue, Boolean newValue) {
                allCheckBox.setSelected(newValue);
                if (monitoredUserItems.size() > 0) {
                    for (MonitoredUserData user : monitoredUserItems) {
                        user.setIsSelected(newValue);
                    }
                    monitorTable.refresh();
                    checkMonitoredUsers();
                }
            }
        });
        AnchorPane monitorAp = new AnchorPane();
        monitorAp.getChildren().addAll(monitorLabel, allCheckBox);
        AnchorPane.setLeftAnchor(monitorLabel, 2.0);
        AnchorPane.setRightAnchor(allCheckBox, 2.0);
        createAgentDataTable();
        VBox selGroupVbox1 = new VBox(5);
        selGroupVbox1.getChildren().addAll(superLabel, superCombo);

        createSelectionButtons();
        HBox selHbox3 = new HBox(5);
        selHbox3.getChildren().addAll(buttonForceReady, buttonForceNotReady, buttonForceLogout);
        selHbox3.setAlignment(Pos.CENTER);

        VBox selGroupVbox2 = new VBox(5);
        selGroupVbox2.getChildren().addAll(monitorAp, monitorTable, selHbox3);

        // Right pane
        Label conStatusLabel = new Label("Monitor Connection Status");
        conStatusField = new TextField();
        conStatusField.setEditable(false);
        conStatusField.setText(BLANKCONTACTINFO);
        conStatusField.getStyleClass().add("statusField");

        VBox termGroupVbox1 = new VBox(5);
        termGroupVbox1.getChildren().addAll(conStatusLabel, conStatusField);
        VBox.setMargin(conStatusField, new Insets(0, 0, 8, 0));

        Label monitoredLabel = new Label("Current Contacts of Monitored Agents");
        createInteractionTable();

        createInteractionButtons();
        HBox termGroupHbox3 = new HBox(5);
        termGroupHbox3.getChildren().addAll(buttonObserve, buttonWhisper, buttonBargein, buttonHold, buttonEnd);
        termGroupHbox3.setAlignment(Pos.CENTER);

        VBox termGroupVbox2 = new VBox(5);
        termGroupVbox2.getChildren().addAll(monitoredLabel, monitoredInteractionTable, termGroupHbox3);

        selectionVbox.getChildren().addAll(selGroupVbox1, selGroupVbox2, sideBar.getSideBar());
        interactionVbox.getChildren().addAll(termGroupVbox1, termGroupVbox2);
        selectionVbox.getStyleClass().add("selvbox");
        interactionVbox.getStyleClass().add("intvbox");

        sp.getItems().addAll(selectionVbox, interactionVbox);
        sp.setDividerPositions(0.3f);

        Scene supervisorScene = new Scene(sp, supervisorSceneWidth, supervisorSceneHeight);
        supervisorScene.getStylesheets().add(css);
        supervisorStage.setTitle("Supervisor Control");
        supervisorStage.setScene(supervisorScene);

        // Handler for when the supervisor window is closed
        supervisorStage.setOnCloseRequest(event -> {
            LOG.info("supervisorStage.setOnCloseRequest(): Supervisor window closed");
            shouldMonitor = false;
            checkMonitoredUsers();
            checkMonitoring();
        });

        supervisorStage.setOnShowing(event -> {
            LOG.info("supervisorStage.setOnShowing(): Supervisor window opened");
            shouldMonitor = true;
            checkMonitoring();
            checkMonitoredUsers();
        });
    }

    private void createSelectionButtons() {

        buttonForceReady = new Button(forceReadyButtonText);
        buttonForceReady.getStyleClass().add("agent");
        buttonForceReady.setDisable(true);
        buttonForceReady.setOnAction((ActionEvent e) -> {
            String methodName = "ForceReadyButton() ";
            MonitoredUserData user = monitorTable.getSelectionModel().getSelectedItem();
            if (user != null) {
                LOG.info(methodName + " SupervisorID: " + user.getSupervisorId() + " UserId:" + user.getId());
                monitoredExe.forceReady(user.getSupervisorId(), user.getId());
            } else
                LOG.info(methodName + " User==NULL");
        });

        buttonForceNotReady = new Button(forceNotReadyButtonText);
        buttonForceNotReady.getStyleClass().add("agent");
        buttonForceNotReady.setDisable(true);
        buttonForceNotReady.setOnAction((ActionEvent e) -> {
            String methodName = "ForceNotReadyButton() ";
            MonitoredUserData user = monitorTable.getSelectionModel().getSelectedItem();
            if (user != null) {
                LOG.info(methodName + " SupervisorID: " + user.getSupervisorId() + " UserId:" + user.getId());
                monitoredExe.forceNotReady(user.getSupervisorId(), user.getId());
            } else
                LOG.info(methodName + " User==NULL");
        });

        buttonForceLogout = new Button(forceLogoutButtonText);
        buttonForceLogout.getStyleClass().add("agent");
        buttonForceLogout.setDisable(true);
        buttonForceLogout.setOnAction((ActionEvent e) -> {
            String methodName = "ForceLogoutButton() ";
            MonitoredUserData user = monitorTable.getSelectionModel().getSelectedItem();
            if (user != null) {
                LOG.info(methodName + " SupervisorID: " + user.getSupervisorId() + " UserId:" + user.getId());
                monitoredExe.forceLogout(user.getSupervisorId(), user.getId());
            } else
                LOG.info(methodName + " User==NULL");

        });
    }

    private void createInteractionButtons() {

        buttonObserve = new Button(observeButtonText);
        buttonObserve.getStyleClass().add("agentButton");
        buttonObserve.setDisable(true);
        buttonObserve.setOnAction((ActionEvent e) -> {
        	//somewhat analogous to Answer in InteractionDataGui
            MonitoredInteractionData monitoredInteractionData = getSelectedMonitoredInteraction();
            LOG.info("ObserveButton SupervisorID: " + supervisorUserId + " UserId:"
                    + monitoredInteractionData.getMonitoredUserID() + " InteractionID:" + monitoredInteractionData.getId());
            interactionExe.observe(supervisorUserId, monitoredInteractionData.getMonitoredUserID(),
                    monitoredInteractionData.getId());
            
            //the Observed  interaction will come in from CCS as a NEW interaction, with an ID composed of supervisorID+"M"+contactId
            String newInteractionId = supervisorUserId + "M" + monitoredInteractionData.getContactID();
        	LOG.info("createInteractionButtons() creating a new form with the newly composed id of :" + newInteractionId);
        	
        	WebchatForm newWebchatForm= new WebchatForm(newInteractionId,interactionExe, css);
        	webchatForms.put(newInteractionId, newWebchatForm);
        });

        buttonWhisper = new Button(whisperButtonText);
        buttonWhisper.getStyleClass().add("agentButton");
        buttonWhisper.setDisable(true);
        buttonWhisper.setOnAction((ActionEvent e) -> {
            MonitoredInteractionData monitoredInteraction = getSelectedMonitoredInteraction();
            LOG.info("WhisperButton SupervisorID: " + supervisorUserId + " UserId:"
                    + monitoredInteraction.getMonitoredUserID() + " InteractionID:" + monitoredInteraction.getId());
            interactionExe.whisper(supervisorUserId, monitoredInteraction.getMonitoredUserID(),
                    monitoredInteraction.getId());
        });

        buttonBargein = new Button(bargeinButtonText);
        buttonBargein.getStyleClass().add("agentButton");
        buttonBargein.setDisable(true);
        buttonBargein.setOnAction((ActionEvent e) -> {
            MonitoredInteractionData monitoredInteraction = getSelectedMonitoredInteraction();
            LOG.info("BargeInButton SupervisorID: " + supervisorUserId + " UserId:"
                    + monitoredInteraction.getMonitoredUserID() + " InteractionID:" + monitoredInteraction.getId());
            interactionExe.bargeIn(supervisorUserId, monitoredInteraction.getMonitoredUserID(),
                    monitoredInteraction.getId());
        });

        buttonHold = new Button(holdButtonText);
        buttonHold.getStyleClass().add("agentButton");
        buttonHold.setDisable(true);
        buttonHold.setOnAction((ActionEvent e) -> {
            MonitoredInteractionData monitoredInteraction = getSelectedMonitoredInteraction();

            // Find the supervisors interaction so we can hold/unhold it
            InteractionData interaction = findInteraction(supervisorUserId, monitoredInteraction.getContactID());
            if (interaction != null) {

                // Toggle the button label and send correct action
                // according to capabilities
                if (interaction.canUnhold()) {
                    LOG.info("HoldButton:UnHold:SupervisorID: " + supervisorUserId + " ContactID:"
                            + monitoredInteraction.getContactID() + " InteractionID:" + interaction.getId());
                    buttonHold.setText(holdButtonText);
                    interactionExe.unHold(interaction.getId());
                } else {
                    LOG.info("HoldButton:Hold:SupervisorID: " + supervisorUserId + " ContactID:"
                            + monitoredInteraction.getContactID() + " InteractionID:" + interaction.getId());
                    buttonHold.setText(unHoldButtonText);
                    interactionExe.hold(interaction.getId());
                }
            } else
                LOG.info("HoldButton SupervisorID: " + supervisorUserId + " ContactID:"
                        + monitoredInteraction.getContactID() + " Interaction not found");
        });

        buttonEnd = new Button(endButtonText);
        buttonEnd.getStyleClass().add("agentButton");
        buttonEnd.setDisable(true);
        buttonEnd.setOnAction((ActionEvent e) -> {
            MonitoredInteractionData monitoredInteraction = getSelectedMonitoredInteraction();

            // Find the supervisors interaction so we can end it
            InteractionData interaction = findInteraction(supervisorUserId, monitoredInteraction.getContactID());
            if (interaction != null) {
                LOG.info("endButton:SupervisorID: " + supervisorUserId + " ContactID:"
                        + monitoredInteraction.getContactID() + " InteractionID:" + interaction.getId());
                interactionExe.end(interaction.getId());
            } else
                LOG.info("EndButton SupervisorID: " + supervisorUserId + " ContactID:"
                        + monitoredInteraction.getContactID() + " Interaction not found");
        });
    }

    private void refreshInteractionControls() {
        MonitoredInteractionData monitoredInteraction = getSelectedMonitoredInteraction();
        if (monitoredInteraction != null) {
            // There is a valid monitored interaction selected
            InteractionData interaction = findInteraction(supervisorUserId, monitoredInteraction.getContactID());
            monitoredInteraction.updateSupervisorInteraction(interaction);

            if (interaction != null) {
                // There is a related interaction found on the supervisor
                String agentIds = "[]";
                List<String> agents = null;
                agents = findAgentsOnContact(interaction.getContactID());
                if (agents != null) {
                    if (!agents.isEmpty()) {
                        agentIds = agents.toString();
                    }
                }
                conStatusField.setText("Active Contact: " + interaction.getContactID() + " Agents: " + agentIds
                        + " Mode: " + interaction.getParticipationReasonString() + " State: " + interaction.getState());
                setHoldButtonState(interaction.canHold(), interaction.canUnhold());
                buttonEnd.setDisable(!interaction.canEnd());
            } else {
                conStatusField.setText(BLANKCONTACTINFO);
                setHoldButtonState(false, false);
                buttonEnd.setDisable(true);
            }
            this.sideBar.update(monitoredInteraction);
            buttonObserve.setDisable(!monitoredInteraction.canObserveCalculated());
            buttonBargein.setDisable(!monitoredInteraction.canBargeInCalculated());
            buttonWhisper.setDisable(!monitoredInteraction.canWhisperCalculated());
            LOG.trace("refreshInteractionControls()" + " obs=" + monitoredInteraction.canObserve() + " whisp="
                    + monitoredInteraction.canWhisper() + " bge=" + monitoredInteraction.canBargeIn());
        } else {
            // There is no valid monitored interaction selected
            buttonObserve.setDisable(true);
            buttonBargein.setDisable(true);
            buttonWhisper.setDisable(true);
            buttonEnd.setDisable(true);
            setHoldButtonState(false, false);
            conStatusField.setText(BLANKCONTACTINFO);
            this.sideBar.update(new MonitoredInteractionData(null));
        }

    }

    private void setHoldButtonState(boolean canHold, boolean canUnhold) {
        if (!canHold && !canUnhold) {
            buttonHold.setText(holdButtonText);
            buttonHold.setDisable(true);
        } else if (canHold) {
            buttonHold.setText(holdButtonText);
            buttonHold.setDisable(false);
        } else if (canUnhold) {
            buttonHold.setText(unHoldButtonText);
            buttonHold.setDisable(false);
        }
    }

    private void createAgentDataTable() {

        monitorTable.setEditable(true);
        monitorTable.setPlaceholder(new Label(BLANKMONITORDATA));
        monitorTable.getStyleClass().add("table");

        TableColumn<MonitoredUserData, CheckBox> activeCol = new TableColumn<MonitoredUserData, CheckBox>("");

        // Any change in tickbox is reflected in the data and vice versa
        activeCol.setCellValueFactory(
                new Callback<TableColumn.CellDataFeatures<MonitoredUserData, CheckBox>, ObservableValue<CheckBox>>() {

                    @Override
                    public ObservableValue<CheckBox> call(
                            TableColumn.CellDataFeatures<MonitoredUserData, CheckBox> arg0) {
                        MonitoredUserData user = arg0.getValue();

                        CheckBox checkBox = new CheckBox();

                        checkBox.selectedProperty().setValue(user.isSelected());
                        checkBox.selectedProperty().addListener(new ChangeListener<Boolean>() {
                            @Override
                            public void changed(ObservableValue<? extends Boolean> ov, Boolean old_val,
                                    Boolean new_val) {

                                user.setIsSelected(new_val);
                                checkMonitoredUsers();
                            }
                        });

                        return new SimpleObjectProperty<>(checkBox);
                    }
                });

        TableColumn<MonitoredUserData, String> agentCol = new TableColumn<MonitoredUserData, String>("Agent");
        agentCol.setCellValueFactory(
                new Callback<TableColumn.CellDataFeatures<MonitoredUserData, String>, ObservableValue<String>>() {
                    @Override
                    public ObservableValue<String> call(TableColumn.CellDataFeatures<MonitoredUserData, String> p) {
                        return new ReadOnlyObjectWrapper<String>(
                                p.getValue().getLoginId() + "[" + p.getValue().getFullName() + "]");
                    }
                });

        TableColumn<MonitoredUserData, String> stateCol = new TableColumn<>("State");
        stateCol.setCellValueFactory(
                new Callback<TableColumn.CellDataFeatures<MonitoredUserData, String>, ObservableValue<String>>() {
                    @Override
                    public ObservableValue<String> call(TableColumn.CellDataFeatures<MonitoredUserData, String> p) {
                        return new ReadOnlyObjectWrapper<String>(p.getValue().getState().toString());
                    }
                });

        monitorTable.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY);
        activeCol.setMinWidth(32.0);
        activeCol.setMaxWidth(32.0);
        agentCol.setMaxWidth(1f * Integer.MAX_VALUE * 60);
        stateCol.setMaxWidth(1f * Integer.MAX_VALUE * 30);
        monitorTable.setItems(monitoredUserItems);
        monitorTable.getColumns().add(activeCol);
        monitorTable.getColumns().add(agentCol);
        monitorTable.getColumns().add(stateCol);

        // Row selection listener, called when a row is clicked
        monitorTable.getSelectionModel().selectedItemProperty().addListener((obs, oldSelection, newSelection) -> {
            if (newSelection != null) {
                buttonForceReady.setDisable(!newSelection.canForceReady());
                buttonForceNotReady.setDisable(!newSelection.canForceNotReady());
                buttonForceLogout.setDisable(!newSelection.canForceLogout());
            }
        });
    }

    private void createInteractionTable() {
        monitoredInteractionTable.setEditable(true);
        monitoredInteractionTable.setPlaceholder(new Label("No interactions"));
        monitoredInteractionTable.getStyleClass().add("table");
        TableColumn<MonitoredInteractionData, String> agent = new TableColumn<>("Agent");
        agent.setCellValueFactory(
                new Callback<TableColumn.CellDataFeatures<MonitoredInteractionData, String>, ObservableValue<String>>() {
                    @Override
                    public ObservableValue<String> call(
                            TableColumn.CellDataFeatures<MonitoredInteractionData, String> p) {
                        return new ReadOnlyObjectWrapper<String>(p.getValue().getMonitoredUserID());
                    }
                });

        TableColumn<MonitoredInteractionData, String> address = new TableColumn<>("Address");
        address.setCellValueFactory(
                new Callback<TableColumn.CellDataFeatures<MonitoredInteractionData, String>, ObservableValue<String>>() {
                    @Override
                    public ObservableValue<String> call(
                            TableColumn.CellDataFeatures<MonitoredInteractionData, String> p) {
                        return new ReadOnlyObjectWrapper<String>(p.getValue().getAddress());
                    }
                });

        TableColumn<MonitoredInteractionData, String> callType = new TableColumn<>("Type");
        callType.setCellValueFactory(
                new Callback<TableColumn.CellDataFeatures<MonitoredInteractionData, String>, ObservableValue<String>>() {
                    @Override
                    public ObservableValue<String> call(
                            TableColumn.CellDataFeatures<MonitoredInteractionData, String> p) {
                        return new ReadOnlyObjectWrapper<String>(p.getValue().getContactType().name());
                    }
                });

        TableColumn<MonitoredInteractionData, String> contactID = new TableColumn<>("ContactID");
        contactID.setCellValueFactory(
                new Callback<TableColumn.CellDataFeatures<MonitoredInteractionData, String>, ObservableValue<String>>() {
                    @Override
                    public ObservableValue<String> call(
                            TableColumn.CellDataFeatures<MonitoredInteractionData, String> p) {
                        return new ReadOnlyObjectWrapper<String>(p.getValue().getContactID());
                    }
                });

        TableColumn<MonitoredInteractionData, String> state = new TableColumn<>("State");
        state.setCellValueFactory(
                new Callback<TableColumn.CellDataFeatures<MonitoredInteractionData, String>, ObservableValue<String>>() {
                    @Override
                    public ObservableValue<String> call(
                            TableColumn.CellDataFeatures<MonitoredInteractionData, String> p) {
                        return new ReadOnlyObjectWrapper<String>(p.getValue().getState().toString());
                    }
                });

        TableColumn<MonitoredInteractionData, String> callingAddressName = new TableColumn<>("Calling");
        callingAddressName.setCellValueFactory(
                new Callback<TableColumn.CellDataFeatures<MonitoredInteractionData, String>, ObservableValue<String>>() {
                    @Override
                    public ObservableValue<String> call(
                            TableColumn.CellDataFeatures<MonitoredInteractionData, String> p) {
                        return new ReadOnlyObjectWrapper<String>(p.getValue().getCallingAddress());
                    }
                });

        TableColumn<MonitoredInteractionData, String> stateReason = new TableColumn<>("Reason");
        stateReason.setCellValueFactory(
                new Callback<TableColumn.CellDataFeatures<MonitoredInteractionData, String>, ObservableValue<String>>() {
                    @Override
                    public ObservableValue<String> call(
                            TableColumn.CellDataFeatures<MonitoredInteractionData, String> p) {
                        return new ReadOnlyObjectWrapper<String>(p.getValue().getStateReason().toString());
                    }
                });

        monitoredInteractionTable.setItems(monitoredInteractionItems);
        monitoredInteractionTable.getColumns().add(agent);
        monitoredInteractionTable.getColumns().add(address);
        monitoredInteractionTable.getColumns().add(callType);
        monitoredInteractionTable.getColumns().add(contactID);
        monitoredInteractionTable.getColumns().add(state);
        monitoredInteractionTable.getColumns().add(callingAddressName);
        monitoredInteractionTable.getColumns().add(stateReason);
        monitoredInteractionTable.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY);
        monitoredInteractionTable.setPrefHeight(5000.0);

        // Row selection listener, called when a row is clicked
        monitoredInteractionTable.getSelectionModel().selectedItemProperty()
                .addListener((obs, oldSelection, newSelection) -> {
                    refreshInteractionControls();
                });
    }

    public void show() {
        supervisorStage.show();
        if (!supervisorStage.isFocused()) {
            supervisorStage.toFront();
        }
    }

    public void clearData() {
        String methodName = "clearData() ";
        LOG.info(methodName + "clearing data");

        shouldMonitor = false;
        checkMonitoredUsers();
        checkMonitoring();

        shouldMonitor = supervisorStage.isShowing();
    }

    public void removeData() {
        String methodName = "removeData() ";
        LOG.trace(methodName + "removing data");

        shouldMonitor = false;
        checkMonitoredUsers();
        checkMonitoring();

        superCombo.setValue(null);
        userItems.clear();
        interactionItems.clear();
        monitoredUserItems.clear();
        monitoredInteractionItems.clear();
        shouldMonitor = supervisorStage.isShowing();
        supervisorUserId = null;
        areMonitoringSupervisor = false;
        monitoredUserIds = new ArrayList<>();
        areMonitoringUsers = false;
        allCheckBox.setSelected(false);

        sideBar.update(new InteractionData(null));
    }

    // Return the current user selection or a dummy value if there is none
    protected UserData getCurrentItemSelection() {
        String methodName = "getCurrentItemSelection() ";
        String currentSelection = superCombo.getValue();
        if (currentSelection != null) {
            LOG.trace(methodName + "current supervisor selection = " + currentSelection);
            return getUserItem(currentSelection);
        } else {
            LOG.trace(methodName + "current supervisor selection is NULL, returning null");
            return null;
        }
    }

    public UserData getUserItem(String id) {
        String methodName = "getUserItem() ";
        LOG.trace(methodName + " ID:" + id);
        for (UserData item : this.userItems) {
            if (item.getId().equals(id)) {
                LOG.trace(methodName + "item found");
                return item;
            }
        }
        return null;
    }

    public String getSelectedInteractionId() {
        String selectedInteractionId = null;

        MonitoredInteractionData selectedInteraction;
        selectedInteraction = getSelectedMonitoredInteraction();
        if (selectedInteraction != null) {
            selectedInteractionId = selectedInteraction.getId();

        }
        return selectedInteractionId;
    }

    public MonitoredInteractionData getSelectedMonitoredInteraction() {
        return monitoredInteractionTable.getSelectionModel().getSelectedItem();
    }

    public void UpdateUserData(List<UserData> data) {
        // Need to convert to use data objects before setting....
        ObservableList<UserData> newData = FXCollections.observableArrayList();
        newData.addAll(data);
        this.updateUser(newData);
    }

    public void UpdateUserData(UserData data) {
        this.updateUser(data);
    }

    // This method is responsible for updating the gui with any new user related
    // data
    public void updateUser(ObservableList<UserData> data) {
        String methodName = "updateUser() ";
        LOG.trace(methodName + "new count =  " + data.size());
        userItems.clear();
        this.userItems.addAll(data);
        refreshListsAndRedraw();
    }

    // This version assumes only the new data object needs to be updated
    public void updateUser(UserData data) {
        String methodName = "updateUser() ";
        LOG.trace(methodName + "Data:" + data.getId());
        // Look in existing list of entry
        UserData itemFound = null;
        for (UserData item : this.userItems) {
            if (item.getId().equals(data.getId())) {
                // if there update it
                itemFound = item;
                break;
            }
        }
        if (itemFound != null) {
            LOG.trace(methodName + "existing entry found");
            this.userItems.remove(itemFound);
            if (data.isDeleted()) {
                LOG.trace(methodName + "update is a deletion update, no adding new data");
            } else {
                LOG.trace(methodName + "updating entry");
                this.userItems.add(data);
            }
        } else if (data.isDeleted()) {
            LOG.trace(methodName + "existing entry not found, but update is a delete so NOT adding");
        } else {
            LOG.trace(methodName + "existing entry not found, adding");
            this.userItems.add(data);
        }
        refreshListsAndRedraw();
    }

    public void UpdateMonitoredUserData(List<MonitoredUserData> data) {
        // Need to convert to use data objects before setting....
        ObservableList<MonitoredUserData> newData = FXCollections.observableArrayList();
        newData.addAll(data);
        this.updateMonitoredUser(newData);
    }

    public void UpdateMonitoredUserData(MonitoredUserData data) {
        this.updateMonitoredUser(data);
    }

    // This method is responsible for updating the gui with any new monitored
    // user related data
    public void updateMonitoredUser(ObservableList<MonitoredUserData> data) {
        String methodName = "updateMonitoredUser() ";
        LOG.trace(methodName + "new count =  " + data.size());

        List<String> currentMultiSelection = new ArrayList<>();

        for (MonitoredUserData user : monitoredUserItems) {
            if (user.isSelected()) {
                currentMultiSelection.add(user.getId());
            }
        }
        LOG.trace(methodName + "current monitored selection = " + currentMultiSelection);

        // If some rows are selected
        if (currentMultiSelection.size() > 0) {
            // Check if those selected rows still exist in the new data
            // and set them as selected in that
            for (String selectedId : currentMultiSelection) {
                for (MonitoredUserData user : data) {
                    if (user.getId().equals(selectedId)) {
                        user.setIsSelected(true);
                    }
                }
            }
        }
        monitoredUserItems.clear();
        this.monitoredUserItems.addAll(data);
        refreshListsAndRedraw();
    }

    // This version assumes only the new data object needs to be updated
    public void updateMonitoredUser(MonitoredUserData data) {
        String methodName = "updateMonitoredUser() ";
        LOG.trace(methodName + "ID:" + data.getId());

        MonitoredUserData selectedItem = monitorTable.getSelectionModel().getSelectedItem();
        if (selectedItem == null) {
            LOG.trace(methodName + "selected item:NULL");
        } else {
            LOG.trace(methodName + "selected item:" + selectedItem.getId());
        }

        // Look in existing list of entry
        MonitoredUserData itemFound = null;
        for (MonitoredUserData item : this.monitoredUserItems) {
            if (item.getId().equals(data.getId())) {
                // if there update it
                itemFound = item;

                break;
            }
        }
        if (itemFound != null) {
            LOG.trace(methodName + "existing entry found");
            boolean wasSelected = itemFound.isSelected();
            this.monitoredUserItems.remove(itemFound);
            if (data.isDeleted()) {
                LOG.trace(methodName + "update is a deletion update, no adding new data");
            } else {
                LOG.trace(methodName + "updating entry");
                this.monitoredUserItems.add(data);

                // maintain its selected state
                int i = this.monitoredUserItems.indexOf(data);
                if (i != -1) {
                    this.monitoredUserItems.get(i).setIsSelected(wasSelected);
                }
            }
        } else if (data.isDeleted()) {
            LOG.trace(methodName + "existing entry not found, but update is a delete so NOT adding");
        } else {
            LOG.trace(methodName + "existing entry not found, adding");
            this.monitoredUserItems.add(data);
        }

        // Re-select the equivalent newly added item
        if (selectedItem != null) {
            int i = 0;
            for (MonitoredUserData user : monitorTable.getItems()) {
                if (user.getId().equals(selectedItem.getId())) {
                    monitorTable.getSelectionModel().select(i);
                    break;
                }
                i++;
            }
        }

        refreshListsAndRedraw();
    }

    public void UpdateMonitoredInteractionData(List<MonitoredInteractionData> data) {
        // Need to convert to use data objects before setting....
        ObservableList<MonitoredInteractionData> newData = FXCollections.observableArrayList();
        newData.addAll(data);
        this.updateMonitoredInteraction(newData);
    }

    // This method is responsible for updating the gui with any new monitored
    // interaction related data
    public void updateMonitoredInteraction(ObservableList<MonitoredInteractionData> data) {
        String methodName = "updateMonitoredInteraction() ";
        LOG.trace(methodName + "new count =  " + data.size());
        monitoredInteractionItems.clear();
        this.monitoredInteractionItems.addAll(data);
        refreshListsAndRedraw();
    }

    // This version assumes only the new data object needs to be updated
    public void updateMonitoredInteraction(MonitoredInteractionData data) {
        String methodName = "updateMonitoredInteraction() ";
        LOG.trace(methodName + "Data:" + data);
        // Look in existing list of entry
        MonitoredInteractionData itemFound = null;

        for (MonitoredInteractionData item : this.monitoredInteractionItems) {
            if (item.getId().equals(data.getId())) {
                // if there update it
                itemFound = item;
                break;
            }
        }
        if (itemFound != null) {
            LOG.trace(methodName + "existing entry found");

            if (data.isDeleted()) {
                LOG.trace(methodName + "update is a deletion update, removing data");
                this.monitoredInteractionItems.remove(itemFound);
            } else {
                LOG.trace(methodName + "updating entry");
                itemFound.update(data);
                this.monitoredInteractionTable.refresh();
                MonitoredInteractionData currentSelection = monitoredInteractionTable.getSelectionModel()
                        .getSelectedItem();
                if (currentSelection != null) {
                    // If we are updating the entry that is currently selected
                    // then also update states
                    if (currentSelection.getId().equals(data.getId())) {
                        refreshInteractionControls();
                    }
                }
            }
        } else {
            LOG.trace(methodName + "existing entry not found, adding");
            this.monitoredInteractionItems.add(data);
            MonitoredInteractionData currentSelection = monitoredInteractionTable.getSelectionModel().getSelectedItem();
            if (currentSelection != null) {
                // If we are adding the entry that is currently selected then
                // also update states
                if (currentSelection.getId().equals(data.getId())) {
                    refreshInteractionControls();
                }
            }
        }
        refreshListsAndRedraw();
    }

    public List<String> findAgentsOnContact(String contactId) {

        List<String> theList = new ArrayList<>();

        if (notNullOrEmpty(contactId) && !monitoredInteractionItems.isEmpty()) {
            for (MonitoredInteractionData item : this.monitoredInteractionItems) {
                if (item.getContactID().equals(contactId)) {
                    theList.add(item.getMonitoredUserID());
                }
            }
        }

        return theList;
    }

    // This version assumes only the new data object needs to be updated
    public void updateInteraction(InteractionData data) {

        String methodName = "updateInteraction() ";
        LOG.trace(methodName + "DataID:" + data.getId());
        // Look in existing list of entry
        InteractionData itemFound = null;

        for (InteractionData item : this.interactionItems) {
            if (item.getId().equals(data.getId())) {
                // if there update it
                itemFound = item;
                break;
            }
        }
        if (itemFound != null) {
            LOG.trace(methodName + "existing entry found");
            this.interactionItems.remove(itemFound);
            if (data.isDeleted()) {
                LOG.trace(methodName + "update is a deletion, not adding new data");
                refreshInteractionControls();
            } else {
                LOG.trace(methodName + "updating entry");
                this.interactionItems.add(data);
                refreshInteractionControls();
            }
        } else if (data.isDeleted()) {
            LOG.trace(methodName + "existing entry not found, but update is a delete so NOT adding");
        } else {
            LOG.trace(methodName + "existing entry not found, adding");
            this.interactionItems.add(data);
            refreshInteractionControls();
        }

    }

    public InteractionData findInteraction(String userId, String contactId) {

        for (InteractionData item : this.interactionItems) {
            if (item.getUserID().equals(userId) && item.getContactID().equals(contactId)) {
                return item;
            }
        }
        return null;
    }

    /**
     * The match is only done based on contact id, is this enough?????? in the
     * case of a delete thats all we will get, unless we check the other values
     * before finishing processing the delete that is.....
     * 
     * @param userId
     * @param contactId
     * @return
     */
    public MonitoredInteractionData findMonitoredInteraction(InteractionData data) {

        for (MonitoredInteractionData item : this.monitoredInteractionItems) {
            if (item.getContactID().equals(data.getContactID())) {
                return item;
            }
        }
        return null;
    }

    private boolean isNullOrEmpty(String str) {
        if (str == null || str.isEmpty()) {
            return true;
        }

        return false;
    }

    private boolean notNullOrEmpty(String str) {
        if (str != null && !str.isEmpty()) {
            return true;
        }

        return false;
    }

    private void checkMonitoredUsers() {
        String methodName = "checkMonitoredUsers() ";

        LOG.trace(methodName + "+");
        List<String> currentMultiSelection = new ArrayList<>();
        for (MonitoredUserData user : monitoredUserItems) {
            if (user.isSelected()) {
                currentMultiSelection.add(user.getId());
            }
        }

        LOG.trace(methodName + "currentMultiSelection = " + currentMultiSelection);

        if (shouldMonitor) {
            LOG.trace(methodName + "should monitor = true");
            if (!areMonitoringUsers) {
                LOG.trace(methodName + "areMonitoringUsers = false");
                monitoredUserIds.clear();
                if (currentMultiSelection.size() > 0) {
                    for (String userId : currentMultiSelection) {
                        monitoredUserIds.add(userId);
                        this.monitoredExe.monitorInteractions(supervisorUserId, userId);
                    }
                    areMonitoringUsers = true;
                    LOG.trace(methodName + "monitoredUserIds are now = " + monitoredUserIds);
                }
            } else {
                LOG.trace(methodName + "areMonitoringUsers = true");
                // Already monitoring
                if (currentMultiSelection.size() > 0) {
                    List<String> newList = new ArrayList<>();

                    // Start new subscriptions for any new monitered users
                    for (String userId : currentMultiSelection) {
                        newList.add(userId);
                        if (!monitoredUserIds.contains(userId)) {
                            LOG.trace(methodName + "begin monitoring interactions Id = " + userId);
                            this.monitoredExe.monitorInteractions(supervisorUserId, userId);
                        }
                    }

                    // Stop any existing subscriptions that are no longer valid
                    for (String userId : monitoredUserIds) {
                        if (!newList.contains(userId)) {
                            LOG.trace(methodName + "stop monitoring interactions Id = " + userId);
                            this.monitoredExe.unMonitorInteractions(supervisorUserId, userId);
                            removeUsersInteractions(userId);
                        }
                    }

                    monitoredUserIds = newList;
                } else {
                    LOG.trace(methodName + "stop monitoring interactions as none are selected");
                    // Stop monitoring as none are selected
                    if (!monitoredUserIds.isEmpty()) {
                        for (String userId : monitoredUserIds) {
                            LOG.trace(methodName + "stop monitoring interactions Id = " + userId);
                            this.monitoredExe.unMonitorInteractions(supervisorUserId, userId);
                            removeUsersInteractions(userId);
                        }

                        monitoredUserIds.clear();
                    }
                    areMonitoringUsers = false;
                }
            }
        } else {
            LOG.trace(methodName + "should monitor = false");
            // Should not be monitoring
            if (areMonitoringUsers) {
                if (!monitoredUserIds.isEmpty()) {
                    LOG.trace(methodName + "stop monitoring interactions as we should not be monitoring");
                    for (String userId : monitoredUserIds) {
                        LOG.trace(methodName + "stop monitoring interactions Id = " + userId);
                        this.monitoredExe.unMonitorInteractions(supervisorUserId, userId);
                    }

                    monitoredUserIds.clear();
                }
                areMonitoringUsers = false;
            }
        }
        LOG.trace(methodName + "-");
    }

    private void checkMonitoring() {
        String methodName = "checkMonitoring() ";

        LOG.trace(methodName + "+");
        if (shouldMonitor) {
            LOG.trace(methodName + "should monitor = true");
            // Start the notification streams for any selected items if not
            // already
            if (!areMonitoringSupervisor) {
                LOG.trace(methodName + "areMonitoringSupervisor = false");
                if (isNullOrEmpty(supervisorUserId)) {
                    LOG.trace(methodName + "supervisorUserId empty so using current selection");
                    supervisorUserId = superCombo.getValue();
                }

                if (notNullOrEmpty(supervisorUserId)) {
                    UserData checkUser = getUserItem(supervisorUserId);
                    if (checkUser != null && checkUser.canMonitorUsers()) {
                        LOG.trace(methodName + "begin monitoring Id = " + supervisorUserId);
                        this.monitoredExe.monitorUsers(supervisorUserId);
                        areMonitoringSupervisor = true;
                    } else {
                        LOG.trace(methodName + "Will not monitor non-supervisor Id = " + supervisorUserId);
                    }
                }
            }
        } else {
            LOG.trace(methodName + "should monitor = false");
            // Stop the notifications
            if (areMonitoringSupervisor) {
                LOG.trace(methodName + "areMonitoringSupervisor = true");
                if (notNullOrEmpty(supervisorUserId)) {
                    LOG.trace(methodName + "stop monitoring Id = " + supervisorUserId);
                    this.monitoredExe.unMonitorUsers(supervisorUserId);
                    areMonitoringSupervisor = false;
                    supervisorUserId = null;
                }
            }
        }
        LOG.trace(methodName + "-");
    }

    // Clean out their interactions when we stop monitoring a particular user
    private void removeUsersInteractions(String userId) {
        String methodName = "removeUsersInteractions() ";
        ObservableList<MonitoredInteractionData> toBeRemoved = FXCollections.observableArrayList();
        LOG.trace(methodName + "clearing interactions table for userId = " + userId);

        if (monitoredInteractionItems.size() > 0) {
            for (MonitoredInteractionData inter : monitoredInteractionItems) {
                if (inter.getMonitoredUserID().equals(userId)) {
                    toBeRemoved.add(inter);
                }
            }

            if (toBeRemoved.size() > 0) {
                monitoredInteractionItems.removeAll(toBeRemoved);
            }
        }
    }

    private void refreshListsAndRedraw() {
        refreshLists();
        refreshInteractionControls();
    }

    private void refreshLists() {
        // Check if the current selection is still in the list of users
        String methodName = "refreshLists() ";

        // Refresh monitored user list
        String currentSelection = superCombo.getValue();

        boolean stillExists = false;
        for (Data user : this.userItems) {
            if (user.getId().equals(currentSelection)) {
                stillExists = true;
            }
        }

        // If it is save this value to set it again
        superCombo.getItems().clear();
        for (UserData user : this.userItems) {
            LOG.trace(methodName + "combo user = " + user.getId() + " canMonitor? " + user.canMonitorUsers());
            if (user.canMonitorUsers()) {
                LOG.trace(methodName + "added supervisor");
                superCombo.getItems().add(user.getId());
            }
        }

        if (stillExists) {
            LOG.trace(methodName + "existing user selection still exists:" + currentSelection);
            superCombo.setValue(currentSelection);
        } else if (this.userItems.size() > 0) {
            LOG.trace(methodName + "existing user selection does not exist, setting to first element");
            superCombo.setValue(this.userItems.get(0).getId());
        } else {
            LOG.trace(methodName + "user list is empty");
        }

        // Make sure we have the most up to date selection of supervisor
        currentSelection = superCombo.getValue();
        if (notNullOrEmpty(currentSelection) && !currentSelection.equals(supervisorUserId)) {
            // Supervisor selection has changed and is valid
            LOG.trace(methodName + "Supervisor selection has changed and is valid");

            // Clear down existing subscriptions
            if (notNullOrEmpty(supervisorUserId) && areMonitoringSupervisor) {
                LOG.trace(methodName + "clearing old subscription for " + supervisorUserId);
                this.monitoredExe.unMonitorUsers(supervisorUserId);
                areMonitoringSupervisor = false;
            }

            // Start new subscription if necessary
            if (shouldMonitor) {
                supervisorUserId = currentSelection;
                LOG.trace(methodName + "starting new subscription for " + supervisorUserId);
                this.monitoredExe.monitorUsers(supervisorUserId);
                areMonitoringSupervisor = true;
            }
        }
        LOG.trace(methodName + "interaction items = " + monitoredInteractionItems);
    }

    
    public WebchatForm getWebChatFormForContact(String id) {
    	LOG.info("getWebchatFormForContact called. Id:" + id);
    	WebchatForm form = webchatForms.get(id);
    	return form;   	 
	}

}
