/*
 * Copyright 2016 the original author or authors.
 *
 * 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 org.gradle.process.internal.worker;

import org.gradle.api.logging.LogLevel;
import org.gradle.internal.classloader.ClasspathUtil;
import org.gradle.internal.operations.BuildOperationIdentifierRegistry;
import org.gradle.internal.remote.ObjectConnection;
import org.gradle.process.internal.JavaExecHandleBuilder;
import org.gradle.process.internal.worker.request.Receiver;
import org.gradle.process.internal.worker.request.RequestProtocol;
import org.gradle.process.internal.worker.request.ResponseProtocol;
import org.gradle.process.internal.worker.request.WorkerAction;

import java.io.File;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Set;

class DefaultSingleRequestWorkerProcessBuilder<PROTOCOL> implements SingleRequestWorkerProcessBuilder<PROTOCOL> {
    private final Class<PROTOCOL> protocolType;
    private final Class<? extends PROTOCOL> workerImplementation;
    private final DefaultWorkerProcessBuilder builder;

    public DefaultSingleRequestWorkerProcessBuilder(Class<PROTOCOL> protocolType, Class<? extends PROTOCOL> workerImplementation, DefaultWorkerProcessBuilder builder) {
        this.protocolType = protocolType;
        this.workerImplementation = workerImplementation;
        this.builder = builder;
        builder.worker(new WorkerAction(workerImplementation));
        builder.setImplementationClasspath(ClasspathUtil.getClasspath(workerImplementation.getClassLoader()).getAsURLs());
    }

    @Override
    public WorkerProcessSettings setBaseName(String baseName) {
        builder.setBaseName(baseName);
        return this;
    }

    @Override
    public String getBaseName() {
        return builder.getBaseName();
    }

    @Override
    public WorkerProcessSettings applicationClasspath(Iterable<File> files) {
        builder.applicationClasspath(files);
        return this;
    }

    @Override
    public Set<File> getApplicationClasspath() {
        return builder.getApplicationClasspath();
    }

    @Override
    public WorkerProcessSettings sharedPackages(String... packages) {
        builder.sharedPackages(packages);
        return this;
    }

    @Override
    public WorkerProcessSettings sharedPackages(Iterable<String> packages) {
        builder.sharedPackages(packages);
        return this;
    }

    @Override
    public Set<String> getSharedPackages() {
        return builder.getSharedPackages();
    }

    @Override
    public JavaExecHandleBuilder getJavaCommand() {
        return builder.getJavaCommand();
    }

    @Override
    public LogLevel getLogLevel() {
        return builder.getLogLevel();
    }

    @Override
    public WorkerProcessSettings setLogLevel(LogLevel logLevel) {
        builder.setLogLevel(logLevel);
        return this;
    }

    @Override
    public PROTOCOL build() {
        return protocolType.cast(Proxy.newProxyInstance(protocolType.getClassLoader(), new Class[]{protocolType}, new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                Receiver receiver = new Receiver(getBaseName());
                try {
                    WorkerProcess workerProcess = builder.build();
                    workerProcess.start();
                    ObjectConnection connection = workerProcess.getConnection();
                    RequestProtocol requestProtocol = connection.addOutgoing(RequestProtocol.class);
                    connection.addIncoming(ResponseProtocol.class, receiver);
                    connection.useJavaSerializationForParameters(workerImplementation.getClassLoader());
                    connection.connect();
                    // TODO(ew): inject BuildOperationIdentifierRegistry instead of static use
                    requestProtocol.runThenStop(method.getName(), method.getParameterTypes(), args, BuildOperationIdentifierRegistry.getCurrentOperationIdentifier());
                    boolean hasResult = receiver.awaitNextResult();
                    workerProcess.waitForStop();
                    if (!hasResult) {
                        // Reached the end of input, worker has exited without failing
                        throw new IllegalStateException(String.format("No response was received from %s but the worker process has finished.", getBaseName()));
                    }
                } catch (Exception e) {
                    throw WorkerProcessException.runFailed(getBaseName(), e);
                }
                return receiver.getNextResult();
            }
        }));
    }

}
