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=true;				// load signatures
	
	private float[] context0;
	private float[] context1;
	
	private Thread[] t_predict;
	private Thread[] t_learn;
	
	
	private int counter=0;

	private int nbPaths;
	
	
	public WorkMemory[] workMemory;
	
	
	public SignatureList(Agent a){
		agent=a;

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

		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(i+InteractionList.nbDF), this);
	}
	
	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;

		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], signatureList);

				// learn alternative interactions
				if (inter==0){
					signatureList.get(1+InteractionList.nbDF).learn(input,-1, signatureList);
					signatureList.get(2+InteractionList.nbDF).learn(input,-1, signatureList);
					signatureList.get(3+InteractionList.nbDF).learn(input,-1, signatureList);
				}
				else if (inter==1){
					signatureList.get(0+InteractionList.nbDF).learn(input,-1, signatureList);
					signatureList.get(2+InteractionList.nbDF).learn(input,-1, signatureList);
					signatureList.get(3+InteractionList.nbDF).learn(input,-1, signatureList);
				}
				else if (inter==2){
					signatureList.get(0+InteractionList.nbDF).learn(input,-1, signatureList);
					signatureList.get(1+InteractionList.nbDF).learn(input,-1, signatureList);
					signatureList.get(3+InteractionList.nbDF).learn(input,-1, signatureList);
				}
				else if (inter==3){
					signatureList.get(0+InteractionList.nbDF).learn(input,-1, signatureList);
					signatureList.get(1+InteractionList.nbDF).learn(input,-1, signatureList);
					signatureList.get(2+InteractionList.nbDF).learn(input,-1, signatureList);
				}
			}
		}
	}
	
	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++){
				System.out.println("------------------------- update memory "+i+" ----------------------");
				workMemory[i].resetMemory();
				workMemory[i].projection(signatureList.get(InteractionList.nbDF+i));
				
				// remove redundant paths
				workMemory[i].cleanMemory(i);
			}


			/*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++;
						}
					}
					i++;
				}
				imax=imax*6;
			}*/
		}
		
		// compute position of object instances
		if (counter>=2000){

			// detect instances and proto-objects
			for (short k=0;k<4;k++){
				//System.out.println("----------- detection workMemory "+k+" ----------");
				workMemory[k].detectPaths(context0, signatureList);
			}
			// remove invalid instances and proto-objects and detect movable objects
			for (short k=0;k<4;k++){
				System.out.println("----------- validation workMemory "+k+" ----------");
				workMemory[k].validePaths(context0, signatureList);
			}
			for (short k=0;k<4;k++){
				System.out.println("----------- detection workMemory "+k+" ----------");
				workMemory[k].detectConstructible(signatureList);
			}
		}
		counter++;
	}
	
	// hypothesis on paths
	public static 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,^
			
			if (path.length>4 && i<path.length-2){				// for long paths
				if (path[i]==5 && path[i+1]==5 && path[i+2]==5) return false; // useless sequence ^,^,^
				if (path[i]==4 && path[i+1]==4 && path[i+2]==4) return false; // useless sequence v,v,v
			}
		}
		return true;
	}
	
	public float[] getCuriosityUtility(){
		
		float[] utility=new float[6];
		for (int k=0;k<6;k++) utility[k]=0;
		
		// for each non-enactable interaction
		for (int i=0;i<4;i++){

			if (signatureList.get(i+InteractionList.nbDF).getPrediction()<0){
				
				// curiosity utility of the interaction
				float[] utility2=signatureList.get(i+InteractionList.nbDF).getCuriosityUtility(workMemory[i].instanceList);
				for (int k=0;k<6;k++) utility[k]+=utility2[k];
				
				// curiosity utility of secondary interactions
				for (int j=i*InteractionList.size*InteractionList.size*4;j<(i+1)*InteractionList.size*InteractionList.size*4;j++){
					utility2=signatureList.get(j).getCuriosityUtility(workMemory[i].instanceList);
					for (int k=0;k<6;k++) utility[k]+=utility2[k];
				}	
			}
		}
		
		for (int k=0;k<6;k++) utility[k]+=utility[k]/InteractionList.length;
		
		//System.out.println("-----------------------");
		//for (int k=0;k<6;k++) System.out.println(utility[k]/InteractionList.length);
		//System.out.println("-----------------------");
		
		return utility;
	}

	
	public float getInstanceUtility(int id){
		
		float utility=0;
		
		for (int i=0;i<4;i++){
			
			for (int j1=0;j1<workMemory[i].instancesPosition.size();j1++){
				for (int j2=0;j2<workMemory[i].instancesPosition.get(j1).size();j2++){
					
					int l=workMemory[i].instancesPosition.get(j1).get(j2).length;
					
					if (id == workMemory[i].instancesPosition.get(j1).get(j2)[l-1]){
						
						float val=signatureList.get(i+InteractionList.nbDF).interaction.valence() * (float)Math.exp(-0.1*(float)l);
						
						//System.out.print("instance ");
						//for (int m=l-1;m>=0;m--) System.out.print(workMemory[i].instancesPosition.get(j1).get(j2)[m]+", ");
						//System.out.println(" of inter "+i+" add "+val+" to inter "+id);
						utility+=val;
					}
					
				}
			}
			
		}
		//System.out.println(" instance utility of inter "+id +" : "+utility);
		return utility;
	}
	
	
	///////////////////////////////////////////////////////////////////////////////////////////////////////////
	public void save(){
		System.out.println("=====================prepare to save agnostic memory...======================");
		
		String fileName = Observer.path+"spaceMemory/spaceMemoryV10_2_2_1 "+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 "+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, signatureList);
				else signatureList.get(i).learn(context1,-1, signatureList);
			}
		}
	} 
	
}
