/*
 * Copyright 2018 Confluent Inc.
 *
 * Licensed under the Confluent Community License (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.confluent.io/confluent-community-license
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OF ANY KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations under the License.
 */
package io.confluent.kafkarest.mock;

import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.Vector;

import io.confluent.kafkarest.Time;
import io.confluent.kafkarest.entities.ConsumerRecord;
import kafka.common.MessageStreamsExistException;
import kafka.common.OffsetAndMetadata;
import kafka.common.TopicAndPartition;
import kafka.consumer.KafkaStream;
import kafka.consumer.TopicFilter;
import kafka.javaapi.consumer.ConsumerConnector;
import kafka.javaapi.consumer.ConsumerRebalanceListener;
import kafka.serializer.Decoder;
import kafka.serializer.DefaultDecoder;
import kafka.utils.VerifiableProperties;

import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;

public class MockConsumerConnector implements ConsumerConnector {

  public String clientId;
  public Set<String> subscribedTopics = new HashSet<String>();

  private Time time;
  private Map<String, List<Map<Integer, List<ConsumerRecord<byte[], byte[]>>>>> streamDataSchedules;
  private static int consumerTimeoutMs;
  private boolean allowMissingSchedule;
  private boolean messageStreamCreated;
  private static Decoder<byte[]> decoder = new DefaultDecoder(null);

  /**
   * MockConsumerConnector lets you give a predetermined schedule for how when data should be
   * delivered to consumers.
   */
  public MockConsumerConnector(
      Time time, String clientId,
      Map<String, List<Map<Integer, List<ConsumerRecord<byte[], byte[]>>>>> streamDataSchedules,
      int consumerTimeoutMs,
      boolean allowMissingSchedule) {
    this.time = time;
    this.clientId = clientId;
    this.streamDataSchedules = streamDataSchedules;
    this.consumerTimeoutMs = consumerTimeoutMs;
    this.allowMissingSchedule = allowMissingSchedule;
    this.messageStreamCreated = false;
  }

  @Override
  public <K, V> Map<String, List<KafkaStream<K, V>>> createMessageStreams(
      Map<String, Integer> topicCountMap, Decoder<K> kDecoder, Decoder<V> vDecoder) {
    assert (kDecoder instanceof DefaultDecoder);
    assert (vDecoder instanceof DefaultDecoder);

    if (messageStreamCreated) {
      throw new MessageStreamsExistException("Subscribed twice", null);
    }

    Map<String, List<KafkaStream<K, V>>>
        result =
        new HashMap<String, List<KafkaStream<K, V>>>();

    for (Map.Entry<String, Integer> topicEntry : topicCountMap.entrySet()) {
      String topic = topicEntry.getKey();
      assertFalse(
          "MockConsumerConnector does not support multiple subscription requests to a topic",
          subscribedTopics.contains(topic));
      if (!allowMissingSchedule) {
        assertTrue(
            "MockConsumerConnector should have a predetermined schedule for requested streams",
            streamDataSchedules.containsKey(topic));
        assertTrue(
            "Calls to MockConsumerConnector.createMessageStreams should request the same number "
            + "of streams as provided to the constructor",
            streamDataSchedules.get(topic).size() == topicEntry.getValue());
      }
      if (streamDataSchedules.get(topic) != null) {
        List<KafkaStream<K, V>> streams = new Vector<KafkaStream<K, V>>();
        for (int i = 0; i < topicEntry.getValue(); i++) {
          streams.add(new KafkaStream<K, V>(
              new MockConsumerQueue(time, streamDataSchedules.get(topic).get(i)),
              consumerTimeoutMs, kDecoder, vDecoder, clientId));
        }
        subscribedTopics.add(topic);
        result.put(topic, streams);
      }
    }

    messageStreamCreated = true;
    return result;
  }

  @Override
  public Map<String, List<KafkaStream<byte[], byte[]>>> createMessageStreams(
      Map<String, Integer> topicCountMap) {
    return createMessageStreams(topicCountMap,
                                new DefaultDecoder(new VerifiableProperties()),
                                new DefaultDecoder(new VerifiableProperties()));
  }

  @Override
  public <K, V> List<KafkaStream<K, V>> createMessageStreamsByFilter(TopicFilter topicFilter, int i,
                                                                     Decoder<K> kDecoder,
                                                                     Decoder<V> vDecoder) {
    throw new UnsupportedOperationException();
  }

  @Override
  public List<KafkaStream<byte[], byte[]>> createMessageStreamsByFilter(TopicFilter topicFilter,
                                                                        int i) {
    throw new UnsupportedOperationException();
  }

  @Override
  public List<KafkaStream<byte[], byte[]>> createMessageStreamsByFilter(TopicFilter topicFilter) {
    throw new UnsupportedOperationException();
  }

  @Override
  public void commitOffsets() {
    return;
  }

  @Override
  public void commitOffsets(boolean b) {
    throw new UnsupportedOperationException();
  }

  @Override
  public void commitOffsets(Map<TopicAndPartition, OffsetAndMetadata> offsetsToCommit,
                            boolean retryOnFailure) {

  }

  @Override
  public void setConsumerRebalanceListener(ConsumerRebalanceListener listener) {

  }

  @Override
  public void shutdown() {

  }
}
