// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

package org.chromium.mojo.bindings;

import org.chromium.mojo.bindings.Callbacks.Callback1;
import org.chromium.mojo.bindings.Interface.AbstractProxy.HandlerImpl;
import org.chromium.mojo.bindings.interfacecontrol.QueryVersion;
import org.chromium.mojo.bindings.interfacecontrol.RequireVersion;
import org.chromium.mojo.bindings.interfacecontrol.RunInput;
import org.chromium.mojo.bindings.interfacecontrol.RunMessageParams;
import org.chromium.mojo.bindings.interfacecontrol.RunOrClosePipeInput;
import org.chromium.mojo.bindings.interfacecontrol.RunOrClosePipeMessageParams;
import org.chromium.mojo.bindings.interfacecontrol.RunOutput;
import org.chromium.mojo.bindings.interfacecontrol.RunResponseMessageParams;
import org.chromium.mojo.system.Core;
import org.chromium.mojo.system.MessagePipeHandle;
import org.chromium.mojo.system.MojoException;
import org.chromium.mojo.system.Pair;

import java.io.Closeable;
import java.util.concurrent.Executor;

/**
 * Base class for mojo generated interfaces.
 */
public interface Interface extends ConnectionErrorHandler, Closeable {

    /**
     * The close method is called when the connection to the interface is closed.
     *
     * @see java.io.Closeable#close()
     */
    @Override
    public void close();

    /**
     * A proxy to a mojo interface. This is base class for all generated proxies. It implements the
     * Interface and each time a method is called, the parameters are serialized and sent to the
     * {@link MessageReceiverWithResponder}, along with the response callback if needed.
     */
    public interface Proxy extends Interface {
        /**
         * Class allowing to interact with the proxy itself.
         */
        public interface Handler extends Closeable {
            /**
             * Sets the {@link ConnectionErrorHandler} that will be notified of errors.
             */
            public void setErrorHandler(ConnectionErrorHandler errorHandler);

            /**
             * Unbinds the proxy and passes the handle. Can return null if the proxy is not bound or
             * if the proxy is not over a message pipe.
             */
            public MessagePipeHandle passHandle();

            /**
             * Returns the version number of the interface that the remote side supports.
             */
            public int getVersion();

            /**
             * Queries the max version that the remote side supports. On completion, the result will
             * be returned as the input of |callback|. The version number of this interface pointer
             * will also be updated.
             */
            public void queryVersion(Callback1<Integer> callback);

            /**
             * If the remote side doesn't support the specified version, it will close its end of
             * the message pipe asynchronously. The call does nothing if |version| is no greater
             * than getVersion().
             * <p>
             * If you make a call to requireVersion() with a version number X which is not supported
             * by the remote side, it is guaranteed that all calls to the interface methods after
             * requireVersion(X) will be ignored.
             */
            public void requireVersion(int version);
        }

        /**
         * Returns the {@link Handler} object allowing to interact with the proxy itself.
         */
        public Handler getProxyHandler();
    }

    /**
     * Base implementation of {@link Proxy}.
     */
    abstract class AbstractProxy implements Proxy {
        /**
         * Implementation of {@link Handler}.
         */
        protected static class HandlerImpl implements Proxy.Handler, ConnectionErrorHandler {
            /**
             * The {@link Core} implementation to use.
             */
            private final Core mCore;

            /**
             * The {@link MessageReceiverWithResponder} that will receive a serialized message for
             * each method call.
             */
            private final MessageReceiverWithResponder mMessageReceiver;

            /**
             * The {@link ConnectionErrorHandler} that will be notified of errors.
             */
            private ConnectionErrorHandler mErrorHandler;

            /**
             * The currently known version of the interface.
             */
            private int mVersion;

            /**
             * Constructor.
             *
             * @param core the Core implementation used to create pipes and access the async waiter.
             * @param messageReceiver the message receiver to send message to.
             */
            protected HandlerImpl(Core core, MessageReceiverWithResponder messageReceiver) {
                this.mCore = core;
                this.mMessageReceiver = messageReceiver;
            }

            void setVersion(int version) {
                mVersion = version;
            }

            /**
             * Returns the message receiver to send message to.
             */
            public MessageReceiverWithResponder getMessageReceiver() {
                return mMessageReceiver;
            }

            /**
             * Returns the Core implementation.
             */
            public Core getCore() {
                return mCore;
            }

            /**
             * Sets the {@link ConnectionErrorHandler} that will be notified of errors.
             */
            @Override
            public void setErrorHandler(ConnectionErrorHandler errorHandler) {
                this.mErrorHandler = errorHandler;
            }

            /**
             * @see ConnectionErrorHandler#onConnectionError(MojoException)
             */
            @Override
            public void onConnectionError(MojoException e) {
                if (mErrorHandler != null) {
                    mErrorHandler.onConnectionError(e);
                }
            }

            /**
             * @see Closeable#close()
             */
            @Override
            public void close() {
                mMessageReceiver.close();
            }

            /**
             * @see Interface.Proxy.Handler#passHandle()
             */
            @Override
            public MessagePipeHandle passHandle() {
                @SuppressWarnings("unchecked")
                HandleOwner<MessagePipeHandle> handleOwner =
                        (HandleOwner<MessagePipeHandle>) mMessageReceiver;
                return handleOwner.passHandle();
            }

            /**
             * @see Handler#getVersion()
             */
            @Override
            public int getVersion() {
                return mVersion;
            }

            /**
             * @see Handler#queryVersion(org.chromium.mojo.bindings.Callbacks.Callback1)
             */
            @Override
            public void queryVersion(final Callback1<Integer> callback) {
                RunMessageParams message = new RunMessageParams();
                message.input = new RunInput();
                message.input.setQueryVersion(new QueryVersion());

                InterfaceControlMessagesHelper.sendRunMessage(getCore(), mMessageReceiver, message,
                        new Callback1<RunResponseMessageParams>() {
                            @Override
                            public void call(RunResponseMessageParams response) {
                                if (response.output != null
                                        && response.output.which()
                                                == RunOutput.Tag.QueryVersionResult) {
                                    mVersion = response.output.getQueryVersionResult().version;
                                }
                                callback.call(mVersion);
                            }
                        });
            }

            /**
             * @see Handler#requireVersion(int)
             */
            @Override
            public void requireVersion(int version) {
                if (mVersion >= version) {
                    return;
                }
                mVersion = version;
                RunOrClosePipeMessageParams message = new RunOrClosePipeMessageParams();
                message.input = new RunOrClosePipeInput();
                message.input.setRequireVersion(new RequireVersion());
                message.input.getRequireVersion().version = version;
                InterfaceControlMessagesHelper.sendRunOrClosePipeMessage(
                        getCore(), mMessageReceiver, message);
            }
        }

        /**
         * The handler associated with this proxy.
         */
        private final HandlerImpl mHandler;

        protected AbstractProxy(Core core, MessageReceiverWithResponder messageReceiver) {
            mHandler = new HandlerImpl(core, messageReceiver);
        }

        /**
         * @see Interface#close()
         */
        @Override
        public void close() {
            mHandler.close();
        }

        /**
         * @see Proxy#getProxyHandler()
         */
        @Override
        public HandlerImpl getProxyHandler() {
            return mHandler;
        }

        /**
         * @see ConnectionErrorHandler#onConnectionError(org.chromium.mojo.system.MojoException)
         */
        @Override
        public void onConnectionError(MojoException e) {
            mHandler.onConnectionError(e);
        }
    }

    /**
     * Base implementation of Stub. Stubs are message receivers that deserialize the payload and
     * call the appropriate method in the implementation. If the method returns result, the stub
     * serializes the response and sends it back.
     *
     * @param <I> the type of the interface to delegate calls to.
     */
    abstract class Stub<I extends Interface> implements MessageReceiverWithResponder {

        /**
         * The {@link Core} implementation to use.
         */
        private final Core mCore;

        /**
         * The implementation to delegate calls to.
         */
        private final I mImpl;

        /**
         * Constructor.
         *
         * @param core the {@link Core} implementation to use.
         * @param impl the implementation to delegate calls to.
         */
        public Stub(Core core, I impl) {
            mCore = core;
            mImpl = impl;
        }

        /**
         * Returns the Core implementation.
         */
        protected Core getCore() {
            return mCore;
        }

        /**
         * Returns the implementation to delegate calls to.
         */
        protected I getImpl() {
            return mImpl;
        }

        /**
         * @see org.chromium.mojo.bindings.MessageReceiver#close()
         */
        @Override
        public void close() {
            mImpl.close();
        }

    }

    /**
     * A {@link MessageReceiverWithResponder} implementation that forwards all calls to the thread
     * the ThreadSafeForwarder was created.
     */
    class ThreadSafeForwarder implements MessageReceiverWithResponder {

        /**
         * The {@link MessageReceiverWithResponder} that will receive a serialized message for
         * each method call.
         */
        private final MessageReceiverWithResponder mMessageReceiver;

        /**
         * The {@link Executor} to forward all tasks to.
         */
        private final Executor mExecutor;

        /**
         * Constructor.
         *
         * @param core the Core implementation used to create pipes and access the async waiter.
         * @param messageReceiver the message receiver to send message to.
         */
        public ThreadSafeForwarder(Core core, MessageReceiverWithResponder messageReceiver) {
            mMessageReceiver = messageReceiver;
            mExecutor = ExecutorFactory.getExecutorForCurrentThread(core);
        }

        /**
         * @see org.chromium.mojo.bindings.MessageReceiver#close()
         */
        @Override
        public void close() {
            mExecutor.execute(() -> {
                mMessageReceiver.close();
            });
        }

        /**
         * @see org.chromium.mojo.bindings.MessageReceiver#accept()
         */
        @Override
        public boolean accept(Message message) {
            mExecutor.execute(() -> {
                mMessageReceiver.accept(message);
            });
            return true;
        }

        /**
         * @see org.chromium.mojo.bindings.MessageReceiverWithResponder#acceptWithResponder()
         */
        @Override
        public boolean acceptWithResponder(Message message, MessageReceiver responder) {
            mExecutor.execute(() -> {
                mMessageReceiver.acceptWithResponder(message, responder);
            });
            return true;
        }
    }

    /**
     * The |Manager| object enables building of proxies and stubs for a given interface.
     *
     * @param <I> the type of the interface the manager can handle.
     * @param <P> the type of the proxy the manager can handle. To be noted, P always extends I.
     */
    abstract class Manager<I extends Interface, P extends Proxy> {

        /**
         * Returns the name of the interface. This is an opaque (but human readable) identifier used
         * by the service provider to identify services.
         */
        public abstract String getName();

        /**
         * Returns the version of the managed interface.
         */
        public abstract int getVersion();

        /**
         * Binds the given implementation to the handle.
         */
        public void bind(I impl, MessagePipeHandle handle) {
            // The router (and by consequence the handle) is intentionally leaked. It will close
            // itself when the connected handle is closed and the proxy receives the connection
            // error.
            Router router = new RouterImpl(handle);
            bind(handle.getCore(), impl, router);
            router.start();
        }

        /**
         * Binds the given implementation to the InterfaceRequest.
         */
        public final void bind(I impl, InterfaceRequest<I> request) {
            bind(impl, request.passHandle());
        }

        /**
         * Returns a Proxy that will send messages to the given |handle|. This implies that the
         * other end of the handle must be bound to an implementation of the interface.
         */
        public final P attachProxy(MessagePipeHandle handle, int version) {
            RouterImpl router = new RouterImpl(handle);
            P proxy = attachProxy(handle.getCore(), router);
            DelegatingConnectionErrorHandler handlers = new DelegatingConnectionErrorHandler();
            handlers.addConnectionErrorHandler(proxy);
            router.setErrorHandler(handlers);
            router.start();
            ((HandlerImpl) proxy.getProxyHandler()).setVersion(version);
            return proxy;
        }

        /**
         * Constructs a new |InterfaceRequest| for the interface. This method returns a Pair where
         * the first element is a proxy, and the second element is the request. The proxy can be
         * used immediately.
         */
        public final Pair<P, InterfaceRequest<I>> getInterfaceRequest(Core core) {
            Pair<MessagePipeHandle, MessagePipeHandle> handles = core.createMessagePipe(null);
            P proxy = attachProxy(handles.first, 0);
            return Pair.create(proxy, new InterfaceRequest<I>(handles.second));
        }

        public final InterfaceRequest<I> asInterfaceRequest(MessagePipeHandle handle) {
            return new InterfaceRequest<I>(handle);
        }

        /**
         * Constructs a thread-safe Proxy forwarding the calls to the given message receiver.
         * All calls can be performed from any thread and are posted to the {@link Executor} that
         * is associated with the thread on which this method was called on.
         *
         * The original Proxy object is unbound.
         */
        public final P buildThreadSafeProxy(P proxy) {
            HandlerImpl handlerImpl = (HandlerImpl) proxy.getProxyHandler();
            Core core = handlerImpl.getCore();
            int version = handlerImpl.getVersion();

            Router router = new RouterImpl(handlerImpl.passHandle());
            // Close the original proxy now that its handle has been passed.
            proxy.close();

            proxy = buildProxy(
                core, new ThreadSafeForwarder(core, new AutoCloseableRouter(core, router)));
            DelegatingConnectionErrorHandler handlers = new DelegatingConnectionErrorHandler();
            handlers.addConnectionErrorHandler(proxy);
            router.setErrorHandler(handlers);
            router.start();
            ((HandlerImpl) proxy.getProxyHandler()).setVersion(version);
            return proxy;
        }

        /**
         * Binds the implementation to the given |router|.
         */
        final void bind(Core core, I impl, Router router) {
            router.setErrorHandler(impl);
            router.setIncomingMessageReceiver(buildStub(core, impl));
        }

        /**
         * Returns a Proxy that will send messages to the given |router|.
         */
        final P attachProxy(Core core, Router router) {
            return buildProxy(core, new AutoCloseableRouter(core, router));
        }

        /**
         * Creates a new array of the given |size|.
         */
        protected abstract I[] buildArray(int size);

        /**
         * Constructs a Stub delegating to the given implementation.
         */
        protected abstract Stub<I> buildStub(Core core, I impl);

        /**
         * Constructs a Proxy forwarding the calls to the given message receiver.
         */
        protected abstract P buildProxy(Core core, MessageReceiverWithResponder messageReceiver);

    }
}
