/**
 * 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 org.apache.flume.sink;

import com.google.common.base.Preconditions;
import org.apache.flume.Channel;
import org.apache.flume.Context;
import org.apache.flume.CounterGroup;
import org.apache.flume.Event;
import org.apache.flume.EventDeliveryException;
import org.apache.flume.Sink;
import org.apache.flume.Transaction;
import org.apache.flume.conf.Configurable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * <p>
 * A {@link Sink} implementation that simply discards all events it receives. A
 * <tt>/dev/null</tt> for Flume.
 * </p>
 * <p>
 * <b>Configuration options</b>
 * </p>
 * <p>
 * <i>This sink has no configuration parameters.</i>
 * </p>
 * <p>
 * <b>Metrics</b>
 * </p>
 * <p>
 * TODO
 * </p>
 */
public class NullSink extends AbstractSink implements Configurable {

  private static final Logger logger = LoggerFactory.getLogger(NullSink.class);

  private static final int DFLT_BATCH_SIZE = 100;
  private static final int DFLT_LOG_EVERY_N_EVENTS = 10000;

  private CounterGroup counterGroup;
  private int batchSize = DFLT_BATCH_SIZE;
  private int logEveryNEvents = DFLT_LOG_EVERY_N_EVENTS;

  public NullSink() {
    counterGroup = new CounterGroup();
  }

  @Override
  public void configure(Context context) {
    batchSize = context.getInteger("batchSize", DFLT_BATCH_SIZE);
    logger.debug(this.getName() + " " +
        "batch size set to " + String.valueOf(batchSize));
    Preconditions.checkArgument(batchSize > 0, "Batch size must be > 0");

    logEveryNEvents = context.getInteger("logEveryNEvents", DFLT_LOG_EVERY_N_EVENTS);
    logger.debug(this.getName() + " " +
        "log event N events set to " + logEveryNEvents);
    Preconditions.checkArgument(logEveryNEvents > 0, "logEveryNEvents must be > 0");
  }

  @Override
  public Status process() throws EventDeliveryException {
    Status status = Status.READY;

    Channel channel = getChannel();
    Transaction transaction = channel.getTransaction();
    Event event = null;
    long eventCounter = counterGroup.get("events.success");

    try {
      transaction.begin();
      int i = 0;
      for (i = 0; i < batchSize; i++) {
        event = channel.take();
        if (++eventCounter % logEveryNEvents == 0) {
          logger.info("Null sink {} successful processed {} events.", getName(), eventCounter);
        }
        if (event == null) {
          status = Status.BACKOFF;
          break;
        }
      }
      transaction.commit();
      counterGroup.addAndGet("events.success", (long) Math.min(batchSize, i));
      counterGroup.incrementAndGet("transaction.success");
    } catch (Exception ex) {
      transaction.rollback();
      counterGroup.incrementAndGet("transaction.failed");
      logger.error("Failed to deliver event. Exception follows.", ex);
      throw new EventDeliveryException("Failed to deliver event: " + event, ex);
    } finally {
      transaction.close();
    }

    return status;
  }

  @Override
  public void start() {
    logger.info("Starting {}...", this);

    counterGroup.setName(this.getName());
    super.start();

    logger.info("Null sink {} started.", getName());
  }

  @Override
  public void stop() {
    logger.info("Null sink {} stopping...", getName());

    super.stop();

    logger.info("Null sink {} stopped. Event metrics: {}",
        getName(), counterGroup);
  }

  @Override
  public String toString() {
    return "NullSink " + getName() + " { batchSize: " + batchSize + " }";
  }

}
