package lava.net.common;

import java.net.URL;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.Hashtable;

/**
 * UNA:<br>
 *   Description of the logical Name of an Object.<br>
 *   Format is similar to URL.<br>
 *   Scheme-Name is psyc://<br>
 *   Objects might be Servers, Groups, Users, Protocols.<br>
 *   Servers:  resource=null<br>
 *   Groups:   resource begins with '@'<br>
 *   Users:    resource begins with '~'<br>
 *   Protocols: resource begins with '$'<br>
 *   Every Object only can have only one UNA.<br>
 **/
public class UNA extends java.lang.Object 
{
   /**
    *
    **/
   private static Hashtable cache = new Hashtable();

   /**
    *
    **/
   private static Hashtable revcache = new Hashtable();

   /**
    *
    **/
   private static boolean instantiated = false;

   /**
    * Flag to mark that we're an applet.
    **/
   private static boolean applet = false;

   /**
    *
    **/
   private static String defaultScheme = null;

   /**
    *
    **/
   private static Hashtable defaultHost = new Hashtable();

   /**
    *
    **/
   private static Hashtable defaultPort = new Hashtable();

   /**
    *
    **/
   private static Hashtable defaultProtocol = new Hashtable();

   /**
    *
    **/
   private static Hashtable defaultResource = new Hashtable();

   /**
    *
    **/
   private String scheme = null;

   /**
    *
    **/
   private String host = null;

   /**
    *
    **/
   private InetAddress hostCache = null;

   /**
    *
    **/
   private int port = -1;

   /**
    *
    **/
   private String protocol = null;

   /**
    *
    **/
   private String resource = null;

   /**
    *
    **/
   private int hashCode = -1;

   /**
    *
    **/
   private String string = null;

   /**
    *
    **/
   public UNA()
   {
      instantiated = true;
      setScheme(null);
      setHost((String)null);
      setPort(-1);
      setProtocol(null);
      setResource(null);
   }

   /**
    *
    **/
   public UNA(String spec)
   {
      this ((UNA)null,spec);
   }

   /**
    *
    **/
   public UNA(UNA base, String spec)
   {
      this ();
      // pre-initialize it with our base
      if(base != null)
      {
         setScheme(base.getScheme());
         setHost(base.getHostName());
         setPort(base.getPort());
         setProtocol(base.getProtocol());
         setResource(base.getResource());
      }
      if(spec == null)
         return;
      int i, c, limit = spec.length();
      int start = 0;
      while((limit > 0) && (spec.charAt(limit - 1) <= ' '))
      {
         // eliminate trailing whitespace
         limit--;
      }
      while((start < limit) && (spec.charAt(start) <= ' '))
      {
         // eliminate leading whitespace
         start++;
      }
      if(spec.indexOf("://") != -1)
      {
         for(i = start;(i < limit) && ((c = spec.charAt(i)) != '/');i++)
         {
            if(c == ':')
            {
               if(i > start)
                  setScheme(spec.substring(start,i).toLowerCase());
               // jump over "://"
               start = i + 3;
               break;
            }
         }
      }
      i = spec.indexOf('/',start);
      if(i >= 0)
      {
         if(limit > i)
            setResource(spec.substring(i,limit));
         limit = i;
      }
      i = spec.indexOf(':',start);
      if(i >= 0 && i < limit)
      {
         int j;
         for(j = i + 1;j < limit;++j)
            if(!Character.isDigit(spec.charAt(j)))
               break;
         if(j > i + 1)
            setPort(Integer.parseInt(spec.substring(i + 1,j)));
         if(j < limit)
            setProtocol(spec.substring(j,limit));
         limit = i;
      }
      if(limit > start)
         setHost(spec.substring(start,limit));
      if(host == null && resource == null)
      {
         // special case: no real host given
         // parse all the stuff as resource, prepend "/"
         setPort(-1);
         setProtocol(null);
         setResource("/" + spec.substring(start,limit));
      }
   }

   /**
    *
    **/
   public UNA(String scheme, String host, int port, String protocol, //
   String resource)
   {
      instantiated = true;
      setScheme(scheme);
      setHost(host);
      setPort(port);
      setProtocol(protocol);
      setResource(resource);
   }

   /**
    *
    **/
   public UNA(String scheme, InetAddress host, int port, String protocol, //
   String resource)
   {
      instantiated = true;
      setScheme(scheme);
      setHost(host);
      setPort(port);
      setProtocol(protocol);
      setResource(resource);
   }

   /**
    *
    **/
   public String getScheme()
   {
      return scheme != null ? scheme : getDefaultScheme();
   }

   /**
    *
    **/
   public InetAddress getHost()
   {
      if(hostCache == null)
         hostCache = lookup(getHostName());
      return hostCache;
   }

   /**
    *
    **/
   public String getHostName()
   {
      return host != null ? host : getDefaultHost(getScheme());
   }

   /**
    *
    **/
   public String getHostAddress()
   {
      InetAddress h = getHost();
      return h == null ? null : h.getHostAddress();
   }

   /**
    *
    **/
   public int getPort()
   {
      return port > 0 ? port : getDefaultPort(getScheme());
   }

   /**
    *
    **/
   public String getProtocol()
   {
      return protocol != null ? protocol : getDefaultProtocol(getScheme());
   }

   /**
    *
    **/
   public String getResource()
   {
      return resource != null ? resource : getDefaultResource(getScheme());
   }

   /**
    *
    **/
   protected void setScheme(String newScheme)
   {
      scheme = newScheme;
   }

   /**
    *
    **/
   protected void setHost(String newHost)
   {
      host = newHost;
   }

   /**
    *
    **/
   protected void setHost(InetAddress newHost)
   {
      setHost(lookup(newHost));
   }

   /**
    *
    **/
   protected void setPort(int newPort)
   {
      port = newPort;
   }

   /**
    *
    **/
   protected void setProtocol(String newProtocol)
   {
      protocol = newProtocol;
   }

   /**
    *
    **/
   protected void setResource(String newResource)
   {
      if(newResource != null && newResource.length() > 1 && //
      newResource.charAt(newResource.length() - 1) != '/')
         newResource = newResource + "/";
      resource = newResource;
   }

   /**
    *
    **/
   public boolean equalsInScheme(UNA other)
   {
      if(other == null)
         return false;
      String s = getScheme(), os = other.getScheme();
      return (s == null || os == null ? s == null && //
      os == null : s.toLowerCase().equals(os.toLowerCase()));
   }

   /**
    *
    **/
   public boolean equalsInHost(UNA other)
   {
      if(other == null)
         return false;
      InetAddress h = getHost();
      String n;
      return (h == null ? (n = getHostName()) == null ? //
      other.getHostName() == null : n.equals(other.getHostName()) : //
      h.equals(other.getHost()));
   }

   /**
    *
    **/
   public boolean equalsInPort(UNA other)
   {
      return (other != null) && (getPort() == other.getPort());
   }

   /**
    *
    **/
   public boolean equalsInProtocol(UNA other)
   {
      if(other == null)
         return false;
      String p = getProtocol(), op = other.getProtocol();
      return (p == null || op == null ? //
      p == null && op == null : p.equals(op));
   }

   /**
    *
    **/
   public boolean equalsInResource(UNA other)
   {
      if(other == null)
         return false;
      String r = getResource(), or = other.getResource();
      return (r == null || or == null ? //
      r == null && or == null : r.equals(or));
   }

   /**
    *
    **/
   public boolean masterInResource(UNA other)
   {
      if(other == null)
         return false;
      try
      {
         String r = getResource(), or = other.getResource();
         return (r == null || or == null ? r == null : //
         or.indexOf(r) == 0 && (or.charAt(r.length() - 1) == '/' || //
         or.charAt(r.length()) == '/'));
      }
      catch(StringIndexOutOfBoundsException e)
      {
         // resources are equal
         return true;
      }
   }

   /**
    *
    **/
   public boolean master(UNA other)
   {
      return equalsInHost(other) && //
      equalsInPort(other) && //
      equalsInProtocol(other) && //
      masterInResource(other);
   }

   /**
    *
    **/
   public boolean equals(Object obj)
   {
      return (obj instanceof UNA) && //
      equalsInScheme((UNA)obj) && //
      equalsInHost((UNA)obj) && //
      equalsInPort((UNA)obj) && //
      equalsInProtocol((UNA)obj) && //
      equalsInResource((UNA)obj);
   }

   /**
    *
    **/
   public int hashCode()
   {
      if(hashCode == -1)
      {
         String s = getScheme(), p = getProtocol(), r = getResource();
         InetAddress h = getHost();
         String n;
         hashCode = (s == null ? 0 : s.hashCode()) ^ //
         (h == null ? (n = getHostName()) == null ? 0 : n.hashCode() : //
         h.hashCode()) ^ getPort() ^ //
         (p == null ? 0 : p.hashCode()) ^ //
         (r == null ? 0 : r.hashCode());
      }
      return hashCode;
   }

   /**
    *
    **/
   public String toString()
   {
      if(string != null)
         return string;
      StringBuffer me = new StringBuffer();
      String s = getScheme(), p = getProtocol(), //
      dp = getDefaultProtocol(s), r = getResource(), h = getHostName();
      int po = getPort(), dpo = getDefaultPort(s);
      if(s != null)
         me.append(s + "://");
      if((h != null) && h != null)
         me.append(h);
      if((po >= 0 || p != null) && (po != dpo || p != dp))
         me.append(":");
      if(po >= 0 && po != dpo)
         me.append(po);
      if(p != null && p != dp)
         me.append(p);
      if(r != null)
         me.append(r);
      string = me.toString();
      return string;
   }

   /**
    *
    **/
   public static void setDefaults(String scheme, InetAddress host, //
   int port, String protocol, String resource)
   {
      String h = null;
      if(host != null)
         try
         {
            h = host.getHostAddress();
         }
         catch(Exception e)
         {
            h = null;
         }
      setDefaults(scheme,h,port,protocol,resource);
   }

   /**
    *
    **/
   public static void setDefaults(String scheme, String host, //
   int port, String protocol, String resource)
   {
      if(instantiated || scheme == null)
         return;
      if(defaultScheme == null)
         defaultScheme = scheme;
      scheme = scheme.toLowerCase();
      if(host != null && defaultHost.get(scheme) == null)
         defaultHost.put(scheme,host);
      if(port > 0 && defaultPort.get(scheme) == null)
         defaultPort.put(scheme,new Integer(port));
      if(protocol != null && defaultProtocol.get(scheme) == null)
         defaultProtocol.put(scheme,protocol);
      if(resource != null && defaultResource.get(scheme) == null)
         defaultResource.put(scheme,resource);
   }

   /**
    *
    **/
   public static String getDefaultScheme()
   {
      return defaultScheme;
   }

   /**
    *
    **/
   public static String getDefaultHost(String scheme)
   {
      if(scheme == null)
         return null;
      return (String)defaultHost.get(scheme.toLowerCase());
   }

   /**
    *
    **/
   public static int getDefaultPort(String scheme)
   {
      if(scheme == null)
         return -1;
      Integer port = (Integer)defaultPort.get(scheme.toLowerCase());
      if(port == null)
         return -1;
      return port.intValue();
   }

   /**
    *
    **/
   public static int getDefaultPort()
   {
      return getDefaultPort(getDefaultScheme());
   }

   /**
    *
    **/
   public static String getDefaultProtocol(String scheme)
   {
      if(scheme == null)
         return null;
      return (String)defaultProtocol.get(scheme.toLowerCase());
   }

   /**
    *
    **/
   public static String getDefaultProtocol()
   {
      return getDefaultProtocol(getDefaultScheme());
   }

   /**
    *
    **/
   public static String getDefaultResource(String scheme)
   {
      if(scheme == null)
         return null;
      return (String)defaultResource.get(scheme.toLowerCase());
   }

   /**
    *
    **/
   public static String getDefaultResource()
   {
      return getDefaultResource(getDefaultScheme());
   }

   /**
    *
    **/
   public static void cache(String name, InetAddress addr)
   {
      if(name != null && addr != null)
      {
         cache.put(name,addr);
         revcache.put(addr,name);
      }
   }

   /**
    *
    **/
   public static InetAddress lookup(String name)
   {
      if(name == null)
         return null;
      InetAddress addr = (InetAddress)cache.get(name);
      if(addr == null && !applet)
      {
         try
         {
            addr = InetAddress.getByName(name);
            cache(name,addr);
         }
         catch(Exception e)
         {
            addr = null;
         }
      }
      return addr;
   }

   /**
    *
    **/
   public static String lookup(InetAddress addr)
   {
      if(addr == null)
         return null;
      String name = (String)revcache.get(addr);
      if(name == null)
      {
         if(!applet)
         {
            try
            {
               name = addr.getHostName();
               if(name != null && name.indexOf('.') < 0)
                  // refuse the name if it is not fully qualified
                  name = null;
               else
                  // only cache the name, if it seems to be a fully
                  // qualified one
                  cache(name,addr);
            }
            catch(Exception e)
            {
               name = null;
            }
         }
         if(name == null)
         {
            name = addr.getHostAddress();
            cache(name,addr);
         }
      }
      return name;
   }

   /**
    *
    **/
   public static void addAppletHost(String name)
   {
      applet = false;
      lookup(name);
      applet = true;
   }

   /**
    *
    **/
   public static void addAppletHost(InetAddress addr)
   {
      applet = false;
      lookup(addr);
      applet = true;
   }

   /**
    *
    **/
   public static boolean isApplet()
   {
      return applet;
   }

}

