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 main.java.fr.ericlab.sondy.algo.eventdetection.EventDetectionMethod;
  import main.java.fr.ericlab.sondy.core.app.AppParameters;
  import main.java.fr.ericlab.sondy.core.app.Main;
  import main.java.fr.ericlab.sondy.core.structures.Event;
  import main.java.fr.ericlab.sondy.core.structures.Events;
  import main.java.fr.ericlab.sondy.algo.Parameter;
  import main.java.fr.ericlab.sondy.core.ui.factories.EventTableContextMenu;
  import main.java.fr.ericlab.sondy.core.utils.ArrayUtils;
  import main.java.fr.ericlab.sondy.core.utils.UIUtils;
  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 javafx.event.ActionEvent;
  import javafx.event.EventHandler;
  import javafx.geometry.Insets;
  import javafx.geometry.Orientation;
  import javafx.geometry.Pos;
  import javafx.scene.Group;
  import javafx.scene.Scene;
  import javafx.scene.chart.LineChart;
  import javafx.scene.chart.NumberAxis;
  import javafx.scene.chart.XYChart;
  import javafx.scene.control.Button;
  import javafx.scene.control.ChoiceBox;
  import javafx.scene.control.ContextMenu;
  import javafx.scene.control.Label;
  import javafx.scene.control.ListView;
  import javafx.scene.control.Separator;
  import javafx.scene.control.TableCell;
  import javafx.scene.control.TableColumn;
  import javafx.scene.control.TableColumn.CellEditEvent;
  import javafx.scene.control.TableView;
  import javafx.scene.control.TextField;
  import javafx.scene.control.Tooltip;
  import javafx.scene.control.cell.PropertyValueFactory;
  import javafx.scene.control.cell.TextFieldTableCell;
  import javafx.scene.input.KeyCode;
  import javafx.scene.input.KeyEvent;
  import javafx.scene.input.MouseButton;
  import javafx.scene.input.MouseEvent;
  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.shape.Ellipse;
  import javafx.scene.shape.EllipseBuilder;
  import javafx.scene.shape.LineTo;
  import javafx.scene.shape.MoveTo;
  import javafx.scene.shape.Path;
  import javafx.scene.shape.Rectangle;
  import javafx.scene.text.Font;
  import javafx.scene.text.Text;
  import javafx.stage.Modality;
  import javafx.stage.Stage;
  import javafx.stage.StageStyle;
  import main.java.fr.ericlab.sondy.core.structures.Message;
  import org.reflections.Reflections;
  
  /**
   *
   *   @author Adrien GUILLE, Laboratoire ERIC, Université Lumière Lyon 2
   */
  public class EventDetectionUI {
      public GridPane grid;
      
      // Methods
      ListView<String> methodList;
      // - parameters
      TableView<Parameter> parameterTable;
      HashMap<String,String> methodMap;
      // - apply
      Label methodDescriptionLabel;
      Button applyButton;
      EventDetectionMethod selectedMethod;
      
      // Detected events
      // - filter
      TextField filterEventsField;
      // - list
      TableView<Event> eventTable = new TableView<>();
      Events eventList = new Events();
      // - frequency chart
      NumberAxis xAxis;
      NumberAxis yAxis;
      LineChart<Number,Number> frequencyChart;
      int maxNumberOfCurves = 3;
      Rectangle rectangleSelection;
      
      public EventDetectionUI(){
          // 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("Detected events"),0,3);
          grid.add(new Separator(),0,4);
          
          availabeMethodsUI();
          detectedEventsUI();        
      }
  
      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 detectedEventsUI(){
          // Detected events
          HBox detectedEventsBOTH = new HBox(0);
          initializeEventTable();
          initializeFrequencyChart();
          VBox detectedEventsRIGHT = new VBox(3);
          filterEventsField = new TextField();
          filterEventsField.setPromptText("Filter events");
          UIUtils.setSize(filterEventsField, Main.columnWidthRIGHT, 24);
          final EventHandler<KeyEvent> enterPressed =
              new EventHandler<KeyEvent>() {
                  @Override
                  public void handle(final KeyEvent keyEvent) {
                      if(keyEvent.getCode()==KeyCode.ENTER){
                          eventTable.getItems().clear();
                          selectedMethod.events.filterList(filterEventsField.getText());
                          eventTable.setItems(selectedMethod.events.observableList);
                      }
                  }
              };
          filterEventsField.setOnKeyReleased(enterPressed);
          detectedEventsRIGHT.getChildren().addAll(filterEventsField,eventTable,createTimelineButton());
          detectedEventsBOTH.getChildren().addAll(frequencyChart,detectedEventsRIGHT);
          grid.add(detectedEventsBOTH,0,5);
          rectangleSelection = new Rectangle(0,240);
          rectangleSelection.setOpacity(0.22);
          rectangleSelection.setTranslateY(-28);
          grid.add(rectangleSelection,0,5);
      }
      
      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 = (EventDetectionMethod) 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<CellEditEvent<Parameter, String>>() {
                  @Override
                  public void handle(CellEditEvent<Parameter, String> t) {
                      ((Parameter) t.getTableView().getItems().get(t.getTablePosition().getRow())).setValue(t.getNewValue());
                  }
              }
          );
          parameterTable.getColumns().addAll(keyColumn,valueColumn);
      }
      
      public final void initializeEventTable(){
          eventTable = new TableView<>();
          eventTable.setItems(eventList.observableList);
          UIUtils.setSize(eventTable, Main.columnWidthRIGHT, 247);
          TableColumn textualDescription = new TableColumn("Textual desc.");
          textualDescription.setMinWidth(Main.columnWidthRIGHT/2);
          TableColumn temporalDescription = new TableColumn("Temporal desc.");
          temporalDescription.setMinWidth(Main.columnWidthRIGHT/2-1);
          textualDescription.setCellValueFactory(new PropertyValueFactory<>("textualDescription"));
          temporalDescription.setCellValueFactory(new PropertyValueFactory<>("temporalDescription"));
          EventTableContextMenu tableCellFactory = new EventTableContextMenu(createSelectedEventHandler(), new ContextMenu());
          textualDescription.setCellFactory(tableCellFactory);
          eventTable.getColumns().addAll(textualDescription,temporalDescription);
      }
      
      public final Button createApplyMethodButton(){
          Button button = new Button("Apply method");
          button.setOnAction((ActionEvent ae) -> {
              try {
                  if(!AppParameters.dataset.corpus.preprocessing.equals("")){
                      AppParameters.disableUI(true);
                      eventTable.getItems().clear();
                      frequencyChart.getData().clear();
                      rectangleSelection.setWidth(0);
                      LogUI.addLogEntry("Running '"+selectedMethod.getName()+"'...");
                      final Task<String> waitingTask = new Task<String>() {
                          @Override
                          public String call() throws Exception {
                              Thread job = new Thread(selectedMethod);
                              job.start();
                              job.join();
                              eventList = selectedMethod.events;
                              return "" ;
                          }
                      };
                      waitingTask.setOnSucceeded((WorkerStateEvent event1) -> {
                          eventTable.getItems().clear();
                          eventTable.getItems().addAll(eventList.observableList);
                          AppParameters.disableUI(false);
                          LogUI.addLogEntry("Done: "+selectedMethod.getLog());
                      }); 
                      ExecutorService executor = Executors.newSingleThreadExecutor();
                      executor.submit(waitingTask);
                  }else{
                      LogUI.addLogEntry("Error: no dataset loaded");
                  }
              } catch (Exception ex) {
                  Logger.getLogger(EventDetectionUI.class.getName()).log(Level.SEVERE, null, ex);
              }
          });
          return button;
      }
      
      public final void initializeFrequencyChart(){
          xAxis = new NumberAxis(0,1,1);
          yAxis = new NumberAxis();
          xAxis.setTickLength(5);
          yAxis.setTickLabelsVisible(false);
          yAxis.setTickMarkVisible(false);
          xAxis.setLabel("Time (days)");
          frequencyChart = new LineChart<>(xAxis,yAxis);
          frequencyChart.setLegendVisible(true);
          frequencyChart.setCreateSymbols(false);
          frequencyChart.setTranslateX(-5);
          UIUtils.setSize(frequencyChart, Main.columnWidthLEFT+5, 300);
      }
      
      public final Button createTimelineButton(){
          Button button = new Button("Generate timeline");
          UIUtils.setSize(button,Main.columnWidthRIGHT,24);
          button.setOnAction((ActionEvent ae) -> {
              createTimelineStage();
          });
          return button;
      }
      
      public final void updateAvailableMethods(){
          Reflections reflections = new Reflections("main.java.fr.ericlab.sondy.algo.eventdetection");    
          Set<Class<? extends EventDetectionMethod>> classes = reflections.getSubTypesOf(EventDetectionMethod.class);   
          for(Class<? extends EventDetectionMethod> aClass : classes){
              try {
                  EventDetectionMethod method = (EventDetectionMethod) 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 updateFrequencyChart(){
          xAxis.setTickUnit(0.5);
          xAxis.setUpperBound(AppParameters.dataset.corpus.getLength());
          frequencyChart.getData().clear();
          String[] terms = AppParameters.event.getTextualDescription().split(" ");
          for(int j = 0; j < maxNumberOfCurves && j < terms.length; j++){
              short[] frequency = AppParameters.dataset.corpus.getTermFrequency(terms[j]);
              int windowSize = AppParameters.dataset.corpus.messageDistribution.length/Main.columnWidthLEFT;
              float[] smoothedFrequency;
              if(windowSize > 1){
                  smoothedFrequency = ArrayUtils.smoothArray(frequency, windowSize);    
              }else{
                  smoothedFrequency = ArrayUtils.toFloatArray(frequency);
              }
              XYChart.Series series = new XYChart.Series();
              series.setName(terms[j]);
              for(int i = AppParameters.timeSliceA; i < AppParameters.timeSliceB; i++){
                  double x = AppParameters.dataset.corpus.convertTimeSliceToDay(i);
                  double y = smoothedFrequency[i];
                  series.getData().add(new XYChart.Data(x,y));
              }
              frequencyChart.getData().add(series);
          }
          String[] interval = AppParameters.event.getTemporalDescription().split(",");
          double rectX = (Double.parseDouble(interval[0])/AppParameters.dataset.corpus.getLength())*(Main.columnWidthLEFT-5)+3;
          double rectY = (Double.parseDouble(interval[1])/AppParameters.dataset.corpus.getLength())*(Main.columnWidthLEFT-5)+3;
          rectangleSelection.setTranslateX(rectX);
          rectangleSelection.setWidth(rectY-rectX);
      }
      
      public final EventHandler<MouseEvent> createSelectedEventHandler(){
          EventHandler<MouseEvent> eventHandler = new EventHandler<MouseEvent>() {
              @Override
              public void handle(MouseEvent t) {
                  AppParameters.event = (Event) eventTable.getItems().get(((TableCell)t.getSource()).getIndex());
                  updateFrequencyChart();
                  if(t.getButton() == MouseButton.SECONDARY){
                      eventRelatedMessages();
                  }
              }
          };
          return eventHandler;
      }
      
      public final void eventRelatedMessages(){
          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(AppParameters.event));
          
          Text tableText = new Text("Related 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) -> {
              messageTable.getItems().clear();
              String[] words = filterMessagesField1.getText().split(" ");
              messageTable.setItems(AppParameters.dataset.corpus.getFilteredMessages(AppParameters.event,words,operatorChoiceBox.getSelectionModel().getSelectedIndex()));
              tableText.setText("Related messages ("+messageTable.getItems().size()+")");
          });
          
          Scene scene = new Scene(VBoxBuilder.create().children(new Text("Topic"),new Separator(),topicLabel,new Text("Time interval"),new Separator(),intervalLabel,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();
      }
      
      public void createTimelineStage(){
          HashMap<Integer,Integer> map = new HashMap<>();
          Path path = new Path();
          MoveTo moveTo = new MoveTo();
          moveTo.setX(0);
          moveTo.setY(0);
          LineTo lineTo = new LineTo();
          lineTo.setX(Main.columnWidthLEFT);
          lineTo.setY(0);
          path.getElements().add(moveTo);
          path.getElements().add(lineTo);
          path.setStrokeWidth(4);
          path.setStroke(Color.STEELBLUE);
          path.setTranslateY(30);
          path.setTranslateX(10);
          VBox timelineBox = new VBox(5);
          Group groupTimeline = new Group();
          groupTimeline.getChildren().add(path);
          for(Event event : selectedMethod.events.observableList){
              Date date = AppParameters.dataset.corpus.start;
              Instant fromInstant = date.toInstant();
              Instant toInstant = date.toInstant();
              String[] interval = 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));
              Tooltip tooltip = new Tooltip("topic: "+event.getTextualDescription()+"\ntime interval: from "+fromInstant+" to "+toInstant);
              Ellipse circle = EllipseBuilder.create()
              .centerX(0)
              .centerY(0)
              .radiusX(6)
              .radiusY(6)
              .strokeWidth(3)
              .stroke(Color.web("#f2f2f2"))
              .fill(Color.STEELBLUE)
              .build();
              int yPos = 30;
              int xPos = (int) ((Double.parseDouble(interval[0])/AppParameters.dataset.corpus.getLength())*Main.columnWidthLEFT+10);
              if(map.get(xPos) == null){
                   map.put(xPos, 1);
              }else{
                   int count = map.get(xPos);
                   yPos += 12*count;
                   map.put(xPos, count+1);
              }
              circle.setTranslateY(yPos);
              circle.setTranslateX(xPos);
              Tooltip.install(circle, tooltip);
              groupTimeline.getChildren().add(circle);
          }
          Label startTime = new Label(AppParameters.dataset.corpus.start.toString());
          startTime.setFont(new Font(null,10));
          startTime.setTranslateY(5);
          startTime.setTranslateX(10);
          Label endTime = new Label(AppParameters.dataset.corpus.end.toString());
          endTime.setFont(new Font(null,10));
          endTime.setTranslateY(5);
          endTime.setTranslateX(Main.columnWidthLEFT-145);
          groupTimeline.getChildren().addAll(startTime,endTime);
          final Stage dialogStageTimeline = new Stage();
  
          Button closeButton = new Button("Close");
          UIUtils.setSize(closeButton, Main.columnWidthLEFT, 24);
          closeButton.setOnAction((ActionEvent ae) -> {
              dialogStageTimeline.close();
          });
          
          timelineBox.getChildren().addAll(groupTimeline,closeButton);
          Scene sceneTimeline = new Scene(VBoxBuilder.create().children(groupTimeline,closeButton).alignment(Pos.CENTER).padding(new Insets(10)).spacing(3).build());
          sceneTimeline.getStylesheets().add("resources/fr/ericlab/sondy/css/GlobalStyle.css");
          dialogStageTimeline.initStyle(StageStyle.UNDECORATED);
          dialogStageTimeline.initModality(Modality.APPLICATION_MODAL);
          dialogStageTimeline.setScene(sceneTimeline);
          dialogStageTimeline.show();
      }
  }
  