package org.optaplanner.core.impl.heuristic.selector.entity.mimic;

import java.util.Iterator;
import java.util.ListIterator;

import org.optaplanner.core.impl.domain.entity.PlanningEntityDescriptor;
import org.optaplanner.core.impl.heuristic.selector.common.iterator.SelectionIterator;
import org.optaplanner.core.impl.heuristic.selector.entity.AbstractEntitySelector;
import org.optaplanner.core.impl.phase.AbstractSolverPhaseScope;
import org.optaplanner.core.impl.phase.step.AbstractStepScope;

public class MimicReplayingEntitySelector extends AbstractEntitySelector {

    protected final MimicRecordingEntitySelector recordingEntitySelector;

    protected boolean hasRecordedSelectionCreated;
    protected boolean hasRecordedSelection;
    protected boolean recordedSelectionCreated;
    protected Object recordedSelection;

    public MimicReplayingEntitySelector(MimicRecordingEntitySelector recordingEntitySelector) {
        this.recordingEntitySelector = recordingEntitySelector;
        // No solverPhaseLifecycleSupport because the MimicRecordingEntitySelector is hooked up elsewhere too
        recordingEntitySelector.addMimicReplayingEntitySelector(this);
    }

    // ************************************************************************
    // Worker methods
    // ************************************************************************

    @Override
    public void phaseStarted(AbstractSolverPhaseScope phaseScope) {
        super.phaseStarted(phaseScope);
        // Doing this in phaseStarted instead of stepStarted due to QueuedEntityPlacer compatibility
        hasRecordedSelectionCreated = false;
        recordedSelectionCreated = false;
    }

    @Override
    public void phaseEnded(AbstractSolverPhaseScope phaseScope) {
        super.phaseEnded(phaseScope);
        // Doing this in phaseEnded instead of stepEnded due to QueuedEntityPlacer compatibility
        hasRecordedSelectionCreated = false;
        hasRecordedSelection = false;
        recordedSelectionCreated = false;
        recordedSelection = null;
    }

    public PlanningEntityDescriptor getEntityDescriptor() {
        return recordingEntitySelector.getEntityDescriptor();
    }

    public boolean isContinuous() {
        return recordingEntitySelector.isContinuous();
    }

    public boolean isNeverEnding() {
        return recordingEntitySelector.isNeverEnding();
    }

    public long getSize() {
        return recordingEntitySelector.getSize();
    }

    public Iterator<Object> iterator() {
        return new ReplayingEntityIterator();
    }

    public void recordedHasNext(boolean hasNext) {
        hasRecordedSelection = hasNext;
        hasRecordedSelectionCreated = true;
    }

    public void recordedNext(Object next) {
        recordedSelection = next;
        recordedSelectionCreated = true;
    }

    private class ReplayingEntityIterator extends SelectionIterator<Object> {

        public boolean hasNext() {
            if (!hasRecordedSelectionCreated) {
                throw new IllegalStateException("Replay must occur after record."
                        + " The recordingEntitySelector (" + recordingEntitySelector
                        + ")'s hasNext() has not been called yet. ");
            }
            return hasRecordedSelection;
        }

        public Object next() {
            if (!recordedSelectionCreated) {
                throw new IllegalStateException("Replay must occur after record."
                        + " The recordingEntitySelector (" + recordingEntitySelector
                        + ")'s next() has not been called yet. ");
            }
            Object next = recordedSelection;
            // Until the recorder records something, this iterator has no next.
            hasRecordedSelection = false;
            recordedSelection = null;
            return next;
        }

    }

    public Iterator<Object> endingIterator() {
        // No replaying, because the endingIterator() is used for determining size
        return recordingEntitySelector.endingIterator();
    }

    public ListIterator<Object> listIterator() {
        // TODO Not yet implemented
        throw new UnsupportedOperationException();
    }

    public ListIterator<Object> listIterator(int index) {
        // TODO Not yet implemented
        throw new UnsupportedOperationException();
    }

    @Override
    public String toString() {
        return "Replaying(" + recordingEntitySelector + ")";
    }

}

@@ -118,16 +118,57 @@
 terator;
+%0Aimport java.util.NoSuchElementException;
 %0A%0Aimport
@@ -472,71 +472,8 @@
 ope;
-%0Aimport org.optaplanner.core.impl.phase.step.AbstractStepScope;
 %0A%0Apu
@@ -648,35 +648,27 @@
 an hasRecord
-edSelection
+ing
 Created;%0A   
@@ -691,35 +691,27 @@
 an hasRecord
-edSelection
+ing
 ;%0A    protec
@@ -724,35 +724,27 @@
 olean record
-edSelection
+ing
 Created;%0A   
@@ -767,27 +767,67 @@
 t record
-edSelection
+ing;%0A    protected boolean recordingAlreadyReturned
 ;%0A%0A    p
@@ -1588,35 +1588,27 @@
    hasRecord
-edSelection
+ing
 Created = fa
@@ -1622,35 +1622,27 @@
       record
-edSelection
+ing
 Created = fa
@@ -1881,35 +1881,27 @@
    hasRecord
-edSelection
+ing
 Created = fa
@@ -1918,35 +1918,27 @@
    hasRecord
-edSelection
+ing
  = false;%0A  
@@ -1945,35 +1945,27 @@
       record
-edSelection
+ing
 Created = fa
@@ -1979,35 +1979,27 @@
       record
-edSelection
+ing
  = null;%0A   
@@ -2576,27 +2576,55 @@
 asRecord
-edSelection
+ingCreated = true;%0A        hasRecording
  = hasNe
@@ -2639,41 +2639,99 @@
     
-hasR
+r
 ecord
-edSelectionCreat
+ingCreated = false;%0A        recording = null;%0A        recordingAlreadyReturn
 ed = 
-tru
+fals
 e;%0A 
@@ -2792,72 +2792,163 @@
     
-r
+hasR
 ecord
-edSelection = next;%0A        recordedSelectionCreat
+ingCreated = true;%0A        hasRecording = true;%0A        recordingCreated = true;%0A        recording = next;%0A        recordingAlreadyReturn
 ed = 
-tru
+fals
 e;%0A 
@@ -3028,24 +3028,210 @@
 %3CObject%3E %7B%0A%0A
+        private ReplayingEntityIterator() %7B%0A            // Reset so the last recording plays again even if it has already played%0A            recordingAlreadyReturned = false;%0A        %7D%0A%0A
         publ
@@ -3275,35 +3275,27 @@
  (!hasRecord
-edSelection
+ing
 Created) %7B%0A 
@@ -3568,35 +3568,56 @@
 rn hasRecord
-edSelection
+ing && !recordingAlreadyReturned
 ;%0A        %7D%0A
@@ -3671,27 +3671,19 @@
 (!record
-edSelection
+ing
 Created)
@@ -3949,40 +3949,259 @@
     
-Object next = recordedSelection;
+if (recordingAlreadyReturned) %7B%0A                throw new NoSuchElementException(%22The recordingAlreadyReturned (%22 + recordingAlreadyReturned%0A                        + %22) is impossible. Check if hasNext() returns true before this call.%22);%0A            %7D
 %0A   
@@ -4293,74 +4293,39 @@
     
-hasR
+r
 ecord
-edSelection = false;%0A            recordedSelection = null
+ingAlreadyReturned = true
 ;%0A  
@@ -4341,20 +4341,25 @@
  return 
-next
+recording
 ;%0A      

make mimic compatible with cartesian product in queued entity placer