package errorReachableAnalyzer;

import java.util.ArrayList;
import java.util.List;
import java.util.Stack;

import multiConcurrentModel.Model;
import multiConcurrentModel.ModelInterface;
import multiConcurrentModel.MultiConcurrentModel;
import multiConcurrentModel.MultiConcurrentState;
import multiConcurrentModel.MultiConcurrentTransition;
import multiConcurrentModel.State;
import multiConcurrentModel.Transition;

//環境モデルと要求モニタのセットからモデルの並列合成を行うクラス（遷移が追加された時の並列合成モデルの更新も行う）
public class MultiConcurrentSystemModelMaker {
	static List<String> controllableAction=new ArrayList<String>();
	static List<Transition> updatePart=new ArrayList<Transition>();
	static int updateNumber=0;
	static List<Model> requirements;
	static ModelInterface environment;
	static String targetError;
//	static List<MultiConcurrentTransition> errorTransitions;
	//並列合成開始メソッド：環境モデルと要求モニタのセット,コントローラブルアクションを引数に取る
	private MultiConcurrentSystemModelMaker(){
		
	}
	
	public static MultiConcurrentModel makeConcurrentSystem(ModelInterface env,List<Model> req,List<String>cActions,String errorString){
		targetError=errorString;
		return makeConcurrentSystem(env,req,cActions);
	}
	public static MultiConcurrentModel makeConcurrentSystem(ModelInterface env,List<Model> req,List<String>cActions){
		environment=env;
		requirements=req;
		controllableAction=cActions;
		List<State> initReq=new ArrayList<State>();
		for(int i=0;i<req.size();i++){
			initReq.add(req.get(i).getInitialState());
		}
		MultiConcurrentModel mcm=new MultiConcurrentModel(new MultiConcurrentState(env.getInitialState(),initReq));

		return compose(new Current(env.getInitialState(),initReq,mcm.getConcurrentState(env.getInitialState(), initReq)),mcm);
	}
	
	
	//環境モデル上で状態遷移を繰り返しながら並列合成モデルの状態を生成していくメソッド
	//実際の状態生成はaddConcurrentStateで行う
	private static MultiConcurrentModel compose(Current c,MultiConcurrentModel mcm){
		Stack<Current> currentStack=new Stack<Current>();
		Current cu;
		currentStack.push(c);
		while(!currentStack.isEmpty()){
			cu=currentStack.pop(); 
			if(cu.env.hasNext()){
				currentStack.push(cu);
				if((cu=addConcurrentState(cu,mcm))!=null)currentStack.push(cu);
			}
		}	
		return mcm;
		
	}
	//与えられた環境モデルの状態と要求モニタの状態のから並列合成モデルの状態を実際に生成するメソッド
	private static Current addConcurrentState(Current c,MultiConcurrentModel mcm){
		Transition t=(Transition)c.env.next();
		MultiConcurrentState mcs;
		if(t.getTo().isDead()){
			int[] stop=new int[(requirements.size()/32)+1];
			for(int i=0;i<stop.length;i++)stop[i]=0;
			connection(c.mcs,t,mcm.getErrorState(stop));
			return null;
		}else{
			List<State> newReqMoni=getNewReqMoni(c,t,mcm);
			if(newReqMoni==null){
				System.out.println("connect to error or critical error");
				return null;
			}else if(containsError(newReqMoni)){
				return addErrorState(c,mcm,t,newReqMoni);
			}else if(!mcm.existsConcurrentState(t.getTo(), newReqMoni)){
				mcs=new MultiConcurrentState(t.getTo(),newReqMoni);
				connection(c.mcs, t, mcs);
				mcm.addMultiConcurrentState(mcs);
				return new Current(t.getTo().getClone(),newReqMoni,mcs);				
			}else{
				mcs=mcm.getConcurrentState(t.getTo(),newReqMoni);
				connection(c.mcs,t,mcs);
				return null;
			}			
		}
	}
	//並列合成モデルを生成するために利用
	//環境モデルの遷移で要求モニタ側で遷移が起きるかを調べ、遷移が起きた場合は遷移先の要求モニタの状態を返すメソッド
	private static List<State> getNewReqMoni(Current c,Transition t,MultiConcurrentModel mcm){
		List<State> newReqMoni=new ArrayList<State>();
		for(int i=0;i<c.reqMoni.size();i++){
			State m=c.reqMoni.get(i);
			State nextM;
			if(m.containsToTransition(t.toString())){
				nextM=m.getToStateByTransition(t.toString());
				newReqMoni.add(nextM);
			}else{
				newReqMoni.add(m);
			}
		}
		return newReqMoni;
	}
	private static boolean containsError(List<State> newReqMoni){
		int i=0;
		while(i<newReqMoni.size()){
			if(newReqMoni.get(i).toString().equals("ERROR"))return true;
			i++;
		}
		return false;
	}
	//２つのMultiConcurrentStateをTransitionの情報から繋げるためのメソッド。MultiConcurrentTransitionが新たに生成される。
	public static MultiConcurrentTransition connection(MultiConcurrentState from,Transition t,MultiConcurrentState to){
		MultiConcurrentTransition newT=new MultiConcurrentTransition(t.toString());
		if(controllableAction.contains(newT.toString())){
//			System.out.println("ControllableAction"+newT.toString());
			newT.setIsControllable();
		}else{
//			System.out.println("MonitorableAction"+newT.toString());
			
		}
		from.addToTransition(newT);
		newT.setFrom(from);
		newT.setTo(to);
		to.addFromTransition(newT);
		return newT;
	}	
	private static Current addErrorState(Current c,MultiConcurrentModel mcm,Transition t,List<State> reqMoni){
		int[]errors=new int[(requirements.size()/32)+1];
		String e=""; 
		Stack<Integer> size=new Stack<Integer>();
		for(int i=0;i<reqMoni.size();i++){
			if(reqMoni.get(i).toString().equals("ERROR")){
				size.add(i);
				int error;
				int count=0;
				for(int j=i;j>=0;j-=32){
					error=1<<(j);
					errors[count]=error;
					count++;
				}
					
				if(!(targetError!=null&&targetError.contains("ERROR"+i))){
					if(!e.equals(""))e=e.concat("&&");
					e=e.concat("ERROR"+i);
				}
			}
		}
		searchReqMoni(mcm,t.getTo(),reqMoni,size);
		if(e.equals("")){
			if(mcm.existsConcurrentState(t.getTo(), reqMoni)){
				mcm.addErrorTransition(connectionWithError(c.mcs,t,mcm.getConcurrentState(t.getTo(),reqMoni),errors));
				return null;				
			}else{
				MultiConcurrentState mcs=new MultiConcurrentState(t.getTo(),reqMoni);
				mcm.addErrorTransition(connectionWithError(c.mcs, t, mcs,errors));
				mcm.addMultiConcurrentState(mcs);
				return new Current(t.getTo().getClone(),reqMoni,mcs);				
			}
		}else{
			mcm.addErrorTransition(connectionWithError(c.mcs,t,mcm.getErrorState(errors),errors));
			return null;
		}
	}
	private static List<State> searchReqMoni(MultiConcurrentModel mcm,State env,List<State> reqMoni,Stack<Integer> stack){
		if(stack.size()>1){
			int p=stack.pop();
			for(int i=0;i<requirements.get(p).getSize();i++){
				reqMoni.set(p, requirements.get(p).getState(i));
				List<State> r=searchReqMoni(mcm,env,reqMoni,stack);
				if(r!=null){
					for(int j=0;j<reqMoni.size();j++)
						System.out.print(reqMoni.get(j));
					System.out.println(" is new ReqMoni");
					return r;			
				}
				reqMoni.set(p, requirements.get(p).getInitialState());
			}
		}else if(!stack.isEmpty()){
			int p=stack.pop();
			for(int i=1;i<requirements.get(p).getSize();i++){
				reqMoni.set(p, requirements.get(p).getState(i));
				if(mcm.existsConcurrentState(env, reqMoni))return reqMoni;
			}
			reqMoni.set(p, requirements.get(p).getInitialState());
		}
		return null;
	}
	
	private static MultiConcurrentTransition connectionWithError(MultiConcurrentState from,Transition t,MultiConcurrentState to,int[] error){
		MultiConcurrentTransition newT=new MultiConcurrentTransition(t.toString());
		newT.setIsDead(error);
		if(controllableAction.contains(newT.toString())){
//			System.out.println("Controllable Action"+newT.toString());
			newT.setIsControllable();
		}else{
//			System.out.println("Monitorable Action"+newT.toString());
		}
		from.addToTransition(newT);
		newT.setFrom(from);
		newT.setTo(to);
		to.addFromTransition(newT);
		return newT;
		
	}
	//環境モデルを更新するためのメソッド
	//環境モデルと追加される遷移とその遷移が追加される場所を引数に取る
	public static Model attachTransition(Model e,String from,String t,String to){
		Transition tr= new Transition(t,e.getState(from),e.getState(to));
		e.getState(from).addToTransition(tr);
		e.getState(to).addFromTransition(tr);
		return e;
	}
	//追加された遷移によって並列合成モデルを更新するためのメソッド
	//並列合成モデルとその元となった環境モデル,要求モニタ,更に追加された遷移に関する情報を引数に取る
	//並列合成モデルの更新された部分の内、要求のチェックに利用する部分は並列合成モデル内に記憶しておく
	public static MultiConcurrentModel modelUpdate(MultiConcurrentModel cm,Model e,List<Model> moni,String from,String t,String to){
		updateNumber++;
		updatePart=new ArrayList<Transition>();
		e=attachTransition(e,from,t,to);
		
		Transition target=e.getState(from).getToTransition(t);
//		System.out.println(target.getFrom()+"->"+target.toString()+"->"+target.getTo());
		List<MultiConcurrentState> lcs=getCandidate(target,cm);
		for(int i=0;i<lcs.size();i++){
			List<State> reqMoniStates=new ArrayList<State>();
			List<String> moniStatesNames=lcs.get(i).getReqMoniList();
			if(lcs.get(i).getReqMoniList().size()!=0){
				for(int j=0;j<moni.size();j++)reqMoniStates.add(moni.get(j).getState(moniStatesNames.get(j)));
				cm=compose(new Current(target.getFrom().getClone(),reqMoniStates,lcs.get(i)),cm);
				if(lcs.get(i).getToTransition(t).getTo().getName().contains("ERROR")){
					
				}
				updatePart.add(lcs.get(i).getToTransition(t));
			}			
		}		
		cm.setUpdatedPart(updatePart);
		return cm;		
	}
	
	//並列合成モデルの状態の内、環境モデルの更新に伴って遷移が加わった状態のセットを取得するメソッド
	private static List<MultiConcurrentState> getCandidate(Transition tr,MultiConcurrentModel mcm){
		List<MultiConcurrentState> l=new ArrayList<MultiConcurrentState>();
		for(int i=0;i<mcm.getSize();i++){
			MultiConcurrentState cs=mcm.getState(i);
			if(cs.getEnv().equals(tr.getFrom().toString())){
				l.add(cs);
			}
		}
		return l;
	}

	static Current retreiveStates(String stateString,MultiConcurrentState mcs){
		if(stateString.contains("ERROR")){
			System.out.println("ERROR is included in retrieveing process");
			return null;
		}
		String[] strings=stateString.split("Q");
		State e=environment.getState(Integer.parseInt(strings[1]));
		e=e.getClone();
		e.reset();
		List<State> reqs=new ArrayList<State>();
		for(int i=0;i<strings.length-2;i++){
			reqs.add(requirements.get(i).getState(Integer.parseInt(strings[i+2])));
		}
		return new Current(e,reqs,mcs);
	}
}

//並列合成を行う際に必要な環境モデルと要求セットの状態を記憶しておくためのクラス
class Current{
	State env;
	List<State> reqMoni;
	MultiConcurrentState mcs;
	Current(State env,List<State> initReq,MultiConcurrentState mcs){
		this.env=env;
		this.reqMoni=initReq;
		this.mcs=mcs;
	}
	State env(){
		return env;
	}
	List<State> reqMoni(){
		return reqMoni;
	}
}
