package com.android.exchange.eas;

import android.content.Context;
import android.net.TrafficStats;
import android.text.format.DateUtils;

import com.android.emailcommon.TrafficFlags;
import com.android.emailcommon.provider.Account;
import com.android.emailcommon.provider.EmailContent;
import com.android.emailcommon.provider.Mailbox;
import com.android.exchange.CommandStatusException;
import com.android.exchange.Eas;
import com.android.exchange.EasResponse;
import com.android.exchange.adapter.AbstractSyncParser;
import com.android.exchange.adapter.Parser;
import com.android.exchange.adapter.Serializer;
import com.android.exchange.adapter.Tags;
import com.android.mail.utils.LogUtils;

import org.apache.http.HttpEntity;

import java.io.IOException;

/**
 * Performs an EAS sync operation for one folder (excluding mail upsync).
 * TODO: Merge with {@link EasSync}, which currently handles mail upsync.
 */
public class EasSyncBase extends EasOperation {

    private static final String TAG = Eas.LOG_TAG;

    public static final int RESULT_DONE = 0;
    public static final int RESULT_MORE_AVAILABLE = 1;

    private boolean mInitialSync;
    private final Mailbox mMailbox;
    private EasSyncCollectionTypeBase mCollectionTypeHandler;

    private int mNumWindows;

    // TODO: Convert to accountId when ready to convert to EasService.
    public EasSyncBase(final Context context, final Account account, final Mailbox mailbox) {
        super(context, account);
        mMailbox = mailbox;
    }

    /**
     * Get the sync key for this mailbox.
     * @return The sync key for the object being synced. "0" means this is the first sync. If
     *      there is an error in getting the sync key, this function returns null.
     */
    protected String getSyncKey() {
        if (mMailbox == null) {
            return null;
        }
        if (mMailbox.mSyncKey == null) {
            mMailbox.mSyncKey = "0";
        }
        return mMailbox.mSyncKey;
    }

    @Override
    protected String getCommand() {
        return "Sync";
    }

    @Override
    public boolean init(final boolean allowReload) {
        final boolean result = super.init(allowReload);
        if (result) {
            mCollectionTypeHandler = getCollectionTypeHandler(mMailbox.mType);
            if (mCollectionTypeHandler == null) {
                return false;
            }
            // Set up traffic stats bookkeeping.
            final int trafficFlags = TrafficFlags.getSyncFlags(mContext, mAccount);
            TrafficStats.setThreadStatsTag(trafficFlags | mCollectionTypeHandler.getTrafficFlag());
        }
        return result;
    }

    @Override
    protected HttpEntity getRequestEntity() throws IOException {
        final String className = Eas.getFolderClass(mMailbox.mType);
        final String syncKey = getSyncKey();
        LogUtils.d(TAG, "Syncing account %d mailbox %d (class %s) with syncKey %s", mAccount.mId,
                mMailbox.mId, className, syncKey);
        mInitialSync = EmailContent.isInitialSyncKey(syncKey);
        final Serializer s = new Serializer();
        s.start(Tags.SYNC_SYNC);
        s.start(Tags.SYNC_COLLECTIONS);
        s.start(Tags.SYNC_COLLECTION);
        // The "Class" element is removed in EAS 12.1 and later versions
        if (getProtocolVersion() < Eas.SUPPORTED_PROTOCOL_EX2007_SP1_DOUBLE) {
            s.data(Tags.SYNC_CLASS, className);
        }
        s.data(Tags.SYNC_SYNC_KEY, syncKey);
        s.data(Tags.SYNC_COLLECTION_ID, mMailbox.mServerId);
        mCollectionTypeHandler.setSyncOptions(mContext, s, getProtocolVersion(), mAccount, mMailbox,
                mInitialSync, mNumWindows);
        s.end().end().end().done();

        return makeEntity(s);
    }

    @Override
    protected int handleResponse(final EasResponse response)
            throws IOException, CommandStatusException {
        try {
            final AbstractSyncParser parser = mCollectionTypeHandler.getParser(mContext, mAccount,
                    mMailbox, response.getInputStream());
            final boolean moreAvailable = parser.parse();
            if (moreAvailable) {
                return RESULT_MORE_AVAILABLE;
            }
        } catch (final Parser.EmptyStreamException e) {
            // This indicates a compressed response which was empty, which is OK.
        }
        return RESULT_DONE;
    }

    @Override
    public int performOperation() {
        int result = RESULT_MORE_AVAILABLE;
        mNumWindows = 1;
        final String key = getSyncKey();
        while (result == RESULT_MORE_AVAILABLE) {
            result = super.performOperation();
            if (result == RESULT_MORE_AVAILABLE || result == RESULT_DONE) {
                mCollectionTypeHandler.cleanup(mContext, mAccount);
            }
            // TODO: Clear pending request queue.
            final String newKey = getSyncKey();
            if (result == RESULT_MORE_AVAILABLE && key.equals(newKey)) {
                LogUtils.e(TAG,
                        "Server has more data but we have the same key: %s numWindows: %d",
                        key, mNumWindows);
                mNumWindows++;
            } else {
                mNumWindows = 1;
            }
        }
        return result;
    }

    @Override
    protected long getTimeout() {
        if (mInitialSync) {
            return 120 * DateUtils.SECOND_IN_MILLIS;
        }
        return super.getTimeout();
    }

    /**
     * Get an instance of the correct {@link EasSyncCollectionTypeBase} for a specific collection
     * type.
     * @param type The type of the {@link Mailbox} that we're trying to sync.
     * @return An {@link EasSyncCollectionTypeBase} appropriate for this type.
     */
    private EasSyncCollectionTypeBase getCollectionTypeHandler(final int type) {
        switch (type) {
            case Mailbox.TYPE_MAIL:
            case Mailbox.TYPE_INBOX:
            case Mailbox.TYPE_DRAFTS:
            case Mailbox.TYPE_SENT:
            case Mailbox.TYPE_TRASH:
            case Mailbox.TYPE_JUNK:
                return new EasSyncMail();
            case Mailbox.TYPE_CALENDAR: {
                return new EasSyncCalendar(mContext, mAccount, mMailbox);
            }
            case Mailbox.TYPE_CONTACTS:
                return new EasSyncContacts(mAccount.mEmailAddress);
            default:
                LogUtils.e(LOG_TAG, "unexpected collectiontype %d", type);
                return null;
        }
    }
}
