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

import java.util.HashMap;
import java.util.Map;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import com.android.commands.monkey.ape.model.State;
import com.android.commands.monkey.ape.model.ModelAction;
import com.android.commands.monkey.ape.tree.GUITreeAction;

/** Run-local review decisions shared by synchronous and asynchronous paths. */
public final class JevReviewLedger {
    public static final class Decision {
        public final String outcome;
        public final Double confidence;
        public final String representativeModelActionId;
        public final String inputFingerprint;
        public final String requestId;
        public final String candidateId;
        // Navigation-only, run-local receipt. An arrival never consumes PROMOTE.
        private boolean navigationArrived;
        Decision(String value,Double score,String representative,String fingerprint) {
            this(value,score,representative,fingerprint,null);
        }
        Decision(String value,Double score,String representative,String fingerprint,String request) {
            this(value,score,representative,fingerprint,request,null);
        }
        Decision(String value,Double score,String representative,String fingerprint,String request,String candidate) {
            outcome=value;confidence=score;representativeModelActionId=representative;inputFingerprint=fingerprint;requestId=request;candidateId=candidate;
        }
    }
    static final class PairResult {
        final String choice; final double confidence; final org.json.JSONObject probabilities;
        PairResult(org.json.JSONObject answer)throws org.json.JSONException {
            choice=answer.getString("choice"); confidence=answer.getDouble("confidence");
            probabilities=answer.has("probabilities")?new org.json.JSONObject(answer.getJSONObject("probabilities").toString()):null;
        }
        org.json.JSONObject json()throws org.json.JSONException {
            org.json.JSONObject out=new org.json.JSONObject().put("choice",choice).put("confidence",confidence);
            if(probabilities!=null)out.put("probabilities",new org.json.JSONObject(probabilities.toString()));return out;
        }
    }
    private static final class Key {
        final String contract;
        final String activityScope;
        final ConcreteActionIdentity identity;
        Key(String c,String scope,ConcreteActionIdentity i){contract=c;activityScope=scope==null?"":scope;identity=i;}
        public int hashCode(){return 31*(31*contract.hashCode()+activityScope.hashCode())+identity.hashCode();}
        public boolean equals(Object other){
            if(!(other instanceof Key))return false;
            Key value=(Key)other;return contract.equals(value.contract)&&activityScope.equals(value.activityScope)&&identity.equals(value.identity);
        }
    }
    private final java.util.concurrent.locks.ReentrantLock budgetLock=new java.util.concurrent.locks.ReentrantLock();
    private Map<Key,Decision> decisions=new HashMap<Key,Decision>();
    private Map<String,Map<ConcreteActionIdentity,Decision>> fingerprints=new HashMap<String,Map<ConcreteActionIdentity,Decision>>();
    private Map<String,PairResult> pairResults=new HashMap<String,PairResult>();
    private final Map<Key,Set<String>> guiBindings=new HashMap<Key,Set<String>>();
    private final Map<Key,Long> bindingModelVersions=new HashMap<Key,Long>();
    private final Map<Key,Long> bindingRevisions=new HashMap<Key,Long>();
    private final Map<Key,Integer> bindingConfirmationCounts=new HashMap<Key,Integer>();
    private final Map<Key,GUITreeAction> sources=new HashMap<Key,GUITreeAction>();
    private final Map<Key,State> sourceStates=new HashMap<Key,State>();
    static final class Staged {
        final Decision decision;final String contract,scope;final GUITreeAction source;
        Staged(Key key,Decision value,GUITreeAction origin){decision=value;contract=key.contract;scope=key.activityScope;source=origin;}
    }
    static final class Update {
        final Key key;final Decision decision;
        Decision previous; // Audit of an actual same-key replacement, populated by the existing transaction.
        Update(String contract,String scope,ConcreteActionIdentity identity,String outcome,Double confidence,String representative,String fingerprint,String request) {
            this(contract,scope,identity,outcome,confidence,representative,fingerprint,request,null);
        }
        Update(String contract,String scope,ConcreteActionIdentity identity,String outcome,Double confidence,String representative,String fingerprint,String request,String candidate) {
            key=new Key(contract,scope,identity);decision=new Decision(outcome,confidence,representative,fingerprint,request,candidate);
        }
    }
    /** Build privately; publish only while the caller's original deadline is live. */
    boolean putBeforeDeadline(java.util.List<Update> updates,long deadline) {JevDeadline.lock(budgetLock);try {
        Map<Key,Decision> next=new HashMap<Key,Decision>();
        for(Map.Entry<Key,Decision> e:decisions.entrySet()) {
            if(System.nanoTime()>=deadline)return false;
            next.put(e.getKey(),e.getValue());
        }
        Map<String,Map<ConcreteActionIdentity,Decision>> fps=new HashMap<String,Map<ConcreteActionIdentity,Decision>>();
        for(Map.Entry<String,Map<ConcreteActionIdentity,Decision>> e:fingerprints.entrySet()) {
            if(System.nanoTime()>=deadline)return false;
            fps.put(e.getKey(),e.getValue());
        }
        Set<String> copied=new HashSet<String>();
        for(Update u:updates) {
            if(System.nanoTime()>=deadline)return false;
            u.previous=next.put(u.key,u.decision);
            if(u.decision.inputFingerprint!=null){String k=scopeKey(u.key.contract,u.key.activityScope,u.decision.inputFingerprint);
                Map<ConcreteActionIdentity,Decision> values=fps.get(k);
                if(copied.add(k)) {
                    Map<ConcreteActionIdentity,Decision> privateValues=new HashMap<ConcreteActionIdentity,Decision>();
                    if(values!=null)for(Map.Entry<ConcreteActionIdentity,Decision> e:values.entrySet()) {
                        if(System.nanoTime()>=deadline)return false;
                        privateValues.put(e.getKey(),e.getValue());
                    }
                    values=privateValues;fps.put(k,values);
                }
                values.put(u.key.identity,u.decision);}
        }
        if(System.nanoTime()>=deadline || Thread.currentThread().isInterrupted())return false;
        decisions=next;fingerprints=fps;return true;
    } finally {budgetLock.unlock();}}
    public Decision get(String contract,ConcreteActionIdentity identity) {JevDeadline.lock(budgetLock);try {
        return get(contract,"",identity);
    } finally {budgetLock.unlock();}}
    public Decision get(String contract,String activityScope,ConcreteActionIdentity identity) {JevDeadline.lock(budgetLock);try {return decisions.get(new Key(contract,activityScope,identity));} finally {budgetLock.unlock();}}
    public void put(String contract,ConcreteActionIdentity identity,String outcome,Double confidence,
            String representativeModelActionId,String inputFingerprint) {JevDeadline.lock(budgetLock);try {
        put(contract,"",identity,outcome,confidence,representativeModelActionId,inputFingerprint);
    } finally {budgetLock.unlock();}}
    public void put(String contract,String activityScope,ConcreteActionIdentity identity,String outcome,Double confidence,
            String representativeModelActionId,String inputFingerprint) {JevDeadline.lock(budgetLock);try {
        put(contract,activityScope,identity,outcome,confidence,representativeModelActionId,inputFingerprint,null);
    } finally {budgetLock.unlock();}}
    public void put(String contract,String activityScope,ConcreteActionIdentity identity,String outcome,Double confidence,
            String representativeModelActionId,String inputFingerprint,String requestId) {JevDeadline.lock(budgetLock);try {
        if("REVIEW_FAILED".equals(outcome)||"IN_FLIGHT".equals(outcome))return;
        Decision decision=new Decision(outcome,confidence,representativeModelActionId,inputFingerprint,requestId);
        decisions.put(new Key(contract,activityScope,identity),decision);
        if(inputFingerprint!=null) {
            String key=scopeKey(contract,activityScope,inputFingerprint);Map<ConcreteActionIdentity,Decision> values=fingerprints.get(key);
            if(values==null){values=new HashMap<ConcreteActionIdentity,Decision>();fingerprints.put(key,values);}values.put(identity,decision);
        }
    } finally {budgetLock.unlock();}}
    public Map<ConcreteActionIdentity,Decision> byFingerprint(String contract,String inputFingerprint) {JevDeadline.lock(budgetLock);try {
        return byFingerprint(contract,"",inputFingerprint);
    } finally {budgetLock.unlock();}}
    public Map<ConcreteActionIdentity,Decision> byFingerprint(String contract,String activityScope,String inputFingerprint) {JevDeadline.lock(budgetLock);try {
        Map<ConcreteActionIdentity,Decision> values=fingerprints.get(scopeKey(contract,activityScope,inputFingerprint));
        return values==null?Collections.<ConcreteActionIdentity,Decision>emptyMap():Collections.unmodifiableMap(new HashMap<ConcreteActionIdentity,Decision>(values));
    } finally {budgetLock.unlock();}}
    public int size(){JevDeadline.lock(budgetLock);try {return decisions.size();} finally {budgetLock.unlock();}}
    Map<String,PairResult> pairResults(java.util.Collection<String> keys) {JevDeadline.lock(budgetLock);try {
        Map<String,PairResult> out=new HashMap<String,PairResult>();for(String key:keys){PairResult value=pairResults.get(key);if(value==null)return Collections.emptyMap();out.put(key,value);}return out;
    } finally {budgetLock.unlock();}}
    void putPairResults(Map<String,PairResult> values) {JevDeadline.lock(budgetLock);try {pairResults.putAll(values);} finally {budgetLock.unlock();}}
    /** Read the existing ledger at close; no new pending-Probe inventory or terminal inference. */
    java.util.List<org.json.JSONObject> snapshot(String contract) {JevDeadline.lock(budgetLock);try {
        java.util.List<org.json.JSONObject> rows=new java.util.ArrayList<org.json.JSONObject>();
        for(Map.Entry<Key,Decision> entry:decisions.entrySet()) {
            Key key=entry.getKey();Decision d=entry.getValue();
            if(!key.contract.equals(contract)||d.requestId==null||d.candidateId==null)continue;
            rows.add(JevRegistry.fields("entry_id",d.requestId+"-"+d.candidateId,"request_id",d.requestId,
                "candidate_id",d.candidateId,"decision_role","candidate_decision","ledger_status",d.outcome,
                "input_fingerprint",d.inputFingerprint,"concrete_identity",JevEvidence.digest(key.identity.toString()),
                "decision_scope",JevEvidence.digest(key.activityScope)));
        }
        return rows;
    } finally {budgetLock.unlock();}}
    public void bind(String contract,ConcreteActionIdentity identity,String guiIdentity) {JevDeadline.lock(budgetLock);try {
        bind(contract,"",identity,guiIdentity);
    } finally {budgetLock.unlock();}}
    public void bind(String contract,String activityScope,ConcreteActionIdentity identity,String guiIdentity) {JevDeadline.lock(budgetLock);try {
        bind(contract,activityScope,identity,guiIdentity,null,null);
    } finally {budgetLock.unlock();}}
    public void bind(String contract,String activityScope,ConcreteActionIdentity identity,String guiIdentity,Long modelVersion,Long revision) {JevDeadline.lock(budgetLock);try {
        bind(contract,activityScope,identity,guiIdentity,modelVersion,revision,null);
    } finally {budgetLock.unlock();}}
    public void bind(String contract,String activityScope,ConcreteActionIdentity identity,String guiIdentity,Long modelVersion,Long revision,Integer confirmations) {JevDeadline.lock(budgetLock);try {
        if(guiIdentity==null||guiIdentity.length()==0)return;
        Key key=new Key(contract,activityScope,identity);Set<String> values=guiBindings.get(key);
        if(values==null){values=new HashSet<String>();guiBindings.put(key,values);}values.add(guiIdentity);
        if(modelVersion!=null)bindingModelVersions.put(key,modelVersion);
        if(revision!=null)bindingRevisions.put(key,revision);
        if(confirmations!=null)bindingConfirmationCounts.put(key,confirmations);
    } finally {budgetLock.unlock();}}
    public Set<String> bindings(String contract,ConcreteActionIdentity identity) {JevDeadline.lock(budgetLock);try {
        return bindings(contract,"",identity);
    } finally {budgetLock.unlock();}}
    public Set<String> bindings(String contract,String activityScope,ConcreteActionIdentity identity) {JevDeadline.lock(budgetLock);try {
        Set<String> values=guiBindings.get(new Key(contract,activityScope,identity));
        return values==null?Collections.<String>emptySet():Collections.unmodifiableSet(new HashSet<String>(values));
    } finally {budgetLock.unlock();}}
    /** Associate the existing decision key with its native source, not a second Probe inventory. */
    void bindSource(String contract,String scope,ConcreteActionIdentity identity,String gui,long version,long revision,int confirmations,GUITreeAction source){
        JevDeadline.lock(budgetLock);try {
            bind(contract,scope,identity,gui,version,revision,confirmations);
            Key key=new Key(contract,scope,identity);sources.put(key,source);sourceStates.put(key,source.getGUITree().getCurrentState());
        } finally {budgetLock.unlock();}
    }
    private static boolean reviewContract(String value,String base){
        return value.equals(base)||value.equals(base+":"+JevHistoryPairwiseProtocol.EVIDENCE_VIEW_VERSION)
            ||value.equals(base+":"+JevHistoryPairwiseProtocol.INCREMENTAL_VIEW_VERSION);
    }
    static final class PendingProbe {
        private final Key key;
        final Decision decision;final GUITreeAction source;final ConcreteActionIdentity identity;
        final boolean navigationArrived;
        PendingProbe(Key k,Decision d,GUITreeAction s){key=k;decision=d;source=s;identity=k.identity;navigationArrived=d.navigationArrived;}
    }
    /** Read only accepted source-bound decisions; no GUI realization or evidence construction. */
    java.util.List<PendingProbe> pendingProbes(String contract,State state) {
        JevDeadline.lock(budgetLock);try {
            java.util.List<PendingProbe> out=new java.util.ArrayList<PendingProbe>();
            for(Map.Entry<Key,Decision> entry:decisions.entrySet()) {
                Key key=entry.getKey();Decision d=entry.getValue();
                if(reviewContract(key.contract,contract)&&sourceStates.get(key)==state&&"PROBE".equals(d.outcome))
                    out.add(new PendingProbe(key,d,sources.get(key)));
            }
            return out;
        } finally {budgetLock.unlock();}
    }
    /** Necessary condition only; staged/executable retain all binding checks. */
    boolean hasPendingProbe(String contract,State state) {
        long examined=0;
        JevDeadline.lock(budgetLock);try {
            for(Map.Entry<Key,Decision> entry:decisions.entrySet()) {
                JevDeadline.check();examined++;
                if(reviewContract(entry.getKey().contract,contract)&&sourceStates.get(entry.getKey())==state
                        &&"PROBE".equals(entry.getValue().outcome))return true;
            }
            return false;
        } finally {budgetLock.unlock();JevReviewProfile.add("pending_probe_entries",0,examined);}
    }
    void navigationArrived(PendingProbe probe) {
        JevDeadline.lock(budgetLock);try {
            if(decisions.get(probe.key)==probe.decision)probe.decision.navigationArrived=true;
        } finally {budgetLock.unlock();}
    }
    /** PROMOTE is a run-local pending execution opportunity. History/model
        revisions invalidate raw model work, not this already accepted result.
        Other outcomes retain the existing GUI/revision cache policy. */
    Staged staged(String contract,State state,ModelAction action,ConcreteActionIdentity identity,
            String gui,long version,long revision,int confirmations){
        long profileStart=JevReviewProfile.start(),examined=0;
        JevDeadline.lock(budgetLock);try {
            Staged found=null;
            for(Map.Entry<Key,Decision> entry:decisions.entrySet()){
                examined++;
                JevDeadline.check();Key key=entry.getKey();Decision value=entry.getValue();GUITreeAction origin=sources.get(key);
                if(!reviewContract(key.contract,contract)||!key.identity.equals(identity)||origin==null||sourceStates.get(key)!=state)continue;
                if(origin.getGUITree().getCurrentState()!=state || origin.getModelAction()==null || origin.getModelAction().getState()!=state
                        ||origin.getActionType()!=action.getType()||!origin.getModelAction().getTarget().equals(action.getTarget()))continue;
                if("PROBE".equals(value.outcome)||"EXECUTED".equals(value.outcome)){
                    if(found==null || "EXECUTED".equals(value.outcome) || ("PROBE".equals(value.outcome)&&!"EXECUTED".equals(found.decision.outcome)))found=new Staged(key,value,origin);
                    continue;
                }
                Set<String> bound=guiBindings.get(key);Long mv=bindingModelVersions.get(key),rv=bindingRevisions.get(key);
                Integer cc=bindingConfirmationCounts.get(key);
                if(bound==null||!bound.contains(gui)||mv==null||mv!=version||rv==null||rv!=revision)continue;
                if(found==null)found=new Staged(key,value,origin);
            }
            return found;
        } finally {budgetLock.unlock();JevReviewProfile.end("ledger_staged",profileStart);JevReviewProfile.add("staged_entries",0,examined);}
    }
    /** Promote every contract-local decision for a confirmed native execution. */
    public void markExecuted(ConcreteActionIdentity identity) {JevDeadline.lock(budgetLock);try {
        markExecuted(null,identity);
    } finally {budgetLock.unlock();}}
    /** Promote decisions only within the confirmed Activity scope. */
    public void markExecuted(String activityScope,ConcreteActionIdentity identity) {JevDeadline.lock(budgetLock);try {
        for(Map.Entry<Key,Decision> entry:new HashMap<Key,Decision>(decisions).entrySet()) {
            if(!entry.getKey().identity.equals(identity) || (activityScope!=null && !entry.getKey().activityScope.equals(activityScope)))continue;
            Decision old=entry.getValue(),updated=new Decision("EXECUTED",old.confidence,old.representativeModelActionId,old.inputFingerprint,old.requestId,old.candidateId);
            decisions.put(entry.getKey(),updated);
            if(old.inputFingerprint!=null) {
                String fingerprint=scopeKey(entry.getKey().contract,entry.getKey().activityScope,old.inputFingerprint);Map<ConcreteActionIdentity,Decision> values=fingerprints.get(fingerprint);
                if(values!=null)values.put(identity,updated);
            }
        }
    } finally {budgetLock.unlock();}}
    /** Only a confirmed execution in this native State can finish its pending decisions. */
    void markExecutedMatching(String contract,State state,ConcreteActionIdentity identity){
        JevDeadline.lock(budgetLock);try {
            for(Map.Entry<Key,Decision> entry:new HashMap<Key,Decision>(decisions).entrySet()){
                Key key=entry.getKey();
                if((contract!=null&&!reviewContract(key.contract,contract))||sourceStates.get(key)!=state||!key.identity.equals(identity))continue;
                Decision old=entry.getValue(),done=new Decision("EXECUTED",old.confidence,old.representativeModelActionId,old.inputFingerprint,old.requestId,old.candidateId);
                decisions.put(key,done);
                if(old.inputFingerprint!=null){Map<ConcreteActionIdentity,Decision> values=fingerprints.get(scopeKey(key.contract,key.activityScope,old.inputFingerprint));if(values!=null)values.put(identity,done);}
            }
        } finally {budgetLock.unlock();}
    }
    private static String scopeKey(String contract,String activityScope,String fingerprint) {
        return contract+"|"+(activityScope==null?"":activityScope)+"|"+fingerprint;
    }
}
