package com.android.commands.monkey.ape.jev;

import java.util.*;
import org.json.*;
import com.android.commands.monkey.ape.model.*;
import com.android.commands.monkey.ape.tree.*;

/** Read-only native mapping checks and diagnostic receipts. Never repairs model/history objects. */
final class JevHistoryAudit {
    static boolean identityContains(Collection<?> items,Object wanted) {
        for(Object item:items){JevDeadline.check();if(item==wanted)return true;}
        return false;
    }
    static String mappingProblem(GUITreeAction concrete,Collection<State> live) {
        if(concrete==null || concrete.getGUITree()==null || concrete.getModelAction()==null)return "missing_action_or_tree";
        GUITree tree=concrete.getGUITree();State state=tree.getCurrentState();ModelAction action=concrete.getModelAction();
        if(state==null || !identityContains(live,state))return "source_state_not_live";
        if(action.getState()!=state || !identityContains(state.getActions(),action))return "action_state_link_inconsistent";
        if(action.requireTarget()) {
            GUITreeNode node=concrete.getGUITreeNode();
            if(node==null || node.getXPathName()==null)return "target_or_current_name_missing";
            GUITreeNode root=node;while(root.getParent()!=null){JevDeadline.check();root=root.getParent();}
            if(root!=tree.getRootNode())return "target_not_in_source_tree";
            if(!node.getXPathName().equals(action.getTarget()))return "target_name_action_mismatch";
            int matches=0;for(ModelAction a:state.getActions())
                if(a.requireTarget() && a.getType()==action.getType() && node.getXPathName().equals(a.getTarget()))matches++;
            if(matches!=1)return "non_unique_current_action";
        }
        return null;
    }
    static JSONObject capture(Model model,State current,JevRegistry registry,Set<GUITreeAction> confirmed,
            IdentityHashMap<GUITreeTransition,String> ids,int indexed,long step) throws JSONException {
        long begin=System.nanoTime(),version=model.getVersion();
        List<GUITreeTransition> history=model.getGraph().getTreeHistory();int size=history.size(),confirmationSize=confirmed.size();
        Set<GUITreeAction> seen=Collections.newSetFromMap(new IdentityHashMap<GUITreeAction,Boolean>());
        List<GUITreeAction> all=new ArrayList<GUITreeAction>();
        IdentityHashMap<GUITreeAction,JSONArray> transitions=new IdentityHashMap<GUITreeAction,JSONArray>();
        for(GUITreeTransition t:history) {
            GUITreeAction a=t.getAction();if(seen.add(a))all.add(a);JSONArray ts=transitions.get(a);
            if(ts==null){ts=new JSONArray();transitions.put(a,ts);}
            ts.put(JevRegistry.fields("transition_id",ids.get(t),"source_tree",t.getSource().getId(),
                "source_gui_timestamp",t.getSource().getTimestamp(),"target_step",t.getTarget().getTimestamp()));
        }
        for(GUITreeAction a:confirmed)if(seen.add(a))all.add(a);
        JSONArray rows=new JSONArray();IdentityHashMap<ModelAction,JSONArray> members=new IdentityHashMap<ModelAction,JSONArray>();
        IdentityHashMap<GUITreeAction,Integer> rowIds=new IdentityHashMap<GUITreeAction,Integer>();
        int unknown=0,otherState=0,currentState=0,unconfirmed=0,missingTransition=0;
        // Native tree order first. Identity set order has no semantic significance.
        for(GUITreeAction a:all) {
            if(!confirmed.contains(a)){unconfirmed++;continue;}
            int id=rows.length();rowIds.put(a,id);
            GUITree tree=a==null?null:a.getGUITree();ModelAction action=a==null?null:a.getModelAction();
            GUITreeNode node=a==null?null:a.getGUITreeNode();String problem=mappingProblem(a,model.getGraph().getStates());
            String scope=problem!=null?"UNKNOWN":tree.getCurrentState()==current?"CURRENT_STATE":"OTHER_STATE";
            if(problem!=null)unknown++;else if(tree.getCurrentState()==current)currentState++;else otherState++;
            JSONArray ts=transitions.get(a);if(ts==null){ts=new JSONArray();missingTransition++;}
            JSONObject attrs=node==null || action==null?null:JevRegistry.fields("action_type",action.getType().name(),"text",node.getText(),
                "content_desc",node.getContentDesc(),"resource_id",node.getResourceID(),"class",node.getClassName(),
                "bounds",node.getBoundsInScreen().flattenToString(),"index",node.getIndex());
            rows.put(JevRegistry.fields("id",id,"mapping_scope",scope,"mapping_problem",problem==null?JSONObject.NULL:problem,
                "source_tree",tree==null?JSONObject.NULL:tree.getId(),"source_gui_timestamp",tree==null?JSONObject.NULL:tree.getTimestamp(),
                "current_state",tree==null || tree.getCurrentState()==null?JSONObject.NULL:tree.getCurrentState().getGraphId(),
                "current_action",action==null?JSONObject.NULL:action.getGraphId(),"input_confirmed",true,
                "confirmation_step",JSONObject.NULL,"confirmation_basis","member of live confirmedInputs before audit; not function completion",
                "transitions",ts,"attributes",attrs==null?JSONObject.NULL:attrs,
                "attribute_provenance","retained source GUITreeNode at audit; not an immutable execution-time snapshot"));
            if(problem==null && tree.getCurrentState()==current) {
                JSONArray m=members.get(action);if(m==null){m=new JSONArray();members.put(action,m);}m.put(id);
            }
        }
        boolean stable=version==model.getVersion() && version==registry.version() && size==history.size() && indexed==size && confirmationSize==confirmed.size();
        JSONArray groups=new JSONArray();
        for(ModelAction action:current.getActions()) {
            if(!action.requireTarget())continue;
            JSONArray m=members.get(action);if(m==null)m=new JSONArray();
            List<JevRegistry.Execution> executions=registry.historyAuditExecutions(current,action);
            Set<JevRegistry.Anchor> anchors=new HashSet<JevRegistry.Anchor>();int recoverable=0;
            for(int i=0;i<m.length();i++) {
                JSONObject r=rows.getJSONObject(m.getInt(i));if(!r.isNull("attributes"))recoverable++;
            }
            for(GUITreeAction a:confirmed)if(a!=null && a.getGUITree()!=null && a.getGUITree().getCurrentState()==current
                    && a.getModelAction()==action && mappingProblem(a,model.getGraph().getStates())==null)
                anchors.add(new JevRegistry.Anchor(a.getGUITree(),a.getGUITreeNode(),action.getType().name()));
            boolean indexMatches=executions.size()==anchors.size();JSONArray registered=new JSONArray();
            for(JevRegistry.Execution e:executions) {
                if(!anchors.contains(e.anchor))indexMatches=false;
                registered.put(JevRegistry.fields("confirmed_row",rowIds.get(e.concreteAction),"transition_ids",new JSONArray(e.transitions),"transition_observations",e.count));
            }
            boolean complete=stable && unknown==0 && identityContains(model.getGraph().getStates(),current);
            groups.put(JevRegistry.fields("model_action_id",action.getGraphId(),"legal_confirmed_action_objects",m.length(),
                "confirmed_rows",m,"other_confirmed_action_objects",confirmed.size()-m.length()-unknown,
                "unknown_mapping_upper_bound",unknown,"recoverable_attribute_objects",recoverable,
                "registry_entries",registered,"registry_matches_confirmed_anchors",indexMatches,
                "enumeration_complete",complete,"registry_history_complete",complete && indexMatches,
                "all_legal_source_node_attributes_recoverable",recoverable==m.length(),
                "semantic_evidence_sufficient",JSONObject.NULL,
                "basis","live confirmedInputs union native tree history; live state/action identity plus node current Name; synchronized model version"));
        }
        return JevRegistry.fields("schema","history-enumeration-readonly-v1","review_step",step,"abstraction_version",version,
            "registry_version",registry.version(),"state_id",current.getGraphId(),"native_tree_history_total",size,
            "confirmation_set_objects",confirmed.size(),"unconfirmed_transition_action_objects",unconfirmed,
            "current_state_confirmed_objects",currentState,"other_state_confirmed_objects",otherState,"unknown_mapping_objects",unknown,
            "confirmed_without_transition_objects",missingTransition,"synchronized_version_and_history",stable,
            "raw_confirmed_input_event_count",JSONObject.NULL,"raw_event_count_limitation","existing set deduplicates action objects; no event count inferred",
            "records",rows,"groups",groups,"capture_ns",System.nanoTime()-begin);
    }
}
