<commit_before>#region License
/* Teak -- Copyright (C) 2014 GoCarrot Inc.
 *
 * 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.
 */
#endregion

#region References
using System;
using System.Net;
using UnityEngine;
using System.Text;
using System.Security;
using System.Reflection;
using System.Collections;
using System.Net.Security;
using GoCarrotInc.MiniJSON;
using GoCarrotInc.Amazon.Util;
using System.Collections.Generic;
using System.Security.Cryptography;
using System.Text.RegularExpressions;
using System.Runtime.InteropServices;
using System.Security.Cryptography.X509Certificates;
#endregion

public partial class Teak
{
    /// @cond hide_from_doxygen

    #region Internal
    private enum FacebookSDKType : int
    {
        None = -1,
        OfficialUnitySDK = 0,
        JavaScriptSDK = 1
    }

    protected void getDeepLinkResult(object fbResult)
    {
        mLaunchURL = mFBResultPropertyText.GetValue(fbResult, null) as string;
    }

    Teak()
    {
        mCache = Cache.Create();
        this.InstallDate = new DateTime(1970, 1, 1, 0, 0, 0, 0).AddSeconds(mCache.InstallDate);

        // Check to see if the official Facebook SDK is being used
        mFacebookDelegateType = Type.GetType("Facebook.FacebookDelegate,IFacebook");
        if(mFacebookDelegateType != null)
        {
            Type t = Type.GetType("FB");
            mOfficialFBSDKFeedMethod = t.GetMethod("Feed", BindingFlags.Static | BindingFlags.Public);

            t = Type.GetType("FBResult,IFacebook");
            mFBResultPropertyText = t.GetProperty("Text");
            mFBResultPropertyError = t.GetProperty("Error");
            mFacebookDelegateType = Type.GetType("Facebook.FacebookDelegate,IFacebook");

            if(mFacebookDelegateType != null &&
               mOfficialFBSDKFeedMethod != null &&
               mFBResultPropertyText != null &&
               mFBResultPropertyError != null &&
               mFacebookDelegateType != null)
            {
                mFacebookSDKType = FacebookSDKType.OfficialUnitySDK;

                // Get deep link via FB SDK
                MethodInfo getDeepLink = t.GetMethod("GetDeepLink", BindingFlags.Static | BindingFlags.Public);

                if(getDeepLink != null)
                {
                    MethodInfo mi1 = typeof(Teak).GetMethod("getDeepLinkResult", BindingFlags.NonPublic | BindingFlags.Instance);
                    object fbDelegate = Delegate.CreateDelegate(mFacebookDelegateType, this, mi1);
                    getDeepLink.Invoke(null, new object[] { fbDelegate });
                }
            }
        }
#if UNITY_WEBPLAYER
        // If on Unity WebPlayer, some use the JavaScript SDK
        else
        {
            Application.ExternalEval("if(window.__teakUnityInstance == null || window.__teakUnityInstance == undefined) {" +
                                     "    window.__teakUnityInstance = UnityObject2.instances[0];" +
                                     "}" +
                                     "window.__teakUnityInstance.getUnity().SendMessage('TeakGameObject', 'assignUnityObject2Instance', 'window.__teakUnityInstance');" +
                                     "window.__teakUnityInstance.getUnity().SendMessage('TeakGameObject', 'assignLaunchURL', 'document.URL');" +
            );
        }
#endif
    }

#if UNITY_WEBPLAYER
    private void assignUnityObject2Instance(string message)
    {
        mFacebookSDKType = FacebookSDKType.JavaScriptSDK;
        mUnityObject2Instance = message;
    }

    private void assignLaunchURL(string message)
    {
        mLaunchURL = message;
    }
#endif

    private RequestResponse cachedRequestHandler(CachedRequest cachedRequest, RequestResponse callback)
    {
        return (Response ret, string errorText, Dictionary<string, object> reply) => {
                switch(ret)
                {
                    case Response.OK:
                    case Response.NotFound:
                    case Response.ParameterError:
                        cachedRequest.RemoveFromCache();
                        break;

                    default:
                        cachedRequest.AddRetryInCache();
                        break;
                }
                if(callback != null) callback(ret, errorText, reply);
        };
    }

    protected void unitySDKFeedPostCallback(object fbResult)
    {
        string result = mFBResultPropertyText.GetValue(fbResult, null) as string;

        Dictionary<string, object> reply = null;
        if(!string.IsNullOrEmpty(result))
        {
            reply = Json.Deserialize(result) as Dictionary<string, object>;
        }
        else
        {
            result = mFBResultPropertyError.GetValue(fbResult, null) as string;
            if(!string.IsNullOrEmpty(result))
            {
                reply = Json.Deserialize(result) as Dictionary<string, object>;
            }
        }
        feedPostCallbackHandler(reply);
    }

#if UNITY_WEBPLAYER
    protected void javascriptSDKFeedPostCallback(string message)
    {
        Dictionary<string, object> reply = Json.Deserialize(message) as Dictionary<string, object>;
        feedPostCallbackHandler(reply);
    }
#endif

    protected void feedPostCallbackHandler(Dictionary<string, object> reply)
    {
        string postId = null;
        if(reply != null)
        {
            postId = (reply.ContainsKey("post_id") ? reply["post_id"] as string : (reply.ContainsKey("id") ? reply["id"] as string : null));
        }

        if(postId != null)
        {
            StartCoroutine(cachedRequestCoroutine(ServiceType.Metrics, "/feed_dialog_post.json", new Dictionary<string, object>() {
                {"platform_id", postId}
            }));
        }
    }

    private IEnumerator loadMobileAdvertisingIds()
    {
        // Get attribution id so we can generate a custom audience id via graph call
        // to app_id/custom_audience_third_party_id
#if !UNITY_EDITOR && (UNITY_ANDROID || UNITY_IPHONE)
        if(string.IsNullOrEmpty(mCache.MobileAdvertisingId))
        {
#   if UNITY_ANDROID
            AndroidJavaClass unityPlayerClass = new AndroidJavaClass("com.unity3d.player.UnityPlayer");
            AndroidJavaObject currentActivity = unityPlayerClass.GetStatic<AndroidJavaObject>("currentActivity");

            // New Android SDK method
            try
            {
                AndroidJavaClass AttributionIdentifiersClass = new AndroidJavaClass("com.facebook.internal.AttributionIdentifiers");
                AndroidJavaObject attributionIdentifiers = AttributionIdentifiersClass.CallStatic<AndroidJavaObject>("getAttributionIdentifiers", new object[] { currentActivity });
                mCache.MobileAdvertisingId = attributionIdentifiers.Call<string>("getAttributionId");
                Debug.Log("Android Advertising Id via com.facebook.internal.AttributionIdentifiers: " + mCache.MobileAdvertisingId);
            }
            catch { Debug.Log("com.facebook.internal.AttributionIdentifiers is not available."); }

            // Try old Android SDK
            if(string.IsNullOrEmpty(mCache.MobileAdvertisingId))
            {
                // Old Android SDK method
                try
                {
                    AndroidJavaClass FacebookSettingsClass = new AndroidJavaClass("com.facebook.Settings");
                    AndroidJavaObject contentResolver = currentActivity.Call<AndroidJavaObject>("getContentResolver");
                    mCache.MobileAdvertisingId = FacebookSettingsClass.CallStatic<string>("getAttributionId", new object[] { contentResolver });
                    Debug.Log("Android Advertising Id via com.facebook.Settings: " + mCache.MobileAdvertisingId);
                }
                catch { Debug.Log("com.facebook.Settings is not available."); }
            }

            // Check for Google Advertising Id
            if(string.IsNullOrEmpty(mCache.MobileAdvertisingId))
            {
                try
                {
                    AndroidJavaClass GooglePlayServicesUtilClass = new AndroidJavaClass("com.google.android.gms.common.GooglePlayServicesUtil");
                    if(GooglePlayServicesUtilClass.CallStatic<int>("isGooglePlayServicesAvailable", new object[] { currentActivity }) == 0)
                    {
                        // TODO: Retrieve advertising id
                        Debug.Log("isGooglePlayServicesAvailable == TRUE");
                    }
                }
                catch { Debug.Log("Google Play Services are not available."); }
            }
#   elif UNITY_IPHONE
            // TODO: C-call in to native code
            byte[] buffer = new byte[256];
            GCHandle pinnedArray = GCHandle.Alloc(buffer, GCHandleType.Pinned);
            UIntPtr result = TeakHelper_GetAttributionId(pinnedArray.AddrOfPinnedObject(), new UIntPtr(256));
            if(!result.Equals(UIntPtr.Zero))
            {
                mCache.MobileAdvertisingId = System.Text.Encoding.UTF8.GetString(buffer);
            }
            pinnedArray.Free();
#   endif
        }

        // Resolve to a custom audience id
        if(!string.IsNullOrEmpty(mCache.MobileAdvertisingId) &&
            string.IsNullOrEmpty(mCache.CustomAudienceId))
        {
            string url = string.Format("https://graph.facebook.com/{0}/custom_audience_third_party_id?udid={1}", mFacebookAppId, mCache.MobileAdvertisingId);
            Debug.Log(url);
            UnityEngine.WWW request = new UnityEngine.WWW(url);
            yield return request;
            if(string.IsNullOrEmpty(request.error))
            {
                Dictionary<string, object> reply = Json.Deserialize(request.text) as Dictionary<string, object>;
                if(reply != null && reply.ContainsKey("custom_audience_third_party_id"))
                {
                    mCache.CustomAudienceId = reply["custom_audience_third_party_id"] as string;
                }
            }
        }
#endif
        yield return null;
    }
    #endregion

    #region Metrics
    private IEnumerator sendInstallMetricIfNeeded(float delay = 0.0f)
    {
        if(!mCache.InstallMetricSent)
        {
            if(delay > 0.0f)
            {
                yield return new WaitForSeconds(delay);
            }

            Debug.Log("Sending install metric.");
            Dictionary<string, object> payload = new Dictionary<string, object>() {<commit_msg>Temporarily remove advertiser id fetch.<commit_after>#region License
/* Teak -- Copyright (C) 2014 GoCarrot Inc.
 *
 * 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.
 */
#endregion

#region References
using System;
using System.Net;
using UnityEngine;
using System.Text;
using System.Security;
using System.Reflection;
using System.Collections;
using System.Net.Security;
using GoCarrotInc.MiniJSON;
using GoCarrotInc.Amazon.Util;
using System.Collections.Generic;
using System.Security.Cryptography;
using System.Text.RegularExpressions;
using System.Runtime.InteropServices;
using System.Security.Cryptography.X509Certificates;
#endregion

public partial class Teak
{
    /// @cond hide_from_doxygen

    #region Internal
    private enum FacebookSDKType : int
    {
        None = -1,
        OfficialUnitySDK = 0,
        JavaScriptSDK = 1
    }

    protected void getDeepLinkResult(object fbResult)
    {
        mLaunchURL = mFBResultPropertyText.GetValue(fbResult, null) as string;
    }

    Teak()
    {
        mCache = Cache.Create();
        this.InstallDate = new DateTime(1970, 1, 1, 0, 0, 0, 0).AddSeconds(mCache.InstallDate);

        // Check to see if the official Facebook SDK is being used
        mFacebookDelegateType = Type.GetType("Facebook.FacebookDelegate,IFacebook");
        if(mFacebookDelegateType != null)
        {
            Type t = Type.GetType("FB");
            mOfficialFBSDKFeedMethod = t.GetMethod("Feed", BindingFlags.Static | BindingFlags.Public);

            t = Type.GetType("FBResult,IFacebook");
            mFBResultPropertyText = t.GetProperty("Text");
            mFBResultPropertyError = t.GetProperty("Error");
            mFacebookDelegateType = Type.GetType("Facebook.FacebookDelegate,IFacebook");

            if(mFacebookDelegateType != null &&
               mOfficialFBSDKFeedMethod != null &&
               mFBResultPropertyText != null &&
               mFBResultPropertyError != null &&
               mFacebookDelegateType != null)
            {
                mFacebookSDKType = FacebookSDKType.OfficialUnitySDK;

                // Get deep link via FB SDK
                MethodInfo getDeepLink = t.GetMethod("GetDeepLink", BindingFlags.Static | BindingFlags.Public);

                if(getDeepLink != null)
                {
                    MethodInfo mi1 = typeof(Teak).GetMethod("getDeepLinkResult", BindingFlags.NonPublic | BindingFlags.Instance);
                    object fbDelegate = Delegate.CreateDelegate(mFacebookDelegateType, this, mi1);
                    getDeepLink.Invoke(null, new object[] { fbDelegate });
                }
            }
        }
#if UNITY_WEBPLAYER
        // If on Unity WebPlayer, some use the JavaScript SDK
        else
        {
            Application.ExternalEval("if(window.__teakUnityInstance == null || window.__teakUnityInstance == undefined) {" +
                                     "    window.__teakUnityInstance = UnityObject2.instances[0];" +
                                     "}" +
                                     "window.__teakUnityInstance.getUnity().SendMessage('TeakGameObject', 'assignUnityObject2Instance', 'window.__teakUnityInstance');" +
                                     "window.__teakUnityInstance.getUnity().SendMessage('TeakGameObject', 'assignLaunchURL', 'document.URL');" +
            );
        }
#endif
    }

#if UNITY_WEBPLAYER
    private void assignUnityObject2Instance(string message)
    {
        mFacebookSDKType = FacebookSDKType.JavaScriptSDK;
        mUnityObject2Instance = message;
    }

    private void assignLaunchURL(string message)
    {
        mLaunchURL = message;
    }
#endif

    private RequestResponse cachedRequestHandler(CachedRequest cachedRequest, RequestResponse callback)
    {
        return (Response ret, string errorText, Dictionary<string, object> reply) => {
                switch(ret)
                {
                    case Response.OK:
                    case Response.NotFound:
                    case Response.ParameterError:
                        cachedRequest.RemoveFromCache();
                        break;

                    default:
                        cachedRequest.AddRetryInCache();
                        break;
                }
                if(callback != null) callback(ret, errorText, reply);
        };
    }

    protected void unitySDKFeedPostCallback(object fbResult)
    {
        string result = mFBResultPropertyText.GetValue(fbResult, null) as string;

        Dictionary<string, object> reply = null;
        if(!string.IsNullOrEmpty(result))
        {
            reply = Json.Deserialize(result) as Dictionary<string, object>;
        }
        else
        {
            result = mFBResultPropertyError.GetValue(fbResult, null) as string;
            if(!string.IsNullOrEmpty(result))
            {
                reply = Json.Deserialize(result) as Dictionary<string, object>;
            }
        }
        feedPostCallbackHandler(reply);
    }

#if UNITY_WEBPLAYER
    protected void javascriptSDKFeedPostCallback(string message)
    {
        Dictionary<string, object> reply = Json.Deserialize(message) as Dictionary<string, object>;
        feedPostCallbackHandler(reply);
    }
#endif

    protected void feedPostCallbackHandler(Dictionary<string, object> reply)
    {
        string postId = null;
        if(reply != null)
        {
            postId = (reply.ContainsKey("post_id") ? reply["post_id"] as string : (reply.ContainsKey("id") ? reply["id"] as string : null));
        }

        if(postId != null)
        {
            StartCoroutine(cachedRequestCoroutine(ServiceType.Metrics, "/feed_dialog_post.json", new Dictionary<string, object>() {
                {"platform_id", postId}
            }));
        }
    }

    private IEnumerator loadMobileAdvertisingIds()
    {
#if false // TEMPORARY: Remove this functionality
        // Get attribution id so we can generate a custom audience id via graph call
        // to app_id/custom_audience_third_party_id
#if !UNITY_EDITOR && (UNITY_ANDROID || UNITY_IPHONE)
        if(string.IsNullOrEmpty(mCache.MobileAdvertisingId))
        {
#   if UNITY_ANDROID
            AndroidJavaClass unityPlayerClass = new AndroidJavaClass("com.unity3d.player.UnityPlayer");
            AndroidJavaObject currentActivity = unityPlayerClass.GetStatic<AndroidJavaObject>("currentActivity");

            // New Android SDK method
            try
            {
                AndroidJavaClass AttributionIdentifiersClass = new AndroidJavaClass("com.facebook.internal.AttributionIdentifiers");
                AndroidJavaObject attributionIdentifiers = AttributionIdentifiersClass.CallStatic<AndroidJavaObject>("getAttributionIdentifiers", new object[] { currentActivity });
                mCache.MobileAdvertisingId = attributionIdentifiers.Call<string>("getAttributionId");
                Debug.Log("Android Advertising Id via com.facebook.internal.AttributionIdentifiers: " + mCache.MobileAdvertisingId);
            }
            catch { Debug.Log("com.facebook.internal.AttributionIdentifiers is not available."); }

            // Try old Android SDK
            if(string.IsNullOrEmpty(mCache.MobileAdvertisingId))
            {
                // Old Android SDK method
                try
                {
                    AndroidJavaClass FacebookSettingsClass = new AndroidJavaClass("com.facebook.Settings");
                    AndroidJavaObject contentResolver = currentActivity.Call<AndroidJavaObject>("getContentResolver");
                    mCache.MobileAdvertisingId = FacebookSettingsClass.CallStatic<string>("getAttributionId", new object[] { contentResolver });
                    Debug.Log("Android Advertising Id via com.facebook.Settings: " + mCache.MobileAdvertisingId);
                }
                catch { Debug.Log("com.facebook.Settings is not available."); }
            }

            // Check for Google Advertising Id
            if(string.IsNullOrEmpty(mCache.MobileAdvertisingId))
            {
                try
                {
                    AndroidJavaClass GooglePlayServicesUtilClass = new AndroidJavaClass("com.google.android.gms.common.GooglePlayServicesUtil");
                    if(GooglePlayServicesUtilClass.CallStatic<int>("isGooglePlayServicesAvailable", new object[] { currentActivity }) == 0)
                    {
                        // TODO: Retrieve advertising id
                        Debug.Log("isGooglePlayServicesAvailable == TRUE");
                    }
                }
                catch { Debug.Log("Google Play Services are not available."); }
            }
#   elif UNITY_IPHONE
            // TODO: C-call in to native code
            byte[] buffer = new byte[256];
            GCHandle pinnedArray = GCHandle.Alloc(buffer, GCHandleType.Pinned);
            UIntPtr result = TeakHelper_GetAttributionId(pinnedArray.AddrOfPinnedObject(), new UIntPtr(256));
            if(!result.Equals(UIntPtr.Zero))
            {
                mCache.MobileAdvertisingId = System.Text.Encoding.UTF8.GetString(buffer);
            }
            pinnedArray.Free();
#   endif
        }

        // Resolve to a custom audience id
        if(!string.IsNullOrEmpty(mCache.MobileAdvertisingId) &&
            string.IsNullOrEmpty(mCache.CustomAudienceId))
        {
            string url = string.Format("https://graph.facebook.com/{0}/custom_audience_third_party_id?udid={1}", mFacebookAppId, mCache.MobileAdvertisingId);
            Debug.Log(url);
            UnityEngine.WWW request = new UnityEngine.WWW(url);
            yield return request;
            if(string.IsNullOrEmpty(request.error))
            {
                Dictionary<string, object> reply = Json.Deserialize(request.text) as Dictionary<string, object>;
                if(reply != null && reply.ContainsKey("custom_audience_third_party_id"))
                {
                    mCache.CustomAudienceId = reply["custom_audience_third_party_id"] as string;
                }
            }
        }
#endif

#endif // End temporary remove
        yield return null;
    }
    #endregion

    #region Metrics
    private IEnumerator sendInstallMetricIfNeeded(float delay = 0.0f)
    {
        if(!mCache.InstallMetricSent)
        {
            if(delay > 0.0f)
            {
                yield return new WaitForSeconds(delay);
            }

            Debug.Log("Sending install metric.");
            Dictionary<string, object> payload = new Dictionary<string, object>() {
{'fraction_non_alphanumeric': 0.0899909008189263, 'fraction_numerical': 0.0041856232939035485, 'mean_word_length': 2.188887276947628, 'pattern_counts': {'":': 0, '<': 39, '<?xml version=': 0, '>': 43, 'https://': 2, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 54, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '2198637', 'n_tokens_mistral': 5801, 'n_tokens_neox': 5439, 'n_words': 1196}