
/*
 * Copyright Contributors to the OpenCue Project
 *
 * 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 com.imageworks.spcue.servant;

import java.util.HashSet;

import com.google.protobuf.Descriptors;
import io.grpc.Status;
import io.grpc.stub.StreamObserver;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.env.Environment;
import org.springframework.dao.EmptyResultDataAccessException;

import com.imageworks.spcue.LayerDetail;
import com.imageworks.spcue.LocalHostAssignment;
import com.imageworks.spcue.Source;
import com.imageworks.spcue.dao.LayerDao;
import com.imageworks.spcue.dao.criteria.FrameSearchFactory;
import com.imageworks.spcue.dao.criteria.FrameSearchInterface;
import com.imageworks.spcue.depend.FrameByFrame;
import com.imageworks.spcue.depend.LayerOnFrame;
import com.imageworks.spcue.depend.LayerOnJob;
import com.imageworks.spcue.depend.LayerOnLayer;
import com.imageworks.spcue.dispatcher.DispatchQueue;
import com.imageworks.spcue.dispatcher.commands.DispatchDropDepends;
import com.imageworks.spcue.dispatcher.commands.DispatchEatFrames;
import com.imageworks.spcue.dispatcher.commands.DispatchKillFrames;
import com.imageworks.spcue.dispatcher.commands.DispatchReorderFrames;
import com.imageworks.spcue.dispatcher.commands.DispatchRetryFrames;
import com.imageworks.spcue.dispatcher.commands.DispatchSatisfyDepends;
import com.imageworks.spcue.dispatcher.commands.DispatchStaggerFrames;
import com.imageworks.spcue.grpc.job.FrameSearchCriteria;
import com.imageworks.spcue.grpc.job.FrameSeq;
import com.imageworks.spcue.grpc.job.Layer;
import com.imageworks.spcue.grpc.job.LayerAddLimitRequest;
import com.imageworks.spcue.grpc.job.LayerAddLimitResponse;
import com.imageworks.spcue.grpc.job.LayerAddRenderPartitionRequest;
import com.imageworks.spcue.grpc.job.LayerAddRenderPartitionResponse;
import com.imageworks.spcue.grpc.job.LayerCreateDependOnFrameRequest;
import com.imageworks.spcue.grpc.job.LayerCreateDependOnFrameResponse;
import com.imageworks.spcue.grpc.job.LayerCreateDependOnJobRequest;
import com.imageworks.spcue.grpc.job.LayerCreateDependOnJobResponse;
import com.imageworks.spcue.grpc.job.LayerCreateDependOnLayerRequest;
import com.imageworks.spcue.grpc.job.LayerCreateDependOnLayerResponse;
import com.imageworks.spcue.grpc.job.LayerCreateFrameByFrameDependRequest;
import com.imageworks.spcue.grpc.job.LayerCreateFrameByFrameDependResponse;
import com.imageworks.spcue.grpc.job.LayerDropDependsRequest;
import com.imageworks.spcue.grpc.job.LayerDropDependsResponse;
import com.imageworks.spcue.grpc.job.LayerDropLimitRequest;
import com.imageworks.spcue.grpc.job.LayerDropLimitResponse;
import com.imageworks.spcue.grpc.job.LayerEatFramesRequest;
import com.imageworks.spcue.grpc.job.LayerEatFramesResponse;
import com.imageworks.spcue.grpc.job.LayerEnableMemoryOptimizerRequest;
import com.imageworks.spcue.grpc.job.LayerEnableMemoryOptimizerResponse;
import com.imageworks.spcue.grpc.job.LayerFindLayerRequest;
import com.imageworks.spcue.grpc.job.LayerFindLayerResponse;
import com.imageworks.spcue.grpc.job.LayerGetFramesRequest;
import com.imageworks.spcue.grpc.job.LayerGetFramesResponse;
import com.imageworks.spcue.grpc.job.LayerGetLayerRequest;
import com.imageworks.spcue.grpc.job.LayerGetLayerResponse;
import com.imageworks.spcue.grpc.job.LayerGetLimitsRequest;
import com.imageworks.spcue.grpc.job.LayerGetLimitsResponse;
import com.imageworks.spcue.grpc.job.LayerGetOutputPathsRequest;
import com.imageworks.spcue.grpc.job.LayerGetOutputPathsResponse;
import com.imageworks.spcue.grpc.job.LayerGetWhatDependsOnThisRequest;
import com.imageworks.spcue.grpc.job.LayerGetWhatDependsOnThisResponse;
import com.imageworks.spcue.grpc.job.LayerGetWhatThisDependsOnRequest;
import com.imageworks.spcue.grpc.job.LayerGetWhatThisDependsOnResponse;
import com.imageworks.spcue.grpc.job.LayerInterfaceGrpc;
import com.imageworks.spcue.grpc.job.LayerKillFramesRequest;
import com.imageworks.spcue.grpc.job.LayerKillFramesResponse;
import com.imageworks.spcue.grpc.job.LayerMarkdoneFramesRequest;
import com.imageworks.spcue.grpc.job.LayerMarkdoneFramesResponse;
import com.imageworks.spcue.grpc.job.LayerRegisterOutputPathRequest;
import com.imageworks.spcue.grpc.job.LayerRegisterOutputPathResponse;
import com.imageworks.spcue.grpc.job.LayerReorderFramesRequest;
import com.imageworks.spcue.grpc.job.LayerReorderFramesResponse;
import com.imageworks.spcue.grpc.job.LayerRetryFramesRequest;
import com.imageworks.spcue.grpc.job.LayerRetryFramesResponse;
import com.imageworks.spcue.grpc.job.LayerSetMaxCoresRequest;
import com.imageworks.spcue.grpc.job.LayerSetMaxCoresResponse;
import com.imageworks.spcue.grpc.job.LayerSetMinCoresRequest;
import com.imageworks.spcue.grpc.job.LayerSetMinCoresResponse;
import com.imageworks.spcue.grpc.job.LayerSetMaxGpusRequest;
import com.imageworks.spcue.grpc.job.LayerSetMaxGpusResponse;
import com.imageworks.spcue.grpc.job.LayerSetMinGpusRequest;
import com.imageworks.spcue.grpc.job.LayerSetMinGpusResponse;
import com.imageworks.spcue.grpc.job.LayerSetMinGpuMemoryRequest;
import com.imageworks.spcue.grpc.job.LayerSetMinGpuMemoryResponse;
import com.imageworks.spcue.grpc.job.LayerSetMinMemoryRequest;
import com.imageworks.spcue.grpc.job.LayerSetMinMemoryResponse;
import com.imageworks.spcue.grpc.job.LayerSetTagsRequest;
import com.imageworks.spcue.grpc.job.LayerSetTagsResponse;
import com.imageworks.spcue.grpc.job.LayerSetThreadableRequest;
import com.imageworks.spcue.grpc.job.LayerSetThreadableResponse;
import com.imageworks.spcue.grpc.job.LayerSetTimeoutRequest;
import com.imageworks.spcue.grpc.job.LayerSetTimeoutResponse;
import com.imageworks.spcue.grpc.job.LayerSetTimeoutLLURequest;
import com.imageworks.spcue.grpc.job.LayerSetTimeoutLLUResponse;
import com.imageworks.spcue.grpc.job.LayerStaggerFramesRequest;
import com.imageworks.spcue.grpc.job.LayerStaggerFramesResponse;
import com.imageworks.spcue.grpc.limit.Limit;
import com.imageworks.spcue.grpc.renderpartition.RenderPartition;
import com.imageworks.spcue.grpc.renderpartition.RenderPartitionType;
import com.imageworks.spcue.service.DependManager;
import com.imageworks.spcue.service.JobManager;
import com.imageworks.spcue.service.JobManagerSupport;
import com.imageworks.spcue.service.LocalBookingSupport;
import com.imageworks.spcue.service.Whiteboard;
import com.imageworks.spcue.util.Convert;
import com.imageworks.spcue.util.FrameSet;

import static com.imageworks.spcue.servant.ServantUtil.attemptChange;

public class ManageLayer extends LayerInterfaceGrpc.LayerInterfaceImplBase {

    private LayerDetail layer;
    private FrameSearchInterface frameSearch;
    private JobManager jobManager;
    private DependManager dependManager;
    private JobManagerSupport jobManagerSupport;
    private LayerDao layerDao;
    private DispatchQueue manageQueue;
    private Whiteboard whiteboard;
    private LocalBookingSupport localBookingSupport;
    private FrameSearchFactory frameSearchFactory;
    private final String property = "layer.finished_jobs_readonly";
    @Autowired
    private Environment env;

    @Override
    public void findLayer(LayerFindLayerRequest request, StreamObserver<LayerFindLayerResponse> responseObserver) {
        try {
            responseObserver.onNext(LayerFindLayerResponse.newBuilder()
                    .setLayer(whiteboard.findLayer(request.getJob(), request.getLayer()))
                    .build());
            responseObserver.onCompleted();
        } catch (EmptyResultDataAccessException e) {
            responseObserver.onError(Status.NOT_FOUND
                    .withDescription(e.getMessage())
                    .withCause(e)
                    .asRuntimeException());
        }
    }

    @Override
    public void getLayer(LayerGetLayerRequest request, StreamObserver<LayerGetLayerResponse> responseObserver) {
        try {
            responseObserver.onNext(LayerGetLayerResponse.newBuilder()
                    .setLayer(whiteboard.getLayer(request.getId()))
                    .build());
            responseObserver.onCompleted();
        } catch (EmptyResultDataAccessException e) {
            responseObserver.onError(Status.NOT_FOUND
                    .withDescription(e.getMessage())
                    .withCause(e)
                    .asRuntimeException());
        }
    }

    @Override
    public void eatFrames(LayerEatFramesRequest request, StreamObserver<LayerEatFramesResponse> responseObserver) {
        updateLayer(request.getLayer());
        if (attemptChange(env, property, jobManager, layer, responseObserver)) {
            manageQueue.execute(new DispatchEatFrames(frameSearch,
                    new Source(request.toString()), jobManagerSupport));
            responseObserver.onNext(LayerEatFramesResponse.newBuilder().build());
            responseObserver.onCompleted();
        }
    }

    @Override
    public void getFrames(LayerGetFramesRequest request, StreamObserver<LayerGetFramesResponse> responseObserver) {
        updateLayer(request.getLayer());
        FrameSeq frames = whiteboard.getFrames(frameSearchFactory.create(layer, request.getS()));
        responseObserver.onNext(LayerGetFramesResponse.newBuilder()
                .setFrames(frames)
                .build());
        responseObserver.onCompleted();
    }

    @Override
    public void killFrames(LayerKillFramesRequest request, StreamObserver<LayerKillFramesResponse> responseObserver) {
        updateLayer(request.getLayer());
        if (attemptChange(env, property, jobManager, layer, responseObserver)) {
            manageQueue.execute(new DispatchKillFrames(frameSearch,
                    new Source(request.toString()), jobManagerSupport));
            responseObserver.onNext(LayerKillFramesResponse.newBuilder().build());
            responseObserver.onCompleted();
        }
    }

    @Override
    public void markdoneFrames(LayerMarkdoneFramesRequest request,
                               StreamObserver<LayerMarkdoneFramesResponse> responseObserver) {
        updateLayer(request.getLayer());
        if (attemptChange(env, property, jobManager, layer, responseObserver)) {
            manageQueue.execute(new DispatchSatisfyDepends(layer, jobManagerSupport));
            responseObserver.onNext(LayerMarkdoneFramesResponse.newBuilder().build());
            responseObserver.onCompleted();
        }
    }

    @Override
    public void retryFrames(LayerRetryFramesRequest request,
                            StreamObserver<LayerRetryFramesResponse> responseObserver) {
        updateLayer(request.getLayer());
        if (attemptChange(env, property, jobManager, layer, responseObserver)) {
            manageQueue.execute(new DispatchRetryFrames(frameSearch,
                    new Source(request.toString()), jobManagerSupport));
            responseObserver.onNext(LayerRetryFramesResponse.newBuilder().build());
            responseObserver.onCompleted();
        }
    }

    @Override
    public void setTags(LayerSetTagsRequest request, StreamObserver<LayerSetTagsResponse> responseObserver) {
        updateLayer(request.getLayer());
        if (attemptChange(env, property, jobManager, layer, responseObserver)) {
            layerDao.updateLayerTags(layer, new HashSet<>(request.getTagsList()));
            responseObserver.onNext(LayerSetTagsResponse.newBuilder().build());
            responseObserver.onCompleted();
        }
    }

    @Override
    public void setMinCores(LayerSetMinCoresRequest request, StreamObserver<LayerSetMinCoresResponse> responseObserver) {
        updateLayer(request.getLayer());
        if (attemptChange(env, property, jobManager, layer, responseObserver)) {
            jobManager.setLayerMinCores(layer, Convert.coresToCoreUnits(request.getCores()));
            responseObserver.onNext(LayerSetMinCoresResponse.newBuilder().build());
            responseObserver.onCompleted();
        }
    }

    @Override
    public void setMinGpus(LayerSetMinGpusRequest request, StreamObserver<LayerSetMinGpusResponse> responseObserver) {
        updateLayer(request.getLayer());
        if (attemptChange(env, property, jobManager, layer, responseObserver)) {
            jobManager.setLayerMinGpus(layer, request.getMinGpus());
            responseObserver.onNext(LayerSetMinGpusResponse.newBuilder().build());
            responseObserver.onCompleted();
        }
    }

    @Override
    public void setMinMemory(LayerSetMinMemoryRequest request, StreamObserver<LayerSetMinMemoryResponse> responseObserver) {
        updateLayer(request.getLayer());
        if (attemptChange(env, property, jobManager, layer, responseObserver)) {
            layerDao.updateLayerMinMemory(layer, request.getMemory());
            responseObserver.onNext(LayerSetMinMemoryResponse.newBuilder().build());
            responseObserver.onCompleted();
        }
    }

    @Override
    public void setMinGpuMemory(LayerSetMinGpuMemoryRequest request,
                                StreamObserver<LayerSetMinGpuMemoryResponse> responseObserver) {
        updateLayer(request.getLayer());
        if (attemptChange(env, property, jobManager, layer, responseObserver)) {
            layerDao.updateLayerMinGpuMemory(layer, request.getGpuMemory());
            responseObserver.onNext(LayerSetMinGpuMemoryResponse.newBuilder().build());
            responseObserver.onCompleted();
        }
    }

    @Override
    public void createDependencyOnFrame(LayerCreateDependOnFrameRequest request,
                                        StreamObserver<LayerCreateDependOnFrameResponse> responseObserver) {
        updateLayer(request.getLayer());
        if (attemptChange(env, property, jobManager, layer, responseObserver)) {
            LayerOnFrame depend = new LayerOnFrame(layer, jobManager.getFrameDetail(request.getFrame().getId()));
            dependManager.createDepend(depend);
            responseObserver.onNext(LayerCreateDependOnFrameResponse.newBuilder()
                    .setDepend(whiteboard.getDepend(depend))
                    .build());
            responseObserver.onCompleted();
        }
    }

    @Override
    public void createDependencyOnJob(LayerCreateDependOnJobRequest request,
                                      StreamObserver<LayerCreateDependOnJobResponse> responseObserver) {
        updateLayer(request.getLayer());
        if (attemptChange(env, property, jobManager, layer, responseObserver)) {
            LayerOnJob depend = new LayerOnJob(layer, jobManager.getJobDetail(request.getJob().getId()));
            dependManager.createDepend(depend);
            responseObserver.onNext(LayerCreateDependOnJobResponse.newBuilder()
                    .setDepend(whiteboard.getDepend(depend))
                    .build());
            responseObserver.onCompleted();
        }
    }

    @Override
    public void createDependencyOnLayer(LayerCreateDependOnLayerRequest request,
                                        StreamObserver<LayerCreateDependOnLayerResponse> responseObserver) {
        updateLayer(request.getLayer());
        if (attemptChange(env, property, jobManager, layer, responseObserver)) {
            LayerOnLayer depend = new LayerOnLayer(layer, jobManager.getLayerDetail(request.getDependOnLayer().getId()));
            dependManager.createDepend(depend);
            responseObserver.onNext(LayerCreateDependOnLayerResponse.newBuilder()
                    .setDepend(whiteboard.getDepend(depend))
                    .build());
            responseObserver.onCompleted();
        }
    }

    @Override
    public void createFrameByFrameDependency(LayerCreateFrameByFrameDependRequest request,
                                             StreamObserver<LayerCreateFrameByFrameDependResponse> responseObserver) {
        updateLayer(request.getLayer());
        if (attemptChange(env, property, jobManager, layer, responseObserver)) {
            FrameByFrame depend = new FrameByFrame(layer, jobManager.getLayerDetail(request.getDependLayer().getId()));
            dependManager.createDepend(depend);
            responseObserver.onNext(LayerCreateFrameByFrameDependResponse.newBuilder()
                    .setDepend(whiteboard.getDepend(depend))
                    .build());
            responseObserver.onCompleted();
        }
    }

    @Override
    public void getWhatDependsOnThis(LayerGetWhatDependsOnThisRequest request,
                                     StreamObserver<LayerGetWhatDependsOnThisResponse> responseObserver) {
        updateLayer(request.getLayer());
        responseObserver.onNext(LayerGetWhatDependsOnThisResponse.newBuilder()
                .setDepends(whiteboard.getWhatDependsOnThis(layer))
                .build());
        responseObserver.onCompleted();
    }

    @Override
    public void getWhatThisDependsOn(LayerGetWhatThisDependsOnRequest request,
                                     StreamObserver<LayerGetWhatThisDependsOnResponse> responseObserver) {
        updateLayer(request.getLayer());
        responseObserver.onNext(LayerGetWhatThisDependsOnResponse.newBuilder()
                .setDepends(whiteboard.getWhatThisDependsOn(layer))
                .build());
        responseObserver.onCompleted();
    }

    @Override
    public void dropDepends(LayerDropDependsRequest request,
                            StreamObserver<LayerDropDependsResponse> responseObserver) {
        updateLayer(request.getLayer());
        if (attemptChange(env, property, jobManager, layer, responseObserver)) {
            manageQueue.execute(new DispatchDropDepends(layer, request.getTarget(), dependManager));
            responseObserver.onNext(LayerDropDependsResponse.newBuilder().build());
            responseObserver.onCompleted();
        }
    }

    @Override
    public void dropLimit(LayerDropLimitRequest request,
                         StreamObserver<LayerDropLimitResponse> responseObserver) {
        updateLayer(request.getLayer());
        if (attemptChange(env, property, jobManager, layer, responseObserver)) {
            layerDao.dropLimit(layer, request.getLimitId());
            responseObserver.onNext(LayerDropLimitResponse.newBuilder().build());
            responseObserver.onCompleted();
        }
    }

    @Override
    public void reorderFrames(LayerReorderFramesRequest request,
                              StreamObserver<LayerReorderFramesResponse> responseObserver) {
        updateLayer(request.getLayer());
        if (attemptChange(env, property, jobManager, layer, responseObserver)) {
            manageQueue.execute(new DispatchReorderFrames(layer, new FrameSet(request.getRange()), request.getOrder(),
                    jobManagerSupport));
            responseObserver.onNext(LayerReorderFramesResponse.newBuilder().build());
            responseObserver.onCompleted();
        }
    }

    @Override
    public void staggerFrames(LayerStaggerFramesRequest request,
                              StreamObserver<LayerStaggerFramesResponse> responseObserver) {
        updateLayer(request.getLayer());
        if (attemptChange(env, property, jobManager, layer, responseObserver)) {
            manageQueue.execute(new DispatchStaggerFrames(layer, request.getRange(), request.getStagger(),
                    jobManagerSupport));
            responseObserver.onNext(LayerStaggerFramesResponse.newBuilder().build());
            responseObserver.onCompleted();
        }
    }

    @Override
    public void setThreadable(LayerSetThreadableRequest request, StreamObserver<LayerSetThreadableResponse> responseObserver) {
        updateLayer(request.getLayer());
        if (attemptChange(env, property, jobManager, layer, responseObserver)) {
            layerDao.updateThreadable(layer, request.getThreadable());
            responseObserver.onNext(LayerSetThreadableResponse.newBuilder().build());
            responseObserver.onCompleted();
        }
    }

    @Override
    public void setTimeout(LayerSetTimeoutRequest request, StreamObserver<LayerSetTimeoutResponse> responseObserver) {
        updateLayer(request.getLayer());
        if (attemptChange(env, property, jobManager, layer, responseObserver)) {
            layerDao.updateTimeout(layer, request.getTimeout());
            responseObserver.onNext(LayerSetTimeoutResponse.newBuilder().build());
            responseObserver.onCompleted();
        }
    }

    @Override
    public void setTimeoutLLU(LayerSetTimeoutLLURequest request, StreamObserver<LayerSetTimeoutLLUResponse> responseObserver) {
        updateLayer(request.getLayer());
        if (attemptChange(env, property, jobManager, layer, responseObserver)) {
            layerDao.updateTimeoutLLU(layer, request.getTimeoutLlu());
            responseObserver.onNext(LayerSetTimeoutLLUResponse.newBuilder().build());
            responseObserver.onCompleted();
        }
    }

    @Override
    public void addLimit(LayerAddLimitRequest request,
                         StreamObserver<LayerAddLimitResponse> responseObserver) {
        updateLayer(request.getLayer());
        if (attemptChange(env, property, jobManager, layer, responseObserver)) {
            layerDao.addLimit(layer, request.getLimitId());
            responseObserver.onNext(LayerAddLimitResponse.newBuilder().build());
            responseObserver.onCompleted();
        }
    }

    @Override
    public void addRenderPartition(LayerAddRenderPartitionRequest request,
                                   StreamObserver<LayerAddRenderPartitionResponse> responseObserver) {
        updateLayer(request.getLayer());
        if (attemptChange(env, property, jobManager, layer, responseObserver)) {
            LocalHostAssignment lha = new LocalHostAssignment();
            lha.setThreads(request.getThreads());
            lha.setMaxCoreUnits(request.getMaxCores() * 100);
            lha.setMaxMemory(request.getMaxMemory());
            lha.setMaxGpuUnits(request.getMaxGpus());
            lha.setMaxGpuMemory(request.getMaxGpuMemory());
            lha.setType(RenderPartitionType.LAYER_PARTITION);
            if (localBookingSupport.bookLocal(layer, request.getHost(), request.getUsername(), lha)) {
                RenderPartition partition = whiteboard.getRenderPartition(lha);
                responseObserver.onNext(LayerAddRenderPartitionResponse.newBuilder()
                        .setRenderPartition(partition)
                        .build());
                responseObserver.onCompleted();
            } else {
                responseObserver.onError(Status.INTERNAL
                        .withDescription("Failed to find suitable frames.")
                        .asRuntimeException());
            }
        }

    }

    @Override
    public void registerOutputPath(LayerRegisterOutputPathRequest request,
                                   StreamObserver<LayerRegisterOutputPathResponse> responseObserver) {
        updateLayer(request.getLayer());
        if (attemptChange(env, property, jobManager, layer, responseObserver)) {
            jobManager.registerLayerOutput(layer, request.getSpec());
            responseObserver.onNext(LayerRegisterOutputPathResponse.newBuilder().build());
            responseObserver.onCompleted();
        }
    }

    @Override
    public void getLimits(LayerGetLimitsRequest request,
                          StreamObserver<LayerGetLimitsResponse> responseObserver) {
        updateLayer(request.getLayer());
        responseObserver.onNext(LayerGetLimitsResponse.newBuilder()
                .addAllLimits(whiteboard.getLimits(layer))
                .build());
        responseObserver.onCompleted();
    }

    @Override
    public void getOutputPaths(LayerGetOutputPathsRequest request, StreamObserver<LayerGetOutputPathsResponse> responseObserver) {
        updateLayer(request.getLayer());
        responseObserver.onNext(LayerGetOutputPathsResponse.newBuilder()
                .addAllOutputPaths(jobManager.getLayerOutputs(layer))
                .build());
        responseObserver.onCompleted();
    }

    @Override
    public void enableMemoryOptimizer(LayerEnableMemoryOptimizerRequest request,
                                      StreamObserver<LayerEnableMemoryOptimizerResponse> responseObserver) {
        updateLayer(request.getLayer());
        if (attemptChange(env, property, jobManager, layer, responseObserver)) {
            jobManager.enableMemoryOptimizer(layer, request.getValue());
            responseObserver.onNext(LayerEnableMemoryOptimizerResponse.newBuilder().build());
            responseObserver.onCompleted();
        }
    }

    @Override
    public void setMaxCores(LayerSetMaxCoresRequest request, StreamObserver<LayerSetMaxCoresResponse> responseObserver) {
        updateLayer(request.getLayer());
        if (attemptChange(env, property, jobManager, layer, responseObserver)) {
            jobManager.setLayerMaxCores(layer, Convert.coresToWholeCoreUnits(request.getCores()));
            responseObserver.onNext(LayerSetMaxCoresResponse.newBuilder().build());
            responseObserver.onCompleted();
        }
    }

    @Override
    public void setMaxGpus(LayerSetMaxGpusRequest request, StreamObserver<LayerSetMaxGpusResponse> responseObserver) {
        updateLayer(request.getLayer());
        jobManager.setLayerMaxGpus(layer, request.getMaxGpus());
        responseObserver.onNext(LayerSetMaxGpusResponse.newBuilder().build());
        responseObserver.onCompleted();
    }

    public DependManager getDependManager() {
        return dependManager;
    }

    public void setDependManager(DependManager dependManager) {
        this.dependManager = dependManager;
    }

    public DispatchQueue getManageQueue() {
        return manageQueue;
    }

    public void setManageQueue(DispatchQueue dispatchQueue) {
        this.manageQueue = dispatchQueue;
    }

    public JobManager getJobManager() {
        return jobManager;
    }

    public void setJobManager(JobManager jobManager) {
        this.jobManager = jobManager;
    }

    public LayerDetail getLayer() {
        return layer;
    }

    public void setLayer(LayerDetail layer) {
        this.layer = layer;
    }

    public Whiteboard getWhiteboard() {
        return whiteboard;
    }

    public void setWhiteboard(Whiteboard whiteboard) {
        this.whiteboard = whiteboard;
    }

    public LayerDao getLayerDao() {
        return layerDao;
    }

    public void setLayerDao(LayerDao layerDao) {
        this.layerDao = layerDao;
    }

    public JobManagerSupport getJobManagerSupport() {
        return jobManagerSupport;
    }

    public void setJobManagerSupport(JobManagerSupport jobManagerSupport) {
        this.jobManagerSupport = jobManagerSupport;
    }

    public LocalBookingSupport getLocalBookingSupport() {
        return localBookingSupport;
    }

    public void setLocalBookingSupport(LocalBookingSupport localBookingSupport) {
        this.localBookingSupport = localBookingSupport;
    }

    public FrameSearchFactory getFrameSearchFactory() {
        return frameSearchFactory;
    }

    public void setFrameSearchFactory(FrameSearchFactory frameSearchFactory) {
        this.frameSearchFactory = frameSearchFactory;
    }

    private void updateLayer(Layer layerData) {
        setJobManager(jobManagerSupport.getJobManager());
        setDependManager(jobManagerSupport.getDependManager());
        layer = layerDao.getLayerDetail(layerData.getId());
        frameSearch = frameSearchFactory.create(layer);
    }
}

