package konstructs.plugin;

import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;

import konstructs.api.messages.BoxQueryResult;
import scala.concurrent.duration.Duration;

import akka.actor.ActorRef;
import akka.actor.UntypedActorWithStash;
import konstructs.api.*;
import konstructs.api.messages.*;

public abstract class KonstructsActor extends UntypedActorWithStash {

    ActorRef universe;

    public KonstructsActor(ActorRef universe) {
        this.universe = universe;
    }

    /**
     * Called by Akka when we receive a message
     */
    public void onReceive(Object message) {

        if (message instanceof ViewBlockResult) {
            ViewBlockResult blockPosition = (ViewBlockResult)message;
            onViewBlockResult(blockPosition);
            return;
        }

        if (message instanceof ReceiveStack) {
            ReceiveStack receiveBlock = (ReceiveStack)message;
            onReceiveStack(receiveBlock);
            return;
        }

        if (message instanceof BlockUpdateEvent) {
            BlockUpdateEvent event = (BlockUpdateEvent)message;
            onBlockUpdateEvent(event);
            return;
        }

        if (message instanceof BoxQueryResult) {
            BoxQueryResult result = (BoxQueryResult)message;
            onBoxQueryResult(result);
            return;
        }

        unhandled(message);
    }

    /**
     * Return universe ActorRef.
     * @return ActorRef
     */
    public ActorRef getUniverse() {
        return universe;
    }

    /**
     * This function is called when we receive a ViewBlockResult message.
     */
    public void onViewBlockResult(ViewBlockResult blockPosition) {
        unhandled(blockPosition);
    }

    /**
     * This function is called when we receive a ReceiveStack message.
     */
    public void onReceiveStack(ReceiveStack receiveStack) {
        unhandled(receiveStack);
    }

    /**
     * This function is called when we receive a BoxQueryResult
     */
    public void onBoxQueryResult(BoxQueryResult result) {
        unhandled(result);
    }

    /**
     * Called when a block is updated/created
     * @param block     The block
     */
    public void onBlockUpdateEvent(BlockUpdateEvent event) {
        unhandled(event);
    }

    /**
     * Ask the server for a block
     * @param   p   The position
     */
    public void viewBlock(Position p) {
        universe.tell(new ViewBlock(p), getSelf());
    }

    /** Query for a box of blocks
     *  @param from Starting corner of box (this block is included)
     *  @param until End corner of box (this block is excluded)
     */
    public void boxQuery(Box box) {
        universe.tell(box, getSelf());
    }

    /**
     * Schedule a message to my self
     * @param   obj  The object to send
     * @param   msec Time to wait, in milliseconds
     */
    public void scheduleSelfOnce(Object obj, int msec) {
        scheduleOnce(obj, msec, getSelf());
    }

    /**
     * Schedule a message to another actor
     * @param   obj  The object to send
     * @param   msec Time to wait, in milliseconds
     * @param   to   The actor that will receive the message
     */
    public void scheduleOnce(Object obj, int msec, ActorRef to) {
        getContext().system().scheduler().scheduleOnce(
                Duration.create(msec, TimeUnit.MILLISECONDS),
                to, obj, getContext().system().dispatcher(), null);
    }

}

@@ -2718,32 +2718,1753 @@
 Self());%0A    %7D%0A%0A
+    /**%0A     * Replace all blocks that matches a filter with blocks from a%0A     * position to BlockTypeId mapping%0A     * @param filter The filter to be matched for each block to be replaced%0A     * @param blocks The blocks to replace with%0A     */%0A    public void replaceBlocks(BlockFilter filter, Map%3CPosition, BlockTypeId%3E blocks) %7B%0A        getUniverse().tell(new ReplaceBlocks(filter, blocks), getSelf());%0A    %7D%0A%0A    /**%0A     * Replace any block type at a given position with a block of%0A     * VACUUM.  This can be seen as a way to %22remove%22 blocks.%0A     * @param position The position of the block to be replaced%0A     */%0A    public void replaceWithVacuum(Position position) %7B%0A        replaceWithVacuum(BlockFilterFactory.VACUUM, position);%0A    %7D%0A%0A    /**%0A     * Replace a block that matches the filter at a given position%0A     * with a block of VACUUM.  This can be seen as a way to%0A     * %22remove%22 blocks.%0A     * @param filter The filter to be matched before replacing the block%0A     * @param position The position of the block to be replaced%0A     */%0A    public void replaceWithVacuum(BlockFilter filter, Position position) %7B%0A        getUniverse().tell(new ReplaceBlock(filter, position, Block.create(BlockTypeId.VACUUM)), getSelf());%0A    %7D%0A%0A    /**%0A     * Replace a block if it is VACUUM. This can be seen as a way to%0A     * only add a block to the world if what is already there is%0A     * VACUUM.%0A     * @param position The position of the block to try to replace%0A     * @param block The block to be added in the other blocks place%0A     */%0A    public void replaceVacuumBlock(Position position, Block block) %7B%0A        getUniverse().tell(new ReplaceBlock(BlockFilterFactory.VACUUM, position, block), getSelf());%0A    %7D%0A%0A
     /**%0A     * S

Add convenience methods for replacing blocks