/**
 * 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.hbase.types;

import org.apache.hadoop.hbase.util.Order;
import org.apache.hadoop.hbase.util.PositionedByteRange;
import org.apache.hadoop.hbase.util.SimplePositionedMutableByteRange;
import org.apache.yetus.audience.InterfaceAudience;

/**
 * Wraps an existing {@link DataType} implementation as a fixed-length
 * version of itself. This has the useful side-effect of turning an existing
 * {@link DataType} which is not {@code skippable} into a {@code skippable}
 * variant.
 */
@InterfaceAudience.Public
public class FixedLengthWrapper<T> implements DataType<T> {

  protected final DataType<T> base;
  protected final int length;

  /**
   * Create a fixed-length version of the {@code wrapped}.
   * @param base the {@link DataType} to restrict to a fixed length.
   * @param length the maximum length (in bytes) for encoded values.
   */
  public FixedLengthWrapper(DataType<T> base, int length) {
    this.base = base;
    this.length = length;
  }

  /**
   * Retrieve the maximum length (in bytes) of encoded values.
   */
  public int getLength() {
    return length;
  }

  @Override
  public boolean isOrderPreserving() {
    return base.isOrderPreserving();
  }

  @Override
  public Order getOrder() {
    return base.getOrder();
  }

  @Override
  public boolean isNullable() {
    return base.isNullable();
  }

  @Override
  public boolean isSkippable() {
    return true;
  }

  @Override
  public int encodedLength(T val) {
    return length;
  }

  @Override
  public Class<T> encodedClass() {
    return base.encodedClass();
  }

  @Override
  public int skip(PositionedByteRange src) {
    src.setPosition(src.getPosition() + this.length);
    return this.length;
  }

  @Override
  public T decode(PositionedByteRange src) {
    if (src.getRemaining() < length) {
      throw new IllegalArgumentException("Not enough buffer remaining. src.offset: "
          + src.getOffset() + " src.length: " + src.getLength() + " src.position: "
          + src.getPosition() + " max length: " + length);
    }
    // create a copy range limited to length bytes. boo.
    PositionedByteRange b = new SimplePositionedMutableByteRange(length);
    src.get(b.getBytes());
    return base.decode(b);
  }

  @Override
  public int encode(PositionedByteRange dst, T val) {
    if (dst.getRemaining() < length) {
      throw new IllegalArgumentException("Not enough buffer remaining. dst.offset: "
          + dst.getOffset() + " dst.length: " + dst.getLength() + " dst.position: "
          + dst.getPosition() + " max length: " + length);
    }
    int written = base.encode(dst, val);
    if (written > length) {
      throw new IllegalArgumentException("Length of encoded value (" + written
          + ") exceeds max length (" + length + ").");
    }
    // TODO: is the zero-padding appropriate?
    for (; written < length; written++) {
      dst.put((byte) 0x00);
    }
    return written;
  }
}
