using System;
using UnityEngine;
using UnityEditor;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using UnityEngine.ProBuilder;
using Object = UnityEngine.Object;

namespace UnityEditor.ProBuilder
{
    /// <summary>
    /// Helper functions for working with files and directories.
    /// </summary>
    static class FileUtility
    {
        // ProBuilder folder path.
        static string s_ProBuilderFolderPath = "Packages/com.unity.probuilder/";
        static string s_ProBuilderDataPath = "Assets/ProBuilder Data/";

        // The order is important - always search for the package manager installed version first
        static readonly string[] k_PossibleInstallDirectories = new string[]
        {
            "Packages/com.unity.probuilder/",
            "UnityPackageManager/com.unity.probuilder/",
            "Assets/",
        };

        /// <summary>
        /// Check that the directory contains a valid ProBuilder install.
        /// </summary>
        /// <param name="dir">Directory to check</param>
        /// <returns></returns>
        internal static bool ValidateProBuilderRoot(string dir)
        {
            return !string.IsNullOrEmpty(dir) &&
                Directory.Exists(dir + "/Editor/EditorCore") &&
                Directory.Exists(dir + "/Runtime/Core") &&
                Directory.Exists(dir + "/Runtime/MeshOperations");
        }

        /// <summary>
        /// Return a relative path to the ProBuilder directory. Can be in the packages cache or Assets folder.
        /// If the project is in the packman cache it is immutable.
        /// </summary>
        /// <returns></returns>
        internal static string GetProBuilderInstallDirectory()
        {
            if (ValidateProBuilderRoot(s_ProBuilderFolderPath))
                return s_ProBuilderFolderPath;

            foreach (var install in k_PossibleInstallDirectories)
            {
                s_ProBuilderFolderPath = install;

                if (ValidateProBuilderRoot(s_ProBuilderFolderPath))
                    return s_ProBuilderFolderPath;
            }

            // It's not in any of the usual haunts, start digging through Assets until we find it (likely an A$ install)
            s_ProBuilderFolderPath = FindAssetStoreProBuilderInstall();

            if (Directory.Exists(s_ProBuilderFolderPath))
                return s_ProBuilderFolderPath;

            // Things are dire. ProBuilder was nowhere to be found in the Assets directory, which means either the user
            // has renamed the folder, or something very spooky is going on.
            // Either way, just create a new ProBuilder folder in Assets and return that so at the very least
            // local preferences and the material/color palettes will still work.
            Log.Warning("Creating a new ProBuilder directory... was the ProBuilder folder renamed?\nIcons & preferences may not work in this state.");
            s_ProBuilderFolderPath = "Assets/ProBuilder";
            Directory.CreateDirectory(s_ProBuilderFolderPath);

            return s_ProBuilderFolderPath;
        }

        /// <summary>
        /// Scan the Assets directory for an install of ProBuilder.
        /// </summary>
        /// <returns></returns>
        internal static string FindAssetStoreProBuilderInstall()
        {
            string dir = null;

            string[] matches = Directory.GetDirectories("Assets", "ProBuilder", SearchOption.AllDirectories);

            foreach (var match in matches)
            {
                dir = match.Replace("\\", "/") +  "/";
                if (dir.Contains("ProBuilder") && ValidateProBuilderRoot(dir))
                    break;
            }

            return dir;
        }

        /// <summary>
        /// Get the path to the local ProBuilder/Data folder
        /// </summary>
        /// <returns></returns>
        internal static string GetLocalDataDirectory(bool initializeIfMissing = false)
        {
            if (Directory.Exists(s_ProBuilderDataPath))
                return s_ProBuilderDataPath;

            string root = GetProBuilderInstallDirectory();

            if (root.StartsWith("Assets"))
            {
                // Installed from Asset Store or manual package import
                s_ProBuilderDataPath = root + "Data/";
            }
            else
            {
                // Scan project for ProBuilder Data folder
                // none found? create one at root
                string[] matches = Directory.GetDirectories("Assets", "ProBuilder Data", SearchOption.AllDirectories);
                s_ProBuilderDataPath = matches.Length > 0 ? matches[0] : "Assets/ProBuilder Data/";
            }

            if (!Directory.Exists(s_ProBuilderDataPath) && initializeIfMissing)
                Directory.CreateDirectory(s_ProBuilderDataPath);

            return s_ProBuilderDataPath;
        }

        internal static string[] FindAssets<T>(string pattern) where T : UnityEngine.Object
        {
            return AssetDatabase.FindAssets("t:" + typeof(T).ToString() + " " + pattern);
        }

        internal static T[] FindAndLoadAssets<T>() where T : UnityEngine.Object
        {
            return AssetDatabase.FindAssets("t:" + typeof(T).ToString())
                .Select(x => AssetDatabase.LoadAssetAtPath<T>(AssetDatabase.GUIDToAssetPath(x))).ToArray();
        }

        internal static T FindAssetOfType<T>() where T : UnityEngine.Object
        {
            foreach (var i in AssetDatabase.FindAssets("t:" + typeof(T).ToString()))
            {
                T o = AssetDatabase.LoadAssetAtPath<T>(AssetDatabase.GUIDToAssetPath(i));
                if (o != null)
                    return o;
            }

            return null;
        }

        /// <summary>
        /// Get the selected directory relative to project root.
        /// </summary>
        /// <returns></returns>
        internal static string GetSelectedDirectory()
        {
            Object o = Selection.activeObject;

            if (o != null)
            {
                string path = AssetDatabase.GetAssetPath(o.GetInstanceID());

                if (!string.IsNullOrEmpty(path))
                {
                    if (Directory.Exists(path))
                        return GetRelativePath(Path.GetFullPath(path));

                    string res = Path.GetDirectoryName(path);

                    if (!string.IsNullOrEmpty(res) && Directory.Exists(res))
                        return GetRelativePath(Path.GetFullPath(res));
                }
            }

            return "Assets";
        }

        /// <summary>
        /// Get a file or folder path relative to the Unity project directory.
        /// </summary>
        /// <param name="path">File or directory path, either relative or absolute.</param>
        /// <returns>A new path relative to the current project root.</returns>
        public static string GetRelativePath(string path)
        {
            string full = Path.GetFullPath(path).Replace("\\", "/");
            string cur = Directory.GetCurrentDirectory().Replace("\\", "/");
            if (!cur.EndsWith("/"))
                cur += "/";
            return full.Replace(cur, "");
        }

        /// <summary>
        /// Check if a file or folder exists at path.
        /// </summary>
        /// <param name="path"></param>
        /// <returns></returns>
        public static bool Exists(string path)
        {
            return Directory.Exists(path) || File.Exists(path);
        }

        /// <summary>
        /// Load an internal asset relative to the ProBuilder directory.
        /// </summary>
        /// <param name="path"></param>
        /// <typeparam name="T"></typeparam>
        /// <returns></returns>
        internal static T LoadInternalAsset<T>(string path) where T : Object
        {
            string full = string.Format("{0}{1}", GetProBuilderInstallDirectory(), path);
            return Load<T>(full);
        }

        /// <summary>
        /// Fetch a default asset from path.  If not found, a new one is created.
        /// </summary>
        /// <param name="path"></param>
        /// <typeparam name="T"></typeparam>
        /// <returns></returns>
        internal static T LoadRequired<T>(string path) where T : ScriptableObject, IHasDefault
        {
            T asset = Load<T>(path);

            if (asset == null)
            {
                asset = ScriptableObject.CreateInstance<T>();

                asset.SetDefaultValues();

                UnityEditor.EditorUtility.SetDirty(asset);

                string folder = Path.GetDirectoryName(path);

                if (!Directory.Exists(folder))
                    Directory.CreateDirectory(folder);

                AssetDatabase.CreateAsset(asset, path);
            }

            return asset;
        }

        static T Load<T>(string path) where T : Object
        {
            return AssetDatabase.LoadAssetAtPath<T>(path);
        }

        /// <summary>
        /// Write contents to a file path.
        /// </summary>
        /// <param name="path"></param>
        /// <param name="contents"></param>
        [System.Obsolete("Use WriteAllText")]
        public static void WriteFile(string path, string contents)
        {
            WriteAllText(path, contents);
        }

        /// <summary>
        /// Write contents to a file path, creating a new directory if necessary.
        /// </summary>
        /// <param name="path"></param>
        /// <param name="contents"></param>
        public static void WriteAllText(string path, string contents)
        {
            string dir = Path.GetDirectoryName(path);

            if (string.IsNullOrEmpty(dir))
            {
                Log.Error("Cannot write file to \"{0}\", invalid path.", path);
                return;
            }

            if (!Directory.Exists(path))
                Directory.CreateDirectory(dir);

            File.WriteAllText(path, contents);
        }

        /// <summary>
        /// Save an image to the specified path.
        /// </summary>
        /// <param name="texture"></param>
        /// <param name="path"></param>
        /// <returns>True on success, false if operation failed.</returns>
        internal static bool SaveTexture(Texture2D texture, string path)
        {
            byte[] bytes = texture.EncodeToPNG();

            if (string.IsNullOrEmpty(path))
                return false;

            System.IO.File.WriteAllBytes(path, bytes);
            AssetDatabase.Refresh();
            return true;
        }
    }
}
