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

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

import org.xml.sax.*;
import org.xml.sax.helpers.DefaultHandler;
import javax.xml.parsers.SAXParserFactory;
import javax.xml.parsers.SAXParser;

/**
 * XML Deserializer.
 * @author Milind Bhandarkar
 */
public class XmlRecordInput implements RecordInput {
    
  static private class Value {
    private String type;
    private StringBuffer sb;
        
    public Value(String t) {
      type = t;
      sb = new StringBuffer();
    }
    public void addChars(char[] buf, int offset, int len) {
      sb.append(buf, offset, len);
    }
    public String getValue() { return sb.toString(); }
    public String getType() { return type; }
  }
    
  private class XMLParser extends DefaultHandler {
    private boolean charsValid = false;
        
    private ArrayList<Value> valList;
        
    private XMLParser(ArrayList<Value> vlist) {
      valList = vlist;
    }
        
    public void startDocument() throws SAXException {}
        
    public void endDocument() throws SAXException {}
        
    public void startElement(String ns,
                             String sname,
                             String qname,
                             Attributes attrs) throws SAXException {
      charsValid = false;
      if ("boolean".equals(qname) ||
          "i4".equals(qname) ||
          "int".equals(qname) ||
          "string".equals(qname) ||
          "double".equals(qname) ||
          "ex:i1".equals(qname) ||
          "ex:i8".equals(qname) ||
          "ex:float".equals(qname)) {
        charsValid = true;
        valList.add(new Value(qname));
      } else if ("struct".equals(qname) ||
                 "array".equals(qname)) {
        valList.add(new Value(qname));
      }
    }
        
    public void endElement(String ns,
                           String sname,
                           String qname) throws SAXException {
      charsValid = false;
      if ("struct".equals(qname) ||
          "array".equals(qname)) {
        valList.add(new Value("/"+qname));
      }
    }
        
    public void characters(char buf[], int offset, int len)
      throws SAXException {
      if (charsValid) {
        Value v = valList.get(valList.size()-1);
        v.addChars(buf, offset, len);
      }
    }
        
  }
    
  private class XmlIndex implements Index {
    public boolean done() {
      Value v = valList.get(vIdx);
      if ("/array".equals(v.getType())) {
        valList.set(vIdx, null);
        vIdx++;
        return true;
      } else {
        return false;
      }
    }
    public void incr() {}
  }
    
  private ArrayList<Value> valList;
  private int vLen;
  private int vIdx;
    
  private Value next() throws IOException {
    if (vIdx < vLen) {
      Value v = valList.get(vIdx);
      valList.set(vIdx, null);
      vIdx++;
      return v;
    } else {
      throw new IOException("Error in deserialization.");
    }
  }
    
  /** Creates a new instance of XmlRecordInput */
  public XmlRecordInput(InputStream in) {
    try{
      valList = new ArrayList<Value>();
      DefaultHandler handler = new XMLParser(valList);
      SAXParserFactory factory = SAXParserFactory.newInstance();
      SAXParser parser = factory.newSAXParser();
      parser.parse(in, handler);
      vLen = valList.size();
      vIdx = 0;
    } catch (Exception ex) {
      throw new RuntimeException(ex);
    }
  }
    
  public byte readByte(String tag) throws IOException {
    Value v = next();
    if (!"ex:i1".equals(v.getType())) {
      throw new IOException("Error deserializing "+tag+".");
    }
    return Byte.parseByte(v.getValue());
  }
    
  public boolean readBool(String tag) throws IOException {
    Value v = next();
    if (!"boolean".equals(v.getType())) {
      throw new IOException("Error deserializing "+tag+".");
    }
    return "1".equals(v.getValue());
  }
    
  public int readInt(String tag) throws IOException {
    Value v = next();
    if (!"i4".equals(v.getType()) &&
        !"int".equals(v.getType())) {
      throw new IOException("Error deserializing "+tag+".");
    }
    return Integer.parseInt(v.getValue());
  }
    
  public long readLong(String tag) throws IOException {
    Value v = next();
    if (!"ex:i8".equals(v.getType())) {
      throw new IOException("Error deserializing "+tag+".");
    }
    return Long.parseLong(v.getValue());
  }
    
  public float readFloat(String tag) throws IOException {
    Value v = next();
    if (!"ex:float".equals(v.getType())) {
      throw new IOException("Error deserializing "+tag+".");
    }
    return Float.parseFloat(v.getValue());
  }
    
  public double readDouble(String tag) throws IOException {
    Value v = next();
    if (!"double".equals(v.getType())) {
      throw new IOException("Error deserializing "+tag+".");
    }
    return Double.parseDouble(v.getValue());
  }
    
  public String readString(String tag) throws IOException {
    Value v = next();
    if (!"string".equals(v.getType())) {
      throw new IOException("Error deserializing "+tag+".");
    }
    return Utils.fromXMLString(v.getValue());
  }
    
  public Buffer readBuffer(String tag) throws IOException {
    Value v = next();
    if (!"string".equals(v.getType())) {
      throw new IOException("Error deserializing "+tag+".");
    }
    return Utils.fromXMLBuffer(v.getValue());
  }
    
  public void startRecord(String tag) throws IOException {
    Value v = next();
    if (!"struct".equals(v.getType())) {
      throw new IOException("Error deserializing "+tag+".");
    }
  }
    
  public void endRecord(String tag) throws IOException {
    Value v = next();
    if (!"/struct".equals(v.getType())) {
      throw new IOException("Error deserializing "+tag+".");
    }
  }
    
  public Index startVector(String tag) throws IOException {
    Value v = next();
    if (!"array".equals(v.getType())) {
      throw new IOException("Error deserializing "+tag+".");
    }
    return new XmlIndex();
  }
    
  public void endVector(String tag) throws IOException {}
    
  public Index startMap(String tag) throws IOException {
    return startVector(tag);
  }
    
  public void endMap(String tag) throws IOException { endVector(tag); }

}
