/*
 * Copyright (c) 2015-2021, David A. Bauer. All rights reserved.
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 * http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package io.actor4j.core.actors;

import java.util.ArrayDeque;
import java.util.Deque;
import java.util.Queue;
import java.util.UUID;
import java.util.function.Predicate;

import io.actor4j.core.messages.ActorMessage;

public abstract class EmbeddedActor {
	protected EmbeddedHostActor host;
	
	protected String name;
	
	protected UUID id;
	
	protected boolean active;
	
	protected Deque<Predicate<ActorMessage<?>>> behaviourStack;
	
	protected Queue<ActorMessage<?>> stash; //must be initialized by hand
	
	public EmbeddedActor(EmbeddedHostActor host) {
		this(null, host);
	}
	
	public EmbeddedActor(String name, EmbeddedHostActor host, UUID id) {
		super();
		this.name = name;
		this.host = host;
		this.id = id;
		active = true;
		behaviourStack = new ArrayDeque<>();
	}
	
	public EmbeddedActor(String name, EmbeddedHostActor host) {
		this(name, host, UUID.randomUUID());
	}
	
	public ActorRef host() {
		return host;
	}

	public String getName() {
		return name;
	}
	
	public UUID getId() {
		return id;
	}
	
	public UUID self() {
		return id;
	}
	
	public UUID getParent() {
		return host.getId();
	}
	
	public boolean isActive() {
		return active;
	}

	public void setActive(boolean active) {
		this.active = active;
	}
	
	public boolean embedded(ActorMessage<?> message) {
		boolean result = false;
		
		if (active) {
			Predicate<ActorMessage<?>> behaviour = behaviourStack.peek();
			if (behaviour==null)
				result = receive(message);
			else
				result = behaviour.test(message);	
		}
		
		return result;
	}
	
	public <T> boolean embedded(T value, int tag, UUID dest) {
		return embedded(new ActorMessage<T>(value, tag, getParent(), dest));
	}
	
	public abstract boolean receive(ActorMessage<?> message);
	
	public void become(Predicate<ActorMessage<?>> behaviour, boolean replace) {
		if (replace && !behaviourStack.isEmpty())
			behaviourStack.pop();
		behaviourStack.push(behaviour);
	}
	
	public void become(Predicate<ActorMessage<?>> behaviour) {
		become(behaviour, true);
	}
	
	public void unbecome() {
		behaviourStack.pop();
	}
	
	public void unbecomeAll() {
		behaviourStack.clear();
	}
	
	public void await(final Predicate<ActorMessage<?>> action, boolean replace) {
		become(new Predicate<ActorMessage<?>>() {
			@Override
			public boolean test(ActorMessage<?> message) {
				return action.test(message);
			}
		}, replace);
	}
	
	public void await(final Predicate<ActorMessage<?>> action) {
		await(action, true);
	}
	
	public void await(final UUID source, final Predicate<ActorMessage<?>> action, boolean replace) {
		become(new Predicate<ActorMessage<?>>() {
			@Override
			public boolean test(ActorMessage<?> message) {
				boolean result = false;
				if (message.source.equals(source))
					result = action.test(message);
				return result;
			}
		}, replace);
	}
	
	public void await(final UUID source, final Predicate<ActorMessage<?>> action) {
		await(source, action, true);
	}
	
	public void await(final int tag, final Predicate<ActorMessage<?>> action, boolean replace) {
		become(new Predicate<ActorMessage<?>>() {
			@Override
			public boolean test(ActorMessage<?> message) {
				boolean result = false;
				if (message.tag==tag)
					result = action.test(message);
				return result;
			}
		}, replace);
	}
	
	public void await(final int tag, final Predicate<ActorMessage<?>> action) {
		await(tag, action, true);
	}
	
	public void await(final UUID source, final int tag, final Predicate<ActorMessage<?>> action, boolean replace) {
		become(new Predicate<ActorMessage<?>>() {
			@Override
			public boolean test(ActorMessage<?> message) {
				boolean result = false;
				if (message.source.equals(source) && message.tag==tag)
					result = action.test(message);
				return result;
			}
		}, replace);
	}
	
	public void await(final UUID source, final int tag, final Predicate<ActorMessage<?>> action) {
		await(source, tag, action, true);
	}
	
	public void await(final Predicate<ActorMessage<?>> predicate, final Predicate<ActorMessage<?>> action, boolean replace) {
		become(new Predicate<ActorMessage<?>>() {
			@Override
			public boolean test(ActorMessage<?> message) {
				boolean result = false;
				if (predicate.test(message))
					result = action.test(message);
				return result;
			}
		}, replace);
	}
	
	public void await(final Predicate<ActorMessage<?>> predicate, final Predicate<ActorMessage<?>> action) {
		await(predicate, action, true);
	}
	
	public void send(ActorMessage<?> message) {
		if (host!=null)
			host.sendWithinHost(message);
	}
	
	public void send(ActorMessage<?> message, UUID dest) {
		message.source = self();
		message.dest   = dest;
		send(message);
	}
	
	public <T> void tell(T value, int tag, UUID dest) {
		send(new ActorMessage<T>(value, tag, self(), dest));
	}
	
	public void forward(ActorMessage<?> message, UUID dest) {
		message.dest   = dest;
		send(message);
	}
	
	public void preStart() {
		// empty
	}
	
	public void preRestart(Exception reason) {
		// empty
	}
	
	public void postRestart(Exception reason) {
		preStart();
	}
	
	public void postStop() {
		// empty
	}
}

@@ -2859,355 +2859,8 @@
 %0A%09%0D%0A
-%09public void await(final Predicate%3CActorMessage%3C?%3E%3E action, boolean replace) %7B%0D%0A%09%09become(new Predicate%3CActorMessage%3C?%3E%3E() %7B%0D%0A%09%09%09@Override%0D%0A%09%09%09public boolean test(ActorMessage%3C?%3E message) %7B%0D%0A%09%09%09%09return action.test(message);%0D%0A%09%09%09%7D%0D%0A%09%09%7D, replace);%0D%0A%09%7D%0D%0A%09%0D%0A%09public void await(final Predicate%3CActorMessage%3C?%3E%3E action) %7B%0D%0A%09%09await(action, true);%0D%0A%09%7D%0D%0A%09%0D%0A
 %09pub

Update for EmbeddedActor