
import jason.architecture.*;
import jason.asSemantics.*;
import jason.asSyntax.*;
import jason.infra.centralised.BaseCentralisedMAS;
import java.util.*;
import java.util.zip.*;
import java.util.concurrent.*;
import java.util.logging.*;
import java.io.*;
import jaca.*;
/**
 * Example of an agent that only uses Jason BDI engine. It runs without all
 * Jason IDE stuff. (see Jason FAQ for more information about this example)
 *
 * The class must extend AgArch class to be used by the Jason engine.
 */
public class EJasonArch extends AgArch {
  //******************Editable variables*********************
  private static final String actionID = "!";
  private static final String perceptID = "";
  private static final String failID = "@";
  private static final String messageID = "*";
  //*********************************************************
  private static Logger logger = Logger.getLogger(EJasonArch.class.getSimpleName());

  //JasonBulb makes the interface between the Agent and the rest of the system
  private JasonBulb bulb = new JasonBulb(this);
  Thread bulbThread = new Thread(bulb);

  private List<Literal> worldState = new ArrayList<Literal>();
  private List<Message> mailBox = new ArrayList<Message>();

  private ConcurrentHashMap<ActionExec,String> waitingConfirmList = new ConcurrentHashMap<ActionExec,String>();

  @Override
  public void init(){
    //initialize the bulb
    bulbThread.setDaemon(true);
    bulbThread.start();

    while(!bulb.isReady()){
      try{
          Thread.sleep(20);//wait a bit for bulb to start up
      }catch(Exception e){e.printStackTrace();}
    }
  }

    // this method just add some perception for the agent
  @Override
  public Collection<Literal> perceive() {
      super.perceive();
      Collection<Literal> p = new ArrayList<Literal>();//super.perceive();
      p.addAll(this.worldState);
      //p.addAll();
      //bulb.bulbSend(this.heartbeat);//request new percepts
      return p;
  }

  protected CAgentArch getCartagoArch() {
    AgArch arch = getTS().getUserAgArch().getFirstAgArch();
    while (arch != null) {
        if (arch instanceof CAgentArch) {
            return (CAgentArch)arch;
        }
        arch = arch.getNextAgArch();
    }
    return null;
  }

    // this method get the agent actions
  @Override
  public void act(ActionExec action) {
    //System.out.println(action.toString());
    if(action.getActionTerm().getNS().toString().equals("ext")){
      waitingConfirmList.put(action,actionToString(action));
      System.out.println(actionToString(action));
      boolean done = bulb.bulbSend(encodeAction(actionToString(action)));

      if(!done){
        //Abort action
        action.setResult(false);
        actionExecuted(action);
        System.out.println("action fail");
      }
    }
    else{
      //System.out.println("mandei pro cartago");
      super.act(action);
    }
  }

  private static Object stringToObject( String s ) throws IOException , ClassNotFoundException {
    try{
      byte [] data = Base64.getDecoder().decode( s.getBytes() );
      ByteArrayInputStream bais = new ByteArrayInputStream(data);
      GZIPInputStream gzipIn = new GZIPInputStream(bais);
      ObjectInputStream ois = new ObjectInputStream( gzipIn );
      Object o  = ois.readObject();
      ois.close();
      gzipIn.close();
      bais.close();
      return o;
    } catch (Exception e) {
      //System.out.println(s);
      throw e;
    }
  }

  private static String objectToString( Serializable o ) throws IOException {
    ByteArrayOutputStream baos = new ByteArrayOutputStream();
    GZIPOutputStream gzipOut = new GZIPOutputStream(baos);
    ObjectOutputStream oos = new ObjectOutputStream( gzipOut );
    oos.writeObject( o );
    oos.flush();
    oos.close();
    return Base64.getEncoder().encodeToString(baos.toByteArray());
  }

  @Override
  public void checkMail() {
    Circumstance C = getTS().getC();
    Message im = new Message(); // pega a msgs da tua conexao
    while (!this.mailBox.isEmpty()) {
      im = this.mailBox.remove(0); // pega  aprox. msgs da tua conexao
      //System.out.println("Content is of type: " + im.getPropCont().getClass().getName());
      //System.out.println("Received message: " + im.toString());
      C.addMsg(im);
      if (logger.isLoggable(Level.FINE)) logger.fine("received message: " + im);
    }
  }

  @Override
  public void sendMsg(jason.asSemantics.Message m) throws Exception {
    String recipient = m.getReceiver();
    String recipientWithMessage = recipient + "," + this.objectToString(this.setPropContAsObject(m));
    boolean sent = bulb.bulbSend(encodeMessage(recipientWithMessage));
    if(!sent){
      throw new Exception("Message not sent");
    }
  }

  //broadcasts are the same as send, but the destination is null
  @Override
  public void broadcast(jason.asSemantics.Message m) throws Exception {
    String recipientWithMessage = "," + this.objectToString(this.setPropContAsObject(m));
    boolean sent = bulb.bulbSend(encodeMessage(recipientWithMessage));
    if(!sent){
      throw new Exception("Broadcast not sent");
    }
  }

  private jason.asSemantics.Message setPropContAsObject(jason.asSemantics.Message m){
    if (m.getPropCont() instanceof Atom) {
        Object o = getCartagoArch().getJavaLib().getObject((Atom)m.getPropCont());
        if (o != null){
                m.setPropCont(o);
        }
    }
    return m;
  }

  public void addToMailBox(String strMsg){
    try{//Message.parseMsg(strMsg)
      this.mailBox.add( new Message( (Message) this.stringToObject(strMsg)));
    } catch(Exception ignored){
      //e.printStackTrace();
    }
  }

  public void addPercepts(List<Literal> newState){
    this.worldState = newState;
  }

  public List<String[]> splitPercepts(String percepts){
    List<String[]> terms = new ArrayList<String[]>();
    String[] splitPercepts = percepts.split(";"); //at this stage, each element is a string p(x,y)
    for(int i = 0; i<splitPercepts.length; i++){
      terms.add(splitPercepts[i].split("[(),]"));//each element of terms become [p, x, y]
    }
    return terms;
  }

  public List<Literal> toLiteral(List<String[]> strPercepts){
    List<Literal> perceptsList = new ArrayList<Literal>();
    if(!strPercepts.isEmpty()){
      for(int i = 0; i<strPercepts.size(); i++){
        String[] strTerms = strPercepts.get(i);

        if(!strTerms[0].isEmpty()){//if there is a functor
          Literal literalPercept = ASSyntax.createLiteral(strTerms[0]);//first is functor
          if(strTerms.length>1){//if there are other terms
            for(int j=1; j < strTerms.length; j++){
              try{
                literalPercept.addTerm(ASSyntax.parseTerm(strTerms[j]));
              }catch(Exception e){e.printStackTrace();}
            }
          }
          perceptsList.add(literalPercept);
        }
      }
    }
    return perceptsList;
  }

  private List<Literal> findFunctor(List<Literal> list, String functor){
    List<Literal> matches = new ArrayList<Literal>();
    for(Literal object : list){
      if(object.getFunctor().equals(functor)){
        matches.add(object);
      }
    }
    return matches;
  }

  public List<String[]> getPercepts(String functor){
    List<Literal> litPercepts = findFunctor(this.worldState, functor);
    List<String[]> strPercepts = new ArrayList<String[]>();
    for(Literal object : litPercepts){
      strPercepts.add(((Literal) object).toString().split("[(),]"));
    }
    return strPercepts;
  }

  public List<String[]> getPercepts(){
    List<Literal> litPercepts = this.worldState;
    List<String[]> strPercepts = new ArrayList<String[]>();
    for(Literal object : litPercepts){
      strPercepts.add(((Literal) object).toString().split("[(),]"));
    }
    return strPercepts;
  }

  public String actionToString(ActionExec action){
    String s = action.getActionTerm().getFunctor();
    List<Term> terms = action.getActionTerm().getTerms();
    if (action.getActionTerm().hasTerm()){
      s = s + "(";
      for(Term term : terms){
        if(term.isString()){
          s = s + ((StringTerm) term).getString() + ",";
        } else if(term.isNumeric()){
          try{
            s = s + Double.toString(((NumberTerm) term).solve()) + ",";
          } catch(Exception e) {e.printStackTrace();}
        }
      }
      s = s.substring(0, s.length()-1) + ")"; //take last ',' out and close with ')'
    }
    return s;
  }

  public void confirmAction(String actionStr){
    Iterator it = waitingConfirmList.entrySet().iterator();
    while(it.hasNext()){

      Map.Entry pair = (Map.Entry) it.next();
      if(pair.getValue().equals(actionStr)){
        //set that the execution was ok
        ((ActionExec)pair.getKey()).setResult(true);
        actionExecuted((ActionExec)pair.getKey());
        it.remove();
      }
    }
  }

  public void failAction(String actionStr){
    Iterator it = waitingConfirmList.entrySet().iterator();
    while(it.hasNext()){
      Map.Entry pair = (Map.Entry)it.next();
      if(pair.getValue().equals(actionStr)){
        //set that the execution was ok
        ((ActionExec)pair.getKey()).setResult(false);
        actionExecuted((ActionExec)pair.getKey());
        it.remove();
        //waitingConfirmList.remove(pair.getKey());
      }
    }
  }

  public boolean isFail(String failStr){
    return failStr.substring(0,this.failID.length()).equals(this.failID);
  }

  public String decodeFail(String message){
    return message.substring(this.failID.length(),message.length());
  }

  private String encodeFail(String message){
    return this.failID+message;
  }

  public boolean isAction(String actionStr){
    return actionStr.substring(0,this.actionID.length()).equals(this.actionID);
  }

  public String decodeAction(String message){
    return message.substring(this.actionID.length(),message.length());
  }

  private String encodeAction(String message){
    return this.actionID+message;
  }

  public boolean isPercept(String perceptStr){
    return perceptStr.substring(0,this.perceptID.length()).equals(this.perceptID);
  }

  public String decodePercept(String message){
    return message.substring(this.perceptID.length(),message.length());
  }

  private String encodePercept(String message){
    return this.perceptID+message;
  }

  public boolean isMessage(String message){
    return message.startsWith(this.messageID);
  }

  public String decodeMessage(String message){
    return message.substring(this.messageID.length(),message.length());
  }

  private String encodeMessage(String message){
    return this.messageID+message;
  }


    //@Override
    //public boolean canSleep() {
    //    return true;
    //}

    //@Override
    //public boolean isRunning() {
    //    return true;
    //}

    // a very simple implementation of sleep
    //public void sleep() {
    //    try {
    //        Thread.sleep(1000);
    //    } catch (InterruptedException e) {}
    //}

    // Not used methods
    // This simple agent does not need messages/control/...
    //@Override
    //public void sendMsg(jason.asSemantics.Message m) throws Exception {
    //}

    //@Override
    //public void broadcast(jason.asSemantics.Message m) throws Exception {
    //}
    //@Override
    //public void checkMail() {
    //}
}
