/**
 * 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 static com.google.common.base.Preconditions.checkNotNull;

import java.util.Date;

import org.apache.commons.lang.builder.HashCodeBuilder;
import org.apache.hadoop.classification.InterfaceAudience;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.hdfs.DFSUtil;
import org.apache.hadoop.hdfs.server.namenode.CachePool;
import org.apache.hadoop.util.IntrusiveCollection;
import org.apache.hadoop.util.IntrusiveCollection.Element;

import com.google.common.base.Preconditions;

/**
 * Namenode class that tracks state related to a cached path.
 *
 * This is an implementation class, not part of the public API.
 */
@InterfaceAudience.Private
public final class CacheDirective implements IntrusiveCollection.Element {
  private final long id;
  private final String path;
  private final short replication;
  private CachePool pool;
  private final long expiryTime;

  private long bytesNeeded;
  private long bytesCached;
  private long filesNeeded;
  private long filesCached;

  private Element prev;
  private Element next;

  public CacheDirective(CacheDirectiveInfo info) {
    this(
        info.getId(),
        info.getPath().toUri().getPath(),
        info.getReplication(),
        info.getExpiration().getAbsoluteMillis());
  }

  public CacheDirective(long id, String path,
      short replication, long expiryTime) {
    Preconditions.checkArgument(id > 0);
    this.id = id;
    this.path = checkNotNull(path);
    Preconditions.checkArgument(replication > 0);
    this.replication = replication;
    this.expiryTime = expiryTime;
  }

  public long getId() {
    return id;
  }

  public String getPath() {
    return path;
  }

  public short getReplication() {
    return replication;
  }

  public CachePool getPool() {
    return pool;
  }

  /**
   * @return When this directive expires, in milliseconds since Unix epoch
   */
  public long getExpiryTime() {
    return expiryTime;
  }

  /**
   * @return When this directive expires, as an ISO-8601 formatted string.
   */
  public String getExpiryTimeString() {
    return DFSUtil.dateToIso8601String(new Date(expiryTime));
  }

  /**
   * Returns a {@link CacheDirectiveInfo} based on this CacheDirective.
   * <p>
   * This always sets an absolute expiry time, never a relative TTL.
   */
  public CacheDirectiveInfo toInfo() {
    return new CacheDirectiveInfo.Builder().
        setId(id).
        setPath(new Path(path)).
        setReplication(replication).
        setPool(pool.getPoolName()).
        setExpiration(CacheDirectiveInfo.Expiration.newAbsolute(expiryTime)).
        build();
  }

  public CacheDirectiveStats toStats() {
    return new CacheDirectiveStats.Builder().
        setBytesNeeded(bytesNeeded).
        setBytesCached(bytesCached).
        setFilesNeeded(filesNeeded).
        setFilesCached(filesCached).
        setHasExpired(new Date().getTime() > expiryTime).
        build();
  }

  public CacheDirectiveEntry toEntry() {
    return new CacheDirectiveEntry(toInfo(), toStats());
  }
  
  @Override
  public String toString() {
    StringBuilder builder = new StringBuilder();
    builder.append("{ id:").append(id).
      append(", path:").append(path).
      append(", replication:").append(replication).
      append(", pool:").append(pool).
      append(", expiryTime: ").append(getExpiryTimeString()).
      append(", bytesNeeded:").append(bytesNeeded).
      append(", bytesCached:").append(bytesCached).
      append(", filesNeeded:").append(filesNeeded).
      append(", filesCached:").append(filesCached).
      append(" }");
    return builder.toString();
  }

  @Override
  public boolean equals(Object o) {
    if (o == null) { return false; }
    if (o == this) { return true; }
    if (o.getClass() != this.getClass()) {
      return false;
    }
    CacheDirective other = (CacheDirective)o;
    return id == other.id;
  }

  @Override
  public int hashCode() {
    return new HashCodeBuilder().append(id).toHashCode();
  }

  //
  // Stats related getters and setters
  //

  /**
   * Resets the byte and file statistics being tracked by this CacheDirective.
   */
  public void resetStatistics() {
    bytesNeeded = 0;
    bytesCached = 0;
    filesNeeded = 0;
    filesCached = 0;
  }

  public long getBytesNeeded() {
    return bytesNeeded;
  }

  public void addBytesNeeded(long bytes) {
    this.bytesNeeded += bytes;
    pool.addBytesNeeded(bytes);
  }

  public long getBytesCached() {
    return bytesCached;
  }

  public void addBytesCached(long bytes) {
    this.bytesCached += bytes;
    pool.addBytesCached(bytes);
  }

  public long getFilesNeeded() {
    return filesNeeded;
  }

  public void addFilesNeeded(long files) {
    this.filesNeeded += files;
    pool.addFilesNeeded(files);
  }

  public long getFilesCached() {
    return filesCached;
  }

  public void addFilesCached(long files) {
    this.filesCached += files;
    pool.addFilesCached(files);
  }

  //
  // IntrusiveCollection.Element implementation
  //

  @SuppressWarnings("unchecked")
  @Override // IntrusiveCollection.Element
  public void insertInternal(IntrusiveCollection<? extends Element> list,
      Element prev, Element next) {
    assert this.pool == null;
    this.pool = ((CachePool.DirectiveList)list).getCachePool();
    this.prev = prev;
    this.next = next;
  }

  @Override // IntrusiveCollection.Element
  public void setPrev(IntrusiveCollection<? extends Element> list, Element prev) {
    assert list == pool.getDirectiveList();
    this.prev = prev;
  }

  @Override // IntrusiveCollection.Element
  public void setNext(IntrusiveCollection<? extends Element> list, Element next) {
    assert list == pool.getDirectiveList();
    this.next = next;
  }

  @Override // IntrusiveCollection.Element
  public void removeInternal(IntrusiveCollection<? extends Element> list) {
    assert list == pool.getDirectiveList();
    this.pool = null;
    this.prev = null;
    this.next = null;
  }

  @Override // IntrusiveCollection.Element
  public Element getPrev(IntrusiveCollection<? extends Element> list) {
    if (list != pool.getDirectiveList()) {
      return null;
    }
    return this.prev;
  }

  @Override // IntrusiveCollection.Element
  public Element getNext(IntrusiveCollection<? extends Element> list) {
    if (list != pool.getDirectiveList()) {
      return null;
    }
    return this.next;
  }

  @Override // IntrusiveCollection.Element
  public boolean isInList(IntrusiveCollection<? extends Element> list) {
    return pool == null ? false : list == pool.getDirectiveList();
  }
};
