/*
 * Copyright (c) 2015-2017, Christoph Engelbert (aka noctarius) and
 * contributors. 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 com.noctarius.snowcast.impl;

import com.hazelcast.client.spi.ProxyManager;
import com.noctarius.snowcast.SnowcastEpoch;
import com.noctarius.snowcast.SnowcastSequenceState;
import com.noctarius.snowcast.SnowcastSequencer;

import javax.annotation.Nonnegative;
import javax.annotation.Nonnull;
import javax.validation.constraints.Max;
import javax.validation.constraints.Min;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;

import static com.noctarius.snowcast.impl.ExceptionMessages.ILLEGAL_SEQUENCER_TYPE;
import static com.noctarius.snowcast.impl.ExceptionUtils.exception;

class ClientSequencerService
        implements SequencerService {

    private static final Tracer TRACER = TracingUtils.tracer(ClientSequencerService.class);

    private final ClientSequencerConstructorFunction sequencerConstructor;

    private final ClientCodec clientCodec;

    private final ConcurrentMap<String, SequencerProvision> provisions;

    ClientSequencerService(@Nonnull ProxyManager proxyManager, @Nonnull ClientCodec clientCodec) {
        this.sequencerConstructor = new ClientSequencerConstructorFunction(proxyManager, this, clientCodec);
        this.provisions = new ConcurrentHashMap<>();
        this.clientCodec = clientCodec;
    }

    @Nonnull
    @Override
    public SnowcastSequencer createSequencer(@Nonnull String sequencerName, @Nonnull SnowcastEpoch epoch,
                                             @Min(128) @Max(8192) int maxLogicalNodeCount,
                                             @Nonnegative @Max(Short.MAX_VALUE) short backupCount) {

        TRACER.trace("register sequencer %s with epoch %s, max nodes %s, backups %s", //
                sequencerName, epoch, maxLogicalNodeCount, backupCount);

        SequencerDefinition definition = new SequencerDefinition(sequencerName, epoch, maxLogicalNodeCount, backupCount);

        try {
            SequencerDefinition realDefinition = clientCodec.createSequencerDefinition(sequencerName, definition);
            return getOrCreateSequencerProvision(realDefinition).getSequencer();
        } finally {
            TRACER.trace("register sequencer %s end", sequencerName);
        }
    }

    @Override
    public void destroySequencer(@Nonnull SnowcastSequencer sequencer) {
        if (!(sequencer instanceof ClientSequencer)) {
            throw exception(ILLEGAL_SEQUENCER_TYPE);
        }

        ((InternalSequencer) sequencer).stateTransition(SnowcastSequenceState.Destroyed);

        String sequencerName = sequencer.getSequencerName();
        TRACER.trace("destroy sequencer %s", sequencerName);

        try {
            clientCodec.destroySequencerDefinition(sequencerName);
        } finally {
            TRACER.trace("destroy sequencer %s end", sequencerName);
        }
    }

    @Nonnull
    private SequencerProvision getOrCreateSequencerProvision(@Nonnull SequencerDefinition definition) {
        String sequencerName = definition.getSequencerName();

        SequencerProvision provision = provisions.computeIfAbsent(sequencerName, name -> {
            TRACER.trace("return and cache new sequencer instance for %s", sequencerName);
            return sequencerConstructor.createNew(definition);
        });

        TRACER.trace("return existing sequencer instance for %s", sequencerName);
        return provision;
    }
}