/**
 * 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.hdfs.protocol;

import java.io.EOFException;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;

import org.apache.hadoop.classification.InterfaceAudience;
import org.apache.hadoop.classification.InterfaceStability;
import org.apache.hadoop.hdfs.protocol.proto.HdfsProtos;
import org.apache.hadoop.hdfs.security.token.block.BlockTokenIdentifier;
import org.apache.hadoop.hdfs.util.ExactSizeInputStream;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.security.token.Token;

import com.google.common.collect.Lists;
import com.google.protobuf.ByteString;
import com.google.protobuf.CodedInputStream;

/**
 * Utilities for converting to and from protocol buffers used in the
 * HDFS wire protocol, as well as some generic utilities useful
 * for dealing with protocol buffers.
 */
@InterfaceAudience.Private
@InterfaceStability.Evolving
public abstract class HdfsProtoUtil {
  
  //// Block Token ////
  
  public static HdfsProtos.BlockTokenIdentifierProto toProto(Token<?> blockToken) {
    return HdfsProtos.BlockTokenIdentifierProto.newBuilder()
      .setIdentifier(ByteString.copyFrom(blockToken.getIdentifier()))
      .setPassword(ByteString.copyFrom(blockToken.getPassword()))
      .setKind(blockToken.getKind().toString())
      .setService(blockToken.getService().toString())
      .build();
  }

  public static Token<BlockTokenIdentifier> fromProto(HdfsProtos.BlockTokenIdentifierProto proto) {
    return new Token<BlockTokenIdentifier>(proto.getIdentifier().toByteArray(),
        proto.getPassword().toByteArray(),
        new Text(proto.getKind()),
        new Text(proto.getService()));
  }

  //// Extended Block ////
  
  public static HdfsProtos.ExtendedBlockProto toProto(ExtendedBlock block) {
    return HdfsProtos.ExtendedBlockProto.newBuilder()
      .setBlockId(block.getBlockId())
      .setPoolId(block.getBlockPoolId())
      .setNumBytes(block.getNumBytes())
      .setGenerationStamp(block.getGenerationStamp())
      .build();
  }
    
  public static ExtendedBlock fromProto(HdfsProtos.ExtendedBlockProto proto) {
    return new ExtendedBlock(
        proto.getPoolId(), proto.getBlockId(),
        proto.getNumBytes(), proto.getGenerationStamp());
  }

  //// DatanodeID ////
  
  private static HdfsProtos.DatanodeIDProto toProto(
      DatanodeID dni) {
    return HdfsProtos.DatanodeIDProto.newBuilder()
      .setName(dni.getName())
      .setStorageID(dni.getStorageID())
      .setInfoPort(dni.getInfoPort())
      .build();
  }
  
  private static DatanodeID fromProto(HdfsProtos.DatanodeIDProto idProto) {
    return new DatanodeID(
        idProto.getName(),
        idProto.getStorageID(),
        idProto.getInfoPort(),
        -1); // ipc port not serialized in writables either
  }
  
  //// DatanodeInfo ////
  
  public static HdfsProtos.DatanodeInfoProto toProto(DatanodeInfo dni) {
    return HdfsProtos.DatanodeInfoProto.newBuilder()
      .setId(toProto((DatanodeID)dni))
      .setCapacity(dni.getCapacity())
      .setDfsUsed(dni.getDfsUsed())
      .setRemaining(dni.getRemaining())
      .setBlockPoolUsed(dni.getBlockPoolUsed())
      .setLastUpdate(dni.getLastUpdate())
      .setXceiverCount(dni.getXceiverCount())
      .setLocation(dni.getNetworkLocation())
      .setHostName(dni.getHostName())
      .setAdminState(HdfsProtos.DatanodeInfoProto.AdminState.valueOf(
          dni.getAdminState().name()))
      .build();
  }

  public static DatanodeInfo fromProto(HdfsProtos.DatanodeInfoProto dniProto) {
    DatanodeInfo dniObj = new DatanodeInfo(fromProto(dniProto.getId()),
        dniProto.getLocation(), dniProto.getHostName());

    dniObj.setCapacity(dniProto.getCapacity());
    dniObj.setDfsUsed(dniProto.getDfsUsed());
    dniObj.setRemaining(dniProto.getRemaining());
    dniObj.setBlockPoolUsed(dniProto.getBlockPoolUsed());
    dniObj.setLastUpdate(dniProto.getLastUpdate());
    dniObj.setXceiverCount(dniProto.getXceiverCount());
    dniObj.setAdminState(DatanodeInfo.AdminStates.valueOf(
        dniProto.getAdminState().name()));
    return dniObj;
  }
  
  public static ArrayList<? extends HdfsProtos.DatanodeInfoProto> toProtos(
      DatanodeInfo[] dnInfos, int startIdx) {
    ArrayList<HdfsProtos.DatanodeInfoProto> protos =
      Lists.newArrayListWithCapacity(dnInfos.length);
    for (int i = startIdx; i < dnInfos.length; i++) {
      protos.add(toProto(dnInfos[i]));
    }
    return protos;
  }
  
  public static DatanodeInfo[] fromProtos(
      List<HdfsProtos.DatanodeInfoProto> targetsList) {
    DatanodeInfo[] ret = new DatanodeInfo[targetsList.size()];
    int i = 0;
    for (HdfsProtos.DatanodeInfoProto proto : targetsList) {
      ret[i++] = fromProto(proto);
    }
    return ret;
  }

  public static InputStream vintPrefixed(final InputStream input)
  throws IOException {
    final int firstByte = input.read();
    if (firstByte == -1) {
      throw new EOFException("Premature EOF: no length prefix available");
    }
    
    int size = CodedInputStream.readRawVarint32(firstByte, input);
    assert size >= 0;
  
    return new ExactSizeInputStream(input, size);
  }
}