package org.sana.android.service;

import java.util.Collection;
import java.util.PriorityQueue;

import org.sana.android.provider.Encounters;

import android.content.ContentUris;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.net.Uri;
import android.util.Log;

/**
 * Manages the items in the queue awaiting upload.
 *
 * @author Sana Development Team
 */
public class QueueManager {
    private static final String TAG = QueueManager.class.getSimpleName();

    public static final int UPLOAD_STATUS_NOT_IN_QUEUE = -1;
    public static final int UPLOAD_STATUS_WAITING = 1;
    public static final int UPLOAD_STATUS_SUCCESS = 2;
    public static final int UPLOAD_STATUS_IN_PROGRESS = 3;
    public static final int UPLOAD_NO_CONNECTIVITY = 4;
    public static final int UPLOAD_STATUS_FAILURE = 5;
    public static final int UPLOAD_STATUS_CREDENTIALS_INVALID = 6;

    private static final String[] PROJECTION = {Encounters.Contract._ID,
            Encounters.Contract.UUID, Encounters.Contract.PROCEDURE,
            Encounters.Contract.UPLOAD_QUEUE};

    /**
     * Initializes the in-memory queue with what is stored in the database.
     */
    public static PriorityQueue<Uri> initQueue(Context c) {
        PriorityQueue<Uri> queue = new PriorityQueue<Uri>();
        Cursor cursor = null;
        try {
            // Initialize the queue from the database
            Log.i(TAG, "In initQueue - getting queue from database");
            cursor = c.getContentResolver().query(
                    Encounters.CONTENT_URI, PROJECTION,
                    Encounters.Contract.UPLOAD_QUEUE + " >= 0", null,
                    Encounters.QUEUE_SORT_ORDER);
            cursor.moveToFirst();

            int position = 0;
            while (!cursor.isAfterLast()) {
                int savedProcedureId = cursor.getInt(0);
                Uri savedProcedureUri = ContentUris.withAppendedId(
                        Encounters.CONTENT_URI, savedProcedureId);
                Log.i(TAG, "Queue item #" + position + " has URI " + savedProcedureUri);
                queue.add(savedProcedureUri);
                cursor.moveToNext();
                position++;
            }
            Log.i(TAG, "Queue has been extracted from database. Here is the "
                    + " queue: " + queue);
        } catch (Exception e) {
            Log.e(TAG, "Exception in getting queue from database: "
                    + e.toString());
            e.printStackTrace();
        } finally {
            if (cursor != null)
                cursor.close();
        }
        return queue;
    }

    /**
     * Updates upload status of items currently in the queue
     *
     * @param c     the current context
     * @param queue
     */
    public static void updateQueueInDB(Context c, PriorityQueue<Uri> queue) {
        Log.i(TAG, "Updating queue information in the database");
        Log.i(TAG, "Queue is now: " + queue.toString());

        // Reset all saved procedure to have -1 for UPLOAD_QUEUE. This takes
        // everything out of the queue, then we re-add everything that is in the
        // in-memory queue.
        ContentValues cv = new ContentValues();
        cv.put(Encounters.Contract.UPLOAD_QUEUE, -1);
        c.getContentResolver().update(Encounters.CONTENT_URI, cv,
                null, null);

        // TODO(XXX) This loop is inefficient -- O(n^2) when it could be O(n)
        for (Uri procedureUri : queue) {
            cv = new ContentValues();
            int index = queueIndex(queue, procedureUri);
            Log.i(TAG, "In updateQueueInDB, queueIndex(" + procedureUri
                    + ") returns: " + index);
            cv.put(Encounters.Contract.UPLOAD_QUEUE, index);
            c.getContentResolver().update(procedureUri, cv, null, null);
        }
    }

    /**
     * Adds an item to the global queue.
     *
     * @param c            the current context
     * @param queue        the queue to update from
     * @param procedureUri the procedure in the queue
     */
    public static void addToQueue(Context c, PriorityQueue<Uri> queue,
                                  Uri procedureUri) {
        queue.add(procedureUri);
        setProcedureUploadStatus(c, procedureUri, UPLOAD_STATUS_WAITING);
        updateQueueInDB(c, queue);
    }


    /**
     * Removes an item to the global queue.
     *
     * @param c            the current context
     * @param queue        the queue to update from
     * @param procedureUri the procedure in the queue
     */
    public static boolean removeFromQueue(Context c, PriorityQueue<Uri> queue,
                                          Uri procedureUri) {
        return removeFromQueue(c, queue, procedureUri,
                QueueManager.UPLOAD_STATUS_NOT_IN_QUEUE);
    }

    /**
     * Removes an item to the global queue and updates its upload status.
     *
     * @param c            the current context
     * @param queue        the queue to update from
     * @param procedureUri the procedure in the queue
     * @param newStatus    the new upload status
     * @return true if the procedure was in the queue and updated
     */
    public static boolean removeFromQueue(Context c, PriorityQueue<Uri> queue,
                                          Uri procedureUri, int newStatus) {
        if (QueueManager.isInQueue(queue, procedureUri)) {
            queue.remove(procedureUri);
            QueueManager.updateQueueInDB(c, queue);
            QueueManager.setProcedureUploadStatus(c, procedureUri, newStatus);
            return true;
        }
        return false;
    }

    /**
     * Checks whether a procedure is in the queue
     *
     * @param queue        the queue to check
     * @param procedureUri the procedure look for
     * @return true if the procedure was in the queue and updated
     */
    public static boolean isInQueue(PriorityQueue<Uri> queue, Uri procedureUri) {
        return queue.contains(procedureUri);
    }

    /**
     * Finds the location of procedure is in the queue
     *
     * @param queue        the queue to check
     * @param procedureUri the procedure look for
     * @return index of the procedure in the queue or -1
     */
    public static int queueIndex(PriorityQueue<Uri> queue, Uri procedureUri) {
        if (isInQueue(queue, procedureUri)) {
            int index = 0;
            for (Uri uri : queue) {
                if (uri.equals(procedureUri)) {
                    return index;
                }
                index++;
            }
        }
        return -1;
    }

    /**
     * Updates the upload status of a procedure.
     *
     * @param c            the current context
     * @param procedureUri the procedure
     * @param status       the new status
     */
    public static void setProcedureUploadStatus(Context c, Uri procedureUri,
                                                int status) {
        Log.v(TAG, "Setting upload status for " + procedureUri + " to " + status);
        ContentValues cv = new ContentValues();
        cv.put(Encounters.Contract.UPLOAD_STATUS, status);
        c.getContentResolver().update(procedureUri, cv, null, null);
    }

    /**
     * Updates the upload status for a list procedures.
     *
     * @param c             the current context
     * @param procedureUris the procedures to update
     * @param status        the new status
     */
    public static void setProceduresUploadStatus(Context c,
                                                 Collection<Uri> procedureUris, int status) {
        ContentValues cv = new ContentValues();
        cv.put(Encounters.Contract.UPLOAD_STATUS, status);
        for (Uri uri : procedureUris) {
            c.getContentResolver().update(uri, cv, null, null);
        }
    }

}