/**
 * 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.hadoop.hive.ql.exec;

import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.Collections;

import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.hive.conf.HiveConf;
import org.apache.hadoop.hive.conf.HiveConf.ConfVars;
import org.apache.hadoop.hive.ql.CompilationOpContext;
import org.apache.hadoop.hive.ql.exec.tez.TezContext;
import org.apache.hadoop.hive.ql.metadata.HiveException;
import org.apache.hadoop.hive.ql.plan.AppMasterEventDesc;
import org.apache.hadoop.hive.ql.plan.api.OperatorType;
import org.apache.hadoop.hive.serde2.Serializer;
import org.apache.hadoop.hive.serde2.objectinspector.ObjectInspector;
import org.apache.hadoop.io.DataOutputBuffer;
import org.apache.hadoop.io.Writable;
import org.apache.hadoop.util.ReflectionUtils;
import org.apache.tez.runtime.api.Event;
import org.apache.tez.runtime.api.events.InputInitializerEvent;

/**
 * AppMasterEventOperator sends any rows it receives to the Tez AM. This can be
 * used to control execution dynamically.
 */
@SuppressWarnings({ "deprecation", "serial" })
public class AppMasterEventOperator extends Operator<AppMasterEventDesc> {

  protected transient Serializer serializer;
  protected transient DataOutputBuffer buffer;
  protected transient boolean hasReachedMaxSize = false;
  protected transient long MAX_SIZE;

  /** Kryo ctor. */
  protected AppMasterEventOperator() {
    super();
  }

  public AppMasterEventOperator(CompilationOpContext ctx) {
    super(ctx);
  }

  @Override
  public void initializeOp(Configuration hconf) throws HiveException {
    super.initializeOp(hconf);

    MAX_SIZE = HiveConf.getLongVar(hconf, ConfVars.TEZ_DYNAMIC_PARTITION_PRUNING_MAX_EVENT_SIZE);
    serializer =
        (Serializer) ReflectionUtils.newInstance(conf.getTable().getDeserializerClass(), null);
    initDataBuffer(false);
  }

  protected void initDataBuffer(boolean skipPruning) throws HiveException {
    buffer = new DataOutputBuffer();
    try {
      // add any other header info
      getConf().writeEventHeader(buffer);

      // write byte to say whether to skip pruning or not
      buffer.writeBoolean(skipPruning);
    } catch (IOException e) {
      throw new HiveException(e);
    }
  }

  @Override
  public void process(Object row, int tag) throws HiveException {
    if (hasReachedMaxSize) {
      return;
    }

    ObjectInspector rowInspector = inputObjInspectors[0];
    try {
      Writable writableRow = serializer.serialize(row, rowInspector);
      writableRow.write(buffer);
      if (buffer.getLength() > MAX_SIZE) {
	if (isLogInfoEnabled) {
	  LOG.info("Disabling AM events. Buffer size too large: " + buffer.getLength());
	}
        hasReachedMaxSize = true;
        buffer = null;
      }
    } catch (Exception e) {
      throw new HiveException(e);
    }

    if (isLogDebugEnabled) {
      LOG.debug("AppMasterEvent: " + row);
    }
    forward(row, rowInspector);
  }

  @Override
  public void closeOp(boolean abort) throws HiveException {
    if (!abort) {
      TezContext context = (TezContext) TezContext.get();

      String vertexName = getConf().getVertexName();
      String inputName = getConf().getInputName();

      byte[] payload = null;

      if (hasReachedMaxSize) {
        initDataBuffer(true);
      }

      payload = new byte[buffer.getLength()];
      System.arraycopy(buffer.getData(), 0, payload, 0, buffer.getLength());

      Event event =
          InputInitializerEvent.create(vertexName, inputName,
              ByteBuffer.wrap(payload, 0, payload.length));

      if (isLogInfoEnabled) {
	LOG.info("Sending Tez event to vertex = " + vertexName + ", input = " + inputName
	    + ". Payload size = " + payload.length);
      }

      context.getTezProcessorContext().sendEvents(Collections.singletonList(event));
    }
  }

  @Override
  public OperatorType getType() {
    return OperatorType.EVENT;
  }

  /**
   * @return the name of the operator
   */
  @Override
  public String getName() {
    return AppMasterEventOperator.getOperatorName();
  }

  static public String getOperatorName() {
    return "EVENT";
  }
}
