package org.jacorb.orb.miop;

import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.StringTokenizer;
import org.jacorb.config.Configuration;
import org.jacorb.config.ConfigurationException;
import org.jacorb.orb.CDRInputStream;
import org.jacorb.orb.CDROutputStream;
import org.jacorb.orb.ORB;
import org.jacorb.orb.ParsedIOR;
import org.jacorb.orb.TaggedComponentList;
import org.jacorb.orb.etf.ProfileBase;
import org.jacorb.orb.iiop.IIOPProfile;
import org.omg.CORBA.NO_IMPLEMENT;
import org.omg.ETF.Profile;
import org.omg.GIOP.Version;
import org.omg.IOP.IOR;
import org.omg.IOP.TAG_GROUP;
import org.omg.IOP.TAG_INTERNET_IOP;
import org.omg.IOP.TAG_UIPMC;
import org.omg.IOP.TaggedComponent;
import org.omg.IOP.TaggedComponentSeqHolder;
import org.omg.IOP.TaggedProfile;
import org.omg.IOP.TaggedProfileHolder;
import org.omg.MIOP.UIPMC_ProfileBody;
import org.omg.MIOP.UIPMC_ProfileBodyHelper;
import org.omg.PortableGroup.TagGroupTaggedComponent;
import org.omg.PortableGroup.TagGroupTaggedComponentHelper;


/**
 *
 * @author Alysson Neves Bessani
 * @author Nick Cross
 * @version 1.0
 * @see MIOPFactories
 */
public class MIOPProfile extends ProfileBase
{
   private InetAddress             inetAddress      = null;
   private byte[]                  data             = null;
   private UIPMC_ProfileBody       uipmc            = null;
   private TagGroupTaggedComponent tagGroup         = null;
   private IIOPProfile             groupIIOPProfile = null;


   /**
    * Creates a MIOP profile from a marshaled profile.
    *
    * @param data the byte array
    */
   public MIOPProfile (byte[] data)
   {
      this.data = data;

      CDRInputStream in = new CDRInputStream (data);
      in.openEncapsulatedArray ();
      uipmc = UIPMC_ProfileBodyHelper.read (in);
      in.close ();

      version = uipmc.miop_version;
   }


   /**
    * Creates a MIOP profile based on a complete information set.
    *
    * @param address group address.
    * @param port group port.
    * @param domainId group domain id.
    * @param groupId group id.
    * @param groupVersion group version.
    * @param groupRefVersion group reference version.
    * @param iiop the IIOP group profile..
    */
   public MIOPProfile (String address, short port, String domainId, long groupId,
            Version groupVersion, int groupRefVersion, IIOPProfile iiop)
   {

      this.groupIIOPProfile = iiop;
      this.tagGroup = new TagGroupTaggedComponent(groupVersion, domainId, groupId, groupRefVersion);

      TaggedComponentList list = new TaggedComponentList ();
      list.addComponent (TAG_GROUP.value, tagGroup, TagGroupTaggedComponentHelper.class);

      if (iiop != null)
      {
         list.addAll (iiop.getComponents ());
      }

      this.uipmc = new UIPMC_ProfileBody (new Version ((byte)1, (byte)0), address, port,
               list.asArray ());

      try
      {
         inetAddress = InetAddress.getByName (uipmc.the_address);
      }
      catch (UnknownHostException uke)
      {
         throw new RuntimeException ("Unable to create profile to unknown group address: " +
                  uipmc.the_address);
      }
   }


   /**
    * Creates a MIOP profile from a corbaloc URL.
    *
    * @param corbaloc group corbaloc URL
    */
   public MIOPProfile (String corbaloc)
   {
      this.corbalocStr = corbaloc;
   }


   /**
    * Configure this object. Must be called after the creation for full profile
    * configuration.
    *
    * @param config the config
    * @throws ConfigurationException if any problem with the configuration is
    *         detected
    */
   @Override
   public void configure (Configuration config) throws ConfigurationException
   {
      super.configure(config);

      logger = configuration.getLogger("org.jacorb.miop");

      ORB orb = config.getORB ();

      if (corbalocStr != null)
      {
         if (logger.isDebugEnabled ())
         {
            logger.debug ("MIOPProfile parsing corbaloc: " + corbalocStr);
         }

         // I create the profile from the corbaloc
         if (!corbalocStr.startsWith ("miop:"))
         {
            throw new IllegalArgumentException ("URL must start with \'miop:\'");
         }

         int sub = corbalocStr.indexOf (";");
         if (sub == -1)
         {
            parseMIOPCorbaloc (corbalocStr.substring (0));
         }
         else
         {
            parseMIOPCorbaloc (corbalocStr.substring (0, sub));
         }


         components = new TaggedComponentList ();

         CDROutputStream out = new CDROutputStream ();
         out.beginEncapsulatedArray ();
         TagGroupTaggedComponentHelper.write (out, tagGroup);
         components.addComponent (TAG_GROUP.value, out.getBufferCopy ());
         out.close ();

         if (sub != -1)
         {
            groupIIOPProfile = (IIOPProfile)new ParsedIOR (orb, "corbaloc:" +
                     corbalocStr.substring (sub + 1)).getEffectiveProfile ();

            objectKey = groupIIOPProfile.get_object_key ();
            TaggedProfile taggedProfile = groupIIOPProfile.asTaggedProfile ();
            components.addComponent (taggedProfile.tag, taggedProfile.profile_data);
         }

         uipmc.components = components.asArray ();
      }

      if (tagGroup == null)
      {
         if (logger.isDebugEnabled ())
         {
            logger.debug ("MIOPProfile inspecting uipmc components.");
         }

         components = new TaggedComponentList ();
         for (int i = 0; i < uipmc.components.length; i++)
         {
            TaggedComponent component = uipmc.components[i];
            components.addComponent (component);
            switch (component.tag)
            {
               case TAG_GROUP.value:
               {
                  if (logger.isDebugEnabled ())
                  {
                     logger.debug ("MIOPProfile inspecting tagGroup.");
                  }

                  CDRInputStream in2 = new CDRInputStream (orb, component.component_data);
                  in2.openEncapsulatedArray();
                  tagGroup = TagGroupTaggedComponentHelper.read (in2);
                  in2.close();
                  break;
               }
               case TAG_INTERNET_IOP.value:
               {
                  if (logger.isDebugEnabled ())
                  {
                     logger.debug ("MIOPProfile inspecting group iiop profile.");
                  }
                  IOR ior = new IOR ("IDL:omg.org/CORBA/Object:1.0",
                           new TaggedProfile[] { new TaggedProfile (component.tag,
                                    component.component_data) });

                  groupIIOPProfile = (IIOPProfile)new ParsedIOR (orb, ior).getEffectiveProfile ();
               }
            }
         }
      }

      try
      {
         inetAddress = InetAddress.getByName (uipmc.the_address);
      }
      catch (UnknownHostException uke)
      {
         throw new RuntimeException ("Unable to create profile to unknow group address: " +
                  uipmc.the_address);
      }
   }


   /**
    * The group object key, or the tagGroup as a byte array.
    *
    * @return tagGroup as a byte array.
    */
   @Override
   public byte[] get_object_key ()
   {
      if (objectKey == null)
      {
         CDROutputStream out = new CDROutputStream ();
         out.beginEncapsulatedArray ();
         TagGroupTaggedComponentHelper.write (out, tagGroup);
         objectKey = out.getBufferCopy ();
         out.close();
      }

      return objectKey;
   }


   /**
    * Hash code of this profile.
    *
    * @return the hashcode of this object.
    */
   @Override
   public int hash ()
   {
      return hashCode ();
   }


   /**
    * Test if the passed profile is equal this.
    *
    * @param profile other profile.
    * @return true if it's equal, false otherwise.
    */
   @Override
   public boolean is_match (Profile profile)
   {
      return equals (profile);
   }


   /**
    * Encode this profile as a TaggedProfile (marshaled).
    *
    * @param taggedProfile a tagged profile holder.
    * @param taggedComponentSeq unused.
    */
   @Override
   public void marshal (TaggedProfileHolder taggedProfile,
            TaggedComponentSeqHolder taggedComponentSeq)
   {
      if (data == null)
      {
         CDROutputStream out = new CDROutputStream ();
         out.beginEncapsulatedArray ();
         UIPMC_ProfileBodyHelper.write (out, uipmc);
         data = out.getBufferCopy ();
         out.close ();
      }

      taggedProfile.value = new TaggedProfile (TAG_UIPMC.value, data);
   }


   /**
    * Set this object key. Does nothing because it is a group profile. Objectkey
    * equals serialized tagGroup
    *
    * @param objectKey unused.
    */
   @Override
   public void set_object_key (byte[] objectKey)
   {
   }


   /**
    * Return the profile tag.
    *
    * @return TAG_UIPMC.value.
    */
   @Override
   public int tag ()
   {
      return TAG_UIPMC.value;
   }


   /**
    * This profile MIOP version.
    *
    * @return miop version.
    */
   @Override
   public Version version ()
   {
      return uipmc.miop_version;
   }


   /*
    * Other public methods
    */

   /**
    * Returns the group InetAddress.
    *
    * @return group InetAddress.
    */
   public final InetAddress getGroupInetAddress ()
   {
      return inetAddress;
   }


   /**
    * Returns the UIPMC profile.
    *
    * @return the UIPMC_ProfileBody object.
    */
   public final UIPMC_ProfileBody getUIPMCProfile ()
   {
      return uipmc;
   }


   /**
    * Returns tagGroup, the group logical information struct.
    *
    * @return the GroupInfo object.
    */
   public final TagGroupTaggedComponent getTagGroup ()
   {
      return tagGroup;
   }


   /**
    * the IIOP group profile (for two way requests).
    *
    * @return IIOP profile
    */
   public final IIOPProfile getGroupIIOPProfile ()
   {
      return groupIIOPProfile;
   }


   /**
    * The string address of this profile.
    *
    * @return &lt;group address&gt;:&lt;group port&gt;
    */
   @Override
   public final String toString ()
   {
      return uipmc.the_address + ":" + uipmc.the_port;
   }


   /**
    * Parse a miop corbaloc url. Starting with 'miop:'.
    *
    * @param corbaloc
    */
   private void parseMIOPCorbaloc (String corbaloc)
   {
      // removes the "miop:" header
      corbaloc = corbaloc.substring (corbaloc.indexOf (':') + 1);

      // parse version (optional)
      int sep = corbaloc.indexOf ('@');
      Version version = parseVersion (sep == -1 ? "" : corbaloc.substring (0, sep));
      corbaloc = corbaloc.substring (sep + 1);

      // parse tag group
      sep = corbaloc.indexOf ('/');
      parseTagGroup (corbaloc.substring (0, sep));
      corbaloc = corbaloc.substring (sep + 1);

      // parse group transport address
      parseGroupAddress (corbaloc);

      uipmc.miop_version = version;
   }


   /**
    * Parses a version string into a org.omg.GIOP.Version object.
    *
    * @param ver_str
    * @return a version object.
    */
   private Version parseVersion (String ver_str)
   {
      int major = 1;
      int minor = 0;
      int sep = ver_str.indexOf ('.');
      if (sep != -1)
      {
         try
         {
            major = Integer.parseInt (ver_str.substring (0, sep));
            minor = Integer.parseInt (ver_str.substring (sep + 1));
         }
         catch (NumberFormatException nfe)
         {
            throw new IllegalArgumentException ("Invalid version :" + ver_str);
         }
      }
      return new Version ((byte)major, (byte)minor);
   }


   /**
    * Parses a Tag Group from a String.<br>
    * Example: "1.0-MyDomain-1" where:<br>
    * <code>{@literal <group component version>-<group_domain_id>-<object_group_id>[-<object group
    * reference version>]}</code>
    *
    * @param s
    */
   public void parseTagGroup (String s)
   {
      tagGroup = new TagGroupTaggedComponent();
      if (s.indexOf ('-') != -1)
      {
         try
         {
            StringTokenizer st = new StringTokenizer (s, "-");
            tagGroup.group_version = parseVersion (st.nextToken ());
            tagGroup.group_domain_id = st.nextToken ();
            tagGroup.object_group_id = Long.parseLong (st.nextToken ());
            if (st.hasMoreTokens ())
            {
               tagGroup.object_group_ref_version = Integer.parseInt (st.nextToken ());
            }
         }
         catch (NumberFormatException nfe)
         {
            throw new IllegalArgumentException ("Illegal group information format: " + s);
         }
      }
      else
      {
         throw new IllegalArgumentException ("Illegal group information format: " + s);
      }
   }


   /**
    * Parse the group address.<br>
    * Example: "225.6.7.8:12345" where:<br>
    * <code>{@literal <group IP address>:<group port>}</code>
    *
    * @param s
    */
   private void parseGroupAddress (String s)
   {
      uipmc = new UIPMC_ProfileBody ();

      int sep = s.indexOf (':');
      if (sep != -1)
      {
         try
         {
            uipmc.the_port = (short)Integer.parseInt (s.substring (sep + 1));
            uipmc.the_address = s.substring (0, sep);
         }
         catch (NumberFormatException ill)
         {
            throw new IllegalArgumentException (
                     "Illegal port number in MIOP object address format: " + s);
         }
      }
   }


   /**
    * Read address profile.
    *
    * @param stream the stream
    * @see org.jacorb.orb.etf.ProfileBase#readAddressProfile
    * CDRInputStream)
    */
   @Override
   public void readAddressProfile (CDRInputStream stream)
   {
      throw new NO_IMPLEMENT ("Not yet implemented");
   }


   /**
    * Write address profile.
    *
    * @param stream the stream
    * @see org.jacorb.orb.etf.ProfileBase#writeAddressProfile
    */
   @Override
   public void writeAddressProfile (CDROutputStream stream)
   {
      throw new NO_IMPLEMENT ("Not yet implemented");
   }
}
