package org.jacorb.orb.miop;

import java.io.ByteArrayOutputStream;
import org.jacorb.config.Configurable;
import org.jacorb.config.Configuration;
import org.jacorb.config.ConfigurationException;
import org.omg.MIOP.PacketHeader_1_0;


/**
 * A collection of fragments of a giop message.
 *
 * @author Alysson Neves Bessani
 * @version 1.0
 * @see ServerMIOPConnection
 */
public class FragmentedMessage implements Configurable
{
   public static final int NUMBER_OF_FRAGMENTS = 4;
   public static final int FRAGMENT_INCREMENT  = 2;

   private final long      creationTime        = System.currentTimeMillis ();
   private int             numberOfFragments   = NUMBER_OF_FRAGMENTS;
   private byte[][]        fragments;
   private int             lastPacketArrived   = -1;
   private int             maxPacketArrived    = -1;
   private int             packetsReceived     = 0;
   private boolean         end_flag_dependent  = false;
   private boolean         end_flag_received   = false;

   /** message's configuration parameters */
   private int completionTimeout;
   private short packetDataMaxSize;

   /**
    * Tests if this message can be discarded or not.
    *
    * @return true if it can be discarded.
    */
   boolean canBeDiscarded ()
   {
      return (end_flag_received && !isComplete ())
          || (System.currentTimeMillis () - creationTime > numberOfFragments * completionTimeout);
   }


   /**
    * If all the fragments arrived then the GIOP message can be build. Only called
    * from ServerMIOPConnection is message.isComplete.
    *
    * @return byte[] of a GIOP message.
    */
   byte[] buildMessage ()
   {
      ByteArrayOutputStream out =
          new ByteArrayOutputStream (numberOfFragments * packetDataMaxSize);

      for (int i = 0; i <= maxPacketArrived; i++)
      {
         out.write (fragments[i], 0, fragments[i].length);
      }

      return out.toByteArray ();
   }


   /**
    * It verifies if there is some available position on the packet buffer.
    * Even if the last fragment arrived, there can be some packets that were
    * not delivered yet due to the non FIFO guarantees (of UDP),
    * So, this method verify if all fragments of the message were received.
    *
    * @return true if it contains all message fragments.
    */
   boolean isComplete ()
   {
      if (end_flag_dependent)
      {
         if (end_flag_received)
         {
            for (int i = 0; i < maxPacketArrived; i++)
            {
               if (fragments[i] == null)
               {
                  return false;
               }
            }
            return true;
         }
         else
         {
            return false;
         }
      }
      else
      {
         return packetsReceived == numberOfFragments;
      }
   }


   /**
    * Adds a fragment.
    *
    * @param header the fragment header.
    * @param fragment the fragment data.
    */
   synchronized void addFragment (PacketHeader_1_0 header, byte[] fragment)
   {
      if (fragments == null)
      {
         // Run only on arrival of the first package

         // Get the number of packets that must wait
         // If this number is zero, I have to wait for the flag
         if (header.number_of_packets > 0)
         {
            numberOfFragments = header.number_of_packets;
         }
         else
         {
            end_flag_dependent = true;
         }

         // create the array of fragments
         fragments = new byte[numberOfFragments][];
      }

      // put the fragment in the proper position
      lastPacketArrived = header.packet_number;

      if (lastPacketArrived > maxPacketArrived)
      {
         maxPacketArrived = lastPacketArrived;

         if (maxPacketArrived > fragments.length)
         {
            // the array beyond its size ... we will increase them.
            // Please note that this will never happen if the field
            // number_of_packets were properly completed
            byte[] oldFragments[] = fragments;
            numberOfFragments += FRAGMENT_INCREMENT;
            fragments = new byte[numberOfFragments][];
            System.arraycopy (oldFragments, 0, fragments, 0, fragments.length);
         }
      }

      // Insert here
      if (fragments[lastPacketArrived] == null)
      {
         fragments[lastPacketArrived] = fragment;
         packetsReceived++;
      }

      if (end_flag_dependent)
      {
         end_flag_received = ((header.flags & MulticastUtil.STOP_FLAG) == MulticastUtil.STOP_FLAG);
      }
   }


   public void configure (Configuration config) throws ConfigurationException
   {
       completionTimeout = config.getAttributeAsInteger ("jacorb.miop.message_completion_timeout",
               MulticastUtil.MESSAGE_COMPLETION_TIMEOUT);
       int packetMax = config.getAttributeAsInteger ("jacorb.miop.packet_max_size",
                                                             MulticastUtil.PACKET_MAX_SIZE);
       packetDataMaxSize = (short)(packetMax - MulticastUtil.PACKET_DATA_MAX_SIZE);
   }
}
