/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

package tileworld.agent;

import jakCompilation.MemoryClassLoader;
import java.awt.Color;
import java.lang.reflect.InvocationTargetException;
import java.util.LinkedList;
import java.util.TreeSet;
import org.epochx.ge.representation.GECandidateProgram;
import sim.app.woims.Vector2D;
import sim.display.GUIState;
import sim.portrayal.Inspector;
import sim.portrayal.LocationWrapper;
import sim.portrayal.Portrayal;


import tileworld.Parameters;
import tileworld.environment.*;
import tileworld.exceptions.CellBlockedException;

/**
 *
 * @author jakub tomasek
 */
public class SimpleTWAgent extends TWAgent{

    protected GECandidateProgram program;
    
    protected int numberOfTiles=0;
    
    protected TWTile tile1,tile2;
    protected TWHole hole1,hole2;
    Vector2D T1,H1,T2,H2,vector,A1,A2,BD,bestSector,previousVector;
    double angle;
    TWDirection[] directions;
    TreeSet<TWObject> tiles,holes;
    int[] qT1,qT2,qH1,qH2;
    int A2refueling;
    MemoryClassLoader mcl;
    protected double dA2;
    int nOfT;
    
    protected boolean refueling;
    protected int id;
    
    protected  LinkedList<TWAgentPercept> fifo;
    
    
    /**
     * 
     */
    protected Blackboard blackboard;
    
    
    
    
     public SimpleTWAgent(int xpos, int ypos, TWEnvironment env, double fuelLevel,MemoryClassLoader mcl, Blackboard blackboard,int id) {
        super(xpos,ypos,env,fuelLevel);
        this.program=null;
        this.mcl=mcl;
        this.refueling=false;
        this.previousVector=new Vector2D(0, 0);
        this.blackboard=blackboard;
        this.id=id;
        this.fifo=new LinkedList<TWAgentPercept>();
        this.directions=new TWDirection[4];
    }
    
    public SimpleTWAgent(int xpos, int ypos, TWEnvironment env, double fuelLevel, Blackboard blackboard,int id) {
        super(xpos,ypos,env,fuelLevel);
        this.program=null;
        this.refueling=false;
        this.previousVector=new Vector2D(0, 0);
        this.blackboard=blackboard;
        this.id=id;
        this.fifo=new LinkedList<TWAgentPercept>();
        this.directions=new TWDirection[4];
    }
    
    /**
     * Retrieves data from blackboard
     */
    protected void communicateIn(){
        //receive communication
        A2=this.blackboard.getPosition(id);
        A2refueling=(blackboard.getRefueling(id)) ? 1:0;

        //updates my sectors
        //if(A2!=null) memory.visitSectorSec((int) Math.round(A2.x), (int) Math.round(A2.y));
        
        
        LinkedList<TWAgentPercept> percepts=this.blackboard.getPercepts(id);
        while(!percepts.isEmpty()){
            this.memory.addPercept(percepts.poll());
        }
        
    }
    
    /**
     * Interaction with blackboard, limit 3 pieces of information
     */
    protected void communicateOut(){
        //send information
        blackboard.setPosition(id, new Vector2D(this.getX(), getY()));
        TWAgentPercept p=null;
                
        int i=2;
        
        
        if(blackboard.getRefueling(1-id)!=this.refueling  && i>0){
            blackboard.updateRefueling(id, refueling);
            i--;
        }
        
        
        //in case that we have anough of tiles and holes, we don't need to communicate tiles and holes
        if(!isEnvironmentDense()) {
            
            //send mode
            if(this.numberOfTiles()>=2 && blackboard.getMode(1-id)!=TWHole.class && i>0) {
                blackboard.setMode(id, TWHole.class);
                i--;
            }
            else if(this.numberOfTiles()==0 && blackboard.getMode(1-id)!=TWTile.class  && i>0){
                blackboard.setMode(id, TWTile.class);
                i--;
            }
            if(A2!=null) {
                if(blackboard.getMode(id)==TWTile.class && i>0){
                    p= memory.getNearbyPercept((int)Math.round(A2.x),(int) Math.round(A2.y),1000,TWTile.class,this.getX(),this.getY(),0);
                    if(p!=null)  {
                        blackboard.uploadPercept(id, p);
                        p=null;
                        i--;                
                    }
                }
                else if (blackboard.getMode(id)==TWHole.class   && i>0) {                
                    p= memory.getNearbyPercept((int)Math.round(A2.x),(int) Math.round(A2.y),1000,TWHole.class,this.getX(),this.getY(),0);
                    if(p!=null)  {
                        blackboard.uploadPercept(id, p);
                        p=null;
                        i--;
                    }
                }
            }
            
        
       
            while(i>0 && fifo.size()>0){
                blackboard.uploadPercept(id, fifo.pollFirst());
                i--;
            }
        
        }
    }
    

    
    /**
     * Executes the GP program, outputs some vector
     * @return vector in which direction the agent will go
     */
    protected Vector2D decideDirection() {  

        try {
                try {
                    try {
                        Class c = mcl.loadClass("DetermineDirection");
                        try {
                            return (Vector2D) c.getMethod("getDirection", int.class,Vector2D.class,Vector2D.class,Vector2D.class,Vector2D.class,int[].class,int[].class,int[].class,int[].class,Vector2D.class,Vector2D.class,Vector2D.class,Vector2D.class,int.class,double.class).invoke(c.newInstance(),this.numberOfTiles(),T1,T2,H1,H2,qT1,qT2,qH1,qH2,A1,A2,BD,previousVector,A2refueling,dA2);

                            //System.out.println("Vector x:"+vector.x+" y:"+vector.y);
                        } catch (InstantiationException ex) {
                            System.out.println("instatioation");
                            //Logger.getLogger(SimpleTWAgent.class.getName()).log(Level.SEVERE, null, ex);
                        } catch (IllegalAccessException ex) {
                            System.out.println("illegal access");
                            //Logger.getLogger(SimpleTWAgent.class.getName()).log(Level.SEVERE, null, ex);
                        }
                    }  catch (IllegalArgumentException ex) {
                        System.out.println("illegal argument exception");
                        //Logger.getLogger(SimpleTWAgent.class.getName()).log(Level.SEVERE, null, ex);
                    } catch (InvocationTargetException ex) {
                        System.out.println("invocation target exception");
                        //Logger.getLogger(SimpleTWAgent.class.getName()).log(Level.SEVERE, null, ex);
                    }
                } catch (NoSuchMethodException ex) {
                    System.out.println("no such method");
                    //Logger.getLogger(SimpleTWAgent.class.getName()).log(Level.SEVERE, null, ex);
                } catch (SecurityException ex) {
                    System.out.println("Security exception");
                    //Logger.getLogger(SimpleTWAgent.class.getName()).log(Level.SEVERE, null, ex);
                }
            } catch (ClassNotFoundException ex) {
                System.out.println("Class Not found");
                //Logger.getLogger(SimpleTWAgent.class.getName()).log(Level.SEVERE, null, ex);
            }
            return new Vector2D(0, 0);        
           
            

    }
    
    protected TWThought think() {
       
      //we retrive data from blackboard
       this.communicateIn();
       
       memory.visitSector(getX(), getY());
       
       vector=null;
       
       
       //elementary actions of the agent are triggered immediately
        if (this.hasTile() &&  memory.isCellHole(this.x, this.y)) {
            
            return new TWThought(TWAction.PUTDOWN,null);
        }        
        else if (this.numberOfTiles()<3 &&  memory.isCellTile(this.x, this.y)) {
            
            return new TWThought(TWAction.PICKUP,null);
        }
        else if (this.getX()==0 && this.getY()==0 && this.getFuelLevel()<990) {
            this.refueling=false;
            return new TWThought(TWAction.REFUEL,null);
        }
        //triggering refueling
        else if (this.getFuelLevel()<500 && !this.refueling){ 
            
            if(this.getFuelLevel()<=(env.getxDimension()+env.getyDimension()+20) ) this.refueling=true;
            
            else if (A2refueling==0) {
                 int dist= this.getX()+this.getY();
                if(this.getFuelLevel()<(-400/(env.getxDimension()+env.getyDimension())*dist+400)) this.refueling=true;
            }
            
        }
        
        
        tile1=null; hole1=null; tile2=null;hole2=null;
        
        //finds two nearest tiles and holes
        tiles= memory.getNearbyNObjects(x, y, TWTile.class , 2);
        holes= memory.getNearbyNObjects(x, y, TWHole.class , 2);
       
        
        //a bit messy solution to retrieve two tiles and two holes, wasn't necessary
        if(!tiles.isEmpty())  tile1=(TWTile) tiles.pollFirst();
        if(!tiles.isEmpty()) tile2=(TWTile) tiles.pollFirst();
        if(!holes.isEmpty()) hole1=(TWHole) holes.pollFirst();
        if(!holes.isEmpty()) hole2=(TWHole) holes.pollFirst();
        
        //even more messier solution .. should have used separate class
        if(tile1!=null) {
            T1 = new Vector2D(tile1.getX()-x, tile1.getY()-y);
            qT1= memory.getNearbyQuantity(tile1.getX(), tile1.getY());            
        }
        else {
            T1=new Vector2D(0,0);
            qT1=new int[]{0,0,0};
        }
        
        if(hole1!=null)  {
            H1 = new Vector2D(hole1.getX()-x, hole1.getY()-y);
            qH1= memory.getNearbyQuantity(hole1.getX(), hole1.getY());
        }
        else {
            H1=new Vector2D(0,0);
            qH1=new int[]{0,0,0};
        }   
        
        if(tile2!=null)  {
            T2 = new Vector2D(tile2.getX()-x, tile2.getY()-y);
             
            qT2= memory.getNearbyQuantity(tile2.getX(), tile2.getY());  
        }
        else {
            T2=new Vector2D(0,0);
            qT2=new int[]{0,0,0};
        }
        
        if(hole2!=null)  {
            H2 = new Vector2D(hole2.getX()-x, hole2.getY()-y);
             qH2= memory.getNearbyQuantity(hole2.getX(), hole2.getY());
        }
        else {
            H2=new Vector2D(0,0);
            qH2=new int[]{0,0,0};
        }
        
        
       //retrieve the empty sector
        if(BD==null) BD= new Vector2D(memory.bestSector(this.id).x-this.getX(), memory.bestSector(this.id).y-this.getY());
        else if( (BD.x+BD.y)<2 || env.getTime()%10==0 ) BD= new Vector2D(memory.bestSector(this.id).x-this.getX(), memory.bestSector(this.id).y-this.getY());
        
        
        //System.out.println("Best direction -  x:"+bestVector.x+" y:"+bestVector.y);
        
        if(!this.refueling) {
            A1=new Vector2D(getX(),getY());
            if(A2==null) A2=new Vector2D(1,1);
            dA2=Math.sqrt(A2.x*A2.x+A2.y*A2.y);
            
           
            //Vector2D prD=previousVector;
            nOfT=this.numberOfTiles();
            
            vector=this.decideDirection();
        }
        else {
            //in case we are refueling
            double angleT1=O.getVectorAngle(T1);
            double angleT2=O.getVectorAngle(T2);
            double angleH1=O.getVectorAngle(H1);
            double angleH2=O.getVectorAngle(H2);
            if( ((angleT1<=-Math.PI/2 && angleT1>=-Math.PI) || angleT1>=Math.PI) && this.numberOfTiles()<3) vector = T1;
            else if(((angleT2<=-Math.PI/2 && angleT2>=-Math.PI) || angleT2>=Math.PI) && this.numberOfTiles()<3) vector = T2;
            else vector=new Vector2D(-x, -y);
            
            if(O.n(vector)>O.n(H1) && this.numberOfTiles()>0 && ((angleH1<=-Math.PI/2 && angleH1>=-Math.PI) || angleH1>=Math.PI)) vector = H1;
            else if(O.n(vector)>O.n(H2) && this.numberOfTiles()>0 && ((angleH2<=-Math.PI/2 && angleH2>=-Math.PI) || angleH2>=Math.PI)) vector = H2;
           
            
        }
        
       /* if(vector==null || (vector.x==0 && vector.y==0)) {
            vector=BD;
        }*/
        
        
        //in case that output vector is zero, we walk randomly
        if(vector==null || (vector.x==0 && vector.y==0)) {
            //randomly exploring
            vector=O.rV();
            //vector.x+=previousVector.x/2;
            //vector.y+=previousVector.y/2;
        }
        
        
        angle=O.getVectorAngle(vector);
        
        //save previous vector
        previousVector=vector;
        

        directions=getDirectionFromVector();
            
       // System.out.println("I decided to go to: "+direction+" or to "+directions[1]);
        if(freeDirection(directions[0])) {            
            return new TWThought(TWAction.MOVE,directions[0]);
        }
        else if(freeDirection(directions[1])){
             return new TWThought(TWAction.MOVE,directions[1]);
        } 
        else if(freeDirection(directions[2])){
             return new TWThought(TWAction.MOVE,directions[2]);
        }  
        else if(freeDirection(directions[3])){
             return new TWThought(TWAction.MOVE,directions[3]);
        }  
        
        
        //System.out.println("Simple Score: " + this.score);
        //return new TWThought(TWAction.MOVE,getRandomDirection());
     
        //otherwise do nothing   
        return new TWThought(null,null);
    }

    @Override
    protected void act(TWThought thought) {
       // System.out.println(thought.getAction());
        
        
        if(thought.getAction()== TWAction.PUTDOWN) {
             this.putTileInHole(memory.getNearbyHole(x, y, Parameters.endTime));
             this.sendFloor(x, y);             
             return;
        }        
        else if(thought.getAction()== TWAction.PICKUP) {
             
             this.pickUpTile(memory.getNearbyTile(x, y, Parameters.endTime));
             this.sendFloor(x, y);
             
            
             // System.out.println("picked up!!" +this.numberOfTiles());
        }
        else if(thought.getAction()== TWAction.MOVE) {
            try {
                //System.out.println("Direction: "+thought.getDirection());
            this.move(thought.getDirection());
            }  catch (CellBlockedException ex) {

            // Cell is blocked, replan?
            }
        }
        else if(thought.getAction()== TWAction.REFUEL) {
            this.refuel();
        }
        
        this.communicateOut();
        
        
    }

    @Override
    public String getName() {
        if(this.id==0) return "Agent Manchester";
        else return "Agent Singapore";
    }
    
    
    
    
   
    public Portrayal getPort() {
        //red filled box.
        Color c;
        if(this.id==0) c=Color.cyan;
        else c=Color.magenta;
        
            return new TWAgentPortrayal(c, Parameters.defaultSensorRange) {
                @Override
                public Inspector getInspector(LocationWrapper wrapper, GUIState state) {
                    // make the inspector
                    return new AgentInspector(super.getInspector(wrapper, state), wrapper, state);
                }
            };
    }
  
    
    protected boolean freeDirection(TWDirection d) {
        
        if( (d==TWDirection.W && memory.isCellBlocked(this.x-1, this.y)) || (d==TWDirection.E && memory.isCellBlocked(this.x+1, this.y))|| (d==TWDirection.N && memory.isCellBlocked(this.x, this.y-1)) ||(d==TWDirection.S && memory.isCellBlocked(this.x, this.y+1))) {
         
            return false;
        }
        return true;
        
      
    }


    public boolean isRefueling() {
        return refueling;
    }
    
    public void sendFloor(int fx, int fy){
        fifo.add(new TWAgentPercept(new TWFloor(fx, fy, this.getEnvironment(), this.getEnvironment().getTime(),1), this.getEnvironment().getTime()));
        //TWAgentPercept p = new TWAgentPercept(new TWFloor(fx, fy, this.getEnvironment(), this.getEnvironment().getTime(),this.getEnvironment().getTime()+1), this.getEnvironment().getTime());
    }
    
    public void sendObject(TWObject o){
        fifo.add(new TWAgentPercept(o,this.getEnvironment().getTime()));
    }
    
    public boolean isEnvironmentDense(){
        //small cheating
        //wouldn't be too hard to find out in few steps of the program from memory
        return env.getxDimension()==50;
    }
    
    public TWDirection[] getDirectionFromVector(){
        TWDirection[] d=new TWDirection[4];
                if(angle<=Math.PI/4 && angle>= -Math.PI/4) {
            d[0]=TWDirection.E;
            if(angle>=0) {
                d[1]=TWDirection.S;
                d[2]=TWDirection.N;
                d[3]=TWDirection.W;
            }  
            else {
                d[1]=TWDirection.N;
                d[2]=TWDirection.S;
                d[3]=TWDirection.W;
            } 
        } 
        else if(angle>Math.PI/4 && angle<3*Math.PI/4) {
            d[0]=TWDirection.S;
            if(angle>=Math.PI/2) {
                d[1]=TWDirection.W;
                d[2]=TWDirection.E;
                d[3]=TWDirection.N;
            }  
            else {
                d[1]=TWDirection.E;
                d[2]=TWDirection.W;
                d[3]=TWDirection.N;
            } 
        }
        else if(angle>=3*Math.PI/4 || angle<=-3*Math.PI/4){
            d[0]=TWDirection.W;
            if(angle>=0) {
                d[1]=TWDirection.S;
                d[2]=TWDirection.N;
                d[3]=TWDirection.E;
            }  
            else {
                d[1]=TWDirection.N;
                d[2]=TWDirection.S;
                d[3]=TWDirection.E;
            } 
        }
        else if(angle<-Math.PI/4 && angle>-3*Math.PI/4) {
            d[0]=TWDirection.N;
            if(angle>=-Math.PI/2) {
                d[1]=TWDirection.E;
                d[2]=TWDirection.W;
                d[3]=TWDirection.S;
            }  
            else {
                d[1]=TWDirection.W;
                d[2]=TWDirection.E;
                d[3]=TWDirection.S;
            } 
        }
        return d;
    }
    
}
