
package com.android.email.mail.store;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.net.SocketAddress;
import java.net.URI;
import java.net.URISyntaxException;
import java.security.GeneralSecurityException;
import java.security.SecureRandom;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;

import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManager;
import javax.net.ssl.SSLException;

import android.util.Config;
import android.util.Log;

import com.android.email.Email;
import com.android.email.Utility;
import com.android.email.mail.AuthenticationFailedException;
import com.android.email.mail.FetchProfile;
import com.android.email.mail.Flag;
import com.android.email.mail.Folder;
import com.android.email.mail.Message;
import com.android.email.mail.MessageRetrievalListener;
import com.android.email.mail.MessagingException;
import com.android.email.mail.Store;
import com.android.email.mail.CertificateValidationException;
import com.android.email.mail.Folder.OpenMode;
import com.android.email.mail.internet.MimeMessage;

public class Pop3Store extends Store
{
    public static final int CONNECTION_SECURITY_NONE = 0;
    public static final int CONNECTION_SECURITY_TLS_OPTIONAL = 1;
    public static final int CONNECTION_SECURITY_TLS_REQUIRED = 2;
    public static final int CONNECTION_SECURITY_SSL_REQUIRED = 3;
    public static final int CONNECTION_SECURITY_SSL_OPTIONAL = 4;

    private static final Flag[] PERMANENT_FLAGS = { Flag.DELETED };

    private String mHost;
    private int mPort;
    private String mUsername;
    private String mPassword;
    private int mConnectionSecurity;
    private HashMap<String, Folder> mFolders = new HashMap<String, Folder>();
    private Pop3Capabilities mCapabilities;

//    /**
//     * Detected latency, used for usage scaling.
//     * Usage scaling occurs when it is neccesary to get information about
//     * messages that could result in large data loads. This value allows
//     * the code that loads this data to decide between using large downloads
//     * (high latency) or multiple round trips (low latency) to accomplish
//     * the same thing.
//     * Default is Integer.MAX_VALUE implying massive latency so that the large
//     * download method is used by default until latency data is collected.
//     */
//    private int mLatencyMs = Integer.MAX_VALUE;
//
//    /**
//     * Detected throughput, used for usage scaling.
//     * Usage scaling occurs when it is neccesary to get information about
//     * messages that could result in large data loads. This value allows
//     * the code that loads this data to decide between using large downloads
//     * (high latency) or multiple round trips (low latency) to accomplish
//     * the same thing.
//     * Default is Integer.MAX_VALUE implying massive bandwidth so that the
//     * large download method is used by default until latency data is
//     * collected.
//     */
//    private int mThroughputKbS = Integer.MAX_VALUE;

    /**
     * pop3://user:password@server:port CONNECTION_SECURITY_NONE
     * pop3+tls://user:password@server:port CONNECTION_SECURITY_TLS_OPTIONAL
     * pop3+tls+://user:password@server:port CONNECTION_SECURITY_TLS_REQUIRED
     * pop3+ssl+://user:password@server:port CONNECTION_SECURITY_SSL_REQUIRED
     * pop3+ssl://user:password@server:port CONNECTION_SECURITY_SSL_OPTIONAL
     *
     * @param _uri
     */
    public Pop3Store(String _uri) throws MessagingException
    {
        URI uri;
        try
        {
            uri = new URI(_uri);
        }
        catch (URISyntaxException use)
        {
            throw new MessagingException("Invalid Pop3Store URI", use);
        }

        String scheme = uri.getScheme();
        if (scheme.equals("pop3"))
        {
            mConnectionSecurity = CONNECTION_SECURITY_NONE;
            mPort = 110;
        }
        else if (scheme.equals("pop3+tls"))
        {
            mConnectionSecurity = CONNECTION_SECURITY_TLS_OPTIONAL;
            mPort = 110;
        }
        else if (scheme.equals("pop3+tls+"))
        {
            mConnectionSecurity = CONNECTION_SECURITY_TLS_REQUIRED;
            mPort = 110;
        }
        else if (scheme.equals("pop3+ssl+"))
        {
            mConnectionSecurity = CONNECTION_SECURITY_SSL_REQUIRED;
            mPort = 995;
        }
        else if (scheme.equals("pop3+ssl"))
        {
            mConnectionSecurity = CONNECTION_SECURITY_SSL_OPTIONAL;
            mPort = 995;
        }
        else
        {
            throw new MessagingException("Unsupported protocol");
        }

        mHost = uri.getHost();

        if (uri.getPort() != -1)
        {
            mPort = uri.getPort();
        }

        if (uri.getUserInfo() != null)
        {
            String[] userInfoParts = uri.getUserInfo().split(":", 2);
            mUsername = userInfoParts[0];
            if (userInfoParts.length > 1)
            {
                mPassword = userInfoParts[1];
            }
        }
    }

    @Override
    public Folder getFolder(String name) throws MessagingException
    {
        Folder folder = mFolders.get(name);
        if (folder == null)
        {
            folder = new Pop3Folder(name);
            mFolders.put(folder.getName(), folder);
        }
        return folder;
    }

    @Override
    public Folder[] getPersonalNamespaces() throws MessagingException
    {
        return new Folder[]
        {
            getFolder("INBOX"),
        };
    }

    @Override
    public void checkSettings() throws MessagingException
    {
        Pop3Folder folder = new Pop3Folder("INBOX");
        folder.open(OpenMode.READ_WRITE);
        if (!mCapabilities.uidl)
        {
            /*
             * Run an additional test to see if UIDL is supported on the server. If it's not we
             * can't service this account.
             */

            /*
             * If the server doesn't support UIDL it will return a - response, which causes
             * executeSimpleCommand to throw a MessagingException, exiting this method.
             */
            folder.executeSimpleCommand("UIDL");

        }
        folder.close(false);
    }

    class Pop3Folder extends Folder
    {
        private Socket mSocket;
        private InputStream mIn;
        private OutputStream mOut;
        private HashMap<String, Pop3Message> mUidToMsgMap = new HashMap<String, Pop3Message>();
        private HashMap<Integer, Pop3Message> mMsgNumToMsgMap = new HashMap<Integer, Pop3Message>();
        private HashMap<String, Integer> mUidToMsgNumMap = new HashMap<String, Integer>();
        private String mName;
        private int mMessageCount;

        public Pop3Folder(String name)
        {
            this.mName = name;
            if (mName.equalsIgnoreCase("INBOX"))
            {
                mName = "INBOX";
            }
        }

        @Override
        public synchronized void open(OpenMode mode) throws MessagingException
        {
            if (isOpen())
            {
                return;
            }

            if (!mName.equalsIgnoreCase("INBOX"))
            {
                throw new MessagingException("Folder does not exist");
            }

            try
            {
                SocketAddress socketAddress = new InetSocketAddress(mHost, mPort);
                if (mConnectionSecurity == CONNECTION_SECURITY_SSL_REQUIRED ||
                        mConnectionSecurity == CONNECTION_SECURITY_SSL_OPTIONAL)
                {
                    SSLContext sslContext = SSLContext.getInstance("TLS");
                    final boolean secure = mConnectionSecurity == CONNECTION_SECURITY_SSL_REQUIRED;
                    sslContext.init(null, new TrustManager[]
                    {
                        TrustManagerFactory.get(mHost, secure)
                    }, new SecureRandom());
                    mSocket = sslContext.getSocketFactory().createSocket();
                    mSocket.connect(socketAddress, SOCKET_CONNECT_TIMEOUT);
                    mIn = new BufferedInputStream(mSocket.getInputStream(), 1024);
                    mOut = new BufferedOutputStream(mSocket.getOutputStream(), 512);
                }
                else
                {
                    mSocket = new Socket();
                    mSocket.connect(socketAddress, SOCKET_CONNECT_TIMEOUT);
                    mIn = new BufferedInputStream(mSocket.getInputStream(), 1024);
                    mOut = new BufferedOutputStream(mSocket.getOutputStream(), 512);
                }

                mSocket.setSoTimeout(Store.SOCKET_READ_TIMEOUT);
                if (!isOpen())
                {
                    throw new MessagingException("Unable to connect socket");
                }

                // Eat the banner
                executeSimpleCommand(null);

                mCapabilities = getCapabilities();

                if (mConnectionSecurity == CONNECTION_SECURITY_TLS_OPTIONAL
                        || mConnectionSecurity == CONNECTION_SECURITY_TLS_REQUIRED)
                {
                    if (mCapabilities.stls)
                    {
                        writeLine("STLS");

                        SSLContext sslContext = SSLContext.getInstance("TLS");
                        boolean secure = mConnectionSecurity == CONNECTION_SECURITY_TLS_REQUIRED;
                        sslContext.init(null, new TrustManager[]
                        {
                            TrustManagerFactory.get(mHost, secure)
                        }, new SecureRandom());
                        mSocket = sslContext.getSocketFactory().createSocket(mSocket, mHost, mPort,
                                  true);
                        mSocket.setSoTimeout(Store.SOCKET_READ_TIMEOUT);
                        mIn = new BufferedInputStream(mSocket.getInputStream(), 1024);
                        mOut = new BufferedOutputStream(mSocket.getOutputStream(), 512);
                        if (!isOpen())
                        {
                            throw new MessagingException("Unable to connect socket");
                        }
                    }
                    else if (mConnectionSecurity == CONNECTION_SECURITY_TLS_REQUIRED)
                    {
                        throw new MessagingException("TLS not supported but required");
                    }
                }

                try
                {
                    executeSimpleCommand("USER " + mUsername);
                    executeSimpleCommand("PASS " + mPassword);
                }
                catch (MessagingException me)
                {
                    throw new AuthenticationFailedException(null, me);
                }
            }
            catch (SSLException e)
            {
                throw new CertificateValidationException(e.getMessage(), e);
            }
            catch (GeneralSecurityException gse)
            {
                throw new MessagingException(
                    "Unable to open connection to POP server due to security error.", gse);
            }
            catch (IOException ioe)
            {
                throw new MessagingException("Unable to open connection to POP server.", ioe);
            }

            String response = executeSimpleCommand("STAT");
            String[] parts = response.split(" ");
            mMessageCount = Integer.parseInt(parts[1]);

            mUidToMsgMap.clear();
            mMsgNumToMsgMap.clear();
            mUidToMsgNumMap.clear();
        }

        public boolean isOpen()
        {
            return (mIn != null && mOut != null && mSocket != null
                    && mSocket.isConnected() && !mSocket.isClosed());
        }

        @Override
        public OpenMode getMode() throws MessagingException
        {
            return OpenMode.READ_WRITE;
        }

        @Override
        public void close(boolean expunge)
        {
            try
            {
                executeSimpleCommand("QUIT");
            }
            catch (Exception e)
            {
                /*
                 * QUIT may fail if the connection is already closed. We don't care. It's just
                 * being friendly.
                 */
            }

            closeIO();
        }

        private void closeIO()
        {
            try
            {
                mIn.close();
            }
            catch (Exception e)
            {
                /*
                 * May fail if the connection is already closed.
                 */
            }
            try
            {
                mOut.close();
            }
            catch (Exception e)
            {
                /*
                 * May fail if the connection is already closed.
                 */
            }
            try
            {
                mSocket.close();
            }
            catch (Exception e)
            {
                /*
                 * May fail if the connection is already closed.
                 */
            }
            mIn = null;
            mOut = null;
            mSocket = null;
        }

        @Override
        public String getName()
        {
            return mName;
        }

        @Override
        public boolean create(FolderType type) throws MessagingException
        {
            return false;
        }

        @Override
        public boolean exists() throws MessagingException
        {
            return mName.equalsIgnoreCase("INBOX");
        }

        @Override
        public int getMessageCount()
        {
            return mMessageCount;
        }

        @Override
        public int getUnreadMessageCount() throws MessagingException
        {
            return -1;
        }

        @Override
        public Message getMessage(String uid) throws MessagingException
        {
            Pop3Message message = mUidToMsgMap.get(uid);
            if (message == null)
            {
                message = new Pop3Message(uid, this);
            }
            return message;
        }

        @Override
        public Message[] getMessages(int start, int end, MessageRetrievalListener listener)
        throws MessagingException
        {
            if (start < 1 || end < 1 || end < start)
            {
                throw new MessagingException(String.format("Invalid message set %d %d",
                                             start, end));
            }
            try
            {
                indexMsgNums(start, end);
            }
            catch (IOException ioe)
            {
                throw new MessagingException("getMessages", ioe);
            }
            ArrayList<Message> messages = new ArrayList<Message>();
            int i = 0;
            for (int msgNum = start; msgNum <= end; msgNum++)
            {
                Pop3Message message = mMsgNumToMsgMap.get(msgNum);
                if (listener != null)
                {
                    listener.messageStarted(message.getUid(), i++, (end - start) + 1);
                }
                messages.add(message);
                if (listener != null)
                {
                    listener.messageFinished(message, i++, (end - start) + 1);
                }
            }
            return messages.toArray(new Message[messages.size()]);
        }

        /**
         * Ensures that the given message set (from start to end inclusive)
         * has been queried so that uids are available in the local cache.
         * @param start
         * @param end
         * @throws MessagingException
         * @throws IOException
         */
        private void indexMsgNums(int start, int end)
        throws MessagingException, IOException
        {
            int unindexedMessageCount = 0;
            for (int msgNum = start; msgNum <= end; msgNum++)
            {
                if (mMsgNumToMsgMap.get(msgNum) == null)
                {
                    unindexedMessageCount++;
                }
            }
            if (unindexedMessageCount == 0)
            {
                return;
            }
            if (unindexedMessageCount < 50 && mMessageCount > 5000)
            {
                /*
                 * In extreme cases we'll do a UIDL command per message instead of a bulk
                 * download.
                 */
                for (int msgNum = start; msgNum <= end; msgNum++)
                {
                    Pop3Message message = mMsgNumToMsgMap.get(msgNum);
                    if (message == null)
                    {
                        String response = executeSimpleCommand("UIDL " + msgNum);
                        int uidIndex = response.lastIndexOf(' ');
                        String msgUid = response.substring(uidIndex + 1);
                        message = new Pop3Message(msgUid, this);
                        indexMessage(msgNum, message);
                    }
                }
            }
            else
            {
                String response = executeSimpleCommand("UIDL");
                while ((response = readLine()) != null)
                {
                    if (response.equals("."))
                    {
                        break;
                    }
                    String[] uidParts = response.split(" ");
                    Integer msgNum = Integer.valueOf(uidParts[0]);
                    String msgUid = uidParts[1];
                    if (msgNum >= start && msgNum <= end)
                    {
                        Pop3Message message = mMsgNumToMsgMap.get(msgNum);
                        if (message == null)
                        {
                            message = new Pop3Message(msgUid, this);
                            indexMessage(msgNum, message);
                        }
                    }
                }
            }
        }

        private void indexUids(ArrayList<String> uids)
        throws MessagingException, IOException
        {
            HashSet<String> unindexedUids = new HashSet<String>();
            for (String uid : uids)
            {
                if (mUidToMsgMap.get(uid) == null)
                {
                    if (Config.LOGD)
                    {
                        Log.d(Email.LOG_TAG, "Need to index UID " + uid);
                    }
                    unindexedUids.add(uid);
                }
            }
            if (unindexedUids.size() == 0)
            {
                return;
            }
            /*
             * If we are missing uids in the cache the only sure way to
             * get them is to do a full UIDL list. A possible optimization
             * would be trying UIDL for the latest X messages and praying.
             */
            String response = executeSimpleCommand("UIDL");
            while ((response = readLine()) != null)
            {
                if (response.equals("."))
                {
                    break;
                }
                String[] uidParts = response.split(" ");
                Integer msgNum = Integer.valueOf(uidParts[0]);
                String msgUid = uidParts[1];
                if (unindexedUids.contains(msgUid))
                {
                    if (Config.LOGD)
                    {
                        Log.d(Email.LOG_TAG, "Got msgNum " + msgNum + " for UID " + msgUid);
                    }

                    Pop3Message message = mUidToMsgMap.get(msgUid);
                    if (message == null)
                    {
                        message = new Pop3Message(msgUid, this);
                    }
                    indexMessage(msgNum, message);
                }
            }
        }

        private void indexMessage(int msgNum, Pop3Message message)
        {
            if (Config.LOGD)
            {
                Log.d(Email.LOG_TAG, "Adding index for UID " + message.getUid() + " to msgNum " + msgNum);
            }
            mMsgNumToMsgMap.put(msgNum, message);
            mUidToMsgMap.put(message.getUid(), message);
            mUidToMsgNumMap.put(message.getUid(), msgNum);
        }

        @Override
        public Message[] getMessages(MessageRetrievalListener listener) throws MessagingException
        {
            throw new UnsupportedOperationException("Pop3: No getMessages");
        }

        @Override
        public Message[] getMessages(String[] uids, MessageRetrievalListener listener)
        throws MessagingException
        {
            throw new UnsupportedOperationException("Pop3: No getMessages by uids");
        }

        /**
         * Fetch the items contained in the FetchProfile into the given set of
         * Messages in as efficient a manner as possible.
         * @param messages
         * @param fp
         * @throws MessagingException
         */
        public void fetch(Message[] messages, FetchProfile fp, MessageRetrievalListener listener)
        throws MessagingException
        {
            if (messages == null || messages.length == 0)
            {
                return;
            }
            ArrayList<String> uids = new ArrayList<String>();
            for (Message message : messages)
            {
                uids.add(message.getUid());
            }
            try
            {
                indexUids(uids);
            }
            catch (IOException ioe)
            {
                throw new MessagingException("fetch", ioe);
            }
            try
            {
                if (fp.contains(FetchProfile.Item.ENVELOPE))
                {
                    /*
                     * We pass the listener only if there are other things to do in the
                     * FetchProfile. Since fetchEnvelop works in bulk and eveything else
                     * works one at a time if we let fetchEnvelope send events the
                     * event would get sent twice.
                     */
                    fetchEnvelope(messages, fp.size() == 1 ? listener : null);
                }
            }
            catch (IOException ioe)
            {
                throw new MessagingException("fetch", ioe);
            }
            for (int i = 0, count = messages.length; i < count; i++)
            {
                Message message = messages[i];
                if (!(message instanceof Pop3Message))
                {
                    throw new MessagingException("Pop3Store.fetch called with non-Pop3 Message");
                }
                Pop3Message pop3Message = (Pop3Message)message;
                try
                {
                    if (listener != null && !fp.contains(FetchProfile.Item.ENVELOPE))
                    {
                        listener.messageStarted(pop3Message.getUid(), i, count);
                    }
                    if (fp.contains(FetchProfile.Item.BODY))
                    {
                        fetchBody(pop3Message, -1);
                    }
                    else if (fp.contains(FetchProfile.Item.BODY_SANE))
                    {
                        /*
                         * To convert the suggested download size we take the size
                         * divided by the maximum line size (76).
                         */
                        fetchBody(pop3Message,
                                  FETCH_BODY_SANE_SUGGESTED_SIZE / 76);
                    }
                    else if (fp.contains(FetchProfile.Item.STRUCTURE))
                    {
                        /*
                         * If the user is requesting STRUCTURE we are required to set the body
                         * to null since we do not support the function.
                         */
                        pop3Message.setBody(null);
                    }
                    if (listener != null && !(fp.contains(FetchProfile.Item.ENVELOPE) && fp.size() == 1))
                    {
                        listener.messageFinished(message, i, count);
                    }
                }
                catch (IOException ioe)
                {
                    throw new MessagingException("Unable to fetch message", ioe);
                }
            }
        }

        private void fetchEnvelope(Message[] messages,
                                   MessageRetrievalListener listener)  throws IOException, MessagingException
        {
            int unsizedMessages = 0;
            for (Message message : messages)
            {
                if (message.getSize() == -1)
                {
                    unsizedMessages++;
                }
            }
            if (unsizedMessages == 0)
            {
                return;
            }
            if (unsizedMessages < 50 && mMessageCount > 5000)
            {
                /*
                 * In extreme cases we'll do a command per message instead of a bulk request
                 * to hopefully save some time and bandwidth.
                 */
                for (int i = 0, count = messages.length; i < count; i++)
                {
                    Message message = messages[i];
                    if (!(message instanceof Pop3Message))
                    {
                        throw new MessagingException("Pop3Store.fetch called with non-Pop3 Message");
                    }
                    Pop3Message pop3Message = (Pop3Message)message;
                    if (listener != null)
                    {
                        listener.messageStarted(pop3Message.getUid(), i, count);
                    }
                    String response = executeSimpleCommand(String.format("LIST %d",
                                                           mUidToMsgNumMap.get(pop3Message.getUid())));
                    String[] listParts = response.split(" ");
                    int msgNum = Integer.parseInt(listParts[1]);
                    int msgSize = Integer.parseInt(listParts[2]);
                    pop3Message.setSize(msgSize);
                    if (listener != null)
                    {
                        listener.messageFinished(pop3Message, i, count);
                    }
                }
            }
            else
            {
                HashSet<String> msgUidIndex = new HashSet<String>();
                for (Message message : messages)
                {
                    msgUidIndex.add(message.getUid());
                }
                int i = 0, count = messages.length;
                String response = executeSimpleCommand("LIST");
                while ((response = readLine()) != null)
                {
                    if (response.equals("."))
                    {
                        break;
                    }
                    String[] listParts = response.split(" ");
                    int msgNum = Integer.parseInt(listParts[0]);
                    int msgSize = Integer.parseInt(listParts[1]);
                    Pop3Message pop3Message = mMsgNumToMsgMap.get(msgNum);
                    if (pop3Message != null && msgUidIndex.contains(pop3Message.getUid()))
                    {
                        if (listener != null)
                        {
                            listener.messageStarted(pop3Message.getUid(), i, count);
                        }
                        pop3Message.setSize(msgSize);
                        if (listener != null)
                        {
                            listener.messageFinished(pop3Message, i, count);
                        }
                        i++;
                    }
                }
            }
        }

        /**
         * Fetches the body of the given message, limiting the stored data
         * to the specified number of lines. If lines is -1 the entire message
         * is fetched. This is implemented with RETR for lines = -1 or TOP
         * for any other value. If the server does not support TOP it is
         * emulated with RETR and extra lines are thrown away.
         * @param message
         * @param lines
         */
        private void fetchBody(Pop3Message message, int lines)
        throws IOException, MessagingException
        {
            String response = null;
            if (lines == -1 || !mCapabilities.top)
            {
                response = executeSimpleCommand(String.format("RETR %d",
                                                mUidToMsgNumMap.get(message.getUid())));
            }
            else
            {
                response = executeSimpleCommand(String.format("TOP %d %d",
                                                mUidToMsgNumMap.get(message.getUid()),
                                                lines));
            }
            if (response != null)
            {
                try
                {
                    message.parse(new Pop3ResponseInputStream(mIn));
                    if (lines == -1 || !mCapabilities.top)
                    {
                        message.setFlag(Flag.X_DOWNLOADED_FULL, true);
                    }
                }
                catch (MessagingException me)
                {
                    /*
                     * If we're only downloading headers it's possible
                     * we'll get a broken MIME message which we're not
                     * real worried about. If we've downloaded the body
                     * and can't parse it we need to let the user know.
                     */
                    if (lines == -1)
                    {
                        throw me;
                    }
                }
            }
        }

        @Override
        public Flag[] getPermanentFlags() throws MessagingException
        {
            return PERMANENT_FLAGS;
        }

        public void appendMessages(Message[] messages) throws MessagingException
        {
        }

        public void delete(boolean recurse) throws MessagingException
        {
        }
        
        public void delete(Message[] msgs, String trashFolderName) throws MessagingException
        {
            setFlags(msgs, new Flag[] { Flag.DELETED }, true);
        }

        @Override
        public String getUidFromMessageId(Message message) throws MessagingException
        {
            return null;
        }

        public Message[] expunge() throws MessagingException
        {
            return null;
        }

        @Override
        public void setFlags(Flag[] flags, boolean value)
        throws MessagingException
        {
            Message[] messages = getMessages(null);
            setFlags(messages, flags, value);
        }

        public void setFlags(Message[] messages, Flag[] flags, boolean value)
        throws MessagingException
        {
            if (!value || !Utility.arrayContains(flags, Flag.DELETED))
            {
                /*
                 * The only flagging we support is setting the Deleted flag.
                 */
                return;
            }
            ArrayList<String> uids = new ArrayList<String>();
            try
            {
                for (Message message : messages)
                {
                    uids.add(message.getUid());
                }

                indexUids(uids);
            }
            catch (IOException ioe)
            {
                throw new MessagingException("Could not get message number for uid " + uids, ioe);
            }
            for (Message message : messages)
            {

                Integer msgNum = mUidToMsgNumMap.get(message.getUid());
                if (msgNum == null)
                {
                    MessagingException me = new MessagingException("Could not delete message " + message.getUid()
                            + " because no msgNum found; permanent error");
                    me.setPermanentFailure(true);
                    throw me;
                }
                executeSimpleCommand(String.format("DELE %s", msgNum));
            }
        }

//        private boolean isRoundTripModeSuggested() {
//            long roundTripMethodMs =
//                (uncachedMessageCount * 2 * mLatencyMs);
//            long bulkMethodMs =
//                    (mMessageCount * 58) / (mThroughputKbS * 1024 / 8) * 1000;
//        }

        private String readLine() throws IOException
        {
            StringBuffer sb = new StringBuffer();
            int d = mIn.read();
            if (d == -1)
            {
                throw new IOException("End of stream reached while trying to read line.");
            }
            do
            {
                if (((char)d) == '\r')
                {
                    continue;
                }
                else if (((char)d) == '\n')
                {
                    break;
                }
                else
                {
                    sb.append((char)d);
                }
            }
            while ((d = mIn.read()) != -1);
            String ret = sb.toString();
            if (Config.LOGD)
            {
                if (Email.DEBUG)
                {
                    Log.d(Email.LOG_TAG, "<<< " + ret);
                }
            }
            return ret;
        }

        private void writeLine(String s) throws IOException
        {
            if (Config.LOGD)
            {
                if (Email.DEBUG)
                {
                    Log.d(Email.LOG_TAG, ">>> " + s);
                }
            }
            mOut.write(s.getBytes());
            mOut.write('\r');
            mOut.write('\n');
            mOut.flush();
        }

        private Pop3Capabilities getCapabilities() throws IOException, MessagingException
        {
            Pop3Capabilities capabilities = new Pop3Capabilities();
            try
            {
                String response = executeSimpleCommand("CAPA");
                while ((response = readLine()) != null)
                {
                    if (response.equals("."))
                    {
                        break;
                    }
                    if (response.equalsIgnoreCase("STLS"))
                    {
                        capabilities.stls = true;
                    }
                    else if (response.equalsIgnoreCase("UIDL"))
                    {
                        capabilities.uidl = true;
                    }
                    else if (response.equalsIgnoreCase("PIPELINING"))
                    {
                        capabilities.pipelining = true;
                    }
                    else if (response.equalsIgnoreCase("USER"))
                    {
                        capabilities.user = true;
                    }
                    else if (response.equalsIgnoreCase("TOP"))
                    {
                        capabilities.top = true;
                    }
                }
            }
            catch (MessagingException me)
            {
                /*
                 * The server may not support the CAPA command, so we just eat this Exception
                 * and allow the empty capabilities object to be returned.
                 */
            }
            return capabilities;
        }

        private String executeSimpleCommand(String command) throws MessagingException
        {
            try
            {
                open(OpenMode.READ_WRITE);
                if (Config.LOGV)
                {
                    Log.v(Email.LOG_TAG, "POP3: command '" + command + "'");
                }
                if (command != null)
                {
                    writeLine(command);
                }

                String response = readLine();
                if (Config.LOGV)
                {
                    Log.v(Email.LOG_TAG, "POP3: response '" + command + "'");
                }

                if (response.length() > 1 && response.charAt(0) == '-')
                {
                    throw new MessagingException(response);
                }

                return response;
            }
            catch (MessagingException me)
            {
                throw me;
            }
            catch (Exception e)
            {
                closeIO();
                throw new MessagingException("Unable to execute POP3 command", e);
            }
        }

        @Override
        public boolean supportsFetchingFlags()
        {
            return false;
        }//isFlagSupported

        @Override
        public boolean equals(Object o)
        {
            if (o instanceof Pop3Folder)
            {
                return ((Pop3Folder) o).mName.equals(mName);
            }
            return super.equals(o);
        }

    }//Pop3Folder

    class Pop3Message extends MimeMessage
    {
        public Pop3Message(String uid, Pop3Folder folder) throws MessagingException
        {
            mUid = uid;
            mFolder = folder;
            mSize = -1;
            mFlags.add(Flag.X_NO_SEEN_INFO);
        }

        public void setSize(int size)
        {
            mSize = size;
        }

        protected void parse(InputStream in) throws IOException, MessagingException
        {
            super.parse(in);
        }

        @Override
        public void setFlag(Flag flag, boolean set) throws MessagingException
        {
            super.setFlag(flag, set);
            mFolder.setFlags(new Message[] { this }, new Flag[] { flag }, set);
        }

        @Override
        public void delete(String trashFolderName) throws MessagingException
        {
            //  try
            //  {
            //  Poor POP3 users, we can't copy the message to the Trash folder, but they still want a delete
            setFlag(Flag.DELETED, true);
            //   }
//         catch (MessagingException me)
//         {
//          Log.w(Email.LOG_TAG, "Could not delete non-existant message", me);
//         }
        }
    }

    class Pop3Capabilities
    {
        public boolean stls;
        public boolean top;
        public boolean user;
        public boolean uidl;
        public boolean pipelining;

        public String toString()
        {
            return String.format("STLS %b, TOP %b, USER %b, UIDL %b, PIPELINING %b",
                                 stls,
                                 top,
                                 user,
                                 uidl,
                                 pipelining);
        }
    }

    class Pop3ResponseInputStream extends InputStream
    {
        InputStream mIn;
        boolean mStartOfLine = true;
        boolean mFinished;

        public Pop3ResponseInputStream(InputStream in)
        {
            mIn = in;
        }

        @Override
        public int read() throws IOException
        {
            if (mFinished)
            {
                return -1;
            }
            int d = mIn.read();
            if (mStartOfLine && d == '.')
            {
                d = mIn.read();
                if (d == '\r')
                {
                    mFinished = true;
                    mIn.read();
                    return -1;
                }
            }

            mStartOfLine = (d == '\n');

            return d;
        }
    }
}
