package se.emilsjolander.sprinkles;

import android.annotation.TargetApi;
import android.app.LoaderManager;
import android.app.LoaderManager.LoaderCallbacks;
import android.content.Loader;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.os.Build;
import android.os.Bundle;

/**
 * Object representing a Query that will return a single result.
 *
 * @param <T>
 *     The type of model to return
 */
public final class OneQuery<T extends QueryResult> {

    /**
     * Implement to get results delivered from a asynchronous query.
     *
     * @param <T>
     *     The type of model that the result will represent.
     */
    public interface ResultHandler<T extends QueryResult> {

        /**
         * @param result
         *      The result of the query.
         *
         * @return whether or not you want updated result when something changes in the underlying data.
         *
         */
        boolean handleResult(T result);
    }

	Class<T> resultClass;
    String placeholderQuery;
	String rawQuery;

	OneQuery() {
	}

    /**
     * Execute the query synchronously
     *
     * @return the result of the query.
     */
	public T get() {
		final SQLiteDatabase db = Sprinkles.getDatabase();
		final Cursor c = db.rawQuery(rawQuery, null);

		T result = null;
		if (c.moveToFirst()) {
			result = Utils.getResultFromCursor(resultClass, c);
		}

		c.close();
		return result;
	}

    /**
     * Execute the query asynchronously
     *
     * @param lm
     *      The loader manager to use for loading the data
     *
     * @param handler
     *      The ResultHandler to notify of the query result and any updates to that result.
     *
     * @param respondsToUpdatedOf
     *      A list of models excluding the queried model that should also trigger a update to the result if they change.
     *
     * @return the id of the loader.
     */
    @TargetApi(Build.VERSION_CODES.HONEYCOMB)
	public int getAsync(LoaderManager lm, ResultHandler<T> handler, Class<? extends Model>... respondsToUpdatedOf) {
        if (Model.class.isAssignableFrom(resultClass)) {
            respondsToUpdatedOf = Utils.concatArrays(respondsToUpdatedOf, new Class[]{resultClass});
        }
		final int loaderId = placeholderQuery.hashCode();
		lm.restartLoader(loaderId, null, getLoaderCallbacks(rawQuery, resultClass, handler, respondsToUpdatedOf));
        return loaderId;
	}

    /**
     * Execute the query asynchronously
     *
     * @param lm
     *      The loader manager to use for loading the data
     *
     * @param handler
     *      The ResultHandler to notify of the query result and any updates to that result.
     *
     * @param respondsToUpdatedOf
     *      A list of models excluding the queried model that should also trigger a update to the result if they change.
     *
     * @return the id of the loader.
     */
	public int getAsync(android.support.v4.app.LoaderManager lm, ResultHandler<T> handler, Class<? extends Model>... respondsToUpdatedOf) {
        if (Model.class.isAssignableFrom(resultClass)) {
            respondsToUpdatedOf = Utils.concatArrays(respondsToUpdatedOf, new Class[]{resultClass});
        }
		final int loaderId = placeholderQuery.hashCode();
		lm.restartLoader(loaderId, null, getSupportLoaderCallbacks(rawQuery, resultClass, handler, respondsToUpdatedOf));
        return loaderId;
	}

    @TargetApi(Build.VERSION_CODES.HONEYCOMB)
    private LoaderCallbacks<Cursor> getLoaderCallbacks(
            final String sqlQuery,
            final Class<T> resultClass,
            final ResultHandler<T> handler,
            final Class<? extends Model>[] respondsToUpdatedOf) {

        return new LoaderCallbacks<Cursor>() {

            @Override
            public void onLoaderReset(Loader<Cursor> loader) {
                // do nothing
            }

            @Override
            public void onLoadFinished(Loader<Cursor> loader, Cursor c) {
                T result = null;
                if (c.moveToFirst()) {
                    result = Utils.getResultFromCursor(resultClass, c);
                }

                if (!handler.handleResult(result)) {
                    loader.abandon();
                }
                c.close();
            }

            @Override
            public Loader<Cursor> onCreateLoader(int id, Bundle args) {
                return new CursorLoader(Sprinkles.sInstance.mContext, sqlQuery, respondsToUpdatedOf);
            }
        };
    }

	private android.support.v4.app.LoaderManager.LoaderCallbacks<Cursor> getSupportLoaderCallbacks(
			final String sqlQuery,
            final Class<T> resultClass,
			final ResultHandler<T> handler,
			final Class<? extends Model>[] respondsToUpdatedOf) {

		return new android.support.v4.app.LoaderManager.LoaderCallbacks<Cursor>() {

			@Override
			public void onLoaderReset(android.support.v4.content.Loader<Cursor> loader) {
                // do nothing
			}

			@Override
			public void onLoadFinished(
					android.support.v4.content.Loader<Cursor> loader, Cursor c) {
				T result = null;
				if (c.moveToFirst()) {
					result = Utils.getResultFromCursor(resultClass, c);
				}

				if (!handler.handleResult(result)) {
					loader.abandon();
				}
                c.close();
			}

			@Override
			public android.support.v4.content.Loader<Cursor> onCreateLoader(
					int id, Bundle args) {
				return new SupportCursorLoader(Sprinkles.sInstance.mContext, sqlQuery, respondsToUpdatedOf);
			}
		};
	}

}