using System;
using System.Collections;
using System.Collections.Generic;
using System.Runtime.InteropServices;

using UnityEngine;

/// <summary>
/// SLS log level.
/// {VERBOSE, DEBUG, INFO, WARN, ERROR}
/// </summary>
public enum SLSLogLevel
{
    VERBOSE,
    DEBUG,
    INFO,
    WARN,
    ERROR
}

/// <summary>
/// SLS plugin credentials.
/// { instanceId, endpoint, project, accessKeyId, accessKeySeret, securityToken }
/// </summary>
public sealed class Credentials
{
    public string instanceId;
    public string endpoint;
    public string project;

    public string accessKeyId;
    public string accessKeySecret;
    public string securityToken;
}

/// <summary>
/// SLS plugin Userinfo.
/// { uid, channel, ext }
/// </summary>
public sealed class UserInfo
{
    public string uid;
    public string channel;
    public Dictionary<string, string> ext = new Dictionary<string, string>();
}

public sealed class MonoPInvokeCallbackAttribute : Attribute
{ 
    public MonoPInvokeCallbackAttribute(Type type)
    {
        
    }
}

public delegate void callback_delegate(string feature, string result);

/// <summary>
/// Unity plugin for Aliyun SLS.
/// </summary>
public sealed class Unity4SLS
{
    private static callback_delegate s_callback_delegate = null;

    /// <summary>
    /// initialize this SLS plugin.
    /// </summary>
    /// <param name="credentials"></param>
    public static void Initialize(Credentials credentials)
    {
        _InitSLS(credentials);
    }

    public static void RegisterCredentialsCallback(callback_delegate callback)
    {
        s_callback_delegate = callback;
        _RegisterCredentialsCallback(callback);
    }

    /// <summary>
    /// set the log level
    /// SLSLogLevel {VERBOSE, DEBUG, INFO, WARN, ERROR}
    /// </summary>
    /// <param name="level"></param>
    public static void SetLogLevel(SLSLogLevel level)
    {
        _SetLogLevel(level);
    }

    /// <summary>
    /// update the credentials. all Credentials fields can be updated here.
    /// </summary>
    /// <param name="credentials"></param>
    public static void SetCredentials(Credentials credentials)
    {
        _SetCredentials(credentials);
    }

    /// <summary>
    /// update the UserInfo { uid, channle, ext }
    /// </summary>
    /// <param name="info"></param>
    public static void SetUserInfo(UserInfo info)
    {
        _SetUserInfo(info);
    }

    /// <summary>
    /// set extras k-v
    /// </summary>
    /// <param name="key">string key</param>
    /// <param name="values">Dictionary value</param>
    public static void SetExtra(string key, Dictionary<string, string> values)
    {
        _SetExtra(key, values);
    }

    /// <summary>
    /// set extras k-v
    /// </summary>
    /// <param name="key">string key</param>
    /// <param name="value">string value</param>
    public static void SetExtra(string key, string value)
    {
        _SetExtra(key, value);
    }

    /// <summary>
    /// remove special extra with key
    /// </summary>
    /// <param name="key">string key</param>
    public static void RemoveExtra(string key)
    {
        _RemoveExtra(key);
    }

    /// <summary>
    /// clear all extras
    /// </summary>
    public static void ClearExtra()
    {
        _ClearExtra();
    }

    public static void ReportCustomLog(string type, string log)
    {
        _ReportCustomLog(type, log);
    }

    public static void ReportError(string stacktrace)
    {
        _ReportError(stacktrace);
    }

    public static void ReportError(string type, string stacktrace)
    {
        _ReportError(type, stacktrace);
    }

    public static void ReportError(string type, string message, string stacktrace)
    {
        _ReportError(type, message, stacktrace);
    }

    public static void ReportLuaError(string message, string stacktrace)
    {
        _ReportLuaError(message, stacktrace);
    }

    public static void ReportCSharpError(string message, string stacktrace)
    {
        _ReportCSharpError(message, stacktrace);
    }

#if UNITY_EDITOR || UNITY_STANDALONE
    #region Interface(Empty) in Editor
    private static void _InitSLS(Credentials credentials)
    {
        // empty
    }

    private static void _SetLogLevel(SLSLogLevel level)
    {
        // empty
    }

    private static void _SetCredentials(Credentials credentials)
    {
        // empty
    }

    private static void _RegisterCredentialsCallback(callback_delegate callback)
    {
        // empty
    }

    private static void _SetUserInfo(UserInfo info)
    {
        // empty
    }

    private static void _SetExtra(string key, Dictionary<string, string> values)
    {
        // empty
    }

    private static void _SetExtra(string key, string value)
    {
        // empty
    }

    private static void _RemoveExtra(string key)
    {
        // empty
    }

    private static void _ClearExtra()
    {
        // empty
    }

    public static void _ReportError(string stacktrace)
    {
       // empty
    }

    public static void _ReportCustomLog(string type, string log)
    {
        // empty
    }

    public static void _ReportError(string type, string stacktrace)
    {
        // empty
    }

    public static void _ReportError(string type, string message, string stacktrace)
    {
        // empty
    }

    public static void _ReportLuaError(string message, string stacktrace)
    {
        // empty
    }

    public static void _ReportCSharpError(string message, string stacktrace)
    {
        // empty
    }

    #endregion

#elif UNITY_ANDROID
    #region Interface for Android
    private static readonly string SLS_ANDROID_AGNET_CLASS = "com.aliyun.sls.android.plugin.unity.Unity4SLSAndroid";
    private static readonly string SLS_ANDROID_CREDENTIALS_CLASS = "com.aliyun.sls.android.core.configuration.Credentials";
    private static readonly string SLS_ANDROID_USERINFO_CLASS = "com.aliyun.sls.android.core.configuration.UserInfo";
    private static AndroidJavaClass _androidAgentClass = null;
    public static AndroidJavaClass SLSAndroidAgent
    {
        get
        {
            if (null == _androidAgentClass)
            {
                _androidAgentClass = new AndroidJavaClass(SLS_ANDROID_AGNET_CLASS);
            }
            return _androidAgentClass;
        }
    }

    private class CredentialsCallbackAdapter : AndroidJavaProxy {

        public CredentialsCallbackAdapter() : base("com.aliyun.sls.android.plugin.unity.CredentialsCallback")
        {
            // empty
        }
        
        public void onCall(string feature, string result)
        {
            Console.WriteLine ("[Unity4SLS-Android] <DEBUG> - CredentialsCallbackAdapter.onCall. feature: {0}, result: {1}", feature, result);
            if (null != s_callback_delegate)
            {
                Console.WriteLine ("[Unity4SLS-Android] <DEBUG> - CredentialsCallbackAdapter.onCall.s_callback_delegate()");
                s_callback_delegate(feature, result);
            }
        }
    }

    private static void _InitSLS(Credentials credentials)
    {
        try
        {
            AndroidJavaObject _credentials = new AndroidJavaObject(SLS_ANDROID_CREDENTIALS_CLASS);
            _credentials.Set<string>("instanceId", credentials.instanceId);
            _credentials.Set<string>("endpoint", credentials.endpoint);
            _credentials.Set<string>("project", credentials.project);
            _credentials.Set<string>("accessKeyId", credentials.accessKeyId);
            _credentials.Set<string>("accessKeySecret", credentials.accessKeySecret);
            _credentials.Set<string>("securityToken", credentials.securityToken);

            SLSAndroidAgent.CallStatic("initialize", _credentials);
        }
        catch (System.Exception)
        {
            // Console.WriteLine ("[Unity4SLS] <DEBUG> - initialize error. instanceId: {0}", credentials.instanceId);
            throw;
        }
    }

    private static void _SetLogLevel(SLSLogLevel level)
    {
        SLSAndroidAgent.CallStatic("setLogLevel", level + 2);
    }

    private static void _SetCredentials(Credentials credentials)
    {
        try
        {
            AndroidJavaObject _credentials = new AndroidJavaObject(SLS_ANDROID_CREDENTIALS_CLASS);
            _credentials.Set<string>("instanceId", credentials.instanceId);
            _credentials.Set<string>("endpoint", credentials.endpoint);
            _credentials.Set<string>("project", credentials.project);
            _credentials.Set<string>("accessKeyId", credentials.accessKeyId);
            _credentials.Set<string>("accessKeySecret", credentials.accessKeySecret);
            _credentials.Set<string>("securityToken", credentials.securityToken);

            SLSAndroidAgent.CallStatic("setCredentials", _credentials);
        }
        catch (System.Exception)
        {
            // Console.WriteLine ("[Unity4SLS] <DEBUG> - initialize error. instanceId: {0}", credentials.instanceId);
            throw;
        }
    }

    private static void _RegisterCredentialsCallback(callback_delegate callback)
    {
       SLSAndroidAgent.CallStatic("registerCredentialsCallback", new CredentialsCallbackAdapter());
    }

    private static void _SetUserInfo(UserInfo info)
    {
        try
        {
            AndroidJavaObject _userInfo = new AndroidJavaObject(SLS_ANDROID_USERINFO_CLASS);
            _userInfo.Set<string>("uid", info.uid);
            _userInfo.Set<string>("channel", info.channel);

            SLSAndroidAgent.CallStatic("setUserInfo", _userInfo);
            AndroidJavaObject _map = Dictionary2Map(info.ext);
            if (null != _map) {
                SLSAndroidAgent.CallStatic("setUserInfoExt", _map);
            }
        }
        catch (System.Exception)
        {
            // Console.WriteLine ("[Unity4SLS] <DEBUG> - initialize error. instanceId: {0}", credentials.instanceId);
            throw;
        }
    }

    private static void _SetExtra(string key, Dictionary<string, string> values)
    {
        AndroidJavaObject _map = Dictionary2Map(values);
        if (null == _map) {
            return;
        }

        SLSAndroidAgent.CallStatic("setExtra", key, values);
    }

    private static void _SetExtra(string key, string value)
    {
        SLSAndroidAgent.CallStatic("setExtra", key, value);
    }

    private static void _RemoveExtra(string key)
    {
        SLSAndroidAgent.CallStatic("removeExtra", key);
    }

    private static void _ClearExtra()
    {
        SLSAndroidAgent.CallStatic("clearExtra");
    }

    public static void _ReportCustomLog(string type, string log)
    {
        SLSAndroidAgent.CallStatic("reportCustomLog", type, log);
    }

    public static void _ReportError(string stacktrace)
    {
       SLSAndroidAgent.CallStatic("reportError", stacktrace);
    }

    public static void _ReportError(string type, string stacktrace)
    {
        SLSAndroidAgent.CallStatic("reportError", type, stacktrace);
    }

    public static void _ReportError(string type, string message, string stacktrace)
    {
        SLSAndroidAgent.CallStatic("reportError", type, message, stacktrace);
    }

    public static void _ReportLuaError(string message, string stacktrace)
    {
        SLSAndroidAgent.CallStatic("reportLuaError", message, stacktrace);
    }

    public static void _ReportCSharpError(string message, string stacktrace)
    {
        SLSAndroidAgent.CallStatic("reportCSharpError", message, stacktrace);
    }

    private static AndroidJavaObject Dictionary2Map(Dictionary<string, string> dict)
    {
        if (null == dict)
        {
            return null;
        }

        AndroidJavaObject _map = new AndroidJavaObject("java.util.LinkedHashMap");
        IntPtr putMethod = AndroidJNIHelper.GetMethodID(_map.GetRawClass(), "put", "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;");
        foreach (var entry in dict) {
            AndroidJNI.CallObjectMethod(
                _map.GetRawObject(),
                putMethod,
                AndroidJNIHelper.CreateJNIArgArray(new object[] { entry.Key, entry.Value })
            );
        }

        return _map;
    }
    #endregion

#elif UNITY_IPHONE || UNITY_IOS
    #region Interface for iOS
    private static void _InitSLS(Credentials credentials)
    {
        _SLS_InitSLS(credentials.instanceId, credentials.endpoint, credentials.project, credentials.accessKeyId, credentials.accessKeySecret, credentials.securityToken);
    }
    
    private static void _SetLogLevel(SLSLogLevel level)
    {
        _SLS_SetLogLevel((int) level + 2);
    }

    private static void _SetCredentials(Credentials credentials)
    {
        _SLS_SetCredentials(credentials.instanceId, credentials.endpoint, credentials.project, credentials.accessKeyId, credentials.accessKeySecret, credentials.securityToken);
    }

    private static void _RegisterCredentialsCallback(callback_delegate callback)
    {
        _SLS_RegisterCredentialsCallback(cs_callback);
    }

    private static void _SetUserInfo(UserInfo info)
    {
        _SLS_SetUserInfo(info.uid, info.channel);
    }

    private static void _SetExtra(string key, Dictionary<string, string> values)
    {
        // empty
    }

    private static void _SetExtra(string key, string value)
    {
        _SLS_SetExtra(key, value);
    }

    private static void _RemoveExtra(string key)
    {
        _SLS_RemoveExtra(key);
    }

    private static void _ClearExtra()
    {
        _SLS_ClearExtra();
    }
    
    public static void _ReportCustomLog(string type, string log)
    {
        _SLS_ReportCustomLog(type, log);
    }

    public static void _ReportError(string stacktrace)
    {
       _SLS_ReportError("exception", "", stacktrace);
    }

    public static void _ReportError(string type, string stacktrace)
    {
        _SLS_ReportError(type, "", stacktrace);
    }

    public static void _ReportError(string type, string message, string stacktrace)
    {
        _SLS_ReportError(type, message, stacktrace);
    }

    public static void _ReportLuaError(string message, string stacktrace)
    {
        _SLS_ReportLuaError(message, stacktrace);
    }

    public static void _ReportCSharpError(string message, string stacktrace)
    {
        _SLS_ReportCSharpError(message, stacktrace);
    }


    [DllImport("__Internal")]
    private static extern void _SLS_InitSLS(string instanceId, string endpoint, string project, string accesskeyId, string accessKeySecret, string securityToken);

    [DllImport("__Internal")]
    private static extern void _SLS_RegisterCredentialsCallback(callback_delegate callback);

    [MonoPInvokeCallback(typeof(callback_delegate))]
    private static void cs_callback(string feature, string result)
    {
        if (null != s_callback_delegate)
        {
            s_callback_delegate(feature, result);
        }
    }

    [DllImport("__Internal")]
    private static extern void _SLS_SetLogLevel(int level);

    [DllImport("__Internal")]
    private static extern void _SLS_SetCredentials(string instanceId, string endpoint, string project, string accesskeyId, string accessKeySecret, string securityToken);

    [DllImport("__Internal")]
    private static extern void _SLS_SetUserInfo(string uid, string channel);

    [DllImport("__Internal")]
    private static extern void _SLS_SetExtraOfExt(string extKey, string extValue);

    [DllImport("__Internal")]
    private static extern void _SLS_SetExtra(string key, string value);

    [DllImport("__Internal")]
    private static extern void _SLS_RemoveExtra(string key);

    [DllImport("__Internal")]
    private static extern void _SLS_ClearExtra();

    [DllImport("__Internal")]
    private static extern void _SLS_ReportCustomLog(string type, string log);

    [DllImport("__Internal")]
    private static extern void _SLS_ReportError(string type, string message, string stacktrace);

    [DllImport("__Internal")]
    private static extern void _SLS_ReportLuaError(string message, string stacktrace);

    [DllImport("__Internal")]
    private static extern void _SLS_ReportCSharpError(string message, string stacktrace);

    #endregion
#endif

    // Start is called before the first frame update
    void Start()
    {

    }

    // Update is called once per frame
    void Update()
    {

    }
}
