/*
 * Copyright Terracotta, Inc.
 *
 * 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.ehcache.clustered.common.internal.messages;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.terracotta.entity.MessageCodec;
import org.terracotta.entity.MessageCodecException;
import org.terracotta.runnel.Struct;
import org.terracotta.runnel.decoding.Enm;

import java.nio.ByteBuffer;

import static java.nio.ByteBuffer.wrap;
import static org.ehcache.clustered.common.internal.messages.EhcacheMessageType.EHCACHE_MESSAGE_TYPES_ENUM_MAPPING;
import static org.ehcache.clustered.common.internal.messages.EhcacheMessageType.MESSAGE_TYPE_FIELD_INDEX;
import static org.ehcache.clustered.common.internal.messages.EhcacheMessageType.MESSAGE_TYPE_FIELD_NAME;
import static org.ehcache.clustered.common.internal.messages.EhcacheMessageType.isLifecycleMessage;
import static org.ehcache.clustered.common.internal.messages.EhcacheMessageType.isStateRepoOperationMessage;
import static org.ehcache.clustered.common.internal.messages.EhcacheMessageType.isStoreOperationMessage;
import static org.terracotta.runnel.StructBuilder.newStructBuilder;

public class EhcacheCodec implements MessageCodec<EhcacheEntityMessage, EhcacheEntityResponse> {

  private static final Logger LOGGER = LoggerFactory.getLogger(EhcacheCodec.class);

  public static final Struct OP_CODE_DECODER = newStructBuilder().enm(MESSAGE_TYPE_FIELD_NAME, MESSAGE_TYPE_FIELD_INDEX, EHCACHE_MESSAGE_TYPES_ENUM_MAPPING).build();

  private final ServerStoreOpCodec serverStoreOpCodec;
  private final LifeCycleMessageCodec lifeCycleMessageCodec;
  private final StateRepositoryOpCodec stateRepositoryOpCodec;
  private final ResponseCodec responseCodec;

  public EhcacheCodec(ServerStoreOpCodec serverStoreOpCodec, LifeCycleMessageCodec lifeCycleMessageCodec,
               StateRepositoryOpCodec stateRepositoryOpCodec, ResponseCodec responseCodec) {
    this.serverStoreOpCodec = serverStoreOpCodec;
    this.lifeCycleMessageCodec = lifeCycleMessageCodec;
    this.stateRepositoryOpCodec = stateRepositoryOpCodec;
    this.responseCodec = responseCodec;
  }

  @Override
  public byte[] encodeMessage(EhcacheEntityMessage message) {
    if (!(message instanceof EhcacheOperationMessage)) {
      throw new AssertionError("Unexpected message type " + message.getClass());
    }
    EhcacheOperationMessage operationMessage = (EhcacheOperationMessage) message;
    if (isLifecycleMessage(operationMessage.getMessageType())) {
      return lifeCycleMessageCodec.encode((LifecycleMessage) operationMessage);
    } else if (isStoreOperationMessage(operationMessage.getMessageType())) {
      return serverStoreOpCodec.encode((ServerStoreOpMessage) operationMessage);
    } else if (isStateRepoOperationMessage(operationMessage.getMessageType())) {
      return stateRepositoryOpCodec.encode((StateRepositoryOpMessage) operationMessage);
    }
    throw new AssertionError("Unknown message type: " + operationMessage.getMessageType());
  }

  @Override
  public EhcacheEntityMessage decodeMessage(byte[] payload) throws MessageCodecException {
    ByteBuffer byteBuffer = wrap(payload);
    Enm<EhcacheMessageType> opCodeEnm = OP_CODE_DECODER.decoder(byteBuffer).enm("opCode");

    if (!opCodeEnm.isFound()) {
      throw new AssertionError("Got a message without an opCode");
    }
    if (!opCodeEnm.isValid()) {
      LOGGER.warn("Received message with unknown operation code - more recent version at the other end?");
      return null;
    }

    byteBuffer.rewind();

    EhcacheMessageType opCode = opCodeEnm.get();
    return decodeMessage(byteBuffer, opCode);
  }

  public EhcacheEntityMessage decodeMessage(ByteBuffer byteBuffer, EhcacheMessageType opCode) {
    if (isLifecycleMessage(opCode)) {
        return lifeCycleMessageCodec.decode(opCode, byteBuffer);
    } else if (isStoreOperationMessage(opCode)) {
        return serverStoreOpCodec.decode(opCode, byteBuffer);
    } else if (isStateRepoOperationMessage(opCode)) {
        return stateRepositoryOpCodec.decode(opCode, byteBuffer);
    } else {
      throw new UnsupportedOperationException("Unsupported message code: " + opCode);
    }
  }

  @Override
  public byte[] encodeResponse(EhcacheEntityResponse response) throws MessageCodecException {
    return responseCodec.encode(response);
  }

  @Override
  public EhcacheEntityResponse decodeResponse(byte[] payload) throws MessageCodecException {
    return responseCodec.decode(payload);
  }
}
