package info.jbcs.minecraft.vending.network;

import com.google.common.base.Throwables;
import info.jbcs.minecraft.vending.Vending;
import io.netty.buffer.ByteBuf;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.network.Packet;
import net.minecraft.network.PacketBuffer;
import net.minecraft.util.IThreadListener;
import net.minecraftforge.fml.common.network.simpleimpl.IMessage;
import net.minecraftforge.fml.common.network.simpleimpl.IMessageHandler;
import net.minecraftforge.fml.common.network.simpleimpl.MessageContext;
import net.minecraftforge.fml.relauncher.Side;

import java.io.IOException;

/**
 * https://github.com/coolAlias/Tutorial-Demo/blob/master/src/main/java/tutorial/network/AbstractMessage.java
 * <p>
 * Creating an abstract message class similar to {@link Packet} allows us to take
 * advantage of Minecraft's PacketBuffer class, which has many useful methods.
 * <p>
 * Should the IMessageHandler be implemented as a GenericMessageHandler class,
 * every child message class will still have to have an empty implementation of
 * the handler, just so it can be registered:
 * <p>
 * public static class Handler extends GenericMessageHandler<SomeMessage> {}
 * <p>
 * This is kind of ridiculous, so instead the message will also implement the handler,
 * despite the fact that a handler instance shouldn't have any of the class members or
 * methods that a message instance does (since a handler is not a message).
 * <p>
 * To combat that incongruity, the #onMessage method will be made final.
 * <p>
 * As a bonus, registration of this class can be made less verbose than normal, since the
 * same class is used for both the IMessage and the IMessageHandler.
 */
@SuppressWarnings({"WeakerAccess", "FinalPrivateMethod", "FinalStaticMethod", "Convert2Lambda"})
public abstract class AbstractMessage<T extends AbstractMessage<T>> implements IMessage, IMessageHandler<T, IMessage> {
    /**
     * 1.8 ONLY: Ensures that the message is being handled on the main thread
     */
    private static final <T extends AbstractMessage<T>> void checkThreadAndEnqueue(final AbstractMessage<T> msg, final MessageContext ctx) {
        IThreadListener thread = Vending.proxy.getThreadFromContext(ctx);
        // pretty much copied straight from vanilla code, see {@link PacketThreadUtil#checkThreadAndEnqueue}
        thread.addScheduledTask(new Runnable() {
            public void run() {
                msg.process(Vending.proxy.getPlayerEntity(ctx), ctx.side);
            }
        });
    }

    /**
     * Some PacketBuffer methods throw IOException - default handling propagates the exception.
     * if an IOException is expected but should not be fatal, handle it within this method.
     */
    protected abstract void read(PacketBuffer buffer) throws IOException;

    /**
     * Some PacketBuffer methods throw IOException - default handling propagates the exception.
     * if an IOException is expected but should not be fatal, handle it within this method.
     */
    protected abstract void write(PacketBuffer buffer) throws IOException;

    /**
     * Called on whichever side the message is received;
     * for bidirectional packets, be sure to check side
     * If {@link #requiresMainThread()} returns true, this method is guaranteed
     * to be called on the main Minecraft thread for this side.
     */
    public abstract void process(EntityPlayer player, Side side);

    /**
     * If message is sent to the wrong side, an exception will be thrown during handling
     *
     * @return True if the message is allowed to be handled on the given side
     */
    protected boolean isValidOnSide(Side side) {
        return true; // default allows handling on both sides, i.e. a bidirectional packet
    }

    /**
     * Whether this message requires the main thread to be processed (i.e. it
     * requires that the world, player, and other objects are in a valid state).
     */
    protected boolean requiresMainThread() {
        return true;
    }

    @Override
    public void fromBytes(ByteBuf buffer) {
        try {
            read(new PacketBuffer(buffer));
        } catch (IOException e) {
            throw Throwables.propagate(e);
        }
    }

    @Override
    public void toBytes(ByteBuf buffer) {
        try {
            write(new PacketBuffer(buffer));
        } catch (IOException e) {
            throw Throwables.propagate(e);
        }
    }

    //=====================================================================//
    /*
     * Make the implementation final so child classes don't need to bother
	 * with it, since the message class shouldn't have anything to do with
	 * the handler. This is simply to avoid having to have:
	 *
	 * public static class Handler extends GenericMessageHandler<OpenGuiMessage> {}
	 *
	 * in every single message class for the sole purpose of registration.
	 */
    @Override
    public final IMessage onMessage(T msg, MessageContext ctx) {
        if (!msg.isValidOnSide(ctx.side)) {
            throw new RuntimeException("Invalid side " + ctx.side.name() + " for " + msg.getClass().getSimpleName());
        } else if (msg.requiresMainThread()) {
            checkThreadAndEnqueue(msg, ctx);
        } else {
            msg.process(Vending.proxy.getPlayerEntity(ctx), ctx.side);
        }
        return null;
    }

    /**
     * Messages that can only be sent from the server to the client should use this class
     */
    public static abstract class AbstractClientMessage<T extends AbstractMessage<T>> extends AbstractMessage<T> {
        @Override
        protected final boolean isValidOnSide(Side side) {
            return side.isClient();
        }
    }

    /**
     * Messages that can only be sent from the client to the server should use this class
     */
    public static abstract class AbstractServerMessage<T extends AbstractMessage<T>> extends AbstractMessage<T> {
        @Override
        protected final boolean isValidOnSide(Side side) {
            return side.isServer();
        }
    }
}