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

import java.util.ArrayList;
import java.util.List;
import java.util.StringTokenizer;

import org.apache.vinci.debug.Debug;
import org.apache.vinci.transport.Frame;
import org.apache.vinci.transport.FrameComponent;
import org.apache.vinci.transport.FrameLeaf;
import org.apache.vinci.transport.TransportConstants;
import org.apache.vinci.transport.Transportable;
import org.apache.vinci.transport.TransportableFactory;
import org.apache.vinci.transport.VinciFrame;
import org.apache.vinci.transport.vns.VNSConstants;

/**
 * Specialized document (Frame) for representing the result of resolving a service name to host/port
 * through VNS. Also provides utility methods for manipulating qualified/unqualified service names.
 * This class is used by VinciClient to locate the physical location of the requested service from
 * its logical service name.
 */
public class ResolveResult extends Frame {

  public int priority = -1;

  static public TransportableFactory factory = new TransportableFactory() {
    @Override
    public Transportable makeTransportable() {
      return new ResolveResult();
    }
  };

  /**
   * Strip the qualifications from this qualified service name.
   * 
   * @pre service_name != null
   * @param service_name
   *          -
   * @return -
   */
  static public String unqualifiedName(String service_name) {
    if (isQualified(service_name)) {
      service_name = service_name.trim();
      int start = service_name.lastIndexOf('[');
      return service_name.substring(0, start);
    } else {
      return service_name;
    }
  }

  /**
   * Check whether a service_name has qualifications (that is, any or all of level, host, instance
   * are explicitly specified.)
   * 
   * @pre service_name != null
   * @param service_name
   *          -
   * @return -
   */
  static public boolean isQualified(String service_name) {
    if (service_name.indexOf('[') != -1) {
      return true;
    }
    return false;
  }

  /**
   * Create a document representing the VNS resolve query for the specified service. This method
   * accepts either qualified or unqualified service names.
   * 
   * @pre service_name != null
   * @param service_name
   *          -
   * @return -
   */
  static public Frame composeQuery(String service_name) {
    VinciFrame query = new VinciFrame();
    query.fadd(TransportConstants.COMMAND_KEY, VNSConstants.RESOLVE_COMMAND);
    int start = service_name.indexOf('[');
    if (start != -1) {
      // qualified service name ... must parse out qualifications
      int end = service_name.indexOf(']', start);
      if (end != -1) {
        String qualifications = service_name.substring(start + 1, end);
        query.fadd(VNSConstants.SERVICE_KEY, service_name.substring(0, start));
        StringTokenizer tokenizer = new StringTokenizer(qualifications, ",");
        if (tokenizer.hasMoreTokens()) {
          query.fadd(VNSConstants.LEVEL_KEY, tokenizer.nextToken());
          if (tokenizer.hasMoreTokens()) {
            query.fadd(VNSConstants.HOST_KEY, tokenizer.nextToken());
            if (tokenizer.hasMoreTokens()) {
              query.fadd(VNSConstants.INSTANCE_KEY, tokenizer.nextToken());
            }
          }
        }
        return query;
      }
    }
    int at = service_name.indexOf('@');
    if (at == -1) {
      query.fadd(VNSConstants.SERVICE_KEY, service_name);
    } else {
      query.fadd(VNSConstants.SERVICE_KEY, service_name.substring(0, at));
    }
    return query;
  }

  /**
   * Create a document representing the VNS resolve query for the highest priority service(s) whose
   * priority is strictly below the specified priority. This method accepts either qualified or
   * unqualified service names.
   * 
   * @pre service_name != null
   * @param service_name
   *          -
   * @param mypriority
   *          -
   * @return -
   */
  static public Frame composeQuery(String service_name, int mypriority) {
    VinciFrame query = (VinciFrame) composeQuery(service_name);
    query.fadd("LEVEL", mypriority);
    return query;
  }

  private List services = new ArrayList();

  public ResolveResult() {
  }

  private int begin;

  private int current;

  /**
   * Initialize the service listing iterator. This initializes to a random spot to implement simple
   * load balancing across multiple equivalent service instances.
   */
  public void initializeIterator() {
    // Initialize the beginning to a random slot.
    begin = (int) (Math.random() * services.size());
    current = begin;
  }

  /**
   * Determine if there are more service listing to be fetched.
   * 
   * @return -
   */
  public boolean hasMore() {
    return (current - begin < services.size());
  }

  /**
   * Fetch the next service listing.
   * 
   * @pre hasMore()
   * @return -
   */
  public ServiceLocator getNext() {
    ServiceLocator return_me = (ServiceLocator) services.get(current % services.size());
    current++;
    return return_me;
  }

  static public class ServiceLocator extends Frame {
    public String host;

    public int port;

    public int instance;

    // Note that "priority" is a member of ResolveResult instead of this member class, since
    // all ServiceLocators for a particular query will always have the same priority.

    /**
     * Implement the Frame add() callback.
     * 
     * @pre key != null
     * @pre val != null
     */
    @Override
    public void add(String key, FrameComponent val) {
      if (key.equals(VNSConstants.HOST_KEY)) {
        host = ((FrameLeaf) val).toString();
      } else if (key.equals(VNSConstants.PORT_KEY)) {
        port = ((FrameLeaf) val).toInt();
      } else if (key.equals(VNSConstants.INSTANCE_KEY)) {
        instance = ((FrameLeaf) val).toInt();
      }
    }
  }

  /**
   * Implement the frame add() callback.
   * 
   * @pre key != null
   * @pre val != null
   * @pre {@code ((val instanceof ServiceLocator && key.equals(VNSConstants.SERVER_KEY)) || (val
   *      instanceof FrameLeaf && key.equals(VNSConstants.LEVEL_KEY)))}
   */
  @Override
  public void add(String key, FrameComponent val) {
    if (key.equals(VNSConstants.SERVER_KEY)) {
      Debug.Assert(val instanceof ServiceLocator);
      services.add(val);
    } else if (key.equals(VNSConstants.LEVEL_KEY)) {
      priority = ((FrameLeaf) val).toInt();
    }
  }

  /**
   * Override the Frame createSubFrame method to create a ServiceLocator.
   */
  @Override
  public Frame createSubFrame(String key, int capacity) {
    return new ServiceLocator();
  }
  // END Frame implementation

  /*
   * static public void main(String[] args_) { System.out.println(composeQuery(args_[0]).toXML()); }
   */
}
