package org.allmyinfo.operation.nodes.core;

import org.allmyinfo.nature.permission.MeaningPermissionNature;
import org.allmyinfo.nature.permission.SourcePermissionNature;
import org.allmyinfo.nature.permission.TargetPermissionNature;
import org.allmyinfo.nodes.NodeId;
import org.allmyinfo.nodes.ValidationException;
import org.allmyinfo.operation.BaseException;
import org.allmyinfo.operation.Operation;
import org.allmyinfo.operation.PathElement;
import org.allmyinfo.operation.nodes.NodesExecutionContext;
import org.allmyinfo.operation.nodes.NodesVoidOperation;
import org.eclipse.jdt.annotation.NonNull;
import org.eclipse.jdt.annotation.Nullable;

public final class InsertLink extends NodesVoidOperation {
	public static final @NonNull String OPERATION = "insertLink";
	public static final @NonNull String OPERAND_SOURCE = "source";
	public static final @NonNull String OPERAND_TARGET = "target";
	public static final @NonNull String OPERAND_MEANING = "meaning";
	public static final @NonNull String OPERAND_OVERWRITE = "overwrite";
	public final @NonNull Operation sourceOp;
	public final @NonNull Operation targetOp;
	public final @NonNull Operation meaningOp;
	public final @Nullable Operation overwriteOp;
	public final TargetPermissionNature.@NonNull Reactor tpnReactor;
	public final SourcePermissionNature.@NonNull Reactor spnReactor;
	public final MeaningPermissionNature.@NonNull Reactor mpnReactor;

	public InsertLink(final @NonNull Operation sourceOp, final @NonNull Operation targetOp,
			final @NonNull Operation meaningOp, @Nullable final Operation overwriteOp,
			final TargetPermissionNature.@NonNull Reactor tpnReactor,
			final SourcePermissionNature.@NonNull Reactor spnReactor,
			final MeaningPermissionNature.@NonNull Reactor mpnReactor) {
		this.sourceOp = sourceOp;
		this.targetOp = targetOp;
		this.meaningOp = meaningOp;
		this.overwriteOp = overwriteOp;
		this.tpnReactor = tpnReactor;
		this.spnReactor = spnReactor;
		this.mpnReactor = mpnReactor;
	}

	@Override
	public @NonNull String getName() {
		return OPERATION;
	}

	@Override
	protected void execute(final @NonNull PathElement parentPathElement,
			final @NonNull NodesExecutionContext context) {
		final PathElement pathElement = new PathElement(OPERATION, parentPathElement);
		final NodeId sourceNodeId = (NodeId) sourceOp.execute(pathElement, context);
		if (sourceNodeId == null)
			throw new NullPointerException(sourceOp.toString());
		final NodeId targetNodeId = (NodeId) targetOp.execute(pathElement, context);
		if (targetNodeId == null)
			throw new NullPointerException(targetOp.toString());
		final NodeId meaningNodeId = (NodeId) meaningOp.execute(pathElement, context);
		if (meaningNodeId == null)
			throw new NullPointerException(meaningOp.toString());
		final Operation overwriteOp = this.overwriteOp;
		final Boolean overwriteBoolean;
		if (overwriteOp == null)
			overwriteBoolean = null;
		else
			overwriteBoolean = (Boolean) overwriteOp.execute(pathElement, context);
		context.checkPermissions(targetNodeId, pathElement, tpnReactor.getInstance());
		context.checkPermissions(sourceNodeId, pathElement, spnReactor.getInstance());
		context.checkPermissions(meaningNodeId, pathElement, mpnReactor.getInstance());
		try {
			context.getTxn().insertLink(sourceNodeId, targetNodeId, meaningNodeId,
				overwriteBoolean == null ? false : overwriteBoolean.booleanValue());
		}
		catch (final ValidationException e) {
			throw new InsertLinkFailure(e, pathElement);
		}
	}

	public static final class InsertLinkFailure extends BaseException {
		private static final long serialVersionUID = 1L;

		public InsertLinkFailure(final @NonNull Throwable cause,
				final @NonNull PathElement pathElement) {
			super(cause, pathElement);
		}

		@Override
		public @NonNull String getTranslationKey() {
			return "exception.failed_to_insert_link";
		}
	}
}
