package com.android.exchange.eas;

import android.content.ContentValues;
import android.content.Context;
import android.content.SyncResult;

import com.android.emailcommon.Logging;
import com.android.emailcommon.provider.Mailbox;
import com.android.emailcommon.service.SearchParams;
import com.android.exchange.CommandStatusException;
import com.android.exchange.Eas;
import com.android.exchange.EasResponse;
import com.android.exchange.adapter.Serializer;
import com.android.exchange.adapter.Tags;
import com.android.exchange.adapter.SearchParser;
import com.android.mail.providers.UIProvider;
import com.android.mail.utils.LogUtils;

import org.apache.http.HttpEntity;
import org.apache.http.entity.ByteArrayEntity;

import java.io.BufferedOutputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;

public class EasSearch extends EasOperation {

    public final static int RESULT_NO_MESSAGES = 0;
    public final static int RESULT_OK = 1;
    public final static int RESULT_EMPTY_RESPONSE = 2;

    // The shortest search query we'll accept
    // TODO Check with UX whether this is correct
    private static final int MIN_QUERY_LENGTH = 3;
    // The largest number of results we'll ask for per server request
    private static final int MAX_SEARCH_RESULTS = 100;

    final SearchParams mSearchParams;
    final long mDestMailboxId;
    int mTotalResults;

    public EasSearch(final Context context, final long accountId, final SearchParams searchParams,
        final long destMailboxId) {
        super(context, accountId);
        mSearchParams = searchParams;
        mDestMailboxId = destMailboxId;
    }

    public int getTotalResults() {
        return mTotalResults;
    }

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

    @Override
    protected HttpEntity getRequestEntity() throws IOException {
        // Sanity check for arguments
        final int offset = mSearchParams.mOffset;
        final int limit = mSearchParams.mLimit;
        final String filter = mSearchParams.mFilter;
        if (limit < 0 || limit > MAX_SEARCH_RESULTS || offset < 0) {
            return null;
        }
        // TODO Should this be checked in UI?  Are there guidelines for minimums?
        if (filter == null || filter.length() < MIN_QUERY_LENGTH) {
            LogUtils.w(LOG_TAG, "filter too short");
            return null;
        }

        int res = 0;
        final Mailbox searchMailbox = Mailbox.restoreMailboxWithId(mContext, mDestMailboxId);
        // Sanity check; account might have been deleted?
        if (searchMailbox == null) {
            LogUtils.i(LOG_TAG, "search mailbox ceased to exist");
            return null;
        }
        final ContentValues statusValues = new ContentValues(2);
        try {
            // Set the status of this mailbox to indicate query
            statusValues.put(Mailbox.UI_SYNC_STATUS, UIProvider.SyncStatus.LIVE_QUERY);
            searchMailbox.update(mContext, statusValues);

            final Serializer s = new Serializer();
            s.start(Tags.SEARCH_SEARCH).start(Tags.SEARCH_STORE);
            s.data(Tags.SEARCH_NAME, "Mailbox");
            s.start(Tags.SEARCH_QUERY).start(Tags.SEARCH_AND);
            s.data(Tags.SYNC_CLASS, "Email");

            // If this isn't an inbox search, then include the collection id
            final Mailbox inbox =
                    Mailbox.restoreMailboxOfType(mContext, mAccount.mId, Mailbox.TYPE_INBOX);
            if (inbox == null) {
                LogUtils.i(LOG_TAG, "Inbox ceased to exist");
                return null;
            }
            if (mSearchParams.mMailboxId != inbox.mId) {
                s.data(Tags.SYNC_COLLECTION_ID, inbox.mServerId);
            }
            s.data(Tags.SEARCH_FREE_TEXT, filter);

            // Add the date window if appropriate
            if (mSearchParams.mStartDate != null) {
                s.start(Tags.SEARCH_GREATER_THAN);
                s.tag(Tags.EMAIL_DATE_RECEIVED);
                s.data(Tags.SEARCH_VALUE, Eas.DATE_FORMAT.format(mSearchParams.mStartDate));
                s.end(); // SEARCH_GREATER_THAN
            }
            if (mSearchParams.mEndDate != null) {
                s.start(Tags.SEARCH_LESS_THAN);
                s.tag(Tags.EMAIL_DATE_RECEIVED);
                s.data(Tags.SEARCH_VALUE, Eas.DATE_FORMAT.format(mSearchParams.mEndDate));
                s.end(); // SEARCH_LESS_THAN
            }
            s.end().end(); // SEARCH_AND, SEARCH_QUERY
            s.start(Tags.SEARCH_OPTIONS);
            if (offset == 0) {
                s.tag(Tags.SEARCH_REBUILD_RESULTS);
            }
            if (mSearchParams.mIncludeChildren) {
                s.tag(Tags.SEARCH_DEEP_TRAVERSAL);
            }
            // Range is sent in the form first-last (e.g. 0-9)
            s.data(Tags.SEARCH_RANGE, offset + "-" + (offset + limit - 1));
            s.start(Tags.BASE_BODY_PREFERENCE);
            s.data(Tags.BASE_TYPE, Eas.BODY_PREFERENCE_HTML);
            s.data(Tags.BASE_TRUNCATION_SIZE, "20000");
            s.end();                    // BASE_BODY_PREFERENCE
            s.end().end().end().done(); // SEARCH_OPTIONS, SEARCH_STORE, SEARCH_SEARCH
            return makeEntity(s);
        } catch (IOException e) {
            LogUtils.d(LOG_TAG, e, "Search exception");
        } finally {
            // TODO: Handle error states
            // Set the status of this mailbox to indicate query over
            statusValues.put(Mailbox.SYNC_TIME, System.currentTimeMillis());
            statusValues.put(Mailbox.UI_SYNC_STATUS, UIProvider.SyncStatus.NO_SYNC);
            searchMailbox.update(mContext, statusValues);
        }
        LogUtils.i(LOG_TAG, "end returning null");
        return null;
    }

    @Override
    protected int handleResponse(final EasResponse response)
        throws IOException, CommandStatusException {
        if (response.isEmpty()) {
            return RESULT_EMPTY_RESPONSE;
        }
        final InputStream is = response.getInputStream();
        try {
            final Mailbox searchMailbox = Mailbox.restoreMailboxWithId(mContext, mDestMailboxId);
            final SearchParser sp = new SearchParser(mContext, mContext.getContentResolver(),
                    is, searchMailbox, mAccount, mSearchParams.mFilter);
            sp.parse();
            mTotalResults = sp.getTotalResults();
        } finally {
            is.close();
        }
        return RESULT_OK;
    }
}
