package com.framsticks.net.client3D;

import java.io.IOException;
import java.util.ArrayList;

import pl.vorg.mowa.core.graphics.FloatVertexAttrib;
import pl.vorg.mowa.core.graphics.Geometry;
import pl.vorg.mowa.core.graphics.GeometryUtils;
import pl.vorg.mowa.core.graphics.IndexBuffer;
import pl.vorg.mowa.core.graphics.PrimitiveType;
import pl.vorg.mowa.core.graphics.VertexStream;

import com.framsticks.net.client3D.Creature.ModelType;

//TODO: ACT przejscie klienta na eventHandlery (simulation start/stop, creatures, error)
/**
 * Klient sieciowy framstickow. Wysyla komunikaty na serwer i zajmuje sie interpretacja odpowiedzi.
 * @author vorg
 */
public class Client {
	private Communication comm;
	
	public Client() {
	}
	
	public void send(String request) {
		try {
			printResponse(sendRequest(request));
		} catch (Exception e) { 
			Log.getInstance().log("err", e.toString());
		} 
	}

	private ArrayList<String> sendRequest(String request) throws IOException, CommunicationErrorException {
		if (comm == null) {
			Log.getInstance().log("wrn", "Sending request failed. You are not connected!");
			return new ArrayList<String>();
		}
		comm.sendMessage(request);
		ArrayList<String> respond = null;
		try {
			respond = comm.readMessage();
		} 
		catch (InterruptedException e) {
			Log.getInstance().log("err", e.getMessage());
			return new ArrayList<String>();
		}
		return respond;
	}
	
	private void printResponse(ArrayList<String> response) {
		if (response == null) {
			Log.getInstance().log("err","Response is null");
			return;
		}
		for(int i=0; i<response.size(); i++) {
			Log.getInstance().log("<<<", response.get(i));
		}
	}
	
	/**
	 * nawiazanie polaczenia z atrapą serwera
	 */ 
	public void initConnectionMock() {
		comm = new CommunicationMock();
		Log.getInstance().log("dbg","Client.init");
		try {
			Log.getInstance().log("dbg","Connecting to mock");
			comm.connect("127.0.0.1", 9009);
		} 
		catch (Exception e) {
			e.printStackTrace();
		} 
	}
	
	public void initConnection(String host, int port) {
		comm = new Communication();
		Log.getInstance().log("dbg","Client.init");
		try {
			Log.getInstance().log("dbg","Connecting to " + host + ":" + port);
			comm.connect(host, port);
		} 
		catch (Exception e) {
			e.printStackTrace();
		} 
	}
	
	
	public void closeConnection() {
		try {
			if (comm != null) { 
				comm.disconnect();
				comm = null;
			}
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
	
	public Creature[] readCreatures() throws IOException, CommunicationErrorException{
		return readCreatures(-1,-1,true);
	}
	
	public Creature[] readCreatures(int groupNo, int creatureNo, boolean neurons)
	throws IOException, CommunicationErrorException {
		ArrayList<Creature> creatures = new ArrayList<Creature>();
		String request;
		String creatureFields=neurons?"name,parts{x,y,z},mechparts{x,y,z},joints{p1,p2},neurodefs{p,j,d}":"name,parts{x,y,z},mechparts{x,y,z},joints{p1,p2}";
		if(groupNo == -1 || creatureNo == -1){
			request = "get /simulator/populations/groups/+/creatures/+/("+creatureFields+")";			
		}else{
			request = "get /simulator/populations/groups/"+Integer.toString(groupNo)+"/creatures/"+Integer.toString(creatureNo)+"/("+creatureFields+")";			
		}
		String line = null;
		ArrayList<String> response = null;
		response = sendRequest(request);
		printResponse(response);
		final int INIT = 0;
		final int PARTS = 1;
		final int MECHPARTS = 2;
		final int JOINTS = 3;
		final int NEURODEFS = 4;
		int mode = INIT;
		Creature creature = null;
		ArrayList<float[]> parts = null;
		ArrayList<float[]> mechParts = null;
		ArrayList<int[]> joints = null;
		ArrayList<NeuroDef> neurodefs = null;
		float[] part = new float[3];
		float[] mechPart = new float[3];
		int[] joint = new int[2];
		NeuroDef ndef=new NeuroDef();
		for (int k = 0; k < response.size(); ++k) {
			line = response.get(k).toString();
			if (line.startsWith("file")) {
				String groupsStr = "groups/"; 
				int groupBegin = line.indexOf(groupsStr) + groupsStr.length();
				int groupEnd = line.indexOf("/", groupBegin);
				if( groupBegin < 0  || groupEnd < 0){
					
					System.out.println( line );
					System.out.println("groupBegin="+ groupBegin + "; groupEnd=" + groupEnd );
					System.out.println("request=" + request);
					System.out.println("groupNo=" + groupNo + "; creatureNo=" + creatureNo);
					
				}
					
				int group = Integer.parseInt(line.substring(groupBegin, groupEnd));
				String creaturesStr = "creatures/";
				int creatureIndexBegin = line.indexOf(creaturesStr) + creaturesStr.length();
				int creatureIndexEnd = line.indexOf("/", creatureIndexBegin);
				int creatureIndex = Integer.parseInt(line.substring(creatureIndexBegin, creatureIndexEnd));
				
				creature = null;
				for(Creature c : creatures) {
					if ((c.getGroup() == group) && (c.getIndex() == creatureIndex)) {
						creature = c;
						break;
					}
				}
				if (creature == null) {
					creature = new Creature("", group, creatureIndex);
					creatures.add(creature);
				}
				
				if (line.indexOf("/parts") > -1) {
					mode = PARTS;
					parts = new ArrayList<float[]>();
				}
				else if (line.indexOf("/mechparts") > -1) {
					mode = MECHPARTS;
					mechParts = new ArrayList<float[]>();
				}
				else if (line.indexOf("/joints") > -1) {
					mode = JOINTS;
					joints = new ArrayList<int[]>();
				}
				else if (line.indexOf("/neurodefs") > -1) {
					mode = NEURODEFS;
					neurodefs= new ArrayList<NeuroDef>();
				}
			}
			else if (line.startsWith("x")) {
				float value = Float.parseFloat(line.substring(line.indexOf(":")+1));
				if (mode == PARTS) part[0] = value;
				else if (mode == MECHPARTS) mechPart[0] = value;
			}
			else if (line.startsWith("y")) {
				float value = Float.parseFloat(line.substring(line.indexOf(":")+1));
				if (mode == PARTS) part[2] = value; //y and z swap
				else if (mode == MECHPARTS) mechPart[2] = value; //y and z swap
			}
			else if (line.startsWith("z")) {
				float value = Float.parseFloat(line.substring(line.indexOf(":")+1));
				if (mode == PARTS) {
					part[1] = value; //z and y swap
					parts.add(part);
					part = new float[3];
				}
				else if (mode == MECHPARTS) {
					mechPart[1] = value; //z and y swap
					mechParts.add(mechPart);
					mechPart = new float[3];
				}
			}
			else if (line.startsWith("p1")) {
				int p1 = Integer.parseInt(line.substring(line.indexOf(":")+1));
				joint[0] = p1;
			}
			else if (line.startsWith("p2")) {
				int p2 = Integer.parseInt(line.substring(line.indexOf(":")+1));
				joint[1] = p2;
				joints.add(joint);
				joint = new int[2];
			}
			else if ((mode==NEURODEFS)&&(line.startsWith("p:"))) {
			int index=Integer.parseInt(line.substring(line.indexOf(":")+1));
			if (index>=0) {ndef.locationIndex=index; ndef.locationType=NeuroDef.LocationType.Part;}
			}
			else if ((mode==NEURODEFS)&&(line.startsWith("j:"))) {
			int index=Integer.parseInt(line.substring(line.indexOf(":")+1));
			if (index>=0) {ndef.locationIndex=index; ndef.locationType=NeuroDef.LocationType.Joint;}
			}
			else if ((mode==NEURODEFS)&&(line.startsWith("d:"))) {
			ndef.def=line.substring(2);
			neurodefs.add(ndef);
			ndef=new NeuroDef();
			}
			else if (line.startsWith("eof")) {
				if (mode == PARTS) {
					creature.setParts(parts.toArray(new float[][]{}), Creature.ModelType.Parts);
				}
				else if (mode == MECHPARTS) {
					creature.setParts((mechParts.toArray(new float[][]{})), Creature.ModelType.MechParts);
				}
				else if (mode == JOINTS) {
					creature.setJoints(joints.toArray(new int[][]{}));
				}
				else if (mode == NEURODEFS) {
				creature.setNeuroDefs(neurodefs.toArray(new NeuroDef[]{}));
			}
			}
		}
		Log.getInstance().log("dbg", "" + creatures.size() + " creatures loaded");
		
		ArrayList<Creature> invalidCreatures = new ArrayList<Creature>();
		for(Creature c : creatures) {
			if ((c.getJoints() == null) || (c.getParts(ModelType.Parts) == null) || (c.getParts(ModelType.MechParts) == null)) {
				invalidCreatures.add(c);
				Log.getInstance().log("wrn", "Invalid creature found");
			}
		}
		for(Creature c : invalidCreatures) {
			creatures.remove(c);
		}
		invalidCreatures.clear();
		return creatures.toArray(new Creature[]{});
	}
	
	public World readWorld() 
	throws IOException, CommunicationErrorException {
		final int INIT = 0;
		final int POINTS = 1;
		final int FACES = 2;
		World world = new World();
		
		String line = null;
		String request = "get /simulator/world wrldbnd,wrldwat,faces";
		ArrayList<String> response = null;
		
		response = sendRequest(request);
		printResponse(response);
		
		ArrayList<Float> points = new ArrayList<Float>();
		ArrayList<Integer> faces = new ArrayList<Integer>();
		PrimitiveType primitiveType = PrimitiveType.None;
		PrimitiveType facePrimitiveType;
		int mode = INIT;
		
		for (int k = 0; k < response.size(); ++k) {
			line = response.get(k);
			if (line.startsWith("wrldbnd")) {
				world.setBoundaries(Integer.parseInt(line.substring(line.indexOf(":")+1)));
				continue;
			}
			else if (line.startsWith("wrldwat")) {
				world.setWaterLevel(Float.parseFloat(line.substring(line.indexOf(":")+1)));
				continue;
			}
			else if (line.trim().equals("p")) {
				mode = POINTS;
				continue;
			}
			else if (line.trim().equals("f")) { 
				mode = FACES;
				continue;
			}
			else if (line.trim().equals("~")) {
				break;
			}
			
			if (mode == POINTS) {
				String[] tokens = line.split(" ");
				points.add(Float.parseFloat(tokens[0]));
				points.add(Float.parseFloat(tokens[2])); //y and z swap
				points.add(Float.parseFloat(tokens[1])); //z and y swap
			}
			else if (mode == FACES) {
				String[] tokens = line.split(" ");
				if (tokens.length == 3) facePrimitiveType = PrimitiveType.Triangles;
				else if (tokens.length == 4) facePrimitiveType = PrimitiveType.Quads;
				else facePrimitiveType = PrimitiveType.None;
				
				if (primitiveType == PrimitiveType.None) {
					primitiveType = facePrimitiveType;
				}
				else if (primitiveType != facePrimitiveType) {
					Log.getInstance().log("err", "Unsupported terrain type (" + primitiveType + "!=" + primitiveType + ")");
					return world;
				}
				
				for(String token : tokens) {
					faces.add(Integer.parseInt(token));
				}
			}
		}
		
		float[] positions = new float[points.size()];
		for(int i=0; i<positions.length; i++) {
			positions[i] = points.get(i);
		}
		
		int[] indices = new int[faces.size()];
		for(int i=0; i<indices.length; i++) {
			indices[i] = faces.get(i);
		}
		
		Geometry geometry = new Geometry();
		VertexStream vs = new VertexStream();
		geometry.setVertexStream(vs);
		geometry.setPrimitiveType(primitiveType);
		
		IndexBuffer ib = new IndexBuffer();
		ib.setBuffer(indices);
		vs.setIndexBuffer(ib);
		
		FloatVertexAttrib posAttrib = new FloatVertexAttrib("pos");
		posAttrib.setBuffer(positions);
		vs.addAttrib(posAttrib);
		
		geometry.updateBoundingBox();
		
		GeometryUtils.generateNormals(geometry);
		
		world.setGeometry(geometry);
		
		return world;
	}
	
	public Boolean isConnected() {
		return comm.isConnected();
	}
}