content: /* 
   * Copyright (C) 2015 Adrien Guille <adrien.guille@univ-lyon2.fr>
   *
   * 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, either version 3 of the License, or
   * (at your option) any later version.
   *
   * 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, see <http://www.gnu.org/licenses/>.
   */
  package main.java.fr.ericlab.sondy.core.ui;
  
  import java.time.Instant;
  import java.util.Date;
  import java.util.HashMap;
  import java.util.Set;
  import java.util.concurrent.ExecutorService;
  import java.util.concurrent.Executors;
  import java.util.logging.Level;
  import java.util.logging.Logger;
  import javafx.beans.value.ObservableValue;
  import javafx.collections.FXCollections;
  import javafx.collections.ObservableList;
  import javafx.concurrent.Task;
  import javafx.concurrent.WorkerStateEvent;
  import main.java.fr.ericlab.sondy.core.app.Main;
  import javafx.event.ActionEvent;
  import javafx.event.EventHandler;
  import javafx.geometry.Insets;
  import javafx.geometry.Orientation;
  import javafx.geometry.Pos;
  import javafx.scene.Scene;
  import javafx.scene.chart.BarChart;
  import javafx.scene.chart.CategoryAxis;
  import javafx.scene.chart.NumberAxis;
  import javafx.scene.chart.XYChart;
  import javafx.scene.control.Button;
  import javafx.scene.control.ChoiceBox;
  import javafx.scene.control.Label;
  import javafx.scene.control.ListView;
  import javafx.scene.control.Separator;
  import javafx.scene.control.TableColumn;
  import javafx.scene.control.TableView;
  import javafx.scene.control.TextField;
  import javafx.scene.control.cell.PropertyValueFactory;
  import javafx.scene.control.cell.TextFieldTableCell;
  import javafx.scene.input.MouseButton;
  import javafx.scene.input.MouseEvent;
  import javafx.scene.input.ScrollEvent;
  import javafx.scene.layout.GridPane;
  import javafx.scene.layout.HBox;
  import javafx.scene.layout.VBox;
  import javafx.scene.layout.VBoxBuilder;
  import javafx.scene.paint.Color;
  import javafx.scene.paint.Stop;
  import javafx.scene.shape.Rectangle;
  import javafx.scene.text.Text;
  import javafx.stage.Modality;
  import javafx.stage.Stage;
  import javafx.stage.StageStyle;
  import main.java.fr.ericlab.sondy.algo.Parameter;
  import main.java.fr.ericlab.sondy.algo.influenceanalysis.InfluenceAnalysisMethod;
  import main.java.fr.ericlab.sondy.core.app.AppParameters;
  import main.java.fr.ericlab.sondy.core.structures.Message;
  import main.java.fr.ericlab.sondy.core.utils.ArrayUtils;
  import main.java.fr.ericlab.sondy.core.utils.CustomSwingNode;
  import main.java.fr.ericlab.sondy.core.utils.UIUtils;
  import org.apache.commons.lang3.StringUtils;
  import org.graphstream.graph.Node;
  import org.graphstream.ui.geom.Point3;
  import org.graphstream.ui.swingViewer.View;
  import org.graphstream.ui.swingViewer.Viewer;
  import org.reflections.Reflections;
  
  /**
   *
   *   @author Adrien GUILLE, Laboratoire ERIC, Université Lumière Lyon 2
   */
  public class InfluenceAnalysisUI {
      public GridPane grid;
      
      // Methods
      ListView<String> methodList;
      // - parameters
      TableView<Parameter> parameterTable;
      HashMap<String,String> methodMap;
      // - apply
      Label methodDescriptionLabel;
      Button applyButton;
      InfluenceAnalysisMethod selectedMethod;
      
      // Influence analysis
      // - network visualization
      CustomSwingNode swingNode;
      View view;
      Button zoomInButton;
      Button zoomOutButton;
      // - rank distribution chart
      CategoryAxis xAxis;
      NumberAxis yAxis;
      BarChart<String,Number> rankDistributionChart;
      
      public InfluenceAnalysisUI(){
          // Initializing the main grid
          grid = new GridPane();
          grid.setPadding(new Insets(5, 5, 5, 5));
          
          // Adding separators
          grid.add(new Text("Available methods"),0,0);
          grid.add(new Separator(),0,1);
          grid.add(new Text("Network visualization"),0,3);
          grid.add(new Separator(),0,4);
          
          availabeMethodsUI();
          rankAnalysisUI();
      }
      
      public final void availabeMethodsUI(){
          initializeAvailableMethodList();
          methodDescriptionLabel = new Label("Selected method description");
          methodDescriptionLabel.setId("smalltext");
          UIUtils.setSize(methodDescriptionLabel,Main.columnWidthLEFT,24);
          VBox methodsLEFT = new VBox();
          methodsLEFT.getChildren().addAll(methodList,new Rectangle(0,3),methodDescriptionLabel);
          // Right part
          applyButton = createApplyMethodButton();
          UIUtils.setSize(applyButton, Main.columnWidthRIGHT, 24);
          parameterTable = new TableView<>();
          UIUtils.setSize(parameterTable, Main.columnWidthRIGHT, 64);
          initializeParameterTable();
          VBox methodsRIGHT = new VBox();
          methodsRIGHT.getChildren().addAll(parameterTable,new Rectangle(0,3),applyButton);
          // Both parts
          HBox methodsBOTH = new HBox(5);
          methodsBOTH.getChildren().addAll(methodsLEFT,methodsRIGHT);
          grid.add(methodsBOTH,0,2);
      }
      
      public final void rankAnalysisUI(){
          initializeRankDistributionChart();
          HBox rankAnalysisBOTH = new HBox(0);
          rankAnalysisBOTH.getChildren().addAll(createNetworkVisualization(),rankDistributionChart);
          grid.add(rankAnalysisBOTH,0,5);
      }
      
      public final Button createApplyMethodButton(){
          Button button = new Button("Apply method");
          button.setOnAction((ActionEvent ae) -> {
              try {
                  if(AppParameters.event != null){
                      AppParameters.disableUI(true);
                      rankDistributionChart.getData().clear();
                      LogUI.addLogEntry("Running '"+selectedMethod.getName()+"'...");
                      final Task<String> waitingTask = new Task<String>() {
                          @Override
                          public String call() throws Exception {
                              AppParameters.dataset.network.updateAuthorNetwork();
                              Thread job = new Thread(selectedMethod);
                              job.start();
                              job.join();
                              return "" ;
                          }
                      };
                      waitingTask.setOnSucceeded((WorkerStateEvent event1) -> {
                          updateRankDistributionChart();
                          AppParameters.disableUI(false);
                          LogUI.addLogEntry("Done: "+selectedMethod.getLog());
                      }); 
                      ExecutorService executor = Executors.newSingleThreadExecutor();
                      executor.submit(waitingTask);
                  }else{
                      LogUI.addLogEntry("Error: no event selected");
                  }
              } catch (Exception ex) {
                  Logger.getLogger(EventDetectionUI.class.getName()).log(Level.SEVERE, null, ex);
              }
          });
          return button;
      }
      
      public final void initializeAvailableMethodList(){
          methodMap = new HashMap<>();
          methodList = new ListView<>();
          methodList.setOrientation(Orientation.HORIZONTAL);
          UIUtils.setSize(methodList,Main.columnWidthLEFT,64);
          updateAvailableMethods();
          methodList.getSelectionModel().selectedItemProperty().addListener((ObservableValue<? extends String> ov, String old_val, String new_val) -> {
              try {
                  selectedMethod = (InfluenceAnalysisMethod) Class.forName(methodMap.get(new_val)).newInstance();
                  methodDescriptionLabel.setText(selectedMethod.getDescription());
                  parameterTable.setItems(selectedMethod.parameters.list);
              } catch (ClassNotFoundException | InstantiationException | IllegalAccessException ex) {
                  Logger.getLogger(EventDetectionUI.class.getName()).log(Level.SEVERE, null, ex);
              }
          });
      }
      
      public final void initializeParameterTable(){
          parameterTable.setEditable(true);
          TableColumn keyColumn = new TableColumn("Parameter");
          keyColumn.setMinWidth(Main.columnWidthRIGHT/2-1);
          keyColumn.setCellValueFactory(new PropertyValueFactory<>("name"));
          TableColumn valueColumn = new TableColumn("Value");
          valueColumn.setMinWidth(Main.columnWidthRIGHT/2-1);
          valueColumn.setCellFactory(TextFieldTableCell.forTableColumn());
          valueColumn.setCellValueFactory(new PropertyValueFactory<>("value")); 
          valueColumn.setOnEditCommit(
              new EventHandler<TableColumn.CellEditEvent<Parameter, String>>() {
                  @Override
                  public void handle(TableColumn.CellEditEvent<Parameter, String> t) {
                      ((Parameter) t.getTableView().getItems().get(t.getTablePosition().getRow())).setValue(t.getNewValue());
                  }
              }
          );
          parameterTable.getColumns().addAll(keyColumn,valueColumn);
      }
      
      public final VBox createNetworkVisualization(){
          System.setProperty("org.graphstream.ui.renderer", "org.graphstream.ui.j2dviewer.J2DGraphRenderer");
          Viewer viewer = new Viewer(AppParameters.authorNetwork, Viewer.ThreadingModel.GRAPH_IN_ANOTHER_THREAD);
          view = viewer.addDefaultView(false); 
          viewer.enableAutoLayout();
          view.resizeFrame(Main.columnWidthLEFT, 290);
          swingNode = new CustomSwingNode();
          swingNode.setContent(view);
          swingNode.resize(Main.columnWidthLEFT, 290);
          EventHandler<MouseEvent> mouseHandlerGraphClick = new EventHandler<MouseEvent>() { 
              @Override 
              public void handle(MouseEvent mouseEvent) { 
                  if (mouseEvent.getEventType() == MouseEvent.MOUSE_PRESSED) {
                      if(mouseEvent.getButton() == MouseButton.SECONDARY){
                          Node node = (Node) view.findNodeOrSpriteAt(mouseEvent.getX(), mouseEvent.getY());
                          if(node != null){
                              userMessages(node.getId());
                          }
                      }
                      if(mouseEvent.getButton() == MouseButton.PRIMARY){
                          double translateCoeff = view.getCamera().getViewPercent();
                          Point3 center = view.getCamera().getViewCenter();
                          if(mouseEvent.getY()>175){
                              view.getCamera().setViewCenter(center.x, center.y-5*translateCoeff, center.z);
                          }else{
                              view.getCamera().setViewCenter(center.x, center.y+5*translateCoeff, center.z);
                          }
                          if(mouseEvent.getX()>275){
                              view.getCamera().setViewCenter(center.x+5*translateCoeff, center.y, center.z);
                          }else{
                              view.getCamera().setViewCenter(center.x-5*translateCoeff, center.y, center.z);
                          }
                      }
                      if(mouseEvent.getButton() == MouseButton.MIDDLE){
                          view.getCamera().setViewPercent(view.getCamera().getViewPercent()/2);
                      }
                  }
              }
          };   
          EventHandler<ScrollEvent> mouseHandlerGraphScroll = new EventHandler<ScrollEvent>() { 
              @Override
              public void handle(ScrollEvent event) {
                  if(event.getDeltaY() < 0){
                      view.getCamera().setViewPercent(view.getCamera().getViewPercent()*2);
                  }else{
                      view.getCamera().setViewPercent(view.getCamera().getViewPercent()/2);
                  }
              }
          };   
          swingNode.setOnMousePressed(mouseHandlerGraphClick);
          swingNode.setOnScroll(mouseHandlerGraphScroll);
          VBox graphBox = new VBox();
          graphBox.getChildren().addAll(new Rectangle(Main.columnWidthLEFT,0),swingNode);
          initializeNetworkVisualizationStyle();
          return graphBox;
      }
      
      public void initializeNetworkVisualizationStyle(){
          AppParameters.authorNetwork.addAttribute("event","no event");
          AppParameters.authorNetwork.addAttribute("ui.quality");
          AppParameters.authorNetwork.addAttribute("ui.antialias");
          String css = ""
              + "graph { fill-mode: plain; fill-color : #f4f4f4;}"
              + "edge  { fill-mode: plain; fill-color: rgba(0,0,0,85); size:0.25px; z-index: 1; arrow-shape: arrow; arrow-size: 4px, 2px;}" 
              + "node  { fill-mode: dyn-plain; fill-color: #4682B4,yellow,red ; size: 6px ;z-index : 2; stroke-mode: plain;}"
              + "node:clicked   { fill-color: black;}";
          AppParameters.authorNetwork.addAttribute("ui.stylesheet", css);
      }
      
      public final void initializeRankDistributionChart(){
          xAxis = new CategoryAxis();
          yAxis = new NumberAxis();
          xAxis.setLabel("Rank");
          xAxis.setTickLength(5);
          yAxis.setTickLabelsVisible(false);
          yAxis.setTickMarkVisible(false);
          rankDistributionChart = new BarChart(xAxis,yAxis);
          rankDistributionChart.setLegendVisible(false);
          UIUtils.setSize(rankDistributionChart, Main.columnWidthRIGHT+5, 300);
          rankDistributionChart.setCategoryGap(0);
      }
      
      public final void updateRankDistributionChart(){
          Stop[] stops = new Stop[] { new Stop(0, Color.STEELBLUE), new Stop(0.5, Color.YELLOW), new Stop(1, Color.RED)};
  
          rankDistributionChart.getData().clear();
          int[] rankDistribution = selectedMethod.rankedUsers.extractRankDistribution();
          XYChart.Series series = new XYChart.Series();
          for(int i = 0; i < rankDistribution.length; i++){
              series.getData().add(new XYChart.Data(i+"",rankDistribution[i]));
          }
          rankDistributionChart.getData().add(series);
      }
      
      public final void updateAvailableMethods(){
          Reflections reflections = new Reflections("main.java.fr.ericlab.sondy.algo.influenceanalysis");    
          Set<Class<? extends InfluenceAnalysisMethod>> classes = reflections.getSubTypesOf(InfluenceAnalysisMethod.class);   
          for(Class<? extends InfluenceAnalysisMethod> aClass : classes){
              try {
                  InfluenceAnalysisMethod method = (InfluenceAnalysisMethod) Class.forName(aClass.getName()).newInstance();
                  methodList.getItems().add(method.getName());
                  methodMap.put(method.getName(),aClass.getName());
              } catch (ClassNotFoundException | InstantiationException | IllegalAccessException ex) {
                  Logger.getLogger(EventDetectionUI.class.getName()).log(Level.SEVERE, null, ex);
              }
          }
      }
      
      public final void userMessages(String user){
          final Stage stage = new Stage();
          stage.initModality(Modality.APPLICATION_MODAL);
          stage.initStyle(StageStyle.UTILITY);
          stage.setTitle("Messages");
          
          TableView<Message> messageTable = new TableView<>();
          UIUtils.setSize(messageTable, Main.columnWidthLEFT, 360);
          
          TableColumn authorColumn = new TableColumn("Author");
          TableColumn timeColumn = new TableColumn("Timestamp");
          TableColumn textColumn = new TableColumn("Text");
          authorColumn.setCellValueFactory(new PropertyValueFactory<>("author"));
          timeColumn.setCellValueFactory(new PropertyValueFactory<>("time"));
          textColumn.setCellValueFactory(new PropertyValueFactory<>("text"));
          messageTable.getColumns().addAll(authorColumn,timeColumn,textColumn);
          messageTable.setItems(AppParameters.dataset.corpus.getMessages(user));
          
          Text tableText = new Text("User messages ("+messageTable.getItems().size()+")");
          Label topicLabel = new Label();
          topicLabel.setText(AppParameters.event.getTextualDescription());
          Date date = AppParameters.dataset.corpus.start;
          Instant fromInstant = date.toInstant();
          Instant toInstant = date.toInstant();
          String[] interval = AppParameters.event.getTemporalDescription().split(",");
          fromInstant = fromInstant.plusSeconds((long)(Double.parseDouble(interval[0])*24*60*60));
          toInstant = toInstant.plusSeconds((long)(Double.parseDouble(interval[1])*24*60*60));
          topicLabel.setText(AppParameters.event.getTextualDescription());
          Label intervalLabel = new Label();
          intervalLabel.setText("From "+fromInstant+" to "+toInstant);
          
          TextField filterMessagesField1 = new TextField("");
          filterMessagesField1.setPromptText("Type a list of words (separated by a space)");
          UIUtils.setSize(filterMessagesField1, 2*(Main.columnWidthLEFT-5)/3, 24);
          ChoiceBox operatorChoiceBox = new ChoiceBox();
          ObservableList<String> operatorList = FXCollections.observableArrayList();
          operatorList.add("and");
          operatorList.add("or");
          operatorChoiceBox.setItems(operatorList);
          operatorChoiceBox.getSelectionModel().select(0);
          UIUtils.setSize(operatorChoiceBox, (Main.columnWidthLEFT-5)/3, 24);
          HBox filterMessagesHBox = new HBox(5);
          filterMessagesHBox.getChildren().add(filterMessagesField1);
          filterMessagesHBox.getChildren().add(operatorChoiceBox);
          
          Button filterMessagesButton = new Button("Filter messages");
          UIUtils.setSize(filterMessagesButton, Main.columnWidthLEFT, 24);
          filterMessagesButton.setOnAction((ActionEvent ae) -> {
              String[] words = filterMessagesField1.getText().split(" ");
              ObservableList<Message> messages = FXCollections.observableArrayList();
              int operator = operatorChoiceBox.getSelectionModel().getSelectedIndex();
              for(Message message : messageTable.getItems()){
                  String text = message.getText();
                  short[] test = new short[words.length];
                  for(int j = 0; j < words.length; j++){
                      if(StringUtils.containsIgnoreCase(text,words[j])){
                          test[j] = 1;
                      }else{
                          test[j] = 0;
                      }
                  }
                  int testSum = ArrayUtils.sum(test, 0, test.length-1);
                  if(operator==0 && testSum == test.length){
                      messages.add(message);
                  }
                  if(operator==1 && testSum > 0){
                      messages.add(message);
                  }
              }
              messageTable.getItems().clear();
              messageTable.setItems(messages);
              tableText.setText("User messages ("+messageTable.getItems().size()+")");
          });
          
          Scene scene = new Scene(VBoxBuilder.create().children(new Text("User"),new Separator(),new Label(user),tableText,new Separator(),messageTable,filterMessagesHBox,filterMessagesButton).alignment(Pos.CENTER).padding(new Insets(10)).spacing(3).build());
          scene.getStylesheets().add("resources/fr/ericlab/sondy/css/GlobalStyle.css");
          stage.setScene(scene);
          stage.show();
      }
      
  }
  