package dfatool.strategy.verification;

import java.io.IOException;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import dfatool.expressions.Expression;
import dfatool.expressions.Id;
import dfatool.expressions.ListExpression;
import dfatool.expressions.SetExpression;
import dfatool.expressions.TupleExpression;
import dfatool.operations.SetOperations;
import dfatool.parser.LanguageUtil;
import dfatool.strategy.elements.Atom;
import dfatool.strategy.elements.Configuration;
import dfatool.strategy.elements.Context;
import dfatool.strategy.elements.Description;
import dfatool.strategy.elements.patterns.ResourceAllocationDescription;
import dfatool.strategy.graph.NodeIdentifier;
import dfatool.strategy.verification.elements.EssentialComponent;
import dfatool.strategy.verification.fdr2.FileGeneration;
import dfatool.strategy.verification.fdr2.RefinementChecker;
import dfatool.values.IntValue;
import dfatool.values.SetValue;
import dfatool.values.StringValue;
import dfatool.values.ListValue;
import dfatool.values.Value;

public class ResourceAllocationAdherence {
	private IFileGeneration fileGeneration;
	private IRefinementChecker refinementChecker;
	private EssentialComponent c;
	private ResourceAllocationDescription rad;
	
	public ResourceAllocationAdherence(EssentialComponent c, ResourceAllocationDescription rad) {
		fileGeneration = new FileGeneration();
		refinementChecker = new RefinementChecker();
		this.c = c;
		this.rad = rad;
	}
	
	public boolean verifyAdherence() throws IOException, InterruptedException {
		return verifyBehaviouralRestriction() && verifyStructuralRestriction(); 
	}

	private boolean verifyStructuralRestriction() {
		return partitions() && disjointEvents() && controlledAlpha() && strictOrder();
	}

	private boolean strictOrder() {
		Set<Value> users = rad.getUsers();
		for(Value e : users){
			ListValue atomIdentifier = (ListValue) e;
			String atomName = ((StringValue) atomIdentifier.get(0)).getValue();
			Integer atomNumber = ((IntValue) atomIdentifier.get(1)).getValue();
			NodeIdentifier idU = new NodeIdentifier(atomName,atomNumber);
			List<Value> resourcesId = rad.getResources(idU);
			if(!checkOrder(resourcesId))
				return false;
		}
		return true;
	}

	private boolean checkOrder(List<Value> resourcesId) {
		int lastInt = -1;
		for(Value idR : resourcesId){
			ListValue t = (ListValue) idR;
			Integer number = (Integer) t.get(1).getValue();
			if(!(lastInt < number))
				return false;
			lastInt = number;
		}
		return true;
	}

	private boolean controlledAlpha() {
		Set<Value> voc = this.c.getVocabulary();
		boolean controlledUsers = controlledAlphaUser(voc);
		boolean controlledResources = controlledAlphaResources(voc);
		return controlledUsers && controlledResources;
	}
	
	private boolean controlledAlphaUser(Set<Value> voc) {
		Set<Value> users = rad.getUsers();
		Set<Value> resources = rad.getResources();
		for(Value e : users){
			ListValue atomIdentifier = (ListValue) e;
			String atomName = ((StringValue) atomIdentifier.get(0)).getValue();
			Integer atomNumber = ((IntValue) atomIdentifier.get(1)).getValue();
			NodeIdentifier idU = new NodeIdentifier(atomName,atomNumber);
			List<Value> resourcesId = rad.getResources(idU);
			Atom at = Configuration.getInstance().getDescription().getAtom(atomName);
			Set<Value> alpha = (Set<Value>) LanguageUtil.parseAndEvalWithId(at.getAlphabet(), atomNumber);
			Set<Value> alphaCommunication = SetOperations.inter(alpha, voc);
			if(!(resources.containsAll(resourcesId) && alphaCommunication.equals(acqRelEventsUser(idU)))){
				return false;
			}
		}
		return true;
	}
	
	private boolean controlledAlphaResources(Set<Value> voc) {
		Set<Value> resources = rad.getResources();
		Set<Value> users = rad.getUsers();
		for(Value e : resources){
			ListValue atomIdentifier = (ListValue) e;
			String atomName = ((StringValue) atomIdentifier.get(0)).getValue();
			Integer atomNumber = ((IntValue) atomIdentifier.get(1)).getValue();
			NodeIdentifier idR = new NodeIdentifier(atomName,atomNumber);
			Set<Value> usersId = rad.getUsers(idR);
			Atom at = Configuration.getInstance().getDescription().getAtom(atomName);
			Set<Value> alpha = (Set<Value>) LanguageUtil.parseAndEvalWithId(at.getAlphabet(), atomNumber);
			Set<Value> alphaCommunication = SetOperations.inter(alpha, voc);
			if(!(users.containsAll(usersId) && alphaCommunication.equals(acqRelEventsResource(idR)))){
				return false;
			}
		}
		return true;
	}
	
	private Set<Value> acqRelEventsUser(NodeIdentifier id){
		Set<Value> s = new HashSet<Value>();
		for(Value v : rad.getResources(id)){
			ListValue ni = (ListValue) v;
			NodeIdentifier idR = new NodeIdentifier((String) ni.get(0).getValue(),(Integer) ni.get(1).getValue()); 
			s.add(rad.getAcquireEvent(id,idR));
			s.add(rad.getReleaseEvent(id,idR));
		}
		return s;
	}
	
	private Set<Value> acqRelEventsResource(NodeIdentifier id){
		Set<Value> s = new HashSet<Value>();
		for(Value v : rad.getUsers(id)){
			ListValue ni = (ListValue) v;
			NodeIdentifier idU = new NodeIdentifier((String) ni.get(0).getValue(),(Integer) ni.get(1).getValue()); 
			s.add(rad.getAcquireEvent(idU,id));
			s.add(rad.getReleaseEvent(idU,id));
		}
		return s;
	}

	private boolean disjointEvents() {
		Set<String> namesAcquire = getNames(rad.getAcquireFunc());
		Set<String> namesRelease = getNames(rad.getReleaseFunc());
		return SetOperations.inter(namesAcquire, namesRelease).isEmpty();
	}

	private Set<String> getNames(Map<String, Map<String,Expression>> acquireFunc) {
		Set<String> set = new HashSet<String>();
			for(Map<String,Expression> map : acquireFunc.values()){
				for(Expression te : map.values()){
					TupleExpression tuple = (TupleExpression) te;
					set.add(((Id) tuple.getList().get(0)).getId());				
				}
			}
		return set;
	}

//	private String samePrefix(Set<Value> all, Map<TupleExpression te) {
//		boolean samePrefix = true;
//		String prefix = null;
//		
//		Iterator<Value> it = all.iterator();
//		while(samePrefix && it.hasNext()){
//			TupleValue t = (TupleValue) it.next();
//			String atomName = t.get(0).getValue();
//			int number = t.get(1).getValue();
//			String currentPrefix = ((StringValue) evalChannel(, number).get(0)).getValue();
//			if(prefix == null){
//				prefix = ((StringValue) evalChannel(te, ni.toString()).get(0)).getValue();
//			}
//			samePrefix = currentPrefix.equals(prefix);
//		}
//		
//		return samePrefix ? prefix : null;
//	}
//	
//	public TupleValue evalChannel(TupleExpression te, String id){
//		Context c = Context.createContext();
//		c.putVar("id", new StringValue(id));
//		Value v  = te.evaluate(c);
//		TupleValue t = null;
//		if(v instanceof StringValue){
//			t = new TupleValue();
//			t.add(v);
//		}else{
//			t = (TupleValue) v;
//		}
//		return t;
//	}

	private boolean partitions() {
		Set all = SetOperations.union(rad.getUsers(), rad.getResources());
		Set inter = SetOperations.inter(rad.getUsers(), rad.getResources());
		return c.getComponentsSet().equals(all) &&
				inter.isEmpty();
	}

	private boolean verifyBehaviouralRestriction() throws IOException, InterruptedException {
		fileGeneration.generateModel();
		fileGeneration.generateEssentialComponentAndResourceAllocationDescription(c,rad);
		fileGeneration.generateResourceAllocationVerification(c,rad);
		boolean isConform = refinementChecker.checkFile("ResourceAllocationVerification"+c.getName()+".csp");
		System.out.println("Behavioural:"+isConform);
		return isConform;
	}
	
	public static void main(String[] args) {
//		EssentialComponent ec = new EssentialComponent("Comp");
//		ec.addComponent(new NodeIdentifier("P", 0));
//		ec.addComponent(new NodeIdentifier("P", 1));
//		Configuration conf = Configuration.getInstance();
//		conf.testConfiguration();
//		
//		SetExpression users = (SetExpression) LanguageUtil.parseString(" {P.1} ");
//		SetExpression resources = (SetExpression) LanguageUtil.parseString("{P.0}");
//		SetExpression funUser = (SetExpression) LanguageUtil.parseString("{P.0}");
//		ListExpression funRes = (ListExpression) LanguageUtil.parseString("<P.1>");
//		TupleExpression acqExpression = (TupleExpression) LanguageUtil.parseString("acq.0");
//		Map<String,TupleExpression> mAcq = new HashMap<String,TupleExpression>();
//		mAcq.put("P", acqExpression);
//		HashMap<String, Map<String, TupleExpression>> funAcq = new HashMap<String,Map<String,TupleExpression>>();
//		funAcq.put("P", mAcq);
//		TupleExpression relExpression = (TupleExpression) LanguageUtil.parseString("rel.0");
//		Map<String,TupleExpression> mRel = new HashMap<String,TupleExpression>();
//		mRel.put("P", relExpression);
//		HashMap<String, Map<String, TupleExpression>> funRel = new HashMap<String,Map<String,TupleExpression>>();
//		funRel.put("P", mRel);
//		Map<String,SetExpression> userFunc = new HashMap<String,SetExpression>();
//		Map<String,ListExpression> resourceFunc = new HashMap<String,ListExpression>();
//		userFunc.put("P", funUser);
//		resourceFunc.put("P", funRes);
//		ResourceAllocationDescription rad = new ResourceAllocationDescription(users, resources,userFunc,resourceFunc,funAcq,funRel);
//		
//		ResourceAllocationAdherence ra = new ResourceAllocationAdherence(ec, rad);
//		ra.verifyAdherence();
		
//		Set<NodeIdentifier> sni = new HashSet<NodeIdentifier>();
//		Set<NodeIdentifier> sni2 = new HashSet<NodeIdentifier>();
//		NodeIdentifier ni1 = new NodeIdentifier("P", 0);
//		NodeIdentifier ni2 = new NodeIdentifier("P", 0);
//		
//		sni.add(ni1);
//		sni2.addAll(sni);
//		
//		System.out.println(sni);
//		System.out.println(sni2);
	}
	
	
	
	

}
