using UnityEngine;
using System;
using System.IO;
using System.Linq;
using UnityEditor;
using UnityEditor.ShaderGraph.Drawing;

namespace UnityEditor.ShaderGraph
{
    class ShaderGraphAssetPostProcessor : AssetPostprocessor
    {
        static void RegisterShaders(string[] paths)
        {
            foreach (var path in paths)
            {
                if (!path.EndsWith(ShaderGraphImporter.Extension, StringComparison.InvariantCultureIgnoreCase))
                    continue;

                var mainObj = AssetDatabase.LoadMainAssetAtPath(path);
                if (mainObj is Shader)
                    ShaderUtil.RegisterShader((Shader)mainObj);

                var objs = AssetDatabase.LoadAllAssetRepresentationsAtPath(path);
                foreach (var obj in objs)
                {
                    if (obj is Shader)
                        ShaderUtil.RegisterShader((Shader)obj);
                }
            }
        }

        static void UpdateAfterAssetChange(string[] newNames)
        {
            // This will change the title of the window.
            MaterialGraphEditWindow[] windows = Resources.FindObjectsOfTypeAll<MaterialGraphEditWindow>();
            foreach (var matGraphEditWindow in windows)
            {
                for (int i = 0; i < newNames.Length; ++i)
                {
                    if (matGraphEditWindow.selectedGuid == AssetDatabase.AssetPathToGUID(newNames[i]))
                        matGraphEditWindow.UpdateTitle();
                }
            }
        }

        static void DisplayDeletionDialog(string[] deletedAssets)
        {
            MaterialGraphEditWindow[] windows = Resources.FindObjectsOfTypeAll<MaterialGraphEditWindow>();
            foreach (var matGraphEditWindow in windows)
            {
                for (int i = 0; i < deletedAssets.Length; ++i)
                {
                    if (matGraphEditWindow.selectedGuid == AssetDatabase.AssetPathToGUID(deletedAssets[i]))
                        matGraphEditWindow.AssetWasDeleted();
                }
            }
        }

        void OnPreprocessAsset()
        {
            ShaderGraphImporter sgImporter = assetImporter as ShaderGraphImporter;
            if (sgImporter != null)
            {
                // Before importing, clear shader messages for any existing old shaders, if any.
                // This is a terrible way to do it, but currently how the shader message system works at the moment.

                // to workaround a bug with LoadAllAssetsAtPath(), which crashes if the asset has not yet been imported
                // we first call LoadAssetAtPath<>, which handles assets not yet imported by returning null
                if (AssetDatabase.LoadAssetAtPath<Shader>(assetPath) != null)
                {
                    var oldArtifacts = AssetDatabase.LoadAllAssetsAtPath(assetPath);
                    foreach (var artifact in oldArtifacts)
                    {
                        if ((artifact != null) && (artifact is Shader oldShader))
                        {
                            ShaderUtil.ClearShaderMessages(oldShader);
                        }
                    }
                }
            }
        }

        static void OnPostprocessAllAssets(string[] importedAssets, string[] deletedAssets, string[] movedAssets, string[] movedFromAssetPaths)
        {
            RegisterShaders(importedAssets);

            // Moved assets
            bool anyMovedShaders = movedAssets.Any(val => val.EndsWith(ShaderGraphImporter.Extension, StringComparison.InvariantCultureIgnoreCase));
            anyMovedShaders |= movedAssets.Any(val => val.EndsWith(ShaderSubGraphImporter.Extension, StringComparison.InvariantCultureIgnoreCase));
            if (anyMovedShaders)
                UpdateAfterAssetChange(movedAssets);

            // Deleted assets
            bool anyRemovedShaders = deletedAssets.Any(val => val.EndsWith(ShaderGraphImporter.Extension, StringComparison.InvariantCultureIgnoreCase));
            anyRemovedShaders |= deletedAssets.Any(val => val.EndsWith(ShaderSubGraphImporter.Extension, StringComparison.InvariantCultureIgnoreCase));
            if (anyRemovedShaders)
                DisplayDeletionDialog(deletedAssets);

            var changedGraphGuids = importedAssets
                .Where(x => x.EndsWith(ShaderGraphImporter.Extension, StringComparison.InvariantCultureIgnoreCase)
                || x.EndsWith(ShaderSubGraphImporter.Extension, StringComparison.InvariantCultureIgnoreCase))
                .Select(AssetDatabase.AssetPathToGUID)
                .ToList();

            MaterialGraphEditWindow[] windows = null;
            if (changedGraphGuids.Count > 0)
            {
                windows = Resources.FindObjectsOfTypeAll<MaterialGraphEditWindow>();
                foreach (var window in windows)
                {
                    if (changedGraphGuids.Contains(window.selectedGuid))
                    {
                        window.CheckForChanges();
                    }
                }
            }
            // moved or imported subgraphs or HLSL files should notify open shadergraphs that they need to handle them
            var changedFileGUIDs = movedAssets.Concat(importedAssets).Concat(deletedAssets)
                .Where(x =>
                {
                    if (x.EndsWith(ShaderSubGraphImporter.Extension, StringComparison.InvariantCultureIgnoreCase) || CustomFunctionNode.s_ValidExtensions.Contains(Path.GetExtension(x)))
                    {
                        return true;
                    }
                    else
                    {
                        var asset = AssetDatabase.GetMainAssetTypeAtPath(x);
                        return asset is null || asset.IsSubclassOf(typeof(Texture));
                    }
                })
                .Select(AssetDatabase.AssetPathToGUID)
                .Distinct()
                .ToList();

            if (changedFileGUIDs.Count > 0)
            {
                if (windows == null)
                {
                    windows = Resources.FindObjectsOfTypeAll<MaterialGraphEditWindow>();
                }
                foreach (var window in windows)
                {
                    window.ReloadSubGraphsOnNextUpdate(changedFileGUIDs);
                }
            }
        }
    }
}