package lc.minecraft.link;

import lc.esp.sdk.AsyncSender;
import lc.esp.sdk.ESPClient;
import lc.mecha.json.JSONArray;
import lc.mecha.json.JSONObject;
import lc.mecha.log.MechaLogger;
import lc.mecha.log.MechaLoggerFactory;
import org.bukkit.Location;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.block.BlockBreakEvent;
import org.bukkit.event.block.BlockPlaceEvent;
import org.bukkit.event.player.PlayerMoveEvent;

import javax.jms.*;
import java.time.Instant;

/**
 * This class implements a bukkit {@link Listener} which is primarily responsible for receiving events from the server
 * and broadcasting a telemetry signal in realtime to ESP.
 *
 * @author Alex Leigh
 * @since mk2
 */
public class LinkListener implements Listener {
    public static final String KEY_LOC_TO_X = "lc.mc.loc.dst.x";
    public static final String KEY_LOC_TO_Y = "lc.mc.loc.dst.y";
    public static final String KEY_LOC_TO_Z = "lc.mc.loc.dst.z";
    public static final String KEY_LOC_TO_YAW = "lc.mc.loc.dst.yaw";
    public static final String KEY_LOC_TO_PITCH = "lc.mc.loc.dst.pitch";
    public static final String KEY_LOC_FROM_X = "lc.mc.loc.src.x";
    public static final String KEY_LOC_FROM_Y = "lc.mc.loc.src.y";
    public static final String KEY_LOC_FROM_Z = "lc.mc.loc.src.z";
    public static final String KEY_LOC_FROM_YAW = "lc.mc.loc.src.yaw";
    public static final String KEY_LOC_FROM_PITCH = "lc.mc.loc.src.pitch";
    public static final String KEY_EVENT_TYPE = "lc.mc.evt.typ";
    public static final String KEY_BLOCK_TYPE = "lc.mc.blk.typ";
    public static final String KEY_SERVER = "lc.mc.server";
    public static final String KEY_PLAYER_NAME = "lc.mc.player_name";
    public static final String KEY_WORLD = "lc.mc.world";
    public static final String EVENT_BLOCK_BREAK = "lc.mc.evt.blk_break";
    public static final String EVENT_BLOCK_PLACE = "lc.mc.evt.blk_place";
    public static final String EVENT_PLAYER_MOVE = "lc.mc.evt.blk_brk.player_mv";
    private static final MechaLogger logger = MechaLoggerFactory.getLogger(LinkListener.class);
    private final ESPClient esp;
    private final MessageProducer producer;
    private final Session session;
    private final AsyncSender sender;
    private final String serverName;

    public LinkListener(String serverName) throws JMSException {
        this.serverName = serverName;
        esp = new ESPClient();
        // Just for dev/test. This should be in the client, not here.
        // TODO: Move this code to onEnable(), add cleanup code to onDisable()
        Connection connection = esp.getConnectionFactory().createConnection();
        session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
        Destination destination = session.createTopic("LC.TELEMETRY");
        producer = session.createProducer(destination);
        producer.setDeliveryMode(DeliveryMode.NON_PERSISTENT);
        sender = new AsyncSender(producer);
        new Thread(sender).start();
    }

    private void addDstLocation(JSONArray values, Location loc) {
        addValue(values, KEY_LOC_TO_X, Double.toString(loc.getX()));
        addValue(values, KEY_LOC_TO_Y, Double.toString(loc.getY()));
        addValue(values, KEY_LOC_TO_Z, Double.toString(loc.getZ()));
    }

    private void addSrcLocation(JSONArray values, Location loc) {
        addValue(values, KEY_LOC_FROM_X, Double.toString(loc.getX()));
        addValue(values, KEY_LOC_FROM_Y, Double.toString(loc.getY()));
        addValue(values, KEY_LOC_FROM_Z, Double.toString(loc.getZ()));
    }

    private void addDstAngle(JSONArray values, Location loc) {
        // FIXME: Should set UOM to degrees!
        addValue(values, KEY_LOC_TO_YAW, Double.toString(loc.getYaw()));
        addValue(values, KEY_LOC_TO_PITCH, Double.toString(loc.getPitch()));
    }

    private void addSrcAngle(JSONArray values, Location loc) {
        // FIXME: Should set UOM to degrees!
        addValue(values, KEY_LOC_FROM_YAW, Double.toString(loc.getYaw()));
        addValue(values, KEY_LOC_FROM_PITCH, Double.toString(loc.getPitch()));
    }

    private void addValue(JSONArray values, String key, String value) {
        values.put(new JSONObject().put("name", key).put("value", value));
    }

    @EventHandler(ignoreCancelled = true)
    public void onBlockBreak(BlockBreakEvent event) {
        try {
            JSONArray values = new JSONArray();

            addDstLocation(values, event.getBlock().getLocation());
            addSrcLocation(values, event.getPlayer().getLocation());
            addDstLocation(values, event.getBlock().getLocation());
            addSrcLocation(values, event.getPlayer().getLocation());

            TextMessage msg = session.createTextMessage(values.toString());
            msg.setStringProperty(KEY_SERVER, serverName);
            msg.setStringProperty(KEY_PLAYER_NAME, event.getPlayer().getName());
            msg.setStringProperty(KEY_WORLD, event.getBlock().getWorld().getName());
            msg.setStringProperty(KEY_EVENT_TYPE, EVENT_BLOCK_BREAK);
            msg.setBooleanProperty("_is_event", true);
            msg.setDoubleProperty("_time", Instant.now().toEpochMilli());
            logger.trace("Sending message: {}", msg);
            sender.send(msg);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    @EventHandler(ignoreCancelled = true)
    public void onBlockPlaced(BlockPlaceEvent event) {
        try {
            JSONArray values = new JSONArray();
            Location loc = event.getBlock().getLocation();

            addSrcLocation(values, event.getPlayer().getLocation());
            addDstLocation(values, event.getBlock().getLocation());
            addValue(values, KEY_BLOCK_TYPE, event.getBlock().getType().toString());

            TextMessage msg = session.createTextMessage(values.toString());
            msg.setStringProperty(KEY_SERVER, serverName);
            msg.setStringProperty(KEY_PLAYER_NAME, event.getPlayer().getName());
            msg.setStringProperty(KEY_WORLD, loc.getWorld().getName());
            msg.setStringProperty(KEY_EVENT_TYPE, EVENT_BLOCK_PLACE);
            msg.setBooleanProperty("_is_event", true);
            msg.setDoubleProperty("_time", Instant.now().toEpochMilli());
            logger.trace("Sending message: {}", msg);
            sender.send(msg);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }


    @EventHandler(ignoreCancelled = true)
    public void onPlayerMove(PlayerMoveEvent event) {
        try {
            JSONArray values = new JSONArray();
            Location loc = event.getTo();

            addSrcLocation(values, event.getFrom());
            addSrcAngle(values, event.getFrom());
            addDstLocation(values, event.getTo());
            addDstAngle(values, event.getTo());

            TextMessage msg = session.createTextMessage(values.toString());
            msg.setStringProperty(KEY_SERVER, serverName);
            msg.setStringProperty(KEY_PLAYER_NAME, event.getPlayer().getName());
            msg.setStringProperty(KEY_WORLD, loc.getWorld().getName());
            msg.setStringProperty(KEY_EVENT_TYPE, EVENT_PLAYER_MOVE);
            msg.setBooleanProperty("_is_event", true);
            msg.setDoubleProperty("_time", Instant.now().toEpochMilli());
            logger.trace("Sending message: {}", msg);
            sender.send(msg);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
