/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

package org.mozilla.gecko.db;

import android.content.ContentResolver;
import android.content.ContentValues;
import android.database.Cursor;
import android.net.Uri;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.util.Log;

import org.json.JSONException;
import org.mozilla.gecko.annotation.RobocopTarget;
import org.mozilla.gecko.db.BrowserContract.UrlAnnotations.Key;
import org.mozilla.gecko.feeds.subscriptions.FeedSubscription;

public class LocalUrlAnnotations implements UrlAnnotations {
    private static final String LOGTAG = "LocalUrlAnnotations";

    private Uri urlAnnotationsTableWithProfile;

    public LocalUrlAnnotations(final String profile) {
        urlAnnotationsTableWithProfile = DBUtils.appendProfile(profile, BrowserContract.UrlAnnotations.CONTENT_URI);
    }

    /**
     * Get all feed subscriptions.
     */
    @Override
    public Cursor getFeedSubscriptions(ContentResolver cr) {
        return queryByKey(cr,
                Key.FEED_SUBSCRIPTION,
                new String[] { BrowserContract.UrlAnnotations.URL, BrowserContract.UrlAnnotations.VALUE },
                null);
    }

    /**
     * Insert mapping from website URL to URL of the feed.
     */
    @Override
    public void insertFeedUrl(ContentResolver cr, String originUrl, String feedUrl) {
        insertAnnotation(cr, originUrl, Key.FEED, feedUrl);
    }

    @Override
    public boolean hasAcceptedOrDeclinedHomeScreenShortcut(ContentResolver cr, String url) {
        return hasResultsForSelection(cr,
                BrowserContract.UrlAnnotations.URL + " = ?",
                new String[]{url});
    }

    @Override
    public void insertHomeScreenShortcut(ContentResolver cr, String url, boolean hasCreatedShortCut) {
        insertAnnotation(cr, url, Key.HOME_SCREEN_SHORTCUT, String.valueOf(hasCreatedShortCut));
    }

    /**
     * Returns true if there's a mapping from the given website URL to a feed URL. False otherwise.
     */
    @Override
    public boolean hasFeedUrlForWebsite(ContentResolver cr, String websiteUrl) {
        return hasResultsForSelection(cr,
                BrowserContract.UrlAnnotations.URL + " = ? AND " + BrowserContract.UrlAnnotations.KEY + " = ?",
                new String[]{websiteUrl, Key.FEED.getDbValue()});
    }

    /**
     * Returns true if there's a website URL with this feed URL. False otherwise.
     */
    @Override
    public boolean hasWebsiteForFeedUrl(ContentResolver cr, String feedUrl) {
        return hasResultsForSelection(cr,
                BrowserContract.UrlAnnotations.VALUE + " = ? AND " + BrowserContract.UrlAnnotations.KEY + " = ?",
                new String[]{feedUrl, Key.FEED.getDbValue()});
    }

    /**
     * Delete the feed URL mapping for this website URL.
     */
    @Override
    public void deleteFeedUrl(ContentResolver cr, String websiteUrl) {
        deleteAnnotation(cr, websiteUrl, Key.FEED);
    }

    /**
     * Get website URLs that are mapped to the given feed URL.
     */
    @Override
    public Cursor getWebsitesWithFeedUrl(ContentResolver cr) {
        return cr.query(urlAnnotationsTableWithProfile,
                new String[] { BrowserContract.UrlAnnotations.URL },
                BrowserContract.UrlAnnotations.KEY + " = ?",
                new String[] { Key.FEED.getDbValue() },
                null);
    }

    /**
     * Returns true if there's a subscription for this feed URL. False otherwise.
     */
    @Override
    public boolean hasFeedSubscription(ContentResolver cr, String feedUrl) {
        return hasResultsForSelection(cr,
                BrowserContract.UrlAnnotations.URL + " = ? AND " + BrowserContract.UrlAnnotations.KEY + " = ?",
                new String[]{feedUrl, Key.FEED_SUBSCRIPTION.getDbValue()});
    }

    /**
     * Insert the given feed subscription (Mapping from feed URL to the subscription object).
     */
    @Override
    public void insertFeedSubscription(ContentResolver cr, FeedSubscription subscription) {
        try {
            insertAnnotation(cr, subscription.getFeedUrl(), Key.FEED_SUBSCRIPTION, subscription.toJSON().toString());
        } catch (JSONException e) {
            Log.w(LOGTAG, "Could not serialize subscription");
        }
    }

    /**
     * Update the feed subscription with new values.
     */
    @Override
    public void updateFeedSubscription(ContentResolver cr, FeedSubscription subscription) {
        try {
            updateAnnotation(cr, subscription.getFeedUrl(), Key.FEED_SUBSCRIPTION, subscription.toJSON().toString());
        } catch (JSONException e) {
            Log.w(LOGTAG, "Could not serialize subscription");
        }
    }

    /**
     * Delete the subscription for the feed URL.
     */
    @Override
    public void deleteFeedSubscription(ContentResolver cr, FeedSubscription subscription) {
        deleteAnnotation(cr, subscription.getFeedUrl(), Key.FEED_SUBSCRIPTION);
    }

    private int deleteAnnotation(final ContentResolver cr, final String url, final Key key) {
        return cr.delete(urlAnnotationsTableWithProfile,
                BrowserContract.UrlAnnotations.KEY + " = ? AND " + BrowserContract.UrlAnnotations.URL + " = ?",
                new String[] { key.getDbValue(), url  });
    }

    private int updateAnnotation(final ContentResolver cr, final String url, final Key key, final String value) {
        ContentValues values = new ContentValues();
        values.put(BrowserContract.UrlAnnotations.VALUE, value);
        values.put(BrowserContract.UrlAnnotations.DATE_MODIFIED, System.currentTimeMillis());

        return cr.update(urlAnnotationsTableWithProfile,
                values,
                BrowserContract.UrlAnnotations.KEY + " = ? AND " + BrowserContract.UrlAnnotations.URL + " = ?",
                new String[]{key.getDbValue(), url});
    }

    private void insertAnnotation(final ContentResolver cr, final String url, final Key key, final String value) {
        insertAnnotation(cr, url, key.getDbValue(), value);
    }

    @RobocopTarget
    @Override
    public void insertAnnotation(final ContentResolver cr, final String url, final String key, final String value) {
        final long creationTime = System.currentTimeMillis();
        final ContentValues values = new ContentValues(5);
        values.put(BrowserContract.UrlAnnotations.URL, url);
        values.put(BrowserContract.UrlAnnotations.KEY, key);
        values.put(BrowserContract.UrlAnnotations.VALUE, value);
        values.put(BrowserContract.UrlAnnotations.DATE_CREATED, creationTime);
        values.put(BrowserContract.UrlAnnotations.DATE_MODIFIED, creationTime);
        cr.insert(urlAnnotationsTableWithProfile, values);
    }

    /**
     * @return true if the table contains rows for the given selection.
     */
    private boolean hasResultsForSelection(ContentResolver cr, String selection, String[] selectionArgs) {
        Cursor cursor = cr.query(urlAnnotationsTableWithProfile,
                new String[] { BrowserContract.UrlAnnotations._ID },
                selection,
                selectionArgs,
                null);
        if (cursor == null) {
            return false;
        }

        try {
            return cursor.getCount() > 0;
        } finally {
            cursor.close();
        }
    }

    private Cursor queryByKey(final ContentResolver cr, @NonNull final Key key, @Nullable final String[] projections,
                @Nullable final String sortOrder) {
        return cr.query(urlAnnotationsTableWithProfile,
                projections,
                BrowserContract.UrlAnnotations.KEY + " = ?", new String[] { key.getDbValue() },
                sortOrder);
    }

    @Override
    public Cursor getScreenshots(ContentResolver cr) {
        return queryByKey(cr,
                Key.SCREENSHOT,
                new String[] {
                        BrowserContract.UrlAnnotations._ID,
                        BrowserContract.UrlAnnotations.URL,
                        BrowserContract.UrlAnnotations.KEY,
                        BrowserContract.UrlAnnotations.VALUE,
                        BrowserContract.UrlAnnotations.DATE_CREATED,
                },
                BrowserContract.UrlAnnotations.DATE_CREATED + " DESC");
    }

    public void insertScreenshot(final ContentResolver cr, final String pageUrl, final String screenshotPath) {
        insertAnnotation(cr, pageUrl, Key.SCREENSHOT.getDbValue(), screenshotPath);
    }

    @Override
    public void insertReaderViewUrl(final ContentResolver cr, final String pageUrl) {
        insertAnnotation(cr, pageUrl, Key.READER_VIEW.getDbValue(), BrowserContract.UrlAnnotations.READER_VIEW_SAVED_VALUE);
    }

    @Override
    public void deleteReaderViewUrl(ContentResolver cr, String pageURL) {
        deleteAnnotation(cr, pageURL, Key.READER_VIEW);
    }

    public int getAnnotationCount(ContentResolver cr, Key key) {
        final String countColumnname = "count";
        final Cursor c = queryByKey(cr,
                key,
                new String[] {
                        "COUNT(*) AS " + countColumnname
                },
                null);

        try {
            if (c != null && c.moveToFirst()) {
                return c.getInt(c.getColumnIndexOrThrow(countColumnname));
            } else {
                return 0;
            }
        } finally {
            if (c != null) {
                c.close();
            }
        }
    }
}
