/*
 * Copyright (C) 2010 The Android Open Source Project
 *
 * Licensed 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 android.net.sip;

import gov.nist.javax.sdp.SessionDescriptionImpl;
import gov.nist.javax.sdp.fields.AttributeField;
import gov.nist.javax.sdp.fields.ConnectionField;
import gov.nist.javax.sdp.fields.MediaField;
import gov.nist.javax.sdp.fields.OriginField;
import gov.nist.javax.sdp.fields.ProtoVersionField;
import gov.nist.javax.sdp.fields.SessionNameField;
import gov.nist.javax.sdp.fields.TimeField;
import gov.nist.javax.sdp.parser.SDPAnnounceParser;

import android.util.Log;

import java.text.ParseException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Vector;
import javax.sdp.Connection;
import javax.sdp.MediaDescription;
import javax.sdp.SdpException;

/**
 * A session description that follows SDP (Session Description Protocol).
 * Refer to <a href="http://tools.ietf.org/html/rfc4566">RFC 4566</a>.
 * @hide
 */
public class SdpSessionDescription extends SessionDescription {
    private static final String TAG = "SDP";
    private static final String AUDIO = "audio";
    private static final String RTPMAP = "rtpmap";
    private static final String PTIME = "ptime";
    private static final String SENDONLY = "sendonly";
    private static final String RECVONLY = "recvonly";
    private static final String INACTIVE = "inactive";

    private SessionDescriptionImpl mSessionDescription;

    /**
     * The audio codec information parsed from "rtpmap".
     */
    public static class AudioCodec {
        public final int payloadType;
        public final String name;
        public final int sampleRate;
        public final int sampleCount;

        public AudioCodec(int payloadType, String name, int sampleRate,
                int sampleCount) {
            this.payloadType = payloadType;
            this.name = name;
            this.sampleRate = sampleRate;
            this.sampleCount = sampleCount;
        }
    }

    /**
     * The builder class used to create an {@link SdpSessionDescription} object.
     */
    public static class Builder {
        private SdpSessionDescription mSdp = new SdpSessionDescription();
        private SessionDescriptionImpl mSessionDescription;

        public Builder(String sessionName) throws SdpException {
            mSessionDescription = new SessionDescriptionImpl();
            mSdp.mSessionDescription = mSessionDescription;
            try {
                ProtoVersionField proto = new ProtoVersionField();
                proto.setVersion(0);
                mSessionDescription.addField(proto);

                TimeField time = new TimeField();
                time.setZero();
                mSessionDescription.addField(time);

                SessionNameField session = new SessionNameField();
                session.setValue(sessionName);
                mSessionDescription.addField(session);
            } catch (Exception e) {
                throwSdpException(e);
            }
        }

        public Builder setConnectionInfo(String networkType, String addressType,
                String addr) throws SdpException {
            try {
                ConnectionField connection = new ConnectionField();
                connection.setNetworkType(networkType);
                connection.setAddressType(addressType);
                connection.setAddress(addr);
                mSessionDescription.addField(connection);
            } catch (Exception e) {
                throwSdpException(e);
            }
            return this;
        }

        public Builder setOrigin(SipProfile user, long sessionId,
                long sessionVersion, String networkType, String addressType,
                String address) throws SdpException {
            try {
                OriginField origin = new OriginField();
                origin.setUsername(user.getUserName());
                origin.setSessionId(sessionId);
                origin.setSessionVersion(sessionVersion);
                origin.setAddressType(addressType);
                origin.setNetworkType(networkType);
                origin.setAddress(address);
                mSessionDescription.addField(origin);
            } catch (Exception e) {
                throwSdpException(e);
            }
            return this;
        }

        public Builder addMedia(String media, int port, int numPorts,
                String transport, Integer... types) throws SdpException {
            MediaField field = new MediaField();
            Vector<Integer> typeVector = new Vector<Integer>();
            Collections.addAll(typeVector, types);
            try {
                field.setMediaType(media);
                field.setMediaPort(port);
                field.setPortCount(numPorts);
                field.setProtocol(transport);
                field.setMediaFormats(typeVector);
                mSessionDescription.addField(field);
            } catch (Exception e) {
                throwSdpException(e);
            }
           return this;
        }

        public Builder addMediaAttribute(String type, String name, String value)
                throws SdpException {
            try {
                MediaDescription md = mSdp.getMediaDescription(type);
                if (md == null) {
                    throw new SdpException("Should add media first!");
                }
                AttributeField attribute = new AttributeField();
                attribute.setName(name);
                attribute.setValueAllowNull(value);
                mSessionDescription.addField(attribute);
            } catch (Exception e) {
                throwSdpException(e);
            }
            return this;
        }

        public Builder addSessionAttribute(String name, String value)
                throws SdpException {
            try {
                AttributeField attribute = new AttributeField();
                attribute.setName(name);
                attribute.setValueAllowNull(value);
                mSessionDescription.addField(attribute);
            } catch (Exception e) {
                throwSdpException(e);
            }
            return this;
        }

        private void throwSdpException(Exception e) throws SdpException {
            if (e instanceof SdpException) {
                throw (SdpException) e;
            } else {
                throw new SdpException(e.toString(), e);
            }
        }

        public String build() {
            return mSdp.toString();
        }
    }

    private SdpSessionDescription() {
    }

    /**
     * Constructor.
     *
     * @param sdpString an SDP session description to parse
     */
    public SdpSessionDescription(String sdpString) throws SdpException {
        try {
            mSessionDescription = new SDPAnnounceParser(sdpString).parse();
        } catch (ParseException e) {
            throw new SdpException(e.toString(), e);
        }
        verify();
    }

    /**
     * Constructor.
     *
     * @param content a raw SDP session description to parse
     */
    public SdpSessionDescription(byte[] content) throws SdpException {
        this(new String(content));
    }

    private void verify() throws SdpException {
        // make sure the syntax is correct over the fields we're interested in
        Vector<MediaDescription> descriptions = (Vector<MediaDescription>)
                mSessionDescription.getMediaDescriptions(false);
        for (MediaDescription md : descriptions) {
            md.getMedia().getMediaPort();
            Connection connection = md.getConnection();
            if (connection != null) connection.getAddress();
            md.getMedia().getFormats();
        }
        Connection connection = mSessionDescription.getConnection();
        if (connection != null) connection.getAddress();
    }

    /**
     * Gets the connection address of the media.
     *
     * @param type the media type; e.g., "AUDIO"
     * @return the media connection address of the peer
     */
    public String getPeerMediaAddress(String type) {
        try {
            MediaDescription md = getMediaDescription(type);
            Connection connection = md.getConnection();
            if (connection == null) {
                connection = mSessionDescription.getConnection();
            }
            return ((connection == null) ? null : connection.getAddress());
        } catch (SdpException e) {
            // should not occur
            return null;
        }
    }

    /**
     * Gets the connection port number of the media.
     *
     * @param type the media type; e.g., "AUDIO"
     * @return the media connection port number of the peer
     */
    public int getPeerMediaPort(String type) {
        try {
            MediaDescription md = getMediaDescription(type);
            return md.getMedia().getMediaPort();
        } catch (SdpException e) {
            // should not occur
            return -1;
        }
    }

    private boolean containsAttribute(String type, String name) {
        if (name == null) return false;
        MediaDescription md = getMediaDescription(type);
        Vector<AttributeField> v = (Vector<AttributeField>)
                md.getAttributeFields();
        for (AttributeField field : v) {
            if (name.equals(field.getAttribute().getName())) return true;
        }
        return false;
    }

    /**
     * Checks if the media is "sendonly".
     *
     * @param type the media type; e.g., "AUDIO"
     * @return true if the media is "sendonly"
     */
    public boolean isSendOnly(String type) {
        boolean answer = containsAttribute(type, SENDONLY);
        Log.d(TAG, "   sendonly? " + answer);
        return answer;
    }

    /**
     * Checks if the media is "recvonly".
     *
     * @param type the media type; e.g., "AUDIO"
     * @return true if the media is "recvonly"
     */
    public boolean isReceiveOnly(String type) {
        boolean answer = containsAttribute(type, RECVONLY);
        Log.d(TAG, "   recvonly? " + answer);
        return answer;
    }

    /**
     * Checks if the media is in sending; i.e., not "recvonly" and not
     * "inactive".
     *
     * @param type the media type; e.g., "AUDIO"
     * @return true if the media is sending
     */
    public boolean isSending(String type) {
        boolean answer = !containsAttribute(type, RECVONLY)
                && !containsAttribute(type, INACTIVE);

        Log.d(TAG, "   sending? " + answer);
        return answer;
    }

    /**
     * Checks if the media is in receiving; i.e., not "sendonly" and not
     * "inactive".
     *
     * @param type the media type; e.g., "AUDIO"
     * @return true if the media is receiving
     */
    public boolean isReceiving(String type) {
        boolean answer = !containsAttribute(type, SENDONLY)
                && !containsAttribute(type, INACTIVE);
        Log.d(TAG, "   receiving? " + answer);
        return answer;
    }

    private AudioCodec parseAudioCodec(String rtpmap, int ptime) {
        String[] ss = rtpmap.split(" ");
        int payloadType = Integer.parseInt(ss[0]);

        ss = ss[1].split("/");
        String name = ss[0];
        int sampleRate = Integer.parseInt(ss[1]);
        int channelCount = 1;
        if (ss.length > 2) channelCount = Integer.parseInt(ss[2]);
        int sampleCount = sampleRate / (1000 / ptime) * channelCount;
        return new AudioCodec(payloadType, name, sampleRate, sampleCount);
    }

    /**
     * Gets the list of audio codecs in this session description.
     *
     * @return the list of audio codecs in this session description
     */
    public List<AudioCodec> getAudioCodecs() {
        MediaDescription md = getMediaDescription(AUDIO);
        if (md == null) return new ArrayList<AudioCodec>();

        // FIXME: what happens if ptime is missing
        int ptime = 20;
        try {
            String value = md.getAttribute(PTIME);
            if (value != null) ptime = Integer.parseInt(value);
        } catch (Throwable t) {
            Log.w(TAG, "getCodecs(): ignored: " + t);
        }

        List<AudioCodec> codecs = new ArrayList<AudioCodec>();
        Vector<AttributeField> v = (Vector<AttributeField>)
                md.getAttributeFields();
        for (AttributeField field : v) {
            try {
                if (RTPMAP.equals(field.getName())) {
                    AudioCodec codec = parseAudioCodec(field.getValue(), ptime);
                    if (codec != null) codecs.add(codec);
                }
            } catch (Throwable t) {
                Log.w(TAG, "getCodecs(): ignored: " + t);
            }
        }
        return codecs;
    }

    /**
     * Gets the media description of the specified type.
     *
     * @param type the media type; e.g., "AUDIO"
     * @return the media description of the specified type
     */
    public MediaDescription getMediaDescription(String type) {
        MediaDescription[] all = getMediaDescriptions();
        if ((all == null) || (all.length == 0)) return null;
        for (MediaDescription md : all) {
            String t = md.getMedia().getMedia();
            if (t.equalsIgnoreCase(type)) return md;
        }
        return null;
    }

    /**
     * Gets all the media descriptions in this session description.
     *
     * @return all the media descriptions in this session description
     */
    public MediaDescription[] getMediaDescriptions() {
        try {
            Vector<MediaDescription> descriptions = (Vector<MediaDescription>)
                    mSessionDescription.getMediaDescriptions(false);
            MediaDescription[] all = new MediaDescription[descriptions.size()];
            return descriptions.toArray(all);
        } catch (SdpException e) {
            Log.e(TAG, "getMediaDescriptions", e);
        }
        return null;
    }

    @Override
    public String getType() {
        return "sdp";
    }

    @Override
    public byte[] getContent() {
          return mSessionDescription.toString().getBytes();
    }

    @Override
    public String toString() {
        return mSessionDescription.toString();
    }
}
