package javax.sdp;

import gov.nist.javax.sdp.*;
import gov.nist.javax.sdp.fields.*;
import gov.nist.javax.sdp.parser.*;

import java.util.*;
import java.net.*;
import java.text.*;

/**
 * The SdpFactory enables applications to encode and decode SDP messages.
 * The SdpFactory can be used to construct a SessionDescription
 * object programmatically.
 * The SdpFactory can also be used to construct a
 * SessionDescription based on the
 * contents of a String. Acknowledgement:
 * Bugs reported by Brian J. Collins <bjcollins@rockwellcollins.com>.
 * and by  Majdi Abuelbassal <majdi.abuelbassal@bbumail.com>.
 * Please refer to IETF RFC 2327 for a description of SDP.
 *
 *@author Olivier Deruelle <olivier.deruelle@nist.gov>
 *@author M. Ranganathan 
 *
 *
 * @version 1.0
 *
 */
public class SdpFactory extends Object
{
  private static final SdpFactory singletonInstance = new SdpFactory();

  /** Creates new SdpFactory */
  private SdpFactory()
  {
  }

  /** Obtain an instance of an SdpFactory.
   *
   *     This static method returns a factory instance.
   *
   *     Once an application has obtained a reference to an SdpFactory it can use
   *     the factory to configure and obtain parser instances and to create SDP objects.
   * @return a factory instance
   */
  public static SdpFactory getInstance()
  {
    return singletonInstance;
  }

  /** Creates a new, empty SessionDescription. The session is set as follows:
   *
   *     v=0
   *
   *     o=this.createOrigin ("user",
   *     InetAddress.getLocalHost().toString());
   *
   *     s=-
   *
   *     t=0 0
   * @throws SdpException SdpException, - if there is a problem constructing the
   *          SessionDescription.
   * @return a new, empty SessionDescription.
   */
  public SessionDescription createSessionDescription()
      throws SdpException
  {
    SessionDescriptionImpl
        sessionDescriptionImpl = new SessionDescriptionImpl();

    ProtoVersionField ProtoVersionField = new ProtoVersionField();
    ProtoVersionField.setVersion(0);
    sessionDescriptionImpl.setVersion(ProtoVersionField);

    OriginField originImpl = null;
    try
    {
      originImpl = (OriginField) this.createOrigin("user",
                                                   InetAddress.getLocalHost().getHostAddress());
    }
    catch (UnknownHostException e)
    {
      e.printStackTrace();
    }
    sessionDescriptionImpl.setOrigin(originImpl);

    SessionNameField sessionNameImpl = new SessionNameField();
    sessionNameImpl.setValue("-");
    sessionDescriptionImpl.setSessionName(sessionNameImpl);

    TimeDescriptionImpl timeDescriptionImpl = new TimeDescriptionImpl();
    TimeField timeImpl = new TimeField();
    timeImpl.setZero();
    timeDescriptionImpl.setTime(timeImpl);
    Vector times = new Vector();
    times.addElement(timeDescriptionImpl);
    sessionDescriptionImpl.setTimeDescriptions(times);

    return sessionDescriptionImpl;
  }

  /** Creates a SessionDescription populated with the information
   *     contained within the string parameter.
   *
   *     Note: unknown field types should not cause exceptions.
   * @param s s - the sdp message that is to be parsed.
   * @throws SdpParseException SdpParseException - if there is a problem parsing the
   *          String.
   * @return a populated SessionDescription object.
   */
  public SessionDescription createSessionDescription(String s)
      throws SdpParseException
  {
    try
    {

      SDPAnnounceParser sdpParser = new SDPAnnounceParser(s);
      return sdpParser.parse();
    }
    catch (ParseException e)
    {
      e.printStackTrace();
      throw new SdpParseException(0, 0, "Could not parse message");
    }
  }

  /** Returns Bandwidth object with the specified values.
   * @param modifier modifier - the bandwidth type
   * @param value the bandwidth value measured in kilobits per second
   * @return bandwidth
   */
  public BandWidth createBandwidth(String modifier,
                                   int value)
  {
    BandwidthField bandWidthImpl = new BandwidthField();
    try
    {

      bandWidthImpl.setType(modifier);
      bandWidthImpl.setValue(value);

    }
    catch (SdpException s)
    {
      s.printStackTrace();
    }
    return bandWidthImpl;
  }

  /** Returns Attribute object with the specified values.
   * @param name the namee of the attribute
   * @param value the value of the attribute
   * @return Attribute
   */
  public Attribute createAttribute(String name,
                                   String value)
  {
    AttributeField attributeImpl = new AttributeField();
    try
    {

      attributeImpl.setName(name);
      attributeImpl.setValueAllowNull(value);

    }
    catch (SdpException s)
    {
      s.printStackTrace();
    }
    return attributeImpl;
  }

  /** Returns Info object with the specified value.
   * @param value the string containing the description.
   * @return Info
   */
  public Info createInfo(String value)
  {
    InformationField infoImpl = new InformationField();
    try
    {

      infoImpl.setValue(value);

    }
    catch (SdpException s)
    {
      s.printStackTrace();
    }
    return infoImpl;
  }

  /** Returns Phone object with the specified value.
   * @param value the string containing the description.
   * @return Phone
   */
  public Phone createPhone(String value)
  {
    PhoneField phoneImpl = new PhoneField();
    try
    {

      phoneImpl.setValue(value);

    }
    catch (SdpException s)
    {
      s.printStackTrace();
    }
    return phoneImpl;
  }

  /** Returns EMail object with the specified value.
   * @param value the string containing the description.
   * @return EMail
   */
  public EMail createEMail(String value)
  {
    EmailField emailImpl = new EmailField();
    try
    {

      emailImpl.setValue(value);

    }
    catch (SdpException s)
    {
      s.printStackTrace();
    }
    return emailImpl;
  }

  /** Returns URI object with the specified value.
   * @param value the URL containing the description.
   * @throws SdpException
   * @return URI
   */
  public javax.sdp.URI createURI(URL value)
      throws SdpException
  {

    URIField uriImpl = new URIField();
    uriImpl.set(value);
    return uriImpl;

  }

  /** Returns SessionName object with the specified name.
   * @param name the string containing the name of the session.
   * @return SessionName
   */
  public SessionName createSessionName(String name)
  {
    SessionNameField sessionNameImpl = new SessionNameField();
    try
    {

      sessionNameImpl.setValue(name);

    }
    catch (SdpException s)
    {
      s.printStackTrace();
    }
    return sessionNameImpl;
  }

  /** Returns Key object with the specified value.
   * @param method the string containing the method type.
   * @param key the key to set
   * @return Key
   */
  public Key createKey(String method,
                       String key)
  {
    KeyField keyImpl = new KeyField();
    try
    {

      keyImpl.setMethod(method);
      keyImpl.setKey(key);

    }
    catch (SdpException s)
    {
      s.printStackTrace();
      return null;
    }
    return keyImpl;
  }

  /** Returns Version object with the specified values.
   * @param value the version number.
   * @return Version
   */
  public Version createVersion(int value)
  {
    ProtoVersionField protoVersionField = new ProtoVersionField();
    try
    {

      protoVersionField.setVersion(value);

    }
    catch (SdpException s)
    {
      s.printStackTrace();
      return null;
    }
    return protoVersionField;
  }

  /** Returns Media object with the specified properties.
   * @param media the media type, eg "audio"
   * @param port port number on which to receive media
   * @param numPorts number of ports used for this media stream
   * @param transport transport type, eg "RTP/AVP"
   * @param staticRtpAvpTypes vector to set
   * @throws SdpException
   * @return Media
   */
  public Media createMedia(String media,
                           int port,
                           int numPorts,
                           String transport,
                           Vector staticRtpAvpTypes)
      throws SdpException
  {
    MediaField mediaImpl = new MediaField();
    mediaImpl.setMediaType(media);
    mediaImpl.setMediaPort(port);
    mediaImpl.setPortCount(numPorts);
    mediaImpl.setProtocol(transport);
    mediaImpl.setMediaFormats(staticRtpAvpTypes);
    return mediaImpl;
  }

  /** Returns Origin object with the specified properties.
   * @param userName the user name.
   * @param address the IP4 encoded address.
   * @throws SdpException if the parameters are null
   * @return Origin
   */
  public Origin createOrigin(String userName,
                             String address)
      throws SdpException
  {
    OriginField originImpl = new OriginField();
    originImpl.setUsername(userName);
    originImpl.setAddress(address);
    originImpl.setNetworkType(SDPKeywords.IN);
    originImpl.setAddressType(SDPKeywords.IPV4);
    return originImpl;
  }

  /** Returns Origin object with the specified properties.
   * @param userName String containing the user that created the
   *          string.
   * @param sessionId long containing the session identifier.
   * @param sessionVersion long containing the session version.
   * @param networkType String network type for the origin (usually
   *          "IN").
   * @param addrType String address type (usually "IP4").
   * @param address String IP address usually the address of the
   *          host.
   * @throws SdpException if the parameters are null
   * @return Origin object with the specified properties.
   */
  public Origin createOrigin(String userName,
                             long sessionId,
                             long sessionVersion,
                             String networkType,
                             String addrType,
                             String address)
      throws SdpException
  {
    OriginField originImpl = new OriginField();
    originImpl.setUsername(userName);
    originImpl.setAddress(address);
    originImpl.setSessionId(sessionId);
    originImpl.setSessionVersion(sessionVersion);
    originImpl.setAddressType(addrType);
    originImpl.setNetworkType(networkType);
    return originImpl;
  }

  /** Returns MediaDescription object with the specified properties.
   *     The returned object will respond to
   *     Media.getMediaFormats(boolean) with a Vector of media formats.
   * @param media media -
   * @param port port number on which to receive media
   * @param numPorts number of ports used for this media stream
   * @param transport transport type, eg "RTP/AVP"
   * @param staticRtpAvpTypes list of static RTP/AVP media payload
   *          types which should be specified by the returned MediaDescription
   *   throws IllegalArgumentException if passed
   *          an invalid RTP/AVP payload type
   * @throws IllegalArgumentException
   * @throws SdpException
   * @return MediaDescription
   */
  public MediaDescription createMediaDescription(String media,
                                                 int port,
                                                 int numPorts,
                                                 String transport,
                                                 int[] staticRtpAvpTypes)
      throws IllegalArgumentException,
      SdpException
  {
    MediaDescriptionImpl
        mediaDescriptionImpl = new MediaDescriptionImpl();
    MediaField mediaImpl = new MediaField();
    mediaImpl.setMediaType(media);
    mediaImpl.setMediaPort(port);
    mediaImpl.setPortCount(numPorts);
    mediaImpl.setProtocol(transport);
    mediaDescriptionImpl.setMedia(mediaImpl);
    // Bug fix contributed by Paloma Ortega.
    Vector payload = new Vector();
    for (int i = 0; i < staticRtpAvpTypes.length; i++)
      payload.add(new Integer(staticRtpAvpTypes[i]).toString());
    mediaImpl.setMediaFormats(payload);
    return mediaDescriptionImpl;
  }

  /** Returns MediaDescription object with the specified properties.
   *     The returned object will respond to
   *     Media.getMediaFormats(boolean) with a Vector of String objects
   *     specified by the 'formats argument.
   * @param media the media type, eg "audio"
   * @param port port number on which to receive media
   * @param numPorts number of ports used for this media stream
   * @param transport transport type, eg "RTP/AVP"
   * @param formats list of formats which should be specified by the
   *          returned MediaDescription
   * @return MediaDescription
   */
  public MediaDescription createMediaDescription(String media,
                                                 int port,
                                                 int numPorts,
                                                 String transport,
                                                 String[] formats)
  {
    MediaDescriptionImpl mediaDescriptionImpl = new MediaDescriptionImpl();
    try
    {

      MediaField mediaImpl = new MediaField();
      mediaImpl.setMediaType(media);
      mediaImpl.setMediaPort(port);
      mediaImpl.setPortCount(numPorts);
      mediaImpl.setProtocol(transport);

      Vector formatsV = new Vector(formats.length);
      for (int i = 0; i < formats.length; i++)
        formatsV.add(formats[i]);
      mediaImpl.setMediaFormats(formatsV);
      mediaDescriptionImpl.setMedia(mediaImpl);
    }
    catch (SdpException s)
    {
      s.printStackTrace();
    }
    return mediaDescriptionImpl;
  }

  /** Returns TimeDescription object with the specified properties.
   * @param t the Time that the time description applies to. Returns
   *          TimeDescription object with the specified properties.
   * @throws SdpException
   * @return TimeDescription
   */
  public TimeDescription createTimeDescription(Time t)
      throws SdpException
  {
    TimeDescriptionImpl timeDescriptionImpl = new TimeDescriptionImpl();
    timeDescriptionImpl.setTime(t);
    return timeDescriptionImpl;
  }

  /** Returns TimeDescription unbounded (i.e. "t=0 0");
   * @throws SdpException
   * @return TimeDescription unbounded (i.e. "t=0 0");
   */
  public TimeDescription createTimeDescription()
      throws SdpException
  {
    TimeDescriptionImpl timeDescriptionImpl = new TimeDescriptionImpl();
    TimeField timeImpl = new TimeField();
    timeImpl.setZero();
    timeDescriptionImpl.setTime(timeImpl);
    return timeDescriptionImpl;
  }

  /** Returns TimeDescription object with the specified properties.
   * @param start start time.
   * @param stop stop time.
   * @throws SdpException if the parameters are null
   * @return TimeDescription
   */
  public TimeDescription createTimeDescription(Date start,
                                               Date stop)
      throws SdpException
  {
    TimeDescriptionImpl timeDescriptionImpl = new TimeDescriptionImpl();
    TimeField timeImpl = new TimeField();
    timeImpl.setStart(start);
    timeImpl.setStop(stop);
    timeDescriptionImpl.setTime(timeImpl);
    return timeDescriptionImpl;
  }

  /** Returns a String containing the computed form for a
   *   multi-connection address.
   *  Parameters:
   *     addr - connection address
   *    ttl - time to live (TTL) for multicast
   *     addresses
   *     numAddrs - number of addresses used by the
   *    connection
   * Returns:
   *     a String containing the computed form for a
   *     multi-connection address.
   */
  public String formatMulticastAddress(String addr,
                                       int ttl,
                                       int numAddrs)
  {
    String res = addr + "/" + ttl + "/" + numAddrs;
    return res;
  }

  /** Returns a Connection object with the specified properties a
   * @param netType network type, eg "IN" for "Internet"
   * @param addrType address type, eg "IP4" for IPv4 type addresses
   * @param addr connection address
   * @param ttl time to live (TTL) for multicast addresses
   * @param numAddrs number of addresses used by the connection
   * @return Connection
   */
  public Connection createConnection(String netType,
                                     String addrType,
                                     String addr,
                                     int ttl,
                                     int numAddrs) throws SdpException
  {
    ConnectionField connectionImpl = new ConnectionField();

    connectionImpl.setNetworkType(netType);
    connectionImpl.setAddressType(addrType);
    connectionImpl.setAddress(addr);

    return connectionImpl;
  }

  /** Returns a Connection object with the specified properties and no
   *     TTL and a default number of addresses (1).
   * @param netType network type, eg "IN" for "Internet"
   * @param addrType address type, eg "IP4" for IPv4 type addresses
   * @param addr connection address
   * @throws SdpException if the parameters are null
   * @return Connection
   */
  public Connection createConnection(String netType,
                                     String addrType,
                                     String addr)
      throws SdpException
  {
    ConnectionField connectionImpl = new ConnectionField();

    connectionImpl.setNetworkType(netType);
    connectionImpl.setAddressType(addrType);
    connectionImpl.setAddress(addr);

    return connectionImpl;
  }

  /** Returns a Connection object with the specified properties and a
   *     network and address type of "IN" and "IP4" respectively.
   * @param addr connection address
   * @param ttl time to live (TTL) for multicast addresses
   * @param numAddrs number of addresses used by the connection
   * @return Connection
   */
  public Connection createConnection(String addr,
                                     int ttl,
                                     int numAddrs) throws SdpException
  {
    ConnectionField connectionImpl = new ConnectionField();

    connectionImpl.setAddress(addr);

    return connectionImpl;
  }

  /** Returns a Connection object with the specified address. This is
   *     equivalent to
   *
   *        createConnection("IN", "IP4", addr);
   *
   * @param addr connection address
   * @throws SdpException if the parameter is null
   * @return Connection
   */
  public Connection createConnection(String addr)
      throws SdpException
  {

     return createConnection("IN", "IP4", addr);

  }

  /** Returns a Time specification with the specified start and stop
   *     times.
   * @param start start time
   * @param stop stop time
   * @throws SdpException if the parameters are null
   * @return a Time specification with the specified start and stop
   *          times.
   */
  public Time createTime(Date start,
                         Date stop)
      throws SdpException
  {
    TimeField timeImpl = new TimeField();
    timeImpl.setStart(start);
    timeImpl.setStop(stop);
    return timeImpl;
  }

  /** Returns an unbounded Time specification (i.e., "t=0 0").
   * @throws SdpException
   * @return an unbounded Time specification (i.e., "t=0 0").
   */
  public Time createTime()
      throws SdpException
  {
    TimeField timeImpl = new TimeField();
    timeImpl.setZero();
    return timeImpl;
  }

  /** Returns a RepeatTime object with the specified interval,
   *     duration, and time offsets.
   * @param repeatInterval the "repeat interval" in seconds
   * @param activeDuration the "active duration" in seconds
   * @param offsets  the list of offsets relative to the start time of
   *          the Time object with which the returned RepeatTime will be
   *          associated
   * @return RepeatTime
   */
  public RepeatTime createRepeatTime(int repeatInterval,
                                     int activeDuration,
                                     int[] offsets)
  {
    RepeatField repeatTimeField = new RepeatField();
    try
    {

      repeatTimeField.setRepeatInterval(repeatInterval);
      repeatTimeField.setActiveDuration(activeDuration);
      repeatTimeField.setOffsetArray(offsets);

    }
    catch (SdpException s)
    {
      s.printStackTrace();
    }
    return repeatTimeField;
  }

  /** Constructs a timezone adjustment record.
   * @param d the Date at which the adjustment is going to take
   *          place.
   * @param offset the adjustment in number of seconds relative to
   *          the start time of the SessionDescription with which this
   *          object is associated.
   * @return TimeZoneAdjustment
   */
  public TimeZoneAdjustment createTimeZoneAdjustment(Date d,
                                                     int offset)
  {
    ZoneField timeZoneAdjustmentImpl = new ZoneField();
    try
    {

      Hashtable map = new Hashtable();
      map.put(d, new Integer(offset));
      timeZoneAdjustmentImpl.setZoneAdjustments(map);
    }
    catch (SdpException s)
    {
      s.printStackTrace();
    }
    return timeZoneAdjustmentImpl;
  }

  /**
   *     Test main.
   * @param args
   * @throws SdpException
   public static void main(String[] args)
   throws SdpException {

   }
   */

  /**
   * @param ntpTime long to set
   * @return Returns a Date object for a given NTP date value.
   */
  public static Date getDateFromNtp(long ntpTime)
  {
    return new Date((ntpTime - SdpConstants.NTP_CONST) * 1000);
  }

  /** Returns a long containing the NTP value for a given Java Date.
   * @param d Date to set
   * @return long
   */
  public static long getNtpTime(Date d) throws SdpParseException
  {
    if (d == null) return -1;
    return ((d.getTime() / 1000) + SdpConstants.NTP_CONST);
  }

  /**
   public static void main(String[] args)
   throws SdpParseException , SdpException {
   String sdpFields = "v=0\r\n"+
   "o=phone 1057165447541 1057165447541 IN IP4 123.4.566.156\r\n"+
   "s=-\r\n" +
   "c=IN IP4 123.4.566.156\r\n"+
   "t=0 0\r\n" +
   "m=data 3011 tcp OSA\r\n";

   SdpFactory sdpFactory = new SdpFactory();
   SessionDescription sessionDescription =
   sdpFactory.createSessionDescription(sdpFields);

   System.out.println("sessionDescription = " + sessionDescription);
   Vector mediaDescriptions =
   sessionDescription.getMediaDescriptions(false);
   for (int i = 0; i < mediaDescriptions.size(); i++) {
   MediaDescription m = (MediaDescription)
   mediaDescriptions.elementAt(i);
   System.out.println("m = " + m.toString());
   Media media = m.getMedia();
   Vector formats = media.getMediaFormats(false);
   System.out.println("formats = " + formats);
   }
   }
   **/

}