001package conexp.fx.gui.exploration;
002
003/*
004 * #%L
005 * Concept Explorer FX
006 * %%
007 * Copyright (C) 2010 - 2023 Francesco Kriegel
008 * %%
009 * This program is free software: you can redistribute it and/or modify
010 * it under the terms of the GNU General Public License as
011 * published by the Free Software Foundation, either version 3 of the
012 * License, or (at your option) any later version.
013 * 
014 * This program is distributed in the hope that it will be useful,
015 * but WITHOUT ANY WARRANTY; without even the implied warranty of
016 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
017 * GNU General Public License for more details.
018 * 
019 * You should have received a copy of the GNU General Public
020 * License along with this program.  If not, see
021 * <http://www.gnu.org/licenses/gpl-3.0.html>.
022 * #L%
023 */
024
025import java.util.Collections;
026import java.util.Map;
027import java.util.Set;
028import java.util.concurrent.ConcurrentHashMap;
029import java.util.concurrent.CountDownLatch;
030import java.util.concurrent.atomic.AtomicBoolean;
031import java.util.concurrent.atomic.AtomicReference;
032
033import conexp.fx.core.algorithm.exploration.CounterExample;
034import conexp.fx.core.algorithm.exploration.Expert;
035import conexp.fx.core.collections.relation.RelationEvent;
036import conexp.fx.core.collections.relation.RelationEventHandler;
037import conexp.fx.core.context.Context;
038import conexp.fx.core.context.Implication;
039import conexp.fx.core.context.MatrixContext;
040import conexp.fx.core.context.MatrixContext.AutomaticMode;
041import conexp.fx.core.util.IdGenerator;
042import conexp.fx.gui.context.MatrixContextWidget;
043import conexp.fx.gui.dialog.ErrorDialog;
044import conexp.fx.gui.util.Platform2;
045import javafx.scene.Scene;
046import javafx.scene.control.Button;
047import javafx.scene.control.Label;
048import javafx.scene.layout.BorderPane;
049import javafx.scene.layout.HBox;
050import javafx.stage.Stage;
051import javafx.stage.StageStyle;
052
053public final class HumanExpertP<M> implements Expert<String, M> {
054
055  private final class Question {
056
057    private final String                                          object;
058    private final Implication<String, M>                          implication;
059
060    private final CountDownLatch                                  cdl        = new CountDownLatch(1);
061    private final AtomicReference<Set<CounterExample<String, M>>> ref        = new AtomicReference<>();
062
063    private final HBox                                            buttonsBox = new HBox();
064    private final RelationEventHandler<Implication<String, M>, M> eventHandler;
065
066    private Question(final Implication<String, M> implication) {
067      super();
068      this.object = "Counter-Example " + IdGenerator.getNextId(cxt);
069      this.implication = implication;
070      final Button acceptButton = new Button("accept");
071      final Button declineButton = new Button("decline");
072      acceptButton.maxHeightProperty().bind(counterExamplesWidget.rowHeaderPane.rowHeight);
073      acceptButton.minHeightProperty().bind(counterExamplesWidget.rowHeaderPane.rowHeight);
074      acceptButton.prefHeightProperty().bind(counterExamplesWidget.rowHeaderPane.rowHeight);
075      declineButton.maxHeightProperty().bind(counterExamplesWidget.rowHeaderPane.rowHeight);
076      declineButton.minHeightProperty().bind(counterExamplesWidget.rowHeaderPane.rowHeight);
077      declineButton.prefHeightProperty().bind(counterExamplesWidget.rowHeaderPane.rowHeight);
078      acceptButton.setMaxWidth(60);
079      acceptButton.setMinWidth(60);
080      acceptButton.setPrefWidth(60);
081      declineButton.setMaxWidth(60);
082      declineButton.setMinWidth(60);
083      declineButton.setPrefWidth(60);
084      acceptButton.setStyle("-fx-padding:0; -fx-background-radius: 5 0 0 5, 5 0 0 5, 4 0 0 4, 3 0 0 3;");
085      declineButton.setStyle("-fx-padding:0; -fx-background-radius: 0 5 5 0, 0 5 5 0, 0 4 4 0, 0 3 3 0;");
086      acceptButton.setOnAction(__ -> {
087        ref.set(Collections.emptySet());
088        cancel();
089      });
090      declineButton.setOnAction(__ -> {
091        cancel();
092      });
093      buttonsBox.getChildren().addAll(acceptButton, declineButton);
094      synchronized (counterExamples.rowHeads()) {
095        counterExamples.rowHeads().add(implication);
096        counterExamples.row(implication).addAll(implication.getPremise());
097      }
098      ref.set(Collections.singleton(new CounterExample<String, M>(object, implication.getPremise())));
099      final AtomicBoolean blocked = new AtomicBoolean(false);
100      eventHandler = __ -> {
101        if (blocked.get() || !counterExamples.rowHeads().contains(implication))
102          return;
103        if (!counterExamples.row(implication).containsAll(implication.getPremise())
104            || counterExamples.row(implication).containsAll(implication.getConclusion())) {
105          blocked.set(true);
106          counterExamples.row(implication).clear();
107          counterExamples.row(implication).addAll(implication.getPremise());
108          new ErrorDialog(
109              stage,
110              "Invalid Counterexample",
111              "A counterexample must contain all premise attributes, but must not contain all conclusion attributes.")
112                  .showAndWait();
113          blocked.set(false);
114        }
115        ref.set(Collections.singleton(new CounterExample<String, M>(object, counterExamples.row(implication))));
116      };
117      counterExamples.addEventHandler(eventHandler, RelationEvent.ENTRIES);
118      counterExamplesWidget.addRowDecoration(implication, buttonsBox);
119    }
120
121    private final void cancel() {
122      synchronized (counterExamples.rowHeads()) {
123        counterExamples.removeEventHandler(RelationEvent.ENTRIES, eventHandler);
124        counterExamples.rowHeads().remove(implication);
125        pendingQuestions.remove(this);
126      }
127      cdl.countDown();
128    }
129
130  }
131
132  private Stage                                          stage;
133  private final Map<Implication<String, M>, Question>    pendingQuestions = new ConcurrentHashMap<>();
134  private boolean                                        isCancelled      = false;
135
136  private final Context<String, M>                       cxt;
137  private MatrixContext<Implication<String, M>, M>       counterExamples;
138  private MatrixContextWidget<Implication<String, M>, M> counterExamplesWidget;
139
140  public HumanExpertP(final Context<String, M> cxt) {
141    super();
142    this.cxt = cxt;
143  }
144
145  public final void init() {
146    this.stage = new Stage(StageStyle.UTILITY);
147//    this.stage.setResizable(false);
148    this.stage.setTitle("Parallel Attribute Exploration");
149    this.stage.setWidth(800);
150    this.stage.setHeight(500);
151    this.counterExamples = new MatrixContext<>(false, AutomaticMode.NONE);
152    this.counterExamples.colHeads().addAll(cxt.colHeads());
153    this.counterExamplesWidget = new MatrixContextWidget<>(null, true, false, this.counterExamples);
154//    this.counterExamplesWidget.rowHeaderPane.highlight.set(true);
155//    this.counterExamplesWidget.colHeaderPane.highlight.set(true);
156//    this.counterExamplesWidget.contextPane.highlight.set(true);
157    this.stage.setScene(
158        new Scene(
159            new BorderPane(
160                this.counterExamplesWidget,
161                new Label("Please decide whether the following implications are valid."),
162                null,
163                null,
164                null)));
165    this.stage.setOnHiding(__ -> {
166      this.isCancelled = true;
167      for (Question question : pendingQuestions.values()) {
168        question.ref.set(Collections.emptySet());
169        question.cancel();
170      }
171    });
172  }
173
174  public final void show() {
175    stage.show();
176  }
177
178  public final void hide() {
179    stage.hide();
180  }
181
182  @Override
183  public Set<CounterExample<String, M>> getCounterExamples(final Implication<String, M> implication)
184      throws InterruptedException {
185    Platform2.runOnFXThreadAndWaitTryCatch(() -> pendingQuestions.put(implication, new Question(implication)));
186//    return new Future<Set<CounterExample<String, M>>>() {
187//
188//      private 
189    final Question question = pendingQuestions.get(implication);
190//      private boolean        isCancelled = false;
191//
192//      @Override
193//      public boolean cancel(boolean mayInterruptIfRunning) {
194//        if (question.cdl.getCount() == 0)
195//          return false;
196//        isCancelled = true;
197//        question.cancel();
198//        return true;
199//      }
200//
201//      @Override
202//      public boolean isCancelled() {
203//        return isCancelled;
204//      }
205//
206//      @Override
207//      public boolean isDone() {
208//        return question.cdl.getCount() == 0;
209//      }
210//
211//      @Override
212//      public Set<CounterExample<String, M>> get() throws InterruptedException, ExecutionException {
213    question.cdl.await();
214    if (isCancelled)
215      throw new InterruptedException();
216    return question.ref.get();
217//      }
218//
219//      @Override
220//      public Set<CounterExample<String, M>> get(long timeout, TimeUnit unit)
221//          throws InterruptedException, ExecutionException, TimeoutException {
222//        if (question.cdl.await(timeout, unit))
223//          return question.ref.get();
224//        else
225//          throw new TimeoutException();
226//      }
227//    };
228  }
229
230}