/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you 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.datastax.driver.core;

import static com.datastax.driver.core.SchemaElement.AGGREGATE;
import static com.datastax.driver.core.SchemaElement.FUNCTION;
import static com.datastax.driver.core.SchemaElement.KEYSPACE;
import static com.datastax.driver.core.SchemaElement.TABLE;

import io.netty.buffer.ByteBuf;
import java.net.InetSocketAddress;
import java.util.Collections;
import java.util.List;

class ProtocolEvent {

  enum Type {
    TOPOLOGY_CHANGE,
    STATUS_CHANGE,
    SCHEMA_CHANGE
  }

  final Type type;

  private ProtocolEvent(Type type) {
    this.type = type;
  }

  static ProtocolEvent deserialize(ByteBuf bb, ProtocolVersion version) {
    switch (CBUtil.readEnumValue(Type.class, bb)) {
      case TOPOLOGY_CHANGE:
        return TopologyChange.deserializeEvent(bb);
      case STATUS_CHANGE:
        return StatusChange.deserializeEvent(bb);
      case SCHEMA_CHANGE:
        return SchemaChange.deserializeEvent(bb, version);
    }
    throw new AssertionError();
  }

  static class TopologyChange extends ProtocolEvent {
    enum Change {
      NEW_NODE,
      REMOVED_NODE,
      MOVED_NODE
    }

    final Change change;
    final InetSocketAddress node;

    private TopologyChange(Change change, InetSocketAddress node) {
      super(Type.TOPOLOGY_CHANGE);
      this.change = change;
      this.node = node;
    }

    // Assumes the type has already been deserialized
    private static TopologyChange deserializeEvent(ByteBuf bb) {
      Change change = CBUtil.readEnumValue(Change.class, bb);
      InetSocketAddress node = CBUtil.readInet(bb);
      return new TopologyChange(change, node);
    }

    @Override
    public String toString() {
      return change + " " + node;
    }
  }

  static class StatusChange extends ProtocolEvent {

    enum Status {
      UP,
      DOWN
    }

    final Status status;
    final InetSocketAddress node;

    private StatusChange(Status status, InetSocketAddress node) {
      super(Type.STATUS_CHANGE);
      this.status = status;
      this.node = node;
    }

    // Assumes the type has already been deserialized
    private static StatusChange deserializeEvent(ByteBuf bb) {
      Status status = CBUtil.readEnumValue(Status.class, bb);
      InetSocketAddress node = CBUtil.readInet(bb);
      return new StatusChange(status, node);
    }

    @Override
    public String toString() {
      return status + " " + node;
    }
  }

  static class SchemaChange extends ProtocolEvent {

    enum Change {
      CREATED,
      UPDATED,
      DROPPED
    }

    final Change change;
    final SchemaElement targetType;
    final String targetKeyspace;
    final String targetName;
    final List<String> targetSignature;

    SchemaChange(
        Change change,
        SchemaElement targetType,
        String targetKeyspace,
        String targetName,
        List<String> targetSignature) {
      super(Type.SCHEMA_CHANGE);
      this.change = change;
      this.targetType = targetType;
      this.targetKeyspace = targetKeyspace;
      this.targetName = targetName;
      this.targetSignature = targetSignature;
    }

    // Assumes the type has already been deserialized
    static SchemaChange deserializeEvent(ByteBuf bb, ProtocolVersion version) {
      Change change;
      SchemaElement targetType;
      String targetKeyspace, targetName;
      List<String> targetSignature;
      switch (version) {
        case V1:
        case V2:
          change = CBUtil.readEnumValue(Change.class, bb);
          targetKeyspace = CBUtil.readString(bb);
          targetName = CBUtil.readString(bb);
          targetType = targetName.isEmpty() ? KEYSPACE : TABLE;
          targetSignature = Collections.emptyList();
          return new SchemaChange(change, targetType, targetKeyspace, targetName, targetSignature);
        case V3:
        case V4:
        case V5:
        case V6:
          change = CBUtil.readEnumValue(Change.class, bb);
          targetType = CBUtil.readEnumValue(SchemaElement.class, bb);
          targetKeyspace = CBUtil.readString(bb);
          targetName = (targetType == KEYSPACE) ? "" : CBUtil.readString(bb);
          targetSignature =
              (targetType == FUNCTION || targetType == AGGREGATE)
                  ? CBUtil.readStringList(bb)
                  : Collections.<String>emptyList();
          return new SchemaChange(change, targetType, targetKeyspace, targetName, targetSignature);
        default:
          throw version.unsupported();
      }
    }

    @Override
    public String toString() {
      return change.toString()
          + ' '
          + targetType
          + ' '
          + targetKeyspace
          + (targetName.isEmpty() ? "" : '.' + targetName);
    }
  }
}
