#if UNITY_EDITOR
using System.Collections.Generic;
using System.Collections;
using System.IO;
using System.Reflection;
using System.Text.RegularExpressions;
using System.Text;
using System.Xml;
using System;
using UnityEditor.Android;
#if UNITY_2018_1_OR_NEWER
using UnityEditor.Build;
#endif
using UnityEditor.Callbacks;
using UnityEditor;
using UnityEngine;

#if UNITY_2018_1_OR_NEWER
public class UnityWebViewPostprocessBuild : IPreprocessBuild, IPostGenerateGradleAndroidProject
#else
public class UnityWebViewPostprocessBuild
#endif
{
    private static bool nofragment = false;

    //// for android/unity 2018.1 or newer
    //// cf. https://forum.unity.com/threads/android-hardwareaccelerated-is-forced-false-in-all-activities.532786/
    //// cf. https://github.com/Over17/UnityAndroidManifestCallback

#if UNITY_2018_1_OR_NEWER
    public void OnPreprocessBuild(BuildTarget buildTarget, string path) {
        if (buildTarget == BuildTarget.Android) {
            var dev = "Packages/net.gree.unity-webview/Assets/Plugins/Android/WebViewPlugin-development.aar.tmpl";
            var rel = "Packages/net.gree.unity-webview/Assets/Plugins/Android/WebViewPlugin-release.aar.tmpl";
            if (!File.Exists(dev) || !File.Exists(rel)) {
                dev = "Assets/Plugins/Android/WebViewPlugin-development.aar.tmpl";
                rel = "Assets/Plugins/Android/WebViewPlugin-release.aar.tmpl";
            }
            var src = (EditorUserBuildSettings.development) ? dev : rel;
            //Directory.CreateDirectory("Temp/StagingArea/aar");
            //File.Copy(src, "Temp/StagingArea/aar/WebViewPlugin.aar", true);
            Directory.CreateDirectory("Assets/Plugins/Android");
            File.Copy(src, "Assets/Plugins/Android/WebViewPlugin.aar", true);
        }
    }

    public void OnPostGenerateGradleAndroidProject(string basePath) {
        var changed = false;
        var androidManifest = new AndroidManifest(GetManifestPath(basePath));
        if (!nofragment) {
            changed = (androidManifest.AddFileProvider(basePath) || changed);
            {
                var path = GetBuildGradlePath(basePath);
                var lines0 = File.ReadAllText(path).Replace("\r\n", "\n").Replace("\r", "\n").Split(new[]{'\n'});
                {
                    var lines = new List<string>();
                    var independencies = false;
                    foreach (var line in lines0) {
                        if (line == "dependencies {") {
                            independencies = true;
                        } else if (independencies && line == "}") {
                            independencies = false;
                            lines.Add("    implementation 'androidx.core:core:1.6.0'");
                        } else if (independencies) {
                            if (line.Contains("implementation(name: 'core")
                                || line.Contains("implementation(name: 'androidx.core.core")
                                || line.Contains("implementation 'androidx.core:core")) {
                                break;
                            }
                        }
                        lines.Add(line);
                    }
                    if (lines.Count > lines0.Length) {
                        File.WriteAllText(path, string.Join("\n", lines) + "\n");
                    }
                }
            }
            {
                var path = GetGradlePropertiesPath(basePath);
                var lines0 = "";
                var lines = "";
                if (File.Exists(path)) {
                    lines0 = File.ReadAllText(path).Replace("\r\n", "\n").Replace("\r", "\n") + "\n";
                    lines = lines0;
                }
                if (!lines.Contains("android.useAndroidX=true")) {
                    lines += "android.useAndroidX=true\n";
                }
                if (!lines.Contains("android.enableJetifier=true")) {
                    lines += "android.enableJetifier=true\n";
                }
                if (lines != lines0) {
                    File.WriteAllText(path, lines);
                }
            }
        }
        changed = (androidManifest.SetExported(true) || changed);
        changed = (androidManifest.SetWindowSoftInputMode("adjustPan") || changed);
        changed = (androidManifest.SetHardwareAccelerated(true) || changed);
#if UNITYWEBVIEW_ANDROID_USES_CLEARTEXT_TRAFFIC
        changed = (androidManifest.SetUsesCleartextTraffic(true) || changed);
#endif
#if UNITYWEBVIEW_ANDROID_ENABLE_CAMERA
        changed = (androidManifest.AddCamera() || changed);
        changed = (androidManifest.AddGallery() || changed);
#endif
#if UNITYWEBVIEW_ANDROID_ENABLE_MICROPHONE
        changed = (androidManifest.AddMicrophone() || changed);
#endif
        if (changed) {
            androidManifest.Save();
            Debug.Log("unitywebview: adjusted AndroidManifest.xml.");
        }
    }
#endif

    public int callbackOrder {
        get {
            return 1;
        }
    }

    private string GetManifestPath(string basePath) {
        var pathBuilder = new StringBuilder(basePath);
        pathBuilder.Append(Path.DirectorySeparatorChar).Append("src");
        pathBuilder.Append(Path.DirectorySeparatorChar).Append("main");
        pathBuilder.Append(Path.DirectorySeparatorChar).Append("AndroidManifest.xml");
        return pathBuilder.ToString();
    }

    private string GetBuildGradlePath(string basePath) {
        var pathBuilder = new StringBuilder(basePath);
        pathBuilder.Append(Path.DirectorySeparatorChar).Append("build.gradle");
        return pathBuilder.ToString();
    }

    private string GetGradlePropertiesPath(string basePath) {
        var pathBuilder = new StringBuilder(basePath);
        if (basePath.EndsWith("unityLibrary")) {
            pathBuilder.Append(Path.DirectorySeparatorChar).Append("..");
        }
        pathBuilder.Append(Path.DirectorySeparatorChar).Append("gradle.properties");
        return pathBuilder.ToString();
    }

    //// for others

    [PostProcessBuild(100)]
    public static void OnPostprocessBuild(BuildTarget buildTarget, string path) {
#if UNITY_2018_1_OR_NEWER
        try {
            File.Delete("Assets/Plugins/Android/WebViewPlugin.aar");
            File.Delete("Assets/Plugins/Android/WebViewPlugin.aar.meta");
            Directory.Delete("Assets/Plugins/Android");
            File.Delete("Assets/Plugins/Android.meta");
            Directory.Delete("Assets/Plugins");
            File.Delete("Assets/Plugins.meta");
        } catch (Exception) {
        }
#else
        if (buildTarget == BuildTarget.Android) {
            string manifest = Path.Combine(Application.dataPath, "Plugins/Android/AndroidManifest.xml");
            if (!File.Exists(manifest)) {
                string manifest0 = Path.Combine(Application.dataPath, "../Temp/StagingArea/AndroidManifest-main.xml");
                if (!File.Exists(manifest0)) {
                    Debug.LogError("unitywebview: cannot find both Assets/Plugins/Android/AndroidManifest.xml and Temp/StagingArea/AndroidManifest-main.xml. please build the app to generate Assets/Plugins/Android/AndroidManifest.xml and then rebuild it again.");
                    return;
                } else {
                    File.Copy(manifest0, manifest, true);
                }
            }
            var changed = false;
            if (EditorUserBuildSettings.development) {
                if (!File.Exists("Assets/Plugins/Android/WebView.aar")
                    || !File.ReadAllBytes("Assets/Plugins/Android/WebView.aar").SequenceEqual(File.ReadAllBytes("Assets/Plugins/Android/WebViewPlugin-development.aar.tmpl"))) {
                    File.Copy("Assets/Plugins/Android/WebViewPlugin-development.aar.tmpl", "Assets/Plugins/Android/WebView.aar", true);
                    changed = true;
                }
            } else {
                if (!File.Exists("Assets/Plugins/Android/WebView.aar")
                    || !File.ReadAllBytes("Assets/Plugins/Android/WebView.aar").SequenceEqual(File.ReadAllBytes("Assets/Plugins/Android/WebViewPlugin-release.aar.tmpl"))) {
                    File.Copy("Assets/Plugins/Android/WebViewPlugin-release.aar.tmpl", "Assets/Plugins/Android/WebView.aar", true);
                    changed = true;
                }
            }
            var androidManifest = new AndroidManifest(manifest);
            if (!nofragment) {
                changed = (androidManifest.AddFileProvider("Assets/Plugins/Android") || changed);
                var files = Directory.GetFiles("Assets/Plugins/Android/");
                var found = false;
                foreach (var file in files) {
                    if (Regex.IsMatch(file, @"^Assets/Plugins/Android/(androidx\.core\.)?core-.*.aar$")) {
                        found = true;
                        break;
                    }
                }
                if (!found) {
                    foreach (var file in files) {
                        var match = Regex.Match(file, @"^Assets/Plugins/Android/(core.*.aar).tmpl$");
                        if (match.Success) {
                            var name = match.Groups[1].Value;
                            File.Copy(file, "Assets/Plugins/Android/" + name, true);
                            break;
                        }
                    }
                }
            }
            changed = (androidManifest.SetWindowSoftInputMode("adjustPan") || changed);
            changed = (androidManifest.SetHardwareAccelerated(true) || changed);
#if UNITYWEBVIEW_ANDROID_USES_CLEARTEXT_TRAFFIC
            changed = (androidManifest.SetUsesCleartextTraffic(true) || changed);
#endif
#if UNITYWEBVIEW_ANDROID_ENABLE_CAMERA
            changed = (androidManifest.AddCamera() || changed);
            changed = (androidManifest.AddGallery() || changed);
#endif
#if UNITYWEBVIEW_ANDROID_ENABLE_MICROPHONE
            changed = (androidManifest.AddMicrophone() || changed);
#endif
#if UNITY_5_6_0 || UNITY_5_6_1
            changed = (androidManifest.SetActivityName("net.gree.unitywebview.CUnityPlayerActivity") || changed);
#endif
            if (changed) {
                androidManifest.Save();
                Debug.LogError("unitywebview: adjusted AndroidManifest.xml and/or WebView.aar. Please rebuild the app.");
            }
        }
#endif
        if (buildTarget == BuildTarget.iOS) {
            string projPath = path + "/Unity-iPhone.xcodeproj/project.pbxproj";
            var type = Type.GetType("UnityEditor.iOS.Xcode.PBXProject, UnityEditor.iOS.Extensions.Xcode");
            if (type == null)
            {
                Debug.LogError("unitywebview: failed to get PBXProject. please install iOS build support.");
                return;
            }
            var src = File.ReadAllText(projPath);
            //dynamic proj = type.GetConstructor(Type.EmptyTypes).Invoke(null);
            var proj = type.GetConstructor(Type.EmptyTypes).Invoke(null);
            //proj.ReadFromString(src);
            {
                var method = type.GetMethod("ReadFromString");
                method.Invoke(proj, new object[]{src});
            }
            var target = "";
#if UNITY_2019_3_OR_NEWER
            //target = proj.GetUnityFrameworkTargetGuid();
            {
                var method = type.GetMethod("GetUnityFrameworkTargetGuid");
                target = (string)method.Invoke(proj, null);
            }
#else
            //target = proj.TargetGuidByName("Unity-iPhone");
            {
                var method = type.GetMethod("TargetGuidByName");
                target = (string)method.Invoke(proj, new object[]{"Unity-iPhone"});
            }
#endif
            //proj.AddFrameworkToProject(target, "WebKit.framework", false);
            {
                var method = type.GetMethod("AddFrameworkToProject");
                method.Invoke(proj, new object[]{target, "WebKit.framework", false});
            }
            var cflags = "";
            if (EditorUserBuildSettings.development) {
                cflags += " -DUNITYWEBVIEW_DEVELOPMENT";
            }
#if UNITYWEBVIEW_IOS_ALLOW_FILE_URLS
            cflags += " -DUNITYWEBVIEW_IOS_ALLOW_FILE_URLS";
#endif
            cflags = cflags.Trim();
            if (!string.IsNullOrEmpty(cflags)) {
                // proj.AddBuildProperty(target, "OTHER_LDFLAGS", cflags);
                var method = type.GetMethod("AddBuildProperty", new Type[]{typeof(string), typeof(string), typeof(string)});
                method.Invoke(proj, new object[]{target, "OTHER_CFLAGS", cflags});
            }
            var dst = "";
            //dst = proj.WriteToString();
            {
                var method = type.GetMethod("WriteToString");
                dst = (string)method.Invoke(proj, null);
            }
            File.WriteAllText(projPath, dst);
        }
    }
}

internal class AndroidXmlDocument : XmlDocument {
    private string m_Path;
    protected XmlNamespaceManager nsMgr;
    public readonly string AndroidXmlNamespace = "http://schemas.android.com/apk/res/android";

    public AndroidXmlDocument(string path) {
        m_Path = path;
        using (var reader = new XmlTextReader(m_Path)) {
            reader.Read();
            Load(reader);
        }
        nsMgr = new XmlNamespaceManager(NameTable);
        nsMgr.AddNamespace("android", AndroidXmlNamespace);
    }

    public string Save() {
        return SaveAs(m_Path);
    }

    public string SaveAs(string path) {
        using (var writer = new XmlTextWriter(path, new UTF8Encoding(false))) {
            writer.Formatting = Formatting.Indented;
            Save(writer);
        }
        return path;
    }
}

internal class AndroidManifest : AndroidXmlDocument {
    private readonly XmlElement ManifestElement;
    private readonly XmlElement ApplicationElement;

    public AndroidManifest(string path) : base(path) {
        ManifestElement = SelectSingleNode("/manifest") as XmlElement;
        ApplicationElement = SelectSingleNode("/manifest/application") as XmlElement;
    }

    private XmlAttribute CreateAndroidAttribute(string key, string value) {
        XmlAttribute attr = CreateAttribute("android", key, AndroidXmlNamespace);
        attr.Value = value;
        return attr;
    }

    internal XmlNode GetActivityWithLaunchIntent() {
        return
            SelectSingleNode(
                "/manifest/application/activity[intent-filter/action/@android:name='android.intent.action.MAIN' and "
                + "intent-filter/category/@android:name='android.intent.category.LAUNCHER']",
                nsMgr);
    }

    internal bool SetUsesCleartextTraffic(bool enabled) {
        // android:usesCleartextTraffic
        bool changed = false;
        if (ApplicationElement.GetAttribute("usesCleartextTraffic", AndroidXmlNamespace) != ((enabled) ? "true" : "false")) {
            ApplicationElement.SetAttribute("usesCleartextTraffic", AndroidXmlNamespace, (enabled) ? "true" : "false");
            changed = true;
        }
        return changed;
    }

    // for api level 33
    internal bool SetExported(bool enabled) {
        bool changed = false;
        var activity = GetActivityWithLaunchIntent() as XmlElement;
        if (activity.GetAttribute("exported", AndroidXmlNamespace) != ((enabled) ? "true" : "false")) {
            activity.SetAttribute("exported", AndroidXmlNamespace, (enabled) ? "true" : "false");
            changed = true;
        }
        return changed;
    }

    internal bool SetWindowSoftInputMode(string mode) {
        bool changed = false;
        var activity = GetActivityWithLaunchIntent() as XmlElement;
        if (activity.GetAttribute("windowSoftInputMode", AndroidXmlNamespace) != mode) {
            activity.SetAttribute("windowSoftInputMode", AndroidXmlNamespace, mode);
            changed = true;
        }
        return changed;
    }

    internal bool SetHardwareAccelerated(bool enabled) {
        bool changed = false;
        var activity = GetActivityWithLaunchIntent() as XmlElement;
        if (activity.GetAttribute("hardwareAccelerated", AndroidXmlNamespace) != ((enabled) ? "true" : "false")) {
            activity.SetAttribute("hardwareAccelerated", AndroidXmlNamespace, (enabled) ? "true" : "false");
            changed = true;
        }
        return changed;
    }

    internal bool SetActivityName(string name) {
        bool changed = false;
        var activity = GetActivityWithLaunchIntent() as XmlElement;
        if (activity.GetAttribute("name", AndroidXmlNamespace) != name) {
            activity.SetAttribute("name", AndroidXmlNamespace, name);
            changed = true;
        }
        return changed;
    }

    internal bool AddFileProvider(string basePath) {
        bool changed = false;
        var authorities = PlayerSettings.applicationIdentifier + ".unitywebview.fileprovider";
        if (SelectNodes("/manifest/application/provider[@android:authorities='" + authorities + "']", nsMgr).Count == 0) {
            var elem = CreateElement("provider");
            elem.Attributes.Append(CreateAndroidAttribute("name", "androidx.core.content.FileProvider"));
            elem.Attributes.Append(CreateAndroidAttribute("authorities", authorities));
            elem.Attributes.Append(CreateAndroidAttribute("exported", "false"));
            elem.Attributes.Append(CreateAndroidAttribute("grantUriPermissions", "true"));
            var meta = CreateElement("meta-data");
            meta.Attributes.Append(CreateAndroidAttribute("name", "android.support.FILE_PROVIDER_PATHS"));
            meta.Attributes.Append(CreateAndroidAttribute("resource", "@xml/unitywebview_file_provider_paths"));
            elem.AppendChild(meta);
            ApplicationElement.AppendChild(elem);
            changed = true;
            var xml = GetFileProviderSettingPath(basePath);
            if (!File.Exists(xml)) {
                Directory.CreateDirectory(Path.GetDirectoryName(xml));
                File.WriteAllText(
                    xml,
                    "<paths xmlns:android=\"http://schemas.android.com/apk/res/android\">\n" +
                    "  <external-path name=\"unitywebview_file_provider_images\" path=\".\"/>\n" +
                    "</paths>\n");
            }
        }
        return changed;
    }

    private string GetFileProviderSettingPath(string basePath) {
        var pathBuilder = new StringBuilder(basePath);
        pathBuilder.Append(Path.DirectorySeparatorChar).Append("src");
        pathBuilder.Append(Path.DirectorySeparatorChar).Append("main");
        pathBuilder.Append(Path.DirectorySeparatorChar).Append("res");
        pathBuilder.Append(Path.DirectorySeparatorChar).Append("xml");
        pathBuilder.Append(Path.DirectorySeparatorChar).Append("unitywebview_file_provider_paths.xml");
        return pathBuilder.ToString();
    }

    internal bool AddCamera() {
        bool changed = false;
        if (SelectNodes("/manifest/uses-permission[@android:name='android.permission.CAMERA']", nsMgr).Count == 0) {
            var elem = CreateElement("uses-permission");
            elem.Attributes.Append(CreateAndroidAttribute("name", "android.permission.CAMERA"));
            ManifestElement.AppendChild(elem);
            changed = true;
        }
        if (SelectNodes("/manifest/uses-feature[@android:name='android.hardware.camera']", nsMgr).Count == 0) {
            var elem = CreateElement("uses-feature");
            elem.Attributes.Append(CreateAndroidAttribute("name", "android.hardware.camera"));
            ManifestElement.AppendChild(elem);
            changed = true;
        }
        // cf. https://developer.android.com/training/data-storage/shared/media#media-location-permission
        if (SelectNodes("/manifest/uses-permission[@android:name='android.permission.ACCESS_MEDIA_LOCATION']", nsMgr).Count == 0) {
            var elem = CreateElement("uses-permission");
            elem.Attributes.Append(CreateAndroidAttribute("name", "android.permission.ACCESS_MEDIA_LOCATION"));
            ManifestElement.AppendChild(elem);
            changed = true;
        }
        // cf. https://developer.android.com/training/package-visibility/declaring
        if (SelectNodes("/manifest/queries", nsMgr).Count == 0) {
            var elem = CreateElement("queries");
            ManifestElement.AppendChild(elem);
            changed = true;
        }
        if (SelectNodes("/manifest/queries/intent/action[@android:name='android.media.action.IMAGE_CAPTURE']", nsMgr).Count == 0) {
            var action = CreateElement("action");
            action.Attributes.Append(CreateAndroidAttribute("name", "android.media.action.IMAGE_CAPTURE"));
            var intent = CreateElement("intent");
            intent.AppendChild(action);
            var queries = SelectSingleNode("/manifest/queries") as XmlElement;
            queries.AppendChild(intent);
            changed = true;
        }
        return changed;
    }

    internal bool AddGallery() {
        bool changed = false;
        // for api level 33
        if (SelectNodes("/manifest/uses-permission[@android:name='android.permission.READ_MEDIA_IMAGES']", nsMgr).Count == 0) {
            var elem = CreateElement("uses-permission");
            elem.Attributes.Append(CreateAndroidAttribute("name", "android.permission.READ_MEDIA_IMAGES"));
            ManifestElement.AppendChild(elem);
            changed = true;
        }
        if (SelectNodes("/manifest/uses-permission[@android:name='android.permission.READ_MEDIA_VIDEO']", nsMgr).Count == 0) {
            var elem = CreateElement("uses-permission");
            elem.Attributes.Append(CreateAndroidAttribute("name", "android.permission.READ_MEDIA_VIDEO"));
            ManifestElement.AppendChild(elem);
            changed = true;
        }
        if (SelectNodes("/manifest/uses-permission[@android:name='android.permission.READ_MEDIA_AUDIO']", nsMgr).Count == 0) {
            var elem = CreateElement("uses-permission");
            elem.Attributes.Append(CreateAndroidAttribute("name", "android.permission.READ_MEDIA_AUDIO"));
            ManifestElement.AppendChild(elem);
            changed = true;
        }
        // cf. https://developer.android.com/training/package-visibility/declaring
        if (SelectNodes("/manifest/queries", nsMgr).Count == 0) {
            var elem = CreateElement("queries");
            ManifestElement.AppendChild(elem);
            changed = true;
        }
        if (SelectNodes("/manifest/queries/intent/action[@android:name='android.media.action.GET_CONTENT']", nsMgr).Count == 0) {
            var action = CreateElement("action");
            action.Attributes.Append(CreateAndroidAttribute("name", "android.media.action.GET_CONTENT"));
            var intent = CreateElement("intent");
            intent.AppendChild(action);
            var queries = SelectSingleNode("/manifest/queries") as XmlElement;
            queries.AppendChild(intent);
            changed = true;
        }
        return changed;
    }

    internal bool AddMicrophone() {
        bool changed = false;
        if (SelectNodes("/manifest/uses-permission[@android:name='android.permission.MICROPHONE']", nsMgr).Count == 0) {
            var elem = CreateElement("uses-permission");
            elem.Attributes.Append(CreateAndroidAttribute("name", "android.permission.MICROPHONE"));
            ManifestElement.AppendChild(elem);
            changed = true;
        }
        if (SelectNodes("/manifest/uses-feature[@android:name='android.hardware.microphone']", nsMgr).Count == 0) {
            var elem = CreateElement("uses-feature");
            elem.Attributes.Append(CreateAndroidAttribute("name", "android.hardware.microphone"));
            ManifestElement.AppendChild(elem);
            changed = true;
        }
        // cf. https://github.com/gree/unity-webview/issues/679
        // cf. https://github.com/fluttercommunity/flutter_webview_plugin/issues/138#issuecomment-559307558
        // cf. https://stackoverflow.com/questions/38917751/webview-webrtc-not-working/68024032#68024032
        // cf. https://stackoverflow.com/questions/40236925/allowing-microphone-accesspermission-in-webview-android-studio-java/47410311#47410311
        if (SelectNodes("/manifest/uses-permission[@android:name='android.permission.MODIFY_AUDIO_SETTINGS']", nsMgr).Count == 0) {
            var elem = CreateElement("uses-permission");
            elem.Attributes.Append(CreateAndroidAttribute("name", "android.permission.MODIFY_AUDIO_SETTINGS"));
            ManifestElement.AppendChild(elem);
            changed = true;
        }
        if (SelectNodes("/manifest/uses-permission[@android:name='android.permission.RECORD_AUDIO']", nsMgr).Count == 0) {
            var elem = CreateElement("uses-permission");
            elem.Attributes.Append(CreateAndroidAttribute("name", "android.permission.RECORD_AUDIO"));
            ManifestElement.AppendChild(elem);
            changed = true;
        }
        return changed;
    }
}
#endif
