//*******************************************************************************************************************************************
// This class instantiates the core of E-Z Reader (i.e., equivalent to the OP diagram in Reichle et al. (1998, Psychological Review).
//*******************************************************************************************************************************************

package ezreader10;

import java.io.*;
import java.util.*;

class EZReader {
    
    void run(PrintStream diskWriter, Stats stats, ArrayList<Sentence> text) 
            throws FileNotFoundException {
        
        // Initialize classes:
        Random r = new Random();
        Display display = new Display();

        // Flags to indicate if integration failed for particular words:
        boolean[] IFailed = new boolean[EZReader10.maxSentenceLength];
                
        // Start SENTENCE loop:
        for (int S = 0; S < EZReader10.NSentences; S++) {
        
            // Initialize fixation:
            int fixationN = 0;
            ArrayList<Fixation> trace = new ArrayList<>();
            Fixation f = new Fixation();
            f.number = fixationN;
            f.duration = 0;
            f.position = text.get(S).get(0).iv.OVP;
            f.word = 0;
                
            // Start L1:
            ArrayList<Process> active = new ArrayList<>();
            Process p = new Process();
            int N = 0;
            p.initializeL1(text, S, N, true);
            double rate = p.calcRate(text, S, N, f.position);
            p.duration *= rate;
            active.add(p);
                
            // Initialize integration-failure flags for all words:
            for (int i = 0; i < EZReader10.maxSentenceLength; i++) IFailed[i] = false; // i.e., integration has NOT failed
                
            // Initialize model in start state:
            String state;
            state = "[START]";

            boolean sentenceDone = false;
            while (!sentenceDone) { 

                // Display model state:
                if (EZReader10.displayStates == true) display.states(diskWriter, S, N, f, rate, active, IFailed, text, state);
                    
                // Identify process having the shortest duration:
                double minProcessDuration = active.get(0).duration;
                int minProcessID = 0;
                for (int i = active.size() - 1; i >= 0; i--) if (active.get(i).duration < minProcessDuration) {
                    minProcessDuration = active.get(i).duration;
                    minProcessID = i;
                }
                    
                // Store attributes of shortest process:
                Process sp = new Process();
                sp.duration = active.get(minProcessID).duration;
                sp.durationCopy = active.get(minProcessID).durationCopy;
                sp.name = active.get(minProcessID).name;
                sp.length = active.get(minProcessID).length;
                sp.word = active.get(minProcessID).word;
                    
                // Remove shortest process from list of active processes:
                active.remove(minProcessID);
                    
                // Decrement durations of all remaining active processes:
                for (int i = 0; i < active.size(); i++) active.get(i).duration -= sp.duration;
                            
                // Increase fixation duration (except if a saccade was execute):
                if (!sp.name.equals("S")) f.duration += sp.duration;
                    
                //**********************************************************
                // DETERMINE & EXECUTE NEXT MODEL STATE
                //**********************************************************
                    
                // PRE-ATTENTIVE VISUAL PROCESSING (V):
                if (sp.name.equals("V")) {
                    state = "[V]";
                        
                    // Adjust L1 rate:
                    for (int i = 0; i < active.size(); i++) if (active.get(i).name.equals("L1")) {
                            
                        active.get(i).duration /= rate;                            
                        rate = p.calcRate(text, S, N, f.position);                       
                        active.get(i).duration *= rate;                           
                    }
                }
                    
                // FAMILIARITY CHECK (L1):
                else if (sp.name.equals("L1")) {
                    state = "[L1]";
                        
                    // Determine if previous words is still being integrated:
                    boolean ongoingI = false;
                    for (int i = 0; i < active.size(); i++) if (active.get(i).name.equals("I") && active.get(i).word == (N - 1)) ongoingI = true;
                        
                    // Start L2:
                    p = new Process();
                    p.initializeL2(text, S, N, ongoingI);
                    active.add(p);
                        
                    // Cancel any pending M1 and start M1:
                    if (N < text.get(S).numberWords - 1) {
                        p = new Process();
                        p.initializeM1(active, f.position, text.get(S).get(N + 1).iv.OVP, N + 1);
                        active.add(p);
                    }
                }
                    
                // LEXICAL ACCESS (L2):
                else if (sp.name.equals("L2")) {
                        
                    // Determine if integation of the previous word failed:
                    boolean IFailure = false;
                    int IFWord = 0; 
                    for (int i = 0; i < active.size(); i++) if (active.get(i).name.equals("I")) {
                        IFWord = active.get(i).word;
                        active.remove(i);
                    }
                        
                    // Prohibit integration failure on a given word from happening twice:
                    if (IFWord > 0 && IFailed[IFWord] == false) { 
                        IFailed[IFWord] = true;
                        IFailure = true;
                        N = IFWord;
                    }
                
                    // Slow integration failure:
                    if (IFailure == true) {
                        state = "[L2/SLOW I]";
                            
                        // Note: A and/or L1 do NOT have to be removed from active processes because L2 just finished (i.e., by definition, 
                        // A and L1 cannot be ongoing).
                            
                        // Start A:
                        p = new Process();
                        p.initializeA(N);
                        active.add(p);
                            
                        // Cancel any pending M1 and start new M1:
                        p = new Process();
                        p.initializeM1(active, f.position, text.get(S).get(N).iv.OVP, N);
                        active.add(p);
                    }
                        
                    // Integration was successful:
                    else { 
                        state = "[L2]";
                            
                        // Start I:
                        p = new Process();
                        p.initializeI(text, S, N); // integration of current word starts
                        active.add(p);
                        
                        // Start A:
                        if (N < text.get(S).numberWords - 1) {
                            p = new Process();
                            p.initializeA(sp.word + 1); // attention directed towards next word
                            active.add(p);
                        } 
                    }
                }
                    
                // POST-LEXICAL INTEGRATION (I):
                else if (sp.name.equals("I")) {  
                        
                    // Rapid integration failure:
                    double PrIFailure = r.nextDouble();
                    if (IFailed[sp.word] == false && N > 0 && ((PrIFailure < EZReader10.pF && sp.word != text.get(S).target) || 
                            (PrIFailure < EZReader10.pFTarget && sp.word == text.get(S).target))) {    
    
                        state = "[RAPID I]";
                            
                        // Flag word for integration failure:
                        IFailed[sp.word] = true;
                            
                        // Stop A, L1, and/or L2:
                        for (int i = 0; i < active.size(); i++) if (active.get(i).name.equals("A")) active.remove(i);
                        for (int i = 0; i < active.size(); i++) if (active.get(i).name.equals("L1")) active.remove(i);
                        for (int i = 0; i < active.size(); i++) if (active.get(i).name.equals("L2")) active.remove(i);

                        // Start A:
                        N = sp.word; // i.e., shift attention to source of integration difficulty
                        p = new Process();
                        p.initializeA(N);
                        active.add(p);
                            
                        // Cancel any pending M1 and start new M1:
                        p = new Process();
                        p.initializeM1(active, f.position, text.get(S).get(N).iv.OVP, N);
                        active.add(p);
                    }
                    else {   
                        state = "[I]";
                            
                        if (sp.word == text.get(S).numberWords - 1) sentenceDone = true;
                    }
                }
                    
                // ATTENTION SHIFT (A):
                else if (sp.name.equals("A")) {
                    state = "[A]";
                        
                    // Shift attention to word:
                    N = sp.word;
                        
                    // Determine if integration of previous word is on-going:
                    boolean ongoingI = false;
                    for (int i = 0; i < active.size(); i++) if (active.get(i).name.equals("I") && active.get(i).word == (N - 1)) ongoingI = true;
                        
                    // Start L1:
                    p = new Process();
                    p.initializeL1(text, S, N, ongoingI);
                    rate = p.calcRate(text, S, N, f.position);
                    p.duration *= rate;
                    active.add(p);
                }
                    
                // LABILE SACCADIC PROGRAMMING (M1):
                else if (sp.name.equals("M1")) {
                    state = "[M1]";
                        
                    // Start M2:
                    p = new Process();
                    p.initializeM2(sp.length, f.duration, sp.word);
                    active.add(p);
                }
                    
                // NON-LABILE SACCADIC PROGRAMMING (M2):
                else if (sp.name.equals("M2")) {
                    state = "[M2]";
                        
                    // Start S:
                    p = new Process();
                    p.initializeS(sp.length, sp.word);
                    active.add(p);
                }
                    
                // SACCADE EXECUTION (S):
                else if (sp.name.equals("S")) {
                    state = "[S]";
                        
                    // Terminate fixation:
                    trace.add(f);
                        
                    // Start new fixation:
                    fixationN++;
                    f = new Fixation();
                    f.number = fixationN;
                    f.duration = 0;
                    f.position = trace.get(fixationN - 1).position + sp.length;
                    if (f.position < 0) f.position = 0;
                    double lastChar = text.get(S).get(text.get(S).numberWords - 1).iv.positionN - 0.1;
                    if (f.position > lastChar) f.position = lastChar; 
                    for (int i = 0; i < text.get(S).numberWords; i++) if (f.position >= text.get(S).get(i).iv.position0 && 
                            f.position < text.get(S).get(i).iv.positionN) f.word = i;
                       
                    // Start V:
                    p = new Process();
                    p.initializeV(N);
                    active.add(p);
                        
                    // Start M1 (for automatic refixation):
                    double PrRefixate = r.nextDouble();
                    double saccadeError = Math.abs(text.get(S).get(sp.word).iv.OVP - f.position);
                    if (PrRefixate < (EZReader10.Lambda * saccadeError)) {
                        p = new Process();
                        p.initializeM1(active, f.position, text.get(S).get(sp.word).iv.OVP, sp.word);
                        active.add(p);
                    }
                }
            }
            active.clear(); // remove any remaining active processes
                       
            // Display trace of individual fixations:
            if (EZReader10.displayTraces == true) display.traces(diskWriter, text, S, trace);
                
            // Determine if trial included interword regression:
            boolean regression = false;
            for (int i = 1; i < trace.size(); i++) if (trace.get(i - 1).word > trace.get(i).word) regression = true;
            if (regression == true) text.get(S).regressionN++;
                
            // Calculate word-based means conditional upon the presence vs. absence of interword regression(s):
            if ((EZReader10.includeRegressionTrials == false && regression == false) || EZReader10.includeRegressionTrials == true) 
                stats.wordDVs(text, S, trace);
            
            trace.clear(); // remove trace of fixations     
        } 
    }
}

//*******************************************************************************************************************************************


