/*
 * Copyright 2014 Google Inc. All rights reserved.
 *
 * 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.google.samples.apps.iosched.util;

import com.google.android.gms.analytics.GoogleAnalytics;
import com.google.android.gms.analytics.HitBuilders;
import com.google.android.gms.analytics.Tracker;
import com.google.samples.apps.iosched.BuildConfig;
import com.google.samples.apps.iosched.R;
import com.google.samples.apps.iosched.settings.ConfMessageCardUtils;
import com.google.samples.apps.iosched.settings.SettingsUtils;

import android.content.Context;
import android.content.SharedPreferences;
import android.preference.PreferenceManager;
import android.util.Log;

import static com.google.samples.apps.iosched.util.LogUtils.LOGD;

/**
 * Centralized Analytics interface to ensure proper initialization and
 * consistent analytics application across the app.
 *
 * For the purposes of this application, initialization of the Analytics tracker is broken
 * into two steps.  {@link #prepareAnalytics(Context)} is called upon app creation, which sets up
 * a listener for changes to shared settings_prefs.  When the user agrees to TOS, the listener triggers
 * the actual initialization step, setting up a Google Analytics tracker.  This ensures that
 * no data is collected or accidentally sent before the TOS step, and that campaign tracking data
 * isn't accidentally deleted by starting and immediately disabling a tracker upon app creation.
 *
 */
public class AnalyticsHelper {

    private final static String TAG = LogUtils.makeLogTag(AnalyticsHelper.class);

    private static Context sAppContext = null;

    private static Tracker mTracker;

    /** Custom dimension slot number for the "attendee at venue" preference.
     * There's a finite number of custom dimensions, and they need to consistently be sent
     * in the same index in order to be tracked properly.  For each custom dimension or metric,
     * always reserve an index.
     */
    private static final int SLOT_ATTENDING_DIMENSION = 1;

    /**
     * The {@link PreferenceManager doesn't store a strong references to preference change
     * listeners.  To prevent one from being garbage collected, a strong reference must be
     * created in app code.
     */
    private static SharedPreferences.OnSharedPreferenceChangeListener sPrefListener;

    /**
     * Log a specific screen view under the {@code screenName} string.
     */
    public static void sendScreenView(String screenName) {
        if (isInitialized()) {
            mTracker.setScreenName(screenName);
            mTracker.send(new HitBuilders.AppViewBuilder().build());
            LOGD(TAG, "Screen View recorded: " + screenName);
        }
    }

    /**
     * Log a specific event under the {@code category}, {@code action}, and {@code label}.
     */
    public static void sendEvent(String category, String action, String label, long value,
                                 HitBuilders.EventBuilder eventBuilder) {
        if(isInitialized()) {
            mTracker.send(eventBuilder
                    .setCategory(category)
                    .setAction(action)
                    .setLabel(label)
                    .setValue(value)
                    .build());

            LOGD(TAG, "Event recorded: \n" +
                    "\tCategory: " + category +
                    "\tAction: " + action +
                    "\tLabel: " + label +
                    "\tValue: " + value);
        }
    }

    /**
     * Log an specific event under the {@code category}, {@code action}, and {@code label}.
     */
    public static void sendEvent(String category, String action, String label) {
        HitBuilders.EventBuilder eventBuilder = new HitBuilders.EventBuilder();
        sendEvent(category, action, label, 0, eventBuilder);
    }

    /**
     * Log an specific event under the {@code category}, {@code action}, and {@code label}.  Attach
     * a custom dimension using the provided {@code dimensionIndex} and {@code dimensionValue}
     */
    public static void sendEventWithCustomDimension(String category, String action, String label,
                                                    int dimensionIndex, String dimensionValue) {
        // Create a new HitBuilder, populate it with the custom dimension, and send it along
        // to the rest of the event building process.
        HitBuilders.EventBuilder eventBuilder = new HitBuilders.EventBuilder();
        eventBuilder.setCustomDimension(dimensionIndex, dimensionValue);
        sendEvent(category, action, label, 0, eventBuilder);

        LOGD(TAG, "Custom Dimension Attached:\n" +
                "\tindex: " + dimensionIndex +
                "\tvalue: " + dimensionValue);
    }

    /**
     * Sets up Analytics to be initialized when the user agrees to TOS.  If the user has already
     * done so (all runs of the app except the first run), initialize analytics Immediately. Note
     * that {@applicationContext} must be the Application level {@link Context} or this class will
     * leak the context.
     *
     * @param applicationContext  The context that will later be used to initialize Analytics.
     */
    public static void prepareAnalytics(Context applicationContext) {
        sAppContext = applicationContext;

        // The listener will initialize Analytics when the TOS is signed, or enable/disable
        // Analytics based on the "anonymous data collection" setting.
        setupPreferenceChangeListener();

        // If TOS hasn't been signed yet, it's the first run.  Exit.
        if (SettingsUtils.isTosAccepted(sAppContext)) {
            initializeAnalyticsTracker(sAppContext);
        }
    }

    /**
     * Initialize the analytics tracker in use by the application. This should only be called
     * once, when the TOS is signed. The {@code applicationContext} parameter MUST be the
     * application context or an object leak could occur.
     */
    private static synchronized void initializeAnalyticsTracker(Context applicationContext) {
        sAppContext = applicationContext;
        if (mTracker == null) {
            int useProfile;
            if (BuildConfig.DEBUG) {
                LOGD(TAG, "Analytics manager using DEBUG ANALYTICS PROFILE.");
                useProfile = R.xml.analytics_debug;
            } else {
                useProfile = R.xml.analytics_release;
            }

            try {
                mTracker = GoogleAnalytics.getInstance(applicationContext).newTracker(useProfile);
            } catch (Exception e) {
                // If anything goes wrong, force an opt-out of tracking. It's better to accidentally
                // protect privacy than accidentally collect data.
                setAnalyticsEnabled(false);
            }
        }
    }

    /**
     * Listens for preference changes.  When a preference change relevant to toggling Analytics
     * is detected, {@link AnalyticsHelper#enableOrDisableAnalyticsAsNecessary()} is called, which
     * will decide whether Analytics should be enabled or disabled based on settings_prefs and
     * application state.
     */
    private static void setupPreferenceChangeListener() {
        SharedPreferences userPrefs = PreferenceManager.getDefaultSharedPreferences(sAppContext);
        sPrefListener = new SharedPreferences.OnSharedPreferenceChangeListener() {

            @Override
            public void onSharedPreferenceChanged(SharedPreferences prefs, String key) {

                // Most of the preferences will use these defaults.
                String category = "Preference";

                if (key != null) {
                    if (key.equals(SettingsUtils.PREF_TOS_ACCEPTED)
                            || key.equals(SettingsUtils.PREF_ANALYTICS_ENABLED)) {

                        // If TOS is accepted, initialize the Analytics Tracker.
                        if (key.equals(SettingsUtils.PREF_TOS_ACCEPTED)
                                && prefs.getBoolean(key, false)
                                && mTracker == null) {
                            initializeAnalyticsTracker(sAppContext);
                        }

                        // Technically it's possible to just look up the values in the pref
                        // object provided and enable/disable in here, but it's safer to have all the
                        // "should analytics run" logic collected in one place.
                        enableOrDisableAnalyticsAsNecessary();
                    } else if (key.equals(SettingsUtils.PREF_LOCAL_TIMES)) {
                        String label = "Local time";
                        // ANALYTICS EVENT:  Updated "Show Local Times" setting.
                        // Contains: The checkbox state of this setting.
                        sendEvent(category, getAction(prefs, key), label);
                    } else if (key.equals(SettingsUtils.PREF_ATTENDEE_AT_VENUE)) {
                        // Toggle the "Attending in person" custom dimension so we can track
                        // how venue attendee behavior contrasts with remote attendee behavior.
                        boolean attending = prefs.getBoolean(key, true);
                        // ANALYTICS EVENT:  Updated "On-Site Attendee" preference.
                        // Contains: Whether the attendee is identifying themselves as onsite or remote.
                        String attendeeType = attending ? "On-Site Attendee" : "Remote Attendee";
                        String label = "Will be at I/O";

                        sendEventWithCustomDimension(category, getAction(prefs, key), label,
                                SLOT_ATTENDING_DIMENSION,
                                attendeeType);
                    } else if (key.equals(ConfMessageCardUtils.PREF_CONF_MESSAGE_CARDS_ENABLED)) {
                        String label = "Conference Notification Cards";
                        // ANALYTICS EVENT:  Updated "Conference Notification Cards" setting.
                        // Contains: The checkbox state of this setting.
                        sendEvent(category, getAction(prefs, key), label);
                    } else if (key.equals(SettingsUtils.PREF_SYNC_CALENDAR)) {
                        String label = "Sync with Google Calendar";
                        // ANALYTICS EVENT:  Updated "Sync with Google Calendar" setting.
                        // Contains: The checkbox state of this setting.
                        sendEvent(category, getAction(prefs, key), label);
                    } else if (key.equals(SettingsUtils.PREF_SHOW_SESSION_REMINDERS)) {
                        String label = "Session Reminders";
                        // ANALYTICS EVENT:  Updated "Session Reminders" setting.
                        // Contains: The checkbox state of this setting.
                        sendEvent(category, getAction(prefs, key), label);
                    } else if (key.equals(SettingsUtils.PREF_SHOW_SESSION_FEEDBACK_REMINDERS)) {
                        String label = "Feedback Reminders";
                        // ANALYTICS EVENT:  Updated "Feedback Reminders" setting.
                        // Contains: The checkbox state of this setting.
                        sendEvent(category, getAction(prefs, key), label);
                    }
                }
            }
        };
        userPrefs.registerOnSharedPreferenceChangeListener(sPrefListener);
    }

    private static String getAction(SharedPreferences prefs, String key) {
        return prefs.getBoolean(key, true) ? "Checked" : "Unchecked";
    }

    /**
     * Return the current initialization state which indicates whether events can be logged.
     */
    private static boolean isInitialized() {
        // Google Analytics is initialized when this class has a reference to an app context and
        // an Analytics tracker has been created.
        return sAppContext != null // Is there an app context?
                && mTracker != null; // Is there a tracker?
    }

    /**
     * Performs the checks to determine if Analytics should be enabled.
     * @return whether or not it's safe to enable Analytics.
     */
    private static boolean shouldEnableAnalytics() {
        // Analytics shouldn't run unless all the following are true:
        // 1) A tracker has been initialized in this class (as opposed to elsewhere in the app).
        // 2) The user has accepted TOS.
        // 3) "Anonymous usage data" is enabled in settings.
        return isInitialized() // Has Analytics been initialized?
                && SettingsUtils.isTosAccepted(sAppContext) // User has accepted TOS.
                && SettingsUtils.isAnalyticsEnabled(sAppContext); // Analytics enabled in settings.
    }

    /**
     * Checks application state and settings_prefs, then explicitly either enables or
     * disables the tracker.
     */
    public static void enableOrDisableAnalyticsAsNecessary() {
        try {
            setAnalyticsEnabled(shouldEnableAnalytics());
            LOGD(TAG, "Analytics" + (isInitialized() ? "" : " not") + " initialized"
                    + ", TOS" + (SettingsUtils.isTosAccepted(sAppContext) ? "" : " not") + " accepted"
                    + ", Setting is" + (SettingsUtils.isAnalyticsEnabled(sAppContext) ? "" : " not")
                    + " checked");
        } catch (Exception e) {
            setAnalyticsEnabled(false);
        }
    }

    /**
     * Enables or disables Analytics.
     * @param enableAnalytics Whether analytics should be enabled.
     */
    private static void setAnalyticsEnabled(boolean enableAnalytics) {
        GoogleAnalytics instance  = GoogleAnalytics.getInstance(sAppContext);
        if (instance != null) {
            instance.setAppOptOut(!enableAnalytics);
            LOGD(TAG, "Analytics enabled: " + enableAnalytics);
        }

    }
}
