package savi_ros_java.savi_ros_bdi.agent_core;

import jason.architecture.AgArch;
import jason.asSemantics.*;
import jason.asSyntax.Literal;
import jason.asSyntax.Structure;
import savi_ros_java.savi_ros_bdi.agent_state.SyncAgentState;

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.InputStream;
import java.nio.file.FileSystems;
import java.nio.file.Path;
import java.util.List;
import java.util.Properties;

/**
 * The agent class. This is responsible for the agent reasoning cycle with the Jason bDI engine.
 * It runs without all the Jason IDE stuff. (see Jason FAQ)
 *
 * Based upon a class found in SAVI: https://github.com/NMAI-lab/SAVI
 *
 * @author Patrick Gavigan
 * @date 6 December 2019
 */
public class SaviAgent extends AgArch implements Runnable {
    private static final String broadcastID = "BROADCAST";

    private String configFilePath;
    private String name;
    private SyncAgentState agentState;
    private boolean running;

    //public SaviAgent(String id, String type) {
    public SaviAgent() {
        this("../../../resources/main/settings.cfg");
        // Load parameters from configuration file
        //Properties agentProperties = this.loadProperties();

        // Load the agent
        //this.loadAgent(agentProperties);

        // Get the agent state
        //agentState = SyncAgentState.getSyncAgentState();
    }

    public SaviAgent(String configPath) {
        this.configFilePath = new String(configPath);

        // Load parameters from configuration file
        Properties agentProperties = this.loadProperties();

        // Load the agent
        this.loadAgent(agentProperties);

        // Get the agent state
        agentState = SyncAgentState.getSyncAgentState();
    }

    /**
     * Execute the BDI reasoning cycle.
     */
    public void run() {
        System.out.println("I'm a Jason agent_core and I'm starting");

        try {
            running = true;

            while (isRunning()) {
                // calls the Jason engine to perform one reasoning cycle
                System.out.println("agent_core is reasoning....");
                long reasoningStartTime = System.currentTimeMillis();
                getTS().reasoningCycle();
                Long elapsed = new Long(System.currentTimeMillis() - reasoningStartTime);
                this.agentState.addPerformanceMeasure(elapsed.toString());

                //if (getTS().canSleep()) {
                //    sleep();
                //}
            }
            System.out.println("agent_core stopped.");
        } catch (Exception e) {
            System.out.println("Run error " + e.toString());
        }
    }

    /**
     * Get the agent's name
     * @return
     */
    public String getAgName() {
        return name;
    }


    /**
     * Get the perceptions for the agent. This is automatically called by the agent's reasoning cycle.
     * @return
     */
    @Override
    public List<Literal> perceive() {

        System.out.println("I'm in the perceive method");
        while(this.agentState.isPerceptionAvailable() == false) {
            sleep();       // Wait until there is a perception
        }

        // Get the list of literals to send to the agent
        List<Literal> perceptionLiterals = this.agentState.getPerceptions();
        System.out.println("agent_core Perceiving: ");
        System.out.println(perceptionLiterals.toString());

        return perceptionLiterals;
    }

    /**
     * This method gets the agent actions. This is automatically called by the agent's reasoning cycle.
     */
    @Override
    public void act(ActionExec action) {
        // Get the action term
        Structure actionTerm = action.getActionTerm();
        String actionString = actionTerm.toString();

        // Add the action to agentState if it is longer than 0 characters
        if (actionString.length() > 0) {
            agentState.addAction(actionString);
        }

        // Set that the execution was OK and flag it as complete.
        action.setResult(true);
        actionExecuted(action);
    }

    /**
     * Determine if the agent can sleep
     * @return
     */
    @Override
    public boolean canSleep() {
        return !agentState.isPerceptionAvailable();
    }

    /**
     * Determine if the agent is running
     * @return
     */
    @Override
    public boolean isRunning() {
        return running;
    }

    /**
     * Stop the agent
     */
    @Override
    public void stop() {
        running = false;
    }

    /**
     * A very simple implementation of sleep
     */
    public void sleep() {
        //logger.log(Level.FINE, "Snoozing");
        //System.out.println("Snoozing");
        try {
            Thread.sleep(1);                    // TODO: Need to revisit this
        } catch (InterruptedException e) {
        }
    }

    /**
     * Send message to another agent (via simulated wifi).
     */
    @Override
    public void sendMsg(Message m) throws Exception {
        // Make sure sender parameter is set
        if (m.getSender() == null)  m.setSender(getAgName());

        // Put the message in the wifi queue
        this.agentState.addToOutbox(m.toString());
    }

    /**
     * in case agent is sleeping
     * TODO: in case we get problems of agents not waking up on messages, this is what to use!!
     * */
    public void wakeAgent() {
        wakeUpSense();
    }

    @Override
    public void broadcast(Message m) throws Exception {
        m.setReceiver(broadcastID);
        this.sendMsg(m);
    }

    @Override
    public void checkMail() {
        if (agentState.checkInboxMailAvailable()) {
            Circumstance circ = getTS().getC();
            List<Message> messages = this.agentState.getInbox();
            for (Message currentMessage:messages) {
                if (currentMessage.getReceiver().equals(broadcastID) || currentMessage.getReceiver().equals(this.getAgName())) {
                    circ.addMsg(currentMessage);
                }
            }
        }
    }

    /**
     * Load the properties of the agent from the configuration file
     * @return
     */
    private Properties loadProperties() {
        Path path = FileSystems.getDefault().getPath(".").toAbsolutePath();
        System.out.println("Current directory: " + path.toString());

        // Load parameters from configuration file
        InputStream input = null;
        Properties agentProperties = new Properties();
        String configFileName = this.configFilePath;
        System.out.println("Config file path: " + configFileName);

        try {
            input = new FileInputStream(configFileName);
        } catch (FileNotFoundException e) {
            System.out.println("File not found");
        } catch (Exception e) {
            System.out.println("Exception occurred");
            System.out.println(e.toString());
        }

        try{
            agentProperties.load(input);
        } catch (Exception e) {
            System.out.println("Exception occurred");
            System.out.println(e.toString());
        }

        return agentProperties;
    }

    /**
     * Set up the agent usign the provided properties
     * @param agentProperties
     */
    private void loadAgent(Properties agentProperties) {

        // Get the properteis
        String aslPath = agentProperties.getProperty("ASL_PATH");
        String agentType = agentProperties.getProperty("AGENT_TYPE");
        String agentName = agentProperties.getProperty("AGENT_NAME");

        // Set up the Jason agent
        try {
            Agent ag = new SaviAgentBrain();
            new TransitionSystem(ag, null, null, this);
            this.name = agentName;

            System.out.println("ASL_PATH: " + aslPath);
            InputStream aslFile = new FileInputStream(aslPath);
            ag.initAg();
            ag.load(aslFile, agentType);
        } catch (Exception e) {
            System.out.println("Init error loading asl file: " + e.toString());
            System.out.println("ASL Path was: " + aslPath);
        }
    }
}
