/**
 * 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.tools.offlineImageViewer;

import java.io.IOException;
import java.util.AbstractMap;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;

/**
 * A DelimitedImageVisitor generates a text representation of the fsimage,
 * with each element separated by a delimiter string.  All of the elements
 * common to both inodes and inodes-under-construction are included. When 
 * processing an fsimage with a layout version that did not include an 
 * element, such as AccessTime, the output file will include a column
 * for the value, but no value will be included.
 * 
 * Individual block information for each file is not currently included.
 * 
 * The default delimiter is tab, as this is an unlikely value to be included
 * an inode path or other text metadata.  The delimiter value can be via the
 * constructor.
 */
class DelimitedImageVisitor extends TextWriterImageVisitor {
  private static final String defaultDelimiter = "\t"; 
  
  final private LinkedList<ImageElement> elemQ = new LinkedList<ImageElement>();
  private long fileSize = 0l;
  // Elements of fsimage we're interested in tracking
  private final Collection<ImageElement> elementsToTrack;
  // Values for each of the elements in elementsToTrack
  private final AbstractMap<ImageElement, String> elements = 
                                            new HashMap<ImageElement, String>();
  private final String delimiter;

  {
    elementsToTrack = new ArrayList<ImageElement>();
    
    // This collection determines what elements are tracked and the order
    // in which they are output
    Collections.addAll(elementsToTrack,  ImageElement.INODE_PATH,
                                         ImageElement.REPLICATION,
                                         ImageElement.MODIFICATION_TIME,
                                         ImageElement.ACCESS_TIME,
                                         ImageElement.BLOCK_SIZE,
                                         ImageElement.NUM_BLOCKS,
                                         ImageElement.NUM_BYTES,
                                         ImageElement.NS_QUOTA,
                                         ImageElement.DS_QUOTA,
                                         ImageElement.PERMISSION_STRING,
                                         ImageElement.USER_NAME,
                                         ImageElement.GROUP_NAME);
  }
  
  public DelimitedImageVisitor(String filename) throws IOException {
    this(filename, false);
  }

  public DelimitedImageVisitor(String outputFile, boolean printToScreen) 
                                                           throws IOException {
    this(outputFile, printToScreen, defaultDelimiter);
  }
  
  public DelimitedImageVisitor(String outputFile, boolean printToScreen, 
                               String delimiter) throws IOException {
    super(outputFile, printToScreen);
    this.delimiter = delimiter;
    reset();
  }

  /**
   * Reset the values of the elements we're tracking in order to handle
   * the next file
   */
  private void reset() {
    elements.clear();
    for(ImageElement e : elementsToTrack) 
      elements.put(e, null);
    
    fileSize = 0l;
  }
  
  @Override
  void leaveEnclosingElement() throws IOException {
    ImageElement elem = elemQ.pop();

    // If we're done with an inode, write out our results and start over
    if(elem == ImageElement.INODE || 
       elem == ImageElement.INODE_UNDER_CONSTRUCTION) {
      writeLine();
      write("\n");
      reset();
    }
  }

  /**
   * Iterate through all the elements we're tracking and, if a value was
   * recorded for it, write it out.
   */
  private void writeLine() throws IOException {
    Iterator<ImageElement> it = elementsToTrack.iterator();
    
    while(it.hasNext()) {
      ImageElement e = it.next();
      
      String v = null;
      if(e == ImageElement.NUM_BYTES)
        v = String.valueOf(fileSize);
      else
        v = elements.get(e);
      
      if(v != null)
        write(v);
      
      if(it.hasNext())
        write(delimiter);
    }
  }

  @Override
  void visit(ImageElement element, String value) throws IOException {
    // Explicitly label the root path
    if(element == ImageElement.INODE_PATH && value.equals(""))
      value = "/";
    
    // Special case of file size, which is sum of the num bytes in each block
    if(element == ImageElement.NUM_BYTES)
      fileSize += Long.parseLong(value);
    
    if(elements.containsKey(element) && element != ImageElement.NUM_BYTES)
      elements.put(element, value);
    
  }

  @Override
  void visitEnclosingElement(ImageElement element) throws IOException {
    elemQ.push(element);
  }

  @Override
  void visitEnclosingElement(ImageElement element, ImageElement key,
      String value) throws IOException {
    // Special case as numBlocks is an attribute of the blocks element
    if(key == ImageElement.NUM_BLOCKS 
        && elements.containsKey(ImageElement.NUM_BLOCKS))
      elements.put(key, value);
    
    elemQ.push(element);
  }
  
  @Override
  void start() throws IOException { /* Nothing to do */ }
}
