/**
 * $RCSfile: JingleTransport.java,v $
 * $Revision: 1.1 $
 * $Date: 2007/07/02 17:41:08 $
 *
 * Copyright 2003-2005 Jive Software.
 *
 * All rights reserved. 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 org.jivesoftware.smackx.packet;

import org.jivesoftware.smack.packet.PacketExtension;
import org.jivesoftware.smackx.jingle.nat.ICECandidate;
import org.jivesoftware.smackx.jingle.nat.TransportCandidate;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;

/**
 * A jingle transport extension
 *
 * @author Alvaro Saurin <alvaro.saurin@gmail.com>
 */
public class JingleTransport implements PacketExtension {

    // static

    public static final String NODENAME = "transport";

    // non-static

    protected String namespace;

    protected final List<JingleTransportCandidate> candidates = new ArrayList<JingleTransportCandidate>();

    /**
     * Default constructor.
     */
    public JingleTransport() {
        super();
    }

    /**
     * Utility constructor, with a transport candidate element.
     *
     * @param candidate A transport candidate element to add.
     */
    public JingleTransport(final JingleTransportCandidate candidate) {
        super();
        addCandidate(candidate);
    }

    /**
     * Copy constructor.
     *
     * @param tr the other jingle transport.
     */
    public JingleTransport(final JingleTransport tr) {
        if (tr != null) {
            namespace = tr.namespace;

            if (tr.candidates.size() > 0) {
                candidates.addAll(tr.candidates);
            }
        }
    }

    /**
     * Adds a transport candidate.
     *
     * @param candidate the candidate
     */
    public void addCandidate(final JingleTransportCandidate candidate) {
        if (candidate != null) {
            synchronized (candidates) {
                candidates.add(candidate);
            }
        }
    }

    /**
     * Get an iterator for the candidates
     *
     * @return an iterator
     */
    public Iterator getCandidates() {
        return Collections.unmodifiableList(getCandidatesList()).iterator();
    }

    /**
     * Get the list of candidates.
     *
     * @return The candidates list.
     */
    public List<JingleTransportCandidate> getCandidatesList() {
        ArrayList<JingleTransportCandidate> res = null;
        synchronized (candidates) {
            res = new ArrayList<JingleTransportCandidate>(candidates);
        }
        return res;
    }

    /**
     * Get the number of transport candidates.
     *
     * @return The number of transport candidates contained.
     */
    public int getCandidatesCount() {
        return getCandidatesList().size();
    }

    /**
     * Returns the XML element name of the element.
     *
     * @return the XML element name of the element.
     */
    public String getElementName() {
        return NODENAME;
    }

    /**
     * Set the namespace.
     *
     * @param ns The namespace
     */
    protected void setNamespace(final String ns) {
        namespace = ns;
    }

    /**
     * Get the namespace.
     *
     * @return The namespace
     */
    public String getNamespace() {
        return namespace;
    }

    /**
     * Return the XML representation for this element.
     */
    public String toXML() {
        StringBuilder buf = new StringBuilder();

        buf.append("<").append(getElementName()).append(" xmlns=\"");
        buf.append(getNamespace()).append("\" ");

        synchronized (candidates) {
            if (getCandidatesCount() > 0) {
                buf.append(">");
                Iterator iter = getCandidates();

                while (iter.hasNext()) {
                    JingleTransportCandidate candidate = (JingleTransportCandidate) iter
                            .next();
                    buf.append(candidate.toXML());
                }
                buf.append("</").append(getElementName()).append(">");
            } else {
                buf.append("/>");
            }
        }

        return buf.toString();
    }

    /**
     * Candidate element in the transport. This class acts as a view of the
     * "TransportCandidate" in the Jingle space.
     *
     * @author Alvaro Saurin
     * @see TransportCandidate
     */
    public static abstract class JingleTransportCandidate {

        public static final String NODENAME = "candidate";

        // The transport candidate contained in the element.
        protected TransportCandidate transportCandidate;

        /**
         * Creates a new TransportNegotiator child.
         */
        public JingleTransportCandidate() {
            super();
        }

        /**
         * Creates a new TransportNegotiator child.
         *
         * @param candidate the jmf transport candidate
         */
        public JingleTransportCandidate(final TransportCandidate candidate) {
            super();
            setMediaTransport(candidate);
        }

        /**
         * Returns the XML element name of the element.
         *
         * @return the XML element name of the element.
         */
        public static String getElementName() {
            return NODENAME;
        }

        /**
         * Get the current transportElement candidate.
         *
         * @return the transportElement candidate
         */
        public TransportCandidate getMediaTransport() {
            return transportCandidate;
        }

        /**
         * Set the transportElement candidate.
         *
         * @param cand the transportElement candidate
         */
        public void setMediaTransport(final TransportCandidate cand) {
            if (cand != null) {
                transportCandidate = cand;
            }
        }

        /**
         * Get the list of attributes.
         *
         * @return a string with the list of attributes.
         */
        protected String getChildElements() {
            return null;
        }

        /**
         * Obtain a valid XML representation of a trancport candidate
         *
         * @return A string containing the XML dump of the transport candidate.
         */
        public String toXML() {
            StringBuilder buf = new StringBuilder();
            String childElements = getChildElements();

            if (transportCandidate != null && childElements != null) {
                buf.append("<").append(getElementName()).append(" ");
                buf.append(childElements);
                buf.append("/>");
            }

            return buf.toString();
        }
    }

    // Subclasses

    /**
     * RTP-ICE profile
     */
    public static class Ice extends JingleTransport {
        public static final String NAMESPACE = "urn:xmpp:jingle:transports:ice-udp:1";

        public Ice() {
            super();
            setNamespace(NAMESPACE);
        }

        /**
         * Add a transport candidate
         *
         * @see org.jivesoftware.smackx.packet.JingleTransport#addCandidate(org.jivesoftware.smackx.packet.JingleTransport.JingleTransportCandidate)
         */
        public void addCandidate(final JingleTransportCandidate candidate) {
            super.addCandidate(candidate);
        }

        /**
         * Get the list of candidates. As a "raw-udp" transport can only contain
         * one candidate, we use the first in the list...
         *
         * @see org.jivesoftware.smackx.packet.JingleTransport#getCandidates()
         */
        public List<JingleTransportCandidate> getCandidatesList() {
            List<JingleTransportCandidate> copy = new ArrayList<JingleTransportCandidate>();
            List<JingleTransportCandidate> superCandidatesList = super.getCandidatesList();
            for (int i = 0; i < superCandidatesList.size(); i++) {
                copy.add(superCandidatesList.get(i));
            }

            return copy;
        }

        public static class Candidate extends JingleTransportCandidate {
            /**
             * Default constructor
             */
            public Candidate() {
                super();
            }

            /**
             * Constructor with a transport candidate.
             */
            public Candidate(final TransportCandidate tc) {
                super(tc);
            }

            /**
             * Get the elements of this candidate.
             */
            protected String getChildElements() {
                StringBuilder buf = new StringBuilder();

                if (transportCandidate != null) {// && transportCandidate instanceof ICECandidate) {
                    ICECandidate tci = (ICECandidate) transportCandidate;

                    // We convert the transportElement candidate to XML here...
                    buf.append(" generation=\"").append(tci.getGeneration()).append("\"");
                    buf.append(" ip=\"").append(tci.getIp()).append("\"");
                    buf.append(" port=\"").append(tci.getPort()).append("\"");
                    buf.append(" network=\"").append(tci.getNetwork()).append("\"");
                    buf.append(" username=\"").append(tci.getUsername()).append("\"");
                    buf.append(" password=\"").append(tci.getPassword()).append("\"");
                    buf.append(" preference=\"").append(tci.getPreference()).append("\"");
                    buf.append(" type=\"").append(tci.getType()).append("\"");

                    // Optional elements
                    if (transportCandidate.getName() != null) {
                        buf.append(" name=\"").append(tci.getName()).append("\"");
                    }
                }

                return buf.toString();
            }

        }
    }

    /**
     * Raw UDP profile.
     */
    public static class RawUdp extends JingleTransport {
        public static final String NAMESPACE = "http://www.xmpp.org/extensions/xep-0177.html#ns";

        public RawUdp() {
            super();
            setNamespace(NAMESPACE);
        }

        /**
         * Add a transport candidate
         *
         * @see org.jivesoftware.smackx.packet.JingleTransport#addCandidate(org.jivesoftware.smackx.packet.JingleTransport.JingleTransportCandidate)
         */
        public void addCandidate(final JingleTransportCandidate candidate) {
            candidates.clear();
            super.addCandidate(candidate);
        }

        /**
         * Get the list of candidates. As a "raw-udp" transport can only contain
         * one candidate, we use the first in the list...
         *
         * @see org.jivesoftware.smackx.packet.JingleTransport#getCandidates()
         */
        public List<JingleTransportCandidate> getCandidatesList() {
            List<JingleTransportCandidate> copy = new ArrayList<JingleTransportCandidate>();
            List<JingleTransportCandidate> superCandidatesList = super.getCandidatesList();
            if (superCandidatesList.size() > 0) {
                copy.add(superCandidatesList.get(0));
            }

            return copy;
        }

        /**
         * Raw-udp transport candidate.
         */
        public static class Candidate extends JingleTransportCandidate {
            /**
             * Default constructor
             */
            public Candidate() {
                super();
            }

            /**
             * Constructor with a transport candidate.
             */
            public Candidate(final TransportCandidate tc) {
                super(tc);
            }

            /**
             * Get the elements of this candidate.
             */
            protected String getChildElements() {
                StringBuilder buf = new StringBuilder();

                if (transportCandidate != null && transportCandidate instanceof TransportCandidate.Fixed) {
                    TransportCandidate.Fixed tcf = (TransportCandidate.Fixed) transportCandidate;

                    buf.append(" generation=\"").append(tcf.getGeneration()).append("\"");
                    buf.append(" ip=\"").append(tcf.getIp()).append("\"");
                    buf.append(" port=\"").append(tcf.getPort()).append("\"");

                    // Optional parameters
                    String name = tcf.getName();
                    if (name != null) {
                        buf.append(" name=\"").append(name).append("\"");
                    }
                }
                return buf.toString();
            }

        }
    }
}
