/**
 * DatapointHelper.java
 * Copyright (C) 2009 Char Software Inc., DBA Localytics
 *
 *  This code is provided under the Localytics Modified BSD License.
 *  A copy of this license has been distributed in a file called LICENSE
 *  with this source code.
 *
 *  Please visit www.localytics.com for more information.
 */

package com.localytics.android;

import java.math.BigInteger;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.TimeZone;

import android.content.Context;
import android.content.pm.PackageManager;
import android.net.wifi.WifiManager;
import android.provider.Settings.System;
import android.telephony.TelephonyManager;
import android.util.Log;
/**
 * Provides a number of static functions to aid in the collection and formatting
 * of datapoints.
 * @author Localytics
 */
@SuppressWarnings("nls")
public final class DatapointHelper
{
	// This class should never be instantiated
	private DatapointHelper() { /**/ }

    private static final String LOG_PREFIX = "(DatapointHelper) ";
    private static final String DROID2_ANDROID_ID = "9774d56d682e549c";

	// Each YAML entry either goes to the session controller, the event controller
	// or the optin controller
	public static final String CONTROLLER_SESSION = "- c: se\n";
	public static final String CONTROLLER_EVENT   = "- c: ev\n";
	public static final String CONTROLLER_OPT     = "- c: optin\n";

	// Each entry is either a create action, or an update action
	public static final String ACTION_CREATE      = "  a: c\n";
    public static final String ACTION_UPDATE      = "  a: u\n";
    public static final String ACTION_OPTIN       = "  a: optin\n";

	// The target object for the data being set up.
	public static final String OBJECT_SESSION_DP  = "  se:\n";
    public static final String OBJECT_EVENT_DP    = "  ev:\n";
    public static final String OBJECT_OPT         = "  optin:\n";

    // Events can have attributes
    public static final String EVENT_ATTRIBUTE    = "   attrs:\n";

    /*******************************
     * WEBSERVICE PARAMENTER NAMES *
     * *****************************
     */

    // Every object has a UUID
    public static final String PARAM_UUID			= "u";

    // The identifier for this application, generated by the user on the webservice
    public static final String PARAM_APP_UUID      	 = "au";

    // The version of this application, taken from the application's manifest.
    public static final String PARAM_APP_VERSION     = "av";

    // A session's UUID as previously created.
    public static final String PARAM_SESSION_UUID    = "su";

    // A hashed identifier unique to this device
    public static final String PARAM_DEVICE_UUID     = "du";

    // android, iphone, blackberry, windowsmobile
    public static final String PARAM_DEVICE_PLATFORM = "dp";

    // maker of this device (currently not supported by Android)
    public static final String PARAM_DEVICE_MAKE     = "dma";

    // model of the device
    public static final String PARAM_DEVICE_MODEL    = "dmo";

    // version of the OS on this device
    public static final String PARAM_OS_VERSION      = "dov";

    // country device is from (obtained by querying the SIM card)
    public static final String PARAM_DEVICE_COUNTRY  = "dc";

    // country the current locale is set to
    public static final String PARAM_LOCALE_COUNTRY  = "dlc";

    // country the language is set to
    public static final String PARAM_LOCALE_LANGUAGE = "dll";

    // Locale as a language_country string.  (Not collected because this info
    //  is already provided by LOCALE_LANGUAGE and LOCALE_COUNTRY.
    public static final String PARAM_LOCALE          = "dl";

    // Country the user is currently in (comes from Sim card)
    public static final String PARAM_NETWORK_COUNTRY = "nc";

    // Current carrier (comes from sim card)
    public static final String PARAM_NETWORK_CARRIER = "nca";

    // Current mobile network code (comes from sim card)
    public static final String PARAM_NETWORK_MNC     = "mnc";

    // current mobile country code (comes from sim card)
    public static final String PARAM_NETWORK_MCC     = "mcc";

    // type of data connection (wifi, umts, gprs, evdo, ...)
    public static final String PARAM_DATA_CONNECTION = "dac";

    // the version of this Localytics client library
    public static final String PARAM_LIBRARY_VERSION = "lv";

    // The source where the location came from
    public static final String PARAM_LOCATION_SOURCE = "ls";

    // the latitude returned by the location provider
    public static final String PARAM_LOCATION_LAT    = "lat";

    // the longitude from the location provider
    public static final String PARAM_LOCATION_LNG    = "lng";

    // the current time on the user's device
    public static final String PARAM_CLIENT_TIME     = "ct";

    // sent at closing time, the current time on the users's device
    public static final String PARAM_CLIENT_CLOSED_TIME = "ctc";

    // The name an event that occured
    public static final String PARAM_EVENT_NAME      = "n";

    // the optin value sent in if a user opts in or out.
    public static final String PARAM_OPT_VALUE       = "optin";

    /**
     * Returns the given key/value pair as a YAML string.  This string is intended to be
     * used to define values for the first level of data in the YAML file.  This is
     * different from the datapoints which belong another level in.
     * @param paramName The name of the parameter
     * @param paramValue The value of the parameter
     * @param paramIndent The indent level of the parameter
     * @return a YAML string which can be dumped to the YAML file
     */
    public static String formatYAMLLine(String paramName,String paramValue, int paramIndent)
    {
        if (paramName.length() > LocalyticsSession.MAX_NAME_LENGTH)
        {
            Log.v(DatapointHelper.LOG_PREFIX, "Parameter name exceeds "
                    + LocalyticsSession.MAX_NAME_LENGTH + " character limit.  Truncating.");
            paramName = paramName.substring(0, LocalyticsSession.MAX_NAME_LENGTH);
        }
        if (paramValue.length() > LocalyticsSession.MAX_NAME_LENGTH)
        {
            Log.v(DatapointHelper.LOG_PREFIX, "Parameter value exceeds "
                    + LocalyticsSession.MAX_NAME_LENGTH + " character limit.  Truncating.");
            paramValue = paramValue.substring(0, LocalyticsSession.MAX_NAME_LENGTH);
        }
    	// The params are stored in the second tier of the YAML data.
    	// so with spacing, the expected result is: "    paramname:paramvalue\n"
        StringBuffer formattedString = new StringBuffer();
        for (int currentIndent = 0; currentIndent < paramIndent; currentIndent++)
        {
            formattedString.append(" ");
        }

        formattedString.append(escapeString(paramName));
        formattedString.append(": ");

        // Escape the string.
        formattedString.append(escapeString(paramValue));

        formattedString.append("\n");

        return formattedString.toString();
    }

    /**
     * Gets a 1-way hashed value of the device's unique ID.  This value is encoded using a SHA-256
     * one way hash and cannot be used to determine what device this data came from.
     * @param appContext The context used to access the settings resolver
     * @return An 1-way hashed identifier unique to this device or null if an ID, or the hashing
     * algorithm is not available.
     */
    public static String getGlobalDeviceId(final Context appContext)
    {
    	String systemId = System.getString(appContext.getContentResolver(), System.ANDROID_ID);
    	if(systemId == null || systemId.toLowerCase().equals(DROID2_ANDROID_ID))
    	{
    		return null;
    	}

    	try
    	{
    		MessageDigest md = MessageDigest.getInstance("SHA-256");
    		byte[] digest = md.digest(systemId.getBytes());
    		BigInteger hashedNumber = new BigInteger(1, digest);
    		return new String(hashedNumber.toString(16));

    	}
    	catch(NoSuchAlgorithmException e)
    	{
    		return null;
    	}
    }

    /**
     * Determines the type of network this device is connected to.
     * @param appContext the context used to access the device's WIFI
     * @param telephonyManager The manager used to access telephony info
     * @return The type of network, or unknown if the information is unavailable
     */
    public static String getNetworkType(
    									final Context appContext,
    									TelephonyManager telephonyManager)
    {
    	WifiManager wifiManager = (WifiManager)appContext.getSystemService(Context.WIFI_SERVICE);

    	// this will only work for apps which already have wifi permissions.
    	try
    	{
    		if(wifiManager.isWifiEnabled())
    		{
    			return "wifi";
    		}
    	}
    	catch (Exception e) { /**/ }

    	switch (telephonyManager.getNetworkType())
        {
            case TelephonyManager.NETWORK_TYPE_EDGE : return "edge";
            case TelephonyManager.NETWORK_TYPE_GPRS : return "GPRS";
            case TelephonyManager.NETWORK_TYPE_UMTS : return "UMTS";
            case TelephonyManager.NETWORK_TYPE_UNKNOWN : return "unknown";
        }

        return "none";
    }

    /**
     * Gets the pretty string for this application's version.
     * @param appContext The context used to examine packages
     * @return The application's version as a pretty string
     */
    public static String getAppVersion(final Context appContext)
    {
    	PackageManager pm = appContext.getPackageManager();

    	try
    	{
    		return pm.getPackageInfo(appContext.getPackageName(), 0).versionName;
    	}
    	catch (PackageManager.NameNotFoundException	e)
    	{
    		return "unknown";
    	}
    }

    /**
     * Gets the current time, along with local timezone, formatted as a DateTime for the webservice.
     * @return a DateTime of the current local time and timezone.
     */
    public static String getTimeAsDatetime()
    {
    	SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss-00:00");
    	sdf.setTimeZone(TimeZone.getTimeZone("UTC"));
    	return sdf.format(new Date());
    }

    /***************************
     * Private Helper Functions *
     ***************************/

    /**
     * Escapes strings for YAML parser
     * @param rawString The string we want to escape.
     * @return An escaped string ready for YAML
     */
    private static String escapeString(String rawString)
    {
        StringBuffer parseString = new StringBuffer("\"");

        int startRead = 0;       // Index to start reading at
        int stopRead = 0;        // Index characters get read from and where the substring ends
        int bufferLength = rawString == null ? 0 : rawString.length();

        if (rawString == null)
        {
            return "";
        }

        // Every time we come across a " or \, append what we have so far, append a \,
        // then manage our indexes to continue where we left off.
        while (stopRead < bufferLength)
        {
            if (rawString.charAt(stopRead) == '\"' || rawString.charAt(stopRead) == '\\')
            {
                parseString.append(rawString.substring(startRead, stopRead));
                parseString.append('\\');
                startRead = stopRead;
            }
            // Skip null characters.
            else if (rawString.charAt(stopRead) == '\0')
            {
                parseString.append(rawString.substring(startRead, stopRead));
                startRead = stopRead + 1;
            }
            stopRead++;
        }
        // Append whatever is left after parsing
        parseString.append(rawString.substring(startRead, stopRead));
        // and finish with a closing "
        parseString.append('\"');
        return parseString.toString();
    }
}
