/*
 * 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.vinci.transport.document;

import java.io.IOException;

import org.apache.vinci.transport.Attributes;
import org.apache.vinci.transport.Frame;
import org.apache.vinci.transport.FrameComponent;
import org.apache.vinci.transport.FrameLeaf;
import org.apache.vinci.transport.ServiceDownException;
import org.apache.vinci.transport.ServiceException;
import org.apache.vinci.transport.Transportable;
import org.apache.vinci.transport.TransportableFactory;
import org.apache.vinci.transport.VNSException;
import org.apache.vinci.transport.VinciClient;
import org.apache.vinci.transport.VinciFrame;
import org.apache.vinci.transport.util.TransportableConverter;

/**
 * This class is a VinciFrame with extensions for support of XML attributes.
 * 
 * This class provides a set of "aadd" decorator methods that are almost exactly the same as the
 * "fadd" methods of the Frame class, except they return an empty set of attributes that can then be
 * populated. See the "main()" method for an example of how to use this class to easily create
 * documents with attributes.
 */

public class AFrame extends VinciFrame {
  private Attributes a = null;

  @Override
  public void setAttributes(Attributes s) {
    a = s;
  }

  @Override
  public Attributes getAttributes() {
    return a;
  }

  public Attributes createAttributes() {
    if (a == null) {
      a = new Attributes();
    }
    return a;
  }

  private static TransportableFactory aFrameFactory = new TransportableFactory() {
    @Override
    public Transportable makeTransportable() {
      return new AFrame();
    }
  };

  /**
   * Get a TransportableFactory that creates new AFrames.
   * 
   * @return -
   */
  public static TransportableFactory getAFrameFactory() {
    return aFrameFactory;
  }

  public AFrame() {
    this(10);
  }

  /**
   * @pre capacity &ge; 0
   * @param capacity
   *          -
   */
  public AFrame(int capacity) {
    super(capacity);
  }

  /**
   * Create an AFrame that is a (deep) copy of the given transportable.
   * 
   * @pre t != null
   * @param t
   *          -
   * @return -
   */
  public static AFrame toAFrame(Transportable t) {
    return (AFrame) TransportableConverter.convert(t, getAFrameFactory());
  }

  /**
   * Override the createSubFrame to create an AFrame of precise capacity.
   * 
   * @pre tag_name != null
   * @pre initialCapacity &ge; 0
   */
  @Override
  public Frame createSubFrame(String tag_name, int initialCapacity) {
    return new AFrame(initialCapacity);
  }

  /**
   * Override the createFrameLeaf to create an AFrameLeaf so that leaf values can have attributes.
   * 
   * @pre array != null
   */
  @Override
  public FrameLeaf createFrameLeaf(byte[] array) {
    return new AFrameLeaf(array, false);
  }

  /**
   * Convenience method for fetching sub-frames when their type is known to be AFrame
   * 
   * @param key
   *          The key identifying the value to retrieve.
   * @exception ClassCastException
   *              if the value was not of type AFrame.
   * @return The requested value, or null if the specified key does not exist.
   * 
   * @pre key != null
   */
  public AFrame fgetAFrame(String key) {
    return (AFrame) fgetFirst(key);
  }

  /**
   * Get the attributes associated with a particular key. If there is more than one matching key,
   * then the attributes of only the first matching key are returned.
   * 
   * @param key
   *          The key whose attributes to fetch.
   * @return The (possibly empty) set of attributes associated with the key, or null (which
   *         indicates no such key).
   * 
   * @pre key != null
   */
  public Attributes aget(String key) {
    FrameComponent comp = fgetFirst(key);
    if (comp != null) {
      Attributes not_null = comp.getAttributes();
      if (not_null == null) {
        return new Attributes();
      } else {
        return not_null;
      }
    }
    return null;
  }

  /**
   * Decorator method for adding float-valued tags.
   * 
   * @param key
   *          The key to be associated with the value.
   * @param val
   *          -
   * @return The (empty) set of attributes associated with the added key.
   * 
   * @pre key != null
   */
  public Attributes aadd(String key, float val) {
    AFrameLeaf l = new AFrameLeaf(val);
    add(key, l);
    return l.createAttributes();
  }

  /**
   * Decorator method for adding float-array valued tags.
   * 
   * @param key
   *          The key to be associated with the value.
   * @param val
   *          -
   * @return The (empty) set of attributes associated with the added key.
   * 
   * @pre key != null
   */
  public Attributes aadd(String key, float[] val) {
    if (val != null) {
      AFrameLeaf l = new AFrameLeaf(val);
      add(key, l);
      return l.createAttributes();
    }
    return null;
  }

  /**
   * Decorator method for adding double valued tags.
   * 
   * @param key
   *          The key to be associated with the value.
   * @param val
   *          -
   * @return The (empty) set of attributes associated with the added key.
   * 
   * @pre key != null
   */
  public Attributes aadd(String key, double val) {
    AFrameLeaf l = new AFrameLeaf(val);
    add(key, l);
    return l.createAttributes();
  }

  /**
   * Decorator method for adding double-array valued tags.
   * 
   * @param key
   *          The key to be associated with the value.
   * @param val
   *          The array to add. The array is immediately converted to string representation.
   * @return The (empty) set of attributes associated with the added key.
   * 
   * @pre key != null
   */
  public Attributes aadd(String key, double[] val) {
    if (val != null) {
      AFrameLeaf l = new AFrameLeaf(val);
      add(key, l);
      return l.createAttributes();
    }
    return null;
  }

  /**
   * Decorator method for adding int valued tags.
   * 
   * @param key
   *          The key to be associated with the value.
   * @param val
   *          The int to add.
   * @return The (empty) set of attributes associated with the key.
   * 
   * @pre key != null
   */
  public Attributes aadd(String key, int val) {
    AFrameLeaf l = new AFrameLeaf(val);
    add(key, l);
    return l.createAttributes();
  }

  /**
   * Decorator method for adding int-array valued tags.
   * 
   * @param key
   *          The key to be associated with the value.
   * @param val
   *          The array to add. The array is immediately converted to string representation.
   * @return The set of attributes associated with the key.
   * 
   * @pre key != null
   */
  public Attributes aadd(String key, int[] val) {
    if (val != null) {
      AFrameLeaf l = new AFrameLeaf(val);
      add(key, l);
      return l.createAttributes();
    }
    return null;
  }

  /**
   * Decorator method for adding long valued tags.
   * 
   * @param key
   *          The key to be associated with the value.
   * @param val
   *          The long value to add.
   * @return The (empty) set of attributes associated with the key.
   * 
   * @pre key != null
   */
  public Attributes aadd(String key, long val) {
    AFrameLeaf l = new AFrameLeaf(val);
    add(key, l);
    return l.createAttributes();
  }

  /**
   * Decorator method for adding long-array valued tags.
   * 
   * @param key
   *          The key to be associated with the value.
   * @param val
   *          The array to add. The array is immediately converted to string representation.
   * @return The (empty) set of attributes associated with the key.
   * 
   * @pre key != null
   */
  public Attributes aadd(String key, long[] val) {
    if (val != null) {
      AFrameLeaf l = new AFrameLeaf(val);
      add(key, l);
      return l.createAttributes();
    }
    return null;
  }

  /**
   * Decorator method for adding String valued tags.
   * 
   * @param key
   *          The key to be associated with the value.
   * @param val
   *          The string to add.
   * @return The (empty) set of attributes associated with the added key.
   * 
   * @pre key != null
   */
  public Attributes aadd(String key, String val) {
    if (val != null) {
      AFrameLeaf l = new AFrameLeaf(val);
      add(key, l);
      return l.createAttributes();
    }
    return null;
  }

  /**
   * Decorator method for adding binary valued tags. Encodes the data in Base64.
   * 
   * @param key
   *          The key to be associated with the value.
   * @param val
   *          The data to be encoded and added to this frame.
   * @return The (empty) set of attributes associated with the added key.
   * 
   * @pre key != null
   */
  public Attributes aadd(String key, byte[] val) {
    if (val != null) {
      AFrameLeaf l = new AFrameLeaf(val, true);
      add(key, l);
      return l.createAttributes();
    }
    return null;
  }

  /**
   * Decorator method for adding boolean valued tags.
   * 
   * @param key
   *          The key to be associated with the value.
   * @param val
   *          The boolean value to add.
   * @return The (empty) set of attributes associated with the added key.
   * 
   * @pre key != null
   */
  public Attributes aadd(String key, boolean val) {
    AFrameLeaf l = new AFrameLeaf(val);
    add(key, l);
    return l.createAttributes();
  }

  /**
   * Decorator method for adding Frame-valued tags.
   * 
   * @param key
   *          The key to be associated with the value.
   * @param val
   *          The sub-frame to add. Note this frame is not copied.
   * @return The (empty) set of attributes associated with the added key.
   * 
   * @pre key != null
   */
  public Attributes aadd(String key, AFrame val) {
    if (val != null) {
      add(key, val);
      return val.createAttributes();
    }
    return null;
  }

  /**
   * Decorator method for adding a valueless tag.
   * 
   * @param key
   *          The key name.
   * @return The (empty) set of attributes associated with the added key.
   * 
   * @pre key != null
   */
  public Attributes aadd(String key) {
    AFrame sub = new AFrame(0);
    add(key, sub);
    return sub.createAttributes();
  }

  /**
   * This is a hack method which allows you to add binary-valued tags to Frames in a manner such
   * that there is no textual encoding overhead of that binary data. This is NOT necessarily
   * XTalk-1.0 compatible which formally requires only UTF-8, but it still works. Binary data added
   * using this method can be retrieved using QueryableFrame/VinciFrame getter method
   * fgetTrueBinary(String).
   * 
   * WARNING: if the default XTalk parser is replaced with another one, applications that depend on
   * this method may break!
   * 
   * NOTE: This method should only be used when performance hit of Base64 encoding binary data [as
   * performed by aadd(String, byte[])] is unacceptable.
   * 
   * @param key
   *          The key to be associated with the value.
   * @param val
   *          The byte array to be added to the frame. Note the array is NOT copied or converted in
   *          any way.
   * @return The (empty) set of attributes associated with the added key.
   * 
   * @pre key != null
   */
  public Attributes aaddTrueBinary(String key, byte[] val) {
    if (val != null) {
      AFrameLeaf l = new AFrameLeaf(val, false);
      add(key, l);
      return l.createAttributes();
    }
    return null;
  }

  /**
   * @pre in != null
   * @pre service_name != null
   * 
   * @param in
   *          -
   * @param service_name
   *          -
   * @return -
   * @throws IOException
   *           -
   * @throws ServiceException
   *           -
   * @throws ServiceDownException
   *           -
   * @throws VNSException
   *           -
   * @throws IllegalStateException
   *           if VNS_HOST is not specified.
   */
  public static AFrame rpc(Transportable in, String service_name)
          throws IOException, ServiceException, ServiceDownException, VNSException {
    return (AFrame) VinciClient.sendAndReceive(in, service_name, getAFrameFactory());
  }

  /**
   * @pre in != null
   * @pre service_name != null
   * @pre socket_timeout &ge; 0
   * 
   * @param in
   *          -
   * @param service_name
   *          -
   * @param socket_timeout
   *          -
   * @return -
   * @throws IOException
   *           -
   * @throws ServiceException
   *           -
   * @throws ServiceDownException
   *           -
   * @throws VNSException
   *           -
   * @throws IllegalStateException
   *           if VNS_HOST is not specified.
   */
  public static AFrame rpc(Transportable in, String service_name, int socket_timeout)
          throws IOException, ServiceException, ServiceDownException, VNSException {
    return (AFrame) VinciClient.sendAndReceive(in, service_name, getAFrameFactory(),
            socket_timeout);
  }

  /**
   * @pre in != null
   * @pre service_name != null
   * @pre socket_timeout &ge; 0
   * 
   *      WARNING: This method relies on JDK-1.4 specific functions. USE IT ONLY if you don't need
   *      to maintain JDK1.3 compatability.
   * 
   * @param in
   *          -
   * @param service_name
   *          -
   * @param socket_timeout
   *          -
   * @param connect_timeout
   *          -
   * @return -
   * @throws IOException
   *           -
   * @throws ServiceException
   *           -
   * @throws ServiceDownException
   *           -
   * @throws VNSException
   *           -
   * @throws IllegalStateException
   *           if VNS_HOST is not specified.
   */
  public static AFrame rpc(Transportable in, String service_name, int socket_timeout,
          int connect_timeout)
          throws IOException, ServiceException, ServiceDownException, VNSException {
    return (AFrame) VinciClient.sendAndReceive(in, service_name, getAFrameFactory(), socket_timeout,
            connect_timeout);
  }

  /*
   * public static void main(String[] args) { // EXAMPLE of creating a document with attributes.
   * AFrame f = new AFrame(); f.setAttributes((Attributes)new Attributes().fadd("foo", "bar")); //
   * Once we create the empty AFrame, we can call "aadd" to add a key/value pair and // retrieve an
   * empty set of attributes that we can then populate with attributes that will // be associated
   * with the just-added key. Note that the attribute list object is itself a // special type of
   * VinciFrame, thus we can use the standard "fadd()" methods to create the // attribute list.
   * f.aadd("KEY", "value1") // Add XML element and retrieve the attribute list object
   * .fadd("attr1", "aval1") // Add an attribute for the tag. .fadd("attr2", "aval2") // Add another
   * attribute for the tag. .fadd("attr3", "you get the point"); // Note that with fadd() you can
   * potentially create a duplicate attribute (an XML no-no), // so the "idiot proof" method is to
   * instead use fset(): f.aadd("VALUELESS_KEY") // Add a valueless tag that has a couple of
   * attributes .fset("foo", "bar") .fset("foo2", 2) .fset("foo", "replaced bar"); // Here's how to
   * create a nested AFrame: AFrame nested = new AFrame(); nested.aadd("NESTED_KEY", 1234.567)
   * .fadd("nested", "attribute"); f.aadd("NESTED_FRAME", nested) .fadd("attributename", new int[]
   * {1,2,3}); // We can use either fadd() or aadd() to add attributeless tags, though fadd is //
   * preferrable since it avoids creating the attributes object. nested.fadd("ATTRIBUTELESS_KEY",
   * "done with fadd()"); nested.aadd("ANOTHER_ATTRIBUTELESS_KEY", "done with aadd()");
   * 
   * System.out.println(f.toXML()); System.out.println(); // Querying key values: String foo_value =
   * f.aget("VALUELESS_KEY").fgetString("foo");
   * System.out.println("Attribute value of foo from VALUELESS_KEY: " + foo_value);
   * System.out.println("TEST: " + toAFrame(f).toXML()); }
   */
}
