package org.commcare.session;

import org.commcare.suite.model.EntityDatum;
import org.commcare.suite.model.SessionDatum;
import org.commcare.suite.model.Text;
import org.javarosa.core.model.condition.EvaluationContext;
import org.javarosa.core.model.instance.TreeReference;
import org.javarosa.xpath.XPathException;

import java.util.Vector;

/**
 * Performs all logic involved in polling the current CommCareSession to determine what information
 * or action is needed by the session, and then sends a signal to the registered
 * SessionNavigationResponder to indicate what should be done to get it (or if an error occurred)
 *
 * @author amstone
 */
public class SessionNavigator {

    // Result codes to be interpreted by the SessionNavigationResponder
    public static final int ASSERTION_FAILURE = 0;
    public static final int NO_CURRENT_FORM = 1;
    public static final int START_FORM_ENTRY = 2;
    public static final int GET_COMMAND = 3;
    public static final int START_ENTITY_SELECTION = 4;
    public static final int LAUNCH_CONFIRM_DETAIL = 5;
    public static final int XPATH_EXCEPTION_THROWN = 6;
    public static final int START_SYNC_REQUEST = 7;
    public static final int PROCESS_QUERY_REQUEST = 8;
    public static final int REPORT_CASE_AUTOSELECT = 9;

    private final SessionNavigationResponder responder;
    private CommCareSession currentSession;
    private EvaluationContext ec;

    private TreeReference currentAutoSelectedCase;
    private XPathException thrownException;


    public SessionNavigator(SessionNavigationResponder r) {
        this.responder = r;
    }

    private void sendResponse(int resultCode) {
        responder.processSessionResponse(resultCode);
    }

    public TreeReference getCurrentAutoSelection() {
        return this.currentAutoSelectedCase;
    }

    public XPathException getCurrentException() {
        return this.thrownException;
    }

    /**
     * Polls the CommCareSession to determine what information is needed in order to proceed with
     * the next entry step in the session, and then executes the action to get that info, OR
     * proceeds with trying to enter the form if no more info is needed
     */
    public void startNextSessionStep() {
        currentSession = responder.getSessionForNavigator();
        ec = responder.getEvalContextForNavigator();
        String needed;
        try {
            needed = currentSession.getNeededData(ec);
        } catch (XPathException e) {
            thrownException = e;
            sendResponse(XPATH_EXCEPTION_THROWN);
            return;
        }

        if (needed == null) {
            readyToProceed();
        } else if (needed.equals(SessionFrame.STATE_COMMAND_ID)) {
            sendResponse(GET_COMMAND);
        } else if (needed.equals(SessionFrame.STATE_SYNC_REQUEST)) {
            sendResponse(START_SYNC_REQUEST);
        } else if (needed.equals(SessionFrame.STATE_QUERY_REQUEST)) {
            sendResponse(PROCESS_QUERY_REQUEST);
        } else if (needed.equals(SessionFrame.STATE_DATUM_VAL)) {
            handleGetDatum();
        } else if (needed.equals(SessionFrame.STATE_DATUM_COMPUTED)) {
            handleCompute();
        }
    }

    private void readyToProceed() {
        Text text = currentSession.getCurrentEntry().getAssertions().getAssertionFailure(ec);
        if (text != null) {
            // We failed one of our assertions
            sendResponse(ASSERTION_FAILURE);
        }
        else if (currentSession.getForm() == null) {
            sendResponse(NO_CURRENT_FORM);
        } else {
            sendResponse(START_FORM_ENTRY);
        }
    }

    private void handleGetDatum() {
        TreeReference autoSelection = getAutoSelectedCase();
        if (autoSelection == null) {
            sendResponse(START_ENTITY_SELECTION);
        } else {
            sendResponse(REPORT_CASE_AUTOSELECT);
            this.currentAutoSelectedCase = autoSelection;
            handleAutoSelect();
        }
    }

    private void handleAutoSelect() {
        SessionDatum selectDatum = currentSession.getNeededDatum();
        if (selectDatum instanceof EntityDatum) {
            EntityDatum entityDatum = (EntityDatum)selectDatum;
            if (entityDatum.getLongDetail() == null) {
                // No confirm detail defined for this entity select, so just set the case id right away
                // and proceed
                String autoSelectedCaseId = EntityDatum.getCaseIdFromReference(
                        currentAutoSelectedCase, entityDatum, ec);
                currentSession.setDatum(entityDatum.getDataId(), autoSelectedCaseId);
                startNextSessionStep();
            } else {
                sendResponse(LAUNCH_CONFIRM_DETAIL);
            }
        }
    }

    /**
     * Returns the auto-selected case for the next needed datum, if there should be one.
     * Returns null if auto selection is not enabled, or if there are multiple available cases
     * for the datum (and therefore auto-selection should not be used).
     */
    private TreeReference getAutoSelectedCase() {
        SessionDatum selectDatum = currentSession.getNeededDatum();
        if (selectDatum instanceof EntityDatum) {
            EntityDatum entityDatum = (EntityDatum)selectDatum;
            if (entityDatum.isAutoSelectEnabled()) {
                Vector<TreeReference> entityListElements = ec.expandReference(entityDatum.getNodeset());
                if (entityListElements.size() == 1) {
                    return entityListElements.elementAt(0);
                }
            }
        }
        return null;
    }

    private void handleCompute() {
        try {
            currentSession.setComputedDatum(ec);
        } catch (XPathException e) {
            this.thrownException = e;
            sendResponse(XPATH_EXCEPTION_THROWN);
            return;
        }
        startNextSessionStep();
    }

    public void stepBack() {
        currentSession.stepBack(ec);
    }
}

@@ -2601,93 +2601,234 @@
     
-if (needed == null) %7B%0A            readyToProceed();%0A        %7D else if (needed.equals(
+dispatchOnNeededData(needed);%0A    %7D%0A%0A    private void dispatchOnNeededData(String needed) %7B%0A        if (needed == null) %7B%0A            readyToProceed();%0A            return;%0A        %7D%0A%0A        switch (needed) %7B%0A            case 
 Sess
@@ -2852,20 +2852,17 @@
 MMAND_ID
-)) %7B
+:
 %0A       
@@ -2858,32 +2858,36 @@
 ID:%0A            
+    
 sendResponse(GET
@@ -2909,33 +2909,40 @@
     
-%7D else if (needed.equals(
+        break;%0A            case 
 Sess
@@ -2960,37 +2960,38 @@
 ATE_SYNC_REQUEST
-)) %7B%0A
+:%0A    
             send
@@ -3032,33 +3032,40 @@
     
-%7D else if (needed.equals(
+        break;%0A            case 
 Sess
@@ -3092,21 +3092,22 @@
 _REQUEST
-)) %7B%0A
+:%0A    
         
@@ -3159,33 +3159,40 @@
     
+    
-%7D else if (needed.equals(
+    break;%0A            case 
 Sess
@@ -3215,21 +3215,22 @@
 ATUM_VAL
-)) %7B%0A
+:%0A    
         
@@ -3263,33 +3263,40 @@
     
+ 
-%7D else if (needed.equals(
+       break;%0A            case 
 Sess
@@ -3324,21 +3324,22 @@
 COMPUTED
-)) %7B%0A
+:%0A    
         
@@ -3355,24 +3355,47 @@
 eCompute();%0A
+                break;%0A
         %7D%0A  

Use string switch statement