package version10_2_2_1.agnosticMemory;

import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.util.ArrayList;

import version10_2_2_1.Interface.InteractionList;
import version10_2_2_1.Interface.PrimitiveInteraction;
import version10_2_2_1.platform.Agent;
import version10_2_2_1.platform.Observer;

/**
 * List of signatures
 * @author simon
 */
public class SignatureList {

	public static int NB_THREAD=5;
	public static int NB_SIG_PER_THREAD=(int)(InteractionList.length/NB_THREAD)+1;
	
	private Agent agent;
	
	public ArrayList<Signature> signatureList;

	public static boolean load=false;				// load signatures
	
	private float[] context0;
	private float[] context1;
	
	private Thread[] t_predict;
	private Thread[] t_learn;
	
	
	private int counter=0;
	
	public Projection[] projections;
	public ArrayList<Byte[]> pathList;
	public ArrayList<Byte> objects;
	public ArrayList<Byte> lock;
	
	private int nbPaths;
	
	
	public WorkMemory[] workMemory;
	
	
	public SignatureList(Agent a){
		agent=a;

		signatureList=new ArrayList<Signature>();
		for (int i=0;i<InteractionList.length-1;i++){
			signatureList.add(new Signature( getInteractionOfIndex(i)));
		}
		
		t_predict=new Thread[NB_THREAD];
		t_learn  =new Thread[NB_THREAD];

		
		projections=new Projection[4];
		for (byte i=0;i<4;i++) projections[i]=new Projection(signatureList.get(InteractionList.nbDF+i), signatureList, i);
		
		objects=new ArrayList<Byte>();
		pathList=new ArrayList<Byte[]>();
		lock=new ArrayList<Byte>();
		
		nbPaths=6;
		for (short i=1;i<Projection.LENGTH;i++) nbPaths=nbPaths*6;
		
		
		if (load) load();
		
		workMemory=new WorkMemory[4];
		for (byte i=0;i<4;i++) workMemory[i]=new WorkMemory(signatureList.get(InteractionList.nbDF+i));
	}
	
	public Signature get(int i){
		return signatureList.get(i);
	}
	
	public int size(){
		return signatureList.size();
	}
	
	/**
	 * integrate the last result of enaction cycle
	 * @param input : environemental context E_{t-1} (context before enaction)
	 * @param output: environemental context E_t (interactions defined as a success)
	 * @param enacted : enacted primary interaction
	 */
	public void learn(float[] input, float[] output, PrimitiveInteraction enacted){
		
		int inter=-1;
		if (enacted.getAction().getName().equals(">")){
			if (enacted.getPerception().getId()==1) inter=1;
			else if (enacted.getPerception().getId()==2) inter=2;
			else if (enacted.getPerception().getId()==3) inter=3;
			else inter=0;
		}
		else if (enacted.getAction().getName().equals("v")) inter=4;
		else if (enacted.getAction().getName().equals("^")) inter=5;


		// learn dynamic features if corresponding primitive interaction is enacted
		int min=inter*InteractionList.size*InteractionList.size*4;
		int max=(inter+1)*InteractionList.size*InteractionList.size*4;
		
		//setCounter(min);
		context0=output;
		context1=input;
		
		for (int i=0;i<NB_THREAD;i++) t_learn[i]=new LearnThread(i, min, max);
		for (int i=0;i<NB_THREAD;i++) t_learn[i].start();
		try {
			for (int i=0;i<NB_THREAD;i++) t_learn[i].join();
		} catch (InterruptedException e){e.printStackTrace();}
		
		
		// learn primitive interaction if success or fail
		for (int i=InteractionList.nbDF;i<InteractionList.length-1;i++){
			if (output[i]!=0){
				// learn as success
				signatureList.get(i).learn(input,output[i]);

				// learn alternative interactions
				if (inter==0){
					signatureList.get(1+InteractionList.nbDF).learn(input,-1);
					signatureList.get(2+InteractionList.nbDF).learn(input,-1);
					signatureList.get(3+InteractionList.nbDF).learn(input,-1);
				}
				else if (inter==1){
					signatureList.get(0+InteractionList.nbDF).learn(input,-1);
					signatureList.get(2+InteractionList.nbDF).learn(input,-1);
					signatureList.get(3+InteractionList.nbDF).learn(input,-1);
				}
				else if (inter==2){
					signatureList.get(0+InteractionList.nbDF).learn(input,-1);
					signatureList.get(1+InteractionList.nbDF).learn(input,-1);
					signatureList.get(3+InteractionList.nbDF).learn(input,-1);
				}
				else if (inter==3){
					signatureList.get(0+InteractionList.nbDF).learn(input,-1);
					signatureList.get(1+InteractionList.nbDF).learn(input,-1);
					signatureList.get(2+InteractionList.nbDF).learn(input,-1);
				}
			}
		}
	}
	
	public void setPredictions(float[] img){

		// compute prediction of primary interactions
		for (short i=0;i<6;i++) signatureList.get(i+InteractionList.nbDF).prediction(img);
		
		//setCounter(0);
		
		for (int i=0;i<NB_THREAD;i++) t_predict[i]=new PredictThread(i);
		for (int i=0;i<NB_THREAD;i++) t_predict[i].start();
		try {
			for (int i=0;i<NB_THREAD;i++) t_predict[i].join();
		} catch (InterruptedException e){e.printStackTrace();}
		
		
		// define images of signatures
		if (counter%500==0){
			for (byte i=0;i<4;i++){
				projections[i]=new Projection(signatureList.get(InteractionList.nbDF+i), signatureList,i);
			}
			
			for (byte i=0;i<4;i++){
				workMemory[i].resetMemory();
				workMemory[i].projection(signatureList);
			}
			
			// define valid paths
			pathList.clear();
			objects.clear();
			lock.clear();

			Byte[] path;
			int imax=1;
			for (int lmax=0;lmax<=Projection.LENGTH;lmax++){
			
				int i=0;
				
				while (i<imax){
				
					int index=i;
					byte l=0;
					boolean end=false;
					
					path=new Byte[Projection.LENGTH+1];
					for (int n=0;n<lmax;n++) path[n]=0;
					for (int n=lmax;n<Projection.LENGTH+1;n++) path[n]=-1;
					
					while (l<lmax && !end){
						
						if (index<6){
							path[l]=(byte)index;
							end=true;
							l++;
						}
						else{
							path[l]=(byte)(index%6);
							index=(int)(index/6);
							l++;
						}
					}
					
					/*for (int j=0;j<Projection.LENGTH;j++){
						System.out.print(path[j]+", ");
					}
					System.out.println();/**/
					
					boolean found=false;
					for (short k=0;k<4;k++){
						if (pathValid(path)){
							if (projections[k].isPathValid(path)) found=true;
							
							//workMemory[k].projection(path,signatureList);
						}
					}
					if (found){
						pathList.add(path);
						objects.add((byte)-1);
						lock.add((byte)0);
					}
					
					i++;
				}
				imax=imax*6;
			}
			
			
			/*for (short i=0;i<2;i++){
				
				workMemory[i].initialize(signatureList.get(InteractionList.nbDF+i));

				if (signatureList.get(InteractionList.nbDF+i).nb_s_f==100){
					System.out.println("test2 "+i);
					workMemory[i].construction(signatureList);
				}
				System.out.println("test3 "+i);
			}*/
			
		}
		
		/*System.out.println("--- "+pathList.size());
		for (short i=0;i<pathList.size();i++){
			for (int j=0;j<Projection.LENGTH;j++){
				System.out.print(pathList.get(i)[j]+", ");
			}
			System.out.println();
		}
		System.out.println();/**/
		
		
		// compute position of object instances
		if (counter>=2000){

			for (int p=0;p<pathList.size();p++){
				objects.set(p, (byte)-1);
				if (lock.get(p)>0) lock.set(p,(byte)(lock.get(p)-1));
				
				if (getFirstStep(pathList.get(p))==-1 ||signatureList.get(InteractionList.nbDF+getFirstStep(pathList.get(p))).getPrediction()==1){
				for (short k=0;k<4;k++){
					if (projections[k].getResult(pathList.get(p))){
						if (objects.get(p)==-1) objects.set(p, (byte)k);
						else objects.set(p, (byte)-2);						// conflict
					}
				}
				}
				//else{
				//	objects.set(p, (byte)-3);
				//}
			}
		}
	
	
		counter++;
	}
	
	public boolean pathValid(Byte[] path){
		for (short i=0;i<path.length-1;i++){
			if (path[i]==2 || path[i+1]==2) return false;		// >X does not produce movement
			
			//if (path[i]==2 && path[i+1]==0) return false;		// impossible sequence >X, >O
			//if (path[i]==2 && path[i+1]==1) return false;		// impossible sequence >X, >P
			//if (path[i]==2 && path[i+1]==3) return false;		// impossible sequence >X, >F
			
			if (path[i]==0 && path[i+1]==1) return false;		// impossible sequence >P, >O
			
			if (path[i]==4 && path[i+1]==5) return false;		// useless sequence ^,v
			if (path[i]==5 && path[i+1]==4) return false;		// useless sequence v,^
		}
		return true;
	}
	
	// get the id of the associated primary interaction
	public PrimitiveInteraction getInteractionOfIndex(int id){
		int ret=0;
		if (id>=InteractionList.nbDF){
			ret=id-InteractionList.nbDF;
		}
		else{
			if (id>=0 && id<InteractionList.size*InteractionList.size*3) ret=0;
			else if (id>=  InteractionList.size*InteractionList.size*3 && id<2*InteractionList.size*InteractionList.size*3) ret=1;
			else if (id>=2*InteractionList.size*InteractionList.size*3 && id<3*InteractionList.size*InteractionList.size*3) ret=2;
			else if (id>=3*InteractionList.size*InteractionList.size*3 && id<4*InteractionList.size*InteractionList.size*3) ret=3;
			else if (id>=4*InteractionList.size*InteractionList.size*3 && id<5*InteractionList.size*InteractionList.size*3) ret=4;
		}

		return agent.interactionList.getInteraction(ret);
	}

	
	public byte getFirstStep(Byte[] path){
		int i=path.length-1;
		while (i>=0){
			if (path[i]!=-1) return path[i];
			i--;
		}
		return -1;
	}
	
	
	
	///////////////////////////////////////////////////////////////////////////////////////////////////////////
	public void save(){
		System.out.println("=====================prepare to save agnostic memory...======================");
		
		String fileName = Observer.path+"spaceMemory/spaceMemoryV10_2_2_1 "+Projection.LENGTH+" "+agent.getIdent()+".txt";
		
		try {
			PrintWriter file  = new PrintWriter(new FileWriter(fileName));
			
			// save counter
			file.println(agent.action.getNbStep());
			file.println("***");
			
			// save signatures
			for (int i=0;i<signatureList.size();i++){
			  
				// write data related to the signature
				file.println(signatureList.get(i).nb_s_t+" "+signatureList.get(i).nb_test_t+" "+signatureList.get(i).nb_s_f+" "+signatureList.get(i).nb_test_f);
				
				// write the test map
				for (int l=0;l<InteractionList.length;l++) file.print(signatureList.get(i).testMap[l]+" ");
				file.println();
				
				
				// write contexts "true"
				file.println(signatureList.get(i).context_t.size());
				for (int n=0;n<signatureList.get(i).context_t.size();n++){
					saveContext(file, signatureList.get(i).context_t.get(n));
				}
				
				// write contexts "false"
				file.println(signatureList.get(i).context_f.size());
				for (int n=0;n<signatureList.get(i).context_f.size();n++){
					saveContext(file, signatureList.get(i).context_f.get(n));
				}
			}
			file.close();
			System.out.println("memory file saved");
		}
		catch (Exception e) {e.printStackTrace();}
	}
	
	private void saveContext(PrintWriter file, Context c){
		
		// save date related to current context
		file.println(c.index_primary+" "+c.nb_s+" "+c.nb_test+" "+c.failure+" "+c.type+" "+c.level);
		
		// save context itself
		for (int i=0;i<c.context.size();i++){
			file.print(c.context.get(i)+" ");
		}
		file.println();
		
		// save included contexts
		file.println(c.included.size());
		for (int i=0;i<c.included.size();i++){
			saveContext(file, c.included.get(i));
		}
	}
	
	
	public void load(){
		String fileName = Observer.path+"spaceMemory/spaceMemoryV10_2_2_1 10 "+agent.getIdent()+".txt";;
		
		String[] elements;
		System.out.println("load file");
		try {
			InputStream ips=new FileInputStream(fileName); 
			InputStreamReader ipsr=new InputStreamReader(ips);
			BufferedReader br=new BufferedReader(ipsr);
			String line;
			
			// set agent's step counter
			line=br.readLine();
			agent.action.setNbstep(Integer.parseInt(line));
			counter=Integer.parseInt(line);
			// separator
			line=br.readLine();
			
			// load signatures
			int i=0;
			while ((line=br.readLine())!=null){
				elements=line.split(" ");
				
				// get data related to the signature
				signatureList.get(i).nb_s_t=Byte.parseByte(elements[0]);
				signatureList.get(i).nb_test_t=Byte.parseByte(elements[1]);
				signatureList.get(i).nb_s_f=Byte.parseByte(elements[2]);
				signatureList.get(i).nb_test_f=Byte.parseByte(elements[3]);
				
				// read the test map
				line=br.readLine();
				elements=line.split(" ");
				for (int l=0;l<InteractionList.length;l++) signatureList.get(i).testMap[l]=Byte.parseByte(elements[l]);
				
				// load contexts "true"
				line=br.readLine();
				elements=line.split(" ");
				for (int n=0;n<Integer.parseInt(elements[0]);n++){
					signatureList.get(i).context_t.add(loadContext(br));
				}
				
				// load contexts "false"
				line=br.readLine();
				elements=line.split(" ");
				for (int n=0;n<Integer.parseInt(elements[0]);n++){
					signatureList.get(i).context_f.add(loadContext(br));
				}
				
				if (i%1000==0) System.out.println(i);
				i++;
			}
		} 
		catch (Exception e){System.out.println("no file found : space memory");}
	}
	
	private Context loadContext(BufferedReader br) throws IOException{
		
		String line;
		String[] elements;
		Context temp=new Context();
		
		// load date related to current context
		line=br.readLine();
		elements=line.split(" ");
		temp.index_primary=Short.parseShort(elements[0]);
		temp.nb_s=Byte.parseByte(elements[1]);
		temp.nb_test=Byte.parseByte(elements[2]);
		temp.failure=Byte.parseByte(elements[3]);
		temp.type=Byte.parseByte(elements[4]);
		temp.level=Byte.parseByte(elements[5]);;
		
		// load context itself
		line=br.readLine();
		elements=line.split(" ");
		for (int i=0;i<elements.length;i++){
			temp.context.add(Short.parseShort(elements[i]));
		}

		// load included contexts
		line=br.readLine();
		elements=line.split(" ");
		for (int i=0;i<Integer.parseInt(elements[0]);i++){
			temp.included.add(loadContext(br));
		}
		return temp;
	}
	
	
	//////////////////////////////////////////////////////////////////////////////
	//////////////////////////////////////////////////////////////////////////////
	
	private class PredictThread extends Thread {
		
		private int id;
		
		public PredictThread(int i){
			super();
			id=i;
		}
		public void run(){
			
			int min=id*NB_SIG_PER_THREAD;
			int max=Math.min((id+1)*NB_SIG_PER_THREAD, InteractionList.nbDF);
			
			for (int i=min;i<max;i++){
				signatureList.get(i).prediction(context0);
			}
		}
	}
	
	
	private class LearnThread extends Thread {
		
		private int id;
		private int min;
		private int max;
		
		public LearnThread(int i, int minimum, int maximum){
			super();
			id=i;
			min=minimum;
			max=maximum;
		}
		public void run(){
			
			int min2= min + id * ((int)((max-min)/NB_THREAD)+1);
			int max2= Math.min(min +(id+1)* ((int)((max-min)/NB_THREAD)+1) , max);
			
			for (int i=min2;i<max2;i++){
				if (context0[i]==1) signatureList.get(i).learn(context1,1);
				else signatureList.get(i).learn(context1,-1);
			}
		}
	} 
	
}
