/*
 * Copyright (C) 2008-2009 Marc Blank
 * Licensed to The Android Open Source Project.
 *
 * 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 com.android.exchange;

import com.android.emailcommon.provider.Account;
import com.android.emailcommon.provider.HostAuth;
import com.android.emailcommon.provider.Mailbox;
import com.android.exchange.utility.FileLogger;

import android.content.Context;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.net.NetworkInfo.DetailedState;
import android.os.Bundle;
import android.util.Log;

import java.util.concurrent.LinkedBlockingQueue;

/**
 * Base class for all protocol services SyncManager (extends Service, implements
 * Runnable) instantiates subclasses to run a sync (either timed, or push, or
 * mail placed in outbox, etc.) EasSyncService is currently implemented; my goal
 * would be to move IMAP to this structure when it comes time to introduce push
 * functionality.
 */
public abstract class AbstractSyncService implements Runnable {

    public String TAG = "AbstractSyncService";

    public static final int SECONDS = 1000;
    public static final int MINUTES = 60*SECONDS;
    public static final int HOURS = 60*MINUTES;
    public static final int DAYS = 24*HOURS;

    public static final int CONNECT_TIMEOUT = 30*SECONDS;
    public static final int NETWORK_WAIT = 15*SECONDS;

    public static final String EAS_PROTOCOL = "eas";
    public static final int EXIT_DONE = 0;
    public static final int EXIT_IO_ERROR = 1;
    public static final int EXIT_LOGIN_FAILURE = 2;
    public static final int EXIT_EXCEPTION = 3;
    public static final int EXIT_SECURITY_FAILURE = 4;
    public static final int EXIT_ACCESS_DENIED = 5;

    public Mailbox mMailbox;
    protected long mMailboxId;
    protected int mExitStatus = EXIT_EXCEPTION;
    protected String mMailboxName;
    public Account mAccount;
    public Context mContext;
    public int mChangeCount = 0;
    public volatile int mSyncReason = 0;
    protected volatile boolean mStop = false;
    protected volatile Thread mThread;
    protected final Object mSynchronizer = new Object();

    protected volatile long mRequestTime = 0;
    protected LinkedBlockingQueue<Request> mRequestQueue = new LinkedBlockingQueue<Request>();

    /**
     * Sent by SyncManager to request that the service stop itself cleanly
     */
    public abstract void stop();

    /**
     * Sent by SyncManager to indicate that an alarm has fired for this service, and that its
     * pending (network) operation has timed out. The service is NOT automatically stopped,
     * although the behavior is service dependent.
     *
     * @return true if the operation was stopped normally; false if the thread needed to be
     * interrupted.
     */
    public abstract boolean alarm();

    /**
     * Sent by SyncManager to request that the service reset itself cleanly; the meaning of this
     * operation is service dependent.
     */
    public abstract void reset();

    /**
     * Called to validate an account; abstract to allow each protocol to do what
     * is necessary. For consistency with the Email app's original
     * functionality, success is indicated by a failure to throw an Exception
     * (ugh). Parameters are self-explanatory
     *
     * @param hostAuth
     * @return a Bundle containing a result code and, depending on the result, a PolicySet or an
     * error message
     */
    public abstract Bundle validateAccount(HostAuth hostAuth, Context context);

    public AbstractSyncService(Context _context, Mailbox _mailbox) {
        mContext = _context;
        mMailbox = _mailbox;
        mMailboxId = _mailbox.mId;
        mMailboxName = _mailbox.mServerId;
        mAccount = Account.restoreAccountWithId(_context, _mailbox.mAccountKey);
    }

    // Will be required when subclasses are instantiated by name
    public AbstractSyncService(String prefix) {
    }

    /**
     * The UI can call this static method to perform account validation.  This method wraps each
     * protocol's validateAccount method.   Arguments are self-explanatory, except where noted.
     *
     * @param klass the protocol class (EasSyncService.class for example)
     * @param hostAuth
     * @param context
     * @return a Bundle containing a result code and, depending on the result, a PolicySet or an
     * error message
     */
    public static Bundle validate(Class<? extends AbstractSyncService> klass,
            HostAuth hostAuth, Context context) {
        AbstractSyncService svc;
        try {
            svc = klass.newInstance();
            return svc.validateAccount(hostAuth, context);
        } catch (IllegalAccessException e) {
        } catch (InstantiationException e) {
        }
        return null;
    }

    public static class ValidationResult {
        static final int NO_FAILURE = 0;
        static final int CONNECTION_FAILURE = 1;
        static final int VALIDATION_FAILURE = 2;
        static final int EXCEPTION = 3;

        static final ValidationResult succeeded = new ValidationResult(true, NO_FAILURE, null);
        boolean success;
        int failure = NO_FAILURE;
        String reason = null;
        Exception exception = null;

        ValidationResult(boolean _success, int _failure, String _reason) {
            success = _success;
            failure = _failure;
            reason = _reason;
        }

        ValidationResult(boolean _success) {
            success = _success;
        }

        ValidationResult(Exception e) {
            success = false;
            failure = EXCEPTION;
            exception = e;
        }

        public boolean isSuccess() {
            return success;
        }

        public String getReason() {
            return reason;
        }
    }

    public boolean isStopped() {
        return mStop;
    }

    public Object getSynchronizer() {
        return mSynchronizer;
    }

    /**
     * Convenience methods to do user logging (i.e. connection activity).  Saves a bunch of
     * repetitive code.
     */
    public void userLog(String string, int code, String string2) {
        if (Eas.USER_LOG) {
            userLog(string + code + string2);
        }
    }

    public void userLog(String string, int code) {
        if (Eas.USER_LOG) {
            userLog(string + code);
        }
    }

    public void userLog(String str, Exception e) {
        if (Eas.USER_LOG) {
            Log.e(TAG, str, e);
        } else {
            Log.e(TAG, str + e);
        }
        if (Eas.FILE_LOG) {
            FileLogger.log(e);
        }
    }

    /**
     * Standard logging for EAS.
     * If user logging is active, we concatenate any arguments and log them using Log.d
     * We also check for file logging, and log appropriately
     * @param strings strings to concatenate and log
     */
    public void userLog(String ...strings) {
        if (Eas.USER_LOG) {
            String logText;
            if (strings.length == 1) {
                logText = strings[0];
            } else {
                StringBuilder sb = new StringBuilder(64);
                for (String string: strings) {
                    sb.append(string);
                }
                logText = sb.toString();
            }
            Log.d(TAG, logText);
            if (Eas.FILE_LOG) {
                FileLogger.log(TAG, logText);
            }
        }
    }

    /**
     * Error log is used for serious issues that should always be logged
     * @param str the string to log
     */
    public void errorLog(String str) {
        Log.e(TAG, str);
        if (Eas.FILE_LOG) {
            FileLogger.log(TAG, str);
        }
    }

    /**
     * Waits for up to 10 seconds for network connectivity; returns whether or not there is
     * network connectivity.
     *
     * @return whether there is network connectivity
     */
    public boolean hasConnectivity() {
        ConnectivityManager cm =
                (ConnectivityManager)mContext.getSystemService(Context.CONNECTIVITY_SERVICE);
        int tries = 0;
        while (tries++ < 1) {
            // Use the same test as in ExchangeService#waitForConnectivity
            // TODO: Create common code for this test in emailcommon
            NetworkInfo info = cm.getActiveNetworkInfo();
            if (info != null) {
                return true;
            }
            try {
                Thread.sleep(10*SECONDS);
            } catch (InterruptedException e) {
            }
        }
        return false;
    }

    /**
     * Request handling (common functionality)
     * Can be overridden if desired
     */

    public void addRequest(Request req) {
        mRequestQueue.offer(req);
    }

    public void removeRequest(Request req) {
        mRequestQueue.remove(req);
    }

    public boolean hasPendingRequests() {
        return !mRequestQueue.isEmpty();
    }

    public void clearRequests() {
        mRequestQueue.clear();
    }
}
