/*
 * Copyright 2006-2007 Queplix Corp.
 *
 * Licensed under the Queplix Public License, Version 1.1.1 (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.queplix.com/solutions/commercial-open-source/queplix-public-license/
 *
 * 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 com.queplix.core.modules.inbox.utils;

import com.queplix.core.modules.inbox.InboxHelper;
import com.queplix.core.modules.inbox.error.InvalidAccountException;
import com.queplix.core.utils.DateHelper;
import com.queplix.core.utils.StringHelper;

import javax.mail.Flags;
import javax.mail.Folder;
import javax.mail.Message;
import javax.mail.MessagingException;
import javax.mail.Session;
import javax.mail.Store;
import java.util.Date;
import java.util.TimeZone;

/**
 * Abstract Inbox Provider.
 *
 * @author [ALB] Baranov Andrey
 * @version $Revision: 1.4 $ $Date: 2006/07/10 11:07:28 $
 */

public abstract class AbstractInboxProvider
        extends AbstractInboxPluggableModule implements InboxProvider {

    // ----------------------------------------------------- variables

    protected Store store;
    protected Folder folder;
    protected InboxResultSet rs;

    // ----------------------------------------------------- public methods

    /*
     * No javadoc
     * @see InboxProvider#open
     */

    public void open()
            throws MessagingException {

        // Get session.
        Session session = Session.getDefaultInstance(System.getProperties(),
                null);

        // Opening store.
        store = openStore(session);

        // Opening folder.
        folder = openFolder(store, Folder.READ_WRITE);
    }

    /*
     * No javadoc
     * @see InboxProvider#close
     */
    public void close(boolean expunge) {

        // Close result set first.
        if(rs != null) {
            rs.close();
        }

        if(folder != null) {
            try {
                folder.close(expunge);
            } catch (Exception ex) {
                ex.printStackTrace();
            }
        }

        if(store != null) {
            try {
                store.close();
            } catch (Exception ex) {
                ex.printStackTrace();
            }
        }
    }

    /*
     * No javadoc
     * @see InboxProvider#getResultSet
     */
    public ResultSet getResultSet(MessageFilter filter)
            throws MessagingException {

        if(rs == null) {
            Message[] messages = null;
            if(folder.getMessageCount() > 0) {
                messages = folder.getMessages();
            }

            // Create new ResultSet.
            rs = new InboxResultSet(messages);
            rs.setMessageFilter(filter);
        }

        return rs;
    }

    /*
     * No javadoc
     * @see InboxProvider#setDeletedMsgFlag
     */
    public final void setDeletedMsgFlag(Message message)
            throws MessagingException {
        message.setFlag(Flags.Flag.DELETED, true);
    }

    /*
     * No javadoc
     * @see InboxProvider#setSeenMsgFlag
     */
    public final void setSeenMsgFlag(Message message)
            throws MessagingException {
        message.setFlag(Flags.Flag.SEEN, true);
    }

    /*
     * No javadoc
     * @see InboxProvider#getServerDate
     */
    public Date getServerDate(Message message)
            throws MessagingException {

        Date d = message.getReceivedDate();
        if(d != null) {
            d = new Date(DateHelper.toSystem(d.getTime(),
                    TimeZone.getDefault()));
        }
        return d;
    }

    /*
     * No javadoc
     * @see InboxProvider#checkAccount
     */
    public void checkAccount()
            throws InvalidAccountException {

        if(account == null) {
            throw new NullPointerException("Account not set!");
        }

        String host = account.getServer();
        Integer port = account.getPort();
        String user = account.getName();
        String password = account.getPassword();

        // Just checking parameters.
        if(StringHelper.isEmpty(host)) {
            throw new InvalidAccountException("Host name is empty.");

        } else
        if(port != null && (port.intValue() <= 0 || port.intValue() > 65535)) {
            throw new InvalidAccountException(
                    "Server port = " + port + " must be in [0..65535] range.");

        } else if(StringHelper.isEmpty(user)) {
            throw new InvalidAccountException("User name is empty.");

        } else if(StringHelper.isEmpty(password)) {
            throw new InvalidAccountException("Password is empty.");
        }

        // Try to connect.
        try {
            open();

        } catch (MessagingException ex) {
            // Generate error message in expanded format.
            StringBuffer sb = new StringBuffer();
            while(true) {
                Exception e = ex.getNextException();
                if(e == null) {
                    break;
                }
                if(sb.length() > 0) {
                    sb.append("\n");
                }
                sb.append(e.getMessage());
                if(e instanceof MessagingException) {
                    ex = (MessagingException) e;
                } else {
                    break;
                }
            }
            if(sb.length() == 0) {
                sb.append(ex.getMessage());
            }

            ERROR(sb.toString(), ex);
            throw new InvalidAccountException(sb.toString());

        } catch (Exception ex) {
            ERROR(ex);
            throw new InvalidAccountException(ex.getMessage());

        } finally {
            close(false); // don't touch the content
        }
    }

    /*
     * No javadoc
     * @see Object#toString
     */
    public String toString() {
        return "class=" + getClass().getName() +
                "; proto=" + getJavaMailProto() +
                "; uri=" + InboxHelper.toURI(account);
    }

    // ----------------------------------------------------- protected methods

    /**
     * Opens message store.
     *
     * @param session mail session
     * @return Store
     * @throws MessagingException
     */
    protected Store openStore(Session session)
            throws MessagingException {

        if(account == null) {
            throw new NullPointerException("Account not set!");
        }
        String host = account.getServer();
        Integer port = account.getPort();
        String user = account.getName();
        String password = account.getPassword();

        Store store = session.getStore(getJavaMailProto());

        // Connecting...
        if(port != null) {
            store.connect(host, port.intValue(), user, password);
        } else {
            store.connect(host, user, password);
        }

        return store;
    }

    /**
     * Opens folder for mail messages.
     *
     * @param store Store
     * @param flag  open the Folder READ_ONLY or READ_WRITE
     * @return Folder
     * @throws MessagingException
     */
    protected Folder openFolder(Store store, int flag)
            throws MessagingException {

        if(account == null) {
            throw new NullPointerException("Account not set!");
        }
        String folderName = account.getFolderName();

        // Opening the default folder.
        Folder folder = store.getDefaultFolder();
        if(folder == null) {
            throw new NullPointerException("No default mail folder.");
        }

        // Opening the <code>folderName</code> folder or 'INBOX' folder.
        String name;
        if(StringHelper.isEmpty(folderName)) {
            name = "INBOX";
        } else {
            name = folderName;
        }
        folder = folder.getFolder(name);
        if(folder == null) {
            throw new NullPointerException(
                    "Unable to get '" + name + "' folder.");
        }

        folder.open(flag);
        return folder;
    }

    // ----------------------------------------------------- inner class

    /**
     * <p>Generic implementation of ResultSet</p>
     *
     * @version $Revision: 1.4 $ $Date: 2006/07/10 11:07:28 $
     */
    public class InboxResultSet
            implements ResultSet {

        protected Message[] messages;
        protected MessageFilter filter;
        protected int size;
        protected int pos = 0;

        /**
         * Constructor.
         *
         * @param messages Message[]
         */
        public InboxResultSet(Message[] messages) {
            this.messages = messages;
            this.size = (messages == null) ? 0:messages.length;
        }

        /*
         * No javadoc
         * @see ResultSet#setMessageFilter
         */
        public void setMessageFilter(MessageFilter filter) {
            this.filter = filter;
        }

        /*
         * No javadoc
         * @see ResultSet#next
         */
        public Message next()
                throws MessagingException {

            Message message;
            while((message = _next()) != null) {
                // Checking.
                if(filter != null) {
                    boolean ret = filter.filter(message);
                    if(!ret) {
                        try {
                            // Fail - log message.
                            String info = filter.getInfo();
                            WARN(logName + info);
                            publisher.WARN(info);
                            continue;
                        } catch (Exception ex) {
                            // empty
                        }
                    }
                }

                // Ok - break cycle.
                break;
            }

            return message;
        }

        /*
         * No javadoc
         * @see ResultSet#next
         */
        public Message nextByAccount(long accountId)
                throws MessagingException {

            Message message;
            while((message = _next()) != null) {
                // Checking.
                if(filter != null) {
                    boolean ret = filter.filter(message);
                    if(!ret) {
                        // Fail - log message.
                        String info = filter.getInfo();
                        WARN(logName + info);
                        publisher.WARN(info, new Long(accountId));
                        continue;
                    }
                }

                // Ok - break cycle.
                break;
            }

            return message;
        }

        /*
         * No javadoc
         * @see ResultSet#close
         */
        public void close() {
            messages = null;
        }

        //
        // Simply returns next message.
        //
        protected Message _next() {
            if(pos >= size) {
                return null;
            } else {
                return messages[pos++];
            }
        }

    } // -- end of inner class
}
